feat: add key verification and IndexNow submission worker

- Implement KeyVerificationController to handle ID-based key verification.
- Create SubmitToIndexnowWorker for background processing of URL submissions.
- Add routes for key verification and extend functionality of URL submissions to IndexNow.

This commit enhances the application by allowing for verification of API keys and automatic submission of URLs to multiple endpoints, improving SEO and resource accessibility.
This commit is contained in:
songtianlun 2025-04-08 14:04:16 +08:00
parent 11d7adba99
commit 5852d8724e
3 changed files with 164 additions and 0 deletions

View File

@ -0,0 +1,10 @@
# app/controllers/key_verification_controller.rb
class KeyVerificationController < ApplicationController
def show
if params[:id] == "339ecd3e9cf648c29b767f5673329e48"
render plain: "339ecd3e9cf648c29b767f5673329e48"
else
render status: :not_found, plain: "Not Found"
end
end
end

View File

@ -0,0 +1,151 @@
# app/workers/submit_to_indexnow_worker.rb
class SubmitToIndexnowWorker
include Sidekiq::Worker
require "redis"
require "net/http"
require "uri"
require "json"
sidekiq_options retry: 3, queue: :indexnow
INDEXNOW_KEY = "339ecd3e9cf648c29b767f5673329e48"
INDEXNOW_ENDPOINTS = [
"https://api.indexnow.org/indexnow",
"https://www.bing.com/indexnow"
]
def perform(recent)
lock_key = "submit_to_indexnow_lock"
lock_ttl = 300 # 锁的生存时间,单位为秒
recently_updated = recent ? DateTime.now - 1.day : DateTime.now - 356.day
redis = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/1"))
if redis.set(lock_key, Time.current.to_s, nx: true, ex: lock_ttl)
begin
@host = Rails.env.production? ? "https://todayaiweather.com" : "http://127.0.0.1:3000"
Rails.application.routes.default_url_options[:host] = @host
# 收集所有需要提交的 URLs
urls = collect_urls
# 分批提交 URLs (每批最多 10000 个 URLs符合 IndexNow 限制)
urls.each_slice(100) do |url_batch|
submit_urls(url_batch)
end
Rails.logger.info "Successfully submitted #{urls.size} URLs to IndexNow"
rescue => e
Rails.logger.error "Error submitting URLs to IndexNow: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
raise e
ensure
redis.del(lock_key)
end
else
Rails.logger.info "IndexNow submission is already in progress"
end
end
private
def collect_urls
urls = []
available_locales = I18n.available_locales
# 添加首页
urls << @host
available_locales.each do |locale|
urls << "#{@host}/#{locale}"
end
# 城市列表页
urls << "#{@host}/cities"
available_locales.each do |locale|
urls << "#{@host}/#{locale}/cities"
end
# 艺术作品列表页
urls << "#{@host}/arts"
available_locales.each do |locale|
urls << "#{@host}/#{locale}/arts"
end
# 城市详情页
City.find_each do |city|
urls << "#{@host}/cities/#{city.id}"
available_locales.each do |locale|
urls << "#{@host}/#{locale}/cities/#{city.id}"
end
end
# 天气艺术作品页
WeatherArt.includes(:city).find_each do |art|
if art.image.attached?
urls << "#{@host}/cities/#{art.city.id}/weather_arts/#{art.id}"
available_locales.each do |locale|
urls << "#{@host}/#{locale}/cities/#{art.city.id}/weather_arts/#{art.id}"
end
end
end
urls.uniq
end
def submit_urls(urls)
return if urls.empty?
# 随机选择一个 IndexNow 端点
endpoint = INDEXNOW_ENDPOINTS.sample
# 准备提交的数据
data = {
host: URI.parse(@host).host,
key: INDEXNOW_KEY,
urlList: urls
}
# 发送请求到 IndexNow API
uri = URI.parse(endpoint)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, "Content-Type" => "application/json")
request.body = data.to_json
response = http.request(request)
unless response.code.to_i == 200
Rails.logger.warn "IndexNow submission failed with code #{response.code}: #{response.body}"
# 如果第一个端点失败,尝试使用备用端点
if endpoint == INDEXNOW_ENDPOINTS.first
retry_with_alternate_endpoint(urls)
end
end
end
def retry_with_alternate_endpoint(urls)
# 使用备用端点
endpoint = INDEXNOW_ENDPOINTS.last
data = {
host: URI.parse(@host).host,
key: INDEXNOW_KEY,
urlList: urls
}
uri = URI.parse(endpoint)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, "Content-Type" => "application/json")
request.body = data.to_json
response = http.request(request)
unless response.code.to_i == 200
Rails.logger.error "All IndexNow submissions failed. Last error: #{response.code}: #{response.body}"
end
end
end

View File

@ -44,6 +44,9 @@ Rails.application.routes.draw do
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# IndexNow key验证
get "/:id.txt", to: "key_verification#show", constraints: { id: /\w{32}/ }
# Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker