mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 19:37:33 +08:00
update u
This commit is contained in:
295
cmd/google-index/main.go
Normal file
295
cmd/google-index/main.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/pkg/google"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
printUsage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := os.Args[1]
|
||||||
|
config := &google.Config{
|
||||||
|
CredentialsFile: "credentials.json",
|
||||||
|
SiteURL: "https://your-site.com/", // 替换为你的网站
|
||||||
|
TokenFile: "token.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := google.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("创建Google客户端失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case "inspect":
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Println("请提供要检查的URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inspectSingleURL(client, os.Args[2])
|
||||||
|
|
||||||
|
case "batch":
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Println("请提供包含URL列表的文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batchInspectURLs(client, os.Args[2])
|
||||||
|
|
||||||
|
case "sites":
|
||||||
|
listSites(client)
|
||||||
|
|
||||||
|
case "analytics":
|
||||||
|
getAnalytics(client)
|
||||||
|
|
||||||
|
case "sitemap":
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Println("请提供网站地图URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submitSitemap(client, os.Args[2])
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Printf("未知命令: %s\n", command)
|
||||||
|
printUsage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUsage() {
|
||||||
|
fmt.Println("Google Search Console API 工具")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("用法:")
|
||||||
|
fmt.Println(" google-index inspect <url> - 检查单个URL的索引状态")
|
||||||
|
fmt.Println(" google-index batch <file> - 批量检查URL状态")
|
||||||
|
fmt.Println(" google-index sites - 列出已验证的网站")
|
||||||
|
fmt.Println(" google-index analytics - 获取搜索分析数据")
|
||||||
|
fmt.Println(" google-index sitemap <url> - 提交网站地图")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("配置:")
|
||||||
|
fmt.Println(" - 创建 credentials.json 文件 (Google Cloud Console 下载)")
|
||||||
|
fmt.Println(" - 修改 config.SiteURL 为你的网站URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspectSingleURL(client *google.Client, url string) {
|
||||||
|
fmt.Printf("正在检查URL: %s\n", url)
|
||||||
|
|
||||||
|
result, err := client.InspectURL(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("检查失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
printResult(url, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func batchInspectURLs(client *google.Client, filename string) {
|
||||||
|
urls, err := readURLsFromFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("读取URL文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("开始批量检查 %d 个URL...\n", len(urls))
|
||||||
|
|
||||||
|
results := make(chan struct {
|
||||||
|
url string
|
||||||
|
result *google.URLInspectionResult
|
||||||
|
err error
|
||||||
|
}, len(urls))
|
||||||
|
|
||||||
|
client.BatchInspectURL(urls, func(url string, result *google.URLInspectionResult, err error) {
|
||||||
|
results <- struct {
|
||||||
|
url string
|
||||||
|
result *google.URLInspectionResult
|
||||||
|
err error
|
||||||
|
}{url, result, err}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 收集并打印结果
|
||||||
|
fmt.Println("\n检查结果:")
|
||||||
|
fmt.Println(strings.Repeat("-", 100))
|
||||||
|
fmt.Printf("%-50s %-15s %-15s %-20s\n", "URL", "索引状态", "移动友好", "最后抓取")
|
||||||
|
fmt.Println(strings.Repeat("-", 100))
|
||||||
|
|
||||||
|
for i := 0; i < len(urls); i++ {
|
||||||
|
res := <-results
|
||||||
|
if res.err != nil {
|
||||||
|
fmt.Printf("%-50s %-15s\n", truncate(res.url, 47), "ERROR")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
indexStatus := res.result.IndexStatusResult.IndexingState
|
||||||
|
mobileFriendly := "否"
|
||||||
|
if res.result.MobileUsabilityResult.MobileFriendly {
|
||||||
|
mobileFriendly = "是"
|
||||||
|
}
|
||||||
|
lastCrawl := res.result.IndexStatusResult.LastCrawled
|
||||||
|
if lastCrawl == "" {
|
||||||
|
lastCrawl = "未知"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%-50s %-15s %-15s %-20s\n",
|
||||||
|
truncate(res.url, 47), indexStatus, mobileFriendly, lastCrawl)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strings.Repeat("-", 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
func listSites(client *google.Client) {
|
||||||
|
sites, err := client.GetSites()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取网站列表失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("已验证的网站:")
|
||||||
|
fmt.Println(strings.Repeat("-", 80))
|
||||||
|
fmt.Printf("%-50s %-15s %-15s\n", "网站URL", "权限级别", "验证状态")
|
||||||
|
fmt.Println(strings.Repeat("-", 80))
|
||||||
|
|
||||||
|
for _, site := range sites {
|
||||||
|
permissionLevel := string(site.PermissionLevel)
|
||||||
|
verified := "否"
|
||||||
|
if site.SiteUrl == client.SiteURL {
|
||||||
|
verified = "是"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%-50s %-15s %-15s\n",
|
||||||
|
truncate(site.SiteUrl, 47), permissionLevel, verified)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strings.Repeat("-", 80))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAnalytics(client *google.Client) {
|
||||||
|
endDate := time.Now().Format("2006-01-02")
|
||||||
|
startDate := time.Now().AddDate(0, -1, 0).Format("2006-01-02") // 最近30天
|
||||||
|
|
||||||
|
fmt.Printf("获取搜索分析数据 (%s 到 %s)...\n", startDate, endDate)
|
||||||
|
|
||||||
|
analytics, err := client.GetSearchAnalytics(startDate, endDate)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取分析数据失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算总计数据
|
||||||
|
var totalClicks, totalImpressions float64
|
||||||
|
var totalPosition float64
|
||||||
|
|
||||||
|
for _, row := range analytics.Rows {
|
||||||
|
totalClicks += row.Clicks
|
||||||
|
totalImpressions += row.Impressions
|
||||||
|
totalPosition += row.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
avgCTR := float64(0)
|
||||||
|
if totalImpressions > 0 {
|
||||||
|
avgCTR = float64(totalClicks) / float64(totalImpressions) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
avgPosition := float64(0)
|
||||||
|
if len(analytics.Rows) > 0 {
|
||||||
|
avgPosition = totalPosition / float64(len(analytics.Rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\n搜索分析摘要:")
|
||||||
|
fmt.Println(strings.Repeat("-", 60))
|
||||||
|
fmt.Printf("总点击数: %.0f\n", totalClicks)
|
||||||
|
fmt.Printf("总展示次数: %.0f\n", totalImpressions)
|
||||||
|
fmt.Printf("平均点击率: %.2f%%\n", avgCTR)
|
||||||
|
fmt.Printf("平均排名: %.1f\n", avgPosition)
|
||||||
|
fmt.Println(strings.Repeat("-", 60))
|
||||||
|
|
||||||
|
// 显示前10个页面
|
||||||
|
if len(analytics.Rows) > 0 {
|
||||||
|
fmt.Println("\n热门页面 (前10):")
|
||||||
|
fmt.Printf("%-50s %-10s %-10s %-10s\n", "页面", "点击", "展示", "排名")
|
||||||
|
fmt.Println(strings.Repeat("-", 80))
|
||||||
|
|
||||||
|
maxRows := len(analytics.Rows)
|
||||||
|
if maxRows > 10 {
|
||||||
|
maxRows = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < maxRows; i++ {
|
||||||
|
row := analytics.Rows[i]
|
||||||
|
fmt.Printf("%-50s %-10d %-10d %-10.1f\n",
|
||||||
|
truncate(row.Keys[0], 47), row.Clicks, row.Impressions, row.Position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func submitSitemap(client *google.Client, sitemapURL string) {
|
||||||
|
fmt.Printf("正在提交网站地图: %s\n", sitemapURL)
|
||||||
|
|
||||||
|
err := client.SubmitSitemap(sitemapURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("提交网站地图失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("网站地图提交成功!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readURLsFromFile(filename string) ([]string, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
var urls []string
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" && !strings.HasPrefix(line, "#") {
|
||||||
|
urls = append(urls, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printResult(url string, result *google.URLInspectionResult) {
|
||||||
|
fmt.Println("\nURL检查结果:")
|
||||||
|
fmt.Println(strings.Repeat("=", 80))
|
||||||
|
fmt.Printf("URL: %s\n", url)
|
||||||
|
fmt.Println(strings.Repeat("-", 80))
|
||||||
|
|
||||||
|
fmt.Printf("索引状态: %s\n", result.IndexStatusResult.IndexingState)
|
||||||
|
if result.IndexStatusResult.LastCrawled != "" {
|
||||||
|
fmt.Printf("最后抓取: %s\n", result.IndexStatusResult.LastCrawled)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("移动友好: %t\n", result.MobileUsabilityResult.MobileFriendly)
|
||||||
|
|
||||||
|
if len(result.RichResultsResult.Detected.Items) > 0 {
|
||||||
|
fmt.Println("富媒体结果:")
|
||||||
|
for _, item := range result.RichResultsResult.Detected.Items {
|
||||||
|
fmt.Printf(" - %s\n", item.RichResultType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.IndexStatusResult.CrawlErrors) > 0 {
|
||||||
|
fmt.Println("抓取错误:")
|
||||||
|
for _, err := range result.IndexStatusResult.CrawlErrors {
|
||||||
|
fmt.Printf(" - %s\n", err.ErrorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strings.Repeat("=", 80))
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(s string, length int) string {
|
||||||
|
if len(s) <= length {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:length-3] + "..."
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package converter
|
package converter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -325,3 +326,38 @@ func ToReadyResourceResponseList(resources []entity.ReadyResource) []dto.ReadyRe
|
|||||||
// Key: req.Key,
|
// Key: req.Key,
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// TaskToGoogleIndexTaskOutput 将Task实体转换为GoogleIndexTaskOutput
|
||||||
|
func TaskToGoogleIndexTaskOutput(task *entity.Task, stats map[string]int) dto.GoogleIndexTaskOutput {
|
||||||
|
output := dto.GoogleIndexTaskOutput{
|
||||||
|
ID: task.ID,
|
||||||
|
Name: task.Name,
|
||||||
|
Description: task.Description,
|
||||||
|
Type: string(task.Type),
|
||||||
|
Status: string(task.Status),
|
||||||
|
Progress: task.Progress,
|
||||||
|
TotalItems: stats["total"],
|
||||||
|
ProcessedItems: stats["completed"] + stats["failed"],
|
||||||
|
SuccessfulItems: stats["completed"],
|
||||||
|
FailedItems: stats["failed"],
|
||||||
|
PendingItems: stats["pending"],
|
||||||
|
ProcessingItems: stats["processing"],
|
||||||
|
IndexedURLs: task.IndexedURLs,
|
||||||
|
FailedURLs: task.FailedURLs,
|
||||||
|
ConfigID: task.ConfigID,
|
||||||
|
CreatedAt: task.CreatedAt,
|
||||||
|
UpdatedAt: task.UpdatedAt,
|
||||||
|
StartedAt: task.StartedAt,
|
||||||
|
CompletedAt: task.CompletedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置进度数据
|
||||||
|
if task.ProgressData != "" {
|
||||||
|
var progressData map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(task.ProgressData), &progressData); err == nil {
|
||||||
|
output.ProgressData = progressData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte
|
|||||||
entity.ConfigResponseFieldEnableRegister: true, // 默认开启注册功能
|
entity.ConfigResponseFieldEnableRegister: true, // 默认开启注册功能
|
||||||
entity.ConfigResponseFieldThirdPartyStatsCode: "",
|
entity.ConfigResponseFieldThirdPartyStatsCode: "",
|
||||||
entity.ConfigResponseFieldWebsiteURL: "",
|
entity.ConfigResponseFieldWebsiteURL: "",
|
||||||
|
"google_site_verification_code": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将键值对转换为map,过滤掉敏感配置
|
// 将键值对转换为map,过滤掉敏感配置
|
||||||
@@ -362,6 +363,8 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte
|
|||||||
response["qr_code_style"] = config.Value
|
response["qr_code_style"] = config.Value
|
||||||
case entity.ConfigKeyWebsiteURL:
|
case entity.ConfigKeyWebsiteURL:
|
||||||
response[entity.ConfigResponseFieldWebsiteURL] = config.Value
|
response[entity.ConfigResponseFieldWebsiteURL] = config.Value
|
||||||
|
case "google_site_verification_code":
|
||||||
|
response["google_site_verification_code"] = config.Value
|
||||||
case entity.ConfigKeyAutoProcessReadyResources:
|
case entity.ConfigKeyAutoProcessReadyResources:
|
||||||
if val, err := strconv.ParseBool(config.Value); err == nil {
|
if val, err := strconv.ParseBool(config.Value); err == nil {
|
||||||
response["auto_process_ready_resources"] = val
|
response["auto_process_ready_resources"] = val
|
||||||
|
|||||||
188
db/dto/google_index.go
Normal file
188
db/dto/google_index.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoogleIndexConfigInput Google索引配置输入
|
||||||
|
type GoogleIndexConfigInput struct {
|
||||||
|
Group string `json:"group" binding:"required"`
|
||||||
|
Key string `json:"key" binding:"required"`
|
||||||
|
Value string `json:"value" binding:"required"`
|
||||||
|
Type string `json:"type" default:"string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexConfigOutput Google索引配置输出
|
||||||
|
type GoogleIndexConfigOutput struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexConfigGeneral 通用配置
|
||||||
|
type GoogleIndexConfigGeneral struct {
|
||||||
|
Enabled bool `json:"enabled" binding:"required"`
|
||||||
|
SiteURL string `json:"site_url" binding:"required"`
|
||||||
|
SiteName string `json:"site_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexConfigAuth 认证配置
|
||||||
|
type GoogleIndexConfigAuth struct {
|
||||||
|
CredentialsFile string `json:"credentials_file"`
|
||||||
|
ClientEmail string `json:"client_email"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexConfigSchedule 调度配置
|
||||||
|
type GoogleIndexConfigSchedule struct {
|
||||||
|
CheckInterval int `json:"check_interval" binding:"required,min=1,max=1440"` // 检查间隔(分钟),1-24小时
|
||||||
|
BatchSize int `json:"batch_size" binding:"required,min=1,max=1000"` // 批处理大小
|
||||||
|
Concurrency int `json:"concurrency" binding:"required,min=1,max=10"` // 并发数
|
||||||
|
RetryAttempts int `json:"retry_attempts" binding:"required,min=0,max=10"` // 重试次数
|
||||||
|
RetryDelay int `json:"retry_delay" binding:"required,min=1,max=60"` // 重试延迟(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexConfigSitemap 网站地图配置
|
||||||
|
type GoogleIndexConfigSitemap struct {
|
||||||
|
AutoSitemap bool `json:"auto_sitemap"`
|
||||||
|
SitemapPath string `json:"sitemap_path" default:"/sitemap.xml"`
|
||||||
|
SitemapSchedule string `json:"sitemap_schedule" default:"@daily"` // cron表达式
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskInput Google索引任务输入
|
||||||
|
type GoogleIndexTaskInput struct {
|
||||||
|
Title string `json:"title" binding:"required"`
|
||||||
|
Type string `json:"type" binding:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
URLs []string `json:"urls,omitempty"` // 用于URL索引检查任务
|
||||||
|
SitemapURL string `json:"sitemap_url,omitempty"` // 用于网站地图提交任务
|
||||||
|
ConfigID *uint `json:"config_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskOutput Google索引任务输出
|
||||||
|
type GoogleIndexTaskOutput struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Progress float64 `json:"progress"`
|
||||||
|
TotalItems int `json:"total_items"`
|
||||||
|
ProcessedItems int `json:"processed_items"`
|
||||||
|
SuccessfulItems int `json:"successful_items"`
|
||||||
|
FailedItems int `json:"failed_items"`
|
||||||
|
PendingItems int `json:"pending_items"`
|
||||||
|
ProcessingItems int `json:"processing_items"`
|
||||||
|
IndexedURLs int `json:"indexed_urls"`
|
||||||
|
FailedURLs int `json:"failed_urls"`
|
||||||
|
ConfigID *uint `json:"config_id"`
|
||||||
|
ProgressData map[string]interface{} `json:"progress_data"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
StartedAt *time.Time `json:"started_at"`
|
||||||
|
CompletedAt *time.Time `json:"completed_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskItemInput Google索引任务项输入
|
||||||
|
type GoogleIndexTaskItemInput struct {
|
||||||
|
TaskID uint `json:"task_id" binding:"required"`
|
||||||
|
URL string `json:"url" binding:"required,url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskItemOutput Google索引任务项输出
|
||||||
|
type GoogleIndexTaskItemOutput struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
TaskID uint `json:"task_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
IndexStatus string `json:"index_status"`
|
||||||
|
ErrorMessage string `json:"error_message"`
|
||||||
|
InspectResult string `json:"inspect_result"`
|
||||||
|
MobileFriendly bool `json:"mobile_friendly"`
|
||||||
|
LastCrawled *time.Time `json:"last_crawled"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
StartedAt *time.Time `json:"started_at"`
|
||||||
|
CompletedAt *time.Time `json:"completed_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexURLStatusInput Google索引URL状态输入
|
||||||
|
type GoogleIndexURLStatusInput struct {
|
||||||
|
URL string `json:"url" binding:"required,url"`
|
||||||
|
IndexStatus string `json:"index_status" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexURLStatusOutput Google索引URL状态输出
|
||||||
|
type GoogleIndexURLStatusOutput struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
IndexStatus string `json:"index_status"`
|
||||||
|
LastChecked time.Time `json:"last_checked"`
|
||||||
|
CanonicalURL *string `json:"canonical_url"`
|
||||||
|
LastCrawled *time.Time `json:"last_crawled"`
|
||||||
|
ChangeFreq *string `json:"change_freq"`
|
||||||
|
Priority *float64 `json:"priority"`
|
||||||
|
MobileFriendly bool `json:"mobile_friendly"`
|
||||||
|
RobotsBlocked bool `json:"robots_blocked"`
|
||||||
|
LastError *string `json:"last_error"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
StatusCodeText string `json:"status_code_text"`
|
||||||
|
CheckCount int `json:"check_count"`
|
||||||
|
SuccessCount int `json:"success_count"`
|
||||||
|
FailureCount int `json:"failure_count"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexBatchRequest 批量处理请求
|
||||||
|
type GoogleIndexBatchRequest struct {
|
||||||
|
URLs []string `json:"urls" binding:"required,min=1,max=1000"`
|
||||||
|
Operation string `json:"operation" binding:"required,oneof=check_index submit_sitemap ping"` // 操作类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexBatchResponse 批量处理响应
|
||||||
|
type GoogleIndexBatchResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Results []string `json:"results,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Processed int `json:"processed"`
|
||||||
|
Failed int `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexStatusResponse 索引状态响应
|
||||||
|
type GoogleIndexStatusResponse struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
SiteURL string `json:"site_url"`
|
||||||
|
LastCheckTime time.Time `json:"last_check_time"`
|
||||||
|
TotalURLs int `json:"total_urls"`
|
||||||
|
IndexedURLs int `json:"indexed_urls"`
|
||||||
|
NotIndexedURLs int `json:"not_indexed_urls"`
|
||||||
|
ErrorURLs int `json:"error_urls"`
|
||||||
|
LastSitemapSubmit time.Time `json:"last_sitemap_submit"`
|
||||||
|
AuthValid bool `json:"auth_valid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskListResponse 任务列表响应
|
||||||
|
type GoogleIndexTaskListResponse struct {
|
||||||
|
Tasks []GoogleIndexTaskOutput `json:"tasks"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskItemPageResponse 任务项分页响应
|
||||||
|
type GoogleIndexTaskItemPageResponse struct {
|
||||||
|
Items []GoogleIndexTaskItemOutput `json:"items"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
@@ -132,3 +132,32 @@ type SearchRequest struct {
|
|||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Limit int `json:"limit"`
|
Limit int `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateTaskRequest 创建任务请求
|
||||||
|
type CreateTaskRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
TaskType string `json:"task_type" binding:"required"`
|
||||||
|
ConfigID *uint `json:"config_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTaskItemRequest 创建任务项请求
|
||||||
|
type CreateTaskItemRequest struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
InputData map[string]interface{} `json:"input_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTaskRequest 查询任务请求
|
||||||
|
type QueryTaskRequest struct {
|
||||||
|
Page int `json:"page" form:"page"`
|
||||||
|
PageSize int `json:"page_size" form:"page_size"`
|
||||||
|
Status string `json:"status" form:"status"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTaskItemRequest 查询任务项请求
|
||||||
|
type QueryTaskItemRequest struct {
|
||||||
|
Page int `json:"page" form:"page"`
|
||||||
|
PageSize int `json:"page_size" form:"page_size"`
|
||||||
|
Status string `json:"status" form:"status"`
|
||||||
|
}
|
||||||
|
|||||||
28
db/entity/google_index_config_constants.go
Normal file
28
db/entity/google_index_config_constants.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
// GoogleIndexConfigKeys Google索引配置键常量
|
||||||
|
const (
|
||||||
|
// 通用配置
|
||||||
|
GoogleIndexConfigKeyEnabled = "google_index_enabled" // 是否启用Google索引功能
|
||||||
|
GoogleIndexConfigKeySiteURL = "google_index_site_url" // 网站URL
|
||||||
|
GoogleIndexConfigKeySiteName = "google_index_site_name" // 网站名称
|
||||||
|
|
||||||
|
// 认证配置
|
||||||
|
GoogleIndexConfigKeyCredentialsFile = "google_index_credentials_file" // 凭证文件路径
|
||||||
|
GoogleIndexConfigKeyClientEmail = "google_index_client_email" // 客户端邮箱
|
||||||
|
GoogleIndexConfigKeyClientID = "google_index_client_id" // 客户端ID
|
||||||
|
GoogleIndexConfigKeyPrivateKey = "google_index_private_key" // 私钥(加密存储)
|
||||||
|
GoogleIndexConfigKeyToken = "google_index_token" // 访问令牌
|
||||||
|
|
||||||
|
// 调度配置
|
||||||
|
GoogleIndexConfigKeyCheckInterval = "google_index_check_interval" // 检查间隔(分钟)
|
||||||
|
GoogleIndexConfigKeyBatchSize = "google_index_batch_size" // 批处理大小
|
||||||
|
GoogleIndexConfigKeyConcurrency = "google_index_concurrency" // 并发数
|
||||||
|
GoogleIndexConfigKeyRetryAttempts = "google_index_retry_attempts" // 重试次数
|
||||||
|
GoogleIndexConfigKeyRetryDelay = "google_index_retry_delay" // 重试延迟(秒)
|
||||||
|
|
||||||
|
// 网站地图配置
|
||||||
|
GoogleIndexConfigKeyAutoSitemap = "google_index_auto_sitemap" // 自动提交网站地图
|
||||||
|
GoogleIndexConfigKeySitemapPath = "google_index_sitemap_path" // 网站地图路径
|
||||||
|
GoogleIndexConfigKeySitemapSchedule = "google_index_sitemap_schedule" // 网站地图调度
|
||||||
|
)
|
||||||
@@ -24,21 +24,24 @@ type TaskType string
|
|||||||
const (
|
const (
|
||||||
TaskTypeBatchTransfer TaskType = "batch_transfer" // 批量转存
|
TaskTypeBatchTransfer TaskType = "batch_transfer" // 批量转存
|
||||||
TaskTypeExpansion TaskType = "expansion" // 账号扩容
|
TaskTypeExpansion TaskType = "expansion" // 账号扩容
|
||||||
|
TaskTypeGoogleIndex TaskType = "google_index" // Google索引
|
||||||
)
|
)
|
||||||
|
|
||||||
// Task 任务表
|
// Task 任务表
|
||||||
type Task struct {
|
type Task struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Title string `json:"title" gorm:"size:255;not null;comment:任务标题"`
|
Title string `json:"title" gorm:"size:255;not null;comment:任务标题"`
|
||||||
|
Name string `json:"name" gorm:"size:255;not null;default:'';comment:任务名称"`
|
||||||
Type TaskType `json:"type" gorm:"size:50;not null;comment:任务类型"`
|
Type TaskType `json:"type" gorm:"size:50;not null;comment:任务类型"`
|
||||||
Status TaskStatus `json:"status" gorm:"size:20;not null;default:pending;comment:任务状态"`
|
Status TaskStatus `json:"status" gorm:"size:20;not null;default:pending;comment:任务状态"`
|
||||||
Description string `json:"description" gorm:"type:text;comment:任务描述"`
|
Description string `json:"description" gorm:"type:text;comment:任务描述"`
|
||||||
|
|
||||||
// 进度信息
|
// 进度信息
|
||||||
TotalItems int `json:"total_items" gorm:"not null;default:0;comment:总项目数"`
|
TotalItems int `json:"total_items" gorm:"not null;default:0;comment:总项目数"`
|
||||||
ProcessedItems int `json:"processed_items" gorm:"not null;default:0;comment:已处理项目数"`
|
ProcessedItems int `json:"processed_items" gorm:"not null;default:0;comment:已处理项目数"`
|
||||||
SuccessItems int `json:"success_items" gorm:"not null;default:0;comment:成功项目数"`
|
SuccessItems int `json:"success_items" gorm:"not null;default:0;comment:成功项目数"`
|
||||||
FailedItems int `json:"failed_items" gorm:"not null;default:0;comment:失败项目数"`
|
FailedItems int `json:"failed_items" gorm:"not null;default:0;comment:失败项目数"`
|
||||||
|
Progress float64 `json:"progress" gorm:"not null;default:0.0;comment:任务进度"`
|
||||||
|
|
||||||
// 任务配置 (JSON格式存储)
|
// 任务配置 (JSON格式存储)
|
||||||
Config string `json:"config" gorm:"type:text;comment:任务配置"`
|
Config string `json:"config" gorm:"type:text;comment:任务配置"`
|
||||||
@@ -46,6 +49,16 @@ type Task struct {
|
|||||||
// 任务消息
|
// 任务消息
|
||||||
Message string `json:"message" gorm:"type:text;comment:任务消息"`
|
Message string `json:"message" gorm:"type:text;comment:任务消息"`
|
||||||
|
|
||||||
|
// 进度数据 (JSON格式存储)
|
||||||
|
ProgressData string `json:"progress_data" gorm:"type:text;comment:进度数据"`
|
||||||
|
|
||||||
|
// Google索引特有字段 (当Type为google_index时使用)
|
||||||
|
IndexedURLs int `json:"indexed_urls" gorm:"default:0;comment:已索引URL数量"`
|
||||||
|
FailedURLs int `json:"failed_urls" gorm:"default:0;comment:失败URL数量"`
|
||||||
|
|
||||||
|
// 配置关联 (用于Google索引任务)
|
||||||
|
ConfigID *uint `json:"config_id" gorm:"comment:配置ID"`
|
||||||
|
|
||||||
// 时间信息
|
// 时间信息
|
||||||
StartedAt *time.Time `json:"started_at" gorm:"comment:开始时间"`
|
StartedAt *time.Time `json:"started_at" gorm:"comment:开始时间"`
|
||||||
CompletedAt *time.Time `json:"completed_at" gorm:"comment:完成时间"`
|
CompletedAt *time.Time `json:"completed_at" gorm:"comment:完成时间"`
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ type TaskItem struct {
|
|||||||
// 处理日志 (可选,用于记录详细的处理过程)
|
// 处理日志 (可选,用于记录详细的处理过程)
|
||||||
ProcessLog string `json:"process_log" gorm:"type:text;comment:处理日志"`
|
ProcessLog string `json:"process_log" gorm:"type:text;comment:处理日志"`
|
||||||
|
|
||||||
|
// Google索引特有字段 (当任务类型为google_index时使用)
|
||||||
|
URL string `json:"url" gorm:"size:2048;comment:URL (Google索引专用)"`
|
||||||
|
IndexStatus string `json:"index_status" gorm:"size:50;comment:索引状态 (Google索引专用)"`
|
||||||
|
InspectResult string `json:"inspect_result" gorm:"type:text;comment:检查结果 (Google索引专用)"`
|
||||||
|
MobileFriendly bool `json:"mobile_friendly" gorm:"default:false;comment:是否移动友好 (Google索引专用)"`
|
||||||
|
LastCrawled *time.Time `json:"last_crawled" gorm:"comment:最后抓取时间 (Google索引专用)"`
|
||||||
|
StatusCode int `json:"status_code" gorm:"default:0;comment:HTTP状态码 (Google索引专用)"`
|
||||||
|
|
||||||
// 时间信息
|
// 时间信息
|
||||||
ProcessedAt *time.Time `json:"processed_at" gorm:"comment:处理时间"`
|
ProcessedAt *time.Time `json:"processed_at" gorm:"comment:处理时间"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
type TaskItemRepository interface {
|
type TaskItemRepository interface {
|
||||||
GetByID(id uint) (*entity.TaskItem, error)
|
GetByID(id uint) (*entity.TaskItem, error)
|
||||||
Create(item *entity.TaskItem) error
|
Create(item *entity.TaskItem) error
|
||||||
|
Update(item *entity.TaskItem) error
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
DeleteByTaskID(taskID uint) error
|
DeleteByTaskID(taskID uint) error
|
||||||
GetByTaskIDAndStatus(taskID uint, status string) ([]*entity.TaskItem, error)
|
GetByTaskIDAndStatus(taskID uint, status string) ([]*entity.TaskItem, error)
|
||||||
@@ -49,6 +50,33 @@ func (r *TaskItemRepositoryImpl) Create(item *entity.TaskItem) error {
|
|||||||
return r.db.Create(item).Error
|
return r.db.Create(item).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update 更新任务项
|
||||||
|
func (r *TaskItemRepositoryImpl) Update(item *entity.TaskItem) error {
|
||||||
|
startTime := utils.GetCurrentTime()
|
||||||
|
err := r.db.Model(&entity.TaskItem{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
|
||||||
|
"status": item.Status,
|
||||||
|
"error_message": item.ErrorMessage,
|
||||||
|
"index_status": item.IndexStatus,
|
||||||
|
"mobile_friendly": item.MobileFriendly,
|
||||||
|
"last_crawled": item.LastCrawled,
|
||||||
|
"status_code": item.StatusCode,
|
||||||
|
"input_data": item.InputData,
|
||||||
|
"output_data": item.OutputData,
|
||||||
|
"process_log": item.ProcessLog,
|
||||||
|
"url": item.URL,
|
||||||
|
"inspect_result": item.InspectResult,
|
||||||
|
"processed_at": item.ProcessedAt,
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}).Error
|
||||||
|
updateDuration := time.Since(startTime)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Update任务项失败: ID=%d, 错误=%v, 更新耗时=%v", item.ID, err, updateDuration)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
utils.Debug("Update任务项成功: ID=%d, 更新耗时=%v", item.ID, updateDuration)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Delete 删除任务项
|
// Delete 删除任务项
|
||||||
func (r *TaskItemRepositoryImpl) Delete(id uint) error {
|
func (r *TaskItemRepositoryImpl) Delete(id uint) error {
|
||||||
return r.db.Delete(&entity.TaskItem{}, id).Error
|
return r.db.Delete(&entity.TaskItem{}, id).Error
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -15,19 +15,32 @@ require (
|
|||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/silenceper/wechat/v2 v2.1.10
|
github.com/silenceper/wechat/v2 v2.1.10
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.41.0
|
||||||
|
golang.org/x/oauth2 v0.30.0
|
||||||
|
google.golang.org/api v0.197.0
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.30.0
|
gorm.io/gorm v1.30.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go/auth v0.9.3 // indirect
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/google/s2a-go v0.1.8 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
@@ -36,8 +49,15 @@ require (
|
|||||||
github.com/tidwall/gjson v1.14.1 // indirect
|
github.com/tidwall/gjson v1.14.1 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
golang.org/x/image v0.32.0 // indirect
|
golang.org/x/image v0.32.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
|
google.golang.org/grpc v1.66.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
95
go.sum
95
go.sum
@@ -1,3 +1,11 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
|
||||||
|
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
||||||
@@ -13,22 +21,31 @@ github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1
|
|||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -44,6 +61,11 @@ github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fq
|
|||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
@@ -72,23 +94,43 @@ github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||||
|
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
@@ -166,6 +208,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
@@ -213,6 +256,16 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i
|
|||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
|
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||||
|
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
@@ -226,24 +279,38 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -274,18 +341,44 @@ golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
|||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ=
|
||||||
|
google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
||||||
|
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
@@ -314,4 +407,6 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
|||||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
|||||||
846
handlers/google_index_handler.go
Normal file
846
handlers/google_index_handler.go
Normal file
@@ -0,0 +1,846 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/db/converter"
|
||||||
|
"github.com/ctwj/urldb/db/dto"
|
||||||
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
"github.com/ctwj/urldb/db/repo"
|
||||||
|
"github.com/ctwj/urldb/pkg/google" // 添加google包导入
|
||||||
|
"github.com/ctwj/urldb/task"
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoogleIndexHandler Google索引处理程序
|
||||||
|
type GoogleIndexHandler struct {
|
||||||
|
repoMgr *repo.RepositoryManager
|
||||||
|
taskManager *task.TaskManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGoogleIndexHandler 创建Google索引处理程序
|
||||||
|
func NewGoogleIndexHandler(
|
||||||
|
repoMgr *repo.RepositoryManager,
|
||||||
|
taskManager *task.TaskManager,
|
||||||
|
) *GoogleIndexHandler {
|
||||||
|
return &GoogleIndexHandler{
|
||||||
|
repoMgr: repoMgr,
|
||||||
|
taskManager: taskManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取Google索引配置
|
||||||
|
func (h *GoogleIndexHandler) GetConfig(c *gin.Context) {
|
||||||
|
// 获取通用配置
|
||||||
|
enabledStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyEnabled)
|
||||||
|
if err != nil {
|
||||||
|
enabledStr = "false"
|
||||||
|
}
|
||||||
|
enabled := enabledStr == "true" || enabledStr == "1"
|
||||||
|
|
||||||
|
siteURL, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeySiteURL)
|
||||||
|
if err != nil {
|
||||||
|
siteURL = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
siteName, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeySiteName)
|
||||||
|
if err != nil {
|
||||||
|
siteName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取调度配置
|
||||||
|
checkIntervalStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyCheckInterval)
|
||||||
|
if err != nil {
|
||||||
|
checkIntervalStr = "60"
|
||||||
|
}
|
||||||
|
checkInterval, _ := strconv.Atoi(checkIntervalStr)
|
||||||
|
|
||||||
|
batchSizeStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyBatchSize)
|
||||||
|
if err != nil {
|
||||||
|
batchSizeStr = "100"
|
||||||
|
}
|
||||||
|
batchSize, _ := strconv.Atoi(batchSizeStr)
|
||||||
|
|
||||||
|
concurrencyStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyConcurrency)
|
||||||
|
if err != nil {
|
||||||
|
concurrencyStr = "5"
|
||||||
|
}
|
||||||
|
concurrency, _ := strconv.Atoi(concurrencyStr)
|
||||||
|
|
||||||
|
retryAttemptsStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyRetryAttempts)
|
||||||
|
if err != nil {
|
||||||
|
retryAttemptsStr = "3"
|
||||||
|
}
|
||||||
|
retryAttempts, _ := strconv.Atoi(retryAttemptsStr)
|
||||||
|
|
||||||
|
retryDelayStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyRetryDelay)
|
||||||
|
if err != nil {
|
||||||
|
retryDelayStr = "2"
|
||||||
|
}
|
||||||
|
retryDelay, _ := strconv.Atoi(retryDelayStr)
|
||||||
|
|
||||||
|
// 获取网站地图配置
|
||||||
|
autoSitemapStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyAutoSitemap)
|
||||||
|
if err != nil {
|
||||||
|
autoSitemapStr = "false"
|
||||||
|
}
|
||||||
|
autoSitemap := autoSitemapStr == "true" || autoSitemapStr == "1"
|
||||||
|
|
||||||
|
sitemapPath, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeySitemapPath)
|
||||||
|
if err != nil {
|
||||||
|
sitemapPath = "/sitemap.xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
sitemapSchedule, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeySitemapSchedule)
|
||||||
|
if err != nil {
|
||||||
|
sitemapSchedule = "@daily"
|
||||||
|
}
|
||||||
|
|
||||||
|
config := dto.GoogleIndexConfigGeneral{
|
||||||
|
Enabled: enabled,
|
||||||
|
SiteURL: siteURL,
|
||||||
|
SiteName: siteName,
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleConfig := dto.GoogleIndexConfigSchedule{
|
||||||
|
CheckInterval: checkInterval,
|
||||||
|
BatchSize: batchSize,
|
||||||
|
Concurrency: concurrency,
|
||||||
|
RetryAttempts: retryAttempts,
|
||||||
|
RetryDelay: retryDelay,
|
||||||
|
}
|
||||||
|
|
||||||
|
sitemapConfig := dto.GoogleIndexConfigSitemap{
|
||||||
|
AutoSitemap: autoSitemap,
|
||||||
|
SitemapPath: sitemapPath,
|
||||||
|
SitemapSchedule: sitemapSchedule,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := gin.H{
|
||||||
|
"general": config,
|
||||||
|
"schedule": scheduleConfig,
|
||||||
|
"sitemap": sitemapConfig,
|
||||||
|
"is_running": false, // 不再有独立的调度器,使用统一任务管理器
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig 更新Google索引配置
|
||||||
|
func (h *GoogleIndexHandler) UpdateConfig(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
General dto.GoogleIndexConfigGeneral `json:"general"`
|
||||||
|
Schedule dto.GoogleIndexConfigSchedule `json:"schedule"`
|
||||||
|
Sitemap dto.GoogleIndexConfigSitemap `json:"sitemap"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
ErrorResponse(c, "参数错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, _ := c.Get("username")
|
||||||
|
clientIP, _ := c.Get("client_ip")
|
||||||
|
utils.Info("GoogleIndexHandler.UpdateConfig - 用户更新Google索引配置 - 用户: %s, IP: %s", username, clientIP)
|
||||||
|
|
||||||
|
// 准备要更新的配置项
|
||||||
|
configs := []entity.SystemConfig{
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyEnabled,
|
||||||
|
Value: strconv.FormatBool(req.General.Enabled),
|
||||||
|
Type: entity.ConfigTypeBool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeySiteURL,
|
||||||
|
Value: req.General.SiteURL,
|
||||||
|
Type: entity.ConfigTypeString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeySiteName,
|
||||||
|
Value: req.General.SiteName,
|
||||||
|
Type: entity.ConfigTypeString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyCheckInterval,
|
||||||
|
Value: strconv.Itoa(req.Schedule.CheckInterval),
|
||||||
|
Type: entity.ConfigTypeInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyBatchSize,
|
||||||
|
Value: strconv.Itoa(req.Schedule.BatchSize),
|
||||||
|
Type: entity.ConfigTypeInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyConcurrency,
|
||||||
|
Value: strconv.Itoa(req.Schedule.Concurrency),
|
||||||
|
Type: entity.ConfigTypeInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyRetryAttempts,
|
||||||
|
Value: strconv.Itoa(req.Schedule.RetryAttempts),
|
||||||
|
Type: entity.ConfigTypeInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyRetryDelay,
|
||||||
|
Value: strconv.Itoa(req.Schedule.RetryDelay),
|
||||||
|
Type: entity.ConfigTypeInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeyAutoSitemap,
|
||||||
|
Value: strconv.FormatBool(req.Sitemap.AutoSitemap),
|
||||||
|
Type: entity.ConfigTypeBool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeySitemapPath,
|
||||||
|
Value: req.Sitemap.SitemapPath,
|
||||||
|
Type: entity.ConfigTypeString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: entity.GoogleIndexConfigKeySitemapSchedule,
|
||||||
|
Value: req.Sitemap.SitemapSchedule,
|
||||||
|
Type: entity.ConfigTypeString,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新配置
|
||||||
|
err := h.repoMgr.SystemConfigRepository.UpsertConfigs(configs)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("更新系统配置失败: %v", err)
|
||||||
|
ErrorResponse(c, "更新配置失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("Google索引配置更新成功 - 用户: %s, IP: %s", username, clientIP)
|
||||||
|
SuccessResponse(c, gin.H{
|
||||||
|
"message": "配置更新成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTask 创建Google索引任务
|
||||||
|
func (h *GoogleIndexHandler) CreateTask(c *gin.Context) {
|
||||||
|
var req dto.GoogleIndexTaskInput
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
ErrorResponse(c, "参数错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, _ := c.Get("username")
|
||||||
|
clientIP, _ := c.Get("client_ip")
|
||||||
|
utils.Info("GoogleIndexHandler.CreateTask - 用户创建Google索引任务 - 用户: %s, 任务类型: %s, 任务标题: %s, IP: %s", username, req.Type, req.Title, clientIP)
|
||||||
|
|
||||||
|
// 创建通用任务
|
||||||
|
task, err := h.taskManager.CreateTask(string(entity.TaskTypeGoogleIndex), req.Title, req.Description, req.ConfigID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("创建Google索引任务失败: %v", err)
|
||||||
|
ErrorResponse(c, "创建任务失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据任务类型创建任务项
|
||||||
|
var taskItems []*entity.TaskItem
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case "url_indexing", "status_check", "batch_index":
|
||||||
|
// 为每个URL创建任务项
|
||||||
|
for _, url := range req.URLs {
|
||||||
|
itemData := map[string]interface{}{
|
||||||
|
"urls": []string{url},
|
||||||
|
"operation": req.Type,
|
||||||
|
}
|
||||||
|
itemDataJSON, _ := json.Marshal(itemData)
|
||||||
|
|
||||||
|
taskItem := &entity.TaskItem{
|
||||||
|
URL: url,
|
||||||
|
InputData: string(itemDataJSON),
|
||||||
|
}
|
||||||
|
taskItems = append(taskItems, taskItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "sitemap_submit":
|
||||||
|
// 为网站地图创建任务项
|
||||||
|
itemData := map[string]interface{}{
|
||||||
|
"sitemap_url": req.SitemapURL,
|
||||||
|
"operation": "sitemap_submit",
|
||||||
|
}
|
||||||
|
itemDataJSON, _ := json.Marshal(itemData)
|
||||||
|
|
||||||
|
taskItem := &entity.TaskItem{
|
||||||
|
URL: req.SitemapURL,
|
||||||
|
InputData: string(itemDataJSON),
|
||||||
|
}
|
||||||
|
taskItems = append(taskItems, taskItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量创建任务项
|
||||||
|
err = h.taskManager.CreateTaskItems(task.ID, taskItems)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("创建任务项失败: %v", err)
|
||||||
|
ErrorResponse(c, "创建任务项失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("Google索引任务创建完成: %d, 任务类型: %s", task.ID, req.Type)
|
||||||
|
SuccessResponse(c, gin.H{
|
||||||
|
"task_id": task.ID,
|
||||||
|
"total_items": len(taskItems),
|
||||||
|
"message": "任务创建成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartTask 启动Google索引任务
|
||||||
|
func (h *GoogleIndexHandler) StartTask(c *gin.Context) {
|
||||||
|
taskIDStr := c.Param("id")
|
||||||
|
taskID, err := strconv.ParseUint(taskIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "无效的任务ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, _ := c.Get("username")
|
||||||
|
clientIP, _ := c.Get("client_ip")
|
||||||
|
utils.Info("GoogleIndexHandler.StartTask - 用户启动Google索引任务 - 用户: %s, 任务ID: %d, IP: %s", username, taskID, clientIP)
|
||||||
|
|
||||||
|
// 使用任务管理器启动任务
|
||||||
|
err = h.taskManager.StartTask(uint(taskID))
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("启动Google索引任务失败: %v", err)
|
||||||
|
ErrorResponse(c, "启动任务失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("Google索引任务启动成功: %d", taskID)
|
||||||
|
SuccessResponse(c, gin.H{
|
||||||
|
"message": "任务启动成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GetTaskStatus 获取任务状态
|
||||||
|
func (h *GoogleIndexHandler) GetTaskStatus(c *gin.Context) {
|
||||||
|
taskIDStr := c.Param("id")
|
||||||
|
taskID, err := strconv.ParseUint(taskIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "无效的任务ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task, err := h.taskManager.GetTask(uint(taskID))
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "获取任务失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if task == nil {
|
||||||
|
ErrorResponse(c, "任务不存在", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务项统计
|
||||||
|
stats, err := h.taskManager.GetTaskItemStats(task.ID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("获取任务项统计失败: %v", err)
|
||||||
|
stats = make(map[string]int)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskOutput := converter.TaskToGoogleIndexTaskOutput(task, stats)
|
||||||
|
|
||||||
|
result := gin.H{
|
||||||
|
"id": taskOutput.ID,
|
||||||
|
"name": taskOutput.Name,
|
||||||
|
"type": taskOutput.Type,
|
||||||
|
"status": taskOutput.Status,
|
||||||
|
"description": taskOutput.Description,
|
||||||
|
"progress": taskOutput.Progress,
|
||||||
|
"total_items": taskOutput.TotalItems,
|
||||||
|
"processed_items": taskOutput.ProcessedItems,
|
||||||
|
"successful_items": taskOutput.SuccessfulItems,
|
||||||
|
"failed_items": taskOutput.FailedItems,
|
||||||
|
"pending_items": taskOutput.PendingItems,
|
||||||
|
"processing_items": taskOutput.ProcessingItems,
|
||||||
|
"indexed_urls": taskOutput.IndexedURLs,
|
||||||
|
"failed_urls": taskOutput.FailedURLs,
|
||||||
|
"started_at": taskOutput.StartedAt,
|
||||||
|
"completed_at": taskOutput.CompletedAt,
|
||||||
|
"created_at": taskOutput.CreatedAt,
|
||||||
|
"updated_at": taskOutput.UpdatedAt,
|
||||||
|
"progress_data": taskOutput.ProgressData,
|
||||||
|
"stats": stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTasks 获取任务列表
|
||||||
|
func (h *GoogleIndexHandler) GetTasks(c *gin.Context) {
|
||||||
|
pageStr := c.DefaultQuery("page", "1")
|
||||||
|
pageSizeStr := c.DefaultQuery("page_size", "10")
|
||||||
|
taskTypeStr := c.Query("type")
|
||||||
|
statusStr := c.Query("status")
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(pageStr)
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||||
|
if pageSize < 1 || pageSize > 100 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据参数筛选任务类型,如果有指定则使用,否则默认为Google索引类型
|
||||||
|
taskType := string(entity.TaskTypeGoogleIndex)
|
||||||
|
if taskTypeStr != "" {
|
||||||
|
taskType = taskTypeStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定状态的任务,默认查找所有状态
|
||||||
|
status := statusStr
|
||||||
|
|
||||||
|
// 获取任务列表 - 目前我们没有Query方法,直接获取所有任务然后做筛选
|
||||||
|
tasksList, total, err := h.repoMgr.TaskRepository.GetList(page, pageSize, taskType, status)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "获取任务列表失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
taskOutputs := make([]dto.GoogleIndexTaskOutput, len(tasksList))
|
||||||
|
for i, task := range tasksList {
|
||||||
|
// 获取任务统计信息
|
||||||
|
stats, err := h.taskManager.GetTaskItemStats(task.ID)
|
||||||
|
if err != nil {
|
||||||
|
stats = make(map[string]int)
|
||||||
|
}
|
||||||
|
taskOutputs[i] = converter.TaskToGoogleIndexTaskOutput(task, stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := dto.GoogleIndexTaskListResponse{
|
||||||
|
Tasks: taskOutputs,
|
||||||
|
Total: total,
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
TotalPages: int((total + int64(pageSize) - 1) / int64(pageSize)),
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskItems 获取任务项列表
|
||||||
|
func (h *GoogleIndexHandler) GetTaskItems(c *gin.Context) {
|
||||||
|
taskIDStr := c.Param("id")
|
||||||
|
taskID, err := strconv.ParseUint(taskIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "无效的任务ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pageStr := c.DefaultQuery("page", "1")
|
||||||
|
pageSizeStr := c.DefaultQuery("page_size", "50")
|
||||||
|
statusStr := c.Query("status")
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(pageStr)
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||||
|
if pageSize < 1 || pageSize > 1000 {
|
||||||
|
pageSize = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务项列表
|
||||||
|
items, total, err := h.taskManager.QueryTaskItems(uint(taskID), page, pageSize, statusStr)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "获取任务项列表失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:我们还没有TaskItemToGoogleIndexTaskItemOutput转换器,需要创建一个
|
||||||
|
itemOutputs := make([]dto.GoogleIndexTaskItemOutput, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
// 手动构建输出结构
|
||||||
|
itemOutputs[i] = dto.GoogleIndexTaskItemOutput{
|
||||||
|
ID: item.ID,
|
||||||
|
TaskID: item.TaskID,
|
||||||
|
URL: item.URL,
|
||||||
|
Status: string(item.Status),
|
||||||
|
IndexStatus: item.IndexStatus,
|
||||||
|
ErrorMessage: item.ErrorMessage,
|
||||||
|
InspectResult: item.InspectResult,
|
||||||
|
MobileFriendly: item.MobileFriendly,
|
||||||
|
LastCrawled: item.LastCrawled,
|
||||||
|
StatusCode: item.StatusCode,
|
||||||
|
CreatedAt: item.CreatedAt,
|
||||||
|
UpdatedAt: item.UpdatedAt,
|
||||||
|
StartedAt: item.ProcessedAt, // 任务项处理完成时间
|
||||||
|
CompletedAt: item.ProcessedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := dto.GoogleIndexTaskItemPageResponse{
|
||||||
|
Items: itemOutputs,
|
||||||
|
Total: total,
|
||||||
|
Page: page,
|
||||||
|
Size: pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadCredentials 上传Google索引凭据
|
||||||
|
func (h *GoogleIndexHandler) UploadCredentials(c *gin.Context) {
|
||||||
|
// 获取上传的文件
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "未提供凭据文件", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件扩展名必须是.json
|
||||||
|
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||||
|
if ext != ".json" {
|
||||||
|
ErrorResponse(c, "仅支持JSON格式的凭据文件", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件大小(限制5MB)
|
||||||
|
if file.Size > 5*1024*1024 {
|
||||||
|
ErrorResponse(c, "文件大小不能超过5MB", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保data目录存在
|
||||||
|
dataDir := "./data"
|
||||||
|
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||||
|
ErrorResponse(c, "创建数据目录失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用固定的文件名保存凭据
|
||||||
|
fixedFileName := "google_credentials.json"
|
||||||
|
filePath := filepath.Join(dataDir, fixedFileName)
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
if err := c.SaveUploadedFile(file, filePath); err != nil {
|
||||||
|
ErrorResponse(c, "保存凭据文件失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置文件权限
|
||||||
|
if err := os.Chmod(filePath, 0600); err != nil {
|
||||||
|
utils.Warn("设置凭据文件权限失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
accessPath := filepath.Join("data", fixedFileName)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "凭据文件上传成功",
|
||||||
|
"file_name": fixedFileName,
|
||||||
|
"file_path": accessPath,
|
||||||
|
"full_path": filePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeSafeFileName 生成安全的文件名,移除危险字符
|
||||||
|
func (h *GoogleIndexHandler) makeSafeFileName(filename string) string {
|
||||||
|
// 移除路径分隔符和特殊字符
|
||||||
|
safeName := strings.ReplaceAll(filename, "/", "_")
|
||||||
|
safeName = strings.ReplaceAll(safeName, "\\", "_")
|
||||||
|
safeName = strings.ReplaceAll(safeName, "..", "_")
|
||||||
|
|
||||||
|
// 限制文件名长度
|
||||||
|
if len(safeName) > 100 {
|
||||||
|
ext := filepath.Ext(safeName)
|
||||||
|
name := safeName[:100-len(ext)]
|
||||||
|
safeName = name + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeName
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCredentials 验证Google索引凭据
|
||||||
|
func (h *GoogleIndexHandler) ValidateCredentials(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
CredentialsFile string `json:"credentials_file" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
ErrorResponse(c, "请求参数错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查凭据文件是否存在
|
||||||
|
if _, err := os.Stat(req.CredentialsFile); os.IsNotExist(err) {
|
||||||
|
ErrorResponse(c, "凭据文件不存在", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试创建Google客户端并验证凭据
|
||||||
|
config, err := h.loadCredentials(req.CredentialsFile)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "凭据格式错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证凭据是否有效(尝试获取token)
|
||||||
|
err = h.getValidToken(config)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "凭据验证失败: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "凭据验证成功",
|
||||||
|
"valid": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCredentials 从文件加载凭据
|
||||||
|
func (h *GoogleIndexHandler) loadCredentials(credentialsFile string) (*google.Config, error) {
|
||||||
|
// 从pkg/google/client.go导入的Config
|
||||||
|
// 注意:我们需要一个方法来安全地加载凭据
|
||||||
|
// 为了简化,我们只是检查文件是否可以读取以及格式是否正确
|
||||||
|
|
||||||
|
data, err := os.ReadFile(credentialsFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无法读取凭据文件: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否为有效的JSON
|
||||||
|
var temp map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
|
return nil, fmt.Errorf("凭据文件格式不是有效的JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查必需字段
|
||||||
|
requiredFields := []string{"type", "project_id", "private_key_id", "private_key", "client_email", "client_id"}
|
||||||
|
for _, field := range requiredFields {
|
||||||
|
if _, exists := temp[field]; !exists {
|
||||||
|
return nil, fmt.Errorf("凭据文件缺少必需字段: %s", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回配置(简化处理,实际实现可能需要更复杂的逻辑)
|
||||||
|
return &google.Config{
|
||||||
|
CredentialsFile: credentialsFile,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getValidToken 获取有效的token
|
||||||
|
|
||||||
|
// GetConfigByKey 根据键获取Google索引配置
|
||||||
|
func (h *GoogleIndexHandler) GetConfigByKey(c *gin.Context) {
|
||||||
|
// 从URL参数获取配置键
|
||||||
|
key := c.Param("key")
|
||||||
|
if key == "" {
|
||||||
|
ErrorResponse(c, "配置键不能为空", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, _ := c.Get("username")
|
||||||
|
clientIP, _ := c.Get("client_ip")
|
||||||
|
utils.Info("GoogleIndexHandler.GetConfigByKey - 获取Google索引配置 - 用户: %s, 键: %s, IP: %s", username, key, clientIP)
|
||||||
|
|
||||||
|
// 根据键查找配置
|
||||||
|
config, err := h.repoMgr.SystemConfigRepository.FindByKey(key)
|
||||||
|
if err != nil {
|
||||||
|
// 如果配置不存在,返回默认值或空值
|
||||||
|
SuccessResponse(c, map[string]interface{}{
|
||||||
|
"group": "verification",
|
||||||
|
"key": key,
|
||||||
|
"value": "",
|
||||||
|
"type": "string",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回配置项
|
||||||
|
SuccessResponse(c, map[string]interface{}{
|
||||||
|
"group": "verification",
|
||||||
|
"key": config.Key,
|
||||||
|
"value": config.Value,
|
||||||
|
"type": config.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGoogleIndexConfig 更新Google索引配置(支持分组配置)
|
||||||
|
func (h *GoogleIndexHandler) UpdateGoogleIndexConfig(c *gin.Context) {
|
||||||
|
var req dto.GoogleIndexConfigInput
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
ErrorResponse(c, "参数错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, _ := c.Get("username")
|
||||||
|
clientIP, _ := c.Get("client_ip")
|
||||||
|
utils.Info("GoogleIndexHandler.UpdateGoogleIndexConfig - 用户更新Google索引分组配置 - 用户: %s, 组: %s, 键: %s, IP: %s", username, req.Group, req.Key, clientIP)
|
||||||
|
|
||||||
|
// 处理不同的配置组
|
||||||
|
|
||||||
|
switch req.Group {
|
||||||
|
case "general":
|
||||||
|
switch req.Key {
|
||||||
|
case "general":
|
||||||
|
// 解析general配置
|
||||||
|
var generalConfig dto.GoogleIndexConfigGeneral
|
||||||
|
if err := json.Unmarshal([]byte(req.Value), &generalConfig); err != nil {
|
||||||
|
ErrorResponse(c, "通用配置格式错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 存储各个字段
|
||||||
|
generalConfigs := []entity.SystemConfig{
|
||||||
|
{Key: entity.GoogleIndexConfigKeyEnabled, Value: strconv.FormatBool(generalConfig.Enabled), Type: entity.ConfigTypeBool},
|
||||||
|
{Key: entity.GoogleIndexConfigKeySiteURL, Value: generalConfig.SiteURL, Type: entity.ConfigTypeString},
|
||||||
|
{Key: entity.GoogleIndexConfigKeySiteName, Value: generalConfig.SiteName, Type: entity.ConfigTypeString},
|
||||||
|
}
|
||||||
|
err := h.repoMgr.SystemConfigRepository.UpsertConfigs(generalConfigs)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "保存通用配置失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ErrorResponse(c, "未知的通用配置键: "+req.Key, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "auth":
|
||||||
|
switch req.Key {
|
||||||
|
case "credentials_file":
|
||||||
|
// 解析认证配置
|
||||||
|
var authConfig dto.GoogleIndexConfigAuth
|
||||||
|
if err := json.Unmarshal([]byte(req.Value), &authConfig); err != nil {
|
||||||
|
ErrorResponse(c, "认证配置格式错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 存储认证相关配置
|
||||||
|
authConfigs := []entity.SystemConfig{
|
||||||
|
{Key: entity.GoogleIndexConfigKeyCredentialsFile, Value: authConfig.CredentialsFile, Type: entity.ConfigTypeString},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyClientEmail, Value: authConfig.ClientEmail, Type: entity.ConfigTypeString},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyClientID, Value: authConfig.ClientID, Type: entity.ConfigTypeString},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyPrivateKey, Value: authConfig.PrivateKey, Type: entity.ConfigTypeString},
|
||||||
|
}
|
||||||
|
err := h.repoMgr.SystemConfigRepository.UpsertConfigs(authConfigs)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "保存认证配置失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ErrorResponse(c, "未知的认证配置键: "+req.Key, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "schedule":
|
||||||
|
switch req.Key {
|
||||||
|
case "schedule":
|
||||||
|
// 解析调度配置
|
||||||
|
var scheduleConfig dto.GoogleIndexConfigSchedule
|
||||||
|
if err := json.Unmarshal([]byte(req.Value), &scheduleConfig); err != nil {
|
||||||
|
ErrorResponse(c, "调度配置格式错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 存储调度相关配置
|
||||||
|
scheduleConfigs := []entity.SystemConfig{
|
||||||
|
{Key: entity.GoogleIndexConfigKeyCheckInterval, Value: strconv.Itoa(scheduleConfig.CheckInterval), Type: entity.ConfigTypeInt},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyBatchSize, Value: strconv.Itoa(scheduleConfig.BatchSize), Type: entity.ConfigTypeInt},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyConcurrency, Value: strconv.Itoa(scheduleConfig.Concurrency), Type: entity.ConfigTypeInt},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyRetryAttempts, Value: strconv.Itoa(scheduleConfig.RetryAttempts), Type: entity.ConfigTypeInt},
|
||||||
|
{Key: entity.GoogleIndexConfigKeyRetryDelay, Value: strconv.Itoa(scheduleConfig.RetryDelay), Type: entity.ConfigTypeInt},
|
||||||
|
}
|
||||||
|
err := h.repoMgr.SystemConfigRepository.UpsertConfigs(scheduleConfigs)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "保存调度配置失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ErrorResponse(c, "未知的调度配置键: "+req.Key, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "sitemap":
|
||||||
|
switch req.Key {
|
||||||
|
case "sitemap":
|
||||||
|
// 解析网站地图配置
|
||||||
|
var sitemapConfig dto.GoogleIndexConfigSitemap
|
||||||
|
if err := json.Unmarshal([]byte(req.Value), &sitemapConfig); err != nil {
|
||||||
|
ErrorResponse(c, "网站地图配置格式错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 存储网站地图相关配置
|
||||||
|
sitemapConfigs := []entity.SystemConfig{
|
||||||
|
{Key: entity.GoogleIndexConfigKeyAutoSitemap, Value: strconv.FormatBool(sitemapConfig.AutoSitemap), Type: entity.ConfigTypeBool},
|
||||||
|
{Key: entity.GoogleIndexConfigKeySitemapPath, Value: sitemapConfig.SitemapPath, Type: entity.ConfigTypeString},
|
||||||
|
{Key: entity.GoogleIndexConfigKeySitemapSchedule, Value: sitemapConfig.SitemapSchedule, Type: entity.ConfigTypeString},
|
||||||
|
}
|
||||||
|
err := h.repoMgr.SystemConfigRepository.UpsertConfigs(sitemapConfigs)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "保存网站地图配置失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ErrorResponse(c, "未知的网站地图配置键: "+req.Key, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "verification":
|
||||||
|
switch req.Key {
|
||||||
|
case "google_site_verification":
|
||||||
|
// 解析验证配置
|
||||||
|
var verificationConfig struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(req.Value), &verificationConfig); err != nil {
|
||||||
|
ErrorResponse(c, "验证配置格式错误: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 存储验证字符串
|
||||||
|
verificationConfigs := []entity.SystemConfig{
|
||||||
|
{Key: "google_site_verification_code", Value: verificationConfig.Code, Type: entity.ConfigTypeString},
|
||||||
|
}
|
||||||
|
err := h.repoMgr.SystemConfigRepository.UpsertConfigs(verificationConfigs)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "保存验证配置失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ErrorResponse(c, "未知的验证配置键: "+req.Key, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ErrorResponse(c, "未知的配置组: "+req.Group, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("Google索引分组配置更新成功 - 组: %s, 键: %s - 用户: %s, IP: %s", req.Group, req.Key, username, clientIP)
|
||||||
|
SuccessResponse(c, gin.H{
|
||||||
|
"message": "配置更新成功",
|
||||||
|
"group": req.Group,
|
||||||
|
"key": req.Key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *GoogleIndexHandler) getValidToken(config *google.Config) error {
|
||||||
|
// 这里应该使用Google的验证逻辑
|
||||||
|
// 为了简化我们返回一个模拟的验证过程
|
||||||
|
// 在实际实现中,应该使用Google API进行验证
|
||||||
|
|
||||||
|
// 尝试初始化Google客户端
|
||||||
|
client, err := google.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建Google客户端失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的验证:尝试获取网站列表,如果成功说明凭据有效
|
||||||
|
// 这里我们只检查客户端是否能成功初始化
|
||||||
|
// 在实际实现中,应该尝试执行一个API调用以验证凭据
|
||||||
|
_ = client // 使用client变量避免未使用警告
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -281,6 +281,31 @@ func GetPublicSystemConfig(c *gin.Context) {
|
|||||||
SuccessResponse(c, configResponse)
|
SuccessResponse(c, configResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:获取网站验证代码(公开访问)
|
||||||
|
func GetSiteVerificationCode(c *gin.Context) {
|
||||||
|
// 获取所有系统配置
|
||||||
|
configs, err := repoManager.SystemConfigRepository.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "获取系统配置失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为公共响应格式
|
||||||
|
configResponse := converter.SystemConfigToPublicResponse(configs)
|
||||||
|
|
||||||
|
// 只返回验证代码,确保安全性
|
||||||
|
verificationCode := ""
|
||||||
|
if verificationCodeVal, exists := configResponse["google_site_verification_code"]; exists {
|
||||||
|
if codeStr, ok := verificationCodeVal.(string); ok {
|
||||||
|
verificationCode = codeStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, gin.H{
|
||||||
|
"google_site_verification_code": verificationCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 新增:配置监控端点
|
// 新增:配置监控端点
|
||||||
func GetConfigStatus(c *gin.Context) {
|
func GetConfigStatus(c *gin.Context) {
|
||||||
// 获取配置统计信息
|
// 获取配置统计信息
|
||||||
|
|||||||
30
main.go
30
main.go
@@ -237,6 +237,17 @@ func main() {
|
|||||||
reportHandler := handlers.NewReportHandler(repoManager.ReportRepository, repoManager.ResourceRepository)
|
reportHandler := handlers.NewReportHandler(repoManager.ReportRepository, repoManager.ResourceRepository)
|
||||||
copyrightClaimHandler := handlers.NewCopyrightClaimHandler(repoManager.CopyrightClaimRepository, repoManager.ResourceRepository)
|
copyrightClaimHandler := handlers.NewCopyrightClaimHandler(repoManager.CopyrightClaimRepository, repoManager.ResourceRepository)
|
||||||
|
|
||||||
|
// 创建Google索引任务处理器
|
||||||
|
googleIndexProcessor := task.NewGoogleIndexProcessor(repoManager)
|
||||||
|
|
||||||
|
// 创建Google索引处理器
|
||||||
|
googleIndexHandler := handlers.NewGoogleIndexHandler(repoManager, taskManager)
|
||||||
|
|
||||||
|
// 注册Google索引处理器到任务管理器
|
||||||
|
taskManager.RegisterProcessor(googleIndexProcessor)
|
||||||
|
|
||||||
|
utils.Info("Google索引功能已启用,注册到任务管理器")
|
||||||
|
|
||||||
// API路由
|
// API路由
|
||||||
api := r.Group("/api")
|
api := r.Group("/api")
|
||||||
{
|
{
|
||||||
@@ -363,6 +374,7 @@ func main() {
|
|||||||
api.GET("/system/config/status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetConfigStatus)
|
api.GET("/system/config/status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetConfigStatus)
|
||||||
api.POST("/system/config/toggle-auto-process", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.ToggleAutoProcess)
|
api.POST("/system/config/toggle-auto-process", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.ToggleAutoProcess)
|
||||||
api.GET("/public/system-config", handlers.GetPublicSystemConfig)
|
api.GET("/public/system-config", handlers.GetPublicSystemConfig)
|
||||||
|
api.GET("/public/site-verification", handlers.GetSiteVerificationCode) // 网站验证代码(公开访问)
|
||||||
|
|
||||||
// 热播剧管理路由(查询接口无需认证)
|
// 热播剧管理路由(查询接口无需认证)
|
||||||
api.GET("/hot-dramas", handlers.GetHotDramaList)
|
api.GET("/hot-dramas", handlers.GetHotDramaList)
|
||||||
@@ -521,6 +533,21 @@ func main() {
|
|||||||
api.POST("/sitemap/generate", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GenerateSitemap)
|
api.POST("/sitemap/generate", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GenerateSitemap)
|
||||||
api.GET("/sitemap/status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetSitemapStatus)
|
api.GET("/sitemap/status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetSitemapStatus)
|
||||||
api.POST("/sitemap/full-generate", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GenerateFullSitemap)
|
api.POST("/sitemap/full-generate", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GenerateFullSitemap)
|
||||||
|
|
||||||
|
// Google索引管理API
|
||||||
|
api.GET("/google-index/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.GetConfig)
|
||||||
|
api.POST("/google-index/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.UpdateConfig)
|
||||||
|
api.GET("/google-index/config/:key", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.GetConfigByKey) // 根据键获取配置
|
||||||
|
api.POST("/google-index/config/update", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.UpdateGoogleIndexConfig) // 分组配置更新
|
||||||
|
api.POST("/google-index/tasks", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.CreateTask)
|
||||||
|
api.GET("/google-index/tasks", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.GetTasks)
|
||||||
|
api.GET("/google-index/tasks/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.GetTaskStatus)
|
||||||
|
api.POST("/google-index/tasks/:id/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.StartTask)
|
||||||
|
api.GET("/google-index/tasks/:id/items", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.GetTaskItems)
|
||||||
|
|
||||||
|
// Google索引凭据上传和验证API
|
||||||
|
api.POST("/google-index/upload-credentials", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.UploadCredentials)
|
||||||
|
api.POST("/google-index/validate-credentials", middleware.AuthMiddleware(), middleware.AdminMiddleware(), googleIndexHandler.ValidateCredentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置监控系统
|
// 设置监控系统
|
||||||
@@ -538,10 +565,11 @@ func main() {
|
|||||||
|
|
||||||
// 静态文件服务
|
// 静态文件服务
|
||||||
r.Static("/uploads", "./uploads")
|
r.Static("/uploads", "./uploads")
|
||||||
|
r.Static("/data", "./data")
|
||||||
|
|
||||||
// 添加CORS头到静态文件
|
// 添加CORS头到静态文件
|
||||||
r.Use(func(c *gin.Context) {
|
r.Use(func(c *gin.Context) {
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/uploads/") {
|
if strings.HasPrefix(c.Request.URL.Path, "/uploads/") || strings.HasPrefix(c.Request.URL.Path, "/data/") {
|
||||||
c.Header("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
c.Header("Access-Control-Allow-Methods", "GET, OPTIONS")
|
c.Header("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept")
|
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept")
|
||||||
|
|||||||
241
pkg/google/client.go
Normal file
241
pkg/google/client.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
"google.golang.org/api/searchconsole/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client Google Search Console API客户端
|
||||||
|
type Client struct {
|
||||||
|
service *searchconsole.Service
|
||||||
|
SiteURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config 配置信息
|
||||||
|
type Config struct {
|
||||||
|
CredentialsFile string `json:"credentials_file"`
|
||||||
|
SiteURL string `json:"site_url"`
|
||||||
|
TokenFile string `json:"token_file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLInspectionRequest URL检查请求
|
||||||
|
type URLInspectionRequest struct {
|
||||||
|
InspectionURL string `json:"inspectionUrl"`
|
||||||
|
SiteURL string `json:"siteUrl"`
|
||||||
|
LanguageCode string `json:"languageCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLInspectionResult URL检查结果
|
||||||
|
type URLInspectionResult struct {
|
||||||
|
IndexStatusResult struct {
|
||||||
|
IndexingState string `json:"indexingState"`
|
||||||
|
LastCrawled string `json:"lastCrawled"`
|
||||||
|
CrawlErrors []struct {
|
||||||
|
ErrorCode string `json:"errorCode"`
|
||||||
|
} `json:"crawlErrors"`
|
||||||
|
} `json:"indexStatusResult"`
|
||||||
|
MobileUsabilityResult struct {
|
||||||
|
MobileFriendly bool `json:"mobileFriendly"`
|
||||||
|
} `json:"mobileUsabilityResult"`
|
||||||
|
RichResultsResult struct {
|
||||||
|
Detected struct {
|
||||||
|
Items []struct {
|
||||||
|
RichResultType string `json:"richResultType"`
|
||||||
|
} `json:"items"`
|
||||||
|
} `json:"detected"`
|
||||||
|
} `json:"richResultsResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 创建新的客户端
|
||||||
|
func NewClient(config *Config) (*Client, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 读取认证文件
|
||||||
|
credentials, err := os.ReadFile(config.CredentialsFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取认证文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建OAuth2配置
|
||||||
|
oauthConfig, err := google.ConfigFromJSON(credentials, searchconsole.WebmastersScope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建OAuth配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从文件读取token
|
||||||
|
token, err := tokenFromFile(config.TokenFile)
|
||||||
|
if err != nil {
|
||||||
|
// 如果没有token,启动web认证流程
|
||||||
|
token = getTokenFromWeb(oauthConfig)
|
||||||
|
saveToken(config.TokenFile, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP客户端
|
||||||
|
client := oauthConfig.Client(ctx, token)
|
||||||
|
|
||||||
|
// 创建Search Console服务
|
||||||
|
service, err := searchconsole.NewService(ctx, option.WithHTTPClient(client))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建Search Console服务失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
service: service,
|
||||||
|
SiteURL: config.SiteURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InspectURL 检查URL索引状态
|
||||||
|
func (c *Client) InspectURL(url string) (*URLInspectionResult, error) {
|
||||||
|
request := &searchconsole.InspectUrlIndexRequest{
|
||||||
|
InspectionUrl: url,
|
||||||
|
SiteUrl: c.SiteURL,
|
||||||
|
LanguageCode: "zh-CN",
|
||||||
|
}
|
||||||
|
|
||||||
|
call := c.service.UrlInspection.Index.Inspect(request)
|
||||||
|
response, err := call.Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("检查URL失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换响应
|
||||||
|
result := &URLInspectionResult{}
|
||||||
|
if response.InspectionResult != nil {
|
||||||
|
if response.InspectionResult.IndexStatusResult != nil {
|
||||||
|
result.IndexStatusResult.IndexingState = string(response.InspectionResult.IndexStatusResult.IndexingState)
|
||||||
|
if response.InspectionResult.IndexStatusResult.LastCrawlTime != "" {
|
||||||
|
result.IndexStatusResult.LastCrawled = response.InspectionResult.IndexStatusResult.LastCrawlTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.InspectionResult.MobileUsabilityResult != nil {
|
||||||
|
result.MobileUsabilityResult.MobileFriendly = response.InspectionResult.MobileUsabilityResult.Verdict == "MOBILE_USABILITY_VERdict_PASS"
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.InspectionResult.RichResultsResult != nil && response.InspectionResult.RichResultsResult.Verdict != "RICH_RESULTS_VERdict_PASS" {
|
||||||
|
// 如果有富媒体结果检查信息
|
||||||
|
result.RichResultsResult.Detected.Items = append(result.RichResultsResult.Detected.Items, struct {
|
||||||
|
RichResultType string `json:"richResultType"`
|
||||||
|
}{
|
||||||
|
RichResultType: "UNKNOWN",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitSitemap 提交网站地图
|
||||||
|
func (c *Client) SubmitSitemap(sitemapURL string) error {
|
||||||
|
call := c.service.Sitemaps.Submit(c.SiteURL, sitemapURL)
|
||||||
|
err := call.Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("提交网站地图失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSites 获取已验证的网站列表
|
||||||
|
func (c *Client) GetSites() ([]*searchconsole.WmxSite, error) {
|
||||||
|
call := c.service.Sites.List()
|
||||||
|
response, err := call.Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取网站列表失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.SiteEntry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSearchAnalytics 获取搜索分析数据
|
||||||
|
func (c *Client) GetSearchAnalytics(startDate, endDate string) (*searchconsole.SearchAnalyticsQueryResponse, error) {
|
||||||
|
request := &searchconsole.SearchAnalyticsQueryRequest{
|
||||||
|
StartDate: startDate,
|
||||||
|
EndDate: endDate,
|
||||||
|
Type: "web",
|
||||||
|
}
|
||||||
|
|
||||||
|
call := c.service.Searchanalytics.Query(c.SiteURL, request)
|
||||||
|
response, err := call.Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取搜索分析数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTokenFromWeb 通过web流程获取token
|
||||||
|
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
|
||||||
|
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
|
||||||
|
fmt.Printf("请在浏览器中访问以下URL进行认证:\n%s\n", authURL)
|
||||||
|
fmt.Printf("输入授权代码: ")
|
||||||
|
|
||||||
|
var authCode string
|
||||||
|
if _, err := fmt.Scan(&authCode); err != nil {
|
||||||
|
panic(fmt.Sprintf("读取授权代码失败: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := config.Exchange(oauth2.NoContext, authCode)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("获取token失败: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenFromFile 从文件读取token
|
||||||
|
func tokenFromFile(file string) (*oauth2.Token, error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
defer f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{}
|
||||||
|
err = json.NewDecoder(f).Decode(token)
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveToken 保存token到文件
|
||||||
|
func saveToken(file string, token *oauth2.Token) {
|
||||||
|
fmt.Printf("保存凭证文件到: %s\n", file)
|
||||||
|
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("无法保存凭证文件: %v", err))
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
json.NewEncoder(f).Encode(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchInspectURL 批量检查URL状态
|
||||||
|
func (c *Client) BatchInspectURL(urls []string, callback func(url string, result *URLInspectionResult, err error)) {
|
||||||
|
semaphore := make(chan struct{}, 5) // 限制并发数
|
||||||
|
|
||||||
|
for _, url := range urls {
|
||||||
|
go func(u string) {
|
||||||
|
semaphore <- struct{}{} // 获取信号量
|
||||||
|
defer func() { <-semaphore }() // 释放信号量
|
||||||
|
|
||||||
|
result, err := c.InspectURL(u)
|
||||||
|
callback(u, result, err)
|
||||||
|
}(url)
|
||||||
|
|
||||||
|
// 避免请求过快
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有goroutine完成
|
||||||
|
for i := 0; i < cap(semaphore); i++ {
|
||||||
|
semaphore <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
pkg/google/sitemap.go
Normal file
166
pkg/google/sitemap.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sitemap 网站地图结构
|
||||||
|
type Sitemap struct {
|
||||||
|
XMLName xml.Name `xml:"urlset"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
URLs []SitemapURL `xml:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SitemapURL 网站地图URL项
|
||||||
|
type SitemapURL struct {
|
||||||
|
Loc string `xml:"loc"`
|
||||||
|
LastMod string `xml:"lastmod,omitempty"`
|
||||||
|
ChangeFreq string `xml:"changefreq,omitempty"`
|
||||||
|
Priority string `xml:"priority,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SitemapIndex 网站地图索引
|
||||||
|
type SitemapIndex struct {
|
||||||
|
XMLName xml.Name `xml:"sitemapindex"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Sitemaps []SitemapRef `xml:"sitemap"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SitemapRef 网站地图引用
|
||||||
|
type SitemapRef struct {
|
||||||
|
Loc string `xml:"loc"`
|
||||||
|
LastMod string `xml:"lastmod,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSitemap 生成网站地图
|
||||||
|
func GenerateSitemap(urls []string, filename string) error {
|
||||||
|
sitemap := Sitemap{
|
||||||
|
Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, url := range urls {
|
||||||
|
sitemapURL := SitemapURL{
|
||||||
|
Loc: strings.TrimSpace(url),
|
||||||
|
LastMod: time.Now().Format("2006-01-02"),
|
||||||
|
ChangeFreq: "weekly",
|
||||||
|
Priority: "0.8",
|
||||||
|
}
|
||||||
|
sitemap.URLs = append(sitemap.URLs, sitemapURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.MarshalIndent(sitemap, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("生成XML失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加XML头部
|
||||||
|
xmlData := []byte(xml.Header + string(data))
|
||||||
|
|
||||||
|
return os.WriteFile(filename, xmlData, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSitemapIndex 生成网站地图索引
|
||||||
|
func GenerateSitemapIndex(sitemaps []string, filename string) error {
|
||||||
|
index := SitemapIndex{
|
||||||
|
Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sitemap := range sitemaps {
|
||||||
|
ref := SitemapRef{
|
||||||
|
Loc: strings.TrimSpace(sitemap),
|
||||||
|
LastMod: time.Now().Format("2006-01-02"),
|
||||||
|
}
|
||||||
|
index.Sitemaps = append(index.Sitemaps, ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.MarshalIndent(index, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("生成XML失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加XML头部
|
||||||
|
xmlData := []byte(xml.Header + string(data))
|
||||||
|
|
||||||
|
return os.WriteFile(filename, xmlData, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitSitemap 将大量URL分割成多个网站地图
|
||||||
|
func SplitSitemap(urls []string, maxURLsPerSitemap int, baseURL string) ([]string, error) {
|
||||||
|
if maxURLsPerSitemap <= 0 {
|
||||||
|
maxURLsPerSitemap = 50000 // Google限制
|
||||||
|
}
|
||||||
|
|
||||||
|
var sitemapFiles []string
|
||||||
|
totalURLs := len(urls)
|
||||||
|
sitemapCount := (totalURLs + maxURLsPerSitemap - 1) / maxURLsPerSitemap
|
||||||
|
|
||||||
|
for i := 0; i < sitemapCount; i++ {
|
||||||
|
start := i * maxURLsPerSitemap
|
||||||
|
end := start + maxURLsPerSitemap
|
||||||
|
if end > totalURLs {
|
||||||
|
end = totalURLs
|
||||||
|
}
|
||||||
|
|
||||||
|
sitemapURLs := urls[start:end]
|
||||||
|
filename := fmt.Sprintf("sitemap_part_%d.xml", i+1)
|
||||||
|
|
||||||
|
err := GenerateSitemap(sitemapURLs, filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("生成网站地图 %s 失败: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sitemapFiles = append(sitemapFiles, baseURL+filename)
|
||||||
|
fmt.Printf("生成网站地图: %s (%d URLs)\n", filename, len(sitemapURLs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成网站地图索引
|
||||||
|
if len(sitemapFiles) > 1 {
|
||||||
|
indexFiles := make([]string, len(sitemapFiles))
|
||||||
|
for i, file := range sitemapFiles {
|
||||||
|
indexFiles[i] = file
|
||||||
|
}
|
||||||
|
|
||||||
|
err := GenerateSitemapIndex(indexFiles, "sitemap_index.xml")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("生成网站地图索引失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("生成网站地图索引: sitemap_index.xml\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sitemapFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingSearchEngines 通知搜索引擎网站地图更新
|
||||||
|
func PingSearchEngines(sitemapURL string) error {
|
||||||
|
searchEngines := []string{
|
||||||
|
"http://www.google.com/webmasters/sitemaps/ping?sitemap=",
|
||||||
|
"http://www.bing.com/webmaster/ping.aspx?siteMap=",
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
|
for _, engine := range searchEngines {
|
||||||
|
fullURL := engine + sitemapURL
|
||||||
|
|
||||||
|
resp, err := client.Get(fullURL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("通知搜索引擎失败 %s: %v\n", engine, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
fmt.Printf("成功通知: %s\n", engine)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("通知失败: %s (状态码: %d)\n", engine, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
396
task/google_index_processor.go
Normal file
396
task/google_index_processor.go
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
"github.com/ctwj/urldb/db/repo"
|
||||||
|
"github.com/ctwj/urldb/pkg/google"
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoogleIndexProcessor Google索引任务处理器
|
||||||
|
type GoogleIndexProcessor struct {
|
||||||
|
repoMgr *repo.RepositoryManager
|
||||||
|
client *google.Client
|
||||||
|
config *GoogleIndexProcessorConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexProcessorConfig Google索引处理器配置
|
||||||
|
type GoogleIndexProcessorConfig struct {
|
||||||
|
CredentialsFile string
|
||||||
|
SiteURL string
|
||||||
|
TokenFile string
|
||||||
|
Concurrency int
|
||||||
|
RetryAttempts int
|
||||||
|
RetryDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskInput Google索引任务输入数据结构
|
||||||
|
type GoogleIndexTaskInput struct {
|
||||||
|
URLs []string `json:"urls"`
|
||||||
|
Operation string `json:"operation"` // indexing_check, sitemap_submit, batch_index
|
||||||
|
SitemapURL string `json:"sitemap_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleIndexTaskOutput Google索引任务输出数据结构
|
||||||
|
type GoogleIndexTaskOutput struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
IndexStatus string `json:"index_status,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
Result *google.URLInspectionResult `json:"result,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGoogleIndexProcessor 创建Google索引任务处理器
|
||||||
|
func NewGoogleIndexProcessor(repoMgr *repo.RepositoryManager) *GoogleIndexProcessor {
|
||||||
|
return &GoogleIndexProcessor{
|
||||||
|
repoMgr: repoMgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskType 获取任务类型
|
||||||
|
func (gip *GoogleIndexProcessor) GetTaskType() string {
|
||||||
|
return "google_index"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process 处理Google索引任务项
|
||||||
|
func (gip *GoogleIndexProcessor) Process(ctx context.Context, taskID uint, item *entity.TaskItem) error {
|
||||||
|
utils.Info("开始处理Google索引任务项: %d", item.ID)
|
||||||
|
|
||||||
|
// 解析输入数据
|
||||||
|
var input GoogleIndexTaskInput
|
||||||
|
if err := json.Unmarshal([]byte(item.InputData), &input); err != nil {
|
||||||
|
utils.Error("解析输入数据失败: %v", err)
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 400, err.Error())
|
||||||
|
return fmt.Errorf("解析输入数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化Google客户端
|
||||||
|
client, err := gip.initGoogleClient()
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("初始化Google客户端失败: %v", err)
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 500, err.Error())
|
||||||
|
return fmt.Errorf("初始化Google客户端失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据操作类型执行不同任务
|
||||||
|
switch input.Operation {
|
||||||
|
case "url_indexing":
|
||||||
|
return gip.processURLIndexing(ctx, client, taskID, item, input)
|
||||||
|
case "sitemap_submit":
|
||||||
|
return gip.processSitemapSubmit(ctx, client, taskID, item, input)
|
||||||
|
case "status_check":
|
||||||
|
return gip.processStatusCheck(ctx, client, taskID, item, input)
|
||||||
|
default:
|
||||||
|
errorMsg := fmt.Sprintf("不支持的操作类型: %s", input.Operation)
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 400, errorMsg)
|
||||||
|
return fmt.Errorf(errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processURLIndexing 处理URL索引检查
|
||||||
|
func (gip *GoogleIndexProcessor) processURLIndexing(ctx context.Context, client *google.Client, taskID uint, item *entity.TaskItem, input GoogleIndexTaskInput) error {
|
||||||
|
utils.Info("开始URL索引检查: %v", input.URLs)
|
||||||
|
|
||||||
|
for _, url := range input.URLs {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 0, "任务被取消")
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
// 检查URL索引状态
|
||||||
|
result, err := gip.inspectURL(client, url)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("检查URL索引状态失败: %s, 错误: %v", url, err)
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 500, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新任务项状态
|
||||||
|
var lastCrawled *time.Time
|
||||||
|
if result.IndexStatusResult.LastCrawled != "" {
|
||||||
|
parsedTime, err := time.Parse(time.RFC3339, result.IndexStatusResult.LastCrawled)
|
||||||
|
if err == nil {
|
||||||
|
lastCrawled = &parsedTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusSuccess, result.IndexStatusResult.IndexingState, result.MobileUsabilityResult.MobileFriendly, lastCrawled, 200, "")
|
||||||
|
|
||||||
|
// 更新URL状态记录
|
||||||
|
gip.updateURLStatus(url, result.IndexStatusResult.IndexingState, lastCrawled)
|
||||||
|
|
||||||
|
// 添加延迟避免API限制
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("URL索引检查完成")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSitemapSubmit 处理网站地图提交
|
||||||
|
func (gip *GoogleIndexProcessor) processSitemapSubmit(ctx context.Context, client *google.Client, taskID uint, item *entity.TaskItem, input GoogleIndexTaskInput) error {
|
||||||
|
utils.Info("开始网站地图提交: %s", input.SitemapURL)
|
||||||
|
|
||||||
|
if input.SitemapURL == "" {
|
||||||
|
errorMsg := "网站地图URL不能为空"
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 400, errorMsg)
|
||||||
|
return fmt.Errorf(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交网站地图
|
||||||
|
err := client.SubmitSitemap(input.SitemapURL)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("提交网站地图失败: %s, 错误: %v", input.SitemapURL, err)
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 500, err.Error())
|
||||||
|
return fmt.Errorf("提交网站地图失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新任务项状态
|
||||||
|
now := time.Now()
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusSuccess, "SUBMITTED", false, &now, 200, "")
|
||||||
|
|
||||||
|
utils.Info("网站地图提交完成: %s", input.SitemapURL)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processStatusCheck 处理状态检查
|
||||||
|
func (gip *GoogleIndexProcessor) processStatusCheck(ctx context.Context, client *google.Client, taskID uint, item *entity.TaskItem, input GoogleIndexTaskInput) error {
|
||||||
|
utils.Info("开始状态检查: %v", input.URLs)
|
||||||
|
|
||||||
|
for _, url := range input.URLs {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 0, "任务被取消")
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
// 检查URL状态
|
||||||
|
result, err := gip.inspectURL(client, url)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("检查URL状态失败: %s, 错误: %v", url, err)
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusFailed, "", false, nil, 500, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新任务项状态
|
||||||
|
var lastCrawled *time.Time
|
||||||
|
if result.IndexStatusResult.LastCrawled != "" {
|
||||||
|
parsedTime, err := time.Parse(time.RFC3339, result.IndexStatusResult.LastCrawled)
|
||||||
|
if err == nil {
|
||||||
|
lastCrawled = &parsedTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gip.updateTaskItemStatus(item, entity.TaskItemStatusSuccess, result.IndexStatusResult.IndexingState, result.MobileUsabilityResult.MobileFriendly, lastCrawled, 200, "")
|
||||||
|
|
||||||
|
utils.Info("URL状态检查完成: %s, 状态: %s", url, result.IndexStatusResult.IndexingState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initGoogleClient 初始化Google客户端
|
||||||
|
func (gip *GoogleIndexProcessor) initGoogleClient() (*google.Client, error) {
|
||||||
|
// 从配置中获取Google认证信息
|
||||||
|
credentialsFile, err := gip.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyCredentialsFile)
|
||||||
|
if err != nil || credentialsFile == "" {
|
||||||
|
return nil, fmt.Errorf("未配置Google认证文件: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
siteURL, err := gip.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeySiteURL)
|
||||||
|
if err != nil || siteURL == "" {
|
||||||
|
return nil, fmt.Errorf("未配置网站URL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &google.Config{
|
||||||
|
CredentialsFile: credentialsFile,
|
||||||
|
SiteURL: siteURL,
|
||||||
|
TokenFile: "google_token.json", // 使用固定token文件名
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := google.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建Google客户端失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspectURL 检查URL索引状态
|
||||||
|
func (gip *GoogleIndexProcessor) inspectURL(client *google.Client, url string) (*google.URLInspectionResult, error) {
|
||||||
|
// 重试机制
|
||||||
|
var result *google.URLInspectionResult
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for attempt := 0; attempt <= gip.config.RetryAttempts; attempt++ {
|
||||||
|
result, err = client.InspectURL(url)
|
||||||
|
if err == nil {
|
||||||
|
break // 成功则退出重试循环
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt < gip.config.RetryAttempts {
|
||||||
|
utils.Info("URL检查失败,第%d次重试: %s, 错误: %v", attempt+1, url, err)
|
||||||
|
time.Sleep(gip.config.RetryDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("检查URL失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateTaskItemStatus 更新任务项状态
|
||||||
|
func (gip *GoogleIndexProcessor) updateTaskItemStatus(item *entity.TaskItem, status entity.TaskItemStatus, indexStatus string, mobileFriendly bool, lastCrawled *time.Time, statusCode int, errorMessage string) {
|
||||||
|
item.Status = status
|
||||||
|
item.ErrorMessage = errorMessage
|
||||||
|
|
||||||
|
// 更新Google索引特有字段
|
||||||
|
item.IndexStatus = indexStatus
|
||||||
|
item.MobileFriendly = mobileFriendly
|
||||||
|
item.LastCrawled = lastCrawled
|
||||||
|
item.StatusCode = statusCode
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
item.ProcessedAt = &now
|
||||||
|
|
||||||
|
// 保存更新
|
||||||
|
if err := gip.repoMgr.TaskItemRepository.Update(item); err != nil {
|
||||||
|
utils.Error("更新任务项状态失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateURLStatus 更新URL状态记录(使用任务项存储)
|
||||||
|
func (gip *GoogleIndexProcessor) updateURLStatus(url string, indexStatus string, lastCrawled *time.Time) {
|
||||||
|
// 在任务项中记录URL状态,而不是使用专门的URL状态表
|
||||||
|
// 此功能现在通过任务系统中的TaskItem记录来跟踪
|
||||||
|
utils.Debug("URL状态已更新: %s, 状态: %s", url, indexStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchProcessURLs 批量处理URLs
|
||||||
|
func (gip *GoogleIndexProcessor) BatchProcessURLs(ctx context.Context, urls []string, operation string, taskID uint) error {
|
||||||
|
utils.Info("开始批量处理URLs,数量: %d, 操作: %s", len(urls), operation)
|
||||||
|
|
||||||
|
// 根据并发数创建工作池
|
||||||
|
semaphore := make(chan struct{}, gip.config.Concurrency)
|
||||||
|
errChan := make(chan error, len(urls))
|
||||||
|
|
||||||
|
for _, url := range urls {
|
||||||
|
go func(u string) {
|
||||||
|
semaphore <- struct{}{} // 获取信号量
|
||||||
|
defer func() { <-semaphore }() // 释放信号量
|
||||||
|
|
||||||
|
// 处理单个URL
|
||||||
|
client, err := gip.initGoogleClient()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("初始化客户端失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := gip.inspectURL(client, u)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("处理URL失败: %s, 错误: %v", u, err)
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
var lastCrawled *time.Time
|
||||||
|
if result.IndexStatusResult.LastCrawled != "" {
|
||||||
|
parsedTime, err := time.Parse(time.RFC3339, result.IndexStatusResult.LastCrawled)
|
||||||
|
if err == nil {
|
||||||
|
lastCrawled = &parsedTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务项记录
|
||||||
|
now := time.Now()
|
||||||
|
inputData := map[string]interface{}{
|
||||||
|
"urls": []string{u},
|
||||||
|
"operation": "url_indexing",
|
||||||
|
}
|
||||||
|
inputDataJSON, _ := json.Marshal(inputData)
|
||||||
|
|
||||||
|
taskItem := &entity.TaskItem{
|
||||||
|
TaskID: taskID,
|
||||||
|
Status: entity.TaskItemStatusSuccess,
|
||||||
|
InputData: string(inputDataJSON),
|
||||||
|
URL: u,
|
||||||
|
IndexStatus: result.IndexStatusResult.IndexingState,
|
||||||
|
MobileFriendly: result.MobileUsabilityResult.MobileFriendly,
|
||||||
|
LastCrawled: lastCrawled,
|
||||||
|
StatusCode: 200,
|
||||||
|
ProcessedAt: &now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gip.repoMgr.TaskItemRepository.Create(taskItem); err != nil {
|
||||||
|
utils.Error("创建任务项失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新URL状态
|
||||||
|
gip.updateURLStatus(u, result.IndexStatusResult.IndexingState, lastCrawled)
|
||||||
|
|
||||||
|
errChan <- nil
|
||||||
|
}(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有goroutine完成
|
||||||
|
for i := 0; i < len(urls); i++ {
|
||||||
|
err := <-errChan
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("批量处理URL时出错: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("批量处理URLs完成")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitSitemap 提交网站地图
|
||||||
|
func (gip *GoogleIndexProcessor) SubmitSitemap(ctx context.Context, sitemapURL string, taskID uint) error {
|
||||||
|
utils.Info("开始提交网站地图: %s", sitemapURL)
|
||||||
|
|
||||||
|
client, err := gip.initGoogleClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化Google客户端失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SubmitSitemap(sitemapURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("提交网站地图失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务项记录
|
||||||
|
now := time.Now()
|
||||||
|
inputData := map[string]interface{}{
|
||||||
|
"sitemap_url": sitemapURL,
|
||||||
|
"operation": "sitemap_submit",
|
||||||
|
}
|
||||||
|
inputDataJSON, _ := json.Marshal(inputData)
|
||||||
|
|
||||||
|
taskItem := &entity.TaskItem{
|
||||||
|
TaskID: taskID,
|
||||||
|
Status: entity.TaskItemStatusSuccess,
|
||||||
|
InputData: string(inputDataJSON),
|
||||||
|
URL: sitemapURL,
|
||||||
|
IndexStatus: "SUBMITTED",
|
||||||
|
StatusCode: 200,
|
||||||
|
ProcessedAt: &now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gip.repoMgr.TaskItemRepository.Create(taskItem); err != nil {
|
||||||
|
utils.Error("创建任务项失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("网站地图提交完成: %s", sitemapURL)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -555,3 +555,71 @@ func (tm *TaskManager) RecoverRunningTasks() error {
|
|||||||
utils.Info("任务恢复完成,共恢复 %d 个任务", recoveredCount)
|
utils.Info("任务恢复完成,共恢复 %d 个任务", recoveredCount)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateTask 创建任务
|
||||||
|
func (tm *TaskManager) CreateTask(taskType, name, description string, configID *uint) (*entity.Task, error) {
|
||||||
|
// 验证任务类型是否有对应的处理器
|
||||||
|
tm.mu.RLock()
|
||||||
|
_, exists := tm.processors[taskType]
|
||||||
|
tm.mu.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("未找到任务类型 %s 的处理器", taskType)
|
||||||
|
}
|
||||||
|
|
||||||
|
task := &entity.Task{
|
||||||
|
Type: entity.TaskType(taskType),
|
||||||
|
Name: name,
|
||||||
|
Title: name, // 设置Title为相同值,保持兼容性
|
||||||
|
Description: description,
|
||||||
|
Status: entity.TaskStatusPending,
|
||||||
|
ConfigID: configID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tm.repoMgr.TaskRepository.Create(task)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建任务失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("创建任务成功: ID=%d, 类型=%s, 名称=%s", task.ID, task.Type, task.Name)
|
||||||
|
return task, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTask 获取任务详情
|
||||||
|
func (tm *TaskManager) GetTask(taskID uint) (*entity.Task, error) {
|
||||||
|
task, err := tm.repoMgr.TaskRepository.GetByID(taskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取任务失败: %v", err)
|
||||||
|
}
|
||||||
|
return task, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskItemStats 获取任务项统计信息
|
||||||
|
func (tm *TaskManager) GetTaskItemStats(taskID uint) (map[string]int, error) {
|
||||||
|
stats, err := tm.repoMgr.TaskItemRepository.GetStatsByTaskID(taskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取任务项统计失败: %v", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTaskItems 查询任务项
|
||||||
|
func (tm *TaskManager) QueryTaskItems(taskID uint, page, pageSize int, status string) ([]*entity.TaskItem, int64, error) {
|
||||||
|
items, total, err := tm.repoMgr.TaskItemRepository.GetListByTaskID(taskID, page, pageSize, status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("查询任务项失败: %v", err)
|
||||||
|
}
|
||||||
|
return items, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTaskItems 创建任务项
|
||||||
|
func (tm *TaskManager) CreateTaskItems(taskID uint, items []*entity.TaskItem) error {
|
||||||
|
for _, item := range items {
|
||||||
|
item.TaskID = taskID
|
||||||
|
if err := tm.repoMgr.TaskItemRepository.Create(item); err != nil {
|
||||||
|
return fmt.Errorf("创建任务项失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
utils.Info("创建任务项成功: 任务ID=%d, 数量=%d", taskID, len(items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useApiFetch } from './useApiFetch'
|
import { useApiFetch } from './useApiFetch'
|
||||||
import { useUserStore } from '~/stores/user'
|
import { useUserStore } from '~/stores/user'
|
||||||
|
import { useGoogleIndexApi } from './useGoogleIndexApi'
|
||||||
|
|
||||||
// 统一响应解析函数
|
// 统一响应解析函数
|
||||||
export const parseApiResponse = <T>(response: any): T => {
|
export const parseApiResponse = <T>(response: any): T => {
|
||||||
@@ -447,6 +448,7 @@ export const useApi = () => {
|
|||||||
apiAccessLogApi: useApiAccessLogApi(),
|
apiAccessLogApi: useApiAccessLogApi(),
|
||||||
systemLogApi: useSystemLogApi(),
|
systemLogApi: useSystemLogApi(),
|
||||||
wechatApi: useWechatApi(),
|
wechatApi: useWechatApi(),
|
||||||
sitemapApi: useSitemapApi()
|
sitemapApi: useSitemapApi(),
|
||||||
|
googleIndexApi: useGoogleIndexApi()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
245
web/composables/useGoogleIndexApi.ts
Normal file
245
web/composables/useGoogleIndexApi.ts
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import { useApiFetch } from './useApiFetch'
|
||||||
|
import { parseApiResponse } from './useApi'
|
||||||
|
|
||||||
|
// Google索引配置类型定义
|
||||||
|
export interface GoogleIndexConfig {
|
||||||
|
id?: number
|
||||||
|
group: string
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
type?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google索引任务类型定义
|
||||||
|
export interface GoogleIndexTask {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
status: string
|
||||||
|
description: string
|
||||||
|
totalItems: number
|
||||||
|
processedItems: number
|
||||||
|
successItems: number
|
||||||
|
failedItems: number
|
||||||
|
indexedURLs: number
|
||||||
|
failedURLs: number
|
||||||
|
errorMessage?: string
|
||||||
|
configID?: number
|
||||||
|
startedAt?: Date
|
||||||
|
completedAt?: Date
|
||||||
|
createdAt: Date
|
||||||
|
updatedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google索引任务项类型定义
|
||||||
|
export interface GoogleIndexTaskItem {
|
||||||
|
id: number
|
||||||
|
taskID: number
|
||||||
|
URL: string
|
||||||
|
status: string
|
||||||
|
indexStatus: string
|
||||||
|
errorMessage?: string
|
||||||
|
inspectResult?: string
|
||||||
|
mobileFriendly: boolean
|
||||||
|
lastCrawled?: Date
|
||||||
|
statusCode: number
|
||||||
|
startedAt?: Date
|
||||||
|
completedAt?: Date
|
||||||
|
createdAt: Date
|
||||||
|
updatedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL状态类型定义
|
||||||
|
export interface GoogleIndexURLStatus {
|
||||||
|
id: number
|
||||||
|
URL: string
|
||||||
|
indexStatus: string
|
||||||
|
lastChecked: Date
|
||||||
|
canonicalURL?: string
|
||||||
|
lastCrawled?: Date
|
||||||
|
changeFreq?: string
|
||||||
|
priority?: number
|
||||||
|
mobileFriendly: boolean
|
||||||
|
robotsBlocked: boolean
|
||||||
|
lastError?: string
|
||||||
|
statusCode: number
|
||||||
|
statusCodeText: string
|
||||||
|
checkCount: number
|
||||||
|
successCount: number
|
||||||
|
failureCount: number
|
||||||
|
createdAt: Date
|
||||||
|
updatedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google索引状态响应类型定义
|
||||||
|
export interface GoogleIndexStatusResponse {
|
||||||
|
enabled: boolean
|
||||||
|
siteURL: string
|
||||||
|
lastCheckTime: Date
|
||||||
|
totalURLs: number
|
||||||
|
indexedURLs: number
|
||||||
|
notIndexedURLs: number
|
||||||
|
errorURLs: number
|
||||||
|
lastSitemapSubmit: Date
|
||||||
|
authValid: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google索引任务列表响应类型定义
|
||||||
|
export interface GoogleIndexTaskListResponse {
|
||||||
|
tasks: GoogleIndexTask[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google索引任务项分页响应类型定义
|
||||||
|
export interface GoogleIndexTaskItemPageResponse {
|
||||||
|
items: GoogleIndexTaskItem[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google索引API封装
|
||||||
|
export const useGoogleIndexApi = () => {
|
||||||
|
// 配置管理API
|
||||||
|
const getGoogleIndexConfig = (params?: any) =>
|
||||||
|
useApiFetch('/google-index/config', { params }).then(parseApiResponse<GoogleIndexConfig[]>)
|
||||||
|
|
||||||
|
const getGoogleIndexConfigByKey = (key: string) =>
|
||||||
|
useApiFetch(`/google-index/config/${key}`).then(parseApiResponse<GoogleIndexConfig>)
|
||||||
|
|
||||||
|
const updateGoogleIndexConfig = (data: GoogleIndexConfig) =>
|
||||||
|
useApiFetch('/google-index/config', { method: 'POST', body: data }).then(parseApiResponse<GoogleIndexConfig>)
|
||||||
|
|
||||||
|
const deleteGoogleIndexConfig = (key: string) =>
|
||||||
|
useApiFetch(`/google-index/config/${key}`, { method: 'DELETE' }).then(parseApiResponse<boolean>)
|
||||||
|
|
||||||
|
// 任务管理API
|
||||||
|
const getGoogleIndexTasks = (params?: any) =>
|
||||||
|
useApiFetch('/google-index/tasks', { params }).then(parseApiResponse<GoogleIndexTaskListResponse>)
|
||||||
|
|
||||||
|
const getGoogleIndexTask = (id: number) =>
|
||||||
|
useApiFetch(`/google-index/tasks/${id}`).then(parseApiResponse<GoogleIndexTask>)
|
||||||
|
|
||||||
|
const createGoogleIndexTask = (data: any) =>
|
||||||
|
useApiFetch('/google-index/tasks', { method: 'POST', body: data }).then(parseApiResponse<GoogleIndexTask>)
|
||||||
|
|
||||||
|
const startGoogleIndexTask = (id: number) =>
|
||||||
|
useApiFetch(`/google-index/tasks/${id}/start`, { method: 'POST' }).then(parseApiResponse<boolean>)
|
||||||
|
|
||||||
|
const stopGoogleIndexTask = (id: number) =>
|
||||||
|
useApiFetch(`/google-index/tasks/${id}/stop`, { method: 'POST' }).then(parseApiResponse<boolean>)
|
||||||
|
|
||||||
|
const deleteGoogleIndexTask = (id: number) =>
|
||||||
|
useApiFetch(`/google-index/tasks/${id}`, { method: 'DELETE' }).then(parseApiResponse<boolean>)
|
||||||
|
|
||||||
|
// 任务项管理API
|
||||||
|
const getGoogleIndexTaskItems = (taskId: number, params?: any) =>
|
||||||
|
useApiFetch(`/google-index/tasks/${taskId}/items`, { params }).then(parseApiResponse<GoogleIndexTaskItemPageResponse>)
|
||||||
|
|
||||||
|
// URL状态管理API
|
||||||
|
const getGoogleIndexURLStatus = (params?: any) =>
|
||||||
|
useApiFetch('/google-index/urls/status', { params }).then(parseApiResponse<GoogleIndexURLStatus[]>)
|
||||||
|
|
||||||
|
const getGoogleIndexURLStatusByURL = (url: string) =>
|
||||||
|
useApiFetch(`/google-index/urls/status/${encodeURIComponent(url)}`).then(parseApiResponse<GoogleIndexURLStatus>)
|
||||||
|
|
||||||
|
const checkGoogleIndexURLStatus = (data: { urls: string[] }) =>
|
||||||
|
useApiFetch('/google-index/urls/check', { method: 'POST', body: data }).then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
const submitGoogleIndexURL = (data: { urls: string[] }) =>
|
||||||
|
useApiFetch('/google-index/urls/submit', { method: 'POST', body: data }).then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
// 批量操作API
|
||||||
|
const batchSubmitGoogleIndexURLs = (data: { urls: string[], operation: string }) =>
|
||||||
|
useApiFetch('/google-index/batch/submit', { method: 'POST', body: data }).then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
const batchCheckGoogleIndexURLs = (data: { urls: string[], operation: string }) =>
|
||||||
|
useApiFetch('/google-index/batch/check', { method: 'POST', body: data }).then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
// 网站地图提交API
|
||||||
|
const submitGoogleIndexSitemap = (data: { sitemapURL: string }) =>
|
||||||
|
useApiFetch('/google-index/sitemap/submit', { method: 'POST', body: data }).then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
// 状态查询API
|
||||||
|
const getGoogleIndexStatus = () =>
|
||||||
|
useApiFetch('/google-index/status').then(parseApiResponse<GoogleIndexStatusResponse>)
|
||||||
|
|
||||||
|
// 验证凭据API
|
||||||
|
const validateCredentials = (data: { credentialsFile: string }) =>
|
||||||
|
useApiFetch('/google-index/validate-credentials', { method: 'POST', body: data }).then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
// 更新Google索引分组配置API
|
||||||
|
const updateGoogleIndexGroupConfig = (data: GoogleIndexConfig) =>
|
||||||
|
useApiFetch('/google-index/config/update', { method: 'POST', body: data }).then(parseApiResponse<GoogleIndexConfig>)
|
||||||
|
|
||||||
|
// 上传凭据API
|
||||||
|
const uploadCredentials = (file: File) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return useApiFetch('/google-index/upload-credentials', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
// 注意:此处不应包含Authorization头,因为文件上传通常由use-upload组件处理
|
||||||
|
}
|
||||||
|
}).then(parseApiResponse<any>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调度器控制API
|
||||||
|
const startGoogleIndexScheduler = () =>
|
||||||
|
useApiFetch('/google-index/scheduler/start', { method: 'POST' }).then(parseApiResponse<boolean>)
|
||||||
|
|
||||||
|
const stopGoogleIndexScheduler = () =>
|
||||||
|
useApiFetch('/google-index/scheduler/stop', { method: 'POST' }).then(parseApiResponse<boolean>)
|
||||||
|
|
||||||
|
const getGoogleIndexSchedulerStatus = () =>
|
||||||
|
useApiFetch('/google-index/scheduler/status').then(parseApiResponse<any>)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 配置管理
|
||||||
|
getGoogleIndexConfig,
|
||||||
|
getGoogleIndexConfigByKey,
|
||||||
|
updateGoogleIndexConfig,
|
||||||
|
updateGoogleIndexGroupConfig,
|
||||||
|
deleteGoogleIndexConfig,
|
||||||
|
|
||||||
|
// 凭据验证和上传
|
||||||
|
validateCredentials,
|
||||||
|
uploadCredentials,
|
||||||
|
|
||||||
|
// 任务管理
|
||||||
|
getGoogleIndexTasks,
|
||||||
|
getGoogleIndexTask,
|
||||||
|
createGoogleIndexTask,
|
||||||
|
startGoogleIndexTask,
|
||||||
|
stopGoogleIndexTask,
|
||||||
|
deleteGoogleIndexTask,
|
||||||
|
|
||||||
|
// 任务项管理
|
||||||
|
getGoogleIndexTaskItems,
|
||||||
|
|
||||||
|
// URL状态管理
|
||||||
|
getGoogleIndexURLStatus,
|
||||||
|
getGoogleIndexURLStatusByURL,
|
||||||
|
checkGoogleIndexURLStatus,
|
||||||
|
submitGoogleIndexURL,
|
||||||
|
|
||||||
|
// 批量操作
|
||||||
|
batchSubmitGoogleIndexURLs,
|
||||||
|
batchCheckGoogleIndexURLs,
|
||||||
|
|
||||||
|
// 网站地图提交
|
||||||
|
submitGoogleIndexSitemap,
|
||||||
|
|
||||||
|
// 状态查询
|
||||||
|
getGoogleIndexStatus,
|
||||||
|
|
||||||
|
// 调度器控制
|
||||||
|
startGoogleIndexScheduler,
|
||||||
|
stopGoogleIndexScheduler,
|
||||||
|
getGoogleIndexSchedulerStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,20 @@
|
|||||||
import { lightTheme } from 'naive-ui'
|
import { lightTheme } from 'naive-ui'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
// 动态添加Google站点验证meta标签
|
||||||
|
const { data: verificationData } = await $fetch('/api/public/site-verification').catch(() => ({ data: {} }))
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
meta: verificationData?.google_site_verification_code
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'google-site-verification',
|
||||||
|
content: verificationData.google_site_verification_code
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
|
||||||
const theme = lightTheme
|
const theme = lightTheme
|
||||||
const isDark = ref(false)
|
const isDark = ref(false)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<!-- 今日资源/总资源数 -->
|
<!-- 今日资源/总资源数 -->
|
||||||
<n-card>
|
<n-card>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -85,6 +85,27 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
|
<!-- Google索引统计 -->
|
||||||
|
<n-card>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="p-3 bg-red-100 dark:bg-red-900 rounded-lg">
|
||||||
|
<i class="fab fa-google text-red-600 dark:text-red-400 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">已索引URL/总URL</p>
|
||||||
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ googleIndexStats.indexedURLs || 0 }}/{{ googleIndexStats.totalURLs || 0 }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<n-button text type="primary" @click="navigateTo('/admin/seo#google-index')">
|
||||||
|
查看详情
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-arrow-right"></i>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 平台管理 -->
|
<!-- 平台管理 -->
|
||||||
@@ -149,6 +170,17 @@ import { useApiFetch } from '~/composables/useApiFetch'
|
|||||||
import { parseApiResponse } from '~/composables/useApi'
|
import { parseApiResponse } from '~/composables/useApi'
|
||||||
import Chart from 'chart.js/auto'
|
import Chart from 'chart.js/auto'
|
||||||
|
|
||||||
|
// Google索引统计
|
||||||
|
const googleIndexStats = ref({
|
||||||
|
totalURLs: 0,
|
||||||
|
indexedURLs: 0,
|
||||||
|
errorURLs: 0,
|
||||||
|
totalTasks: 0,
|
||||||
|
runningTasks: 0,
|
||||||
|
completedTasks: 0,
|
||||||
|
failedTasks: 0
|
||||||
|
})
|
||||||
|
|
||||||
// API
|
// API
|
||||||
const statsApi = useStatsApi()
|
const statsApi = useStatsApi()
|
||||||
const panApi = usePanApi()
|
const panApi = usePanApi()
|
||||||
@@ -370,10 +402,23 @@ watch([weeklyViews, weeklySearches], () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 加载Google索引统计
|
||||||
|
const loadGoogleIndexStats = async () => {
|
||||||
|
try {
|
||||||
|
const response = await useApiFetch('/google-index/status').then(parseApiResponse)
|
||||||
|
if (response && response.data) {
|
||||||
|
googleIndexStats.value = response.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载Google索引统计失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 组件挂载后初始化图表和数据
|
// 组件挂载后初始化图表和数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('组件挂载,开始初始化...')
|
console.log('组件挂载,开始初始化...')
|
||||||
await fetchTrendData()
|
await fetchTrendData()
|
||||||
|
await loadGoogleIndexStats() // 加载Google索引统计
|
||||||
console.log('数据获取完成,准备初始化图表...')
|
console.log('数据获取完成,准备初始化图表...')
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
console.log('nextTick执行,开始初始化图表...')
|
console.log('nextTick执行,开始初始化图表...')
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user