如何在Ruby on Rails中实现用户金币余额及帖子金币交易功能?
嘿,我来一步步帮你实现这个金币购买帖子的功能,咱们从数据库、模型、业务逻辑到控制器视图全流程走一遍:
1. 数据库迁移
首先得给现有模型补充字段,同时创建金币交易记录的表:
- 给用户添加余额字段:
rails generate migration AddBalanceToUsers balance:decimal{10,2}
运行rails db:migrate,用decimal类型能精准存储金额,10位整数+2位小数的精度足够日常使用。
- 给帖子添加定价字段:
rails generate migration AddPriceToPosts price:decimal{10,2}
同样运行迁移,后续会在模型里验证付费帖子的价格必须大于0。
- 创建金币交易记录模型:
rails generate model Coin user:references amount:decimal{10,2} transaction_type:string post:references
这里transaction_type用来区分交易类型(收入/支出/充值),post关联用来标记交易对应的帖子,最后运行迁移。
2. 模型关联与验证
配置模型之间的关系,同时添加必要的数据验证:
User模型
class User < ApplicationRecord has_many :posts has_many :coins, dependent: :destroy # 确保余额不会变成负数 validates :balance, numericality: { greater_than_or_equal_to: 0 } # 可选:添加角色字段,控制谁能创建付费帖子 # 先跑迁移:rails generate migration AddRoleToUsers role:string # validates :role, inclusion: { in: %w[regular author], default: 'regular' } # 辅助方法:增加余额 def add_balance(amount) update!(balance: balance + amount) end # 辅助方法:扣除余额 def deduct_balance(amount) update!(balance: balance - amount) end end
Post模型
class Post < ApplicationRecord belongs_to :user # 付费帖子必须设置大于0的价格 validates :price, numericality: { greater_than: 0 }, if: :paid_post? # 判断是否为付费帖子的辅助方法 def paid_post? price.present? && price > 0 end end
Coin模型
class Coin < ApplicationRecord belongs_to :user belongs_to :post, optional: true # 充值类交易不需要关联帖子 # 限制交易类型的可选值 validates :transaction_type, inclusion: { in: %w[income expense top_up] } validates :amount, numericality: { greater_than: 0 } end
3. 核心业务逻辑:购买交易流程
把购买逻辑封装成Service类,代码更清晰也方便测试,创建app/services/post_purchase_service.rb:
class PostPurchaseService def initialize(buyer, post) @buyer = buyer @post = post @seller = post.user @price = post.price end def execute # 开启数据库事务,确保所有操作要么全部成功,要么全部回滚 ActiveRecord::Base.transaction do # 前置条件检查 raise "不能购买自己的帖子" if @buyer == @seller raise "这不是付费帖子" unless @post.paid_post? raise "余额不足,无法购买" if @buyer.balance < @price # 更新买卖双方的余额 @buyer.deduct_balance(@price) @seller.add_balance(@price) # 生成买家的支出记录 Coin.create!( user: @buyer, amount: @price, transaction_type: 'expense', post: @post ) # 生成卖家的收入记录 Coin.create!( user: @seller, amount: @price, transaction_type: 'income', post: @post ) true end rescue StandardError => e @error = e.message false end attr_reader :error end
4. 控制器实现
在Posts控制器里添加购买动作,同时处理帖子内容的权限判断:
class PostsController < ApplicationController before_action :set_post, only: [:show, :buy] # 帖子详情页 def show # 判断当前用户是否有权查看内容 @can_view = current_user == @post.user || has_purchased? @is_paid_post = @post.paid_post? end # 购买动作 def buy service = PostPurchaseService.new(current_user, @post) if service.execute redirect_to @post, notice: '购买成功!现在可以查看内容了' else redirect_to @post, alert: service.error end end private def set_post @post = Post.find(params[:id]) end # 判断用户是否已经购买过该帖子 def has_purchased? current_user.coins.exists?(transaction_type: 'expense', post: @post) end end
别忘了在config/routes.rb里添加购买路由:
resources :posts do member do post :buy end end
5. 视图实现(帖子详情页)
在app/views/posts/show.html.erb里根据权限显示不同内容:
<h1><%= @post.title %></h1> <% if @can_view %> <!-- 有权限则显示帖子内容 --> <div class="post-content"> <%= @post.content %> </div> <% else %> <% if @is_paid_post %> <p>这是付费帖子,需要支付 <%= number_to_currency(@post.price, unit: '金币') %> 才能查看</p> <%= button_to '立即购买', buy_post_path(@post), method: :post, class: 'btn btn-primary' %> <% else %> <p>你没有权限查看这篇帖子</p> <% end %> <% end %> <!-- 显示用户当前金币余额 --> <% if current_user %> <div class="user-balance mt-3"> 当前金币余额:<%= number_to_currency(current_user.balance, unit: '金币') %> </div> <% end %>
6. 额外优化建议
- 充值功能:如果需要用户能充值金币,可以参考购买逻辑写一个
TopUpService,生成top_up类型的Coin记录并增加用户余额。 - 付费帖子创建权限:如果只有特定用户能创建付费帖子,可以在User模型加role字段,然后在Posts的new/create动作里验证权限。
- 交易流水页:给用户做一个金币流水页面,展示所有Coin记录,区分收入、支出和充值。
- 测试覆盖:给Service和控制器写单元测试、集成测试,确保交易逻辑的正确性(比如余额不足时不会扣款,事务能正常回滚)。
内容的提问来源于stack exchange,提问作者AzBest
相关产品推荐
相关产品推荐

