class GamesController < ApplicationController
before_action :require_login
before_action :find_game
before_action :find_shixun , only : [ :show , :answer , :rep_content , :choose_build , :game_build , :game_status ]
before_action :allowed
#require 'iconv'
include GamesHelper
include ApplicationHelper
def show
uid_logger ( " --games show start " )
# 防止评测中途ajaxE被取消;3改成0是为了处理首次进入下一关的问题
@game . update_attribute ( :status , 0 ) if @game . status == 1
@game . update_attributes ( status : 0 , open_time : Time . now ) if @game . status == 3
game_challenge = Challenge . base_attrs . find ( @game . challenge_id )
# 选择题类型的实训关卡总分
@st = game_challenge . st
if @st == 1
game_challenge . score = game_challenge . choose_score . to_i
end
game_count = Game . where ( myshixun_id : @game . myshixun_id ) . count
discusses = @shixun . discusses
discusses = discusses . where ( 'hidden = false OR user_id = :user_id' , user_id : current_user . id ) unless current_user . admin?
discusses_count = discusses . count
@user = @game . owner
is_teacher = @user . is_teacher?
# 实训超时设置
time_limit = @shixun . exec_time
# 上一关、下一关
prev_game = @game . prev_of_current_game ( @shixun . id , @game . myshixun_id , game_challenge . position )
next_game = @game . next_of_current_game ( @shixun . id , @game . myshixun_id , game_challenge . position )
# 关卡点赞数, 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 . to_i
# 统计评测时间
record_onsume_time = EvaluateRecord . where ( game_id : @game . id ) . first . try ( :consume_time )
# power判断用户是否有权限查看隐藏测试集(TPM管理员; 平台认证的老师; 花费金币查看者)
# myshixun_manager
myshixun_manager = current_user . manager_of_shixun? ( @shixun ) || ( current_user . is_teacher? && current_user . pro_certification? )
# 选择题和编程题公共部分
@base_date = { st : @st , discusses_count : discusses_count , game_count : game_count , myshixun : @myshixun ,
challenge : game_challenge . attributes . except ( " answer " ) , game : @game . try ( :attributes ) , shixun : @shixun . try ( :attributes ) ,
record_onsume_time : record_onsume_time , prev_game : prev_game , next_game : next_game ,
praise_count : praise_count , user_praise : user_praise , time_limit : time_limit ,
tomcat_url : edu_setting ( 'cloud_tomcat_php' ) , is_teacher : is_teacher ,
myshixun_manager : myshixun_manager }
if @shixun . vnc
begin
shixun_tomcat = edu_setting ( 'cloud_bridge' )
service_host = edu_setting ( 'vnc_url' )
uri = " #{ shixun_tomcat } /bridge/vnc/getvnc "
params = { tpiID : @myshixun . id , :containers = > " #{ Base64 . urlsafe_encode64 ( container_limit ( @shixun . mirror_repositories ) ) } " }
res = uri_post uri , params
if res && res [ 'code' ] . to_i != 0
raise ( " 实训云平台繁忙( 繁忙等级: 99) " )
end
@vnc_url = " http:// #{ service_host } : #{ res [ 'port' ] } /vnc_lite.html?password=headless "
rescue Exception = > e
Rails . logger . error ( e . message )
end
end
# 区分选择题和编程题, st: 0编程题;
if @st == 0
has_answer = game_challenge . challenge_answers . size == 0 ? false : true
game_challenge . answer = nil
mirror_name = @shixun . mirror_name
# 判断tpm是否修改了
begin
tpm_modified = @myshixun . repository_is_modified ( @shixun . repo_path ) # 判断TPM和TPI的版本库是否被改了
rescue
uid_logger ( " 实训平台繁忙, 繁忙等级( 81) " )
end
tpm_cases_modified = ( game_challenge . modify_time != @game . modify_time ? true : false ) # modify_time 决定TPM测试集是否有更新
@task_result = { tpm_modified : tpm_modified , tpm_cases_modified : tpm_cases_modified , mirror_name : mirror_name , has_answer : has_answer }
testset_detail max_query_index , game_challenge
else # 选择题类型的
# 该方法多个地方调用, 比如show、评测
# 最后一个字段true表示只显示数据, false表示可能有添加数据
choose_container ( game_challenge , @game , max_query_index )
end
end
# 查看效果
# todo : 这块代码有很大的改进空间
# todo : 中文排序问题
def picture_display
myshixun = Myshixun . find ( @game . myshixun_id )
if myshixun . main_mirror . try ( :type_name ) == " Android "
@type = " qrcode "
workspace = @game . try ( :picture_path )
game_challenge = @game . challenge
qr = RQRCode :: QRCode . new ( " #{ edu_setting ( 'host_name' ) } /api/shixuns/download_file?file_name= #{ workspace } / #{ game_challenge . picture_path } /manual-ok.apk " , :size = > 12 , :level = > :h )
@qrcode_str = Base64 . encode64 ( qr . to_img . resize ( 400 , 400 ) . to_s )
else
@type = " image "
#conv = Iconv.new("GBK", "utf-8")
@game_challenge = @game . challenge
type = @game_challenge . show_type
workspace_path = @game . try ( :picture_path )
@answer_path = " #{ Rails . root } / #{ workspace_path } / #{ @game_challenge . expect_picture_path } "
@user_path = " #{ Rails . root } / #{ workspace_path } / #{ @game_challenge . picture_path } "
@original_path = " #{ Rails . root } / #{ workspace_path } / #{ @game_challenge . original_picture_path } "
@answer_picture = @game_challenge . expect_picture_path . nil? ? [ ] : get_dir_filename ( @answer_path , type , @game . id )
#@answer_picture = @answer_picture.sort {|x, y| conv.iconv(x) <=> conv.iconv(y)} if @answer_picture.present?
@user_picture = @game_challenge . picture_path . nil? ? [ ] : get_dir_filename ( @user_path , type , @game . id )
#@user_picture = @user_picture.sort {|x, y| conv.iconv(x) <=> conv.iconv(y)}
if @game_challenge . original_picture_path . blank?
@orignal_picture = nil
else
@orignal_picture = @game_challenge . original_picture_path . nil? ? [ ] : get_dir_filename ( @original_path , type , @game . id )
#@orignal_picture = @orignal_picture.sort {|x, y| conv.iconv(x) <=> conv.iconv(y)}
end
end
end
# 同步更新最新代码
# 同步完成后, 需要更新myshixun的commit_id为实训的commit最新值
# --------------------------
# 新思路, 有冲突则则重置, 没有冲突直接pull
# identifier为password---
# --------------------------
# todo: TPI弹框的立即更新
def sync_codes
shixun_tomcat = edu_setting ( 'cloud_bridge' )
begin
git_myshixun_url = repo_ip_url @myshixun . repo_path
git_shixun_url = repo_ip_url @myshixun . shixun . try ( :repo_path )
git_myshixun_url = Base64 . urlsafe_encode64 ( git_myshixun_url )
git_shixun_url = Base64 . urlsafe_encode64 ( git_shixun_url )
# todo: identifier 是以前的密码,用来验证的,新版如果不需要,和中间层协调更改.
params = { tpiID : " #{ @myshixun . try ( :id ) } " , tpiGitURL : " #{ git_myshixun_url } " , tpmGitURL : " #{ git_shixun_url } " ,
identifier : " xinhu1ji2qu3 " }
uri = " #{ shixun_tomcat } /bridge/game/resetTpmRepository "
res = uri_post uri , params
if ( res && res [ 'code' ] != 0 )
tip_exception ( " 实训云平台繁忙( 繁忙等级: 95) " )
end
shixun_new_commit = GitService . commits ( repo_path : @myshixun . shixun . repo_path ) . first [ " id " ]
@myshixun . update_attributes! ( commit_id : shixun_new_commit , reset_time : @myshixun . shixun . try ( :reset_time ) )
if @game . challenge . st == 0 && @game . challenge . path . present?
paths = @game . challenge . path . split ( " ; " )
paths . each do | path |
game_code_init @game . id , path . try ( :strip )
end
end
@path = @game . challenge . path
# 更新完成后,弹框则隐藏不再提示
@myshixun . update_column ( :system_tip , false )
rescue Exception = > e
tip_exception ( " 立即更新代码失败! #{ e . message } " )
end
end
## 给关卡打星星
def star
shixun = Shixun . select ( [ :id , :averge_star , :status ] ) . where ( id : params [ :shixun_id ] ) . first
grades = Grade . where ( user_id : current_user . id , container_id : @game . id , container_type : 'Star' )
if grades . exists?
tip_exception ( " 您已经评价过该实训 " )
else
@game . update_column ( :star , params [ :star ] . to_i )
# 更新实训平均星星数值
averge_star = Game . find_by_sql ( " select ifnull(sum(g.star),0)/ifnull(count(*),1) as averge_star from (games g left join
( myshixuns m join shixuns s on s . id = m . shixun_id ) on m . id = g . myshixun_id )
where star != 0 and s . id = #{shixun.id}").first.try(:averge_star)
averge_star = averge_star . to_f || 5
shixun . update_column ( :averge_star , averge_star . round ( 1 ) )
# 随机生成10-100金币作为奖励
@gold = 0
# 加积分只针对已发布的实训
if shixun . status > = 2
@gold = rand ( 10 .. 100 )
RewardGradeService . call ( current_user , container_id : @game . id , container_type : 'Star' , score : @gold )
end
end
end
## 代码文件目录结构
def git_entries
gpid = params [ :gpid ]
@path = params [ :path ] . try ( :strip )
rev = params [ :rev ] ? params [ :rev ] : " master "
@trees = @g . trees ( gpid , path : @path , rev : rev )
unless @trees . count
tip_exception ( " 版本库异常 " )
end
end
## 是否可以查看答案,如果是管理员或者系统认证的老师则直接查看,不需要弹框
def answer
challenge = Challenge . select ( [ :answer , :id , :score , :st ] ) . find ( @game . challenge_id )
# 这几种情况可以直接查看答案的:实训未发布;当前用户为实训管理员;已经查看过答案;平台认证的老师;
@allowed = @shixun . status < 2 || @game . answer_open == 1 || current_user . shixun_identity ( @shixun ) < = User :: EDU_CERTIFICATION_TEACHER
uid_logger ( " -- is manager #{ current_user . manager_of_shixun? ( @shixun ) } " )
@result = challenge . st == 0 ? challenge . try ( :answer ) : challenge . choose_answer
end
# 获取答案
# GET: /tasks/:identifier/get_answer_info
# 0 直接查看答案, 1 查看答案弹框, 2 答案详情弹框
def get_answer_info
challenge = @game . challenge
@challenge_answers = challenge . challenge_answers
# 平台已认证的老师需要控制
@power = ( @identity < User :: EDU_GAME_MANAGER )
if ! @power
if @challenge_answers . size == 0
tip_exception ( " 无答案可以查看 " )
elsif @challenge_answers . size == 1
# 未看答案,提示弹框
if @game . answer_open == 0
tip_exception ( 1 , { answer_id : @challenge_answers . first . id , answer_score : @challenge_answers . first . score } )
end
else
if @game . answer_open == 0
tip_exception ( 2 , @challenge_answers . map { | a | { answer_id : a . id , answer_name : a . name , answer_score : a . score } } )
end
end
end
end
# 解锁答案
# GET: /tasks/:identifier/get_answer_info?answer_id=?
def unlock_answer
@answer = ChallengeAnswer . find ( params [ :answer_id ] )
challenge = @answer . challenge
# 解锁需要本层级的答案是否需要扣分
points = challenge . challenge_answers . where ( level : @game . answer_open + 1 .. @answer . level ) . sum ( :score )
deduct_score = ( ( points / 100 . 0 ) * challenge . score ) . to_i
uid_logger ( " # # # # # # # # # # # # 金币数目: #{ current_user . grade } " )
unless current_user . grade . to_i - deduct_score > 0
tip_exception ( " 您没有足够的金币 " )
end
ActiveRecord :: Base . transaction do
begin
# 积分消耗情况记录
score = challenge . st . zero? ? - deduct_score : - challenge . choose_score . to_i
RewardGradeService . call ( current_user , container_id : @answer . id , container_type : 'Answer' , score : score )
# 通关查看答案 不扣 得分
if @game . status == 2
@game . update_attributes! ( :answer_open = > @answer . level )
else
# 扣除总分计算
answer_deduction = challenge . challenge_answers . where ( " level <= #{ @answer . level } " ) . sum ( :score )
@game . update_attributes! ( :answer_open = > @answer . level , :answer_deduction = > answer_deduction )
end
rescue Exception = > e
uid_logger_error ( " # # # # # # # 金币扣除异常: #{ e . message } " )
raise ActiveRecord :: Rollback
end
end
end
# 查看答案需要扣取金币
# 必须保证用户的金币数大于关卡的金币数
def answer_grade
challenge = Challenge . select ( [ :answer , :id , :score , :st ] ) . find ( @game . challenge_id )
challenge_score = challenge . try ( :score )
final_score = @game . final_score
@allowed_viewed = current_user . grade . to_i - challenge_score > 0
unless @allowed_viewed
tip_exception ( " 您没有足够的金币 " )
end
ActiveRecord :: Base . transaction do
begin
if @game . answer_open == 0 # 如果这是第一次查看答案
if challenge . st == 0
@final_score = final_score - challenge_score
# 积分消耗情况记录
RewardGradeService . call (
current_user ,
container_id : @game . id ,
container_type : 'Answer' ,
score : - challenge_score
)
else
@final_score = final_score - challenge . choose_score . to_i
# 之所以不用final_score是因为过关后查看答案的final_score为0, 但是记录需要记录扣除的分数
RewardGradeService . call (
current_user ,
container_id : @game . id ,
container_type : 'Answer' ,
score : - challenge . choose_score . to_i )
end
@game . update_attributes! ( :answer_open = > true , :final_score = > final_score )
end
if challenge . st == 0
@answer = challenge . try ( :answer )
else
@answer = challenge . choose_answer
end
@answer =
# 更新当前用户的总金币数
@grade = User . where ( :id = > @game . user_id ) . pluck ( :grade ) . first
rescue Exception = > e
uid_logger_error ( " # # # # # # # 奖励金币异常: #{ e . message } " )
raise ActiveRecord :: Rollback
end
end
end
# 查看隐藏测试集
# REDO:有漏洞, 通过game详情可以看到隐藏的测试集
def check_test_sets
challenge = Challenge . select ( [ :id , :score ] ) . find ( @game . challenge_id )
user_grade = current_user . grade
@minus_grade = challenge . score * 5
@allowed_viewed = user_grade > = @minus_grade
if @allowed_viewed
current_user . update_attribute ( :grade , user_grade - @minus_grade )
@game . update_attribute ( :test_sets_view , true )
# 扣分记录
Grade . create ( :user_id = > current_user . id , :container_id = > @game . id , :score = > - @minus_grade , :container_type = > " testSet " )
max_query_index = game . query_index . to_i
# 区分评测过未评测过,未评测过按需求取数据
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
testset_detail max_query_index , challenge
else
tip_exception ( - 1 , " 本操作需要扣除 #{ @minus_grade } 金币,您的金币不够了 " )
end
end
# # 文件更新;数据评测记录
# # 生成重新评测认证码
# # content_modified:0 表示文件没有更新; content_modified:1 表示文件有更新
# def file_update
# path = params[:path].strip unless params[:path].blank?
# myshixun = @game.myshixun
# rev = params[:rev] ? params[:rev] : "master"
# @content_modified = 0
# # params[:evaluate] 实训评测时更新必须给的参数,需要依据该参数做性能统计,其它类型的更新可以跳过
# # 自动保存的时候evaluate为0; 点评测的时候为1
# if params[:evaluate] == 1
# record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => myshixun.shixun_id, :game_id => @game.id)
# uid_logger("-- game is #{@game.id}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
# 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
# # 远程版本库文件内容
# last_content = GitService.file_content(repo_path: @repo_path, path: path)["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 => rev, :commit_message => params[:evaluate] == 0 ? "auto commit" : "task commit")
# uid_logger("-- file update #{code_file}")
# # REDO: 更新失败的处理
# raise("文件更新失败") unless code_file
# end
#
# 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("实训平台繁忙( 繁忙等级: 81) , 请稍后刷新并重试")
# end
# rescue Exception => e
# uid_logger("-- file update failed #{e.message}")
# raise Educoder::TipException.new("#{e.message}")
# end
# 恢复初始代码
# 注意path为当前打开文件的path
def reset_original_code
path = params [ :path ]
# 恢复初始代码应该找tpm的版本库
repo_path = @myshixun . shixun . repo_path
@content = git_fle_content ( repo_path , path )
tip_exception ( " 初始代码为空,代码重置失败 " ) if @content . nil?
# 将tpm的代码内容同步更新到tpi
update_file_content ( @content , @myshixun . repo_path , path , current_user . mail , current_user . full_name , " reset_original_code " )
rescue Exception = > e
uid_logger_error ( " #{ e . message } " )
tip_exception ( " 初始化代码失败 " )
end
# 加载上次通过的代码
def reset_passed_code
path = params [ :path ]
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
update_file_content ( @content , @myshixun . repo_path , path , current_user . mail , current_user . full_name , " game passed reset " )
else
tip_exception ( " 代码重置失败,代码为空 " )
end
end
# 获取版本库文件内容
# 注: 如果本身path传错, 内容肯定也为空; fork成功后, 可能短时间内也获取不到版本库内容
# params[:status] 1: 目录树点击的请求 0: 正常自动加载
# 返回参数status : -3 需要重试, 带retry参数; -1 给出提示
def rep_content
challenge_path = @game . challenge . try ( :path )
if challenge_path . blank?
tip_exception ( " 代码获取异常,请检查实训模板的评测设置是否正确 " )
end
path = @game . challenge . try ( :path ) . split ( " ; " ) [ 0 ] . strip ( )
path = params [ :path ] || path
status = params [ :status ] . to_i
path = path . try ( :strip )
uid_logger ( " --rep_content: path is #{ path } " )
begin
@content = git_fle_content ( @myshixun . repo_path , path ) || " "
rescue
if params [ :retry ] . present?
begin
begin
# 检测TPM对应的路径代码是否正常
git_fle_content ( @myshixun . shixun . repo_path , path )
rescue Exception = > e
uid_logger_error ( " #{ e . message } " )
if @myshixun . shixun . try ( :status ) < 2
tip_exception ( " 代码获取异常,请检查实训模板的评测设置是否正确 " )
else
tip_exception ( " 代码获取异常,请联系系统管理员 " )
end
end
# 如果模板没有问题,则通过中间层检测实训仓库是否异常
# 监测版本库HEAD是否存在, 不存在则取最新的HEAD
gitUrl = repo_url @myshixun . repo_path
gitUrl = Base64 . urlsafe_encode64 ( gitUrl )
shixun_tomcat = edu_setting ( 'cloud_bridge' )
rep_params = { :tpiID = > " #{ @myshixun . id } " , :tpiGitURL = > " #{ gitUrl } " }
# 监测版本库HEAD是否存在, 不存在则取最新的HEAD
uri = " #{ shixun_tomcat } /bridge/game/check "
res = uri_post uri , rep_params
# res值: 0 表示正常;-1表示有错误; -2表示代码版本库没了
if status == 0 && res && ( res [ 'code' ] == - 2 || res [ 'code' ] == - 1 )
# 删除不需要的仓库
begin
GitService . delete_repository ( repo_path : @myshixun . repo_path )
rescue Exception = > e
uid_logger_error ( " #{ e . message } " )
end
# fork一个新的仓库
project_fork ( @myshixun , @shixun . repo_path , current_user . login )
end
rescue Exception = > e
uid_logger_error ( e . message )
# 报错继续retry
tip_exception ( - 3 , " #{ e . message } " )
end
end
tip_exception ( - 3 , " #{ e . message } " )
end
end
# 编程题评测
def game_build
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 = repo_ip_url @myshixun . repo_path
logger . info ( " # # # # # # # # # # # # # giturl: # #{ gitUrl } " )
gitUrl = Base64 . urlsafe_encode64 ( gitUrl )
shixun_tomcat = edu_setting ( 'cloud_bridge' )
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没有更新, 则轮询等待更新
br_params = { :tpiID = > " #{ @myshixun . id } " , :tpiGitURL = > " #{ gitUrl } " , :buildID = > " #{ @game . id } " ,
:instanceChallenge = > " #{ step } " , :testCases = > " #{ testCases } " , :resubmit = > " #{ resubmit } " ,
:times = > params [ :first ] . to_i , :podType = > @shixun . webssh , :content_modified = > content_modified ,
:containers = > " #{ Base64 . urlsafe_encode64 ( container_limit ( @shixun . mirror_repositories ) ) } " ,
:persistenceName = > @shixun . identifier , :tpmScript = > " #{ tpmScript } " ,
:timeLimit = > " #{ @shixun . exec_time } " , :isPublished = > ( @shixun . status < 2 ? 0 : 1 ) }
# 评测有文件输出的需要特殊传字段 path: 表示文件存储的位置
br_params [ 'file' ] = Base64 . urlsafe_encode64 ( { path : " #{ game_challenge . picture_path } " } . to_json ) if game_challenge . picture_path . present?
# needPortMapping: web类型需要pod端口映射
br_params [ :needPortMapping ] = 8080 if @myshixun . mirror_name . include? ( " Web " )
# 中间层交互
uri = " #{ shixun_tomcat } /bridge/game/gameEvaluate "
res = interface_post uri , br_params , 502 , " gameEvaluate failed "
@result = { status : 1 , resubmit : resubmit , position : game_challenge . position , port : res [ 'port' ] , had_done : @game . had_done }
rescue Exception = > e
uid_logger ( " 评测出错,详情: " + e . message )
@result = { status : - 1 , message : " 实训云平台繁忙( 繁忙等级: 502) , 请稍后刷新并重试 " , position : game_challenge . position ,
had_done : @game . had_done }
end
# 选择题评测
def choose_build
Rails . logger . error ( " # # # # # # # # # # # # # # # # #{ params } " )
# 选择题如果通关了,则不让再评测
if @game . status == 2
raise Educoder :: TipException . new ( " 您已通过该关卡 " )
end
# 更新评测次数
@game . update_column ( :evaluate_count , ( @game . evaluate_count . to_i + 1 ) )
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
had_passed = true
test_sets = [ ]
str = " "
game_challenge . challenge_chooses . includes ( :challenge_tags ) . each_with_index do | choose , index |
# user_answer虽然是传的数组,但是可能存在多选择提的情况.
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 . next_query_index } ', ' #{ Time . now . strftime ( '%Y-%m-%d %H:%M:%S' ) } ', ' #{ Time . now . strftime ( '%Y-%m-%d %H:%M:%S' ) } ') "
# 只要有一题错误就不能通关
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
# 批量插入评测结果
uid_logger ( " # ------------chooice score: #{ score } " )
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
uid_logger ( " @@@@@@@@@@@@@@@@@chooice score: #{ score } " )
# 查看答案的时候处理final_scor和扣分记录
experience = score
reward_attrs = { container_id : @game . id , container_type : 'Game' , score : score }
RewardGradeService . call ( @myshixun . owner , reward_attrs )
@game . update_attribute ( :final_score , score )
final_score = score
RewardExperienceService . call ( @myshixun . owner , reward_attrs )
end
had_done = @game . had_done
@myshixun . update_attribute ( :status , 1 ) if had_done == 1
end
grade = @myshixun . owner . try ( :grade )
# 更新实训关联的作品分数 TODO: 更新作业需要等作业模块开了再打开
# update_myshixun_work_score myshixun
# 高性能取上一关、下一关
prev_game = @game . prev_of_current_game ( @shixun . id , @game . myshixun_id , game_challenge . position )
next_game = @game . next_of_current_game ( @shixun . id , @game . myshixun_id , game_challenge . position ) if had_passed
# 高性能取上一关、下一关
#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)
@result = { grade : grade ,
gold : final_score ,
experience : experience ,
challenge_chooses_count : challenge_chooses_count ,
choose_correct_num : choose_correct_num ,
test_sets : test_sets ,
prev_game : prev_game ,
next_game : next_game }
rescue Exception = > e
uid_logger ( " choose build failed #{ e . message } " )
@result = [ status : - 1 , contents : " #{ e . message } " ]
end
# 轮询获取状态
# resubmit是在file_update中生成的, 从game_build中传入的
def game_status
resubmit_identifier = @game . resubmit_identifier
# 如果没有超时并且正在评测中
# 判断评测中的状态有两种: 1、如果之前没有通关的, 只需判断status为1即可; 如果通过关, 则判断game的resubmit_identifier是否更新
uid_logger ( " # # # # # # # # # # # # # # # # game_status: #{ @game . status } " )
uid_logger ( " # # # # # # # # # # # # # # # # params[:resubmit]: #{ params [ :resubmit ] } " )
uid_logger ( " # # # # # # # # # # # # # # # # resubmit_identifier: #{ resubmit_identifier } " )
uid_logger ( " # # # # # # # # # # # # # # # # time_out: #{ params [ :time_out ] } " )
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 )
render :json = > { running_code_status : running_code_status , running_code_message : running_code_message }
end
uid_logger ( " # # # # # 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 )
if params [ :resubmit ] . blank? # 非重新评测
if game_status == 2 # 通关
if @shixun . status > 1
score = @game . final_score # 查看答案的时候有对最终获得金币进行处理
experience = @game . final_score
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
# 实训的最大评测次数,这个值是为了优化查询,每次只取最新的最新一次评测的结果集
max_query_index = @game . query_index
#max_query_index = @game.outputs.first.try(:query_index)
# 区分评测过未评测过,未评测过按需求取数据
testset_detail max_query_index . to_i , game_challenge
# 处理生成图片类型文件
picture = ( @game . picture_path . nil? ? 0 : @game . id )
# 针对web类型的实训
web_route = game_challenge . try ( :web_route )
mirror_name = @shixun . mirror_name
# 轮询结束,更新评测统计耗时
if game_status == 0 || game_status == 2
e_record = EvaluateRecord . where ( :game_id = > @game . id ) . first
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
uid_logger ( " game is is #{ @game . id } , record id is #{ e_record . try ( :id ) } , time is**** #{ Time . now . strftime ( " %Y-%m-%d %H:%M:%S.%L " ) } " )
# 记录前端总耗时
record_consume_time = EvaluateRecord . where ( :game_id = > @game . id ) . first . try ( :consume_time )
# 实训制作者当前拥有的金币
grade = User . where ( :id = > @game . user_id ) . pluck ( :grade ) . first
# 高性能取上一关、下一关
# 上一关、下一关
prev_game = @game . prev_of_current_game ( @shixun . id , @game . myshixun_id , game_challenge . position )
next_game = @game . next_of_current_game ( @shixun . id , @game . myshixun_id , game_challenge . position ) if game_status == 2
@base_date = { grade : grade , gold : score , experience : experience , status : game_status , had_done : had_done ,
position : game_challenge . position , port : port , record_consume_time : record_consume_time ,
mirror_name : mirror_name , picture : picture , web_route : web_route , star : @game . star ,
next_game : next_game , prev_game : prev_game }
end
# 记录实训花费的时间
# REDO:需要添加详细的说明
def cost_time
cost_time = params [ :time ] . to_i
@game . update_attribute ( :cost_time , cost_time )
end
# 同步challenge的更新时间
def sync_modify_time
modify_time = Challenge . where ( :id = > @game . challenge_id ) . pluck ( :modify_time ) . first
@game . update_column ( :modify_time , modify_time )
sucess_status
end
# tpi弹框状态更新; true则不再显示; false每次刷新显示
def system_update
myshixun = Myshixun . find ( params [ :myshixun_id ] )
myshixun . update_attribute ( :system_tip , true )
sucess_status
end
# 关闭webssh
def close_webssh
myshixun_id = @game . myshixun_id
digest = @game . identifier + edu_setting ( 'bridge_secret_key' )
digest_key = Digest :: SHA1 . hexdigest ( " #{ digest } " )
begin
shixun_tomcat = edu_setting ( 'cloud_bridge' )
uri = " #{ shixun_tomcat } /bridge/webssh/delete "
Rails . logger . info ( " #{ current_user } => cloese_webssh digest is #{ digest } " )
params = { :tpiID = > myshixun_id , :digestKey = > digest_key , :identifier = > @game . identifier }
res = uri_post uri , params
if res && res [ 'code' ] . to_i != 0
raise ( " 实训云平台繁忙( 繁忙等级: 110) " )
end
rescue Exception = > e
Rails . logger . error ( e )
tip_exception ( " 实训云平台繁忙, 关闭失败! " )
end
end
# tpi对于实训关卡的点赞或取消点赞
def plus_or_cancel_praise
challenge = @game . challenge
pt = PraiseTread . where ( :praise_tread_object_id = > challenge . id , :praise_tread_object_type = > 'Challenge' ,
:user_id = > current_user , :praise_or_tread = > 1 ) . first
# 如果当前用户已赞过,则不能重复赞
if pt . blank?
PraiseTread . create! ( :praise_tread_object_id = > challenge . id , :praise_tread_object_type = > 'Challenge' ,
:user_id = > current_user . id , :praise_or_tread = > 1 ) if pt . blank?
@praise = true
else
pt . destroy if pt . present? # 如果已赞过,则删掉这条赞(取消);如果没赞过,则为非法请求不处理
@praise = false
end
@praise_count = PraiseTread . where ( :praise_tread_object_id = > challenge . id , :praise_tread_object_type = > 'Challenge' ,
:praise_or_tread = > 1 ) . count
end
private
# 评测测试机封装
def testset_detail max_query_index , challenge
# 是否允许查看隐藏的测试集, 以前的power
@allowed_hidden_testset = @identity < User :: EDU_GAME_MANAGER || @game . test_sets_view #解锁的用户
if max_query_index > 0
uid_logger ( " max_query_index is #{ max_query_index } game id is #{ @game . id } , challenge_id is #{ challenge . id } " )
@qurey_test_sets = TestSet . find_by_sql ( " SELECT o.code, o.actual_output, o.out_put, o.result, o.test_set_position,
o . query_index , t . is_public , t . input , t . output , o . compile_success FROM outputs o , games g , challenges c ,
test_sets t where g . id = #{@game.id} and c.id=#{challenge.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
" )
@test_sets_count = @qurey_test_sets . count
# 错误的测试集总数
@sets_error_count = 0
@qurey_test_sets . each do | set |
@sets_error_count += 1 unless set . result
end
@last_compile_output = @qurey_test_sets . first [ 'out_put' ] . gsub ( / \ n / , '<br/>' ) . gsub ( / \ t / , "   \ ;  \ ;  \ ;  \ ;  \ ;  \ ;  \ ;  \ ; " ) if @qurey_test_sets . first [ 'out_put' ] . present?
else
@qurey_test_sets = TestSet . find_by_sql ( " SELECT t.is_public, t.input, t.output, t.position
FROM test_sets t where t . challenge_id = #{challenge.id}")
end
end
# 实训选择题需要局部刷新或者显示的部分
def choose_container game_challenge , game , max_query_index
# category 1: 单选题,其它的多选题(目前只有两种)
challenge_chooses = game_challenge . challenge_chooses . includes ( :challenge_questions )
test_sets = [ ]
@chooses = [ ]
# 选择题测试集统计
challenge_chooses_count = challenge_chooses . count
choose_correct_num = game . choose_correct_num ( max_query_index )
game_outputs = game . outputs . where ( :query_index = > max_query_index )
# 判断用户是否有提交
had_submmit = game_outputs . present?
# 判断选择题是否写了标准答案
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 }
end
def find_game
@game = Game . find_by_identifier ( params [ :identifier ] )
if @game . blank?
normal_status ( 404 , " ... " )
return
end
@myshixun = @game . myshixun
end
def find_shixun
@shixun = Shixun . find ( @myshixun . shixun_id )
end
# http://localhost:3000/tasks/hcie39pw2bjn
# 可以访问条件: 学员本身; 管理员; TPM制作者
def allowed
@identity = current_user . game_identity ( @game )
raise Educoder :: TipException . new ( 403 , " .. " ) if @identity > User :: EDU_GAME_MANAGER
end
end