Ruby on Rails游戏类应用中用户评分统计追踪的最优方案咨询
嘿,我来帮你梳理下在Rails里实现这个类游戏应用评分统计系统的最优方案,结合你的需求一步步来:
首先得把核心的模型关系捋顺,你需要一个Rating模型来记录用户之间的评分,关联评分者和被评分者,还要存一个布尔字段标记是否通过:
# app/models/rating.rb class Rating < ApplicationRecord # 关联评分者(发起评分的用户) belongs_to :rater, class_name: "User" # 关联被评分者(被打分的用户) belongs_to :ratee, class_name: "User" # 确保passed只能是true/false validates :passed, inclusion: { in: [true, false] } end
然后生成对应的迁移文件,记得加上复合索引——这对后续的统计查询速度影响很大:
# db/migrate/xxxx_create_ratings.rb class CreateRatings < ActiveRecord::Migration[7.0] def change create_table :ratings do |t| t.references :rater, null: false, foreign_key: { to_table: :users } t.references :ratee, null: false, foreign_key: { to_table: :users } t.boolean :passed, null: false t.timestamps end # 加复合索引,优化按被评分者+通过状态、评分者+通过状态的查询 add_index :ratings, [:ratee_id, :passed] add_index :ratings, [:rater_id, :passed] end end
别忘了在User模型里加上对应的关联关系,方便后续查询:
# app/models/user.rb class User < ApplicationRecord # 用户作为评分者发起的所有评分 has_many :ratings, foreign_key: :rater_id, dependent: :destroy # 用户作为被评分者收到的所有评分 has_many :received_ratings, class_name: "Rating", foreign_key: :ratee_id, dependent: :destroy end
你需要的两类统计,我都封装成User模型的方法,这样调用起来非常方便:
1. 统计用户所通过对象的属性分布
也就是当前用户作为评分者,给哪些用户打了通过,这些用户的各属性值的计数:
# app/models/user.rb # 传入属性名(比如:skill_level),返回该属性各值的计数哈希 def passed_ratees_attribute_stats(attribute) # 只筛选通过的评分,关联被评分用户,按属性分组统计数量 self.ratings.where(passed: true) .joins(:ratee) .group("users.#{attribute}") .count end
调用示例:current_user.passed_ratees_attribute_stats(:style),会返回类似{"casual"=>8, "aggressive"=>15}的结果。
2. 统计给用户打通过的用户的属性分布
也就是当前用户作为被评分者,哪些用户给自己打了通过,这些评分者的各属性值的计数:
# app/models/user.rb def passed_raters_attribute_stats(attribute) self.received_ratings.where(passed: true) .joins(:rater) .group("users.#{attribute}") .count end
调用示例:current_user.passed_raters_attribute_stats(:skill_level),返回评分者的技能等级分布。
如果你的应用用户量和评分量不大,直接用上面的实时查询就够了,但如果数据量上去了,建议做以下优化:
方案一:Rails内置缓存
把统计结果缓存起来,设置过期时间,同时在有新评分创建时主动清除相关缓存,保证数据新鲜:
# app/models/user.rb def passed_ratees_attribute_stats(attribute) # 缓存键包含用户ID、属性名,避免冲突 Rails.cache.fetch(["user_#{id}", "passed_ratees", attribute], expires_in: 1.hour) do self.ratings.where(passed: true) .joins(:ratee) .group("users.#{attribute}") .count end end def passed_raters_attribute_stats(attribute) Rails.cache.fetch(["user_#{id}", "passed_raters", attribute], expires_in: 1.hour) do self.received_ratings.where(passed: true) .joins(:rater) .group("users.#{attribute}") .count end end
然后在Rating模型里加回调,创建新评分时清除相关用户的缓存:
# app/models/rating.rb after_create :clear_related_stats_cache private def clear_related_stats_cache # 清除评分者的统计缓存 Rails.cache.delete_matched(["user_#{rater_id}", "passed_ratees", "*"]) # 清除被评分者的统计缓存 Rails.cache.delete_matched(["user_#{ratee_id}", "passed_raters", "*"]) end
方案二:预计算统计值(适合大规模应用)
如果你的评分量达到百万级,实时查询+缓存还是慢,可以用物化视图或者后台定时任务(比如Sidekiq)预计算统计值,存在单独的统计表中。比如创建一个UserStat模型,存储每个用户的两类统计数据,每天/每小时跑一次任务更新,这样前端页面直接查统计表就行,速度飞快。
在控制器中获取统计数据,传给视图:
# app/controllers/stats_controller.rb class StatsController < ApplicationController before_action :authenticate_user! def show @user = current_user # 把你的10个属性放到这个数组里,比如[:style, :skill_level, :play_mode] @target_attributes = [:style, :skill_level, ...] # 整理两类统计数据,方便视图遍历 @ratees_stats = @target_attributes.each_with_object({}) do |attr, hash| hash[attr] = @user.passed_ratees_attribute_stats(attr) end @raters_stats = @target_attributes.each_with_object({}) do |attr, hash| hash[attr] = @user.passed_raters_attribute_stats(attr) end end end
视图里可以用表格或者图表展示,这里给个简单的表格示例,你也可以用Chart.js之类的前端库做可视化:
<!-- app/views/stats/show.html.erb --> <h1>我的评分统计</h1> <div class="stats-section"> <h2>我通过的用户属性分布</h2> <% @target_attributes.each do |attr| %> <h3><%= attr.humanize %></h3> <table class="stats-table"> <thead> <tr> <th>属性值</th> <th>计数</th> </tr> </thead> <tbody> <% @ratees_stats[attr].each do |value, count| %> <tr> <td><%= value || "未设置" %></td> <td><%= count %></td> </tr> <% end %> </tbody> </table> <% end %> </div> <div class="stats-section"> <h2>给我打通过的用户属性分布</h2> <% @target_attributes.each do |attr| %> <h3><%= attr.humanize %></h3> <table class="stats-table"> <thead> <tr> <th>属性值</th> <th>计数</th> </tr> </thead> <tbody> <% @raters_stats[attr].each do |value, count| %> <tr> <td><%= value || "未设置" %></td> <td><%= count %></td> </tr> <% end %> </tbody> </table> <% end %> </div>
根据你的应用规模选对应的方案:
- 小规模应用:直接用实时查询+简单缓存,开发快,维护简单。
- 中等规模应用:用缓存+回调更新的方式,平衡性能和复杂度。
- 大规模应用:预计算统计值(物化视图/定时任务),把查询压力转移到后台,保证前端响应速度。
另外,一定要记得加复合索引——这是提升统计查询速度最有效的手段之一。
内容的提问来源于stack exchange,提问作者Steve Carey

