init project

This commit is contained in:
Jasder
2020-03-09 00:40:16 +08:00
commit 2937b2a94d
6549 changed files with 7215173 additions and 0 deletions

167
lib/educoder/i18n.rb Normal file
View 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
View 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

View 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
View 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
View 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