mirror of
https://gitlink.org.cn/Gitlink/forgeplus.git
synced 2026-05-03 03:33:05 +08:00
init project
This commit is contained in:
77
app/libs/base64_image_converter.rb
Normal file
77
app/libs/base64_image_converter.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
class Base64ImageConverter
|
||||
BASE64_HEAD = 'data:image/jpeg;base64,'.freeze
|
||||
|
||||
Error = Class.new(StandardError)
|
||||
OutLimit = Class.new(Error)
|
||||
InvalidData = Class.new(Error)
|
||||
InvalidFormat = Class.new(Error)
|
||||
|
||||
attr_reader :opts
|
||||
|
||||
def initialize(**opts)
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def convert(data)
|
||||
raise InvalidData, '不合法的Base64数据' unless valid_base64?(data)
|
||||
|
||||
io = StringIO.new(Base64.decode64(image_data data))
|
||||
|
||||
raise OutLimit, '文件大小超过限制' if opts[:max_size].present? && io.size > opts[:max_size]
|
||||
|
||||
raise InvalidFormat, '无效的格式' unless Image.new(io).image?
|
||||
|
||||
io
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_base64?(data)
|
||||
data&.start_with?(BASE64_HEAD)
|
||||
end
|
||||
|
||||
def image_data(data)
|
||||
data[BASE64_HEAD.size..-1]
|
||||
end
|
||||
|
||||
def size_limit
|
||||
EduSetting.get('upload_avatar_max_size')
|
||||
end
|
||||
|
||||
class Image
|
||||
attr_reader :io
|
||||
|
||||
def initialize(io)
|
||||
raise ArgumentError unless io.respond_to?(:read)
|
||||
@io = io
|
||||
end
|
||||
|
||||
def data
|
||||
@_data ||= begin
|
||||
data = io.read(9)
|
||||
io.rewind
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def image?
|
||||
bitmap? || gif? || jpeg? || png?
|
||||
end
|
||||
|
||||
def bitmap?
|
||||
data[0,2] == 66.chr + 77.chr
|
||||
end
|
||||
|
||||
def gif?
|
||||
data[0,4] == 71.chr + 73.chr + 70.chr + 56.chr
|
||||
end
|
||||
|
||||
def jpeg?
|
||||
data[0,3] == 0xff.chr + 0xd8.chr + 0xff.chr
|
||||
end
|
||||
|
||||
def png?
|
||||
data[0,2] == 0x89.chr + 80.chr
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/libs/custom_regexp.rb
Normal file
8
app/libs/custom_regexp.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
module CustomRegexp
|
||||
PHONE = /1\d{10}/
|
||||
EMAIL = /\A[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/
|
||||
LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/
|
||||
NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/
|
||||
PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/
|
||||
URL = /\Ahttps?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]\z/
|
||||
end
|
||||
45
app/libs/custom_sortable.rb
Normal file
45
app/libs/custom_sortable.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
module CustomSortable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do |base|
|
||||
base.instance_variable_set("@_sort_options", {})
|
||||
base.instance_variable_set("@_sort_columns", [])
|
||||
base.instance_variable_set("@_sort_directions", %w(asc desc))
|
||||
end
|
||||
|
||||
def custom_sort(relations, sort_by, sort_direction)
|
||||
sort_by = self.class.sort_options[:default_by] if sort_by.blank?
|
||||
sort_direction = self.class.sort_options[:default_direction] if sort_direction.blank?
|
||||
|
||||
return relations unless self.class.check_sort_parameter_validate(sort_by.to_s, sort_direction.to_s)
|
||||
|
||||
order_method = self.class.sort_options[:reorder] ? :reorder : :order
|
||||
|
||||
default_table = self.class.sort_options[:default_table]
|
||||
if default_table.present?
|
||||
relations.send(order_method, "#{default_table}.#{sort_by} #{sort_direction}")
|
||||
else
|
||||
relations.send(order_method, "#{sort_by} #{sort_direction}")
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def sort_columns(*columns)
|
||||
opts = columns.extract_options!
|
||||
@_sort_options[:default_by] = opts[:default_by].to_s
|
||||
@_sort_options[:default_direction] = opts[:default_direction].to_s
|
||||
@_sort_options[:reorder] = opts[:reorder]
|
||||
@_sort_options[:default_table] = opts[:default_table]
|
||||
|
||||
@_sort_columns = columns.map(&:to_s)
|
||||
end
|
||||
|
||||
def check_sort_parameter_validate(sort_by, sort_direction)
|
||||
(sort_by.blank? || @_sort_columns.include?(sort_by)) && @_sort_directions.include?(sort_direction)
|
||||
end
|
||||
|
||||
def sort_options
|
||||
@_sort_options
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/libs/gitea.rb
Normal file
20
app/libs/gitea.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module Gitea
|
||||
class << self
|
||||
def gitea_config
|
||||
gitea_config = {}
|
||||
|
||||
begin
|
||||
config = Rails.application.config_for(:configuration).symbolize_keys!
|
||||
gitea_config = config[:gitea].symbolize_keys!
|
||||
raise 'gitea config missing' if gitea_config.blank?
|
||||
rescue => ex
|
||||
raise ex if Rails.env.production?
|
||||
|
||||
puts %Q{\033[33m [warning] gitea config or configuration.yml missing,
|
||||
please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
|
||||
gitea_config = {}
|
||||
end
|
||||
gitea_config
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/libs/hot_search_keyword.rb
Normal file
24
app/libs/hot_search_keyword.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# 基于Redis实现热门搜索关键字
|
||||
class HotSearchKeyword
|
||||
class << self
|
||||
def add(keyword)
|
||||
return if keyword.blank?
|
||||
Rails.logger.info("[Hot Keyword] #{keyword} score increment ~")
|
||||
Rails.cache.data.zincrby(redis_key, 1, keyword)
|
||||
end
|
||||
|
||||
def hot(limit = 5)
|
||||
Rails.cache.data.zrevrange(redis_key, 0, limit - 1)
|
||||
end
|
||||
|
||||
def available?
|
||||
Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redis_key
|
||||
'educoder:es:hot_keyword'
|
||||
end
|
||||
end
|
||||
end
|
||||
2
app/libs/limit_forbid_control.rb
Normal file
2
app/libs/limit_forbid_control.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module LimitForbidControl
|
||||
end
|
||||
56
app/libs/omniauth/strategies/qq.rb
Normal file
56
app/libs/omniauth/strategies/qq.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class QQ < OmniAuth::Strategies::OAuth2
|
||||
option :client_options, {
|
||||
site: 'https://graph.qq.com',
|
||||
authorize_url: '/oauth2.0/authorize',
|
||||
token_url: '/oauth2.0/token'
|
||||
}
|
||||
option :token_params, { parse: :query }
|
||||
|
||||
def request_phase
|
||||
super
|
||||
end
|
||||
|
||||
def authorize_params
|
||||
super.tap do |params|
|
||||
%w[scope client_options].each do |v|
|
||||
if request.params[v]
|
||||
params[v.to_sym] = request.params[v]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uid do
|
||||
@uid ||= begin
|
||||
access_token.options[:mode] = :query
|
||||
access_token.options[:param_name] = :access_token
|
||||
# Response Example: "callback( {\"client_id\":\"11111\",\"openid\":\"000000FFFF\"} );\n"
|
||||
response = access_token.get('/oauth2.0/me')
|
||||
|
||||
matched = response.body.match(/"openid":"(?<openid>\w+)"/)
|
||||
matched[:openid]
|
||||
end
|
||||
end
|
||||
|
||||
info do
|
||||
{
|
||||
name: user_info['nickname'],
|
||||
nickname: user_info['nickname'],
|
||||
image: user_info['figureurl_qq_1']
|
||||
}
|
||||
end
|
||||
|
||||
extra do
|
||||
{ raw_info: user_info }
|
||||
end
|
||||
|
||||
def user_info
|
||||
access_token.options[:mode] = :query
|
||||
param = { oauth_consumer_key: options[:client_id], openid: uid, format: 'json' }
|
||||
@user_info ||= access_token.get('/user/get_user_info', params: param, parse: :json).parsed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/libs/sidebar_util.rb
Normal file
11
app/libs/sidebar_util.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class SidebarUtil
|
||||
class << self
|
||||
def controller_name(name)
|
||||
sidebar_controller_map[name]
|
||||
end
|
||||
|
||||
def sidebar_controller_map
|
||||
@_sidebar_controller_map ||= YAML.load_file(Rails.root.join('config/admins', 'sidebar.yml'))
|
||||
end
|
||||
end
|
||||
end
|
||||
83
app/libs/util.rb
Normal file
83
app/libs/util.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
require 'open-uri'
|
||||
|
||||
module Util
|
||||
module_function
|
||||
|
||||
def days_between(time, other_time)
|
||||
raise ArgumentError if time.blank? || other_time.blank?
|
||||
Date.parse(time.to_s) - Date.parse(other_time.to_s)
|
||||
end
|
||||
|
||||
def convert_base64_image(str, **opts)
|
||||
return if str.blank?
|
||||
|
||||
Base64ImageConverter.new(**opts).convert(str)
|
||||
end
|
||||
|
||||
def write_file(io, path)
|
||||
dir = File.dirname(path)
|
||||
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
||||
|
||||
Rails.logger.info("### save file #{path}, size: #{io.size} ~")
|
||||
File.open(path, 'wb') do |file|
|
||||
if io.respond_to?(:read)
|
||||
io.rewind
|
||||
while buffer = io.read(8192)
|
||||
file.write(buffer)
|
||||
end
|
||||
else
|
||||
file.write(io)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def download_file(url, save_path)
|
||||
data = open(url, &:read)
|
||||
file = File.new(save_path, 'w+')
|
||||
file.binmode
|
||||
file << data
|
||||
file.flush
|
||||
file.close
|
||||
file
|
||||
end
|
||||
|
||||
def logger_error(exception)
|
||||
Rails.logger.error(exception.message)
|
||||
exception.backtrace.each { |message| Rails.logger.error(message) }
|
||||
end
|
||||
|
||||
def map_or_pluck(relation, name)
|
||||
relation.is_a?(Array) || relation.loaded? ? relation.map(&name.to_sym) : relation.pluck(name)
|
||||
end
|
||||
|
||||
def extract_content(str)
|
||||
return '' if str.blank?
|
||||
str.gsub(/<\/?.*?>/, '').gsub(/[\n\t\r]/, '').gsub(/ /, '')
|
||||
end
|
||||
|
||||
def conceal(str, type = nil)
|
||||
str = str.to_s
|
||||
return if str.blank?
|
||||
|
||||
case type
|
||||
when :phone then "#{str[0..2]}****#{str[-4..-1]}"
|
||||
when :email then "#{str[0]}***#{str[(str.rindex('@')-1)..-1]}"
|
||||
else "#{str[0..2]}***#{str[-3..-1]}"
|
||||
end
|
||||
end
|
||||
|
||||
def display_cost_time(time)
|
||||
time = time.to_i
|
||||
return if time.zero? || time < 60
|
||||
|
||||
day = time / (24 * 60 * 60)
|
||||
hour = (time % (24 * 60 * 60)) / (60 * 60)
|
||||
minute = (time % (60 * 60)) / 60
|
||||
|
||||
str = ''
|
||||
str += "#{day}天" unless day.zero?
|
||||
str += "#{hour}小时" unless hour.zero?
|
||||
str += "#{minute}分" unless minute.zero?
|
||||
str
|
||||
end
|
||||
end
|
||||
67
app/libs/util/file_manage.rb
Normal file
67
app/libs/util/file_manage.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
module Util::FileManage
|
||||
module_function
|
||||
|
||||
# 不同的类型扩展不同的目录
|
||||
def relative_path
|
||||
"avatars"
|
||||
end
|
||||
|
||||
def storage_path
|
||||
File.join(Rails.root, "public", "images", relative_path)
|
||||
end
|
||||
|
||||
def disk_filename(source_type, source_id, suffix=nil)
|
||||
File.join(storage_path, "#{source_type}", "#{source_id}#{suffix}")
|
||||
end
|
||||
|
||||
def source_disk_filename(source, suffix=nil)
|
||||
disk_filename(source.class.name, source.id, suffix)
|
||||
end
|
||||
|
||||
def exist?(source_type, source_id, suffix=nil)
|
||||
File.exist?(disk_filename(source_type, source_id, suffix))
|
||||
end
|
||||
|
||||
def exists?(source, suffix=nil)
|
||||
File.exist?(disk_filename(source.class, source.id, suffix))
|
||||
end
|
||||
|
||||
def disk_file_url(source_type, source_id, suffix = nil)
|
||||
t = ctime(source_type, source_id, suffix)
|
||||
File.join('/images', relative_path, "#{source_type}", "#{source_id}#{suffix}") + "?t=#{t}"
|
||||
end
|
||||
|
||||
def source_disk_file_url(source, suffix=nil)
|
||||
disk_file_url(source.class, source.id, suffix)
|
||||
end
|
||||
|
||||
def ctime(source_type, source_id, suffix)
|
||||
return nil unless exist?(source_type, source_id, suffix)
|
||||
|
||||
File.ctime(disk_filename(source_type, source_id, suffix)).to_i
|
||||
end
|
||||
|
||||
def disk_auth_filename(source_type, source_id, type)
|
||||
File.join(storage_path, "#{source_type}", "#{source_id}#{type}")
|
||||
end
|
||||
|
||||
def disk_real_name_auth_filename(source_id)
|
||||
disk_auth_filename('UserAuthentication', source_id, 'ID')
|
||||
end
|
||||
|
||||
def auth_file_url(source_type, source_id, type)
|
||||
disk_file_url(source_type, source_id, type)
|
||||
end
|
||||
|
||||
def real_name_auth_file_url(source_id)
|
||||
auth_file_url('UserAuthentication', source_id, 'ID')
|
||||
end
|
||||
|
||||
def disk_professional_auth_filename(source_id)
|
||||
disk_auth_filename('UserAuthentication', source_id, 'PRO')
|
||||
end
|
||||
|
||||
def professional_auth_file_url(source_id)
|
||||
auth_file_url('UserAuthentication', source_id, 'PRO')
|
||||
end
|
||||
end
|
||||
9
app/libs/util/redis.rb
Normal file
9
app/libs/util/redis.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Util::Redis
|
||||
class << self
|
||||
def online_user_count
|
||||
if Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
|
||||
Rails.cache.data.scan(0, match: 'cache:_session_id:*', count: 100000).last.uniq.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/libs/util/uuid.rb
Normal file
27
app/libs/util/uuid.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
module Util
|
||||
module UUID
|
||||
module_function
|
||||
|
||||
DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
|
||||
|
||||
def time_uuid(format: '%Y%m%d%H%M%S', suffix: 8)
|
||||
"#{Time.zone.now.strftime(format)}#{Random.rand(10**suffix).to_i}"
|
||||
end
|
||||
|
||||
# 随机生成字符
|
||||
def generate_identifier(container, num, pre='')
|
||||
code = DCODES.sample(num).join
|
||||
if container == User
|
||||
while container.exists?(login: pre+code) do
|
||||
code = DCODES.sample(num).join
|
||||
end
|
||||
else
|
||||
while container.exists?(identifier: code) do
|
||||
code = DCODES.sample(num).join
|
||||
end
|
||||
end
|
||||
code
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
2
app/libs/wechat.rb
Normal file
2
app/libs/wechat.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Wechat
|
||||
end
|
||||
74
app/libs/wechat/client.rb
Normal file
74
app/libs/wechat/client.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
class Wechat::Client
|
||||
BASE_SITE = 'https://api.weixin.qq.com'.freeze
|
||||
|
||||
attr_reader :appid, :secret
|
||||
|
||||
def initialize(appid, secret)
|
||||
@appid = appid
|
||||
@secret = secret
|
||||
end
|
||||
|
||||
def access_token
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
|
||||
result['access_token']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_access_token
|
||||
Rails.cache.delete(access_token_cache_key)
|
||||
access_token
|
||||
end
|
||||
|
||||
def jsapi_ticket
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
|
||||
result['ticket']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_jsapi_ticket
|
||||
Rails.cache.delete(jsapi_ticket_cache_key)
|
||||
jsapi_ticket
|
||||
end
|
||||
|
||||
def jscode2session(code)
|
||||
request(:get, '/sns/jscode2session', appid: appid, secret: secret, js_code: code, grant_type: 'authorization_code')
|
||||
end
|
||||
|
||||
def access_token_cache_key
|
||||
"#{base_cache_key}/access_token"
|
||||
end
|
||||
|
||||
def jsapi_ticket_cache_key
|
||||
"#{base_cache_key}/jsapi_ticket"
|
||||
end
|
||||
|
||||
def base_cache_key
|
||||
"wechat/#{appid}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, url, **params)
|
||||
Rails.logger.info("[wechat] request: #{method} #{url} #{params.except(:secret).inspect}")
|
||||
|
||||
client = Faraday.new(url: BASE_SITE)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
Rails.logger.info("[wechat] response:#{response.status} #{result.inspect}")
|
||||
|
||||
if response.status != 200
|
||||
raise Wechat::Error.parse(result)
|
||||
end
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_i.nonzero?
|
||||
raise Wechat::Error.parse(result)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
14
app/libs/wechat/error.rb
Normal file
14
app/libs/wechat/error.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class Wechat::Error < StandardError
|
||||
attr_reader :code
|
||||
|
||||
def initialize(code, message)
|
||||
super message
|
||||
@code = code
|
||||
end
|
||||
|
||||
class << self
|
||||
def parse(result)
|
||||
new(result['errcode'], result['errmsg'])
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/libs/wechat/official_account.rb
Normal file
17
app/libs/wechat/official_account.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class Wechat::OfficialAccount
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jsapi_ticket, to: :client
|
||||
|
||||
def js_sdk_signature(url, noncestr, timestamp)
|
||||
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
|
||||
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
|
||||
Digest::SHA1.hexdigest(str)
|
||||
end
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
end
|
||||
end
|
||||
49
app/libs/wechat/weapp.rb
Normal file
49
app/libs/wechat/weapp.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
class Wechat::Weapp
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jscode2session, to: :client
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
|
||||
def session_key(openid)
|
||||
Rails.cache.read(session_key_cache_key(openid))
|
||||
end
|
||||
|
||||
def write_session_key(openid, session_key)
|
||||
Rails.cache.write(session_key_cache_key(openid), session_key)
|
||||
end
|
||||
|
||||
def verify?(openid, str, signature)
|
||||
session_key = session_key(openid)
|
||||
Digest::SHA1.hexdigest("#{str}#{session_key}") == signature
|
||||
end
|
||||
|
||||
def decrypt(session_key, encrypted_data, iv)
|
||||
session_key = Base64.decode64(session_key)
|
||||
encrypted_data = Base64.decode64(encrypted_data)
|
||||
iv = Base64.decode64(iv)
|
||||
|
||||
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
|
||||
cipher.decrypt
|
||||
cipher.padding = 0
|
||||
cipher.key = session_key
|
||||
cipher.iv = iv
|
||||
Rails.logger.info("[Weapp] encrypted_data: #{encrypted_data}")
|
||||
data = cipher.update(encrypted_data) << cipher.final
|
||||
Rails.logger.info("[Weapp] data: #{data}")
|
||||
result = JSON.parse(data[0...-data.last.ord])
|
||||
|
||||
raise Wechat::Error.new(-1, '解密错误') if result.dig('watermark', 'appid') != appid
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def session_key_cache_key(openid)
|
||||
"weapp:#{appid}:#{openid}:session_key"
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/libs/wechat_oauth.rb
Normal file
13
app/libs/wechat_oauth.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module WechatOauth
|
||||
class << self
|
||||
attr_accessor :appid, :secret, :scope, :base_url
|
||||
|
||||
def logger
|
||||
@_logger ||= STDOUT
|
||||
end
|
||||
|
||||
def logger=(l)
|
||||
@_logger = l
|
||||
end
|
||||
end
|
||||
end
|
||||
14
app/libs/wechat_oauth/error.rb
Normal file
14
app/libs/wechat_oauth/error.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class WechatOauth::Error < StandardError
|
||||
attr_reader :code
|
||||
|
||||
def initialize(code, msg)
|
||||
super(msg)
|
||||
@code = code
|
||||
end
|
||||
|
||||
def message
|
||||
I18n.t("oauth.wechat.#{code}")
|
||||
rescue I18n::MissingTranslationData
|
||||
super
|
||||
end
|
||||
end
|
||||
61
app/libs/wechat_oauth/service.rb
Normal file
61
app/libs/wechat_oauth/service.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
module WechatOauth::Service
|
||||
module_function
|
||||
|
||||
def request(method, url, params)
|
||||
WechatOauth.logger.info("[WechatOauth] [#{method.to_s.upcase}] #{url} || #{params}")
|
||||
|
||||
client = Faraday.new(url: WechatOauth.base_url)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
WechatOauth.logger.info("[WechatOauth] [#{response.status}] #{result}")
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_s != '0'
|
||||
raise WechatOauth::Error.new(result['errcode'], result['errmsg'])
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
# response:
|
||||
# {
|
||||
# "access_token":"ACCESS_TOKEN",
|
||||
# "expires_in":7200,
|
||||
# "refresh_token":"REFRESH_TOKEN",
|
||||
# "openid":"OPENID",
|
||||
# "scope":"SCOPE",
|
||||
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
||||
# }
|
||||
def access_token(code)
|
||||
params = {
|
||||
appid: WechatOauth.appid,
|
||||
secret: WechatOauth.secret,
|
||||
code: code,
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
|
||||
request(:get, '/sns/oauth2/access_token', params)
|
||||
end
|
||||
|
||||
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
|
||||
# response:
|
||||
# {
|
||||
# "openid":"OPENID",
|
||||
# "nickname":"NICKNAME",
|
||||
# "sex":1,
|
||||
# "province":"PROVINCE",
|
||||
# "city":"CITY",
|
||||
# "country":"COUNTRY",
|
||||
# "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
|
||||
# "privilege":[
|
||||
# "PRIVILEGE1",
|
||||
# "PRIVILEGE2"
|
||||
# ],
|
||||
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
||||
#
|
||||
# }
|
||||
def user_info(access_token, openid)
|
||||
request(:get, '/sns/userinfo', access_token: access_token, openid: openid)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user