diff --git a/app/workers/batch_generate_weather_arts_worker.rb b/app/workers/batch_generate_weather_arts_worker.rb index a7deda0..79b26dd 100644 --- a/app/workers/batch_generate_weather_arts_worker.rb +++ b/app/workers/batch_generate_weather_arts_worker.rb @@ -1,38 +1,85 @@ class BatchGenerateWeatherArtsWorker include Sidekiq::Worker + GENERATION_INTERVAL = 36.hours MAX_DURATION = 50.minutes SLEEP_DURATION = 120.seconds - BATCH_SIZE = 20 - - module ProcessType - DO_NOTHING = 0 - GET_WEATHER_WITH_GENERATE_AI_ART = 1 - GET_WEATHER_ONLY = 2 - end + DAILY_GENERATION_LIMIT = 20 # 每日生成图片上限 def perform(*args) start_time = Time.current - cities_to_process = get_eligible_cities.shuffle.take(BATCH_SIZE) - cities_to_process.each do |city| + 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) + end + + private + + def calculate_remaining_slots + today_generations = WeatherArt + .where("DATE(created_at) = ?", Date.today) + .where.not(image_attachment: nil) + .count + + [ 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 "\tID\tName\tState\tCountry\tRegion" + cities.each do |city| + Rails.logger.info "\t#{city.id}\t#{city&.name}\t#{city&.state&.name}\t#{city&.country&.name}\t#{city&.country&.region&.name}" + end + 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 + FROM weather_arts + GROUP BY city_id + ) 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) + .limit(limit) + .to_a + + remaining_slots = limit - active_cities.size + + if remaining_slots > 0 + # 如果还有剩余名额,从其他城市中随机选择 + other_cities = base_query + .where.not(id: active_cities.map(&:id)) + .order("RANDOM()") + .limit(remaining_slots) + .to_a + + active_cities + other_cities + else + 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 - - private - - def get_eligible_cities - cutoff_time = Time.current - GENERATION_INTERVAL - City.active - .joins("LEFT JOIN ( - SELECT city_id, MAX(created_at) as last_generation_time - FROM weather_arts - GROUP BY city_id - ) 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) - .order(:priority) - end end