📅  最后修改于: 2023-12-03 14:56:48.593000             🧑  作者: Mango
红宝石是一门动态编程语言,它的设计目标是追求简洁高效,同时保持代码的可读性。它既可以面向对象编程,也可以函数式编程,因此受到了许多程序员的喜爱。本文将介绍红宝石实践案例,希望能给程序员提供参考和启发。
我们希望开发一个聊天室,需要实现以下功能:
我们使用 Sinatra 框架实现了基本的 Web 服务器,使用 Redis 存储聊天记录和用户信息。
用户的信息包括用户名、密码、邮箱等,存储在 Redis 中。用户注册时,需要判断用户名是否已存在,密码需要加密后存储。用户登录时,需要校验用户名和密码是否匹配,匹配成功后,生成一个 token 作为登录凭证,存储在 Redis 中,并设置有效期。用户注销时,需要删除 token。
以下是注册、登录、注销的代码片段:
class User
def self.add(username, password, email)
raise '用户名已存在' if exists?(username)
password = Digest::MD5.hexdigest(password)
redis.hmset(user_key(username), 'username', username, 'password', password, 'email', email)
end
def self.login(username, password)
password = Digest::MD5.hexdigest(password)
user = redis.hgetall(user_key(username))
raise '用户名或密码错误' unless user['password'] == password
token = SecureRandom.hex
redis.setex(token_key(token), TOKEN_EXPIRES_IN, user['username'])
token
end
def self.logout(token)
redis.del(token_key(token))
end
private
def self.exists?(username)
redis.exists(user_key(username))
end
def self.user_key(username)
"user:#{username}"
end
def self.token_key(token)
"token:#{token}"
end
def self.redis
@redis ||= Redis.new
end
end
用户间的聊天使用 WebSocket 实现,私聊和群聊使用不同的命名空间。客户端发送消息到服务端时,需要指定目标用户或群组,服务端接收到消息后,将消息保存到 Redis 中,并广播消息到指定的用户或群组中。
以下是私聊和群聊的代码片段:
class Chat
def initialize(app)
@app = app
@redis = Redis.new
@sockets = []
@clients = {}
end
def call(env)
if Faye::WebSocket.websocket?(env)
socket = Faye::WebSocket.new(env)
socket.on :open do |event|
@sockets << socket
end
socket.on :message do |event|
data = JSON.parse(event.data)
case data['type']
when 'join'
username = User.valid_token?(data['token'])
if username
@clients[username] ||= []
@clients[username] << socket
emit_all('join', username)
else
socket.close
end
when 'leave'
username = User.valid_token?(data['token'])
if username
remove_client(username, socket)
emit_all('leave', username)
else
socket.close
end
when 'message'
username = User.valid_token?(data['token'])
if username
save_message(username, data['to'], data['content'])
send_message(username, data['to'], data['content'])
else
socket.close
end
end
end
socket.on :close do |event|
@sockets.delete(socket)
@clients.each do |username, sockets|
remove_client(username, socket) if sockets.include?(socket)
end
end
# return async Rack response
socket.rack_response
else
@app.call(env)
end
end
private
def save_message(from, to, content)
message = {
'from' => from,
'to' => to,
'content' => content,
'timestamp' => Time.now.to_i
}
key = message_key(from, to)
@redis.lpush(key, message.to_json)
@redis.ltrim(key, 0, MESSAGE_LIMIT - 1)
end
def send_message(from, to, content)
message = {
'from' => from,
'to' => to,
'content' => content,
'timestamp' => Time.now.to_i
}
if User.group?(to)
emit_group(to, 'message', message)
else
emit_user(to, 'message', message)
emit_user(from, 'message', message)
end
end
def emit_all(type, data)
@sockets.each do |socket|
socket.send(JSON.generate({ 'type' => type, 'data' => data }))
end
end
def emit_user(username, type, data)
if @clients[username]
@clients[username].each do |socket|
socket.send(JSON.generate({ 'type' => type, 'data' => data }))
end
end
end
def emit_group(group, type, data)
redis.smembers(User.group_key(group)).each do |username|
emit_user(username, type, data)
end
end
def remove_client(username, socket)
@clients[username].delete(socket)
@clients.delete(username) if @clients[username].empty?
end
def message_key(from, to)
"message:#{User.group?(to) ? "group:#{to}" : "#{from}/#{to}"}"
end
end
聊天记录存储在 Redis 中,保存最近的 MESSAGE_LIMIT 条记录。查询聊天记录时,可以指定起止时间、发送方、接收方等条件,从 Redis 中读取记录并返回给客户端。
以下是聊天记录查询的代码片段:
class Message
def self.query(params)
from = params['from']
to = params['to']
start_time = params['start_time'] ? Time.parse(params['start_time']).to_i : 0
end_time = params['end_time'] ? Time.parse(params['end_time']).to_i : Time.now.to_i
results = []
if User.group?(to)
redis.smembers(User.group_key(to)).each do |username|
key = message_key(from, username)
results.concat(read_messages(key, start_time, end_time))
end
else
key = message_key(from, to)
results.concat(read_messages(key, start_time, end_time))
end
results
end
private
def self.read_messages(key, start_time, end_time)
messages = redis.lrange(key, 0, MESSAGE_LIMIT - 1)
messages = messages.map { |msg| JSON.parse(msg) }
messages.select { |msg| msg['timestamp'] >= start_time && msg['timestamp'] <= end_time }
end
def self.message_key(from, to)
"message:#{User.group?(to) ? "group:#{to}" : "#{from}/#{to}"}"
end
def self.redis
@redis ||= Redis.new
end
end
系统广播使用类似于消息的方式,向所有在线用户发送公告、通知等信息。客户端可以订阅系统广播,接收到新的广播时,进行页面自动提示。
以下是系统广播的代码片段:
class Broadcast
def self.publish(content)
message = {
'from' => 'system',
'to' => 'broadcast',
'content' => content,
'timestamp' => Time.now.to_i
}
redis.publish(BROADCAST_CHANNEL, message.to_json)
end
def self.subscribe(socket)
@subscriber ||= start_subscriber
@subscriber.subscribe(BROADCAST_CHANNEL) do |on|
on.message do |_channel, message|
socket.send(JSON.generate({ 'type' => 'broadcast', 'data' => JSON.parse(message) }))
end
end
end
private
BROADCAST_CHANNEL = 'broadcast'
def self.start_subscriber
redis = Redis.new
subscriber = Redis.new
Thread.new do
subscriber.subscribe(BROADCAST_CHANNEL) do |on|
on.message do |_channel, message|
redis.lpush(BROADCAST_LOG_KEY, message)
redis.ltrim(BROADCAST_LOG_KEY, 0, MESSAGE_LIMIT - 1)
end
end
end
subscriber
end
def self.redis
@redis ||= Redis.new
end
def self.broadcast_log
redis.lrange(BROADCAST_LOG_KEY, 0, MESSAGE_LIMIT - 1).map { |msg| JSON.parse(msg) }
end
end
红宝石的简洁高效和灵活性,使得它在 Web 开发和数据处理等领域都得到了广泛的应用。同时,大量同类的现成代码库和优秀的教程,也为程序员提供了较为友好的学习和使用体验。希望本文介绍的案例能为红宝石开发者提供参考和启发。