diff --git a/app/models/city.rb b/app/models/city.rb index ee590a3..18fb0a7 100644 --- a/app/models/city.rb +++ b/app/models/city.rb @@ -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 diff --git a/app/workers/batch_generate_weather_arts_worker.rb b/app/workers/batch_generate_weather_arts_worker.rb index 2d29c0e..4daa9a5 100644 --- a/app/workers/batch_generate_weather_arts_worker.rb +++ b/app/workers/batch_generate_weather_arts_worker.rb @@ -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 \ No newline at end of file diff --git a/config/sidekiq_scheduler.yml b/config/sidekiq_scheduler.yml index 6963062..8914b3f 100644 --- a/config/sidekiq_scheduler.yml +++ b/config/sidekiq_scheduler.yml @@ -1,5 +1,5 @@ batch_generate_weather: - cron: '0 8,18 * * *' + cron: '0 */1 * * *' class: BatchGenerateWeatherArtsWorker description: "Batch Generate weather arts" enabled: true