refactor: Improve weather art generation process

- Add formatted_current_time method to City model.
- Modify BatchGenerateWeatherArtsWorker to check local time.
- Update Sidekiq scheduler to run every hour.

This commit improves the weather art generation by considering
local time for image generation and increasing the frequency
of batch processing for testing purposes. The new method
provides formatted time information for each city. This change
ensures that images are generated during daylight hours.
This commit is contained in:
songtianlun 2025-02-15 12:20:24 +08:00
parent df074a81a8
commit 936db76437
3 changed files with 80 additions and 26 deletions

View File

@ -109,7 +109,7 @@ class City < ApplicationRecord
# 定义 latest_weather_art 关联
has_one :latest_weather_art, -> { order(weather_date: :desc) },
class_name: 'WeatherArt'
class_name: "WeatherArt"
# 包含最新天气艺术的 scope
scope :with_latest_weather_art, -> {
@ -124,14 +124,14 @@ class City < ApplicationRecord
# 按最新天气更新时间排序
scope :order_by_latest_weather, -> {
joins(:weather_arts)
.group('cities.id')
.order('MAX(weather_arts.weather_date) DESC')
.group("cities.id")
.order("MAX(weather_arts.weather_date) DESC")
}
# 获取最近24小时内更新过天气的城市
scope :recently_updated, -> {
joins(:weather_arts)
.where('weather_arts.weather_date > ?', 24.hours.ago)
.where("weather_arts.weather_date > ?", 24.hours.ago)
.distinct
}
@ -213,5 +213,36 @@ class City < ApplicationRecord
self.id)
.count
end
end
def formatted_current_time(type = :date, use_local_timezone = true)
# 获取时区
timezone_info = self.country&.timezones.present? ?
eval(self.country.timezones).first :
{ "zoneName" => "UTC", "gmtOffsetName" => "UTC+00:00" }
# 设置时区对象
time_zone = ActiveSupport::TimeZone[timezone_info["zoneName"]] ||
ActiveSupport::TimeZone["UTC"]
time = Time.current
case type
when :date
# 格式化日期
time.strftime("%B %d, %Y")
when :time
use_local_timezone ?
"#{time.in_time_zone(time_zone).strftime('%H:%M')} #{timezone_info['gmtOffsetName']}" :
"#{time.utc.strftime('%H:%M')} UTC"
when :all
# 返回日期 + 时间 + UTC 信息
date = time.strftime("%B %d, %Y")
time = use_local_timezone ?
updated_at.in_time_zone(time_zone).strftime("%H:%M") + " #{timezone_info['gmtOffsetName']}" :
updated_at.utc.strftime("%H:%M") + " UTC"
"#{date} #{time}"
else
"Unknown #{type}"
end
end
end

View File

@ -2,8 +2,8 @@ class BatchGenerateWeatherArtsWorker
include Sidekiq::Worker
GENERATION_INTERVAL = 36.hours
MAX_DURATION = 50.minutes
SLEEP_DURATION = 120.seconds
MAX_DURATION = 5.minutes
SLEEP_DURATION = 10.seconds
DAILY_GENERATION_LIMIT = 50 # 每日生成图片上限
def perform(*args)
@ -11,10 +11,26 @@ class BatchGenerateWeatherArtsWorker
remaining_slots = calculate_remaining_slots
return if remaining_slots <= 0
# 获取符合条件的城市并处理
cities_to_process = select_cities(remaining_slots)&.shuffle
print_cities_list(cities_to_process, start_time)
process_cities(cities_to_process, start_time)
skipped_cities = []
processed_cities = []
cities_to_process.each do |city|
if within_sunrise_time?(city)
Rails.logger.info "Generating weather art for #{city.name}"
GenerateWeatherArtWorker.perform_async(city.id)
processed_cities << city.name
else
Rails.logger.info "Skipping #{city.name} due to local time not being within sunrise hours."
skipped_cities << city.name
end
sleep SLEEP_DURATION
break if Time.current - start_time > MAX_DURATION
end
print_summary(processed_cities, skipped_cities)
end
private
@ -25,23 +41,42 @@ class BatchGenerateWeatherArtsWorker
.where.not(image_attachment: nil)
.count
[ DAILY_GENERATION_LIMIT - today_generations, 0 ].max
[DAILY_GENERATION_LIMIT - today_generations, 0].max
end
def print_cities_list(cities, start_time)
Rails.logger.info "Generate city task start at: #{start_time}"
Rails.logger.info "Generate city list: "
Rails.logger.info "======================================"
Rails.logger.info "ID\tRegion\tCountry\tState\tName"
Rails.logger.info "ID\tRegion\tCountry\tState\tName\tLocalTime"
cities.each do |city|
Rails.logger.info "#{city.id}\t#{city&.country&.region&.name}\t#{city&.country&.name}\t#{city&.state&.name}\t#{city&.name}"
Rails.logger.info "#{city.id}\t#{city.country&.region&.name}\t#{city.country&.name}\t#{city.state&.name}\t#{city.name}\t#{city.formatted_current_time(:all)}"
end
end
def within_sunrise_time?(city)
local_time = get_local_time(city)
local_time.hour >= 8 && local_time.hour <= 18
end
def get_local_time(city)
return Time.current unless city
timezone_info = city.country&.timezones&.first
return Time.current unless timezone_info
timezone = ActiveSupport::TimeZone["zoneName"] ||
ActiveSupport::TimeZone["UTC"]
Time.current.in_time_zone(timezone)
end
def print_summary(processed_cities, skipped_cities)
Rails.logger.info "Processed cities: #{processed_cities.join(', ')}"
Rails.logger.info "Skipped cities: #{skipped_cities.join(', ')}"
end
def select_cities(limit)
cutoff_time = Time.current - GENERATION_INTERVAL
# 基础查询:排除最近生成过的城市
base_query = City
.joins("LEFT JOIN (
SELECT city_id, MAX(created_at) as last_generation_time
@ -50,7 +85,6 @@ class BatchGenerateWeatherArtsWorker
) latest_arts ON cities.id = latest_arts.city_id")
.where("latest_arts.last_generation_time IS NULL OR latest_arts.last_generation_time < ?", cutoff_time)
# 优先选择活跃城市
active_cities = base_query
.where(active: true)
.order(:priority)
@ -60,7 +94,6 @@ class BatchGenerateWeatherArtsWorker
remaining_slots = limit - active_cities.size
if remaining_slots > 0
# 如果还有剩余名额,从其他城市中随机选择
other_cities = base_query
.where.not(id: active_cities.map(&:id))
.order("RANDOM()")
@ -72,14 +105,4 @@ class BatchGenerateWeatherArtsWorker
active_cities
end
end
def process_cities(cities, start_time)
cities.each do |city|
break if Time.current - start_time > MAX_DURATION
Rails.logger.info "Generating weather art for #{city.name}"
GenerateWeatherArtWorker.perform_async(city.id)
sleep SLEEP_DURATION
end
end
end
end

View File

@ -1,5 +1,5 @@
batch_generate_weather:
cron: '0 8,18 * * *'
cron: '0 */1 * * *'
class: BatchGenerateWeatherArtsWorker
description: "Batch Generate weather arts"
enabled: true