我正在通过JSON API开发Rails应用程序作为移动应用程序的后端。我已经对几乎所有事物进行了建模,但是需要设计一个(核心)过程,而我没有找到一种清晰的方法来实现它。
除了其他功能外,该应用还必须匹配两个满足一定条件(主要是地理位置)的用户。出于问题的考虑和简化起见,假设它需要匹配彼此接近且当前正在寻找匹配项的用户(这是一种同步体验),例如:
假设用户相距5公里。经验应该是:
我这里的主要问题是如何在Rails中为“寻找匹配”状态建模。我已经考虑过创建一个表格和模型,其中包括对用户及其位置的引用。但是后来我不知道如何处理“匹配查询”而不陷入主从状态。
基本上,理想的情况是两个用户的应用程序都处于一种空闲状态,并且后端在发生匹配的情况下可以同时通知它们,但是在那种情况下,后端的进程应该ve不是基于请求的,而是可能是一个工作人员...我正在将Postgres与Postgis结合使用,因此可以存储用户的职位,但是由于行数的变化,不确定是否Redis是更好的选择...
我知道我对我的问题含糊不清,但这实际上是采用哪种方法的问题,不仅仅是代码级解决方案。
非常感谢您!
答案 0 :(得分:0)
免责声明:我对您想到的功能没有任何经验。但是,我会做类似以下的事情。
下面的所有代码都使用Postgis
PartnersLocation
模型具有t.st_point :geolocation, geographic: true
属性。 Reference
客户端通过ActionCable
(网络套接字)连接到Rails后端
示例:
// JS
// when "Search Partner" is clicked, perform the following:
var currentRadius = 5;
// let XXX.XX and YYY.YY below to be the "current" location
// Request:
// POST /partners_locations.json
// { partners_location: { radius: 5, latitude: XXX.XX, longitude: YYY.YY } }
// Response:
// 201 Created
// { id: 14, user_id: 6, radius: 5, latitude: XXX.XX, longitude: YYY.YY }
// Using the `partners_location` ID above ^
App.cable.subscriptions.create(
{ channel: 'PartnersLocationsSearchChannel', id: 14 },
{
connected: function() {
this.search()
// search every 5 seconds, and incrementally increase the radius
setInterval(this.search, 5000)
},
// this function will be triggered when there is a match
received: function(data) {
console.log(data)
// i.e. will output: { id: 22, user_id: 7, latitude: XXX.XX, longitude: YYY.YY }
}
search: function() {
this.perform('search', { radius: currentRadius })
currentRadius += 1
}
}
)
后端类似于:
app / controllers / partners_locations_controller.rb:
class PartnersLocationsController < ApplicationController
def create
@partners_location = PartnersLocation.new(partners_location_params)
@partners_location.user = current_user
if @partners_location.save
render @partners_location, status: :created, location: @partners_location
else
render json: @partners_location.errors, status: :unprocessable_entity
end
end
private
def partners_location_params
params.require(:partners_location).permit(:radius, :latitude, :longitude)
end
end
app / channels / application_cable / partners_locations_search_channel.rb:
class PartnersLocationsSearchChannel < ApplicationCable::Channel
def subscribed
@current_partners_location = PartnersLocation.find(params[:id])
stream_for @current_partners_location
end
def unsubscribed
@current_partners_location.destroy
end
def search(data)
radius = data.fetch('radius')
@current_partners_location.update!(radius: radius)
partners_locations = PartnersLocation.where.not(
id: @current_partners_location.id
).where(
# TODO: update this `where` to do a Postgis two-circle (via radius) intersection query to get all the `partners_locations` of which their "radiuses" have already been taken accounted for.
# ^ haven't done this "circle-intersect" before, but probably the following will help: https://gis.stackexchange.com/questions/166685/postgis-intersect-two-circles-each-circle-must-be-built-from-long-lat-degrees?rq=1
)
partners_locations.each do |partners_location|
PartnersLocationsSearchChannel.broadcast_to(
partners_location,
@current_partners_location.as_json
)
PartnersLocationsSearchChannel.broadcast_to(
@current_partners_location,
partners_location.as_json
)
end
end
end
上面的代码仍然需要调整:
只需更新以上任何代码即可使用JSON API
相应地更新客户端(它可能不是JS)。即您可以使用:
partners_locations#create
仍需要进行调整,以将latitude
和longitude
都保存到geolocation
属性中
current_partner_location.as_json
以返回latitude
和longitude
而不是属性geolocation
def search
需要更新:where
Postgis 2圆相交的条件。老实说,我不知道该如何处理。如果有人,请告诉我。这是closest I could find on the web
上面的JS代码仍需要进行调整,以妥善处理连接错误,并在成功匹配后停止setInterval
。
JS代码以从PartnersLocationsSearchChannel
“退订”取决于您的要求)
答案 1 :(得分:0)
Sidekiq + WebSockets + psql应该可以。为了避免出现主从关系,匹配应该基于两个寻找匹配用户之间的邀请。解决方案非常简单:当用户通过websocket连接到您的Rails应用时,它将启动FindMatchJob
。作业会检查5公里距离范围内是否还有其他用户邀请了我们并接受了第一个邀请。否则,它将邀请具有给定范围的其他用户,并以1秒的延迟再次显示自己。我添加了一些代码作为概念验证,但是就并发问题而言,它并不是防弹的。我也简化了范围扩展,因为我很懒:)
class FindMatchJob < ApplicationJob
def perform(user, range: 5)
return unless user.looking_for_a_match?
invites = find_pending_invites(user, range)
return accept_invite(invite.first) if invites.any?
users_in_range = find_users_in_range(user, range)
users_in_range.each do |other_user|
Invite.create!(
inviting: user,
invited: other_user,
distance: other_user.distance_from(user)
)
end
self.class.set(wait: 1.second).perform_later(user, range: range + 1)
end
private
def find_pending_invites(user)
Invite.where('distance <= ?', distance).find_by(invited: user)
end
def accept_invite(invite)
notify_users(invite)
clear_other_invites(invite)
end
def find_users_in_range(user, range)
# somehow find those users
end
def notify_users(invite)
# Implement logic to notify users about a match via websockets
end
def clear_other_invites(invite)
Invite.where(
inviting: match.inviting
).or(
invited: match.inviting
).or(
inviting: match.invited
).or(
invited: match.invited
).delete_all
end
end
还有一个便条。您可能需要考虑使用其他技术堆栈来解决此问题。就并发而言,Rails并不是最好的方法。我会尝试使用GoLang,在这里会更好。