Commit dd40de65 by 梁宇哲

Merge branch 'refactor/app_push' into 'master'

推送服务代码重构一期

See merge request !2
parents 5b2d80ce 41b1d714
...@@ -38,3 +38,6 @@ ...@@ -38,3 +38,6 @@
# Ignore master key for decrypting credentials and more. # Ignore master key for decrypting credentials and more.
/config/master.key /config/master.key
config/database.yml
/db/schema.rb
ruby-2.6.0 ruby-2.6.5
\ No newline at end of file \ No newline at end of file
...@@ -2,12 +2,13 @@ source 'https://gems.ruby-china.com' ...@@ -2,12 +2,13 @@ source 'https://gems.ruby-china.com'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.0' ruby '2.6.5'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.2', '>= 5.2.2.1' gem 'rails', '~> 6.0.0'
# Use sqlite3 as the database for Active Record # Use sqlite3 as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5' # gem 'sqlite3'
gem 'mysql2', '~> 0.5.3'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 3.11' gem 'puma', '~> 3.11'
# Use SCSS for stylesheets # Use SCSS for stylesheets
...@@ -49,13 +50,14 @@ group :development do ...@@ -49,13 +50,14 @@ group :development do
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.0.0'
gem 'capistrano-faster-rails'
# deplopy # deplopy
gem "capistrano", "~> 3.11", require: false gem "capistrano", "~> 3.11", require: false
gem 'capistrano-rvm', '~> 0.1.2', require: false gem 'capistrano-rvm', '~> 0.1.2', require: false
gem 'capistrano-rails', '~> 1.4', require: false gem 'capistrano-rails', '~> 1.4', require: false
gem 'capistrano3-puma', '~> 3.1', '>= 3.1.1', require: false # https://github.com/seuros/capistrano-puma gem 'capistrano3-puma', '~> 3.1', '>= 3.1.1', require: false # https://github.com/seuros/capistrano-puma
gem 'capistrano-sidekiq', '~> 1.0', '>= 1.0.2', require: false gem 'capistrano-sidekiq', '~> 1.0', '>= 1.0.2'
end end
group :test do group :test do
......
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Metal
ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
include left
end
include RailsParam::Param
rescue_from Mime::Type::InvalidMimeType, with: :rescue_mime_type
rescue_from StandardError, with: :rescue_all rescue_from StandardError, with: :rescue_all
include TokenValidate
def rescue_all(e) def rescue_all(e)
ErrorLog.error(e) ErrorLog.error(e)
render json: { code: 500, message: '抱歉~ 系统出错了,攻城狮们已经在修理了!', error: e.class.name.underscore, original_message: e.message } render json: { code: 500, message: '抱歉~ 系统出错了,攻城狮们已经在修理了!', error: e.class.name.underscore, original_message: e.message }
end end
def rescue_mime_type(e)
ErrorLog.error(e)
end
end end
module Token module TokenValidate
extend ActiveSupport::Concern
ACCESS_TOEKN_EXPIRE_TIME = 7200 ACCESS_TOEKN_EXPIRE_TIME = 7200
class << self
def key(app_name) def validate_token(app_name, token)
"push:api:#{app_name}:access_token" !! (token == get_token(app_name))
end
def token_ttl(app_name)
$redis.ttl(tkey(app_name))
end end
def generate_token(app_name) def generate_token(app_name)
token = SecureRandom.hex(32) token = SecureRandom.hex(32)
$redis.set(key(app_name), token, ex: ACCESS_TOEKN_EXPIRE_TIME) $redis.set(tkey(app_name), token, ex: ACCESS_TOEKN_EXPIRE_TIME)
token token
end end
def token(app_name) private
$redis.get(key(app_name))
end
def ttl(app_name) def tkey(app_name)
$redis.ttl(key(app_name)) "push:api:#{app_name}:access_token"
end end
def get_token(app_name)
$redis.get(tkey(app_name))
end end
end end
\ No newline at end of file
class PushsController < ApplicationController class PushsController < ApplicationController
include ActionController::HttpAuthentication::Token include ActionController::HttpAuthentication::Token
skip_before_action :verify_authenticity_token
def token def token
param! :app_name, String, required: true param! :app_name, String, required: true
...@@ -9,48 +8,46 @@ class PushsController < ApplicationController ...@@ -9,48 +8,46 @@ class PushsController < ApplicationController
app_setting = Settings.send(params[:app_name].to_sym) rescue {} app_setting = Settings.send(params[:app_name].to_sym) rescue {}
if app_setting['app_id'] == params[:app_id] && app_setting['app_secret'] == params[:app_secret] if app_setting['app_id'] == params[:app_id] && app_setting['app_secret'] == params[:app_secret]
render json: { code: 0, message: 'success', token: Token.generate_token(params[:app_name]), ttl: Token.ttl(params[:app_name])} render json: { code: 0, message: 'success', token: generate_token(params[:app_name]), ttl: token_ttl(params[:app_name])}
else else
render json: { code: -1, message: '应用不存在或者app_id/app_secret不匹配'} render json: { code: -1, message: '应用不存在或者app_id/app_secret不匹配'}
end end
end end
# app_name 应用名称 def push_v2
# user_device_ids 设备ids param! :app_name, String, required: true, in: %w(crm)
# message 推送信息的json 字符串 # param! :device_ids_opts, String, required: true
# igetui_opts 个推推送的额外选项
def push
param! :app_name, String, required: true
param! :device_ids_opts, String, required: true
param! :message, String, required: true param! :message, String, required: true
param! :igetui_opts, String # param! :igetui_opts, String
param! :app_type, String param! :sync_push, :boolean
param! :sync_push, String param! :user_ids, Array, required: true
igetui_opts = JSON.parse(params[:igetui_opts]) rescue {}
message = JSON.parse(params[:message]) rescue {} message = JSON.parse(params[:message]) rescue {}
device_ids_opts = JSON.parse(params[:device_ids_opts]) rescue {} igetui_opts = message
token = token_and_options(request).first
if token == Token.token(params[:app_name])
# 写入记录
NotificationStatistic.create_by_params(igetui_opts, message, device_ids_opts, params[:app_type])
if params.has_key?(:sync_push) user_ids = params[:user_ids] rescue []
timeout_seconds = $redis.get('push_timeout').to_i
if timeout_seconds > 0
Timeout.timeout(timeout_seconds) { user_device_exist = UserDevice.where(user_id: user_ids).exists?
::PushWorker.new.perform(device_ids_opts, message, igetui_opts, params[:app_type])
} unless user_device_exist
else return render json: { code: -1, message: 'user_device找不到,user_ids错误'}
::PushWorker.new.perform(device_ids_opts, message, igetui_opts, params[:app_type])
end end
else
::PushWorker.perform_async(device_ids_opts, message, igetui_opts, params[:app_type]) token_array = token_and_options(request)
if token_array.blank?
return render json: { code: 401, message: 'token错误'}
end end
params[:sync_push] ||= false
if validate_token(params[:app_name], token_array.first)
push_sync(params[:sync_push], {user_ids: user_ids, message: message, igetui_opts: igetui_opts.merge(pusher_type: 'push_message_to_list')})
render json: { code: 0, message: 'success', describe: '异步任务正在处理'} render json: { code: 0, message: 'success', describe: '异步任务正在处理'}
else else
render json: { code: -1, message: 'token 错误/过期'} render json: { code: 401, message: 'token 错误/过期'}
end end
end end
def received def received
...@@ -65,4 +62,50 @@ class PushsController < ApplicationController ...@@ -65,4 +62,50 @@ class PushsController < ApplicationController
render json: { code: 0, message: "success" } render json: { code: 0, message: "success" }
end end
def push
param! :app_name, String, required: true
param! :device_ids_opts, String, required: true
param! :message, String, required: true
param! :sync_push, :boolean
param! :igetui_opts, String
param! :app_type, String
igetui_opts = JSON.parse(params[:igetui_opts]) rescue {}
message = JSON.parse(params[:message]) rescue {}
device_ids_opts = JSON.parse(params[:device_ids_opts]) rescue {}
token_array = token_and_options(request)
if token_array.blank?
return render json: { code: 401, message: 'token错误'}
end
if validate_token(params[:app_name], token_array.first)
params[:sync_push] ||= false
push_sync(params[:sync_push], {device_ids_opts: device_ids_opts, message: message, app_type: params[:app_type], igetui_opts: igetui_opts.merge(pusher_type: 'push_message_to_list')})
NotificationStatistic.create_by_params(igetui_opts, message, device_ids_opts, params[:app_type])
render json: { code: 0, message: 'success', data: {remind: "请使用新的api,此api近期将不再维护,请尽快迁移到新的api,新api地址 http://apidoc.weiwenjia.com/docs/internal-dev-api/internal-dev-api-1boq6ctdm8m57"}}
else
render json: { code: 401, message: 'token 错误/过期'}
end
end
private
def push_sync(sync_push, opts={})
if sync_push
timeout_seconds = $redis.get('push_timeout').to_i
if timeout_seconds > 0
Timeout.timeout(timeout_seconds) {
::PushWorker.new.perform(opts)
}
else
::PushWorker.new.perform(opts)
end
else
::PushWorker.perform_async(opts)
end
end
end end
class UserDevicesController < ApplicationController
def create
param! :app_type, String, required: true, in: UserDevice::app_types.keys
param! :client_id, String, required: true
param! :user_id, Integer, required: true
_user_device_params = permit_user_device_params
_user_device_params[:platform] ||= 'igetui'
user_id = _user_device_params[:user_id]
UserDevice.where(user_id: user_id).delete_all
user_device = UserDevice.create!(_user_device_params)
render json: { code: 0, message: "success", data: { user_device: user_device } }
end
def update_by_user_id
param! :old_user_id, Integer, required: true
param! :user_id, Integer, required: true
param! :organization_id, Integer, required: true
user_id = params[:user_id]
old_user_id = params[:old_user_id]
user_device = UserDevice.where(user_id: old_user_id).last
if user_device.present?
UserDevice.where(user_id: user_id).delete_all
UserDevice.where(user_id: old_user_id).delete_all
new_user_device = user_device.dup
new_user_device.update(
user_id: user_id,
organization_id: params[:organization_id],
created_at: Time.now,
updated_at: Time.now
)
end
render json: { code: 0, message: "success" }
end
def destroy_by_user_id
param! :user_id, Integer, required: true
user_id = params[:user_id]
UserDevice.where(user_id: user_id).delete_all
render json: { code: 0, message: "success" }
end
private
def permit_user_device_params
params.permit(:platform, :device_token, :client_id, :device_model, :device_platform,
:device_id, :device_version, :device_phone, :app_type, :user_id, :organization_id, :ip)
end
end
...@@ -36,4 +36,29 @@ class NotificationStatistic < ApplicationRecord ...@@ -36,4 +36,29 @@ class NotificationStatistic < ApplicationRecord
e.backtrace.each { |l| Rails.logger.info l } e.backtrace.each { |l| Rails.logger.info l }
end end
def self.static_notification(opts, client_ids, platform, device_platform=nil)
::Log.info("统计数据开始 opts is #{opts.to_json} client_ids is #{client_ids} platform is #{platform} device_platform is #{device_platform}")
if client_ids.is_a? Array
client_ids.each do |client_id|
next if opts[:message][:notifiable_id].blank?
NotificationStatistic.create(
organization_id: opts[:igetui_opts][:organization_id],
user_id: opts[:igetui_opts][:user_id],
notification_id: opts[:message][:notifiable_id],
client_id: client_id,
platform: platform,
device_platform: device_platform,
app_type: opts[:app_type],
ip: opts[:igetui_opts][:ip],
)
end
end
rescue => e
::Log.info "exception is #{e}"
e.backtrace.each { |l| ::Log.error l }
end
end end
class UserDevice < ApplicationRecord
# 当前app端传递的platform的值:
# android: igetui, xiaomi, huawei
# iso: igetui 目前还不清楚为啥不用apns,暂时不修改
enum platform: %i[igetui apns xiaomi huawei]
enum device_platform: %i[ios android]
# 用于区分当前的应用,为了推送准备, 因为推送需要根据不同的应用使用不同的 id,secret等。
enum app_type: %i[ik_duli lx_duli lx_yun aike_yun]
end
\ No newline at end of file
module Push module Huawei
module Huawei class Pusher
ACCESS_TOKEN_KEY = "push:huawei_push:access_token".freeze ACCESS_TOKEN_KEY_PREFIX = "push:huawei_push:access_token".freeze
TOKEN_URL = 'https://login.cloud.huawei.com/oauth2/v2/token'.freeze TOKEN_URL = 'https://login.cloud.huawei.com/oauth2/v2/token'.freeze
PUSH_URL = 'https://api.push.hicloud.com/pushsend.do'.freeze PUSH_URL = 'https://api.push.hicloud.com/pushsend.do'.freeze
ACCESS_TOEKN_EXPIRE_TIME = 3500 ACCESS_TOEKN_EXPIRE_TIME = 3500
BATCH_PUSH_MAX = 80 BATCH_PUSH_MAX = 80
class << self def initialize(device_ids, message, app_type)
@device_tokens = device_ids
@message = message
@app_type = app_type
@uuid = Random.uuid.to_s
end
def access_token_key
ACCESS_TOKEN_KEY_PREFIX + @app_type.to_s
end
# 4a7d376b47c847634571a86b37e93272 测试使用的device_token # 4a7d376b47c847634571a86b37e93272 测试使用的device_token
def push(device_tokens, message) def perform
device_tokens = @device_tokens
message = @message
handle_token_res if access_token.blank? handle_token_res if access_token.blank?
device_tokens.each_slice(BATCH_PUSH_MAX) do |device_token_array| device_tokens.each_slice(BATCH_PUSH_MAX) do |device_token_array|
res = res_push(message, device_token_array) res = res_push(message, device_token_array)
...@@ -19,7 +32,7 @@ module Push ...@@ -19,7 +32,7 @@ module Push
end end
end end
rescue StandardError => err rescue StandardError => err
Push::Log.error(err) ::Log.error(err)
end end
def res_push(message, device_tokens) def res_push(message, device_tokens)
...@@ -27,7 +40,7 @@ module Push ...@@ -27,7 +40,7 @@ module Push
nsp_ctx = { nsp_ctx = {
ver: '1', ver: '1',
appId: Settings.platform_settings.huawei['app_id'] appId: Settings.send(@app_type).huawei['app_id']
} }
url = PUSH_URL + "?nsp_ctx=#{CGI::escape(nsp_ctx.to_json)}" url = PUSH_URL + "?nsp_ctx=#{CGI::escape(nsp_ctx.to_json)}"
post(url, body) post(url, body)
...@@ -43,7 +56,7 @@ module Push ...@@ -43,7 +56,7 @@ module Push
} }
} }
} }
Push::Log.info("huawei_payload: #{payload}, device_tokens: #{device_tokens}") ::Log.info("huawei_payload: #{payload}, device_tokens: #{device_tokens}", @uuid)
params = { params = {
access_token: CGI.escape(access_token), access_token: CGI.escape(access_token),
nsp_svc: CGI.escape('openpush.message.api.send'), nsp_svc: CGI.escape('openpush.message.api.send'),
...@@ -56,6 +69,7 @@ module Push ...@@ -56,6 +69,7 @@ module Push
params_str += "#{key}=#{value}" params_str += "#{key}=#{value}"
params_str += '&' if index + 1 < params.size params_str += '&' if index + 1 < params.size
end end
params_str params_str
end end
...@@ -63,31 +77,33 @@ module Push ...@@ -63,31 +77,33 @@ module Push
loop do loop do
res = req_token res = req_token
if res['error'].blank? && res['access_token'].present? if res['error'].blank? && res['access_token'].present?
$redis.set(ACCESS_TOKEN_KEY, res['access_token'], ex: ACCESS_TOEKN_EXPIRE_TIME) $redis.set(access_token_key, res['access_token'], ex: ACCESS_TOEKN_EXPIRE_TIME)
break break
end end
end end
end end
def req_token def req_token
client_id = Settings.send(@app_type).huawei['app_id']
client_secret = Settings.send(@app_type).huawei['app_secret']
body = { body = {
grant_type: 'client_credentials', grant_type: 'client_credentials',
client_id: Settings.platform_settings.huawei['app_id'], client_id: client_id,
client_secret: Settings.platform_settings.huawei['app_secret'] client_secret: client_secret
} }
post(TOKEN_URL, body) post(TOKEN_URL, body)
end end
def access_token def access_token
$redis.get(ACCESS_TOKEN_KEY) $redis.get(access_token_key)
end end
def post(url, body) def post(url, body)
headers = { 'Content-Type' => 'application/x-www-form-urlencoded' } headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
response = HTTParty.post(url, body: body, headers: headers, timeout: 5) response = HTTParty.post(url, body: body, headers: headers, timeout: 5)
Push::Log.info("huawei_push: \n access_token: #{access_token} expire_time: #{$redis.ttl(Push::Huawei::ACCESS_TOKEN_KEY)}\n url: #{url}\n body: #{body}\n response: #{response}") ::Log.info("huawei_push: \n access_token: #{access_token} expire_time: #{$redis.ttl(access_token_key)}\n url: #{url}\n body: #{body}\n response: #{response}", @uuid)
JSON.parse(response.body) JSON.parse(response.body)
end end
end end
end
end end
module Igetui
class Pusher
def initialize(user_device_ids, opts)
opts = HashWithIndifferentAccess.new(opts)
@template_data = opts[:message]
@igetui_opts = opts[:igetui_opts]
@user_device_ids = user_device_ids
@pusher = pusher(opts[:device_platform], opts[:app_type])
@pusher_type = @igetui_opts[:pusher_type]
@uuid = Random.uuid.to_s
end
def perform
@template_data[:push_type] ||= 2
@igetui_opts[:transmission_type] ||= 0
@igetui_opts[:transmission_content] = @template_data.as_json
template_type = 'TransmissionTemplate'
template_data = @igetui_opts
result = push_message_to(@pusher, @pusher_type, template_type, template_data, @user_device_ids)
::Log.info("igetui_push: \n template_data: #{template_data}\n user_device_ids: #{@user_device_ids}\n response: #{result}", @uuid)
end
private
def pusher(device_platform, app_type)
case app_type
when 'lx_duli'
device_platform == 'android' ? $lx_duli_android_pusher : $lx_duli_ios_pusher
when 'lx_yun'
device_platform == 'android' ? $lx_yun_android_pusher : $lx_yun_ios_pusher
when 'aike_yun'
device_platform == 'android' ? $aike_yun_android_pusher : $aike_yun_ios_pusher
else 'ik_duli'
device_platform == 'android' ? $ik_duli_android_pusher : $ik_duli_ios_pusher
end
end
def push_message_to(pusher, pusher_type, template_type, template_data, client_id)
client_id_list = Array(client_id)
template = set_template_data(template_type, template_data)
case pusher_type
when 'push_message_to_single'
message = IGeTui::SingleMessage.new
message.data = template
client_id_list.map do |client_id|
client = IGeTui::Client.new(client_id)
res = pusher.push_message_to_single(message, client)
::Log.info(" push_message_to_single client: #{client.to_json} and message is #{message.to_json} and res is #{res}", @uuid)
end
when 'push_message_to_list'
message = IGeTui::ListMessage.new
message.data = template
content_id = pusher.get_content_id(message)
clients = client_id_list.map { |client_id| IGeTui::Client.new(client_id) }
res = pusher.push_message_to_list(content_id, clients)
::Log.info("push_message_to_list clients: #{clients.to_json} and message is #{message.to_json} and res is #{res}", @uuid)
when 'push_message_to_app'
message = IGeTui::AppMessage.new
message.data = template
message.app_id_list = client_id_list
res = pusher.push_message_to_app(message)
::Log.info("push_message_to_app message is #{message.to_json} and res is #{res}", @uuid)
end
end
def set_template_data(template_type, template_data)
@template ||= "IGeTui::#{template_type}".constantize.new
case template_type
when 'NotificationTemplate', 'NotyPopLoadTemplate', 'LinkTemplate'
template_data.reverse_merge!(Settings.platform_settings.igetui['template_base'])
template_data.each do |k, v|
@template.send("#{k}=", v)
end
@template.set_push_info(*template_data[:push_info]) if template_data.key? :push_info
when 'TransmissionTemplate'
@template.transmission_content = template_data[:transmission_content].is_a?(Hash) ? template_data[:transmission_content].to_json : template_data[:transmission_content]
@template.transmission_type = template_data[:transmission_type] if template_data.key?(:transmission_type)
@template.set_push_info(*template_data[:push_info]) if template_data.key? :push_info
end
@template
end
end
end
module Push
class << self
# 调用推送服务,推送信息
# message 要推送的具体信息
# opts 是hash类型,主要是一些其他的信息。
# 由于历史遗留问题,封装的不够好,这里主要用来处理个推的一些参数。
# 目前华为推送,小米推送不会用到这里的信息。
# device_ids_opts, 用户设备信息是hash数组。
# device_ids_opts = {
# igetui: [],
# apns: [],
# xiaomi: [],
# huawei: []
# }
def push(device_ids_opts, message, igetui_opts = {}, app_type = nil)
Settings.platform = app_type || Settings::DEFAULT
Push::Log.info("*************** push_service ***************:\n device_ids_opts: #{device_ids_opts}, message: #{message}, igetui_opts: #{igetui_opts} platform: #{Settings.platform}")
message = message.with_indifferent_access
igetui_opts = igetui_opts.with_indifferent_access
# igetui
Push::Igetui.push(device_ids_opts['igetui'], message, igetui_opts) if device_ids_opts['igetui'].present?
Push::Igetui.push(device_ids_opts['apns'], message, igetui_opts) if device_ids_opts['apns'].present?
# xiaomi
# https://dev.mi.com/console/doc/detail?pId=1163 文档中心
Push::Xiaomi.push(device_ids_opts['xiaomi'], message) if device_ids_opts['xiaomi'].present?
# huawei
# https://developer.huawei.com/consumer/cn/service/hms/catalog/huaweipush_agent.html?page=hmssdk_huaweipush_api_reference_agent_s1 接入文档
Push::Huawei.push(device_ids_opts['huawei'], message) if device_ids_opts['huawei'].present?
# etc
rescue StandardError => err
Push::Log.error(err)
end
end
end
module Push
module Igetui
class << self
def push(user_device_ids, template_data = {}, opts = {})
# 包装成igetui 真正需要的参数格式
opts[:transmission_content] = template_data
if user_device_ids.size > 1
push_transmission_to_list_igetui(user_device_ids, opts)
else
push_transmission_to_igetui(user_device_ids, opts)
end
rescue StandardError => err
::Push::Log.error(err)
end
def config
{
push_type: 2
}
end
def push_transmission_to_igetui(user_device_id, template_data = {})
template_data = handle_template_data(template_data)
push_igetui_with_asnyc(
user_device_id,
template_data,
template_type: 'TransmissionTemplate'
)
end
def push_transmission_to_list_igetui(user_device_ids, template_data = {})
template_data = handle_template_data(template_data)
push_list_igetui_with_asnyc(
user_device_ids,
template_data,
template_type: 'TransmissionTemplate', pusher_type: 'push_message_to_list'
)
end
private
def handle_template_data(template_data)
template_data = HashWithIndifferentAccess.new(template_data)
transmission_content = HashWithIndifferentAccess.new(template_data.delete(:transmission_content))
transmission_content[:push_type] ||= config[:push_type]
template_data[:transmission_type] ||= 0
template_data[:transmission_content] = transmission_content.as_json
template_data
end
def push_igetui_with_asnyc(user_device_id, template_data = {}, opts = nil)
return if template_data.blank?
opts ||= HashWithIndifferentAccess.new(template_type: 'TransmissionTemplate')
template_data = HashWithIndifferentAccess.new(template_data)
perform(template_data, opts, user_device_id)
end
def push_list_igetui_with_asnyc(user_device_ids, template_data = {}, opts = { pusher_type: 'push_message_to_list' })
return if user_device_ids.blank? || template_data.blank?
opts ||= HashWithIndifferentAccess.new(template_type: 'TransmissionTemplate')
perform(template_data, opts, user_device_ids)
end
def perform(template_data, opts, user_device_ids)
opts = HashWithIndifferentAccess.new(opts)
igetui = Igetui::Push.new(template_data, opts)
result = igetui.push_message_to(user_device_ids)
::Push::Log.info("igetui_push: \n template_data: #{template_data}\n opts: #{opts}\n user_device_ids: #{user_device_ids}\n response: #{result}")
end
end
end
end
\ No newline at end of file
module Push
module Igetui
class Push
attr_accessor :pusher, :pusher_type, :template_type, :template, :template_data
PUSHERTYPES = %w[push_message_to_single push_message_to_list push_message_to_app].freeze
TEMPLATETYPES = %w[LinkTemplate NotificationTemplate TransmissionTemplate NotyPopLoadTemplate].freeze
DEVICEPLATFORMS = %w[android ios].freeze
def initialize(template_data, opts = {})
opts = HashWithIndifferentAccess.new(opts)
@template_data = HashWithIndifferentAccess.new(template_data || {})
@pusher_type = PUSHERTYPES.include?((opts[:pusher_type]).to_s) ? opts[:pusher_type] : 'push_message_to_single'
@template_type = TEMPLATETYPES.include?((opts[:template_type]).to_s) ? opts[:template_type] : 'NotificationTemplate'
::Push::Log.info("@template_data is #{@template_data.to_json}")
set_message
set_template_data
@message.data = @template
end
def push_message_to(client_id)
client_id_list = Array(client_id)
case pusher_type
when 'push_message_to_single'
client_id_list.map do |client_id|
client = IGeTui::Client.new(client_id)
pusher.push_message_to_single(@message, client)
end
when 'push_message_to_list'
content_id = pusher.get_content_id(@message)
clients = client_id_list.map { |client_id| IGeTui::Client.new(client_id) }
pusher.push_message_to_list(content_id, clients)
when 'push_message_to_app'
@message.app_id_list = client_id_list
pusher.push_message_to_app(@message)
end
end
private
def pusher
@pusher ||=
if DEVICEPLATFORMS.include?((@template_data['device_platform'].to_s))
case @template_data['device_platform'].to_s
when 'android'
IGeTui.pusher(
Settings.platform_settings.igetui.android['app_id'],
Settings.platform_settings.igetui.android['app_key'],
Settings.platform_settings.igetui.android['master_secret']
)
when 'ios'
IGeTui.pusher(
Settings.platform_settings.igetui.ios['app_id'],
Settings.platform_settings.igetui.ios['app_key'],
Settings.platform_settings.igetui.ios['master_secret']
)
end
else
::Push::Log.info("设备类型不正确#{@device_platform}")
raise "Failed to create pusher"
end
end
def set_message
@message = case pusher_type
when 'push_message_to_single'
IGeTui::SingleMessage.new
when 'push_message_to_list'
IGeTui::ListMessage.new
when 'push_message_to_app'
IGeTui::AppMessage.new
end
end
def set_template_data
@template = "IGeTui::#{template_type}".constantize.new
case template_type
when 'NotificationTemplate', 'NotyPopLoadTemplate', 'LinkTemplate'
@template_data.reverse_merge!(Settings.platform_settings.igetui['template_base'])
@template_data.each do |k, v|
@template.send("#{k}=", v)
end
@template.set_push_info(*@template_data[:push_info]) if @template_data.key? :push_info
when 'TransmissionTemplate'
@template.transmission_content = @template_data[:transmission_content].is_a?(Hash) ? @template_data[:transmission_content].to_json : @template_data[:transmission_content]
@template.transmission_type = @template_data[:transmission_type] if @template_data.key?(:transmission_type)
@template.set_push_info(*@template_data[:push_info]) if @template_data.key? :push_info
end
end
end
end
end
#push功能的代理选择用哪种推送工具1.个推 2.小米 3.华为
class PushToolClient
def initialize(opts={})
opts = HashWithIndifferentAccess.new(opts)
@user_ids = opts[:user_ids]
@opts = opts
end
def do_push
# Settings.platform = @opts[:app_type] || Settings::DEFAULT
igetui_android_client_ids, igetui_ios_client_ids, xiaomi_client_ids, huawei_client_ids = fetch_client_ids_group
if igetui_android_client_ids.present?
igetui_android_client_ids.each_pair do |app_type, ids|
Igetui::Pusher.new(ids, @opts.merge(device_platform: 'android', app_type: app_type)).perform
NotificationStatistic.static_notification(@opts.merge(app_type: app_type), ids,'igetui', 'android' )
end
end
if igetui_ios_client_ids.present?
igetui_ios_client_ids.each_pair do |app_type, ids|
Igetui::Pusher.new(ids, @opts.merge(device_platform: 'ios', app_type: app_type)).perform
NotificationStatistic.static_notification(@opts.merge(app_type: app_type), ids,'igetui', 'ios' )
end
end
#现在好像不用了
# Push::IgetuiIos.push(@push_collections[:igetui_ios], @opts) if @push_collections[:apns].present?
# Push::IgetuiAndroid.push(@push_collections[:igetui_android], @opts) if @push_collections[:apns].present?
if xiaomi_client_ids.present?
xiaomi_client_ids.each_pair do |app_type, ids|
message = @opts[:message].merge(@opts[:message][:transmission_content] || {})
Xiaomi::Pusher.new(ids, message, app_type).perform
NotificationStatistic.static_notification(@opts.merge(app_type: app_type), ids,'xiaomi')
end
end
if huawei_client_ids.present?
huawei_client_ids.each_pair do |app_type, ids|
message = @opts[:message].merge(@opts[:message][:transmission_content] || {})
Huawei::Pusher.new(ids, message, app_type).perform
NotificationStatistic.static_notification(@opts.merge(app_type: app_type), ids,'huawei')
end
end
end
def fetch_client_ids_group
igetui_android_client_ids = {}; igetui_ios_client_ids = {}; xiaomi_client_ids = {}; huawei_client_ids = {}
if (@opts[:device_ids_opts].is_a? Hash) && @user_ids.blank? && @opts[:app_type].present?
push_devices = @opts[:device_ids_opts].keys.map(&:to_s)
if push_devices.include? "igetui"
igetui_android_client_ids = {"#{@opts[:app_type]}" => @opts[:device_ids_opts][:igetui]} if @opts[:igetui_opts][:device_platform] == 'android'
igetui_ios_client_ids = {"#{@opts[:app_type]}" =>@opts[:device_ids_opts][:igetui]} if @opts[:igetui_opts][:device_platform] == 'ios'
end
huawei_client_ids = {"#{@opts[:app_type]}" => @opts[:device_ids_opts][:huawei]} if push_devices.include? "huawei"
xiaomi_client_ids = {"#{@opts[:app_type]}" => @opts[:device_ids_opts][:xiaomi]} if push_devices.include? "xiaomi"
else
push_device_collections = UserDevice.where(user_id: @user_ids).group_by(&:platform)
igetui_push_device_collections = push_device_collections['igetui'].group_by(&:device_platform) if push_device_collections['igetui'].is_a? Array
if igetui_push_device_collections.is_a? Hash
if igetui_push_device_collections['android'].is_a? Array
igetui_app_type_group = igetui_push_device_collections['android'].group_by(&:app_type)
igetui_app_type_group.each_pair{|k,v| igetui_android_client_ids.merge!("#{k}"=> v.map(&:client_id))}
end
if igetui_push_device_collections['ios'].is_a? Array
igetui_app_type_group = igetui_push_device_collections['ios'].group_by(&:app_type)
igetui_app_type_group.each_pair{|k,v| igetui_ios_client_ids.merge!("#{k}"=> v.map(&:client_id))}
end
end
if push_device_collections['xiaomi'].is_a? Array
xiaomi_client_group = push_device_collections['xiaomi'].group_by(&:app_type)
xiaomi_client_group.each_pair{|k,v| xiaomi_client_ids.merge!("#{k}"=> v.map(&:client_id))}
end
if push_device_collections['huawei'].is_a? Array
huawei_client_ids = push_device_collections['huawei'].group_by(&:app_type)
huawei_client_ids.each_pair{|k,v| huawei_client_ids.merge!("#{k}"=> v.map(&:client_id))}
end
end
[igetui_android_client_ids, igetui_ios_client_ids, xiaomi_client_ids, huawei_client_ids]
end
end
\ No newline at end of file
module Push module Xiaomi
module Xiaomi class Pusher
PUSH_URL = 'https://api.xmpush.xiaomi.com/v3/message/regid'.freeze PUSH_URL = 'https://api.xmpush.xiaomi.com/v3/message/regid'.freeze
BATCH_PUSH_MAX = 80 BATCH_PUSH_MAX = 80
class << self
def push(registration_ids, payload) def initialize(device_ids, message, app_type)
@registration_ids = device_ids
@payload = message
@app_type = app_type
@uuid = Random.uuid.to_s
end
def perform
registration_ids = @registration_ids
payload = @payload
registration_ids.each_slice(BATCH_PUSH_MAX) do |registration_ids_array| registration_ids.each_slice(BATCH_PUSH_MAX) do |registration_ids_array|
registration_id = registration_ids_array.join(',') registration_id = registration_ids_array.join(',')
post(message(payload, registration_id)) post(message(payload, registration_id))
end end
rescue StandardError => err rescue StandardError => err
Push::Log.error(err) ::Log.error(err)
end end
def message(payload, registration_id) def message(payload, registration_id)
...@@ -29,8 +38,10 @@ module Push ...@@ -29,8 +38,10 @@ module Push
# pass_through # pass_through
# 0 表示通知栏消息, 1 表示透传消息 # 0 表示通知栏消息, 1 表示透传消息
def config def config
restricted_package_name = Settings.send(@app_type).xiaomi['package_name']
{ {
restricted_package_name: Settings.platform_settings.xiaomi['package_name'], restricted_package_name: restricted_package_name,
pass_through: 1, pass_through: 1,
title: 'xiaomi push', title: 'xiaomi push',
description: 'xiaomi push', description: 'xiaomi push',
...@@ -39,15 +50,16 @@ module Push ...@@ -39,15 +50,16 @@ module Push
end end
def post(params) def post(params)
key = Settings.send(@app_type).xiaomi['app_secret']
response = HTTParty.post(PUSH_URL, query: params, response = HTTParty.post(PUSH_URL, query: params,
headers: { headers: {
'Accept' => 'application/json;', 'Accept' => 'application/json;',
"Authorization": "key=#{Settings.platform_settings.xiaomi['app_secret']}" "Authorization": "key=#{key}"
}, },
timeout: 5) timeout: 5)
Push::Log.info("xiaomi_push: \n url: #{PUSH_URL}\n params: #{params}\n response: #{response}") ::Log.info("xiaomi_push: \n url: #{PUSH_URL}\n params: #{params}\n response: #{response}", @uuid)
response response
end end
end end
end
end end
...@@ -2,8 +2,9 @@ class PushWorker ...@@ -2,8 +2,9 @@ class PushWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: :push sidekiq_options queue: :push
def perform(device_ids_opts, message, igetui_opts, platform = nil) def perform(opts)
Push.push(device_ids_opts, message, igetui_opts, platform) ::PushToolClient.new(opts).do_push
# Push.push(user_ids, message, igetui_opts, platform)
rescue StandardError => e rescue StandardError => e
ErrorLog.error(e) ErrorLog.error(e)
end end
......
...@@ -9,7 +9,7 @@ Bundler.require(*Rails.groups) ...@@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
module AppPush module AppPush
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2 config.load_defaults 6.0
require_relative '../app/services/settings' require_relative '../app/services/settings'
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
...@@ -34,5 +34,6 @@ module AppPush ...@@ -34,5 +34,6 @@ module AppPush
config.active_job.queue_adapter = :sidekiq config.active_job.queue_adapter = :sidekiq
config.api_only = true
end end
end end
default: &default default: &default
adapter: mysql2 adapter: mysql2
encoding: utf8 encoding: utf8
pool: 3
host: rm-m5ef10gzq1n5e7b9abo.mysql.rds.aliyuncs.com host: rm-m5ef10gzq1n5e7b9abo.mysql.rds.aliyuncs.com
username: crm_dev username: crm_rw
password: 4LwSJlWiM9Krlb8g password: pHB+!*Uv9Rl9
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development: development:
<<: *default <<: *default
database: crm_dev database: crm_dev
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: db/test.sqlite3
production: production:
<<: *default <<: *default
database: crm_dev database: db/production.sqlite3
\ No newline at end of file
...@@ -8,9 +8,11 @@ set :god_sidekiq_group, 'push_service' ...@@ -8,9 +8,11 @@ set :god_sidekiq_group, 'push_service'
# 当前服务器有app 这个角色。 # 当前服务器有app 这个角色。
# gem capistrano-sidekiq 默认值 set :sidekiq_roles, fetch(:sidekiq_role, :app) # gem capistrano-sidekiq 默认值 set :sidekiq_roles, fetch(:sidekiq_role, :app)
# 因此会造成执行cap sidekiq:start,但是生产环境因为使用了god,不需要执行这个任务 # 因此会造成执行cap sidekiq:start,但是生产环境因为使用了god,不需要执行这个任务
set :sidekiq_roles, 'sidekiq' #生产环境没有这个角色。 set :sidekiq_roles, :sidekiq #生产环境没有这个角色。
set :puma_role, 'app' set :puma_role, 'app'
set :puma_env, fetch(:rack_env, fetch(:rails_env, 'production')) set :puma_env, fetch(:rack_env, fetch(:rails_env, 'production'))
set :sidekiq_default_hooks, true
set :pty, false
set :linked_files, %w[ set :linked_files, %w[
config/credentials.yml.enc config/credentials.yml.enc
...@@ -67,12 +69,15 @@ namespace :deploy do ...@@ -67,12 +69,15 @@ namespace :deploy do
end end
end end
desc 'Restart application' # desc 'Restart application'
task :restart do # task :restart do
on roles(:app), in: :sequence, wait: 5 do # on roles(:app), in: :sequence, wait: 5 do
invoke 'sidekiq:god_restart' # # invoke 'sidekiq:god_restart'
end # puts "restart sidekiq."
end # invoke 'sidekiq:restart'
# sleep 10
# end
# end
before :starting, :check_revision before :starting, :check_revision
after :finishing, :compile_assets after :finishing, :compile_assets
after :finishing, :restart after :finishing, :restart
......
...@@ -12,11 +12,13 @@ set :rails_env, :production ...@@ -12,11 +12,13 @@ set :rails_env, :production
set :port, 40022 set :port, 40022
set :repo_url, 'ssh://gitlab@gitlab.ikcrm.com:40022/ikcrm_server/app_push.git' set :repo_url, 'ssh://gitlab@gitlab.ikcrm.com:40022/ikcrm_server/app_push.git'
set :application, 'app_push' set :application, 'app_push'
set :rvm_ruby_version, '2.6.5'
set :deploy_to, deploy_config['deploy_to'] set :deploy_to, deploy_config['deploy_to']
set :shared_path, -> { fetch(:deploy_to) + '/shared' } set :shared_path, -> { fetch(:deploy_to) + '/shared' }
# Don't change these unless you know what you're doing # Don't change these unless you know what you're doing
set :pty, true set :pty, false
set :use_sudo, false set :use_sudo, false
set :stage, :production set :stage, :production
set :deploy_via, :remote_cache set :deploy_via, :remote_cache
......
...@@ -2,7 +2,7 @@ require 'json' ...@@ -2,7 +2,7 @@ require 'json'
file = File.read("config/server.json") file = File.read("config/server.json")
deploy_config = JSON.parse(file) deploy_config = JSON.parse(file)
branch = deploy_config['branch'] || 'master' branch = 'master'
set :branch, branch set :branch, branch
append :linked_files, 'config/server.json' append :linked_files, 'config/server.json'
...@@ -12,6 +12,7 @@ set :rails_env, :production ...@@ -12,6 +12,7 @@ set :rails_env, :production
set :port, 40022 set :port, 40022
set :repo_url, 'ssh://gitlab@gitlab.ikcrm.com:40022/ikcrm_server/app_push.git' set :repo_url, 'ssh://gitlab@gitlab.ikcrm.com:40022/ikcrm_server/app_push.git'
set :application, 'app_push' set :application, 'app_push'
set :rvm_ruby_version, '2.6.5'
set :deploy_to, deploy_config['deploy_to'] set :deploy_to, deploy_config['deploy_to']
set :shared_path, -> { fetch(:deploy_to) + '/shared' } set :shared_path, -> { fetch(:deploy_to) + '/shared' }
......
...@@ -12,6 +12,8 @@ set :rails_env, :production ...@@ -12,6 +12,8 @@ set :rails_env, :production
set :port, 40022 set :port, 40022
set :repo_url, 'ssh://gitlab@gitlab.ikcrm.com:40022/ikcrm_server/app_push.git' set :repo_url, 'ssh://gitlab@gitlab.ikcrm.com:40022/ikcrm_server/app_push.git'
set :application, 'app_push' set :application, 'app_push'
set :rvm_ruby_version, '2.6.5'
set :deploy_to, deploy_config['deploy_to'] set :deploy_to, deploy_config['deploy_to']
set :shared_path, -> { fetch(:deploy_to) + '/shared' } set :shared_path, -> { fetch(:deploy_to) + '/shared' }
......
...@@ -64,6 +64,7 @@ Rails.application.configure do ...@@ -64,6 +64,7 @@ Rails.application.configure do
# config.active_job.queue_name_prefix = "app_push_#{Rails.env}" # config.active_job.queue_name_prefix = "app_push_#{Rails.env}"
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
config.active_record.cache_versioning = false
# Ignore bad email addresses and do not raise email delivery errors. # Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
......
module Push module Log
module Log
class << self class << self
def info(info) def info(info, uuid= Random.uuid.to_s)
logger.tagged(Time.zone.now) { logger.info info } logger.tagged(uuid + " " + Time.zone.now.to_s) { logger.info info }
end end
def error(e) def error(e)
...@@ -16,5 +15,4 @@ module Push ...@@ -16,5 +15,4 @@ module Push
@logger ||= ActiveSupport::TaggedLogging.new(::Logger.new("#{Rails.root}/log/various_push.log", 'weekly')) @logger ||= ActiveSupport::TaggedLogging.new(::Logger.new("#{Rails.root}/log/various_push.log", 'weekly'))
end end
end end
end
end end
\ No newline at end of file
$ik_duli_android_pusher ||=
IGeTui.pusher(
Settings.ik_duli.igetui.android['app_id'],
Settings.ik_duli.igetui.android['app_key'],
Settings.ik_duli.igetui.android['master_secret']
)
$ik_duli_ios_pusher ||=
IGeTui.pusher(
Settings.ik_duli.igetui.ios['app_id'],
Settings.ik_duli.igetui.ios['app_key'],
Settings.ik_duli.igetui.ios['master_secret']
)
$lx_duli_android_pusher ||=
IGeTui.pusher(
Settings.lx_duli.igetui.android['app_id'],
Settings.lx_duli.igetui.android['app_key'],
Settings.lx_duli.igetui.android['master_secret']
)
$lx_duli_ios_pusher ||=
IGeTui.pusher(
Settings.lx_duli.igetui.ios['app_id'],
Settings.lx_duli.igetui.ios['app_key'],
Settings.lx_duli.igetui.ios['master_secret']
)
$lx_yun_android_pusher ||=
IGeTui.pusher(
Settings.lx_yun.igetui.android['app_id'],
Settings.lx_yun.igetui.android['app_key'],
Settings.lx_yun.igetui.android['master_secret']
)
$lx_yun_ios_pusher ||=
IGeTui.pusher(
Settings.lx_yun.igetui.ios['app_id'],
Settings.lx_yun.igetui.ios['app_key'],
Settings.lx_yun.igetui.ios['master_secret']
)
$aike_yun_android_pusher ||=
IGeTui.pusher(
Settings.aike_yun.igetui.android['app_id'],
Settings.aike_yun.igetui.android['app_key'],
Settings.aike_yun.igetui.android['master_secret']
)
$aike_yun_ios_pusher ||=
IGeTui.pusher(
Settings.aike_yun.igetui.ios['app_id'],
Settings.aike_yun.igetui.ios['app_key'],
Settings.aike_yun.igetui.ios['master_secret']
)
require 'json' unless ENV['RAILS_ENV'] == 'development'
file = File.read('config/server.json')
file = File.read('config/server.json') deploy_config = JSON.parse(file)
deploy_config = JSON.parse(file) port deploy_config['http_port']
port deploy_config['http_port'] environment 'production'
environment 'production' workers 4
workers 2 threads 0, 16
threads 0, 16 bind "unix://#{deploy_config['deploy_to']}/shared/tmp/sockets/push-puma.sock"
bind "unix://#{deploy_config['deploy_to']}/shared/tmp/sockets/push-puma.sock" pidfile "#{deploy_config['deploy_to']}/shared/tmp/pids/puma.pid"
pidfile "#{deploy_config['deploy_to']}/shared/tmp/pids/puma.pid" state_path "#{deploy_config['deploy_to']}/shared/tmp/pids/puma.state"
state_path "#{deploy_config['deploy_to']}/shared/tmp/pids/puma.state" stdout_redirect "#{deploy_config['deploy_to']}/current/log/puma_error.log", "#{deploy_config['deploy_to']}/current/log/puma_access.log", true
stdout_redirect "#{deploy_config['deploy_to']}/current/log/puma_error.log", "#{deploy_config['deploy_to']}/current/log/puma_access.log", true plugin :tmp_restart
plugin :tmp_restart else
port 8094
environment 'development'
workers 1
threads 0, 16
bind "unix://tmp/sockets/push-puma.sock"
pidfile "tmp/pids/puma.pid"
state_path "tmp/pids/puma.state"
plugin :tmp_restart
worker_timeout 7200
end
Rails.application.routes.draw do Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
post 'api/v1/token', to: 'pushs#token' post 'api/v1/token', to: 'pushs#token'
post 'api/v1/push', to: 'pushs#push'
put 'api/v1/received', to: 'pushs#received' put 'api/v1/received', to: 'pushs#received'
post 'api/v1/push_v2', to: 'pushs#push_v2'
post 'api/v1/push', to: 'pushs#push'
require 'sidekiq/web' require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq' mount Sidekiq::Web => '/sidekiq'
Sidekiq::Web.use Rack::Auth::Basic do |username, password| Sidekiq::Web.use Rack::Auth::Basic do |username, password|
username == "sidekiqadmin" && password == "5529d99a" username == "sidekiqadmin" && password == "5529d99a"
end if Rails.env.production? end if Rails.env.production?
resources :user_devices do
collection do
post :update_by_user_id
post :destroy_by_user_id
end
end
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment