|
|
# encoding=utf-8
|
|
|
class GamesService
|
|
|
include ApplicationHelper
|
|
|
include GamesHelper
|
|
|
CODES = %W(1 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
|
|
|
|
|
|
# 关卡详情
|
|
|
def show_game params, current_user
|
|
|
game = Game.min.find_by_identifier(params[:identifier])
|
|
|
Rails.logger.info("game id is #{game.try(:id)}, current_user id is #{current_user.try(:id)}")
|
|
|
if game.blank? || current_user.blank?
|
|
|
return{:status => 404}
|
|
|
end
|
|
|
# 防止评测中途ajax被取消;3改成0是为了处理首次进入下一关的问题
|
|
|
game.update_attribute(:status, 0) if game.status == 1
|
|
|
game.update_attributes(:status => 0, :open_time => Time.now) if game.status == 3 # Redo:后续可以去掉,只是为了兼顾其它模块的耗时统计
|
|
|
|
|
|
myshixun = Myshixun.min.find(game.myshixun_id)
|
|
|
shixun = Shixun.min.find(myshixun.shixun_id)
|
|
|
unless ((myshixun.user_id == current_user.id || current_user.business? || current_user.id == shixun.try(:user_id) ||
|
|
|
current_user.is_certification_teacher) && (shixun.operable?)) || current_user.admin?
|
|
|
Rails.logger.info("######403???")
|
|
|
return{:status => 403}
|
|
|
end
|
|
|
game_challenge = Challenge.min.find(game.challenge_id)
|
|
|
|
|
|
has_answer = game_challenge.answer.blank? ? false : true
|
|
|
game_challenge.answer = nil
|
|
|
if game_challenge.st == 1
|
|
|
game_challenge.score = game_challenge.choose_score.to_i
|
|
|
end
|
|
|
# st:判断是选择类型还是实训类型
|
|
|
st = game_challenge.st
|
|
|
game_count = myshixun.games.count
|
|
|
discusses_count = (current_user.admin? ? shixun.discusses.count :
|
|
|
shixun.discusses.where("hidden = false or user_id = :user_id", user_id: current_user.id).count)
|
|
|
mirror_name = myshixun.mirror_name
|
|
|
user = myshixun.owner
|
|
|
username = user.show_name
|
|
|
image_url = url_to_avatar(user)
|
|
|
user_url = "/users/#{user.login}"
|
|
|
is_teacher = (user.user_extensions.identity == 0)
|
|
|
tpm_identifier = shixun.try(:identifier)
|
|
|
# 实训超时设置
|
|
|
time_limit = shixun.exec_time
|
|
|
|
|
|
# 高性能取上一关、下一关
|
|
|
prev_game = Game.prev_identifier(shixun.id, game.myshixun_id, game_challenge.position)
|
|
|
next_game = if shixun.vnc || current_user.is_certification_teacher || shixun_manager(shixun, current_user) || game.status || shixun.task_pass
|
|
|
Game.next_game(shixun.id, game.myshixun_id, game_challenge.position).try(:identifier)
|
|
|
end
|
|
|
|
|
|
# 该参数是为了判断是否需要加载loading
|
|
|
@is_subject = params[:is_subject]
|
|
|
|
|
|
# 关卡点赞数, praise_or_tread = 1则表示赞过
|
|
|
praise_count = PraiseTread.where(:praise_tread_object_id => game_challenge.id, :praise_tread_object_type => "Challenge", :praise_or_tread => 1).count
|
|
|
user_praise = PraiseTread.where(:praise_tread_object_id => game_challenge.id, :praise_tread_object_type => "Challenge", :user_id => current_user.id, :praise_or_tread => 1).present? ? true : false
|
|
|
|
|
|
# 实训的最大评测次数,这个值是为了优化查询,每次只取最新的最新一次评测的结果集
|
|
|
max_query_index = game.query_index - 1
|
|
|
# 判断实训是否全部通关
|
|
|
had_done = game.had_done
|
|
|
# 统计时间
|
|
|
record = EvaluateRecord.where(:game_id => game.id).first.try(:consume_time)
|
|
|
# 实训制作者当前拥有的金币
|
|
|
grade = myshixun.owner.try(:grade)
|
|
|
|
|
|
# power判断用户是否有权限查看隐藏测试集(TPM管理员;平台认证的老师;花费金币查看者) -1 表示不能解锁 0 表示需要付费解锁 1表示可以看
|
|
|
# myshixun_manager
|
|
|
myshixun_manager = shixun_manager(shixun, current_user) || (current_user.is_certification_teacher)
|
|
|
power = (myshixun_manager || game.test_sets_view ) ? 1 : (shixun.test_set_permission ? 0 : -1)
|
|
|
|
|
|
# 选择题和编程题公共部分
|
|
|
container = {:st => st, :discusses_count => discusses_count, :game_count => game_count, :myshixun => myshixun.try(:attributes),
|
|
|
:challenge => game_challenge.try(:attributes), :game => game.try(:attributes), :shixun => shixun.try(:attributes),
|
|
|
:record => record, :grade => grade, :prev_game => prev_game, :next_game => next_game, :username => username,
|
|
|
:image_url => image_url, :user_url => user_url, :praise_count => praise_count, :user_praise => user_praise, :time_limit => time_limit,
|
|
|
:tomcat_url => Redmine::Configuration['tomcat_php'], :is_teacher => is_teacher, :power => power, :myshixun_manager => myshixun_manager}
|
|
|
if shixun.vnc
|
|
|
begin
|
|
|
shixun_tomcat = Redmine::Configuration['shixun_tomcat']
|
|
|
service_host = Redmine::Configuration['vnc_url']
|
|
|
uri = "#{shixun_tomcat}/bridge/vnc/getvnc"
|
|
|
params = {tpiID: myshixun.id, :containers => "#{Base64.urlsafe_encode64(container_limit(shixun.mirror_repositories))}"}
|
|
|
res = uri_exec uri, params
|
|
|
if res && res['code'].to_i != 0
|
|
|
raise("实训云平台繁忙(繁忙等级:99)")
|
|
|
end
|
|
|
url = "https://#{res['port']}.#{service_host}/vnc_lite.html?password=headless"
|
|
|
Rails.logger.info("66666666sssssss#{url}")
|
|
|
container = container.merge(:vnc_url => url)
|
|
|
Rails.logger.info("777777666sssssss#{container}")
|
|
|
rescue Exception => e
|
|
|
Rails.logger.error(e.message)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
if st == 0 && shixun.status == 2 && myshixun_manager
|
|
|
zip_path = Gitlab.endpoint.to_s + "/projects/" + myshixun.gpid.to_s + "/repository/archive?&private_token=" + Gitlab.private_token.to_s
|
|
|
container = container.merge(:zip_path => zip_path)
|
|
|
end
|
|
|
|
|
|
# 区分选择题和编程题,st:0编程题;
|
|
|
if st == 0
|
|
|
gitUrl = gitlab_url myshixun
|
|
|
gitUrl = Base64.urlsafe_encode64(gitUrl)
|
|
|
shixun_tomcat = Redmine::Configuration['shixun_tomcat']
|
|
|
params = {:tpiID => "#{myshixun.id}", :tpiGitURL => "#{gitUrl}"}
|
|
|
|
|
|
# 判断tpm是否修改了
|
|
|
tpm_modified = repository_is_modified(myshixun, shixun.try(:gpid)) # 判断TPM和TPI的版本库是否被改了
|
|
|
tpm_cases_modified = (game_challenge.modify_time != game.modify_time ? true : false) # modify_time 决定TPM测试集是否有更新
|
|
|
# tpm_script_modified = (shixun.reset_time > myshixun.reset_time ? true : false) # 新结构脚本是实时传送的,所以不会影响
|
|
|
|
|
|
# 区分评测过未评测过,未评测过按需求取数据
|
|
|
sql =
|
|
|
if max_query_index > 0
|
|
|
"SELECT
|
|
|
b.code, b.actual_output, b.out_put, b.result, b.compile_success, a.is_public, a.input, a.output
|
|
|
FROM
|
|
|
(SELECT position, input, output, challenge_id, is_public FROM test_sets where challenge_id=#{game_challenge.id}) a
|
|
|
LEFT JOIN
|
|
|
(SELECT
|
|
|
result, test_set_position, g.challenge_id, o.actual_output, o.out_put, o.compile_success, o.code
|
|
|
FROM
|
|
|
outputs o left join games g on g.id=o.game_id
|
|
|
WHERE
|
|
|
game_id=#{game.id} and query_index = #{max_query_index}
|
|
|
) b
|
|
|
ON
|
|
|
b.challenge_id = a.challenge_id and b.test_set_position = a.position"
|
|
|
else
|
|
|
# 这个地方如果加字段的话一定注意test_set_static_data方法,因为这个地方的字段数在下面有判断
|
|
|
"SELECT t.is_public, t.input, t.output, t.position
|
|
|
FROM games g ,challenges c,test_sets t
|
|
|
WHERE g.id=#{game.id} and c.id= g.challenge_id and t.challenge_id = c.id"
|
|
|
end
|
|
|
qurey_test_sets = TestSet.find_by_sql(sql)
|
|
|
|
|
|
# 测试集统计及处理
|
|
|
unless qurey_test_sets.blank?
|
|
|
check_power = (power == 1 || game.test_sets_view)
|
|
|
test_sets = test_set_static_data(qurey_test_sets, check_power)
|
|
|
end
|
|
|
test_sets_count = qurey_test_sets.size
|
|
|
had_test = Output.where(:game_id => game.id, :query_index => max_query_index)
|
|
|
had_test_count = had_test.count
|
|
|
had_passed_testsests_error_count = had_test.blank? ? 0 : had_test.select{|had_test| had_test.result == false}.count
|
|
|
had_passed_testsests_error_count = had_test_count - had_passed_testsests_error_count
|
|
|
|
|
|
error_position = had_test.blank? ? nil : had_test.select{|had_test| had_test.result == false}.last
|
|
|
Rails.logger.info("latest output id is #{error_position.id unless error_position.blank?}")
|
|
|
latest_output = error_position.try(:out_put).gsub(/\n/, '<br/>').gsub(/\t/, " \; \; \; \; \; \; \; \;") unless error_position.try(:out_put).blank?
|
|
|
Rails.logger.warn(latest_output)
|
|
|
|
|
|
output_hash = {:test_sets => test_sets}.merge(:had_test_count => had_test_count, :test_sets_count => test_sets_count,
|
|
|
:had_passed_testsests_error_count => had_passed_testsests_error_count)
|
|
|
multi_webssh = shixun.webssh == 2 && shixun.multi_webssh
|
|
|
result = {:tpm_modified => tpm_modified, :tpm_cases_modified => tpm_cases_modified, :hide_code => shixun.hide_code, :forbid_copy => shixun.forbid_copy,
|
|
|
:output_sets => output_hash, :latest_output => latest_output, :mirror_name => mirror_name, :multi_webssh => multi_webssh,
|
|
|
:has_answer => has_answer}.merge(container)
|
|
|
else # 选择题类型的
|
|
|
# 该方法多个地方调用,比如show、评测
|
|
|
# 最后一个字段true表示只显示数据,false表示可能有添加数据
|
|
|
result_container = choose_container(game_challenge, game, max_query_index, true)
|
|
|
return result_container.merge(container)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# output为testset的实际输出
|
|
|
# out_put为顶端结果显示输出 power 查看测试集的权限
|
|
|
def test_set_static_data test_sets, power
|
|
|
test_result = []
|
|
|
unless test_sets.blank?
|
|
|
test_sets.each do |test_set|
|
|
|
# 第一次刷新或者没有评测的的时候test_set只需取四个字段,所以取了count
|
|
|
actual_output = test_set.attributes.count > 4 ? test_set.try(:actual_output) : nil
|
|
|
result = test_set.attributes.count > 4 ? test_set.try(:result) : nil
|
|
|
compile_success = test_set.attributes.count > 4 ? test_set.try(:compile_success) : nil
|
|
|
status = test_set.attributes.count > 4 ? test_set.try(:code) : nil
|
|
|
#actual_output = Base64.encode64(actual_output)
|
|
|
|
|
|
actual_output = (compile_success.to_s == "0" && status.to_s == "-1" ? "编译失败,请在测试结果中查看具体的错误信息" : actual_output)
|
|
|
|
|
|
public_result = {:is_public => (test_set.is_public ? 1 : 0), :result => result,
|
|
|
:actual_output => actual_output, :compile_success => compile_success}
|
|
|
# 测试集公开的话才返回input结果
|
|
|
(test_set.is_public || power) && public_result.merge!({:input => test_set.input, :output => test_set.output})
|
|
|
Rails.logger.info("#######################test_set: #{public_result}")
|
|
|
test_result << public_result.to_json
|
|
|
end
|
|
|
end
|
|
|
test_result = test_result.blank? ? test_result : test_result.join(",")
|
|
|
return test_result.gsub(/<\/script>/, '<//script>')
|
|
|
end
|
|
|
|
|
|
# 获取TPI关卡内容
|
|
|
def challenges params
|
|
|
result = []
|
|
|
myshixun = Myshixun.find(Game.where(:identifier => params[:identifier]).pluck(:myshixun_id).first)
|
|
|
shixun = Shixun.select([:id, :status]).find(myshixun.shixun_id)
|
|
|
games = myshixun.games.includes(:challenge).reorder("challenges.position")
|
|
|
games.each do |game|
|
|
|
game_status = game.try(:status)
|
|
|
challenge = game.challenge
|
|
|
if challenge.st == 0 # 实战类型
|
|
|
if game_status == 2 # 通关了则取实际得分,没通关则取总分
|
|
|
gold = (shixun.status <= 1) ? 0 : game.final_score.to_i
|
|
|
# 只要过关了,查看了答案经验值就是0,金币是负数
|
|
|
experience = (shixun.status <= 1 || game.final_score.to_i < 0) ? 0 : challenge.score.to_i
|
|
|
challenge_tags_count = (shixun.status <= 1) ? 0 : challenge.challenge_tags.count
|
|
|
else
|
|
|
gold = challenge.score.to_i
|
|
|
experience = gold
|
|
|
challenge_tags_count = challenge.challenge_tags.count
|
|
|
end
|
|
|
else
|
|
|
if game_status == 2
|
|
|
gold = (shixun.status <= 1) ? 0 : game.final_score.to_i
|
|
|
experience = (shixun.status <= 1 || game.final_score.to_i < 0) ? 0 : challenge.choose_score.to_i
|
|
|
challenge_tags_count = (shixun.status <= 1 || game.final_score.to_i <= 0) ? 0 : challenge.choose_tags_num
|
|
|
else
|
|
|
# 选择题只有在全对的时候才会获取final score总分,错任何一个题final_score就为0
|
|
|
gold = challenge.choose_score.to_i
|
|
|
experience = challenge.choose_score.to_i
|
|
|
challenge_tags_count = challenge.choose_tags_num
|
|
|
end
|
|
|
end
|
|
|
result << {:position => challenge.position, :subject => challenge.subject, :tag_count => challenge_tags_count, :gold => gold,
|
|
|
:experience => experience, :status => game.status, :star => game.star, :identifier => game.identifier}
|
|
|
end
|
|
|
result
|
|
|
end
|
|
|
|
|
|
# 评论打星星
|
|
|
def star params, current_user
|
|
|
game = Game.find_by_identifier(params[:identifier])
|
|
|
shixun = Shixun.select([:id, :status]).find(params[:shixun_id])
|
|
|
# 如果已经评星,则不能重复评
|
|
|
grade = Grade.where(:user_id => current_user.id, :container_id => game.id, :container_type => 'Star').first
|
|
|
if grade.present?
|
|
|
return{:reward_code => -1}
|
|
|
else
|
|
|
game.update_column(:star, params[:star].to_i)
|
|
|
# 随机生成10-100金币
|
|
|
code = rand(10..100)
|
|
|
# 积分消耗情况记录;加积分之针对已发布的实训
|
|
|
if shixun.status >= 2
|
|
|
reward_grade(current_user, game.id, 'Star', code)
|
|
|
return{:reward_code => code}
|
|
|
else
|
|
|
return{:reward_code => 0}
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# TPI版本库文件目录结构
|
|
|
def entries params
|
|
|
g = Gitlab.client
|
|
|
gpid = params[:gpid]
|
|
|
path = params[:path].try(:strip)
|
|
|
rev = params[:rev] ? params[:rev] : "master"
|
|
|
result = []
|
|
|
trees = g.trees(gpid, path: path, rev: rev)
|
|
|
trees.each do |tree|
|
|
|
# 去掉第一行的"/"
|
|
|
new_path = File.join(path,tree.name).reverse.chomp("/").reverse
|
|
|
result << {:name => tree.name, :path => new_path, :kind => tree.type == 'tree' ? 'dir' : 'file'}
|
|
|
end
|
|
|
|
|
|
return result
|
|
|
end
|
|
|
|
|
|
def answer params, current_user
|
|
|
game_base params[:identifier]
|
|
|
challenge = Challenge.select([:answer, :id, :score, :st]).find(@game.challenge_id)
|
|
|
is_teacher = (current_user.user_extensions.identity == 0)
|
|
|
is_certification_teacher = current_user.is_certification_teacher
|
|
|
# 这几种情况可以直接查看答案的:实训未发布;当前用户为实训管理员;已经查看过答案;平台认证的老师;
|
|
|
if (@shixun.status < 2 || shixun_manager(@shixun, current_user) || @game.answer_open == 1 || is_certification_teacher)
|
|
|
Rails.logger.info("answer#{params[:identifier]}#### user is #{current_user.id}, status is #{@shixun.status}, manager is #{shixun_manager(@shixun, current_user)}, open #{@game.answer_open}, cer #{is_certification_teacher}")
|
|
|
if challenge.st == 0
|
|
|
result = challenge.try(:answer)
|
|
|
else
|
|
|
result = []
|
|
|
challenge.challenge_chooses.each do |choose|
|
|
|
result << {:position => choose.position, :answer => (choose.answer.blank? ? choose.standard_answer : choose.answer)}
|
|
|
end
|
|
|
end
|
|
|
return {:view_answer => true, :answer => result}
|
|
|
else
|
|
|
return {:is_teacher => is_teacher, :view_answer => false}
|
|
|
# view_answer = false
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# 查看答案需要扣取金币
|
|
|
# 必须保证用户的金币数大于关卡的金币数
|
|
|
def answer_grade params, current_user
|
|
|
game_base params[:identifier]
|
|
|
challenge = Challenge.select([:answer, :id, :score, :st]).find(@game.challenge_id)
|
|
|
challenge_score = challenge.try(:score)
|
|
|
final_score = @game.final_score
|
|
|
if current_user.grade.to_i - challenge_score > 0
|
|
|
if @game.answer_open == 0 # 如果这是第一次查看答案
|
|
|
if challenge.st == 0
|
|
|
final_score = final_score - challenge_score
|
|
|
# 积分消耗情况记录
|
|
|
reward_grade(current_user, @game.id, 'Answer', -challenge_score)
|
|
|
else
|
|
|
final_score = final_score - challenge.choose_score.to_i
|
|
|
# 之所以不用final_score是因为过关后查看答案的final_score为0,但是记录需要记录扣除的分数
|
|
|
reward_grade(current_user, @game.id, 'Answer', -challenge.choose_score.to_i)
|
|
|
end
|
|
|
@game.update_attributes!(:answer_open => true, :final_score => final_score)
|
|
|
end
|
|
|
else
|
|
|
# 金币不足
|
|
|
raise("0")
|
|
|
end
|
|
|
if challenge.st == 0
|
|
|
answer = challenge.try(:answer)
|
|
|
else
|
|
|
answer = []
|
|
|
challenge.challenge_chooses.each do |choose|
|
|
|
answer << {:position => choose.position, :answer => (choose.answer.blank? ? choose.standard_answer : choose.answer)}
|
|
|
end
|
|
|
end
|
|
|
|
|
|
return {:answer => answer, :final_score => final_score, :grade => @myshixun.owner.try(:grade)}
|
|
|
end
|
|
|
|
|
|
# 查看隐藏测试集
|
|
|
def check_test_sets params, current_user
|
|
|
game = Game.select([:id, :challenge_id, :test_sets_view]).find_by_identifier(params[:identifier])
|
|
|
if game.test_sets_view == true
|
|
|
return {:status => -1, :message => "已解锁,请误重复操作!"}
|
|
|
end
|
|
|
challenge = Challenge.select([:id, :score]).find(game.challenge_id)
|
|
|
user_grade = current_user.grade
|
|
|
minus_grade = challenge.score * 5
|
|
|
max_query_index = game.query_index - 1
|
|
|
# 区分评测过未评测过,未评测过按需求取数据
|
|
|
if max_query_index > 0
|
|
|
qurey_test_sets = TestSet.find_by_sql("SELECT o.code, o.actual_output, o.out_put, o.result, o.compile_success, o.test_set_position, o.query_index,t.is_public,t.input, t.output, g.id as game_id, c.id as challenge_id FROM outputs o,games g ,challenges c,test_sets t where
|
|
|
g.id=#{game.id} and o.query_index=#{max_query_index} and g.id = o.game_id and c.id= g.challenge_id and t.challenge_id = c.id and t.position =o.test_set_position order by o.query_index
|
|
|
")
|
|
|
else
|
|
|
qurey_test_sets = TestSet.find_by_sql("SELECT t.is_public,t.input, t.output,t.position FROM games g ,challenges c,test_sets t where
|
|
|
g.id=#{game.id} and c.id= g.challenge_id and t.challenge_id = c.id
|
|
|
")
|
|
|
end
|
|
|
test_sets = test_set_static_data(qurey_test_sets, true)
|
|
|
if user_grade >= minus_grade
|
|
|
current_user.update_attribute(:grade, user_grade - minus_grade)
|
|
|
game.update_attribute(:test_sets_view, true)
|
|
|
# 扣分记录
|
|
|
gardes = Grade.create(:user_id => current_user.id, :container_id => game.id, :score => -minus_grade, :container_type => "testSet")
|
|
|
gardes.attributes.merge!({test_sets: test_sets})
|
|
|
else
|
|
|
return {:status => -1, :message => "本操作需要扣除#{ minus_grade }金币,您的金币不够了"}
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# 文件更新;数据评测记录
|
|
|
# 生成重新评测认证码
|
|
|
# content_modified:0 表示文件没有更新;content_modified:1 表示文件有更新
|
|
|
def file_update params, current_user
|
|
|
game_base params[:identifier]
|
|
|
path = params[:path].strip unless params[:path].blank?
|
|
|
rev = params[:rev] ? params[:rev] : "master"
|
|
|
content_modified = 0
|
|
|
ActiveRecord::Base.transaction do
|
|
|
|
|
|
# params[:evaluate] 实训评测时更新必须给的参数,需要依据该参数做性能统计,其它类型的更新可以跳过
|
|
|
# 自动保存的时候evaluate为0;点评测的时候为1
|
|
|
if params[:evaluate] == 1
|
|
|
sec_key = generates_identifier(EvaluateRecord, 10)
|
|
|
record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun.id,
|
|
|
:game_id => @game.id, :identifier => sec_key)
|
|
|
Rails.logger.warn("##game is is #{@game.id}, record id is #{record.id}, time is**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
|
|
|
# @myshixun.student_works.update_all(:update_time => Time.now) if !@myshixun.student_works.blank?
|
|
|
student_work_time = format("%.3f", (Time.now.to_f - record.created_at.to_f)).to_f
|
|
|
record.update_attributes!(:student_work => student_work_time)
|
|
|
end
|
|
|
|
|
|
@g = Gitlab.client
|
|
|
last_content = @g.files(@myshixun.gpid, path, "master").try(:content)
|
|
|
|
|
|
last_content = tran_base64_decode64(last_content)
|
|
|
content = if @myshixun.mirror_name.select{|a| a.include?("MachineLearning") || a.include?("Python")}.present? && params[:content].present?
|
|
|
params[:content].gsub(/\t/, ' ')
|
|
|
else
|
|
|
params[:content]
|
|
|
end
|
|
|
if content != last_content
|
|
|
content_modified = 1
|
|
|
code_file = @g.edit_file(@myshixun.gpid, current_user.login, :content => content, :file_path => path, :branch_name => "master",
|
|
|
:commit_message => (params[:evaluate] == 1 ? "commit by author" : "auto commit" ))
|
|
|
end
|
|
|
# REDO:是否有读写分离的问题
|
|
|
if record.present?
|
|
|
consume_time = format("%.3f", (Time.now.to_f - record.created_at.to_f)).to_f
|
|
|
record.update_attributes!(:file_update => consume_time)
|
|
|
end
|
|
|
|
|
|
# status为2说明是重新评测
|
|
|
if @game.status == 2
|
|
|
code = CODES.sample(8).join
|
|
|
resubmit = "#{code}_#{@myshixun.id}"
|
|
|
end
|
|
|
|
|
|
if content != last_content && code_file.blank?
|
|
|
raise("update file failed")
|
|
|
else
|
|
|
return {:success => "success", :resubmit => resubmit ,:content_modified => content_modified, sec_key: record.try(:identifier)}
|
|
|
end
|
|
|
end
|
|
|
rescue Exception => e
|
|
|
Rails.logger.error("file update failed #{e.message}")
|
|
|
return {:status => -1, :success => "fail", :message => "实训云平台繁忙(繁忙等级:81),请稍后刷新并重试", :position => @game.challenge.position, :had_done => @game.had_done}
|
|
|
end
|
|
|
|
|
|
# 恢复初始代码
|
|
|
# 注意path为当前打开文件的path
|
|
|
def reset_original_code params, current_user
|
|
|
g = Gitlab.client
|
|
|
path = params[:path]
|
|
|
begin
|
|
|
file_base params[:identifier]
|
|
|
shixun = Shixun.select(:gpid).find(@myshixun.shixun_id)
|
|
|
content = g.files(shixun.gpid, path, "master").try(:content)
|
|
|
content = tran_base64_decode64(content)
|
|
|
g.edit_file(@myshixun.gpid, current_user.login, :content => content, :file_path => path, :branch_name => "master",
|
|
|
:commit_message => "reset_original_code")
|
|
|
if content.nil?
|
|
|
raise("初始代码为空,代码重置失败")
|
|
|
end
|
|
|
return content
|
|
|
rescue Exception => e
|
|
|
raise("#{e.message}")
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def reset_passed_code params, current_user
|
|
|
g = Gitlab.client
|
|
|
path = params[:path]
|
|
|
game= Game.select([:id, :myshixun_id]).where(:identifier => params[:identifier]).first
|
|
|
myshixun = Myshixun.find(game.myshixun_id)
|
|
|
game_code = GameCode.where(:game_id => game.try(:id), :path => path).first
|
|
|
if game_code.present?
|
|
|
content = game_code.try(:new_code)
|
|
|
content = if myshixun.mirror_name.select{|a| a.include?("MachineLearning") || a.include?("Python")}.present? && content.present?
|
|
|
content.gsub(/\t/, ' ')
|
|
|
else
|
|
|
content
|
|
|
end
|
|
|
g.edit_file(myshixun.try(:gpid), current_user.login, :content => content, :file_path => path, :branch_name => "master", :commit_message => "game passed reset")
|
|
|
return content
|
|
|
else
|
|
|
return {:status => -1, :message => "重置失败,代码为空"}
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# 获取版本库文件内容
|
|
|
# 通过版本库的默认分支可以判断版本库是否正常
|
|
|
# 注:如果本身path传错,内容肯定也为空;fork成功后,可能短时间内也获取不到版本库内容
|
|
|
# status =0 表示获取默认打开的内容,1表示右侧目录结构中获取的内容
|
|
|
def rep_content params, current_user
|
|
|
file_base params[:identifier]
|
|
|
# current_user: fork不应该是用当前用户去fork
|
|
|
current_user = @myshixun.owner
|
|
|
g = Gitlab.client
|
|
|
gpid = @myshixun.try(:gpid)
|
|
|
path = @game.challenge.try(:path).split(";")[0].strip()
|
|
|
path = params[:path] ? params[:path] : path
|
|
|
shixun_gpid = params[:shixun_gpid]
|
|
|
status = params[:status]
|
|
|
path = path.try(:strip)
|
|
|
rev = params[:rev] ? params[:rev] : "master"
|
|
|
Rails.logger.warn("path is #{path}")
|
|
|
content = g.files(gpid, path, rev).try(:content)
|
|
|
|
|
|
if content.blank?
|
|
|
# 监测版本库HEAD是否存在,不存在则取最新的HEAD
|
|
|
gitUrl = gitlab_url @myshixun
|
|
|
gitUrl = Base64.urlsafe_encode64(gitUrl)
|
|
|
shixun_tomcat = Redmine::Configuration['shixun_tomcat']
|
|
|
rep_params = {:tpiID => "#{@myshixun.id}", :tpiGitURL => "#{gitUrl}"}
|
|
|
# 监测版本库HEAD是否存在,不存在则取最新的HEAD
|
|
|
uri = "#{shixun_tomcat}/bridge/game/check"
|
|
|
res = uri_exec uri, rep_params
|
|
|
# res值:0 表示正常;-1表示有错误;-2表示代码版本库没了
|
|
|
if (status == 0 && res && res['code'] == -2 && params[:retry].to_i == 1)
|
|
|
# 没有版本库则删除现有的版本库关联,重新fork一个版本库
|
|
|
g = Gitlab.client
|
|
|
if current_user.gid.nil?
|
|
|
s = Trustie::Gitlab::Sync.new
|
|
|
s.sync_user(current_user)
|
|
|
end
|
|
|
gpid = sync_gitlab_rep @myshixun, shixun_gpid, current_user.try(:gid)
|
|
|
content = g.files(gpid, path, rev).content
|
|
|
else
|
|
|
if (res && res['code'] != 0)
|
|
|
Rails.logger.error("get rep_content failed: bridge return !0")
|
|
|
# raise(status)
|
|
|
return nil
|
|
|
end
|
|
|
end
|
|
|
|
|
|
|
|
|
# gitlab缺陷:forked完成,短暂时间内取不了内容的,所以做一个小轮询,间隔0.2秒
|
|
|
# 超过3秒则失败,需通过页面刷新
|
|
|
# for i in 0..15 do
|
|
|
# sleep(0.2)
|
|
|
# content = g.files(gpid, path, rev).content
|
|
|
# break if content.present?
|
|
|
# end
|
|
|
end
|
|
|
|
|
|
content = tran_base64_decode64(content) if content.present?
|
|
|
return content
|
|
|
end
|
|
|
|
|
|
|
|
|
# 编程题评测
|
|
|
def game_build params
|
|
|
# 三类实训只取基础部分数据
|
|
|
game = Game.select([:myshixun_id, :status, :challenge_id, :id, :evaluate_count]).find_by_identifier(params[:identifier])
|
|
|
myshixun = Myshixun.select([:updated_at, :gpid, :id, :shixun_id]).find(game.myshixun_id)
|
|
|
shixun = Shixun.select([:id, :evaluate_script, :webssh, :exec_time, :sigle_training, :identifier, :status]).find(myshixun.shixun_id)
|
|
|
game_challenge = Challenge.select([:id, :position, :picture_path]).find(game.challenge_id)
|
|
|
|
|
|
# 更新评测次数
|
|
|
game.update_column(:evaluate_count, (game.evaluate_count.to_i + 1))
|
|
|
# 清空代码评测信息
|
|
|
msg = game.run_code_message
|
|
|
msg.update_attributes(:status => 0, :message => nil) if msg.present?
|
|
|
# 更新时间是为了TPM端显示的更新,退出实训及访问实训的时候会更新
|
|
|
myshixun.update_column(:updated_at, Time.now)
|
|
|
|
|
|
gitUrl = gitlab_url myshixun
|
|
|
gitUrl = Base64.urlsafe_encode64(gitUrl)
|
|
|
taskId = game.id
|
|
|
shixun_tomcat = Redmine::Configuration['shixun_tomcat']
|
|
|
step = game_challenge.try(:position)
|
|
|
mirror_repository_limit = shixun.mirror_repositories.where(:main_type => 1).select(:resource_limit).try(:first).try(:resource_limit)
|
|
|
# mirror表中很很大的脚本字段,所以单独查询一个字段效果更好
|
|
|
resource_limit = "echo 'ulimit -f #{mirror_repository_limit}' >> /root/.bashrc ; source /root/.bashrc\n"
|
|
|
tpmScript = shixun.evaluate_script.nil? ? "" : Base64.urlsafe_encode64((resource_limit + shixun.evaluate_script).gsub("\r\n", "\n"))
|
|
|
|
|
|
# status为2已经通过关,是重新评测
|
|
|
if game.status == 2
|
|
|
resubmit = params[:resubmit]
|
|
|
else
|
|
|
# 重新评测不影响已通关的实训状态;first为第一次评测,通过前端JS轮询获取
|
|
|
game.update_attributes!(:status => 1) if params[:first].to_i == 1
|
|
|
end
|
|
|
|
|
|
testSet = []
|
|
|
game_challenge.test_sets.each do |test_set|
|
|
|
input = test_set.input.nil? ? "" : test_set.input.gsub("\r\n", "\n")
|
|
|
output = test_set.output.nil? ? "" : test_set.output.gsub("\r\n", "\n")
|
|
|
test_cases = {:input => input, :output => output}
|
|
|
testSet << test_cases
|
|
|
end
|
|
|
|
|
|
testCases = Base64.urlsafe_encode64(testSet.to_json) unless testSet.blank?
|
|
|
|
|
|
|
|
|
# 注意:这个地方的参数写的时候不能换行
|
|
|
content_modified = params[:content_modified] # 决定文件内容是否有修改,有修改如果中间成pull没有更新,则轮询等待更新
|
|
|
params = {:tpiID => "#{myshixun.id}", :tpiGitURL => "#{gitUrl}", :buildID => "#{taskId}",:instanceChallenge => "#{step}",
|
|
|
:testCases => "#{testCases}", :resubmit => "#{resubmit}", :times => params[:first].to_i, :podType => shixun.webssh,
|
|
|
:containers => "#{Base64.urlsafe_encode64(container_limit(shixun.mirror_repositories))}", :tpmScript => "#{tpmScript}",
|
|
|
:timeLimit => "#{shixun.exec_time}", :content_modified => content_modified, :persistenceName => shixun.identifier,
|
|
|
:isPublished => (shixun.status < 2 ? 0 : 1), :sec_key => params[:sec_key]}
|
|
|
|
|
|
# 评测有文件输出的需要特殊传字段 path:表示文件存储的位置
|
|
|
params['file'] = Base64.urlsafe_encode64({:path => "#{game_challenge.picture_path}"}.to_json) if game_challenge.picture_path.present?
|
|
|
|
|
|
# needPortMapping: web类型需要pod端口映射
|
|
|
params[:needPortMapping] = 8080 if myshixun.mirror_name.include?("Web")
|
|
|
|
|
|
# 中间层交互
|
|
|
#if shixun.sigle_training
|
|
|
# uri = "#{shixun_tomcat}/bridge/game/persistence/gameEvaluate"
|
|
|
#else
|
|
|
uri = "#{shixun_tomcat}/bridge/game/gameEvaluate"
|
|
|
#end
|
|
|
|
|
|
res = interface_post uri, params, 502
|
|
|
# 单评测类型(比较快的类型,实时返回结果,即不用中间层再回调trainint_task_status)
|
|
|
if res['syncResult'].present?
|
|
|
sigle_trainint_data res, myshixun, game
|
|
|
end
|
|
|
# ----单测模式end
|
|
|
|
|
|
return {:result => "success", :resubmit => resubmit, :ableToCreate => res['ableToCreate'], :waitNum => res['waitNum'],
|
|
|
:waitingTime => res['waitingTime'], :position => game_challenge.position, :port => res['port'],
|
|
|
:had_done => game.had_done}
|
|
|
rescue Exception => e
|
|
|
Rails.logger.error("评测出错,详情:" + e.message)
|
|
|
return {:result => 'fail', :contents =>"实训云平台繁忙(繁忙等级:502),请稍后刷新并重试", :position => game_challenge.position, :had_done => game.had_done}
|
|
|
end
|
|
|
|
|
|
# 单评测类型(比较快的类型,实时返回结果,即不用中间层再回调trainint_task_status)
|
|
|
def sigle_trainint_data res, myshixun, game
|
|
|
begin
|
|
|
t1 = Time.now
|
|
|
jsonTestDetails = JSON.parse(res['syncResult']['jsonTestDetails'])
|
|
|
Rails.logger.info("res is #{res} ### jsonTestDetails is #{jsonTestDetails}")
|
|
|
timeCost = JSON.parse(res['syncResult']['timeCost'])
|
|
|
brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present?
|
|
|
return_back_time = format("%.3f", ( t1.to_f - brige_end_time.to_f)).to_f
|
|
|
status = jsonTestDetails['status']
|
|
|
game_id = jsonTestDetails['buildID']
|
|
|
Rails.logger.info("sigle training_task_status start#1**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
|
|
|
resubmit = jsonTestDetails['resubmit']
|
|
|
outPut = tran_base64_decode64(jsonTestDetails['outPut'])
|
|
|
jenkins_testsets = jsonTestDetails['msg']
|
|
|
compile_success = jsonTestDetails['compileSuccess']
|
|
|
Rails.logger.info(outPut)
|
|
|
challenge = game.challenge
|
|
|
if challenge.picture_path.present?
|
|
|
pics = res[:tpiRepoPath]
|
|
|
game.update_column(:picture_path, pics)
|
|
|
end
|
|
|
Rails.logger.info("training_task_status start#2**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
|
|
|
unless jenkins_testsets.blank?
|
|
|
jenkins_testsets.each do |j_test_set|
|
|
|
Rails.logger.info("j_test_set: ############## #{j_test_set}")
|
|
|
actual_output = tran_base64_decode64(j_test_set['output'])
|
|
|
Rails.logger.info "actual_output:################################################# #{actual_output}"
|
|
|
Output.create!(:code => status, :game_id => game_id, :out_put => outPut, :test_set_position => j_test_set['caseId'],
|
|
|
:actual_output => actual_output, :result => j_test_set['passed'].to_i, :query_index => game.query_index,
|
|
|
:compile_success => compile_success.to_i)
|
|
|
end
|
|
|
end
|
|
|
Rails.logger.info("#############status: #{status}")
|
|
|
record = EvaluateRecord.where(:game_id => game_id).first
|
|
|
Rails.logger.info("training_task_status start#3**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
|
|
|
|
|
|
# status:0表示评测成功
|
|
|
if status == "0"
|
|
|
if !resubmit.blank?
|
|
|
game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit)
|
|
|
#if game.had_done == 1
|
|
|
challenge.path.split(";").each do |path|
|
|
|
game_passed_code(game.id, path.try(:strip), myshixun.try(:gpid), 1)
|
|
|
end
|
|
|
#end
|
|
|
else
|
|
|
game.update_attributes!(:status => 2, :end_time => Time.now, :accuracy => format("%.4f", 1.0 / game.query_index))
|
|
|
myshixun.update_attributes!(:status => 1) if game.had_done == 1
|
|
|
challenge.path.split(";").each do |path|
|
|
|
game_passed_code(game.id, path, myshixun.try(:gpid), 1)
|
|
|
end
|
|
|
if !game.answer_open && (challenge.shixun.try(:status) > 1) # 如果没有查看答案,则获得该关卡得分
|
|
|
reward_grade(game.user, game.id, 'Game', challenge.score)
|
|
|
reward_experience(game.user, game.id, 'Game', challenge.score)
|
|
|
game.update_attributes!(:final_score => challenge.score)
|
|
|
end
|
|
|
|
|
|
# 更新实训关联的作品分数
|
|
|
update_myshixun_work_score myshixun
|
|
|
end
|
|
|
# 如果过关了,下一关的状态是3(为开启),则需要把状态改成1(已开启)
|
|
|
# next_game = game.next_game
|
|
|
next_game = Game.next_game(myshixun.shixun_id, game.myshixun_id, challenge.position)
|
|
|
next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3
|
|
|
else # status == "-1" 表示返回结果错误
|
|
|
if !resubmit.blank?
|
|
|
game.update_attributes!(:retry_status => 1, :resubmit_identifier => resubmit)
|
|
|
else
|
|
|
game.update_attributes!(:status => 0)
|
|
|
end
|
|
|
end
|
|
|
test_cases_time = format("%.3f", (Time.now.to_f - t1.to_f)).to_f
|
|
|
if record.present?
|
|
|
consume_time = format("%.3f", (Time.now - record.created_at)).to_f
|
|
|
record.update_attributes!(:consume_time => consume_time, :git_pull => timeCost['pull'] ,
|
|
|
:create_pod => timeCost['createPod'], :pod_execute => timeCost['execute'], :test_cases => test_cases_time,
|
|
|
:brige => timeCost['evaluateAllTime'], :return_back => return_back_time)
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# 选择题评测
|
|
|
def choose_build params
|
|
|
# 三类实训只取基础部分数据
|
|
|
game = Game.select([:myshixun_id, :status, :challenge_id, :id, :answer_open, :final_score, :identifier,
|
|
|
:evaluate_count]).find_by_identifier(params[:identifier])
|
|
|
# 更新评测次数
|
|
|
game.update_column(:evaluate_count, (game.evaluate_count.to_i + 1))
|
|
|
game.challenge.shixun.increment!(:evaluate_count)
|
|
|
|
|
|
# 选择题如果通关了,则不让再评测
|
|
|
if game.status == 2
|
|
|
return {:message => "您已通过该关卡"}
|
|
|
end
|
|
|
|
|
|
myshixun = Myshixun.select([:updated_at, :gpid, :id, :shixun_id, :status, :user_id]).find(game.myshixun_id)
|
|
|
shixun = Shixun.select([:id, :evaluate_script, :webssh, :status]).find(myshixun.shixun_id)
|
|
|
game_challenge = Challenge.select([:id, :position]).find(game.challenge_id)
|
|
|
user_answer = params[:answer]
|
|
|
challenge_chooses_count = user_answer.length
|
|
|
|
|
|
choose_correct_num = 0
|
|
|
score = 0
|
|
|
total_score = 0
|
|
|
had_passed = true
|
|
|
test_sets = []
|
|
|
str = ""
|
|
|
game_challenge.challenge_chooses.includes(:challenge_tags).each_with_index do |choose, index|
|
|
|
user_answer_tran = user_answer[index].size > 1 ? user_answer[index].split("").sort.join("") : user_answer[index]
|
|
|
standard_answer_tran = choose.standard_answer.size > 1 ? choose.standard_answer.split("").sort.join("") : choose.standard_answer
|
|
|
correct = (user_answer_tran == standard_answer_tran) ? true : false
|
|
|
if str.present?
|
|
|
str += ","
|
|
|
end
|
|
|
str += "('#{game.id}', '#{choose.position}', '#{user_answer_tran}', '#{correct ? 1 : 0}', '#{game.query_index}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
|
|
|
# Output.create(:game_id => @game.id, :test_set_position => choose.position, :actual_output => params[:answer][index], :result => correct, :query_index => @game.query_index)
|
|
|
# 只要有一题错误就不能通关
|
|
|
had_passed = false if !correct
|
|
|
choose_correct_num += 1 if correct
|
|
|
|
|
|
# 全部通关的时候,需要对所得的总分记录
|
|
|
# 总分的记录应该是根据每一题累加,如果该题正确,则加分
|
|
|
score += choose.score if correct
|
|
|
|
|
|
standard_answer = correct ? standard_answer_tran : -1
|
|
|
sin_test_set = {:result => correct, :actual_output => user_answer_tran, :standard_answer => standard_answer, :position => choose.position}
|
|
|
test_sets << sin_test_set
|
|
|
end
|
|
|
|
|
|
# 批量插入评测结果
|
|
|
Rails.logger.warn("sql str is #{str}")
|
|
|
sql = "INSERT INTO outputs (game_id, test_set_position, actual_output, result, query_index, created_at, updated_at) VALUES" + str
|
|
|
ActiveRecord::Base.connection.execute sql
|
|
|
|
|
|
|
|
|
# 没通关或者查看了答案通关的时候经验为0
|
|
|
# 通关但是查看了答案,评测的时候金币显示0(避免用户以为重复扣除),但是在关卡列表中金币显示负数
|
|
|
experience = 0
|
|
|
final_score = 0
|
|
|
# 如果本次答题全部正确,并且之前没有通关,则进行相应的奖励,每关只要有错题,则不进行任何奖励
|
|
|
# 注:扣除分数是在查看答案的时候扣除的
|
|
|
if had_passed && !game.had_passed?
|
|
|
game.update_attributes(:status => 2, :end_time => Time.now)
|
|
|
# TPM实训已发布并且没有查看答案
|
|
|
if shixun.is_published? && game.answer_open == 0
|
|
|
# 查看答案的时候处理final_scor和扣分记录
|
|
|
experience = score
|
|
|
reward_grade(myshixun.owner, game.id, 'Game', score)
|
|
|
game.update_attribute(:final_score, score)
|
|
|
final_score = score
|
|
|
reward_experience(myshixun.owner, game.id, 'Game', score)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
had_done = game.had_done
|
|
|
myshixun.update_attribute(:status, 1) if had_done == 1
|
|
|
|
|
|
grade = myshixun.owner.try(:grade)
|
|
|
|
|
|
# 更新实训关联的作品分数
|
|
|
update_myshixun_work_score myshixun
|
|
|
|
|
|
# 高性能取上一关、下一关
|
|
|
prev_game = Game.prev_identifier(shixun.id, game.myshixun_id, game_challenge.position)
|
|
|
next_game = Game.next_game(shixun.id, game.myshixun_id, game_challenge.position)
|
|
|
# 如果过关了,下一关的状态是3(为开启),则需要把状态改成1(已开启)
|
|
|
next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3
|
|
|
|
|
|
return {:grade => grade, :gold => final_score, :experience => experience, :had_submmit => true,
|
|
|
:challenge_chooses_count => challenge_chooses_count, :choose_correct_num => choose_correct_num,
|
|
|
:test_sets => test_sets, :prev_game => prev_game, :next_game => next_game.try(:identifier)}
|
|
|
end
|
|
|
|
|
|
# 实训选择题需要局部刷新或者显示的部分
|
|
|
def choose_container game_challenge, game, max_query_index, just_show
|
|
|
# category 1: 单选题,其它的多选题(目前只有两种)
|
|
|
challenge_chooses = game_challenge.challenge_chooses.includes(:challenge_questions)
|
|
|
test_sets = []
|
|
|
choose_test_cases = []
|
|
|
chooses = []
|
|
|
|
|
|
# 选择题测试集统计
|
|
|
challenge_chooses_count = challenge_chooses.count
|
|
|
choose_correct_num = game.choose_correct_num
|
|
|
game_outputs = game.outputs.where(:query_index => max_query_index)
|
|
|
|
|
|
# 判断用户是否有提交
|
|
|
had_submmit = game_outputs.blank? ? false : true
|
|
|
|
|
|
# 判断选择题是否写了标准答案
|
|
|
has_answer = []
|
|
|
challenge_chooses.each do |choose|
|
|
|
challenge_question = []
|
|
|
output = game_outputs.select{|game_output| game_output.test_set_position == choose.position}[0] unless game_outputs.blank?
|
|
|
|
|
|
category = choose.category
|
|
|
subject = choose.subject
|
|
|
choose.challenge_questions.each do |question|
|
|
|
position = question.position
|
|
|
option_name = question.option_name
|
|
|
challenge_question << {:positon => position, :option_name => option_name}
|
|
|
end
|
|
|
# actual_output为空表示暂时没有评测答题,不云讯查看
|
|
|
actual_output = output.try(:actual_output).try(:strip)
|
|
|
has_answer << choose.answer if choose.answer.present?
|
|
|
# 标准答案处理,错误的不让用户查看,用-1替代
|
|
|
standard_answer = (actual_output.blank? || !output.try(:result)) ? -1 : choose.standard_answer
|
|
|
result = output.try(:result)
|
|
|
sin_test_set = {:result => result, :actual_output => actual_output, :standard_answer => standard_answer, :position => choose.position}
|
|
|
|
|
|
sin_choose = {:category => category, :subject => subject, :challenge_question => challenge_question}
|
|
|
chooses << sin_choose
|
|
|
test_sets << sin_test_set
|
|
|
end
|
|
|
has_answer = has_answer.present?
|
|
|
choose_test_cases = {:had_submmit => had_submmit, :challenge_chooses_count => challenge_chooses_count, :choose_correct_num => choose_correct_num, :test_sets => test_sets}
|
|
|
return {:choose => chooses, :choose_test_cases => choose_test_cases, :has_answer => has_answer}
|
|
|
end
|
|
|
|
|
|
# 轮询获取状态
|
|
|
# resubmit是在file_update中生成的,从game_build中传入的
|
|
|
def game_status params, current_user
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**1")
|
|
|
game = Game.find_by_identifier(params[:identifier])
|
|
|
resubmit_identifier = game.resubmit_identifier
|
|
|
# 如果没有超时并且正在评测中
|
|
|
# 判断评测中的状态有两种:1、如果之前没有通关的,只需判断status为1即可;如果通过关,则判断game的resubmit_identifier是否更新
|
|
|
if (params[:time_out] == "false") && ((params[:resubmit].blank? && game.status==1) || (params[:resubmit].present? && (params[:resubmit] != resubmit_identifier)))
|
|
|
# 代码评测的信息
|
|
|
running_code_status = game.run_code_message.try(:status)
|
|
|
running_code_message = game.run_code_message.try(:message)
|
|
|
return {:running_code_status => running_code_status, :running_code_message => running_code_message}
|
|
|
end
|
|
|
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**2")
|
|
|
Rails.logger.info("##### resubmit_identifier is #{resubmit_identifier}")
|
|
|
port = params[:port]
|
|
|
score = 0
|
|
|
experience = 0
|
|
|
game_status = game.status
|
|
|
had_done = game.had_done
|
|
|
game_challenge = Challenge.select([:id, :score, :position, :shixun_id, :web_route]).find(game.challenge_id)
|
|
|
shixun = Shixun.select([:id, :status, :user_id, :test_set_permission]).find(game_challenge.shixun_id)
|
|
|
|
|
|
if params[:resubmit].blank? # 非重新评测
|
|
|
if game_status == 2 # 通关
|
|
|
if shixun.status > 1
|
|
|
score = game.final_score # 查看答案的时候有对最终获得金币进行处理
|
|
|
experience = game.answer_open > 0 ? 0 : game_challenge.score.to_i
|
|
|
else
|
|
|
score = 0
|
|
|
experience = 0
|
|
|
end
|
|
|
end
|
|
|
else # 重新评测
|
|
|
# 如果满足前面的条件,进入此处只可能是结果已返回并存入了数据库
|
|
|
if params[:resubmit] == resubmit_identifier # 本次重新评测结果已经返回并存入数据库
|
|
|
game_status = (game.retry_status == 2 ? 2 : 0) # retry_status是判断重新评测的通关情况。2表示通关
|
|
|
end
|
|
|
end
|
|
|
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**3")
|
|
|
# 实训的最大评测次数,这个值是为了优化查询,每次只取最新的最新一次评测的结果集
|
|
|
max_query_index = game.query_index - 1
|
|
|
# 区分评测过未评测过,未评测过按需求取数据
|
|
|
if max_query_index > 0
|
|
|
qurey_test_sets = TestSet.find_by_sql("SELECT o.code, o.actual_output,o.result, o.compile_success, o.test_set_position,
|
|
|
o.query_index,t.is_public,t.input, t.output, g.id as game_id, c.id as challenge_id FROM outputs o,games g ,challenges c,test_sets t where
|
|
|
g.id=#{game.id} and o.query_index=#{max_query_index} and g.id = o.game_id and c.id= g.challenge_id and t.challenge_id = c.id and t.position =o.test_set_position order by o.query_index
|
|
|
")
|
|
|
else
|
|
|
qurey_test_sets = TestSet.find_by_sql("SELECT t.is_public,t.input, t.output,t.position FROM games g ,challenges c,test_sets t where
|
|
|
g.id=#{game.id} and c.id= g.challenge_id and t.challenge_id = c.id
|
|
|
")
|
|
|
end
|
|
|
|
|
|
|
|
|
# 能进入到此处,肯定是已经返回了结果,也就是说outputs中肯定有了数据
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**4")
|
|
|
test_sets_count = qurey_test_sets.size
|
|
|
# had_test = Output.where(:game_id => game.id, :query_index => max_query_index)
|
|
|
# had_test_count = had_test.count
|
|
|
had_passed_testsests_error_count = max_query_index > 0 ? qurey_test_sets.select{|qurey_test_set| qurey_test_set.result == false}.count : 0
|
|
|
had_passed_testsests_error_count = test_sets_count - had_passed_testsests_error_count
|
|
|
|
|
|
had_test = Output.where(:game_id => game.id, :query_index => max_query_index)
|
|
|
error_position = had_test.blank? ? nil : had_test.select{|had_test| had_test.result == false}.last
|
|
|
|
|
|
Rails.logger.info("latest output id is #{error_position.id unless error_position.blank?}")
|
|
|
latest_output = error_position.try(:out_put).gsub(/\n/, '<br/>').gsub(/\t/, " \; \; \; \; \; \; \; \;") if error_position.try(:out_put).present?
|
|
|
Rails.logger.warn("last_output is #{latest_output}")
|
|
|
|
|
|
# power判断用户是否有权限查看隐藏测试集(TPM管理员;平台认证的老师;花费金币查看者)
|
|
|
power = (shixun_manager(shixun, current_user) || (current_user.is_certification_teacher)) ? 1 : (shixun.test_set_permission ? 0 : -1)
|
|
|
# 测试集统计及处理
|
|
|
unless qurey_test_sets.blank?
|
|
|
check_power = (power == 1 || game.test_sets_view)
|
|
|
test_sets = test_set_static_data(qurey_test_sets, check_power)
|
|
|
end
|
|
|
# 处理生成图片类型文件
|
|
|
picture = (game.picture_path.nil? ? 0 : game.id)
|
|
|
# 针对web类型的实训
|
|
|
web_route = game_challenge.try(:web_route)
|
|
|
mirror_name = shixun.mirror_name
|
|
|
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**5")
|
|
|
# 轮询结束,更新评测耗时
|
|
|
e_record = EvaluateRecord.where(:identifier => params[:sec_key]).first
|
|
|
if game_status == 0 || game_status == 2
|
|
|
if e_record
|
|
|
front_js = format("%.3f", (Time.now.to_f - e_record.try(:updated_at).to_f)).to_f
|
|
|
consume_time = format("%.3f", (Time.now - e_record.created_at)).to_f
|
|
|
e_record.update_attributes(:consume_time => consume_time, :front_js => front_js)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
Rails.logger.warn("##game is is #{game.id}, record id is #{e_record.id}, time is**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
|
|
|
# 记录前端总耗时
|
|
|
record = e_record.try(:consume_time)
|
|
|
|
|
|
# 实训制作者当前拥有的金币
|
|
|
grade = User.where(:id => game.user_id).pluck(:grade).first
|
|
|
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**6")
|
|
|
# 高性能取上一关、下一关
|
|
|
prev_game = Game.prev_identifier(shixun.id, game.myshixun_id, game_challenge.position)
|
|
|
next_game = Game.next_game(shixun.id, game.myshixun_id, game_challenge.position).try(:identifier)
|
|
|
|
|
|
output_hash = {:test_sets => test_sets, :had_test_count => test_sets_count, :test_sets_count => test_sets_count, :had_passed_testsests_error_count => had_passed_testsests_error_count}
|
|
|
|
|
|
Rails.logger.info("sec_key is #{params[:sec_key]}**7")
|
|
|
return {:grade => grade, :gold => score, :experience => experience, :status => game_status, :had_done => had_done,
|
|
|
:position => game_challenge.position, :port => port, :power => power, :record => record,
|
|
|
:mirror_name => mirror_name, :picture => picture, :web_route => web_route, :latest_output => latest_output,
|
|
|
:star => game.star, :next_game => next_game, :prev_game => prev_game}.merge(output_hash)
|
|
|
end
|
|
|
|
|
|
# 记录实训花费的时间,前端是通过ent_time - open_time,所以最终只更新open_time即可
|
|
|
# 总花费的时间是花费的总时间加上开启的时间(open_time)
|
|
|
# 注意:endtime和当前时间没关系,非时间差值,必须和opentime结合使用才有意义
|
|
|
def cost_time params
|
|
|
game = Game.select([:id, :cost_time, :identifier]).find_by_identifier(params[:identifier])
|
|
|
cost_time = params[:time].to_i
|
|
|
game.update_attribute(:cost_time, cost_time)
|
|
|
return{ :game => game}
|
|
|
end
|
|
|
|
|
|
def sync_modify_time params
|
|
|
game = Game.select([:id, :modify_time, :challenge_id]).find_by_identifier(params[:identifier])
|
|
|
modify_time = Challenge.where(:id => game.challenge_id).pluck(:modify_time).first
|
|
|
game.update_column(:modify_time, modify_time)
|
|
|
return {:status => 1, :message => "success"}
|
|
|
end
|
|
|
|
|
|
# tpi弹框状态更新;true则不再显示;false每次刷新显示
|
|
|
def system_update params
|
|
|
myshixun = Myshixun.find(params[:myshixun_id])
|
|
|
myshixun.update_attribute(:system_tip, true)
|
|
|
return {:status => 1, :message => "success"}
|
|
|
end
|
|
|
|
|
|
# For admin
|
|
|
# 强制重置实训
|
|
|
def reset_my_game params, current_user
|
|
|
unless current_user.admin?
|
|
|
return {:status => 403, :message => "unauthorized"}
|
|
|
end
|
|
|
myshixun = Myshixun.find(Game.where(:identifier => params[:identifier]).pluck(:myshixun_id).first)
|
|
|
shixun = myshixun.shixun
|
|
|
ActiveRecord::Base.transaction do
|
|
|
begin
|
|
|
g = Gitlab.client
|
|
|
shixun_tomcat = Redmine::Configuration['shixun_tomcat']
|
|
|
challenges = shixun.challenges
|
|
|
# 删除选择题用户记录
|
|
|
unless challenges.blank?
|
|
|
challenges.each do |challenge|
|
|
|
if challenge.st != 0
|
|
|
challenge.challenge_chooses.each do |choose|
|
|
|
user_output = choose.choose_outputs
|
|
|
unless user_output.blank?
|
|
|
user_output.delete
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
#end
|
|
|
myshixun_job = Base64.urlsafe_encode64("myshixun_#{myshixun.id}")
|
|
|
StudentWork.where(:myshixun_id => myshixun.id).update_all(:myshixun_id => 0, :work_status => 0, :work_score => nil, :final_score => nil,
|
|
|
:cost_time => 0, :update_time => nil, :compelete_status => 0, :commit_time => nil)
|
|
|
# myshixun.destroy
|
|
|
myshixun.delete
|
|
|
# 主从复制出现脏读的情况
|
|
|
if myshixun.gpid
|
|
|
g.delete_project(myshixun.gpid)
|
|
|
end
|
|
|
rescue Exception => e
|
|
|
Rails.logger.error("myshixun reset failed #{e}")
|
|
|
raise ActiveRecord::Rollback
|
|
|
end
|
|
|
end
|
|
|
# 主从复制出现脏读的情况
|
|
|
sleep(1)
|
|
|
return {:status => 1, :message => "sucess", :url => "/shixuns/#{shixun.identifier}"}
|
|
|
end
|
|
|
|
|
|
def close_webssh params, current_user
|
|
|
myshixun_id = Game.where(:identifier => params[:identifier]).pluck(:myshixun_id).first
|
|
|
digest = params[:identifier]+Redmine::Configuration['bridge_secret_key']
|
|
|
digest_key = Digest::SHA1.hexdigest("#{digest}")
|
|
|
begin
|
|
|
shixun_tomcat = Redmine::Configuration['shixun_tomcat']
|
|
|
uri = "#{shixun_tomcat}/bridge/webssh/delete"
|
|
|
Rails.logger.info("#{current_user} => cloese_webssh digest is #{digest}")
|
|
|
params = {:tpiID => myshixun_id, :digestKey => digest_key, :identifier => params[:identifier]}
|
|
|
res = uri_exec uri, params
|
|
|
if res && res['code'].to_i != 0
|
|
|
raise("实训云平台繁忙(繁忙等级:110)")
|
|
|
end
|
|
|
return {:status => 1, :message => "webssh closed"}
|
|
|
rescue Exception => e
|
|
|
Rails.logger.error(e)
|
|
|
return {:status => 0, :message => "authentication failed"}
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# return: 头像,姓名,用户主页链接,学校, 点赞数, 等级(通关数)
|
|
|
def get_passed_user params
|
|
|
page = params[:page].to_i
|
|
|
offset = page * 15
|
|
|
file_base params[:identifier]
|
|
|
games = @game.challenge.games.where(:status => 2).includes(user: [:myshixuns, user_extensions: :school]).order("cost_time desc").offset(offset).limit(15)
|
|
|
users = format_answer_list games
|
|
|
{user_answer_list: users}
|
|
|
end
|
|
|
|
|
|
def get_passed_code params
|
|
|
file_base params[:identifier]
|
|
|
codes = @game.game_codes.select([:new_code, :path]).where("new_code is not null")
|
|
|
code_list = []
|
|
|
codes.each do |code|
|
|
|
code_list << {path: code.path, code: code.new_code}
|
|
|
end
|
|
|
{code_list: code_list}
|
|
|
end
|
|
|
|
|
|
|
|
|
def shixun_manager shixun, user_current
|
|
|
member = shixun.shixun_members.where(:role => [1,2], :user_id => user_current.id)
|
|
|
(!member.blank? || user_current.admin?) ? true : false
|
|
|
end
|
|
|
|
|
|
# 实训一些基础查询
|
|
|
def game_base identifier
|
|
|
@game = Game.select([:myshixun_id, :status, :final_score, :answer_open, :challenge_id, :id]).find_by_identifier(identifier)
|
|
|
@myshixun = Myshixun.select([:id, :shixun_id, :gpid, :user_id]).find(@game.myshixun_id)
|
|
|
@shixun = Shixun.select([:status, :id]).find(@myshixun.shixun_id)
|
|
|
end
|
|
|
|
|
|
# 与文件代码相关的一些通用查询
|
|
|
def file_base identifier
|
|
|
@game = Game.select([:myshixun_id, :challenge_id, :id]).find_by_identifier(identifier)
|
|
|
@myshixun = Myshixun.select([:shixun_id, :gpid, :id, :user_id]).find(@game.myshixun_id)
|
|
|
end
|
|
|
|
|
|
private
|
|
|
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 generates_identifier(container, num)
|
|
|
code = DCODES.sample(num).join
|
|
|
while container.exists?(identifier: code) do
|
|
|
code = DCODES.sample(num).join
|
|
|
end
|
|
|
code
|
|
|
end
|
|
|
|
|
|
def format_answer_list games
|
|
|
user_info = []
|
|
|
games.each do |game|
|
|
|
user = game.user
|
|
|
votes = game.game_codes.first.votes
|
|
|
passed_count = user.myshixuns.where(:status => 1).count
|
|
|
user_info << {
|
|
|
user_id: user.id,
|
|
|
name: user.show_name,
|
|
|
game_id: game.id,
|
|
|
image_url: url_to_avatar(user),
|
|
|
school_name: user.school_name,
|
|
|
votes: votes,
|
|
|
passed_count: passed_count
|
|
|
}
|
|
|
end
|
|
|
user_info
|
|
|
end
|
|
|
end |