update: ui

This commit is contained in:
ctwj
2025-11-24 08:29:25 +08:00
parent 4e3f9017ac
commit 09baf0cb21
6 changed files with 261 additions and 181 deletions

View File

@@ -305,8 +305,7 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte
entity.ConfigResponseFieldEnableRegister: true, // 默认开启注册功能
entity.ConfigResponseFieldThirdPartyStatsCode: "",
entity.ConfigResponseFieldWebsiteURL: "",
"google_site_verification_code": "",
}
}
// 将键值对转换为map过滤掉敏感配置
for _, config := range configs {
@@ -363,8 +362,6 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte
response["qr_code_style"] = config.Value
case entity.ConfigKeyWebsiteURL:
response[entity.ConfigResponseFieldWebsiteURL] = config.Value
case "google_site_verification_code":
response["google_site_verification_code"] = config.Value
case entity.ConfigKeyAutoProcessReadyResources:
if val, err := strconv.ParseBool(config.Value); err == nil {
response["auto_process_ready_resources"] = val

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"github.com/ctwj/urldb/db/converter"
"github.com/ctwj/urldb/db/dto"
@@ -36,7 +37,162 @@ func NewGoogleIndexHandler(
}
}
// GetConfig 获取Google索引配置
// GetAllConfig 获取所有Google索引配置(以分组形式返回)
func (h *GoogleIndexHandler) GetAllConfig(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"
}
// 获取认证配置
credentialsFile, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyCredentialsFile)
if err != nil {
credentialsFile = ""
}
clientEmail, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyClientEmail)
if err != nil {
clientEmail = ""
}
clientID, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyClientID)
if err != nil {
clientID = ""
}
privateKey, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyPrivateKey)
if err != nil {
privateKey = ""
}
token, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyToken)
if err != nil {
token = ""
}
// 构建各组配置
generalConfig := dto.GoogleIndexConfigGeneral{
Enabled: enabled,
SiteURL: siteURL,
SiteName: siteName,
}
authConfig := dto.GoogleIndexConfigAuth{
CredentialsFile: credentialsFile,
ClientEmail: clientEmail,
ClientID: clientID,
PrivateKey: privateKey,
Token: token,
}
scheduleConfig := dto.GoogleIndexConfigSchedule{
CheckInterval: checkInterval,
BatchSize: batchSize,
Concurrency: concurrency,
RetryAttempts: retryAttempts,
RetryDelay: retryDelay,
}
sitemapConfig := dto.GoogleIndexConfigSitemap{
AutoSitemap: autoSitemap,
SitemapPath: sitemapPath,
SitemapSchedule: sitemapSchedule,
}
// 将配置对象转换为JSON字符串
generalConfigJSON, _ := json.Marshal(generalConfig)
authConfigJSON, _ := json.Marshal(authConfig)
scheduleConfigJSON, _ := json.Marshal(scheduleConfig)
sitemapConfigJSON, _ := json.Marshal(sitemapConfig)
// 以数组格式返回所有配置组
configs := []gin.H{
{
"group": "general",
"key": "general",
"value": string(generalConfigJSON),
},
{
"group": "auth",
"key": "credentials_file", // 使用具体的键名
"value": string(authConfigJSON),
},
{
"group": "schedule",
"key": "schedule",
"value": string(scheduleConfigJSON),
},
{
"group": "sitemap",
"key": "sitemap",
"value": string(sitemapConfigJSON),
},
}
SuccessResponse(c, configs)
}
// GetConfig 获取Google索引配置原有接口为保持兼容性
func (h *GoogleIndexHandler) GetConfig(c *gin.Context) {
// 获取通用配置
enabledStr, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyEnabled)
@@ -607,6 +763,53 @@ func (h *GoogleIndexHandler) ValidateCredentials(c *gin.Context) {
SuccessResponse(c, response)
}
// GetStatus 获取Google索引状态
func (h *GoogleIndexHandler) GetStatus(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 = ""
}
// 获取认证配置
credentialsFile, err := h.repoMgr.SystemConfigRepository.GetConfigValue(entity.GoogleIndexConfigKeyCredentialsFile)
if err != nil {
credentialsFile = ""
}
// 验证凭据是否有效
authValid := false
if credentialsFile != "" {
if _, err := os.Stat(credentialsFile); !os.IsNotExist(err) {
// 检查凭据文件是否存在且可读
authValid = true
}
}
// 获取统计信息(从任务管理器或数据库获取相关统计)
// 这里简化处理,返回一个基本的状态响应
// 在实际实现中,可能需要查询数据库获取更详细的统计信息
statusResponse := dto.GoogleIndexStatusResponse{
Enabled: enabled,
SiteURL: siteURL,
LastCheckTime: time.Now(),
TotalURLs: 0,
IndexedURLs: 0,
NotIndexedURLs: 0,
ErrorURLs: 0,
LastSitemapSubmit: time.Time{},
AuthValid: authValid,
}
SuccessResponse(c, statusResponse)
}
// loadCredentials 从文件加载凭据
func (h *GoogleIndexHandler) loadCredentials(credentialsFile string) (*google.Config, error) {
// 从pkg/google/client.go导入的Config
@@ -640,40 +843,6 @@ func (h *GoogleIndexHandler) loadCredentials(credentialsFile string) (*google.Co
// 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) {
@@ -789,31 +958,7 @@ func (h *GoogleIndexHandler) UpdateGoogleIndexConfig(c *gin.Context) {
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
}

View File

@@ -281,30 +281,6 @@ func GetPublicSystemConfig(c *gin.Context) {
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) {

View File

@@ -105,7 +105,7 @@ export interface GoogleIndexTaskItemPageResponse {
export const useGoogleIndexApi = () => {
// 配置管理API
const getGoogleIndexConfig = (params?: any) =>
useApiFetch('/google-index/config', { params }).then(parseApiResponse<GoogleIndexConfig[]>)
useApiFetch('/google-index/config-all', { params }).then(parseApiResponse<GoogleIndexConfig[]>)
const getGoogleIndexConfigByKey = (key: string) =>
useApiFetch(`/google-index/config/${key}`).then(parseApiResponse<GoogleIndexConfig>)

View File

@@ -32,19 +32,6 @@
import { lightTheme } from 'naive-ui'
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 isDark = ref(false)

View File

@@ -707,35 +707,47 @@
<!-- 所有权验证模态框 -->
<n-modal v-model:show="showVerificationModal" preset="card" title="站点所有权验证" style="max-width: 600px;">
<div class="space-y-6">
<p class="text-gray-600 dark:text-gray-400">
为了验证网站所有权请将以下验证字符串添加到网站的HTML头部中
</p>
<div class="space-y-4">
<div class="bg-gray-50 dark:bg-gray-700/50 p-4 rounded-lg">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">验证字符串</label>
<n-input
v-model:value="verificationCode"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="输入Google Search Console或其他搜索引擎提供的验证字符串"
/>
</div>
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800">
<label class="block text-sm font-medium text-blue-700 dark:text-blue-300 mb-2">在页面中添加的代码</label>
<div class="bg-white dark:bg-gray-800 p-3 rounded border">
<code class="text-sm text-gray-800 dark:text-gray-200">
&lt;meta name="google-site-verification" content="<span class="text-blue-600 dark:text-blue-400">{{ verificationCode || '验证字符串' }}</span>" /&gt;
</code>
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-info-circle text-blue-500 dark:text-blue-400 text-xl"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">DNS方式验证</h3>
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
<p>推荐使用DNS方式验证站点所有权这是最安全和可靠的方法</p>
<ol class="list-decimal list-inside mt-2 space-y-1">
<li>登录您的域名注册商或DNS管理平台</li>
<li>添加一条TXT记录</li>
<li>在Google Search Console中输入您的验证字符串</li>
<li>验证DNS TXT记录是否生效</li>
</ol>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 pt-2">
<n-button @click="showVerificationModal = false">取消</n-button>
<n-button type="primary" @click="saveVerificationCode" :loading="verificationCodeSaving">
保存
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-triangle text-yellow-500 dark:text-yellow-400 text-xl"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">注意事项</h3>
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
<ul class="list-disc list-inside space-y-1">
<li>DNS验证比HTML标签更安全不易被其他网站复制</li>
<li>验证成功后Google会自动检测您的站点所有权</li>
<li>如果您的域名服务商不支持TXT记录请联系客服寻求帮助</li>
</ul>
</div>
</div>
</div>
</div>
<div class="flex justify-end pt-2">
<n-button type="primary" @click="showVerificationModal = false">
确定
</n-button>
</div>
</div>
@@ -1204,22 +1216,6 @@ const showCredentialsGuide = ref(false)
// 所有权验证相关
const showVerificationModal = ref(false)
const verificationCode = ref('')
const verificationCodeSaving = ref(false)
// 获取已保存的验证代码
const loadVerificationCode = async () => {
try {
const api = useApi()
const response = await api.googleIndexApi.getGoogleIndexConfigByKey('google_site_verification')
if (response?.data?.value) {
verificationCode.value = response.data.value
}
} catch (error) {
// 如果配置不存在,使用空值
verificationCode.value = ''
}
}
// Google索引统计
const googleIndexStats = ref({
@@ -1425,16 +1421,30 @@ const handleCredentialsFileSelect = async (event: Event) => {
const api = useApi()
const response = await api.googleIndexApi.uploadCredentials(file)
if (response?.filePath) {
googleIndexConfig.value.credentialsFile = response.filePath
message.success('凭据文件上传成功,请验证凭据')
// 检查API是否成功success字段为true且包含有效的文件路径
if (response?.success === true && response?.data?.file_path) {
googleIndexConfig.value.credentialsFile = response.data.file_path
message.success(response.data.message || '凭据文件上传成功,请验证凭据')
// 清空文件输入以允许重新选择相同文件
if (credentialsFileInput.value) {
credentialsFileInput.value.value = ''
}
// 上传成功后立即更新后端配置并重新加载配置
await api.googleIndexApi.updateGoogleIndexGroupConfig({
group: 'auth',
key: 'credentials_file',
value: JSON.stringify({
credentialsFile: googleIndexConfig.value.credentialsFile
})
})
// 重新加载配置以确保UI状态与后端同步
await loadGoogleIndexConfig()
} else {
message.error('上传响应格式错误')
// 如果API调用成功但返回的数据有问题或者API调用失败
message.error(response?.message || '上传响应格式错误')
}
} catch (error: any) {
console.error('凭据文件上传失败:', error)
@@ -1691,41 +1701,6 @@ const viewTaskItems = async (taskId: number) => {
}
}
// 监听验证弹窗显示状态
watch(showVerificationModal, (show) => {
if (show) {
loadVerificationCode()
}
})
// 保存验证字符串
const saveVerificationCode = async () => {
if (!verificationCode.value.trim()) {
message.warning('请输入验证字符串')
return
}
verificationCodeSaving.value = true
try {
const api = useApi()
// 使用分组配置API保存验证字符串
await api.googleIndexApi.updateGoogleIndexGroupConfig({
group: 'verification',
key: 'google_site_verification',
value: JSON.stringify({
code: verificationCode.value.trim()
})
})
message.success('验证字符串保存成功')
showVerificationModal.value = false
} catch (error) {
console.error('保存验证字符串失败:', error)
message.error('保存验证字符串失败')
} finally {
verificationCodeSaving.value = false
}
}
// 启动任务
const startTask = async (taskId: number) => {