mirror of
https://gitlink.org.cn/Gitlink/forgeplus.git
synced 2026-05-03 11:50:49 +08:00
init project
This commit is contained in:
167
lib/educoder/i18n.rb
Normal file
167
lib/educoder/i18n.rb
Normal file
@@ -0,0 +1,167 @@
|
||||
module Educoder
|
||||
module I18n
|
||||
def self.included(base)
|
||||
base.extend Educoder::I18n
|
||||
end
|
||||
|
||||
def l(*args)
|
||||
case args.size
|
||||
when 1
|
||||
::I18n.t(*args)
|
||||
when 2
|
||||
if args.last.is_a?(Hash)
|
||||
::I18n.t(*args)
|
||||
elsif args.last.is_a?(String)
|
||||
::I18n.t(args.first, :value => args.last)
|
||||
else
|
||||
::I18n.t(args.first, :count => args.last)
|
||||
end
|
||||
else
|
||||
raise "Translation string with multiple values: #{args.first}"
|
||||
end
|
||||
end
|
||||
|
||||
def l_or_humanize(s, options={})
|
||||
k = "#{options[:prefix]}#{s}".to_sym
|
||||
::I18n.t(k, :default => s.to_s.humanize)
|
||||
end
|
||||
|
||||
def l_hours(hours)
|
||||
hours = hours.to_f
|
||||
l((hours < 2.0 ? :label_f_hour : :label_f_hour_plural), :value => ("%.2f" % hours.to_f))
|
||||
end
|
||||
|
||||
def ll(lang, str, value=nil)
|
||||
::I18n.t(str.to_s, :value => value, :locale => lang.to_s.gsub(%r{(.+)\-(.+)$}) { "#{$1}-#{$2.upcase}" })
|
||||
end
|
||||
|
||||
def format_date(date)
|
||||
return nil unless date
|
||||
options = {}
|
||||
options[:format] = Setting.date_format unless Setting.date_format.blank?
|
||||
options[:locale] = User.current.language unless User.current.language.blank?
|
||||
::I18n.l(date.to_date, options)
|
||||
end
|
||||
|
||||
def format_time(time, include_date = true)
|
||||
return nil unless time
|
||||
options = {}
|
||||
options[:format] = (Setting.time_format.blank? ? :time : Setting.time_format)
|
||||
options[:locale] = User.current.language unless User.current.language.blank?
|
||||
time = time.to_time if time.is_a?(String)
|
||||
zone = User.current.time_zone
|
||||
local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
|
||||
(include_date ? "#{format_date(local)} " : "") + ::I18n.l(local, options)
|
||||
end
|
||||
|
||||
def day_name(day)
|
||||
::I18n.t('date.day_names')[day % 7]
|
||||
end
|
||||
|
||||
def day_letter(day)
|
||||
::I18n.t('date.abbr_day_names')[day % 7].first
|
||||
end
|
||||
|
||||
def month_name(month)
|
||||
::I18n.t('date.month_names')[month]
|
||||
end
|
||||
|
||||
def valid_languages
|
||||
::I18n.available_locales
|
||||
end
|
||||
|
||||
# Returns an array of languages names and code sorted by names, example:
|
||||
# [["Deutsch", "de"], ["English", "en"] ...]
|
||||
#
|
||||
# The result is cached to prevent from loading all translations files.
|
||||
def languages_options
|
||||
ActionController::Base.cache_store.fetch "i18n/languages_options" do
|
||||
valid_languages.map {|lang| [ll(lang.to_s, :general_lang_name), lang.to_s]}.sort {|x,y| x.first <=> y.first }
|
||||
end
|
||||
end
|
||||
|
||||
def find_language(lang)
|
||||
@@languages_lookup = valid_languages.inject({}) {|k, v| k[v.to_s.downcase] = v; k }
|
||||
@@languages_lookup[lang.to_s.downcase]
|
||||
end
|
||||
|
||||
def set_language_if_valid(lang)
|
||||
if l = find_language(lang)
|
||||
::I18n.locale = l
|
||||
end
|
||||
end
|
||||
|
||||
def current_language
|
||||
::I18n.locale
|
||||
end
|
||||
|
||||
# Custom backend based on I18n::Backend::Simple with the following changes:
|
||||
# * lazy loading of translation files
|
||||
# * available_locales are determined by looking at translation file names
|
||||
class Backend
|
||||
(class << self; self; end).class_eval { public :include }
|
||||
|
||||
module Implementation
|
||||
include ::I18n::Backend::Base
|
||||
|
||||
# Stores translations for the given locale in memory.
|
||||
# This uses a deep merge for the translations hash, so existing
|
||||
# translations will be overwritten by new ones only at the deepest
|
||||
# level of the hash.
|
||||
def store_translations(locale, data, options = {})
|
||||
locale = locale.to_sym
|
||||
translations[locale] ||= {}
|
||||
data = data.deep_symbolize_keys
|
||||
translations[locale].deep_merge!(data)
|
||||
end
|
||||
|
||||
# Get available locales from the translations filenames
|
||||
def available_locales
|
||||
@available_locales ||= ::I18n.load_path.map {|path| File.basename(path, '.*')}.uniq.sort.map(&:to_sym)
|
||||
end
|
||||
|
||||
# Clean up translations
|
||||
def reload!
|
||||
@translations = nil
|
||||
@available_locales = nil
|
||||
super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def init_translations(locale)
|
||||
locale = locale.to_s
|
||||
paths = ::I18n.load_path.select {|path| File.basename(path, '.*') == locale}
|
||||
load_translations(paths)
|
||||
translations[locale] ||= {}
|
||||
end
|
||||
|
||||
def translations
|
||||
@translations ||= {}
|
||||
end
|
||||
|
||||
# Looks up a translation from the translations hash. Returns nil if
|
||||
# eiher key is nil, or locale, scope or key do not exist as a key in the
|
||||
# nested translations hash. Splits keys or scopes containing dots
|
||||
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
|
||||
# <tt>%w(currency format)</tt>.
|
||||
def lookup(locale, key, scope = [], options = {})
|
||||
init_translations(locale) unless translations.key?(locale)
|
||||
keys = ::I18n.normalize_keys(locale, key, scope, options[:separator])
|
||||
|
||||
keys.inject(translations) do |result, _key|
|
||||
_key = _key.to_sym
|
||||
return nil unless result.is_a?(Hash) && result.has_key?(_key)
|
||||
result = result[_key]
|
||||
result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include Implementation
|
||||
# Adds fallback to default locale for untranslated strings
|
||||
include ::I18n::Backend::Fallbacks
|
||||
end
|
||||
end
|
||||
end
|
||||
75
lib/educoder/sms.rb
Normal file
75
lib/educoder/sms.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
#coding=utf-8
|
||||
|
||||
require 'net/https'
|
||||
require 'uri'
|
||||
|
||||
module Educoder
|
||||
module Sms
|
||||
def self.send(opt={})
|
||||
Rails.logger.info "#{opt[:mobile]} - #{opt[:code]}"
|
||||
begin
|
||||
o = sendYunpian(opt[:mobile], opt[:code], opt[:send_type], opt[:name], opt[:user_name], opt[:result])
|
||||
if o["code"] != 0
|
||||
Rails.logger.error "发送短信出错: #{o['code']}--#{o['msg']}"
|
||||
end
|
||||
return o["code"]
|
||||
rescue => e
|
||||
Rails.logger.error "发送短信出错: #{e}"
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def self.notify_admin(opt)
|
||||
opt[:name] = '管理员'
|
||||
opt[:mobile] = ENV['NOTIFY_ADMIN_PHONE'] || EduSetting.get('notify_admin_phone') || '18711085785'
|
||||
send(opt)
|
||||
end
|
||||
|
||||
def self.sendYunpian(mobile, code, send_type, name, user_name, result)
|
||||
#修改为您的apikey.可在官网(http://www.yunpian.com)登录后用户中心首页看到
|
||||
apikey = EduSetting.get('sms_apikey')
|
||||
#指定模板发送接口HTTP地址
|
||||
send_tpl_sms_uri = URI.parse('https://sms.yunpian.com/v2/sms/single_send.json')
|
||||
|
||||
params = {}
|
||||
params['apikey'] = apikey
|
||||
params['mobile'] = mobile
|
||||
params['text'] = ""
|
||||
if send_type.nil?
|
||||
params['text'] = "【Edu实训】" + code + "(手机验证码),有效期为10分钟。如非本人操作,请忽略。"
|
||||
elsif send_type == 'competition_start'
|
||||
params['text'] = "【Edu实训】亲爱的#{user_name},你参与的#{name}将于#{result}开始,请及时参赛"
|
||||
elsif send_type == "teacher_register"
|
||||
params['mobile'] = EduSetting.get('teacher_register_phone') || '17680641960'
|
||||
params['text'] = "【Edu实训】亲爱的#{user_name},有新的老师#{name}注册啦,请尽快处理"
|
||||
elsif send_type == 'subject_authorization' || send_type == 'shixun_authorization'
|
||||
params['text'] = "【Edu实训】亲爱的#{user_name},您提交的#{name}#{send_type=='subject_authorization'?'实训路径':'实训'}发布申请#{result},请登录平台查看详情"
|
||||
elsif send_type == 'authentication_pro' || send_type == 'authentication'|| send_type == 'trial_authorization' || send_type == 'project_info'
|
||||
params['text'] = "【Edu实训】亲爱的#{user_name},您提交的#{send_type == 'authentication_pro'?'职业认证':(send_type == 'authentication'? '实名认证' : (send_type == 'project_info'?'加入申请':'试用申请' ))}#{result},请登录平台查看详情"
|
||||
elsif send_type == "apply_pro_certification" || send_type == "apply_auth"
|
||||
params['text'] = "【Edu实训】亲爱的#{name},有新的#{send_type == 'apply_pro_certification'?'职业':'实名'}认证申请,请尽快处理"
|
||||
elsif send_type == "publish_subject" ||send_type == "publish_shixun"|| send_type == "user_apply_auth" || send_type == "discuss"
|
||||
params['mobile'] = EduSetting.get('subject_shixun_notify_phone') || '18711011226' if send_type == "publish_subject" || send_type == "publish_shixun"
|
||||
params['text'] = "【Edu实训】亲爱的#{name},有新的#{send_type == 'publish_subject'?'实训路径':(send_type == 'publish_shixun' ? '实训' : (send_type == 'discuss' ? '实训评论':'试用'))}申请发布,请尽快处理"
|
||||
elsif send_type == 'join_course_multi_role'
|
||||
params['text'] = "【Edu实训】亲爱的#{user_name},您的课堂#{name}有助教或者教师申请加入,请尽快审核"
|
||||
elsif send_type == 'applied_project_info'
|
||||
params['text'] = "【Edu实训】亲爱的#{user_name},您的项目#{name}有成员申请加入,请尽快审核"
|
||||
end
|
||||
|
||||
http = Net::HTTP.new(send_tpl_sms_uri.host, send_tpl_sms_uri.port)
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
http.use_ssl = true
|
||||
begin
|
||||
request = Net::HTTP::Post.new(send_tpl_sms_uri.request_uri)
|
||||
request.set_form_data(params)
|
||||
request['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
|
||||
response = http.start { |http| http.request(request) }
|
||||
ActiveSupport::JSON.decode(response.body)
|
||||
rescue =>err
|
||||
Rails.logger.error("#############sendYunpian_error: #{err.message}")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
lib/educoder/tip_exception.rb
Normal file
24
lib/educoder/tip_exception.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
module Educoder
|
||||
|
||||
class TipException < StandardError
|
||||
attr_reader :status, :message
|
||||
|
||||
def initialize(status=-1, message)
|
||||
case status
|
||||
when 403
|
||||
message = "您没有权限进行该操作"
|
||||
when 404
|
||||
message = "您访问的页面不存在或已被删除"
|
||||
end
|
||||
@status = status
|
||||
@message = message
|
||||
|
||||
Rails.logger.error("############# #{@status}, #{@message}")
|
||||
end
|
||||
|
||||
def tip_json
|
||||
{status: self.status, message: self.message}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
130
lib/educoder/ufile.rb
Normal file
130
lib/educoder/ufile.rb
Normal file
@@ -0,0 +1,130 @@
|
||||
#coding=utf-8
|
||||
#
|
||||
# ucloud 文件上传
|
||||
#
|
||||
|
||||
require 'base64'
|
||||
require 'openssl'
|
||||
require 'faraday'
|
||||
|
||||
module Educoder
|
||||
class Ufile
|
||||
PATH_PREFIX = %r{^/}
|
||||
|
||||
def initialize(uploader={})
|
||||
@ucloud_public_key = uploader[:ucloud_public_key]
|
||||
@ucloud_private_key = uploader[:ucloud_private_key]
|
||||
@ucloud_public_read = uploader[:ucloud_public_read]
|
||||
@ucloud_bucket = @ucloud_public_read ? uploader[:ucloud_public_bucket] : uploader[:ucloud_private_bucket]
|
||||
@ucloud_bucket_host = @ucloud_public_read ? uploader[:ucloud_public_bucket_host] : uploader[:ucloud_private_bucket_host]
|
||||
@ucloud_cdn_host = @ucloud_public_read ? uploader[:ucloud_public_cdn_host] : uploader[:ucloud_private_cdn_host]
|
||||
@ucloud_private_expire_seconds = uploader[:ucloud_private_expire_seconds] || 300
|
||||
|
||||
unless @ucloud_cdn_host.include?('//')
|
||||
raise "config.ucloud_cdn_host requirement include // http:// or https://, but you give: #{@ucloud_cdn_host}"
|
||||
end
|
||||
end
|
||||
|
||||
# 上传文件
|
||||
def put(path, file, headers = {})
|
||||
path.sub!(PATH_PREFIX, '')
|
||||
|
||||
response = conn.put(path, file.read) do |req|
|
||||
req.headers = headers
|
||||
token = authorization(req.method, headers['Content-Type'], path)
|
||||
req.headers['Authorization'] = token
|
||||
end
|
||||
|
||||
if response.success?
|
||||
true
|
||||
else
|
||||
raise 'Ucloud上传失败'
|
||||
end
|
||||
end
|
||||
|
||||
# 读取文件
|
||||
def get(path)
|
||||
path.sub!(PATH_PREFIX, '')
|
||||
response = conn.get(url(path))
|
||||
|
||||
if response.success?
|
||||
return response
|
||||
else
|
||||
raise 'Ucloud Get File Fail'
|
||||
end
|
||||
end
|
||||
|
||||
# 删除文件
|
||||
def delete(path)
|
||||
path.sub!(PATH_PREFIX, '')
|
||||
response = conn.delete(url(path)) do |req|
|
||||
req.headers['Authorization'] = authorization(req.method, nil, path)
|
||||
end
|
||||
|
||||
if response.success?
|
||||
true
|
||||
else
|
||||
raise 'Ucloud Get File Fail'
|
||||
end
|
||||
end
|
||||
|
||||
def url(path)
|
||||
if @ucloud_public_read
|
||||
public_get_url(path)
|
||||
else
|
||||
private_get_url(path)
|
||||
end
|
||||
end
|
||||
|
||||
# 公开的访问地址
|
||||
def public_get_url(path)
|
||||
path.sub!(PATH_PREFIX, '')
|
||||
[@ucloud_cdn_host, path].join('/')
|
||||
end
|
||||
|
||||
# 私有空间访问地址
|
||||
def private_get_url(path)
|
||||
public_get_url(path) + privite_get_url_auth(path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conn
|
||||
@conn ||= begin
|
||||
Faraday.new(url: @ucloud_bucket_host) do |req|
|
||||
req.request :url_encoded
|
||||
req.adapter Faraday.default_adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 私密查看url的认证信息
|
||||
def privite_get_url_auth(path)
|
||||
expired_ts = private_expire_ts
|
||||
signed_str = signature(string_to_sign('GET', nil, path, expired_ts))
|
||||
"?UCloudPublicKey=#{@ucloud_public_key}&Expires=#{expired_ts}&Signature=#{signed_str}"
|
||||
end
|
||||
|
||||
def private_expire_ts
|
||||
@ucloud_private_expire_seconds + Time.now.to_i
|
||||
end
|
||||
|
||||
def authorization(http_method, content_type, path)
|
||||
signed_str = signature(string_to_sign(http_method, content_type, path))
|
||||
"UCloud " + @ucloud_public_key + ":" + signed_str
|
||||
end
|
||||
|
||||
def signature(str)
|
||||
Base64.strict_encode64(OpenSSL::HMAC.digest('sha1', @ucloud_private_key, str))
|
||||
end
|
||||
|
||||
def string_to_sign(http_method, ori_content_type, path, expired_ts = nil)
|
||||
http_verb = "#{http_method.upcase}\n"
|
||||
content_md5 = "\n"
|
||||
content_type = "#{ori_content_type}\n"
|
||||
timestamp = "#{expired_ts}\n"
|
||||
full_path = "/#{@ucloud_bucket}/#{path}"
|
||||
http_verb + content_md5 + content_type + timestamp + full_path
|
||||
end
|
||||
end
|
||||
end
|
||||
144
lib/educoder/units.rb
Normal file
144
lib/educoder/units.rb
Normal file
@@ -0,0 +1,144 @@
|
||||
require 'fileutils'
|
||||
|
||||
module Educoder
|
||||
module Utils
|
||||
class << self
|
||||
# Returns the relative root url of the application
|
||||
def relative_url_root
|
||||
ActionController::Base.respond_to?('relative_url_root') ?
|
||||
ActionController::Base.relative_url_root.to_s :
|
||||
ActionController::Base.config.relative_url_root.to_s
|
||||
end
|
||||
|
||||
# Sets the relative root url of the application
|
||||
def relative_url_root=(arg)
|
||||
if ActionController::Base.respond_to?('relative_url_root=')
|
||||
ActionController::Base.relative_url_root=arg
|
||||
else
|
||||
ActionController::Base.config.relative_url_root = arg
|
||||
end
|
||||
end
|
||||
|
||||
# Generates a n bytes random hex string
|
||||
# Example:
|
||||
# random_hex(4) # => "89b8c729"
|
||||
def random_hex(n)
|
||||
SecureRandom.hex(n)
|
||||
end
|
||||
|
||||
def save_upload(upload, path)
|
||||
directory = File.dirname(path)
|
||||
unless File.exists?(directory)
|
||||
FileUtils.mkdir_p directory
|
||||
end
|
||||
File.open(path, "wb") do |f|
|
||||
if upload.respond_to?(:read)
|
||||
buffer = ""
|
||||
while (buffer = upload.read(8192))
|
||||
f.write(buffer)
|
||||
yield buffer if block_given?
|
||||
end
|
||||
else
|
||||
f.write(upload)
|
||||
yield upload if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def digest(diskfile)
|
||||
md5 = Digest::MD5.new
|
||||
File.open(diskfile, "rb") do |f|
|
||||
buffer = ""
|
||||
while (buffer = f.read(8192))
|
||||
md5.update(buffer)
|
||||
end
|
||||
end
|
||||
md5.hexdigest
|
||||
end
|
||||
end
|
||||
|
||||
module Shell
|
||||
|
||||
module_function
|
||||
|
||||
def shell_quote(str)
|
||||
if Redmine::Platform.mswin?
|
||||
'"' + str.gsub(/"/, '\\"') + '"'
|
||||
else
|
||||
"'" + str.gsub(/'/, "'\"'\"'") + "'"
|
||||
end
|
||||
end
|
||||
|
||||
def shell_quote_command(command)
|
||||
if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
|
||||
command
|
||||
else
|
||||
shell_quote(command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module DateCalculation
|
||||
# Returns the number of working days between from and to
|
||||
def working_days(from, to)
|
||||
days = (to - from).to_i
|
||||
if days > 0
|
||||
weeks = days / 7
|
||||
result = weeks * (7 - non_working_week_days.size)
|
||||
days_left = days - weeks * 7
|
||||
start_cwday = from.cwday
|
||||
days_left.times do |i|
|
||||
unless non_working_week_days.include?(((start_cwday + i - 1) % 7) + 1)
|
||||
result += 1
|
||||
end
|
||||
end
|
||||
result
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Adds working days to the given date
|
||||
def add_working_days(date, working_days)
|
||||
if working_days > 0
|
||||
weeks = working_days / (7 - non_working_week_days.size)
|
||||
result = weeks * 7
|
||||
days_left = working_days - weeks * (7 - non_working_week_days.size)
|
||||
cwday = date.cwday
|
||||
while days_left > 0
|
||||
cwday += 1
|
||||
unless non_working_week_days.include?(((cwday - 1) % 7) + 1)
|
||||
days_left -= 1
|
||||
end
|
||||
result += 1
|
||||
end
|
||||
next_working_date(date + result)
|
||||
else
|
||||
date
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the date of the first day on or after the given date that is a working day
|
||||
def next_working_date(date)
|
||||
cwday = date.cwday
|
||||
days = 0
|
||||
while non_working_week_days.include?(((cwday + days - 1) % 7) + 1)
|
||||
days += 1
|
||||
end
|
||||
date + days
|
||||
end
|
||||
|
||||
# Returns the index of non working week days (1=monday, 7=sunday)
|
||||
def non_working_week_days
|
||||
@non_working_week_days ||= begin
|
||||
days = Setting.non_working_week_days
|
||||
if days.is_a?(Array) && days.size < 7
|
||||
days.map(&:to_i)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user