|
|
# encoding: utf-8
|
|
|
# status 控制实训的状态,0:编辑;1: 申请发布; 2:正式发布; 3:关闭
|
|
|
# is_updated 字段为1表示实训的更改可能会影响到后续的评测,0表示更改不会影响后续的评测
|
|
|
# reset_time表示需要更新脚本的时间
|
|
|
# modify_time表示仅测试集更新的时间
|
|
|
# main_mirror_id 实训使用镜像的主类别
|
|
|
# child_mirror_id 实训使用镜像的子类别,子类别可以有多个,所以字段是字符串数组类型
|
|
|
# webssh 0:不开启webssh;1:开启练习模式; 2:开启评测模式
|
|
|
# trainee 实训的难度
|
|
|
# user_scorp
|
|
|
# exec_time 程序执行时间
|
|
|
|
|
|
# 繁忙等级参数:80发布实训失败,返回code为-1;81:实训开启的时候fork失败或者pull失败;83:开启实训的时候,openGameInstance返回非0值;84:jenkins系统异常,post数据不成功
|
|
|
# 85:challenge创建的时候Jenkins处理异常;86:实训评测失败,jenkins返回错误结果; 87:版本库删除异常;88:点击评测失败,没返回数据;89:重置链接jenkins返回失败
|
|
|
# 90: 更新公共测试用例失败,返回code非0;91:实训有改动,评测的时候初始化数据,返回数据code非0;92:webssh开启失败,接口getConnectInfo返回code值非0
|
|
|
# 93: 实训断开连接失败,接口deleteSSH返回code值非0
|
|
|
# 94: 获取评测等待时间失败,接口getWaitingTime返回code值为非0
|
|
|
# 95:代码重置失败
|
|
|
# 96:代码自动修复失败
|
|
|
# 97:tpi获取当前代码为空
|
|
|
# 98:更新tpi脚本异常
|
|
|
# 99:vnc连接异常
|
|
|
# 110:websshPod删除异常
|
|
|
# 116:challenge创建关卡的时候返回结果错误
|
|
|
#
|
|
|
class Shixun < ActiveRecord::Base
|
|
|
attr_accessible :description, :name, :changeset_num, :status, :user_id, :gpid, :language, :identifier, :authentication, :closer_id, :end_time, :publish_time,
|
|
|
:propaedeutics, :trainee, :major_id, :homepage_show, :webssh, :hidden, :fork_from, :can_copy, :modify_time, :reset_time, :git_url, :use_scope,
|
|
|
:vnc, :evaluate_script, :image_text, :exec_time, :test_set_permission, :hide_code, :excute_time, :forbid_copy
|
|
|
|
|
|
belongs_to :creator, foreign_key: :user_id, class_name: 'User'
|
|
|
has_many :users, :through => :shixun_members
|
|
|
has_many :shixun_members, :dependent => :destroy
|
|
|
has_one :repository, :dependent => :destroy
|
|
|
has_many :homework_commons_shixunses
|
|
|
has_many :homework_challenge_settings, :dependent => :destroy
|
|
|
has_many :challenges, :dependent => :destroy
|
|
|
has_many :myshixuns, :dependent => :destroy
|
|
|
has_many :stage_shixuns, :dependent => :destroy
|
|
|
belongs_to :major
|
|
|
belongs_to :major_course
|
|
|
has_many :shixun_major_courses, :dependent => :destroy
|
|
|
has_and_belongs_to_many :homework_commons
|
|
|
has_many :discusses, :as => :dis, :dependent => :destroy
|
|
|
has_many :shixun_ports
|
|
|
has_many :evaluate_records, :dependent => :destroy
|
|
|
has_many :shixun_mirror_repositories, :dependent => :destroy
|
|
|
has_many :mirror_repositories, :through => :shixun_mirror_repositories
|
|
|
has_many :shixun_schools, :dependent => :destroy
|
|
|
has_many :schools, :through => :shixun_schools
|
|
|
has_many :exercise_shixun_challenges, :dependent => :destroy
|
|
|
has_many :exercise_bank_shixun_challenges, :dependent => :destroy
|
|
|
has_many :tag_repertoires, :through => :shixun_tag_repertoires
|
|
|
has_many :shixun_tag_repertoires, :dependent => :destroy
|
|
|
has_many :shixun_service_configs, :dependent => :destroy
|
|
|
|
|
|
scope :visible, lambda{where(status: [2,3])}
|
|
|
scope :min, lambda { select([:id, :name, :gpid, :modify_time, :reset_time, :language, :status, :identifier,
|
|
|
:test_set_permission, :hide_code, :forbid_copy, :hidden, :webssh, :user_id, :code_hidden,
|
|
|
:task_pass, :exec_time, :multi_webssh, :vnc]) }
|
|
|
scope :published, lambda{where(status: 2)}
|
|
|
scope :field_for_recommend, lambda{ select([:id, :name, :identifier, :myshixuns_count]) }
|
|
|
|
|
|
include ApplicationHelper
|
|
|
has_many :tidings, :as => :container, :dependent => :destroy
|
|
|
#scope :visible, -> { where(status: -1) }
|
|
|
after_create :send_tiding
|
|
|
|
|
|
def should_compile?
|
|
|
self.mirror_repositories.published_main_mirror.first.try(:should_compile)
|
|
|
end
|
|
|
|
|
|
# 可供使用的实训
|
|
|
def operable?
|
|
|
logger.info("####")
|
|
|
self.status != -1 && !self.hidden
|
|
|
end
|
|
|
|
|
|
def is_published?
|
|
|
self.status > 1 ? true : false
|
|
|
end
|
|
|
|
|
|
def fork_identifier
|
|
|
self.fork_from.nil? ? "--" : Shixun.where(id: self.fork_from).first.try(:identifier)
|
|
|
end
|
|
|
|
|
|
def get_fork
|
|
|
Shixun.where(:fork_from => self.id)
|
|
|
end
|
|
|
|
|
|
def switch_language
|
|
|
case self.shixun_main_name
|
|
|
when "C#"
|
|
|
"Cxp"
|
|
|
when "C/C++","C","C++"
|
|
|
"Cjia"
|
|
|
else
|
|
|
self.shixun_main_name
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def send_tiding
|
|
|
self.tidings << Tiding.new(:user_id => self.user_id, :trigger_user_id => self.user_id, :belong_container_id => self.id, :belong_container_type =>'Shixun', :tiding_type => "System", :viewed => 0)
|
|
|
end
|
|
|
|
|
|
def is_choice_type
|
|
|
self.challenges.count == self.challenges.where(:st => 1).count ? true : false
|
|
|
end
|
|
|
|
|
|
def is_practice_type
|
|
|
self.challenges.count == self.challenges.where(:st => 0).count ? true : false
|
|
|
end
|
|
|
|
|
|
# 实训关卡的总分(由于大部分是实践题,因此没关联查choose表)
|
|
|
def all_score
|
|
|
sum = 0
|
|
|
self.challenges.each do |challenge|
|
|
|
if challenge.st == 0
|
|
|
sum += challenge.score
|
|
|
else
|
|
|
sum += challenge.choose_score
|
|
|
end
|
|
|
end
|
|
|
return sum
|
|
|
end
|
|
|
|
|
|
# 实训评分 cnt-评分次数 sum-总评分
|
|
|
def shixun_preference
|
|
|
game_star_info = Shixun.find_by_sql("select g.star from
|
|
|
(games g left join (myshixuns m join shixuns s on s.id = m.shixun_id) on m.id = g.myshixun_id)
|
|
|
where g.star != 0 and s.id = #{self.id}")
|
|
|
if game_star_info.present?
|
|
|
cnt = game_star_info.count
|
|
|
sum = game_star_info.map(&:star).sum
|
|
|
star = (sum / cnt.to_f).round(1)
|
|
|
else
|
|
|
5.0
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# 实训评分信息
|
|
|
# return [实训评分, 5星评分比例, 4星评分比例, 3星评分比例, 2星评分比例, 1星评分比例]
|
|
|
def shixun_preference_info
|
|
|
game_star_info = Shixun.find_by_sql("select g.star from
|
|
|
(games g left join (myshixuns m join shixuns s on s.id = m.shixun_id) on m.id = g.myshixun_id)
|
|
|
where g.star != 0 and s.id = #{self.id}")
|
|
|
star_info = []
|
|
|
if game_star_info.present?
|
|
|
5.downto(1) do |i|
|
|
|
star_info << ((game_star_info.select{|s| s.star == i}.count / game_star_info.count.to_f) * 100).round
|
|
|
end
|
|
|
sum = star_info.sum
|
|
|
max = star_info.max
|
|
|
# 四舍五入引起5星比例超过100%, 将最大的比例的评分做出调整
|
|
|
if sum > 100
|
|
|
star_info = star_info.map{|s| s == max ? s - 1 : s}
|
|
|
elsif sum < 100
|
|
|
star_info = star_info.map{|s| s == max ? s + 1 : s}
|
|
|
end
|
|
|
cnt = game_star_info.count
|
|
|
sum = game_star_info.map(&:star).sum
|
|
|
star_info.unshift((sum / cnt.to_f).round(1))
|
|
|
else
|
|
|
star_info = [5.0, 100, 0, 0, 0, 0]
|
|
|
end
|
|
|
|
|
|
return star_info
|
|
|
end
|
|
|
|
|
|
# 实训的版本库创建者为"educoder" 和"eduforge"
|
|
|
# "educoder"为原创,"eduforge"为复制的时候用户
|
|
|
def git_login
|
|
|
self.fork_from.blank? ? "educoder" : "eduforge"
|
|
|
end
|
|
|
|
|
|
def user_evaluate_status
|
|
|
myshixun = self.myshixuns.where(:user_id => User.current.id).first
|
|
|
if myshixun.blank?
|
|
|
"--"
|
|
|
else
|
|
|
game_id = myshixun.games.map(&:id)
|
|
|
output = Output.where(:game_id => game_id).reorder("updated_at desc").first
|
|
|
if output.blank?
|
|
|
"--"
|
|
|
else
|
|
|
if output.try(:code) == -1
|
|
|
time_from_now(output.updated_at).to_s + "评测失败"
|
|
|
else
|
|
|
time_from_now(output.updated_at).to_s + "评测成功"
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def shixun_tag_name
|
|
|
self.tag_repertoires.order("name asc").map(&:name)
|
|
|
end
|
|
|
|
|
|
def has_web_route
|
|
|
self.mirror_name.include?('JFinal') || self.mirror_name.include?('PHP') && self.mirror_name.include?('Mysql') || self.mirror_name.include?('Web')
|
|
|
end
|
|
|
|
|
|
def script_tag
|
|
|
if self.mirror_script_id
|
|
|
MirrorScript.where(:id => self.mirror_script_id).first
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def standrad_script
|
|
|
mirrors_id = self.mirror_repositories.map(&:id)
|
|
|
MirrorScript.where(:mirror_repository_id => mirrors_id)
|
|
|
end
|
|
|
|
|
|
def shixun_main_mirror_id
|
|
|
id = ''
|
|
|
self.mirror_repositories.each do |mirror|
|
|
|
if mirror.main_type == "1"
|
|
|
id = mirror.id
|
|
|
end
|
|
|
end
|
|
|
return id
|
|
|
end
|
|
|
|
|
|
def shixun_child_mirror_id
|
|
|
i = 0
|
|
|
id = ''
|
|
|
self.mirror_repositories.each do |mirror|
|
|
|
if mirror.main_type == "0"
|
|
|
i += 1
|
|
|
if i == 1
|
|
|
id = mirror.id
|
|
|
else
|
|
|
id = "#{id},#{mirror.id}"
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
return id
|
|
|
end
|
|
|
|
|
|
def shixun_main_name
|
|
|
self.mirror_repositories.published_main_mirror.first.try(:type_name)
|
|
|
end
|
|
|
|
|
|
def main_mirror
|
|
|
self.mirror_repositories.published_main_mirror.first
|
|
|
end
|
|
|
|
|
|
def shixun_child_name
|
|
|
name = ''
|
|
|
index = 0
|
|
|
self.mirror_repositories.each do |mirror|
|
|
|
if mirror.main_type == "0"
|
|
|
index += 1
|
|
|
if index == 1
|
|
|
name = mirror.type_name
|
|
|
else
|
|
|
name = "#{name};#{mirror.type_name}"
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
return name
|
|
|
end
|
|
|
|
|
|
def child_mirror_ids
|
|
|
self.mirror_repositories.where(:main_type => 0).pluck(:id)
|
|
|
end
|
|
|
|
|
|
def mirror_name
|
|
|
self.mirror_repositories.map(&:type_name).blank? ? "" : self.mirror_repositories.map(&:type_name)
|
|
|
end
|
|
|
|
|
|
# 实训脚本,如果不存在取标准脚本
|
|
|
#1.若脚本中只有sourceClassName,那么将传入的执行文件名直接替换SOURCECLASSNAME
|
|
|
#2.若脚本中只有challengeProgramName,那么将传入的执行文件名直接替换challengeProgramName
|
|
|
#3.若脚本中有challengeProgramName和sourceClassName,那么将传入文件名直接替换challengeProgramName,并对这些文件名做处理得到执行类名,替换sourceClassName(目前是java类型的需如此)
|
|
|
# 3.1 处理方法:例如src/step1/helloword.java,处理得到:step1.helloword
|
|
|
def tpm_script
|
|
|
script = self.evaluate_script || self.mirror_repositories.published_main_mirror.first.try(:script_template)
|
|
|
if script.present?
|
|
|
script = script.gsub("\r\n", "\n") # 保持和Linux一致
|
|
|
source_class_name = []
|
|
|
challenge_program_name = []
|
|
|
self.challenges.map(&:exec_path).each do |exec_path|
|
|
|
challenge_program_name << '"' + exec_path + '"'
|
|
|
source_class_name = exec_path.split("src/")[1].split(".java")[0]
|
|
|
source_class_name = source_class_name.nil? ? "" : source_class_name.gsub("/", ".")
|
|
|
end
|
|
|
script = if script.include?("sourceClassName") && script.include?("challengeProgramName")
|
|
|
script.gsub("CHALLENGEPROGRAMNAMES","#{challenge_program_name.join(" ")}").gsub("SOURCECLASSNAMES", "#{source_class_name.join(" ")}")
|
|
|
else
|
|
|
script.gsub("CHALLENGEPROGRAMNAMES","#{challenge_program_name.join(" ")}").gsub("SOURCECLASSNAMES", "#{challenge_program_name.join(" ")}")
|
|
|
end
|
|
|
#Base64.urlsafe_encode64(script)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# 获取特定格式的实训测试用例
|
|
|
# params = {:challengeStage => "#{@challenge.position}", :challengeType => "#{@challenge.evaluation_way.to_i}",
|
|
|
# :challengeProgramName => "#{@challenge.exec_path}",
|
|
|
# :trainingID => "#{@shixun.id}", :operationEnvironment => @shixun.language}
|
|
|
def gameInfo
|
|
|
fragments = []
|
|
|
testSet = []
|
|
|
testCases = []
|
|
|
challenges = self.challenges.includes(:test_sets)
|
|
|
challenges.each do |challenge|
|
|
|
pipeline_script = {:challengeStage => "#{challenge.position}",
|
|
|
:challengeType => "#{challenge.evaluation_way.to_i}",
|
|
|
:challengeProgramName => "#{challenge.exec_path}"}
|
|
|
fragments << pipeline_script
|
|
|
# 每一关所有的测试集
|
|
|
challenge.test_sets.each do |test_set|
|
|
|
test_cases = {:input => test_set.input, :output => test_set.output}
|
|
|
testSet << test_cases
|
|
|
end
|
|
|
test_sets_list = {:challengeStage => challenge.position.to_s, :challengeTestCases => testSet}
|
|
|
testCases << test_sets_list
|
|
|
testSet = []
|
|
|
end
|
|
|
gameInfo = {:tpmID => self.id.to_s, :operationEnvironment => "#{self.try(:language)}", :challengeInfo => fragments, :testCases => testCases}
|
|
|
gameInfo = Base64.urlsafe_encode64(gameInfo.to_json) unless gameInfo.blank?
|
|
|
return gameInfo
|
|
|
end
|
|
|
|
|
|
# id 转换成 identifier
|
|
|
def to_param
|
|
|
identifier
|
|
|
end
|
|
|
|
|
|
def shixun_trainee
|
|
|
trainee = ""
|
|
|
case self.trainee
|
|
|
when 1
|
|
|
trainee = "初级学员"
|
|
|
when 2
|
|
|
trainee = "中级学员"
|
|
|
when 3
|
|
|
trainee = "高级学员"
|
|
|
when 4
|
|
|
trainee = "顶级学员"
|
|
|
end
|
|
|
trainee
|
|
|
end
|
|
|
|
|
|
def shixun_level
|
|
|
level = ""
|
|
|
case self.trainee
|
|
|
when 1
|
|
|
level = "初级"
|
|
|
when 2
|
|
|
level = "中级"
|
|
|
when 3
|
|
|
level = "高级"
|
|
|
when 4
|
|
|
level = "顶级"
|
|
|
end
|
|
|
level
|
|
|
end
|
|
|
|
|
|
def owner
|
|
|
User.find(self.user_id)
|
|
|
rescue ActiveRecord::RecordNotFound
|
|
|
render_404
|
|
|
end
|
|
|
|
|
|
# 大部分是实践题题,因此加入逻辑判断可以减少查询
|
|
|
def shixun_score
|
|
|
self.challenges.map{|c| c.choose_score.to_i }.sum
|
|
|
end
|
|
|
|
|
|
def shixun_status
|
|
|
status = ""
|
|
|
case self.status
|
|
|
when 0
|
|
|
status = "编辑中"
|
|
|
when 1
|
|
|
status = "审核中"
|
|
|
when 2
|
|
|
status = "已发布"
|
|
|
when 3
|
|
|
status = "已关闭"
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def school_count
|
|
|
user_ids = self.myshixuns.pluck(:user_id)
|
|
|
num = UserExtensions.where(:user_id => user_ids).pluck(:school_id).uniq.length
|
|
|
end
|
|
|
|
|
|
def collaborators
|
|
|
self.shixun_members.select{|member| member.role == 2} unless self.shixun_members.blank?
|
|
|
end
|
|
|
end
|