You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
educoder/app/controllers/games_controller.rb

947 lines
42 KiB

6 years ago
class GamesController < ApplicationController
5 years ago
before_action :require_login, :check_auth
6 years ago
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 = game_challenge.exec_time
6 years ago
# 上一关、下一关
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则表示赞过
5 years ago
praise_count = game_challenge.praises_count
5 years ago
user_praise = game_challenge.praise_treads.exists?(user_id:current_user.id, praise_or_tread: 1)
6 years ago
# 实训的最大评测次数,这个值是为了优化查询,每次只取最新的最新一次评测的结果集
max_query_index = @game.query_index.to_i
# 统计评测时间
record_onsume_time = EvaluateRecord.where(game_id: @game.id).first.try(:consume_time)
5 years ago
# myshixun_manager判断用户是否有权限查看隐藏测试集(TPM管理员平台认证的老师花费金币查看者)
myshixun_manager = @identity < User::EDU_GAME_MANAGER
6 years ago
# 选择题和编程题公共部分
@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}
6 years ago
if @shixun.vnc
begin
6 years ago
shixun_tomcat = edu_setting('cloud_bridge')
6 years ago
service_host = edu_setting('vnc_url')
uri = "#{shixun_tomcat}/bridge/vnc/getvnc"
params = {tpiID: @myshixun.id, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(@shixun))}"}
6 years ago
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级99")
end
6 years ago
@vnc_url = "http://#{service_host}:#{res['port']}/vnc_lite.html?password=headless"
6 years ago
rescue Exception => e
Rails.logger.error(e.message)
end
end
6 years ago
# 区分选择题和编程题st0编程题
if @st == 0
5 years ago
has_answer = game_challenge.challenge_answers.size != 0
6 years ago
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
5 years ago
tpm_cases_modified = (game_challenge.modify_time != @game.modify_time) # modify_time 决定TPM测试集是否有更新
6 years ago
@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
# 获取实践题答案
6 years ago
# GET: /tasks/:identifier/get_answer_info
# 0 直接查看答案, 1 查看答案弹框, 2 答案详情弹框
def get_answer_info
@challenge = @game.challenge
@challenge_answers = @challenge.challenge_answers
6 years ago
# 平台已认证的老师需要控制
@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
6 years ago
# 获取选择题答案
def get_choose_answer
@challenge = @game.challenge
tip_exception("本接口只能获取选择题答案") if @challenge.st != 1
@power = (@identity < User::EDU_GAME_MANAGER)
# 如果没权限,也没看过答案,则需要解锁
if @game.answer_open == 0 && !@power
tip_exception(1, @challenge.choose_score)
else
@challenge_chooses = @challenge.challenge_chooses
end
6 years ago
end
# 解锁实践题答案
6 years ago
# GET: /tasks/:identifier/get_answer_info?answer_id=?
def unlock_answer
@challenge = @game.challenge
6 years ago
@answer = ChallengeAnswer.find(params[:answer_id])
challenge = @answer.challenge
# 解锁需要本层级的答案是否需要扣分
points = challenge.challenge_answers.where(level: @game.answer_open + 1..@answer.level).sum(:score)
6 years ago
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: @game.id, container_type: 'Answer', score: score)
6 years ago
# 通关查看答案 不扣 得分
answer_open = @challenge.st == 1 ? 1 : @answer.level
6 years ago
if @game.status == 2
@game.update_attributes!(:answer_open =>answer_open)
6 years ago
else
# 扣除总分计算
6 years ago
answer_deduction = challenge.challenge_answers.where("level <= #{@answer.level}").sum(:score)
@game.update_attributes!(:answer_open => answer_open, :answer_deduction => answer_deduction)
6 years ago
end
rescue Exception => e
uid_logger_error("#######金币扣除异常: #{e.message}")
raise ActiveRecord::Rollback
end
end
end
6 years ago
# 解锁选择题答案
def unlock_choose_answer
@challenge = @game.challenge
score = @challenge.choose_score
unless current_user.grade.to_i - score > 0
tip_exception("您没有足够的金币")
end
ActiveRecord::Base.transaction do
begin
# 积分消耗情况记录
RewardGradeService.call(current_user, container_id: @game.id, container_type: 'Answer', score: -score)
# 通关查看答案 不扣 得分
if @game.status == 2
@game.update_attributes!(:answer_open => 1)
else
# 扣除总分计算
@game.update_attributes!(:answer_open => 1, :answer_deduction => 100)
end
@challenge_chooses = @challenge.challenge_chooses
rescue Exception => e
uid_logger_error("#######金币扣除异常: #{e.message}")
raise ActiveRecord::Rollback
end
end
6 years ago
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")
6 years ago
max_query_index = @game.query_index.to_i
6 years ago
testset_detail max_query_index, challenge
6 years ago
else
6 years ago
tip_exception(-1, "本操作需要扣除#{ @minus_grade }金币,您的金币不够了")
6 years ago
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")
6 years ago
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 Exception => e
if e.status == -1
tip_exception(-3, "#{e.message}")
end
if params[:retry].to_i == 1
6 years ago
begin
begin
# 检测TPM对应的路径代码是否正常
git_fle_content(@myshixun.shixun.repo_path, path)
rescue Exception => e
uid_logger_error("#{e.message}")
# 如果已发布的TPM实训也不能获取到内容那么肯定是版本库异常了
6 years ago
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
sec_key = params[:sec_key]
game_challenge = Challenge.select([:id, :position, :picture_path, :exec_time]).find(@game.challenge_id)
6 years ago
# 更新评测次数
@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(shixun_container_limit(@shixun))}",
:persistenceName => @shixun.identifier, :tpmScript => "#{tpmScript}", :sec_key => sec_key,
:timeLimit => "#{game_challenge.exec_time}", :isPublished => (@shixun.status < 2 ? 0 : 1) }
6 years ago
# 评测有文件输出的需要特殊传字段 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
5 years ago
correct = (user_answer_tran == standard_answer_tran)
6 years ago
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}")
6 years ago
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
6 years ago
uid_logger("@@@@@@@@@@@@@@@@@chooice score: #{score}")
6 years ago
# 查看答案的时候处理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
6 years ago
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]}")
6 years ago
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
6 years ago
@allowed_hidden_testset = @identity < User::EDU_GAME_MANAGER || @game.test_sets_view #解锁的用户
6 years ago
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/, "&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;") 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?
6 years ago
# 标准答案处理,错误的不让用户查看,用-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 = true # 选择题永远都有答案
6 years ago
@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