remove: md

This commit is contained in:
ctwj
2025-07-17 02:18:21 +08:00
parent c6147d6c2c
commit b2644accdf
34 changed files with 0 additions and 5714 deletions

View File

@@ -1,182 +0,0 @@
# 网盘工厂模式实现总结
## 完成的工作
我已经成功将您提供的PHP代码翻译为Go代码并实现了一个优雅的工厂模式来处理不同的网盘服务。以下是完成的主要工作
### 1. 核心架构设计
#### 工厂模式核心文件
- **`utils/pan_factory.go`** - 网盘工厂核心,定义了服务类型、配置结构和工厂方法
- **`utils/base_pan.go`** - 基础网盘服务提供通用的HTTP请求和配置管理
- **`utils/quark_pan.go`** - 夸克网盘服务实现(完整实现)
- **`utils/alipan.go`** - 阿里云盘服务实现(完整实现)
- **`utils/baidu_pan.go`** - 百度网盘服务实现(基础框架)
- **`utils/uc_pan.go`** - UC网盘服务实现基础框架
#### 测试和示例
- **`utils/pan_factory_test.go`** - 完整的单元测试
- **`utils/pan_example.go`** - 使用示例
- **`utils/README_PAN_FACTORY.md`** - 详细的使用说明文档
### 2. 设计模式特点
#### 工厂模式
- 通过 `PanFactory` 根据URL自动识别并创建对应的网盘服务
- 支持通过服务类型直接创建服务实例
- 易于扩展新的网盘服务
#### 策略模式
- 每个网盘服务实现相同的接口 `PanService`
- 根据不同的服务类型执行不同的处理策略
- 代码结构清晰,职责分离
#### 模板方法模式
- `BasePanService` 提供通用的HTTP请求方法
- 具体服务类继承基础服务,专注于业务逻辑实现
### 3. 支持的服务类型
| 服务类型 | 状态 | 说明 |
|---------|------|------|
| 夸克网盘 (Quark) | ✅ 完整实现 | 包含完整的转存、分享、文件管理功能 |
| 阿里云盘 (Alipan) | ✅ 完整实现 | 包含完整的转存、分享、文件管理功能 |
| 百度网盘 (BaiduPan) | 🔄 基础框架 | 提供接口框架,等待具体实现 |
| UC网盘 (UC) | 🔄 基础框架 | 提供接口框架,等待具体实现 |
### 4. 核心功能
#### URL解析和识别
```go
// 自动识别服务类型
serviceType := ExtractServiceType(url)
// 提取分享ID
shareID, serviceType := ExtractShareId(url)
```
#### 工厂创建服务
```go
factory := NewPanFactory()
panService, err := factory.CreatePanService(url, config)
```
#### 统一的服务接口
```go
// 转存分享链接
result, err := panService.Transfer(shareID)
// 获取文件列表
result, err := panService.GetFiles(pdirFid)
// 删除文件
result, err := panService.DeleteFiles(fileList)
```
### 5. 集成到现有系统
#### 定时任务集成
`utils/scheduler.go``convertReadyResourceToResource` 函数中已经集成了工厂模式:
```go
func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyResource) error {
// 使用工厂模式创建对应的网盘服务
factory := NewPanFactory()
config := &PanConfig{...}
panService, err := factory.CreatePanService(readyResource.URL, config)
if err != nil {
return err
}
// 根据服务类型进行不同处理
switch serviceType {
case Quark:
// 夸克网盘处理逻辑
case Alipan:
// 阿里云盘处理逻辑
// ... 其他服务类型
}
return nil
}
```
### 6. 错误处理和兼容性
#### 解决冲突
- 修复了与现有 `url_checker.go` 的常量冲突
- 重命名了重复的函数和常量
- 保持了向后兼容性
#### 错误处理
- 统一的错误返回格式 `*TransferResult`
- 详细的错误信息和状态码
- 支持重试机制和超时控制
### 7. 测试验证
#### 单元测试
- ✅ 服务类型识别测试
- ✅ 分享ID提取测试
- ✅ 工厂创建测试
- ✅ 服务类型字符串转换测试
#### 编译测试
- ✅ 项目编译成功
- ✅ 无语法错误
- ✅ 无依赖冲突
### 8. 扩展性
#### 添加新服务
要添加新的网盘服务,只需要:
1. 创建新的服务实现文件(如 `utils/new_pan.go`
2. 实现 `PanService` 接口
3. 在工厂中添加新的服务类型
4. 更新URL模式匹配
#### 配置管理
- 支持通过 `PanConfig` 进行灵活配置
- 可以轻松添加新的配置项
- 支持环境变量和配置文件
### 9. 性能优化
#### HTTP客户端优化
- 连接池管理
- 超时控制
- 重试机制
- 请求头缓存
#### 内存管理
- 合理的对象生命周期
- 避免内存泄漏
- 高效的字符串处理
### 10. 使用建议
#### 生产环境使用
1. 配置适当的超时时间
2. 实现token缓存机制
3. 添加监控和日志
4. 配置错误告警
#### 开发环境使用
1. 使用测试用例验证功能
2. 模拟网络异常情况
3. 测试不同URL格式
4. 验证错误处理逻辑
## 总结
这个工厂模式实现具有以下优势:
1. **优雅的设计** - 使用工厂模式和策略模式,代码结构清晰
2. **易于扩展** - 可以轻松添加新的网盘服务支持
3. **统一接口** - 所有服务使用相同的接口,便于维护
4. **错误处理** - 完善的错误处理和重试机制
5. **测试覆盖** - 完整的单元测试确保代码质量
6. **向后兼容** - 不影响现有功能,平滑集成
这个实现为您的网盘资源管理系统提供了一个强大、灵活、可扩展的基础架构。

View File

@@ -1,284 +0,0 @@
# 网盘工厂模式使用说明
## 概述
本项目实现了一个优雅的网盘服务工厂模式,用于处理不同网盘平台的资源转存和文件管理。该设计采用了工厂模式和策略模式,使得代码结构清晰、易于扩展。
## 架构设计
### 核心组件
1. **PanFactory** - 网盘工厂,负责创建对应的网盘服务实例
2. **PanService** - 网盘服务接口,定义所有网盘服务必须实现的方法
3. **BasePanService** - 基础网盘服务提供通用的HTTP请求和配置管理
4. **具体实现类** - 各网盘平台的具体实现
### 支持的服务类型
- **Quark** - 夸克网盘
- **Alipan** - 阿里云盘
- **BaiduPan** - 百度网盘(基础框架)
- **UC** - UC网盘基础框架
## 使用方法
### 基本使用
```go
package main
import (
"log"
"res_db/utils"
)
func main() {
// 创建工厂实例
factory := utils.NewPanFactory()
// 准备配置
config := &utils.PanConfig{
URL: "https://pan.quark.cn/s/123456789",
Code: "",
IsType: 0, // 0: 转存并分享后的资源信息, 1: 直接获取资源信息
ExpiredType: 1, // 1: 分享永久, 2: 临时
AdFid: "",
Stoken: "",
}
// 创建对应的网盘服务
panService, err := factory.CreatePanService(config.URL, config)
if err != nil {
log.Fatalf("创建网盘服务失败: %v", err)
}
// 提取分享ID
shareID, serviceType := utils.ExtractShareId(config.URL)
if serviceType == utils.NotFound {
log.Fatal("不支持的链接格式")
}
// 执行转存
result, err := panService.Transfer(shareID)
if err != nil {
log.Fatalf("转存失败: %v", err)
}
if result.Success {
log.Printf("转存成功: %s", result.Message)
// 处理转存结果
if resultData, ok := result.Data.(map[string]interface{}); ok {
title := resultData["title"].(string)
shareURL := resultData["shareUrl"].(string)
log.Printf("标题: %s", title)
log.Printf("分享链接: %s", shareURL)
}
} else {
log.Printf("转存失败: %s", result.Message)
}
}
```
### 在定时任务中使用
`utils/scheduler.go``convertReadyResourceToResource` 函数中已经集成了工厂模式:
```go
func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyResource) error {
// 使用工厂模式创建对应的网盘服务
factory := NewPanFactory()
config := &PanConfig{
URL: readyResource.URL,
Code: "",
IsType: 0,
ExpiredType: 1,
AdFid: "",
Stoken: "",
}
panService, err := factory.CreatePanService(readyResource.URL, config)
if err != nil {
return err
}
// 根据服务类型进行不同处理
shareID, serviceType := ExtractShareId(readyResource.URL)
switch serviceType {
case Quark:
// 夸克网盘处理逻辑
case Alipan:
// 阿里云盘处理逻辑
// ... 其他服务类型
}
return nil
}
```
## 扩展新的网盘服务
### 1. 创建新的服务实现
```go
// 在 utils/ 目录下创建新文件,例如 new_pan.go
package utils
// NewPanService 新网盘服务
type NewPanService struct {
*BasePanService
}
// NewNewPanService 创建新网盘服务
func NewNewPanService(config *PanConfig) *NewPanService {
service := &NewPanService{
BasePanService: NewBasePanService(config),
}
// 设置请求头
service.SetHeaders(map[string]string{
"User-Agent": "Mozilla/5.0 ...",
// 其他必要的请求头
})
return service
}
// GetServiceType 获取服务类型
func (n *NewPanService) GetServiceType() ServiceType {
return NewPan // 需要在 ServiceType 中定义新的类型
}
// Transfer 转存分享链接
func (n *NewPanService) Transfer(shareID string) (*TransferResult, error) {
// 实现转存逻辑
return SuccessResult("转存成功", data), nil
}
// GetFiles 获取文件列表
func (n *NewPanService) GetFiles(pdirFid string) (*TransferResult, error) {
// 实现文件列表获取逻辑
return SuccessResult("获取成功", files), nil
}
// DeleteFiles 删除文件
func (n *NewPanService) DeleteFiles(fileList []string) (*TransferResult, error) {
// 实现文件删除逻辑
return SuccessResult("删除成功", nil), nil
}
```
### 2. 更新工厂
`utils/pan_factory.go` 中添加新的服务类型:
```go
const (
Quark ServiceType = iota
Alipan
BaiduPan
UC
NewPan // 添加新的服务类型
NotFound
)
// 在 String 方法中添加
func (s ServiceType) String() string {
switch s {
// ... 现有case
case NewPan:
return "newpan"
default:
return "unknown"
}
}
// 在 CreatePanService 方法中添加
func (f *PanFactory) CreatePanService(url string, config *PanConfig) (PanService, error) {
serviceType := ExtractServiceType(url)
switch serviceType {
// ... 现有case
case NewPan:
return NewNewPanService(config), nil
default:
return nil, fmt.Errorf("不支持的服务类型: %s", url)
}
}
// 在 ExtractServiceType 方法中添加URL模式
func ExtractServiceType(url string) ServiceType {
url = strings.ToLower(url)
patterns := map[string]ServiceType{
// ... 现有模式
"newpan.example.com": NewPan,
}
for pattern, serviceType := range patterns {
if strings.Contains(url, pattern) {
return serviceType
}
}
return NotFound
}
```
## 配置说明
### PanConfig 配置项
- **URL** - 分享链接地址
- **Code** - 分享码(如果需要)
- **IsType** - 处理类型0=转存并分享后的资源信息1=直接获取资源信息
- **ExpiredType** - 有效期类型1=分享永久2=临时
- **AdFid** - 夸克专用分享时带上这个文件的fid
- **Stoken** - 夸克专用分享token
### 环境配置
某些网盘服务可能需要特定的配置,如:
- **夸克网盘** - 需要配置cookie
- **阿里云盘** - 需要配置access_token
- **百度网盘** - 需要配置相关认证信息
## 错误处理
所有服务方法都返回 `*TransferResult``error`
```go
type TransferResult struct {
Success bool `json:"success"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
ShareURL string `json:"shareUrl,omitempty"`
Title string `json:"title,omitempty"`
Fid string `json:"fid,omitempty"`
}
```
## 测试
运行测试:
```bash
cd utils
go test -v
```
## 注意事项
1. **并发安全** - 每个服务实例都是独立的可以安全地在多个goroutine中使用
2. **错误重试** - 基础服务提供了带重试的HTTP请求方法
3. **资源管理** - 记得及时清理不需要的资源
4. **配置管理** - 敏感配置如token应该通过环境变量或配置文件管理
## 性能优化
1. **连接池** - 基础服务使用HTTP客户端连接池
2. **超时控制** - 设置了合理的请求超时时间
3. **重试机制** - 提供了可配置的重试机制
4. **缓存** - 可以实现token缓存等优化
这个工厂模式设计使得代码具有良好的可维护性和可扩展性,可以轻松添加新的网盘服务支持。

View File

@@ -1,212 +0,0 @@
# 网盘服务单例模式分析
## 为什么需要服务单例模式?
### 1. 当前问题分析
在原来的实现中,每次调用 `CreatePanService` 都会创建新的服务实例:
```go
// 原来的实现
func (f *PanFactory) CreatePanService(url string, config *PanConfig) (PanService, error) {
serviceType := ExtractServiceType(url)
switch serviceType {
case Quark:
return NewQuarkPanService(config), nil // 每次都创建新实例
case Alipan:
return NewAlipanService(config), nil // 每次都创建新实例
// ...
}
}
```
### 2. 服务实例的开销分析
#### HTTP客户端开销
```go
// 每个服务实例都包含一个HTTP客户端
httpClient: &http.Client{
Timeout: 30 * time.Second,
}
```
- **内存占用**每个HTTP客户端约占用几KB内存
- **创建时间**:毫秒级别
- **连接池**:每个客户端都有自己的连接池
#### 请求头设置开销
```go
// 每个服务实例都设置相同的请求头
headers: map[string]string{
"Accept": "application/json, text/plain, */*",
"User-Agent": "Mozilla/5.0 ...",
// ... 更多请求头
}
```
- **内存占用**每个map约占用几百字节
- **初始化时间**:微秒级别
#### 配置对象开销
```go
// 每次创建服务都传递配置
config := &PanConfig{
URL: readyResource.URL,
IsType: 0,
ExpiredType: 1,
// ...
}
```
### 3. 单例模式的优势
#### 内存优化
- **避免重复创建HTTP客户端**每个服务类型只有一个HTTP客户端
- **减少请求头重复设置**:请求头只设置一次
- **降低内存占用**:特别是在处理大量资源时
#### 性能优化
- **减少初始化开销**避免重复的HTTP客户端创建
- **提高响应速度**:复用已建立的连接
- **减少GC压力**:减少对象创建和销毁
#### 资源管理
- **连接池复用**HTTP连接池可以更好地复用
- **配置统一管理**:可以在服务实例中维护全局配置
- **状态一致性**:避免多个实例状态不一致
### 4. 实现细节
#### 线程安全的单例实现
```go
// 夸克网盘服务单例
var (
quarkInstance *QuarkPanService
quarkOnce sync.Once
)
func NewQuarkPanService(config *PanConfig) *QuarkPanService {
quarkOnce.Do(func() {
quarkInstance = &QuarkPanService{
BasePanService: NewBasePanService(config),
}
// 设置固定的请求头
quarkInstance.SetHeaders(map[string]string{...})
})
// 更新配置(线程安全)
quarkInstance.UpdateConfig(config)
return quarkInstance
}
```
#### 配置更新机制
```go
// 线程安全的配置更新
func (q *QuarkPanService) UpdateConfig(config *PanConfig) {
if config == nil {
return
}
q.configMutex.Lock()
defer q.configMutex.Unlock()
q.config = config
}
```
### 5. 性能测试结果
#### 服务创建性能对比
```bash
# 单例模式服务创建
BenchmarkQuarkServiceCreation-8 100000000 2.1 ns/op 0 B/op 0 allocs/op
BenchmarkAlipanServiceCreation-8 100000000 2.3 ns/op 0 B/op 0 allocs/op
# 工厂方法获取服务
BenchmarkFactoryGetQuarkService-8 100000000 2.5 ns/op 0 B/op 0 allocs/op
BenchmarkFactoryGetAlipanService-8 100000000 2.7 ns/op 0 B/op 0 allocs/op
```
#### 内存使用对比
- **单例模式**:每个服务类型固定内存占用
- **普通创建**:每次创建都增加内存占用
### 6. 使用建议
#### 推荐使用方式
```go
// ✅ 推荐:使用工厂获取单例服务
factory := panutils.GetInstance()
quarkService := factory.GetQuarkService(config)
alipanService := factory.GetAlipanService(config)
// ✅ 推荐:直接使用单例服务
quarkService := panutils.NewQuarkPanService(config)
alipanService := panutils.NewAlipanService(config)
```
#### 在定时任务中的使用
```go
func (s *Scheduler) processReadyResources() {
factory := panutils.GetInstance()
for _, readyResource := range readyResources {
// 根据URL类型获取对应的单例服务
serviceType := panutils.ExtractServiceType(readyResource.URL)
config := &panutils.PanConfig{...}
var panService panutils.PanService
switch serviceType {
case panutils.Quark:
panService = factory.GetQuarkService(config)
case panutils.Alipan:
panService = factory.GetAlipanService(config)
}
// 使用服务处理资源
result, err := panService.Transfer(shareID)
// ...
}
}
```
### 7. 扩展性考虑
#### 未来可能的扩展
1. **服务实例缓存**:缓存已创建的服务实例
2. **配置缓存**:缓存常用配置
3. **连接池优化**优化HTTP连接池管理
4. **监控和统计**:在服务中添加使用统计
#### 单例模式的适用性
-**适合**无状态服务、HTTP客户端、配置管理
-**不适合**:有状态的对象、需要隔离的实例
### 8. 与工厂模式的结合
#### 双重单例模式
```go
// 工厂单例 + 服务单例
factory := panutils.GetInstance() // 工厂单例
quarkService := factory.GetQuarkService(config) // 服务单例
```
#### 优势
1. **工厂单例**:避免重复创建工厂实例
2. **服务单例**:避免重复创建服务实例
3. **配置更新**:支持动态配置更新
4. **线程安全**:保证并发环境下的正确性
### 9. 总结
对于网盘服务使用单例模式是**强烈推荐的做法**,原因:
1. **显著性能提升**减少HTTP客户端创建开销
2. **内存优化**降低内存占用和GC压力
3. **资源复用**HTTP连接池和请求头可以复用
4. **配置灵活**:支持动态配置更新
5. **线程安全**:保证并发环境下的正确性
6. **扩展性好**:为未来功能扩展提供基础
特别是在高频调用的场景下(如定时任务处理大量资源),服务单例模式能带来显著的性能提升和资源优化。

View File

@@ -1,111 +0,0 @@
package pan
import (
"log"
)
// PanExample 网盘工厂模式使用示例
func PanExample() {
// 创建工厂实例
factory := NewPanFactory()
// 示例URL
urls := []string{
"https://pan.quark.cn/s/123456789",
"https://www.alipan.com/s/abcdef123",
"https://pan.baidu.com/s/xyz789",
"https://drive.uc.cn/s/uc123456",
}
for _, url := range urls {
log.Printf("处理URL: %s", url)
// 创建配置
config := &PanConfig{
URL: url,
Code: "",
IsType: 0, // 转存并分享后的资源信息
ExpiredType: 1, // 永久分享
AdFid: "",
Stoken: "",
}
// 使用工厂创建对应的网盘服务
panService, err := factory.CreatePanService(url, config)
if err != nil {
log.Printf("创建网盘服务失败: %v", err)
continue
}
// 获取服务类型
serviceType := panService.GetServiceType()
log.Printf("检测到服务类型: %s", serviceType.String())
// 提取分享ID
shareID, extractedType := ExtractShareId(url)
if extractedType == NotFound {
log.Printf("不支持的链接格式: %s", url)
continue
}
log.Printf("分享ID: %s", shareID)
// 根据服务类型进行不同处理
switch serviceType {
case Quark:
log.Println("使用夸克网盘服务")
// 这里可以调用 panService.Transfer(shareID)
case Alipan:
log.Println("使用阿里云盘服务")
// 这里可以调用 panService.Transfer(shareID)
case BaiduPan:
log.Println("使用百度网盘服务")
// 这里可以调用 panService.Transfer(shareID)
case UC:
log.Println("使用UC网盘服务")
// 这里可以调用 panService.Transfer(shareID)
default:
log.Printf("暂不支持的服务类型: %s", serviceType.String())
}
log.Println("---")
}
}
// TransferExample 转存示例
func TransferExample(url string) (*TransferResult, error) {
factory := NewPanFactory()
config := &PanConfig{
URL: url,
Code: "",
IsType: 0,
ExpiredType: 1,
AdFid: "",
Stoken: "",
}
// 创建服务
panService, err := factory.CreatePanService(url, config)
if err != nil {
return ErrorResult("创建网盘服务失败"), err
}
// 提取分享ID
shareID, serviceType := ExtractShareId(url)
if serviceType == NotFound {
return ErrorResult("不支持的链接格式"), nil
}
// 执行转存
result, err := panService.Transfer(shareID)
if err != nil {
return ErrorResult("转存失败"), err
}
return result, nil
}

View File

@@ -1,205 +0,0 @@
package pan
import (
"testing"
)
func TestExtractServiceType(t *testing.T) {
tests := []struct {
url string
expected ServiceType
description string
}{
{
url: "https://pan.quark.cn/s/123456",
expected: Quark,
description: "夸克网盘",
},
{
url: "https://www.alipan.com/s/123456",
expected: Alipan,
description: "阿里云盘",
},
{
url: "https://www.aliyundrive.com/s/123456",
expected: Alipan,
description: "阿里云盘别名",
},
{
url: "https://pan.baidu.com/s/123456",
expected: BaiduPan,
description: "百度网盘",
},
{
url: "https://drive.uc.cn/s/123456",
expected: UC,
description: "UC网盘",
},
{
url: "https://fast.uc.cn/s/123456",
expected: UC,
description: "UC网盘别名",
},
{
url: "https://example.com/s/123456",
expected: NotFound,
description: "不支持的链接",
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
result := ExtractServiceType(test.url)
if result != test.expected {
t.Errorf("期望 %v, 实际 %v", test.expected, result)
}
})
}
}
func TestExtractShareId(t *testing.T) {
tests := []struct {
url string
expectedID string
expectedType ServiceType
description string
}{
{
url: "https://pan.quark.cn/s/123456",
expectedID: "123456",
expectedType: Quark,
description: "夸克网盘",
},
{
url: "https://www.alipan.com/s/123456?entry=abc",
expectedID: "123456",
expectedType: Alipan,
description: "阿里云盘带参数",
},
{
url: "https://pan.baidu.com/s/123456#section",
expectedID: "123456",
expectedType: BaiduPan,
description: "百度网盘带锚点",
},
{
url: "https://example.com/s/123456",
expectedID: "123456",
expectedType: NotFound,
description: "不支持的链接",
},
{
url: "https://pan.quark.cn/other/123456",
expectedID: "",
expectedType: NotFound,
description: "无效格式",
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
shareID, serviceType := ExtractShareId(test.url)
if shareID != test.expectedID {
t.Errorf("期望分享ID %s, 实际 %s", test.expectedID, shareID)
}
if serviceType != test.expectedType {
t.Errorf("期望服务类型 %v, 实际 %v", test.expectedType, serviceType)
}
})
}
}
func TestPanFactory(t *testing.T) {
factory := NewPanFactory()
tests := []struct {
url string
config *PanConfig
shouldError bool
description string
}{
{
url: "https://pan.quark.cn/s/123456",
config: &PanConfig{
URL: "https://pan.quark.cn/s/123456",
IsType: 0,
ExpiredType: 1,
},
shouldError: false,
description: "夸克网盘",
},
{
url: "https://www.alipan.com/s/123456",
config: &PanConfig{
URL: "https://www.alipan.com/s/123456",
IsType: 0,
ExpiredType: 1,
},
shouldError: false,
description: "阿里云盘",
},
{
url: "https://example.com/s/123456",
config: &PanConfig{
URL: "https://example.com/s/123456",
IsType: 0,
ExpiredType: 1,
},
shouldError: true,
description: "不支持的链接",
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
service, err := factory.CreatePanService(test.url, test.config)
if test.shouldError {
if err == nil {
t.Error("期望错误,但没有错误")
}
return
}
if err != nil {
t.Errorf("不期望错误,但得到错误: %v", err)
return
}
if service == nil {
t.Error("服务不应该为nil")
return
}
// 测试服务类型
serviceType := service.GetServiceType()
expectedType := ExtractServiceType(test.url)
if serviceType != expectedType {
t.Errorf("期望服务类型 %v, 实际 %v", expectedType, serviceType)
}
})
}
}
func TestServiceTypeString(t *testing.T) {
tests := []struct {
serviceType ServiceType
expected string
}{
{Quark, "quark"},
{Alipan, "alipan"},
{BaiduPan, "baidu"},
{UC, "uc"},
{NotFound, "unknown"},
{ServiceType(999), "unknown"},
}
for _, test := range tests {
t.Run(test.expected, func(t *testing.T) {
result := test.serviceType.String()
if result != test.expected {
t.Errorf("期望 %s, 实际 %s", test.expected, result)
}
})
}
}

View File

@@ -1,52 +0,0 @@
package pan
import (
"testing"
)
// TestQuarkGetUserInfo 测试夸克网盘GetUserInfo功能
func TestQuarkGetUserInfo(t *testing.T) {
// 创建夸克网盘服务实例
service := NewQuarkPanService(&PanConfig{})
// 测试无效cookie
_, err := service.GetUserInfo("invalid_cookie")
if err == nil {
t.Error("期望返回错误,但没有返回")
} else {
t.Logf("正确返回错误: %v", err)
}
}
// TestQuarkGetUserInfoWithValidCookie 测试有效cookie的情况
func TestQuarkGetUserInfoWithValidCookie(t *testing.T) {
// 创建夸克网盘服务实例
service := NewQuarkPanService(&PanConfig{})
// 使用测试cookie需要替换为有效的cookie
testCookie := "your_test_cookie_here"
if testCookie == "your_test_cookie_here" {
t.Skip("跳过测试需要提供有效的cookie")
}
userInfo, err := service.GetUserInfo(testCookie)
if err != nil {
t.Logf("获取用户信息失败: %v", err)
return
}
// 验证返回的用户信息
if userInfo == nil {
t.Fatal("用户信息为空")
}
t.Logf("用户名: %s", userInfo.Username)
t.Logf("VIP状态: %t", userInfo.VIPStatus)
t.Logf("容量信息: %s", userInfo.Capacity)
t.Logf("服务类型: %s", userInfo.ServiceType)
// 基本验证
if userInfo.ServiceType != "quark" {
t.Errorf("服务类型不匹配,期望: quark, 实际: %s", userInfo.ServiceType)
}
}

View File

@@ -1,176 +0,0 @@
package pan
import (
"fmt"
"testing"
)
func TestQuarkServiceSingleton(t *testing.T) {
// 测试夸克服务单例模式
config1 := &PanConfig{
URL: "https://pan.quark.cn/s/123456",
IsType: 0,
ExpiredType: 1,
}
config2 := &PanConfig{
URL: "https://pan.quark.cn/s/789012",
IsType: 1,
ExpiredType: 2,
}
service1 := NewQuarkPanService(config1)
service2 := NewQuarkPanService(config2)
// 应该返回相同的实例
if service1 != service2 {
t.Error("QuarkPanService 应该返回相同的单例实例")
}
// 验证服务类型
if service1.GetServiceType() != Quark {
t.Error("服务类型应该是 Quark")
}
}
func TestAlipanServiceSingleton(t *testing.T) {
// 测试阿里云盘服务单例模式
config1 := &PanConfig{
URL: "https://www.alipan.com/s/123456",
IsType: 0,
ExpiredType: 1,
}
config2 := &PanConfig{
URL: "https://www.alipan.com/s/789012",
IsType: 1,
ExpiredType: 2,
}
service1 := NewAlipanService(config1)
service2 := NewAlipanService(config2)
// 应该返回相同的实例
if service1 != service2 {
t.Error("AlipanService 应该返回相同的单例实例")
}
// 验证服务类型
if service1.GetServiceType() != Alipan {
t.Error("服务类型应该是 Alipan")
}
}
func TestServiceConcurrency(t *testing.T) {
// 测试并发情况下的服务单例
done := make(chan bool, 10)
quarkServices := make([]PanService, 10)
alipanServices := make([]PanService, 10)
// 并发创建夸克服务
for i := 0; i < 10; i++ {
go func(index int) {
config := &PanConfig{
URL: fmt.Sprintf("https://pan.quark.cn/s/%d", index),
IsType: 0,
ExpiredType: 1,
}
service := NewQuarkPanService(config)
quarkServices[index] = service
done <- true
}(i)
}
// 并发创建阿里云盘服务
for i := 0; i < 10; i++ {
go func(index int) {
config := &PanConfig{
URL: fmt.Sprintf("https://www.alipan.com/s/%d", index),
IsType: 0,
ExpiredType: 1,
}
service := NewAlipanService(config)
alipanServices[index] = service
done <- true
}(i)
}
// 等待所有goroutine完成
for i := 0; i < 20; i++ {
<-done
}
// 验证夸克服务单例
firstQuarkService := quarkServices[0]
for i := 1; i < 10; i++ {
if quarkServices[i] != firstQuarkService {
t.Errorf("夸克服务并发测试失败:实例 %d 与第一个实例不同", i)
}
}
// 验证阿里云盘服务单例
firstAlipanService := alipanServices[0]
for i := 1; i < 10; i++ {
if alipanServices[i] != firstAlipanService {
t.Errorf("阿里云盘服务并发测试失败:实例 %d 与第一个实例不同", i)
}
}
}
func BenchmarkQuarkServiceCreation(b *testing.B) {
// 测试夸克服务创建性能
config := &PanConfig{
URL: "https://pan.quark.cn/s/123456",
IsType: 0,
ExpiredType: 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = NewQuarkPanService(config)
}
}
func BenchmarkAlipanServiceCreation(b *testing.B) {
// 测试阿里云盘服务创建性能
config := &PanConfig{
URL: "https://www.alipan.com/s/123456",
IsType: 0,
ExpiredType: 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = NewAlipanService(config)
}
}
func BenchmarkFactoryGetQuarkService(b *testing.B) {
// 测试工厂获取夸克服务性能
factory := GetInstance()
config := &PanConfig{
URL: "https://pan.quark.cn/s/123456",
IsType: 0,
ExpiredType: 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = factory.GetQuarkService(config)
}
}
func BenchmarkFactoryGetAlipanService(b *testing.B) {
// 测试工厂获取阿里云盘服务性能
factory := GetInstance()
config := &PanConfig{
URL: "https://www.alipan.com/s/123456",
IsType: 0,
ExpiredType: 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = factory.GetAlipanService(config)
}
}

View File

@@ -1,71 +0,0 @@
package pan
import (
"testing"
)
func TestSingletonPattern(t *testing.T) {
// 测试多次调用NewPanFactory()返回相同实例
factory1 := NewPanFactory()
factory2 := NewPanFactory()
factory3 := GetInstance()
if factory1 != factory2 {
t.Error("NewPanFactory() 应该返回相同的实例")
}
if factory1 != factory3 {
t.Error("GetInstance() 应该返回相同的实例")
}
if factory2 != factory3 {
t.Error("所有获取方法应该返回相同的实例")
}
// 验证实例不为nil
if factory1 == nil {
t.Error("工厂实例不应该为nil")
}
}
func TestSingletonConcurrency(t *testing.T) {
// 测试并发情况下的单例模式
done := make(chan bool, 10)
instances := make([]*PanFactory, 10)
for i := 0; i < 10; i++ {
go func(index int) {
instances[index] = GetInstance()
done <- true
}(i)
}
// 等待所有goroutine完成
for i := 0; i < 10; i++ {
<-done
}
// 验证所有实例都是相同的
firstInstance := instances[0]
for i := 1; i < 10; i++ {
if instances[i] != firstInstance {
t.Errorf("并发测试失败:实例 %d 与第一个实例不同", i)
}
}
}
func BenchmarkSingletonCreation(b *testing.B) {
// 测试单例创建的性能
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = GetInstance()
}
}
func BenchmarkNewPanFactory(b *testing.B) {
// 测试NewPanFactory的性能
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = NewPanFactory()
}
}

View File

@@ -1,189 +0,0 @@
# API 响应格式标准化
## 概述
为了统一API响应格式提高前后端协作效率所有API接口都使用标准化的响应格式。
## 标准响应格式
### 基础响应结构
```json
{
"success": true,
"message": "操作成功",
"data": {},
"error": "",
"pagination": {}
}
```
### 字段说明
- `success`: 布尔值,表示操作是否成功
- `message`: 字符串,成功时的提示信息(可选)
- `data`: 对象/数组,返回的数据内容(可选)
- `error`: 字符串,错误信息(仅在失败时返回)
- `pagination`: 对象,分页信息(仅在分页接口时返回)
## 分页响应格式
### 分页信息结构
```json
{
"success": true,
"data": [],
"pagination": {
"page": 1,
"page_size": 100,
"total": 1002,
"total_pages": 11
}
}
```
### 分页参数
- `page`: 当前页码从1开始
- `page_size`: 每页条数
- `total`: 总记录数
- `total_pages`: 总页数
## 响应类型
### 1. 成功响应
```go
// 普通成功响应
SuccessResponse(c, data, "操作成功")
// 简单成功响应(无数据)
SimpleSuccessResponse(c, "操作成功")
// 创建成功响应
CreatedResponse(c, data, "创建成功")
```
### 2. 错误响应
```go
// 错误响应
ErrorResponse(c, http.StatusBadRequest, "参数错误")
```
### 3. 分页响应
```go
// 分页响应
PaginatedResponse(c, data, page, pageSize, total)
```
## 接口示例
### 获取待处理资源列表
**请求:**
```
GET /api/ready-resources?page=1&page_size=100
```
**响应:**
```json
{
"success": true,
"data": [
{
"id": 1,
"title": "示例资源",
"url": "https://example.com",
"create_time": "2024-01-01T00:00:00Z",
"ip": "127.0.0.1"
}
],
"pagination": {
"page": 1,
"page_size": 100,
"total": 1002,
"total_pages": 11
}
}
```
### 创建待处理资源
**请求:**
```
POST /api/ready-resources
{
"title": "新资源",
"url": "https://example.com"
}
```
**响应:**
```json
{
"success": true,
"message": "待处理资源创建成功",
"data": {
"id": 1003
}
}
```
### 错误响应示例
**响应:**
```json
{
"success": false,
"error": "参数错误:标题不能为空"
}
```
## 前端调用示例
### 获取分页数据
```typescript
const response = await api.getReadyResources({
page: 1,
page_size: 100
})
if (response.success) {
const resources = response.data
const pagination = response.pagination
// 处理数据
}
```
### 处理错误
```typescript
try {
const response = await api.createResource(data)
if (response.success) {
// 成功处理
}
} catch (error) {
// 网络错误等
}
```
## 实施规范
1. **所有新接口**必须使用标准化响应格式
2. **现有接口**逐步迁移到标准化格式
3. **错误处理**统一使用ErrorResponse
4. **分页接口**必须使用PaginatedResponse
5. **前端调用**统一处理success字段
## 迁移计划
1. ✅ 待处理资源接口ready-resources
2. 🔄 资源管理接口resources
3. 🔄 分类管理接口categories
4. 🔄 用户管理接口users
5. 🔄 统计接口stats

View File

@@ -1,116 +0,0 @@
# 用户认证系统
## 概述
本项目已成功集成了完整的用户认证系统,包括用户注册、登录、权限管理等功能。
## 功能特性
### 1. 用户管理
- **用户注册**: 支持新用户注册
- **用户登录**: JWT令牌认证
- **用户管理**: 管理员可以创建、编辑、删除用户
- **角色管理**: 支持用户(user)和管理员(admin)角色
- **状态管理**: 支持用户激活/禁用状态
### 2. 认证机制
- **JWT令牌**: 使用JWT进行身份验证
- **密码加密**: 使用bcrypt进行密码哈希
- **中间件保护**: 路由级别的权限控制
### 3. 权限控制
- **公开接口**: 资源查看、搜索等
- **用户接口**: 个人资料查看
- **管理员接口**: 所有管理功能需要管理员权限
## 数据库结构
### users表
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
role VARCHAR(20) DEFAULT 'user',
is_active BOOLEAN DEFAULT true,
last_login TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
```
## API接口
### 认证接口
- `POST /api/auth/login` - 用户登录
- `POST /api/auth/register` - 用户注册
- `GET /api/auth/profile` - 获取用户信息
### 用户管理接口(管理员)
- `GET /api/users` - 获取用户列表
- `POST /api/users` - 创建用户
- `PUT /api/users/:id` - 更新用户
- `DELETE /api/users/:id` - 删除用户
## 前端页面
### 登录页面 (`/login`)
- 用户名/密码登录
- 默认管理员账户: admin / password
- 登录成功后跳转到管理页面
### 注册页面 (`/register`)
- 新用户注册
- 注册成功后跳转到登录页面
### 管理页面 (`/admin`)
- 需要登录才能访问
- 显示用户信息和退出登录按钮
- 各种管理功能的入口
### 用户管理页面 (`/users`)
- 仅管理员可访问
- 用户列表展示
- 创建、编辑、删除用户功能
## 中间件
### AuthMiddleware
- 验证JWT令牌
- 将用户信息存储到上下文中
### AdminMiddleware
- 检查用户角色是否为管理员
- 保护管理员专用接口
## 默认数据
系统启动时会自动创建默认管理员账户:
- 用户名: admin
- 密码: password
- 角色: admin
- 邮箱: admin@example.com
## 安全特性
1. **密码加密**: 使用bcrypt进行密码哈希
2. **JWT令牌**: 24小时有效期的JWT令牌
3. **角色权限**: 基于角色的访问控制
4. **输入验证**: 服务器端数据验证
5. **SQL注入防护**: 使用GORM进行参数化查询
## 使用说明
1. 启动服务器后,访问 `/login` 页面
2. 使用默认管理员账户登录: admin / password
3. 登录成功后可以访问管理功能
4. 在用户管理页面可以创建新用户或修改现有用户
## 技术栈
- **后端**: Go + Gin + GORM + JWT
- **前端**: Nuxt.js + Tailwind CSS
- **数据库**: PostgreSQL
- **认证**: JWT + bcrypt

View File

@@ -1,269 +0,0 @@
# 待处理资源自动处理功能修复说明
## 问题描述
在管理后台开启"待处理资源自动处理"功能后,系统没有自动开始任务,并且出现外键约束错误:
```
ERROR: insert or update on table "resources" violates foreign key constraint "fk_resources_pan" (SQLSTATE 23503)
```
## 问题原因
1. **UpdateSchedulerStatus方法参数不完整**`utils/global_scheduler.go` 中的 `UpdateSchedulerStatus` 方法只接收了 `autoFetchHotDramaEnabled` 参数,没有处理 `autoProcessReadyResources` 参数。
2. **UpdateSystemConfig调用参数不完整**`handlers/system_config_handler.go` 中的 `UpdateSystemConfig` 函数只传递了热播剧相关的参数,没有传递待处理资源相关的参数。
3. **调度器间隔时间硬编码**`utils/scheduler.go` 中的 `StartReadyResourceScheduler` 方法使用了硬编码的5分钟间隔而不是使用系统配置中的 `AutoProcessInterval`
4. **平台匹配机制优化**:使用 `serviceType` 来匹配平台,并添加平台映射缓存提高性能。
## 修复内容
### 1. 修复 UpdateSchedulerStatus 方法
**文件**: `utils/global_scheduler.go`
**修改前**:
```go
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool) {
// 只处理热播剧功能
}
```
**修改后**:
```go
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool, autoProcessReadyResources bool) {
// 处理热播剧自动拉取功能
if autoFetchHotDramaEnabled {
if !gs.scheduler.IsRunning() {
log.Println("系统配置启用自动拉取热播剧,启动定时任务")
gs.scheduler.StartHotDramaScheduler()
}
} else {
if gs.scheduler.IsRunning() {
log.Println("系统配置禁用自动拉取热播剧,停止定时任务")
gs.scheduler.StopHotDramaScheduler()
}
}
// 处理待处理资源自动处理功能
if autoProcessReadyResources {
if !gs.scheduler.IsReadyResourceRunning() {
log.Println("系统配置启用自动处理待处理资源,启动定时任务")
gs.scheduler.StartReadyResourceScheduler()
}
} else {
if gs.scheduler.IsReadyResourceRunning() {
log.Println("系统配置禁用自动处理待处理资源,停止定时任务")
gs.scheduler.StopReadyResourceScheduler()
}
}
}
```
### 2. 修复 UpdateSystemConfig 函数
**文件**: `handlers/system_config_handler.go`
**修改前**:
```go
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled)
```
**修改后**:
```go
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled, req.AutoProcessReadyResources)
```
### 3. 修复调度器间隔时间配置
**文件**: `utils/scheduler.go`
**修改前**:
```go
ticker := time.NewTicker(5 * time.Minute) // 每5分钟检查一次
```
**修改后**:
```go
// 获取系统配置中的间隔时间
config, err := s.systemConfigRepo.GetOrCreateDefault()
interval := 5 * time.Minute // 默认5分钟
if err == nil && config.AutoProcessInterval > 0 {
interval = time.Duration(config.AutoProcessInterval) * time.Minute
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
log.Printf("待处理资源自动处理任务已启动,间隔时间: %v", interval)
```
### 4. 优化平台匹配机制
**文件**: `utils/scheduler.go`
**新增平台映射缓存**:
```go
type Scheduler struct {
// ... 其他字段 ...
// 平台映射缓存
panCache map[string]*uint // serviceType -> panID
panCacheOnce sync.Once
}
```
**新增初始化缓存方法**:
```go
// initPanCache 初始化平台映射缓存
func (s *Scheduler) initPanCache() {
s.panCacheOnce.Do(func() {
// 获取所有平台数据
pans, err := s.panRepo.FindAll()
if err != nil {
log.Printf("初始化平台缓存失败: %v", err)
return
}
// 建立 ServiceType 到 PanID 的映射
serviceTypeToPanName := map[string]string{
"quark": "quark",
"alipan": "aliyun", // 阿里云盘在数据库中的名称是 aliyun
"baidu": "baidu",
"uc": "uc",
"unknown": "other",
}
// 创建平台名称到ID的映射
panNameToID := make(map[string]*uint)
for _, pan := range pans {
panID := pan.ID
panNameToID[pan.Name] = &panID
}
// 建立 ServiceType 到 PanID 的映射
for serviceType, panName := range serviceTypeToPanName {
if panID, exists := panNameToID[panName]; exists {
s.panCache[serviceType] = panID
log.Printf("平台映射缓存: %s -> %s (ID: %d)", serviceType, panName, *panID)
} else {
log.Printf("警告: 未找到平台 %s 对应的数据库记录", panName)
}
}
// 确保有默认的 other 平台
if otherID, exists := panNameToID["other"]; exists {
s.panCache["unknown"] = otherID
}
log.Printf("平台映射缓存初始化完成,共 %d 个映射", len(s.panCache))
})
}
```
**新增根据服务类型获取平台ID的方法**:
```go
// getPanIDByServiceType 根据服务类型获取平台ID
func (s *Scheduler) getPanIDByServiceType(serviceType panutils.ServiceType) *uint {
s.initPanCache()
serviceTypeStr := serviceType.String()
if panID, exists := s.panCache[serviceTypeStr]; exists {
return panID
}
// 如果找不到,返回 other 平台的ID
if otherID, exists := s.panCache["other"]; exists {
log.Printf("未找到服务类型 %s 的映射,使用默认平台 other", serviceTypeStr)
return otherID
}
log.Printf("未找到服务类型 %s 的映射且没有默认平台返回nil", serviceTypeStr)
return nil
}
```
**修改资源创建逻辑**:
```go
// 在 convertReadyResourceToResource 方法中
resource := &entity.Resource{
Title: title,
Description: readyResource.Description,
URL: shareURL,
PanID: s.getPanIDByServiceType(serviceType), // 使用 serviceType 匹配
IsValid: true,
IsPublic: true,
}
```
### 5. 添加 PanRepository 依赖
**文件**: `utils/scheduler.go`
**修改前**:
```go
type Scheduler struct {
doubanService *DoubanService
hotDramaRepo repo.HotDramaRepository
readyResourceRepo repo.ReadyResourceRepository
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
stopChan chan bool
isRunning bool
readyResourceRunning bool
processingMutex sync.Mutex
hotDramaMutex sync.Mutex
}
```
**修改后**:
```go
type Scheduler struct {
doubanService *DoubanService
hotDramaRepo repo.HotDramaRepository
readyResourceRepo repo.ReadyResourceRepository
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
panRepo repo.PanRepository
stopChan chan bool
isRunning bool
readyResourceRunning bool
processingMutex sync.Mutex
hotDramaMutex sync.Mutex
// 平台映射缓存
panCache map[string]*uint // serviceType -> panID
panCacheOnce sync.Once
}
```
## 修复效果
现在当您在管理后台开启"待处理资源自动处理"功能时:
1. **系统会立即启动调度器** - 不再需要重启服务器
2. **使用配置的间隔时间** - 不再是固定的5分钟
3. **支持实时开关** - 可以随时开启或关闭功能
4. **正确的外键关联** - 不再出现外键约束错误
5. **智能平台识别** - 根据 serviceType 自动识别对应的平台
6. **高性能缓存** - 平台映射缓存,避免重复数据库查询
## 测试方法
运行测试脚本验证修复效果:
```bash
# 测试自动处理功能
chmod +x test-auto-process.sh
./test-auto-process.sh
# 测试平台匹配机制
chmod +x test-pan-mapping.sh
./test-pan-mapping.sh
```
## 注意事项
1. 确保数据库中有默认的平台数据
2. 确保系统配置中的 `auto_process_interval` 设置合理
3. 如果仍有问题,检查日志中的错误信息

View File

@@ -1,87 +0,0 @@
# 豆瓣服务获取全部数据功能优化
## 问题描述
用户反馈豆瓣接口返回了 `total: 283`,但当前代码只获取了部分数据,希望能够一次性获取全部数据而不是分页获取。
## 解决方案
### 1. 修改豆瓣服务逻辑
`utils/douban_service.go` 中:
-`GetMovieRanking``GetTvRanking` 方法添加了 `limit=0` 的特殊处理
-`limit=0` 时,先获取第一页来确定总数,然后一次性获取全部数据
- 重构了代码将实际的API调用逻辑提取到 `getMovieRankingPage``getTvRankingPage` 方法中
### 2. 更新调度器配置
`utils/scheduler.go` 中:
- 将电影数据处理从 `limit=20` 改为 `limit=0`
- 将电视剧数据处理从 `limit=20` 改为 `limit=0`
- 这样调度器会自动获取并处理全部数据
### 3. 功能特点
- **智能检测**: 当传入 `limit=0` 时,自动检测总数并获取全部数据
- **向后兼容**: 原有的分页功能保持不变
- **日志记录**: 详细记录获取过程,便于调试
- **错误处理**: 如果获取总数失败,会回退到默认行为
## 使用方法
### API调用
```bash
# 获取全部电影数据
curl "http://localhost:8080/api/hot-dramas/movies?category=热门&type=全部&start=0&limit=0"
# 获取全部电视剧数据
curl "http://localhost:8080/api/hot-dramas/tv?category=tv&type=tv&start=0&limit=0"
```
### 调度器自动处理
调度器现在会自动获取全部数据:
```go
// 电影数据处理
movieResult, err := s.doubanService.GetMovieRanking("热门", "全部", 0, 0)
// 电视剧数据处理
tvResult, err := s.doubanService.GetTvRanking("tv", "tv", 0, 0)
```
## 日志输出
当使用 `limit=0` 时,会看到类似以下的日志:
```
=== 开始获取电影榜单 ===
参数: category=热门, rankingType=全部, start=0, limit=0
检测到limit=0将尝试获取全部数据
检测到总数为: 283将一次性获取全部数据
```
## 测试验证
使用提供的测试脚本:
```bash
chmod +x test-douban-full-data.sh
./test-douban-full-data.sh
```
## 优势
1. **数据完整性**: 确保获取到所有可用数据
2. **性能优化**: 减少API调用次数
3. **灵活性**: 支持分页和全量获取两种模式
4. **可维护性**: 代码结构清晰,易于理解和维护
## 注意事项
1. 获取全部数据可能会增加单次请求的响应时间
2. 建议在调度器中使用此功能,避免影响用户界面的响应速度
3. 如果API返回的数据量很大需要考虑内存使用情况

View File

@@ -1,183 +0,0 @@
# 原始豆瓣API Postman配置指南
## 基础信息
根据代码分析原始豆瓣API的基础配置如下
### 基础URL
```
https://m.douban.com/rexxar/api/v2
```
### 请求头配置
```
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1
Referer: https://m.douban.com/
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
```
## Postman配置步骤
### 1. 创建新的Collection
1. 打开Postman
2. 点击 "New" → "Collection"
3. 命名为 "豆瓣API"
### 2. 设置Collection级别的Headers
1. 选择刚创建的Collection
2. 点击 "Variables" 标签
3. 添加以下Headers
| Key | Value |
|-----|-------|
| User-Agent | Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1 |
| Referer | https://m.douban.com/ |
| Accept | application/json, text/plain, */* |
| Accept-Language | zh-CN,zh;q=0.9,en;q=0.8 |
| Accept-Encoding | gzip, deflate |
| Connection | keep-alive |
| Sec-Fetch-Dest | empty |
| Sec-Fetch-Mode | cors |
| Sec-Fetch-Site | same-origin |
### 3. 创建电影榜单请求
#### 请求配置
- **Method**: GET
- **URL**: `https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie`
#### Query Parameters
| Key | Value | Description |
|-----|-------|-------------|
| start | 0 | 起始位置 |
| limit | 20 | 限制数量0表示获取全部 |
| category | 热门 | 分类(热门/最新/豆瓣高分/冷门佳片) |
| type | 全部 | 类型(全部/华语/欧美/韩国/日本) |
#### 示例请求
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=20&category=热门&type=全部
```
### 4. 创建电视剧榜单请求
#### 请求配置
- **Method**: GET
- **URL**: `https://m.douban.com/rexxar/api/v2/subject/recent_hot/tv`
#### Query Parameters
| Key | Value | Description |
|-----|-------|-------------|
| start | 0 | 起始位置 |
| limit | 20 | 限制数量0表示获取全部 |
| category | tv | 分类tv/show |
| type | tv | 类型tv/tv_domestic/tv_american/tv_japanese/tv_korean/tv_animation/tv_documentary |
#### 示例请求
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=20&category=tv&type=tv
```
## 常用请求示例
### 1. 获取全部电影数据
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=0&category=热门&type=全部
```
### 2. 获取华语电影
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=20&category=热门&type=华语
```
### 3. 获取欧美电影
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=20&category=热门&type=欧美
```
### 4. 获取豆瓣高分电影
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=20&category=豆瓣高分&type=全部
```
### 5. 获取国产剧
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=20&category=tv&type=tv_domestic
```
### 6. 获取综艺节目
```
GET https://m.douban.com/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=20&category=show&type=show
```
## 响应格式
### 成功响应示例
```json
{
"items": [
{
"id": "123456",
"title": "电影标题",
"card_subtitle": "导演 / 主演",
"episodes_info": "",
"is_new": false,
"pic": {
"large": "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p123456.jpg",
"normal": "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p123456.jpg"
},
"rating": {
"value": 8.5,
"count": 12345,
"max": 10,
"star_count": 4.25
},
"type": "movie",
"uri": "douban://douban.com/movie/123456",
"year": "2024",
"directors": ["导演名"],
"actors": ["演员1", "演员2"],
"region": "中国大陆",
"genres": ["剧情", "动作"]
}
],
"total": 283,
"categories": [
{
"category": "热门",
"selected": true,
"type": "全部",
"title": "热门"
}
]
}
```
## 注意事项
1. **请求频率**: 建议控制请求频率,避免被限制
2. **User-Agent**: 必须使用移动端User-Agent否则可能返回错误
3. **Referer**: 必须设置正确的Referer头
4. **分页**: 使用start和limit参数进行分页
5. **全量获取**: 设置limit=0可以获取全部数据
## 错误处理
### 常见错误
- **403 Forbidden**: 请求头配置不正确
- **404 Not Found**: URL路径错误
- **429 Too Many Requests**: 请求频率过高
### 调试建议
1. 检查所有请求头是否正确设置
2. 确认URL路径和参数格式
3. 使用浏览器开发者工具对比请求
4. 查看响应状态码和错误信息

View File

@@ -1,161 +0,0 @@
# 首页完整修复说明
## 问题描述
1. 首页默认显示100条数据
2. 今日更新没有显示
3. 总资源数没有显示
4. 首页没有默认加载数据
5. 获取分类失败导致错误
## 问题原因分析
### 1. 数据加载问题
- 首页初始化时调用 `store.fetchResources()` 没有传递分页参数
- Store 中的 `fetchResources` 方法没有设置默认参数
- 后端API需要 `page``page_size` 参数才能正确返回数据
### 2. 数据显示问题
- 模板中使用 `visibleResources` 但该变量没有正确设置
- `visibleResources` 是空数组,导致页面显示"暂无数据"
- 统计数据计算依赖 `safeResources`,但数据没有正确加载
### 3. 类型错误问题
- TypeScript 类型检查错误API 返回的数据类型不明确
### 4. 分类获取问题
- 分类API返回undefined导致错误
- 首页不需要分类功能,应该移除相关调用
## 修复内容
### 1. 修复数据加载参数
**文件**: `web/pages/index.vue`
```javascript
// 修复前
const resourcesPromise = store.fetchResources().then((data: any) => {
localResources.value = data.resources || []
return data
})
// 修复后
const resourcesPromise = store.fetchResources({
page: 1,
page_size: 100
}).then((data: any) => {
localResources.value = data.resources || []
return data
})
```
### 2. 修复visibleResources计算属性
**文件**: `web/pages/index.vue`
```javascript
// 修复前
const visibleResources = ref<any[]>([])
const pageSize = ref(20)
// 修复后
const visibleResources = computed(() => safeResources.value)
const pageSize = ref(100) // 修改为100条数据
```
### 3. 修复Store中的fetchResources方法
**文件**: `web/stores/resource.ts`
```javascript
// 修复前
async fetchResources(params?: any) {
this.loading = true
try {
const { getResources } = useResourceApi()
const data = await getResources(params)
this.resources = data.resources
this.currentPage = data.page
this.totalPages = Math.ceil(data.total / data.limit)
} catch (error) {
console.error('获取资源失败:', error)
} finally {
this.loading = false
}
}
// 修复后
async fetchResources(params?: any) {
this.loading = true
try {
const { getResources } = useResourceApi()
// 确保有默认参数
const defaultParams = {
page: 1,
page_size: 100,
...params
}
const data = await getResources(defaultParams) as any
this.resources = data.resources || []
this.currentPage = data.page || 1
this.totalPages = Math.ceil((data.total || 0) / (data.page_size || 100))
} catch (error) {
console.error('获取资源失败:', error)
} finally {
this.loading = false
}
}
```
### 4. 修复TypeScript类型错误
**文件**: `web/stores/resource.ts`
```javascript
// 为所有API调用添加类型断言
const data = await getResources(defaultParams) as any
const stats = await getStats() as any
```
### 5. 移除分类获取功能
**文件**: `web/pages/index.vue`
```javascript
// 移除分类获取调用
// 移除 localCategories 状态管理
// 简化 safeCategories 计算属性
```
## 修复要点总结
1. **参数传递**: 确保首页初始化时传递正确的分页参数
2. **默认值设置**: Store 方法中设置合理的默认参数
3. **计算属性**: 将 `visibleResources` 改为计算属性,直接使用 `safeResources`
4. **数据量调整**: 将默认显示数据量从20条改为100条
5. **类型安全**: 添加类型断言解决TypeScript错误
6. **字段修正**: 使用正确的字段名 `page_size` 而不是 `limit`
7. **移除分类**: 移除不必要的分类获取功能避免API错误
## 测试验证
运行测试脚本验证修复效果:
```bash
chmod +x test-homepage-fix.sh
./test-homepage-fix.sh
```
## 预期效果
修复后,首页应该能够:
1. ✅ 页面加载时自动显示前100条资源数据
2. ✅ 正确显示今日更新数量(基于当前加载的数据计算)
3. ✅ 正确显示总资源数从统计API获取
4. ✅ 平台筛选功能正常工作
5. ✅ 搜索功能正常工作
6. ✅ "加载更多"功能继续正常工作
7. ✅ 不再出现分类获取错误
## 注意事项
1. **数据计算**: 今日更新数量基于当前加载的100条数据计算如果需要更准确的统计需要加载所有数据或使用专门的统计API
2. **性能考虑**: 加载100条数据可能影响页面加载速度可根据实际需求调整
3. **缓存策略**: 考虑添加数据缓存以提高用户体验
4. **错误处理**: 确保网络错误时有合适的降级处理
## 后续优化建议
1. **虚拟滚动**: 对于大量数据,考虑实现虚拟滚动
2. **分页优化**: 实现更智能的分页策略
3. **缓存机制**: 添加数据缓存减少重复请求
4. **加载状态**: 优化加载状态的用户体验

View File

@@ -1,183 +0,0 @@
# 首页优化说明
## 概述
根据提供的HTML内容我们对网盘资源管理系统的首页进行了全面优化创建了一个更符合网盘资源管理风格的现代化界面。
## 主要优化内容
### 🎨 **界面设计优化**
1. **整体布局**
- 采用深色头部设计,突出系统名称
- 响应式布局,支持移动端和桌面端
- 统一的卡片式设计风格
2. **搜索区域**
- 圆角搜索框,带有搜索图标
- 防抖搜索功能,提升用户体验
- 平台类型筛选按钮
3. **资源列表**
- 表格形式展示,更清晰直观
- 支持链接显示/隐藏功能
- 今日更新资源高亮显示
- 相对时间显示刚刚更新、X分钟前等
### 🔧 **功能增强**
1. **平台管理**
- 新增平台类型筛选
- 支持按平台ID查询资源
- 平台图标自动识别
2. **搜索功能**
- 实时搜索防抖500ms
- 支持文件名和链接搜索
- 平台类型筛选
3. **分页功能**
- 简洁的分页设计
- 支持上一页/下一页
- 当前页高亮显示
4. **统计信息**
- 今日更新数量
- 总资源数量
- 数字动画效果
### 📱 **响应式设计**
1. **移动端适配**
- 搜索框自适应
- 表格在小屏幕上优化显示
- 按钮和链接适配触摸操作
2. **桌面端优化**
- 宽屏布局充分利用
- 多列显示提升信息密度
- 悬停效果增强交互
### 🎯 **用户体验**
1. **加载状态**
- 加载中动画
- 空状态提示
- 错误状态处理
2. **交互反馈**
- 按钮悬停效果
- 链接点击反馈
- 平滑滚动
3. **视觉层次**
- 今日更新资源特殊标记
- 平台图标颜色区分
- 时间信息层次化显示
## 技术实现
### 前端技术栈
- **Nuxt 3**: 现代化Vue框架
- **TypeScript**: 类型安全
- **Tailwind CSS**: 原子化CSS框架
- **Font Awesome**: 图标库
- **Pinia**: 状态管理
### 核心功能
1. **防抖搜索**
```typescript
const debounceSearch = () => {
clearTimeout(searchTimeout)
searchTimeout = setTimeout(() => {
handleSearch()
}, 500)
}
```
2. **平台图标映射**
```typescript
const getPlatformIcon = (platformName: string) => {
const icons: Record<string, string> = {
'百度网盘': '<i class="fas fa-cloud text-blue-500"></i>',
'阿里云盘': '<i class="fas fa-cloud text-orange-500"></i>',
// ... 更多平台
}
return icons[platformName] || icons['unknown']
}
```
3. **相对时间格式化**
```typescript
const formatRelativeTime = (dateString: string) => {
// 计算时间差并返回友好的显示格式
// 支持刚刚更新、X分钟前、X小时前、X天前等
}
```
## 新增页面
### 管理员页面 (`/admin`)
- 功能模块化管理
- 统计信息展示
- 快速操作入口
- 系统设置入口
## 配置更新
### Nuxt配置
- 添加Font Awesome图标库
- 更新页面标题和描述
- 配置API基础URL
## 使用说明
### 启动项目
```bash
# 后端
cd res_db
go run main.go
# 前端
cd web
npm run dev
```
### 主要功能
1. **搜索资源**: 在搜索框输入关键词
2. **筛选平台**: 点击平台类型按钮
3. **查看链接**: 点击"显示链接"按钮
4. **管理资源**: 点击"管理员入口"
## 注意事项
1. **图标显示**: 确保网络连接正常Font Awesome通过CDN加载
2. **响应式**: 在不同设备上测试界面效果
3. **性能**: 大量数据时考虑分页和虚拟滚动
4. **安全性**: 链接显示功能需要根据实际需求调整
## 后续优化建议
1. **缓存优化**: 添加资源列表缓存
2. **搜索增强**: 支持高级搜索和筛选
3. **用户体验**: 添加加载骨架屏
4. **功能扩展**: 支持资源收藏、分享等功能
5. **主题切换**: 支持深色/浅色主题
6. **国际化**: 支持多语言切换
## 文件结构
```
web/
├── pages/
│ ├── index.vue # 优化后的首页
│ └── admin.vue # 新增管理员页面
├── composables/
│ └── useApi.ts # API调用函数
├── stores/
│ └── resource.ts # 状态管理
└── nuxt.config.ts # Nuxt配置
```
这个优化后的首页完全符合网盘资源管理系统的需求,提供了现代化的用户界面和良好的用户体验。

View File

@@ -1,123 +0,0 @@
# 热播剧功能说明
## 功能概述
热播剧功能是一个自动获取和展示豆瓣热门电影、电视剧榜单的功能模块。系统会定时从豆瓣获取最新的热门影视作品信息,并保存到数据库中供用户浏览。
## 主要特性
### 1. 自动数据获取
- 每小时自动从豆瓣获取热门电影和电视剧数据
- 支持电影和电视剧两个分类
- 获取内容包括:剧名、评分、年份、导演、演员等详细信息
### 2. 数据存储
- 创建专门的热播剧数据表 `hot_dramas`
- 支持按豆瓣ID去重避免重复数据
- 记录数据来源和获取时间
### 3. 前端展示
- 美观的卡片式布局展示热播剧信息
- 支持按分类筛选(全部/电影/电视剧)
- 分页显示,支持大量数据
- 响应式设计,适配各种设备
### 4. 管理功能
- 管理员可以手动启动/停止定时任务
- 支持手动获取热播剧数据
- 查看调度器运行状态
## 数据库结构
### hot_dramas 表
```sql
CREATE TABLE hot_dramas (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
rating DECIMAL(3,1) DEFAULT 0.0,
year VARCHAR(10),
directors VARCHAR(500),
actors VARCHAR(1000),
category VARCHAR(50),
sub_type VARCHAR(50),
source VARCHAR(50) DEFAULT 'douban',
douban_id VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
## API 接口
### 热播剧管理
- `GET /api/hot-dramas` - 获取热播剧列表
- `GET /api/hot-dramas/:id` - 获取热播剧详情
- `POST /api/hot-dramas` - 创建热播剧记录(管理员)
- `PUT /api/hot-dramas/:id` - 更新热播剧记录(管理员)
- `DELETE /api/hot-dramas/:id` - 删除热播剧记录(管理员)
### 调度器管理
- `GET /api/scheduler/status` - 获取调度器状态
- `POST /api/scheduler/hot-drama/start` - 启动热播剧定时任务(管理员)
- `POST /api/scheduler/hot-drama/stop` - 停止热播剧定时任务(管理员)
- `GET /api/scheduler/hot-drama/names` - 手动获取热播剧名字(管理员)
## 配置说明
### 系统配置
在系统配置中有一个 `auto_fetch_hot_drama_enabled` 字段,用于控制是否启用自动获取热播剧功能:
- `true`: 启用自动获取,系统会根据配置的间隔时间自动获取数据
- `false`: 禁用自动获取,需要管理员手动启动
## 使用流程
### 1. 启用功能
1. 登录管理后台
2. 进入系统配置页面
3. 开启"自动拉取热播剧名字"选项
4. 保存配置
### 2. 查看热播剧
1. 在首页点击"热播剧"按钮
2. 进入热播剧页面
3. 可以按分类筛选查看
4. 支持分页浏览
### 3. 管理定时任务
1. 管理员可以手动启动/停止定时任务
2. 可以查看调度器运行状态
3. 可以手动触发数据获取
## 技术实现
### 后端架构
- **实体层**: `db/entity/hot_drama.go` - 定义热播剧数据结构
- **DTO层**: `db/dto/hot_drama.go` - 定义数据传输对象
- **转换器**: `db/converter/hot_drama_converter.go` - 实体与DTO转换
- **仓储层**: `db/repo/hot_drama_repository.go` - 数据库操作
- **处理器**: `handlers/hot_drama_handler.go` - API接口处理
- **调度器**: `utils/scheduler.go` - 定时任务管理
- **豆瓣服务**: `utils/douban_service.go` - 豆瓣API调用
### 前端实现
- **页面**: `web/pages/hot-dramas.vue` - 热播剧展示页面
- **导航**: 在首页添加热播剧入口
- **样式**: 使用Tailwind CSS实现响应式设计
## 注意事项
1. **数据来源**: 数据来源于豆瓣移动端API如果API不可用会使用模拟数据
2. **频率限制**: 定时任务每小时执行一次,避免对豆瓣服务器造成压力
3. **数据去重**: 系统会根据豆瓣ID进行去重避免重复数据
4. **权限控制**: 管理功能需要管理员权限
5. **错误处理**: 系统具备完善的错误处理机制,确保稳定性
## 扩展功能
未来可以考虑添加的功能:
1. 支持更多数据源如IMDB、烂番茄等
2. 添加用户收藏功能
3. 支持热播剧搜索
4. 添加数据统计和分析功能
5. 支持热播剧推荐算法

View File

@@ -1,195 +0,0 @@
# 网盘服务工厂模式优化
## 优化背景
用户提出了一个很好的优化建议:可以通过工厂来获取网盘服务的单例实例,所有配置都是一样的,可以不需要 switch 语句了。
## 优化前的问题
### 1. 代码冗余
```go
// 优化前:需要 switch 语句分别处理
switch serviceType {
case panutils.Quark:
quarkService := panutils.GetQuarkInstance()
quarkService.UpdateConfig(config)
result, err := quarkService.Transfer(shareID)
// ... 处理结果
case panutils.Alipan:
alipanService := panutils.GetAlipanInstance()
alipanService.UpdateConfig(config)
result, err := alipanService.Transfer(shareID)
// ... 处理结果
}
```
### 2. 重复逻辑
- 每个 case 都有相似的配置更新逻辑
- 每个 case 都有相似的结果处理逻辑
- 代码维护困难,容易出错
## 优化方案
### 1. 通过工厂获取单例服务
```go
// 优化后:通过工厂统一获取服务
panService, err := factory.CreatePanService(readyResource.URL, config)
if err != nil {
log.Printf("获取网盘服务失败: %v", err)
return err
}
```
### 2. 统一处理逻辑
```go
// 统一处理:尝试转存获取标题
result, err := panService.Transfer(shareID)
if err != nil {
log.Printf("网盘转存失败: %v", err)
return err
}
if !result.Success {
log.Printf("网盘转存失败: %s", result.Message)
return nil
}
// 统一的结果处理逻辑
if resultData, ok := result.Data.(map[string]interface{}); ok {
title := resultData["title"].(string)
shareURL := resultData["shareUrl"].(string)
// 创建资源记录
resource := &entity.Resource{
Title: title,
Description: readyResource.Description,
URL: shareURL,
PanID: s.determinePanID(readyResource.URL),
IsValid: true,
IsPublic: true,
}
return s.resourceRepo.Create(resource)
}
```
### 3. 特殊处理优化
```go
// 阿里云盘特殊处理检查URL有效性
if serviceType == panutils.Alipan {
checkResult, _ := CheckURL(readyResource.URL)
if !checkResult.Status {
log.Printf("阿里云盘链接无效: %s", readyResource.URL)
return nil
}
// 如果有标题,直接创建资源
if readyResource.Title != nil && *readyResource.Title != "" {
// ... 直接创建资源的逻辑
}
}
```
## 优化效果
### 1. 代码简化
- **移除 switch 语句**:从复杂的 switch 结构简化为统一的处理流程
- **减少重复代码**:统一的结果处理逻辑,避免重复
- **提高可读性**:代码结构更清晰,逻辑更直观
### 2. 维护性提升
- **单一职责**:每个函数职责更明确
- **易于扩展**:添加新的网盘服务类型更容易
- **减少错误**:统一的处理逻辑减少出错概率
### 3. 性能优化
- **单例复用**:通过工厂获取单例服务,确保性能
- **配置统一**:所有服务使用相同的配置结构
- **内存优化**:减少不必要的对象创建
## 技术实现
### 1. 工厂模式的优势
```go
// 工厂方法内部已经是单例模式
func (f *PanFactory) CreatePanService(url string, config *PanConfig) (PanService, error) {
serviceType := ExtractServiceType(url)
switch serviceType {
case Quark:
return NewQuarkPanService(config), nil // 内部是单例
case Alipan:
return NewAlipanService(config), nil // 内部是单例
// ...
}
}
```
### 2. 接口统一
```go
// 所有服务都实现相同的接口
type PanService interface {
Transfer(shareID string) (*TransferResult, error)
GetFiles(pdirFid string) (*TransferResult, error)
DeleteFiles(fileList []string) (*TransferResult, error)
GetServiceType() ServiceType
}
```
### 3. 配置统一
```go
// 统一的配置结构
config := &panutils.PanConfig{
URL: readyResource.URL,
Code: "",
IsType: 0,
ExpiredType: 1,
AdFid: "",
Stoken: "",
}
```
## 测试验证
### 编译测试
```bash
go build -o res_db .
# ✅ 编译成功
```
### 功能测试
```bash
go test ./utils/pan -v
# ✅ 所有测试通过
```
### 性能测试
```bash
go test ./utils/pan -bench=. -benchmem
# ✅ 性能良好
```
## 优化总结
### 优势
1. **代码简化**:移除复杂的 switch 语句
2. **逻辑统一**:所有网盘服务使用相同的处理流程
3. **维护性提升**:代码更易维护和扩展
4. **性能优化**:通过工厂获取单例服务
5. **配置统一**:所有服务使用相同的配置结构
### 适用场景
- 多个相似服务需要统一处理
- 服务接口一致,但实现不同
- 需要简化复杂的条件判断逻辑
- 希望提高代码的可维护性
### 最佳实践
1. **使用工厂模式**:通过工厂获取服务实例
2. **统一接口**:确保所有服务实现相同的接口
3. **配置统一**:使用相同的配置结构
4. **特殊处理**:将特殊逻辑提取到条件判断中
5. **错误处理**:统一的错误处理机制
这次优化很好地体现了"简单就是美"的设计原则,通过工厂模式统一了服务获取,通过接口统一了服务调用,大大简化了代码结构,提高了可维护性。

View File

@@ -1,153 +0,0 @@
# 网盘服务单例模式使用问题修复
## 问题描述
`convertReadyResourceToResource` 函数中,我们使用了 `factory.CreatePanService()` 来获取网盘服务,但是没有正确配置就直接使用了。这导致单例服务没有正确的配置信息。
## 问题分析
### 原始代码问题
```go
// 问题代码
panService, err := factory.CreatePanService(readyResource.URL, config)
if err != nil {
log.Printf("创建网盘服务失败: %v", err)
return err
}
// 直接使用 panService但没有配置
result, err := panService.Transfer(shareID)
```
### 问题原因
1. 使用工厂模式创建服务,但单例服务需要先配置
2. 没有正确使用单例模式的 `UpdateConfig` 方法
3. 每次调用都创建新实例,失去了单例的优势
## 修复方案
### 修复后的代码
```go
// 修复后的代码
switch serviceType {
case panutils.Quark:
// 夸克网盘:使用单例服务
quarkService := panutils.GetQuarkInstance()
quarkService.UpdateConfig(config)
result, err := quarkService.Transfer(shareID)
// ... 处理结果
case panutils.Alipan:
// 阿里云盘:使用单例服务
alipanService := panutils.GetAlipanInstance()
alipanService.UpdateConfig(config)
result, err := alipanService.Transfer(shareID)
// ... 处理结果
}
```
## 修复内容
### 1. 直接使用单例服务
- 使用 `panutils.GetQuarkInstance()` 获取夸克网盘单例
- 使用 `panutils.GetAlipanInstance()` 获取阿里云盘单例
- 不再通过工厂创建服务实例
### 2. 正确配置服务
- 调用 `UpdateConfig(config)` 更新服务配置
- 确保每次处理前都有正确的配置信息
- 配置更新是线程安全的
### 3. 优化处理流程
- 先提取分享ID和服务类型
- 根据服务类型选择对应的单例服务
- 更新配置后执行转存操作
## 优势
### 1. 性能提升
- 减少服务实例创建开销
- 复用已创建的单例实例
- 零额外内存分配
### 2. 配置正确
- 每次处理前都更新配置
- 确保配置信息的准确性
- 支持动态配置更新
### 3. 线程安全
- 单例服务支持并发访问
- 配置更新使用读写锁保护
- 避免竞态条件
## 测试验证
### 编译测试
```bash
go build -o res_db .
# ✅ 编译成功
```
### 功能测试
```bash
go test ./utils/pan -v
# ✅ 所有测试通过
```
### 性能测试
```bash
go test ./utils/pan -bench=. -benchmem
# ✅ 性能良好,零内存分配
```
## 使用方式
### 获取单例服务
```go
// 夸克网盘单例
quarkService := panutils.GetQuarkInstance()
// 阿里云盘单例
alipanService := panutils.GetAlipanInstance()
```
### 更新配置
```go
config := &panutils.PanConfig{
URL: "https://pan.quark.cn/s/xxx",
Code: "1234",
IsType: 0,
ExpiredType: 1,
}
quarkService.UpdateConfig(config)
```
### 执行操作
```go
result, err := quarkService.Transfer(shareID)
if err != nil {
// 处理错误
}
```
## 注意事项
1. **配置更新时机**:每次使用前都要调用 `UpdateConfig`
2. **线程安全**:配置更新是线程安全的,可以并发调用
3. **单例特性**:多次调用返回相同实例
4. **错误处理**:需要检查 `Transfer` 方法的返回结果
## 总结
通过修复单例模式的使用问题,我们实现了:
1. ✅ 正确的单例服务使用
2. ✅ 准确的配置管理
3. ✅ 优秀的性能表现
4. ✅ 线程安全的并发访问
5. ✅ 完整的测试覆盖
现在 `convertReadyResourceToResource` 函数可以正确使用单例模式,既保证了性能又确保了配置的正确性。

View File

@@ -1,121 +0,0 @@
# 网盘服务单例模式实现总结
## 概述
本次实现了网盘服务的单例模式,包括工厂单例和服务单例,显著提升了系统性能和资源利用率。
## 实现内容
### 1. 工厂单例模式
- **文件**: `utils/pan/pan_factory.go`
- **实现**: 线程安全的单例工厂
- **性能**: 6.213 ns/op零内存分配
### 2. 服务单例模式
- **文件**: `utils/pan/quark_pan.go`, `utils/pan/alipan.go`
- **实现**: 支持动态配置更新的服务单例
- **特性**: 线程安全,配置热更新
### 3. 定时任务集成
- **文件**: `utils/scheduler.go`
- **修改**: 使用 `panutils.GetInstance()` 获取单例
- **效果**: 减少重复创建开销
## 性能对比
| 操作类型 | 性能 (ns/op) | 内存分配 |
|---------|-------------|----------|
| 单例创建 | 6.213 | 0 B/op |
| 工厂创建 | 7.765 | 0 B/op |
| 服务创建 | 107-132 | 0 B/op |
## 测试覆盖
### 功能测试
- ✅ 服务类型识别
- ✅ 分享ID提取
- ✅ 工厂模式创建
- ✅ 单例模式验证
### 并发测试
- ✅ 线程安全性
- ✅ 并发访问
- ✅ 配置更新
### 性能测试
- ✅ 创建性能
- ✅ 内存分配
- ✅ 并发性能
## 使用方式
### 获取工厂单例
```go
factory := panutils.GetInstance()
```
### 获取服务单例
```go
// 夸克网盘服务
quarkService := panutils.GetQuarkServiceInstance()
// 阿里云盘服务
alipanService := panutils.GetAlipanServiceInstance()
```
### 动态配置更新
```go
// 更新配置
quarkService.UpdateConfig(&panutils.PanConfig{
URL: "new_url",
Code: "new_code",
})
```
## 优势
1. **性能提升**: 减少重复创建开销
2. **内存优化**: 零额外内存分配
3. **线程安全**: 支持并发访问
4. **配置灵活**: 支持动态配置更新
5. **易于维护**: 统一的单例管理
## 注意事项
1. 单例模式适用于高频调用场景
2. 配置更新是线程安全的
3. 服务实例在首次调用时创建
4. 工厂单例在程序启动时初始化
## 后续优化建议
1. 考虑添加服务健康检查
2. 实现服务自动重连机制
3. 添加服务状态监控
4. 支持更多网盘服务类型
## 文件清单
- `utils/pan/pan_factory.go` - 工厂单例实现
- `utils/pan/quark_pan.go` - 夸克网盘服务单例
- `utils/pan/alipan.go` - 阿里云盘服务单例
- `utils/pan/service_singleton_test.go` - 服务单例测试
- `utils/pan/SERVICE_SINGLETON_ANALYSIS.md` - 详细分析文档
- `utils/scheduler.go` - 定时任务集成
## 测试命令
```bash
# 运行所有测试
go test ./utils/pan -v
# 运行性能测试
go test ./utils/pan -bench=. -benchmem
# 编译项目
go build -o res_db .
```
## 总结
网盘服务单例模式的实现成功提升了系统性能,特别是在定时任务等高频调用场景下。通过线程安全的单例模式,既保证了性能又确保了数据一致性。所有测试通过,项目编译成功,可以投入生产使用。

View File

@@ -1,166 +0,0 @@
# 夸克网盘 getShare 方法修复
## 问题描述
在夸克网盘的 `getShare` 函数中遇到了 HTTP 405 错误:
```
HTTP请求失败: 405, {"timestamp":1752368941648,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/1/clouddrive/share/sharepage/detail"}
```
## 问题分析
### 错误原因
- 当前Go代码使用 `HTTPPost` 方法请求 `/1/clouddrive/share/sharepage/detail` 接口
- 但服务器返回 405 错误,表示不支持 POST 方法
- 需要改为 GET 请求
### 对比原始PHP代码
通过对比 `demo/pan/QuarkPan.php` 中的 `getShare` 方法:
```php
public function getShare($pwd_id,$stoken)
{
$urlData = array();
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => "",
"pwd_id" => $pwd_id,
"stoken" => $stoken,
"pdir_fid" => "0",
"force" => "0",
"_page" => "1",
"_size" => "100",
"_fetch_banner" => "1",
"_fetch_share" => "1",
"_fetch_total" => "1",
"_sort" => "file_type:asc,updated_at:desc"
];
return $this->executeApiRequest(
"https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail",
"GET", // 使用 GET 方法
$urlData,
$queryParams
);
}
```
## 修复方案
### 修复前的代码
```go
// 修复前:使用 POST 请求
func (q *QuarkPanService) getShare(shareID, stoken string) (*ShareResult, error) {
data := map[string]interface{}{
"pwd_id": shareID,
"stoken": stoken,
}
queryParams := map[string]string{
"pr": "ucpro",
"fr": "pc",
"uc_param_str": "",
}
respData, err := q.HTTPPost("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", data, queryParams)
// ...
}
```
### 修复后的代码
```go
// 修复后:使用 GET 请求,参数放在 URL 中
func (q *QuarkPanService) getShare(shareID, stoken string) (*ShareResult, error) {
queryParams := map[string]string{
"pr": "ucpro",
"fr": "pc",
"uc_param_str": "",
"pwd_id": shareID,
"stoken": stoken,
"pdir_fid": "0",
"force": "0",
"_page": "1",
"_size": "100",
"_fetch_banner": "1",
"_fetch_share": "1",
"_fetch_total": "1",
"_sort": "file_type:asc,updated_at:desc",
}
respData, err := q.HTTPGet("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", queryParams)
// ...
}
```
## 修复内容
### 1. 请求方法修改
- **从 POST 改为 GET**`HTTPPost``HTTPGet`
- **参数传递方式**:从请求体改为 URL 查询参数
### 2. 参数结构调整
- **移除请求体数据**:不再使用 `data` 参数
- **添加完整查询参数**按照PHP代码添加所有必要的查询参数
- **参数顺序**保持与PHP代码一致的参数顺序
### 3. 参数说明
| 参数 | 说明 | 值 |
|------|------|-----|
| `pr` | 产品标识 | `ucpro` |
| `fr` | 来源标识 | `pc` |
| `uc_param_str` | UC参数 | 空字符串 |
| `pwd_id` | 分享ID | 从URL提取 |
| `stoken` | 安全令牌 | 从getStoken获取 |
| `pdir_fid` | 父目录ID | `0` |
| `force` | 强制标志 | `0` |
| `_page` | 页码 | `1` |
| `_size` | 页面大小 | `100` |
| `_fetch_banner` | 获取横幅 | `1` |
| `_fetch_share` | 获取分享信息 | `1` |
| `_fetch_total` | 获取总数 | `1` |
| `_sort` | 排序方式 | `file_type:asc,updated_at:desc` |
## 验证结果
### 编译测试
```bash
go build -o res_db .
# ✅ 编译成功
```
### 功能测试
```bash
go test ./utils/pan -v
# ✅ 所有测试通过
```
### 预期效果
- **解决 405 错误**:使用正确的 GET 请求方法
- **保持功能完整**:所有参数都正确传递
- **兼容性良好**与原始PHP代码行为一致
## 经验总结
### 1. API 兼容性
- 在翻译代码时,需要仔细对比原始实现的请求方法
- 不同语言的HTTP客户端可能有不同的默认行为
- 需要确保请求方法、参数位置、参数名称都完全一致
### 2. 错误排查
- HTTP 405 错误通常表示请求方法不正确
- 对比原始代码是排查此类问题的最佳方法
- 需要检查请求方法、URL、参数等多个方面
### 3. 最佳实践
- **保持一致性**:与原始实现保持完全一致
- **完整参数**:不要遗漏任何必要的参数
- **测试验证**:修复后要进行充分的测试
## 相关文件
- `utils/pan/quark_pan.go` - 修复的文件
- `demo/pan/QuarkPan.php` - 原始PHP实现参考
- `utils/pan/base_pan.go` - 基础HTTP方法实现
这次修复确保了夸克网盘的 `getShare` 方法与原始PHP实现完全一致解决了HTTP 405错误问题。

View File

@@ -1,245 +0,0 @@
# 待处理资源功能说明
## 概述
新增了 `ready_resource`用于批量添加和管理待处理的资源。支持两种输入格式系统会自动识别标题和URL。
## 数据库表结构
```sql
CREATE TABLE ready_resource (
id SERIAL PRIMARY KEY,
title VARCHAR(255), -- 标题(可选)
url VARCHAR(500) NOT NULL, -- URL必填
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip VARCHAR(45) DEFAULT NULL -- 客户端IP
);
```
## 功能特性
### 🔧 **批量添加支持**
1. **单个添加**
- 支持添加单个资源
- 标题可选URL必填
2. **JSON批量添加**
- 支持JSON格式批量添加
- 适合程序化操作
3. **文本批量添加**
- 支持纯文本格式
- 自动识别标题和URL
- 支持两种格式
### 📝 **输入格式**
#### 格式1标题和URL两行一组
```
电影标题1
https://pan.baidu.com/s/123456
电影标题2
https://pan.baidu.com/s/789012
```
#### 格式2只有URL
```
https://pan.baidu.com/s/123456
https://pan.baidu.com/s/789012
https://pan.baidu.com/s/345678
```
### 🌐 **URL自动识别**
系统会自动识别以下类型的URL
- HTTP/HTTPS链接
- FTP链接
- 磁力链接
- 百度网盘链接
- 阿里云盘链接
- 夸克网盘链接
- 天翼云盘链接
- 迅雷云盘链接
- 微云链接
- 蓝奏云链接
- 123云盘链接
- Google Drive链接
- Dropbox链接
- OneDrive链接
- 城通网盘链接
- 115网盘链接
- UC网盘链接
## API接口
### 1. 获取待处理资源列表
```http
GET /api/ready-resources
```
### 2. 创建单个待处理资源
```http
POST /api/ready-resources
Content-Type: application/json
{
"title": "",
"url": "https://pan.baidu.com/s/123456"
}
```
### 3. 批量创建待处理资源JSON格式
```http
POST /api/ready-resources/batch
Content-Type: application/json
{
"resources": [
{
"title": "1",
"url": "https://pan.baidu.com/s/111111"
},
{
"title": "2",
"url": "https://pan.baidu.com/s/222222"
},
{
"url": "https://pan.baidu.com/s/333333"
}
]
}
```
### 4. 从文本批量创建待处理资源
```http
POST /api/ready-resources/text
Content-Type: application/json
{
"text": "1\nhttps://pan.baidu.com/s/444444\n2\nhttps://pan.baidu.com/s/555555"
}
```
### 5. 删除单个待处理资源
```http
DELETE /api/ready-resources/{id}
```
### 6. 清空所有待处理资源
```http
DELETE /api/ready-resources
```
## 前端页面
### 待处理资源管理页面 (`/ready-resources`)
功能特性:
- 📋 显示所有待处理资源
- 批量添加功能
- 🗑️ 删除单个资源
- 🗑️ 清空所有资源
- 🔄 刷新数据
- 📊 统计信息
### 管理员页面集成
在管理员页面 (`/admin`) 新增了待处理资源模块:
- 快速访问待处理资源管理
- 批量添加资源入口
## 使用示例
### 1. 通过前端页面添加
1. 访问 `/ready-resources` 页面
2. 点击"批量添加"按钮
3. 在文本框中输入资源内容
4. 点击"批量添加"提交
### 2. 通过API添加
```bash
# 添加单个资源
curl -X POST http://localhost:8080/api/ready-resources \
-H "Content-Type: application/json" \
-d '{
"title": "测试电影",
"url": "https://pan.baidu.com/s/123456"
}'
# 批量添加(文本格式)
curl -X POST http://localhost:8080/api/ready-resources/text \
-H "Content-Type: application/json" \
-d '{
"text": "电影标题1\nhttps://pan.baidu.com/s/444444\n电影标题2\nhttps://pan.baidu.com/s/555555"
}'
```
## 工作流程
1. **批量添加** → 用户通过前端或API批量添加资源到 `ready_resource`
2. **自动处理** → 系统后续会自动处理这些资源(标题识别、平台判断等)
3. **正式资源** → 处理完成后移动到正式的 `resources`
## 技术实现
### 后端实现
1. **数据库模型** (`models/resource.go`)
- `ReadyResource` 结构体
- `CreateReadyResourceRequest` 请求结构
- `BatchCreateReadyResourceRequest` 批量请求结构
2. **Handlers** (`handlers/ready_resource.go`)
- `GetReadyResources` - 获取列表
- `CreateReadyResource` - 创建单个
- `BatchCreateReadyResources` - 批量创建JSON
- `CreateReadyResourcesFromText` - 从文本批量创建
- `DeleteReadyResource` - 删除单个
- `ClearReadyResources` - 清空所有
- `isURL` - URL识别函数
3. **路由配置** (`main.go`)
- 添加所有待处理资源相关的API路由
### 前端实现
1. **API调用** (`composables/useApi.ts`)
- `useReadyResourceApi` - 待处理资源API
2. **管理页面** (`pages/ready-resources.vue`)
- 完整的待处理资源管理界面
- 批量添加模态框
- 资源列表显示
- 操作功能
3. **管理员页面集成** (`pages/admin.vue`)
- 添加待处理资源模块
- 快速访问入口
## 测试
运行测试脚本验证功能:
```bash
./test-ready-resources.sh
```
## 注意事项
1. **URL识别**系统会自动识别常见网盘和文件分享平台的URL
2. **标题处理**如果只提供URL标题字段为空系统后续会自动处理
3. **IP记录**自动记录添加资源的客户端IP
4. **事务处理**:批量操作使用数据库事务确保数据一致性
5. **错误处理**:完善的错误处理和用户反馈
## 后续扩展
1. **自动处理**:实现自动将待处理资源转换为正式资源
2. **标题识别**通过URL内容自动识别资源标题
3. **平台分类**:自动识别资源所属平台
4. **重复检测**检测重复URL避免重复添加
5. **批量操作**:支持批量删除、批量移动等功能
这个功能为网盘资源管理系统提供了灵活的批量添加机制,支持多种输入格式,为后续的自动化处理奠定了基础。

View File

@@ -1,137 +0,0 @@
# 搜索统计功能
## 功能概述
搜索统计功能用于记录和分析用户的搜索行为,包括:
- 每日搜索量统计
- 热门关键词分析
- 搜索趋势图表
- 关键词热度排名
## 数据库设计
### 搜索统计表 (search_stats)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL | 主键 |
| keyword | VARCHAR(255) | 搜索关键词 |
| count | INTEGER | 搜索次数 |
| date | DATE | 搜索日期 |
| ip | VARCHAR(45) | 用户IP |
| user_agent | VARCHAR(500) | 用户代理 |
| created_at | TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | 更新时间 |
## API接口
### 1. 记录搜索
```
POST /api/search-stats/record
Content-Type: application/json
{
"keyword": "搜索关键词"
}
```
### 2. 获取搜索统计总览
```
GET /api/search-stats
```
返回数据:
```json
{
"today_searches": 10,
"week_searches": 50,
"month_searches": 200,
"hot_keywords": [
{
"keyword": "关键词",
"count": 15,
"rank": 1
}
],
"daily_stats": [...],
"search_trend": {
"days": ["01-01", "01-02"],
"values": [10, 15]
}
}
```
### 3. 获取热门关键词
```
GET /api/search-stats/hot-keywords?days=30&limit=10
```
参数:
- days: 统计天数默认30
- limit: 返回数量限制默认10
### 4. 获取每日统计
```
GET /api/search-stats/daily?days=30
```
### 5. 获取搜索趋势
```
GET /api/search-stats/trend?days=30
```
### 6. 获取关键词趋势
```
GET /api/search-stats/keyword/{keyword}/trend?days=30
```
## 前端页面
### 搜索统计页面 (/search-stats)
功能特性:
- 今日/本周/本月搜索量统计卡片
- 搜索趋势折线图
- 热门关键词排行榜
- 关键词热度可视化
## 自动记录
系统会在以下情况下自动记录搜索:
- 用户使用搜索功能时
- 记录用户IP和User-Agent信息
- 按日期聚合统计
## 使用说明
1. **管理员访问**:登录后可在管理员页面看到"搜索统计"模块
2. **查看统计**:点击"查看搜索统计"进入详细页面
3. **热门关键词**:查看最受欢迎的关键词排名
4. **趋势分析**:通过图表了解搜索量变化趋势
## 测试
运行测试脚本:
```bash
chmod +x test-search-stats.sh
./test-search-stats.sh
```
## 技术实现
### 后端
- 使用Repository模式管理数据访问
- 支持按日期聚合统计
- 提供多种统计维度API
### 前端
- 使用Chart.js绘制趋势图表
- 响应式设计适配不同设备
- 实时数据更新
## 注意事项
1. 搜索记录会保存用户IP注意隐私保护
2. 大量搜索数据可能影响性能,建议定期清理
3. 关键词统计按天聚合,避免重复记录
4. 图表数据需要足够的历史数据才能显示趋势

View File

@@ -1,151 +0,0 @@
# 表结构变更说明
## 概述
根据需求,我们对 `resources` 表进行了重大结构调整,以支持一个资源对应一个 URL 的模式,并添加了平台标识功能。
## 主要变更
### 1. Resources 表结构变更
**变更前:**
- `url` 字段JSON 格式,存储多个链接
- 一个资源记录包含多个链接
**变更后:**
- `url` 字段VARCHAR(128),存储单个链接
- 新增 `pan_id` 字段INTEGER关联到 `pan` 表,标识链接类型
- 一个资源对应一个链接,多个链接需要创建多条记录
### 2. 新增 Pan 表
```sql
CREATE TABLE pan (
id SERIAL PRIMARY KEY,
name VARCHAR(64) DEFAULT NULL,
key INTEGER DEFAULT NULL,
ck TEXT,
is_valid BOOLEAN DEFAULT true,
space BIGINT DEFAULT 0,
left_space BIGINT DEFAULT 0,
remark VARCHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 3. API 变更
#### 资源相关 API
**新增查询参数:**
- `pan_id`按平台ID筛选资源
**请求体变更:**
```json
{
"title": "资源标题",
"description": "资源描述",
"url": "https://pan.baidu.com/s/123456",
"pan_id": 1, // 新增平台ID
"quark_url": "",
"file_size": "100MB",
"category_id": 1,
"is_valid": true,
"is_public": true,
"tag_ids": [1, 2]
}
```
#### 平台相关 API
**新增 API 端点:**
- `GET /api/pans` - 获取平台列表
- `GET /api/pans/:id` - 获取单个平台
- `POST /api/pans` - 创建平台
- `PUT /api/pans/:id` - 更新平台
- `DELETE /api/pans/:id` - 删除平台
## 前端变更
### useApi.ts 新增方法
```typescript
// 按平台ID获取资源
const getResourcesByPan = async (panId: number, params?: any) => {
return await $fetch('/resources', {
baseURL: config.public.apiBase,
params: { ...params, pan_id: panId }
})
}
```
## 数据迁移建议
1. **备份现有数据**
```sql
-- 备份现有资源表
CREATE TABLE resources_backup AS SELECT * FROM resources;
```
2. **创建新的表结构**
- 运行更新后的数据库初始化代码
3. **数据迁移策略**
- 对于每个资源的多个链接,创建多条记录
- 为每条记录分配适当的 `pan_id`
- 保持原有的标签关联
## 使用示例
### 创建平台
```bash
curl -X POST http://localhost:8080/api/pans \
-H "Content-Type: application/json" \
-d '{
"name": "百度网盘",
"key": 1,
"ck": "test_ck",
"is_valid": true,
"space": 2048,
"left_space": 1024,
"remark": "百度网盘平台"
}'
```
### 创建资源
```bash
curl -X POST http://localhost:8080/api/resources \
-H "Content-Type: application/json" \
-d '{
"title": "测试资源",
"description": "这是一个测试资源",
"url": "https://pan.baidu.com/s/123456",
"pan_id": 1,
"quark_url": "",
"file_size": "100MB",
"category_id": 1,
"is_valid": true,
"is_public": true,
"tag_ids": [1]
}'
```
### 按平台查询资源
```bash
curl "http://localhost:8080/api/resources?pan_id=1"
```
## 注意事项
1. **URL 长度限制**URL 字段现在限制为 128 字符
2. **平台关联**每个资源必须关联到一个平台pan_id
3. **数据完整性**:确保在创建资源时提供有效的 pan_id
4. **向后兼容**API 响应格式保持兼容,只是新增了 pan_id 字段
## 测试
运行测试脚本验证新功能:
```bash
./test-setup.sh
```

View File

@@ -1,211 +0,0 @@
# Vue 3 + Nuxt.js UI框架选择指南
## 🎨 推荐的UI框架
### 1. **Naive UI** ⭐⭐⭐⭐⭐ (强烈推荐)
**特点**: 完整的Vue 3组件库TypeScript支持主题定制
**优势**:
- ✅ 完整的Vue 3支持
- ✅ TypeScript原生支持
- ✅ 组件丰富80+组件)
- ✅ 主题系统强大
- ✅ 文档完善
- ✅ 性能优秀
- ✅ 活跃维护
**适用场景**: 企业级应用,复杂界面,需要高度定制
**安装**:
```bash
npm install naive-ui vfonts @vicons/ionicons5
```
### 2. **Element Plus** ⭐⭐⭐⭐
**特点**: Vue 3版本的Element UI成熟稳定
**优势**:
- ✅ 社区活跃
- ✅ 组件齐全
- ✅ 文档详细
- ✅ 成熟稳定
- ✅ 中文文档
**适用场景**: 后台管理系统,快速开发
**安装**:
```bash
npm install element-plus @element-plus/icons-vue
```
### 3. **Ant Design Vue** ⭐⭐⭐⭐
**特点**: 企业级UI设计语言
**优势**:
- ✅ 设计规范统一
- ✅ 组件丰富
- ✅ 企业级应用
- ✅ 国际化支持
**适用场景**: 企业应用,设计规范要求高
**安装**:
```bash
npm install ant-design-vue @ant-design/icons-vue
```
### 4. **PrimeVue** ⭐⭐⭐
**特点**: 丰富的组件库,支持多种主题
**优势**:
- ✅ 组件数量多
- ✅ 功能强大
- ✅ 主题丰富
- ✅ 响应式设计
**适用场景**: 复杂业务场景
**安装**:
```bash
npm install primevue primeicons
```
### 5. **Vuetify** ⭐⭐⭐
**特点**: Material Design风格
**优势**:
- ✅ 设计美观
- ✅ 响应式好
- ✅ Material Design
- ✅ 组件丰富
**适用场景**: 现代化应用Material Design风格
**安装**:
```bash
npm install vuetify @mdi/font
```
## 🚀 当前项目推荐
### 推荐使用 **Naive UI**
**原因**:
1. **Vue 3原生支持**: 完全基于Vue 3 Composition API
2. **TypeScript友好**: 原生TypeScript支持
3. **组件丰富**: 满足资源管理系统需求
4. **主题系统**: 支持深色/浅色主题切换
5. **性能优秀**: 按需加载,体积小
### 集成步骤
1. **安装依赖**:
```bash
cd web
npm install naive-ui vfonts @vicons/ionicons5 @css-render/vue3-ssr @juggle/resize-observer
```
2. **配置Nuxt**:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
build: {
transpile: ['naive-ui', 'vueuc', '@css-render/vue3-ssr', '@juggle/resize-observer']
}
})
```
3. **创建插件**:
```typescript
// plugins/naive-ui.client.ts
import { setup } from '@css-render/vue3-ssr'
export default defineNuxtPlugin((nuxtApp) => {
if (process.server) {
const { collect } = setup(nuxtApp.vueApp)
// SSR配置
}
})
```
4. **使用组件**:
```vue
<template>
<n-config-provider :theme="theme">
<n-card>
<n-button type="primary">按钮</n-button>
</n-card>
</n-config-provider>
</template>
```
## 📊 框架对比表
| 特性 | Naive UI | Element Plus | Ant Design Vue | PrimeVue | Vuetify |
|------|----------|--------------|----------------|----------|---------|
| Vue 3支持 | ✅ | ✅ | ✅ | ✅ | ✅ |
| TypeScript | ✅ | ✅ | ✅ | ✅ | ✅ |
| 组件数量 | 80+ | 60+ | 60+ | 90+ | 80+ |
| 主题系统 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 中文文档 | ✅ | ✅ | ✅ | ❌ | ❌ |
| 社区活跃度 | 高 | 很高 | 高 | 中 | 中 |
| 学习曲线 | 低 | 低 | 中 | 中 | 中 |
| 性能 | 优秀 | 良好 | 良好 | 良好 | 良好 |
## 🎯 针对资源管理系统的建议
### 核心组件需求
1. **数据表格**: 资源列表展示
2. **表单组件**: 资源添加/编辑
3. **模态框**: 弹窗操作
4. **搜索组件**: 资源搜索
5. **标签组件**: 资源标签
6. **统计卡片**: 数据展示
7. **分页组件**: 列表分页
### Naive UI优势
- **n-data-table**: 功能强大的数据表格
- **n-form**: 完整的表单解决方案
- **n-modal**: 灵活的模态框
- **n-input**: 搜索输入框
- **n-tag**: 标签组件
- **n-card**: 统计卡片
- **n-pagination**: 分页组件
## 🔧 迁移指南
如果要从当前的基础组件迁移到Naive UI
1. **替换基础组件**:
```vue
<!-- 原版 -->
<button class="btn-primary">按钮</button>
<!-- Naive UI -->
<n-button type="primary">按钮</n-button>
```
2. **替换表单组件**:
```vue
<!-- 原版 -->
<input class="input-field" />
<!-- Naive UI -->
<n-input />
```
3. **替换模态框**:
```vue
<!-- 原版 -->
<div class="modal">...</div>
<!-- Naive UI -->
<n-modal>...</n-modal>
```
## 📝 总结
对于您的资源管理系统项目,我强烈推荐使用 **Naive UI**,因为:
1. **完美适配**: 完全支持Vue 3和Nuxt.js
2. **功能完整**: 提供所有需要的组件
3. **开发效率**: 减少大量自定义样式工作
4. **维护性好**: TypeScript支持代码更可靠
5. **性能优秀**: 按需加载,体积小
使用UI框架可以节省70-80%的前端开发时间,让您专注于业务逻辑而不是样式细节。

View File

@@ -1,379 +0,0 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/public/hot-dramas": {
"get": {
"description": "获取热门剧列表,支持分页",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "获取热门剧列表",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"maximum": 100,
"type": "integer",
"default": 20,
"description": "每页数量",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "获取成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/public/resources/add": {
"post": {
"description": "通过公开API添加单个资源到待处理列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "单个添加资源",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"description": "资源信息",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ReadyResourceRequest"
}
}
],
"responses": {
"200": {
"description": "添加成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "请求参数错误",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/public/resources/batch-add": {
"post": {
"description": "通过公开API批量添加多个资源到待处理列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "批量添加资源",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"description": "批量资源信息",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BatchReadyResourceRequest"
}
}
],
"responses": {
"200": {
"description": "批量添加成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "请求参数错误",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/public/resources/search": {
"get": {
"description": "搜索资源,支持关键词、标签、分类过滤",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "资源搜索",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"type": "string",
"description": "搜索关键词",
"name": "keyword",
"in": "query"
},
{
"type": "string",
"description": "标签过滤",
"name": "tag",
"in": "query"
},
{
"type": "string",
"description": "分类过滤",
"name": "category",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"maximum": 100,
"type": "integer",
"default": 20,
"description": "每页数量",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "搜索成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
},
"definitions": {
"dto.BatchReadyResourceRequest": {
"type": "object",
"required": [
"resources"
],
"properties": {
"resources": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.ReadyResourceRequest"
}
}
}
},
"dto.ReadyResourceRequest": {
"type": "object",
"required": [
"title",
"url"
],
"properties": {
"category": {
"type": "string",
"example": "示例分类"
},
"description": {
"type": "string",
"example": "这是一个示例资源描述"
},
"extra": {
"type": "string",
"example": "额外信息"
},
"img": {
"type": "string",
"example": "https://example.com/image.jpg"
},
"source": {
"type": "string",
"example": "数据来源"
},
"tags": {
"type": "string",
"example": "标签1,标签2"
},
"title": {
"type": "string",
"example": "示例资源标题"
},
"url": {
"type": "string",
"example": "https://example.com/resource"
}
}
}
},
"securityDefinitions": {
"ApiTokenAuth": {
"description": "API Token认证",
"type": "apiKey",
"name": "X-API-Token",
"in": "header"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/api/public",
Schemes: []string{},
Title: "网盘资源管理系统公开API",
Description: "网盘资源管理系统的公开API接口文档",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@@ -1,355 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "网盘资源管理系统的公开API接口文档",
"title": "网盘资源管理系统公开API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "localhost:8080",
"basePath": "/api/public",
"paths": {
"/api/public/hot-dramas": {
"get": {
"description": "获取热门剧列表,支持分页",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "获取热门剧列表",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"maximum": 100,
"type": "integer",
"default": 20,
"description": "每页数量",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "获取成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/public/resources/add": {
"post": {
"description": "通过公开API添加单个资源到待处理列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "单个添加资源",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"description": "资源信息",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ReadyResourceRequest"
}
}
],
"responses": {
"200": {
"description": "添加成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "请求参数错误",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/public/resources/batch-add": {
"post": {
"description": "通过公开API批量添加多个资源到待处理列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "批量添加资源",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"description": "批量资源信息",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BatchReadyResourceRequest"
}
}
],
"responses": {
"200": {
"description": "批量添加成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "请求参数错误",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/public/resources/search": {
"get": {
"description": "搜索资源,支持关键词、标签、分类过滤",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PublicAPI"
],
"summary": "资源搜索",
"parameters": [
{
"type": "string",
"description": "API访问令牌",
"name": "X-API-Token",
"in": "header",
"required": true
},
{
"type": "string",
"description": "搜索关键词",
"name": "keyword",
"in": "query"
},
{
"type": "string",
"description": "标签过滤",
"name": "tag",
"in": "query"
},
{
"type": "string",
"description": "分类过滤",
"name": "category",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"maximum": 100,
"type": "integer",
"default": 20,
"description": "每页数量",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "搜索成功",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "认证失败",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
},
"definitions": {
"dto.BatchReadyResourceRequest": {
"type": "object",
"required": [
"resources"
],
"properties": {
"resources": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.ReadyResourceRequest"
}
}
}
},
"dto.ReadyResourceRequest": {
"type": "object",
"required": [
"title",
"url"
],
"properties": {
"category": {
"type": "string",
"example": "示例分类"
},
"description": {
"type": "string",
"example": "这是一个示例资源描述"
},
"extra": {
"type": "string",
"example": "额外信息"
},
"img": {
"type": "string",
"example": "https://example.com/image.jpg"
},
"source": {
"type": "string",
"example": "数据来源"
},
"tags": {
"type": "string",
"example": "标签1,标签2"
},
"title": {
"type": "string",
"example": "示例资源标题"
},
"url": {
"type": "string",
"example": "https://example.com/resource"
}
}
}
},
"securityDefinitions": {
"ApiTokenAuth": {
"description": "API Token认证",
"type": "apiKey",
"name": "X-API-Token",
"in": "header"
}
}
}

View File

@@ -1,246 +0,0 @@
basePath: /api/public
definitions:
dto.BatchReadyResourceRequest:
properties:
resources:
items:
$ref: '#/definitions/dto.ReadyResourceRequest'
type: array
required:
- resources
type: object
dto.ReadyResourceRequest:
properties:
category:
example: 示例分类
type: string
description:
example: 这是一个示例资源描述
type: string
extra:
example: 额外信息
type: string
img:
example: https://example.com/image.jpg
type: string
source:
example: 数据来源
type: string
tags:
example: 标签1,标签2
type: string
title:
example: 示例资源标题
type: string
url:
example: https://example.com/resource
type: string
required:
- title
- url
type: object
host: localhost:8080
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: 网盘资源管理系统的公开API接口文档
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: 网盘资源管理系统公开API
version: "1.0"
paths:
/api/public/hot-dramas:
get:
consumes:
- application/json
description: 获取热门剧列表,支持分页
parameters:
- description: API访问令牌
in: header
name: X-API-Token
required: true
type: string
- default: 1
description: 页码
in: query
name: page
type: integer
- default: 20
description: 每页数量
in: query
maximum: 100
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: 获取成功
schema:
additionalProperties: true
type: object
"401":
description: 认证失败
schema:
additionalProperties: true
type: object
"500":
description: 服务器内部错误
schema:
additionalProperties: true
type: object
summary: 获取热门剧列表
tags:
- PublicAPI
/api/public/resources/add:
post:
consumes:
- application/json
description: 通过公开API添加单个资源到待处理列表
parameters:
- description: API访问令牌
in: header
name: X-API-Token
required: true
type: string
- description: 资源信息
in: body
name: data
required: true
schema:
$ref: '#/definitions/dto.ReadyResourceRequest'
produces:
- application/json
responses:
"200":
description: 添加成功
schema:
additionalProperties: true
type: object
"400":
description: 请求参数错误
schema:
additionalProperties: true
type: object
"401":
description: 认证失败
schema:
additionalProperties: true
type: object
"500":
description: 服务器内部错误
schema:
additionalProperties: true
type: object
summary: 单个添加资源
tags:
- PublicAPI
/api/public/resources/batch-add:
post:
consumes:
- application/json
description: 通过公开API批量添加多个资源到待处理列表
parameters:
- description: API访问令牌
in: header
name: X-API-Token
required: true
type: string
- description: 批量资源信息
in: body
name: data
required: true
schema:
$ref: '#/definitions/dto.BatchReadyResourceRequest'
produces:
- application/json
responses:
"200":
description: 批量添加成功
schema:
additionalProperties: true
type: object
"400":
description: 请求参数错误
schema:
additionalProperties: true
type: object
"401":
description: 认证失败
schema:
additionalProperties: true
type: object
"500":
description: 服务器内部错误
schema:
additionalProperties: true
type: object
summary: 批量添加资源
tags:
- PublicAPI
/api/public/resources/search:
get:
consumes:
- application/json
description: 搜索资源,支持关键词、标签、分类过滤
parameters:
- description: API访问令牌
in: header
name: X-API-Token
required: true
type: string
- description: 搜索关键词
in: query
name: keyword
type: string
- description: 标签过滤
in: query
name: tag
type: string
- description: 分类过滤
in: query
name: category
type: string
- default: 1
description: 页码
in: query
name: page
type: integer
- default: 20
description: 每页数量
in: query
maximum: 100
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: 搜索成功
schema:
additionalProperties: true
type: object
"401":
description: 认证失败
schema:
additionalProperties: true
type: object
"500":
description: 服务器内部错误
schema:
additionalProperties: true
type: object
summary: 资源搜索
tags:
- PublicAPI
securityDefinitions:
ApiTokenAuth:
description: API Token认证
in: header
name: X-API-Token
type: apiKey
swagger: "2.0"

27
main.go
View File

@@ -1,23 +1,3 @@
// @title 网盘资源管理系统公开API
// @version 1.0
// @description 网盘资源管理系统的公开API接口文档
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/public
// @securityDefinitions.apikey ApiTokenAuth
// @in header
// @name X-API-Token
// @description API Token认证
package main
import (
@@ -30,13 +10,9 @@ import (
"res_db/handlers"
"res_db/middleware"
_ "res_db/docs"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func main() {
@@ -220,9 +196,6 @@ func main() {
// 静态文件服务
r.Static("/uploads", "./uploads")
// 注册Swagger UI路由
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
port := os.Getenv("PORT")
if port == "" {
port = "8080"

View File

@@ -1,17 +0,0 @@
-- 为system_configs表添加api_token字段
-- 执行时间: 2024-12-19
-- 添加api_token字段
ALTER TABLE system_configs
ADD COLUMN api_token VARCHAR(100) UNIQUE;
-- 为现有记录生成默认的api_token
UPDATE system_configs
SET api_token = CONCAT('api_', MD5(RANDOM()::text), '_', EXTRACT(EPOCH FROM NOW())::bigint)
WHERE api_token IS NULL;
-- 添加索引以提高查询性能
CREATE INDEX idx_system_configs_api_token ON system_configs(api_token);
-- 添加注释
COMMENT ON COLUMN system_configs.api_token IS '公开API访问令牌用于API认证';

View File

@@ -1,6 +0,0 @@
{
"devDependencies": {
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0"
}
}

574
pnpm-lock.yaml generated
View File

@@ -1,574 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
unplugin-auto-import:
specifier: ^19.3.0
version: 19.3.0
unplugin-vue-components:
specifier: ^28.8.0
version: 28.8.0(@babel/parser@7.28.0)(vue@3.5.17)
packages:
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.27.1':
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.28.0':
resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/types@7.28.0':
resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==}
engines: {node: '>=6.9.0'}
'@jridgewell/sourcemap-codec@1.5.4':
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@vue/compiler-core@3.5.17':
resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==}
'@vue/compiler-dom@3.5.17':
resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==}
'@vue/compiler-sfc@3.5.17':
resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==}
'@vue/compiler-ssr@3.5.17':
resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==}
'@vue/reactivity@3.5.17':
resolution: {integrity: sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==}
'@vue/runtime-core@3.5.17':
resolution: {integrity: sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==}
'@vue/runtime-dom@3.5.17':
resolution: {integrity: sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==}
'@vue/server-renderer@3.5.17':
resolution: {integrity: sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==}
peerDependencies:
vue: 3.5.17
'@vue/shared@3.5.17':
resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
exsolve@1.0.7:
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
fdir@6.4.6:
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
local-pkg@1.1.1:
resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==}
engines: {node: '>=14'}
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
mlly@1.7.4:
resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
picomatch@4.0.2:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.2.0:
resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
quansync@0.2.10:
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
strip-literal@3.0.0:
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
tinyglobby@0.2.14:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
unimport@4.2.0:
resolution: {integrity: sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==}
engines: {node: '>=18.12.0'}
unplugin-auto-import@19.3.0:
resolution: {integrity: sha512-iIi0u4Gq2uGkAOGqlPJOAMI8vocvjh1clGTfSK4SOrJKrt+tirrixo/FjgBwXQNNdS7ofcr7OxzmOb/RjWxeEQ==}
engines: {node: '>=14'}
peerDependencies:
'@nuxt/kit': ^3.2.2
'@vueuse/core': '*'
peerDependenciesMeta:
'@nuxt/kit':
optional: true
'@vueuse/core':
optional: true
unplugin-utils@0.2.4:
resolution: {integrity: sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==}
engines: {node: '>=18.12.0'}
unplugin-vue-components@28.8.0:
resolution: {integrity: sha512-2Q6ZongpoQzuXDK0ZsVzMoshH0MWZQ1pzVL538G7oIDKRTVzHjppBDS8aB99SADGHN3lpGU7frraCG6yWNoL5Q==}
engines: {node: '>=14'}
peerDependencies:
'@babel/parser': ^7.15.8
'@nuxt/kit': ^3.2.2 || ^4.0.0
vue: 2 || 3
peerDependenciesMeta:
'@babel/parser':
optional: true
'@nuxt/kit':
optional: true
unplugin@2.3.5:
resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==}
engines: {node: '>=18.12.0'}
vue@3.5.17:
resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
snapshots:
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.27.1': {}
'@babel/parser@7.28.0':
dependencies:
'@babel/types': 7.28.0
'@babel/types@7.28.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@jridgewell/sourcemap-codec@1.5.4': {}
'@types/estree@1.0.8': {}
'@vue/compiler-core@3.5.17':
dependencies:
'@babel/parser': 7.28.0
'@vue/shared': 3.5.17
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.17':
dependencies:
'@vue/compiler-core': 3.5.17
'@vue/shared': 3.5.17
'@vue/compiler-sfc@3.5.17':
dependencies:
'@babel/parser': 7.28.0
'@vue/compiler-core': 3.5.17
'@vue/compiler-dom': 3.5.17
'@vue/compiler-ssr': 3.5.17
'@vue/shared': 3.5.17
estree-walker: 2.0.2
magic-string: 0.30.17
postcss: 8.5.6
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.17':
dependencies:
'@vue/compiler-dom': 3.5.17
'@vue/shared': 3.5.17
'@vue/reactivity@3.5.17':
dependencies:
'@vue/shared': 3.5.17
'@vue/runtime-core@3.5.17':
dependencies:
'@vue/reactivity': 3.5.17
'@vue/shared': 3.5.17
'@vue/runtime-dom@3.5.17':
dependencies:
'@vue/reactivity': 3.5.17
'@vue/runtime-core': 3.5.17
'@vue/shared': 3.5.17
csstype: 3.1.3
'@vue/server-renderer@3.5.17(vue@3.5.17)':
dependencies:
'@vue/compiler-ssr': 3.5.17
'@vue/shared': 3.5.17
vue: 3.5.17
'@vue/shared@3.5.17': {}
acorn@8.15.0: {}
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
binary-extensions@2.3.0: {}
braces@3.0.3:
dependencies:
fill-range: 7.1.1
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
braces: 3.0.3
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
confbox@0.1.8: {}
confbox@0.2.2: {}
csstype@3.1.3: {}
debug@4.4.1:
dependencies:
ms: 2.1.3
entities@4.5.0: {}
escape-string-regexp@5.0.0: {}
estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
exsolve@1.0.7: {}
fdir@6.4.6(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
fsevents@2.3.3:
optional: true
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
js-tokens@9.0.1: {}
local-pkg@1.1.1:
dependencies:
mlly: 1.7.4
pkg-types: 2.2.0
quansync: 0.2.10
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.4
mlly@1.7.4:
dependencies:
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
ms@2.1.3: {}
nanoid@3.3.11: {}
normalize-path@3.0.0: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
picomatch@4.0.2: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.7.4
pathe: 2.0.3
pkg-types@2.2.0:
dependencies:
confbox: 0.2.2
exsolve: 1.0.7
pathe: 2.0.3
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
quansync@0.2.10: {}
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
scule@1.3.0: {}
source-map-js@1.2.1: {}
strip-literal@3.0.0:
dependencies:
js-tokens: 9.0.1
tinyglobby@0.2.14:
dependencies:
fdir: 6.4.6(picomatch@4.0.2)
picomatch: 4.0.2
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
ufo@1.6.1: {}
unimport@4.2.0:
dependencies:
acorn: 8.15.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
local-pkg: 1.1.1
magic-string: 0.30.17
mlly: 1.7.4
pathe: 2.0.3
picomatch: 4.0.2
pkg-types: 2.2.0
scule: 1.3.0
strip-literal: 3.0.0
tinyglobby: 0.2.14
unplugin: 2.3.5
unplugin-utils: 0.2.4
unplugin-auto-import@19.3.0:
dependencies:
local-pkg: 1.1.1
magic-string: 0.30.17
picomatch: 4.0.2
unimport: 4.2.0
unplugin: 2.3.5
unplugin-utils: 0.2.4
unplugin-utils@0.2.4:
dependencies:
pathe: 2.0.3
picomatch: 4.0.2
unplugin-vue-components@28.8.0(@babel/parser@7.28.0)(vue@3.5.17):
dependencies:
chokidar: 3.6.0
debug: 4.4.1
local-pkg: 1.1.1
magic-string: 0.30.17
mlly: 1.7.4
tinyglobby: 0.2.14
unplugin: 2.3.5
unplugin-utils: 0.2.4
vue: 3.5.17
optionalDependencies:
'@babel/parser': 7.28.0
transitivePeerDependencies:
- supports-color
unplugin@2.3.5:
dependencies:
acorn: 8.15.0
picomatch: 4.0.2
webpack-virtual-modules: 0.6.2
vue@3.5.17:
dependencies:
'@vue/compiler-dom': 3.5.17
'@vue/compiler-sfc': 3.5.17
'@vue/runtime-dom': 3.5.17
'@vue/server-renderer': 3.5.17(vue@3.5.17)
'@vue/shared': 3.5.17
webpack-virtual-modules@0.6.2: {}

View File

@@ -1,48 +0,0 @@
#!/bin/bash
echo "🚀 启动资源管理系统..."
# 检查Go是否安装
if ! command -v go &> /dev/null; then
echo "❌ Go未安装请先安装Go"
exit 1
fi
# 检查Node.js是否安装
if ! command -v node &> /dev/null; then
echo "❌ Node.js未安装请先安装Node.js"
exit 1
fi
# 检查PostgreSQL是否运行
if ! pg_isready -q; then
echo "⚠️ PostgreSQL未运行请确保PostgreSQL服务已启动"
fi
echo "📦 安装Go依赖..."
go mod tidy
echo "🌐 启动后端服务器..."
go run main.go &
BACKEND_PID=$!
echo "⏳ 等待后端启动..."
sleep 3
echo "📦 安装前端依赖..."
cd web
npm install
echo "🎨 启动前端开发服务器..."
npm run dev &
FRONTEND_PID=$!
echo "✅ 系统启动完成!"
echo "📱 前端地址: http://localhost:3000"
echo "🔧 后端地址: http://localhost:8080"
echo ""
echo "按 Ctrl+C 停止服务"
# 等待用户中断
trap "echo '🛑 正在停止服务...'; kill $BACKEND_PID $FRONTEND_PID; exit" INT
wait

View File

@@ -1,24 +0,0 @@
#!/bin/bash
echo "=== 测试豆瓣服务获取全部数据功能 ==="
echo "时间: $(date)"
echo
# 测试电影榜单获取全部数据
echo "1. 测试电影榜单获取全部数据..."
curl -s "http://localhost:8080/api/hot-dramas/movies?category=热门&type=全部&start=0&limit=0" | jq '.'
echo
# 测试电视剧榜单获取全部数据
echo "2. 测试电视剧榜单获取全部数据..."
curl -s "http://localhost:8080/api/hot-dramas/tv?category=tv&type=tv&start=0&limit=0" | jq '.'
echo
# 测试调度器处理全部数据
echo "3. 测试调度器处理全部数据..."
echo "检查日志中的数据处理情况..."
echo "应该看到类似 '检测到limit=0将尝试获取全部数据' 的日志"
echo
echo "=== 测试完成 ==="
echo "如果看到 '检测到总数为: XXX将一次性获取全部数据' 的日志,说明功能正常"

View File

@@ -1,55 +0,0 @@
#!/bin/bash
echo "=== 测试原始豆瓣API ==="
echo "时间: $(date)"
echo
# 设置请求头
HEADERS=(
"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"
"Referer: https://m.douban.com/"
"Accept: application/json, text/plain, */*"
"Accept-Language: zh-CN,zh;q=0.9,en;q=0.8"
"Accept-Encoding: gzip, deflate"
"Connection: keep-alive"
"Sec-Fetch-Dest: empty"
"Sec-Fetch-Mode: cors"
"Sec-Fetch-Site: same-origin"
)
# 构建请求头字符串
HEADER_STR=""
for header in "${HEADERS[@]}"; do
HEADER_STR="$HEADER_STR -H \"$header\""
done
# 测试电影榜单API
echo "1. 测试电影榜单API获取前10条..."
MOVIE_URL="https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=10&category=热门&type=全部"
echo "请求URL: $MOVIE_URL"
echo
eval "curl -s $HEADER_STR \"$MOVIE_URL\" | jq '.'"
echo
# 测试电视剧榜单API
echo "2. 测试电视剧榜单API获取前10条..."
TV_URL="https://m.douban.com/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=10&category=tv&type=tv"
echo "请求URL: $TV_URL"
echo
eval "curl -s $HEADER_STR \"$TV_URL\" | jq '.'"
echo
# 测试获取总数
echo "3. 测试获取电影总数..."
TOTAL_URL="https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=1&category=热门&type=全部"
echo "请求URL: $TOTAL_URL"
echo
eval "curl -s $HEADER_STR \"$TOTAL_URL\" | jq '.total'"
echo
echo "=== 测试完成 ==="
echo "如果看到JSON响应数据说明API配置正确"
echo "如果返回403错误请检查请求头配置"