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/shixuns_controller.rb

1198 lines
45 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

class ShixunsController < ApplicationController
include ShixunsHelper
include ApplicationHelper
include ElasticsearchAble
include CoursesHelper
include GitCommon
before_action :require_login, :check_auth, except: [:download_file, :index, :menus, :show, :show_right, :ranking_list,
:discusses, :collaborators, :fork_list, :propaedeutics]
before_action :check_account, only: [:new, :create, :shixun_exec, :jupyter_exec]
before_action :find_shixun, except: [:index, :new, :create, :menus, :get_recommend_shixuns,
:propaedeutics, :departments, :apply_shixun_mirror,
:get_mirror_script, :download_file, :shixun_list, :batch_send_to_course]
before_action :shixun_access_allowed, except: [:index, :new, :create, :menus, :get_recommend_shixuns,
:propaedeutics, :departments, :apply_shixun_mirror, :jupyter_exec,
:get_mirror_script, :download_file, :shixun_list, :batch_send_to_course]
before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy,
:add_file, :jupyter_exec, :upload_git_file, :delete_git_file, :upload_git_folder]
before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish, :apply_public, :upload_git_folder,
:shixun_members_added, :change_manager, :collaborators_delete, :upload_git_file,
:cancel_apply_public, :cancel_publish, :add_collaborators, :add_file, :delete_git_file]
before_action :portion_allowed, only: [:copy]
before_action :special_allowed, only: [:send_to_course, :search_user_courses]
before_action :shixun_marker, only: [:new, :create]
#before_action :validate_wachat_support, only: [:shixun_exec]
skip_before_action :check_sign, only: [:download_file]
## 获取课程列表
def index
@shixuns = current_laboratory.shixuns.unhidden.publiced
## 方向
if params[:tag_level].present? && params[:tag_id].present?
@shixuns = @shixuns.filter_tag(params[:tag_level].to_i, params[:tag_id].to_i)
case params[:tag_level].to_i
when 1 #大类
@search_tags = Repertoire.find(params[:tag_id].to_i).name
when 2 #子类
@search_tags = SubRepertoire.find(params[:tag_id].to_i).name
when 3 #tag
tag = TagRepertoire.find(params[:tag_id].to_i)
@search_tags = "#{tag.sub_repertoire.name} / #{tag.name}"
end
end
## 搜索关键字 匹配关卡名, 用户名, 实训名 和 空格多搜索
if params[:keyword].present?
keyword = params[:keyword].strip
@shixuns = @shixuns.joins(:user, challenges: :challenge_tags).
where("challenge_tags.name like :keyword
or challenges.subject like :keyword
or concat(lastname, firstname) like :keyword
or shixuns.name like :name",
keyword: "%#{keyword}%", name: "%#{keyword.split(" ").join("%")}%").distinct
end
## 筛选 状态
if params[:status].present? && params[:status].to_i != 0
params[:status] = [0, 1] if params[:status].to_i == 1
@shixuns = @shixuns.where(status: params[:status])
end
## 筛选 难度
if params[:diff].present? && params[:diff].to_i != 0
@shixuns = @shixuns.where(trainee: params[:diff])
end
## 排序参数
bsort = params[:sort] || 'desc'
case params[:order_by] || 'new'
when 'hot'
@shixuns = @shixuns.order("shixuns.public = 2 desc, shixuns.myshixuns_count #{bsort}")
else
@shixuns = @shixuns.order("shixuns.public = 2 desc, shixuns.publish_time #{bsort}")
end
# 用id计数会快10+MS左右,对于搜索的内容随着数据的增加,性能会提升一些。
@total_count = @shixuns.count("shixuns.id")
## 分页参数
page = params[:page] || 1
limit = params[:limit] || 16
@shixuns = @shixuns.includes(:tag_repertoires, :challenges).page(page).per(limit)
#@tag_name_map = TagRepertoire.joins(:shixun_tag_repertoires)
# .where(shixun_tag_repertoires: { shixun_id: @shixuns.map(&:id) })
# .group('shixun_tag_repertoires.shixun_id')
# .select('shixun_id, tag_repertoires.name')
# .each_with_object({}) { |r, obj| obj[r.shixun_id] = r.name }
end
def shixun_list
# 全部实训/我的实训
type = params[:type] || "all"
# 状态:已发布/未发布
status = params[:status] || "all"
# 超级管理员用户显示所有未隐藏的实训、非管理员显示所有已发布的实训(对本单位公开且未隐藏未关闭)
if type == "mine"
@shixuns = current_user.shixuns.none_closed
else
if current_user.admin?
@shixuns = Shixun.none_closed.where(hidden: 0)
else
none_shixun_ids = ShixunSchool.where("school_id != #{current_user.school_id}").pluck(:shixun_id)
@shixuns = Shixun.where.not(id: none_shixun_ids).none_closed.where(hidden: 0)
end
end
unless status == "all"
@shixuns = status == "published" ? @shixuns.where(status: 2) : @shixuns.where(status: [0, 1])
end
## 筛选 难度
if params[:diff].present? && params[:diff].to_i != 0
@shixuns = @shixuns.where(trainee: params[:diff])
end
page = params[:page] || 1
limit = params[:limit] || 10
offset = (page.to_i - 1) * (limit.to_i)
order = params[:order] || "desc"
## 搜索关键字创建者、实训名称、院校名称
keyword = params[:keyword].to_s.strip.presence || '*'
model_options = {
index_name: [Shixun],
model_includes: Shixun.searchable_includes
}
model_options.merge(where: { id: @shixuns.pluck(:id) }).merge(order: {"myshixuns_count" => order}).merge(limit: limit, offset: offset)
model_options.merge(default_options)
@shixuns = Searchkick.search(keyword, model_options)
# @shixuns = Shixun.search keyword, where: {id: @shixuns.pluck(:id)}, order: {"myshixuns_count" => order}, limit: limit, offset: offset
@total_count = @shixuns.total_count
end
## 获取顶部菜单
def menus
@repertoires = current_laboratory.shixun_repertoires
end
## 实训详情
def show
# 当前用户开启的实训
can_fork = current_user.is_certification_teacher || current_user.admin_or_business?
unless can_fork
@can_fork = {can_fork: "已经职业认证的教师才能fork实训",
certi_url: "/account/certification"}
end
@current_myshixun = @shixun.current_myshixun(current_user.id)
if @shixun.fork_from
fork_shixun = Shixun.select(:id, :user_id, :name, :identifier).where(id: @shixun.fork_from).first
@fork_from = {name: fork_shixun.name, username: fork_shixun.owner.try(:full_name),
fork_identifier: fork_shixun.identifier} if fork_shixun
end
@power = current_user.manager_of_shixun?(@shixun)
# 更新是为了首页的排序myshixun的动静需要在实训中展现出来
if @current_myshixun && params[:exit] && !current_user.admin?
@current_myshixun.update_column(:updated_at, Time.now)
end
end
def show_right
owner = @shixun.owner
@user_own_shixuns = owner.shixuns.published.count
@user_tags = @shixun.user_tags_name(current_user)
@shixun_tags = @shixun.challenge_tags_name
@myshixun = @shixun.myshixuns.find_by(user_id: current_user.id)
end
# 排行榜
def ranking_list
if @shixun.status == 2
sql = "
select m.user_id, u.login, u.lastname, m.updated_at,
(select sum(cost_time) from games g where g.myshixun_id = m.id) as time,
(select sum(final_score) from games g where g.myshixun_id = m.id) as score
from (users u left join myshixuns m on m.user_id = u.id) where u.is_test =0 and m.shixun_id = #{@shixun.id} and m.status = 1
order by score desc, time asc limit 10
"
@myshixuns = Myshixun.find_by_sql(sql)
end
end
#评论
def discusses
new_params = params.merge(container_id: @shixun.id, container_type: 'Shixun')
discusses = ShixunsService.new.shixun_discuss new_params, current_user
if discusses.present?
@children_list = discusses[:children_list]
else
@children_list = []
end
end
def copy
ActiveRecord::Base.transaction do
begin
@new_shixun = Shixun.new
@new_shixun.attributes = @shixun.attributes.dup.except("id","user_id","visits","gpid","status", "identifier", "averge_star",
"homepage_show","repo_name", "myshixuns_count", "challenges_count",
"can_copy", "created_at", "updated_at", "public")
@new_shixun.user_id = User.current.id
@new_shixun.averge_star = 5
@new_shixun.identifier = generate_identifier Shixun, 8
@new_shixun.fork_from = @shixun.id
@new_shixun.save!
# 同步shixun_info的信息
if @shixun.shixun_info.present?
ShixunInfo.create!(shixun_id: @new_shixun.id,
description: @shixun.description,
evaluate_script: @shixun.evaluate_script,
fork_reason: params[:reason].to_s.strip)
end
# 同步私密版本库
if @shixun.shixun_secret_repository
# 源仓库的的私密版本库地址
repo_name = @shixun.shixun_secret_repository.repo_name
# 新生成的地址
fork_repository_name = "#{current_user.login}/secret_#{@new_shixun.identifier}"
ShixunSecretRepository.create!(shixun_id: @new_shixun.id,
repo_name: "#{fork_repository_name}",
secret_dir_path: @shixun.shixun_secret_repository.secret_dir_path)
GitService.fork_repository(repo_path: "#{repo_name}.git", fork_repository_path: (fork_repository_name + ".git"))
end
# 同步镜像
if @shixun.mirror_repositories.present?
@shixun.mirror_repositories.each do |mirror|
ShixunMirrorRepository.create!(:shixun_id => @new_shixun.id, :mirror_repository_id => mirror.id)
end
end
# 同步技术标签
@shixun.shixun_tag_repertoires.each do |str|
ShixunTagRepertoire.create!(:tag_repertoire_id => str.tag_repertoire_id, :shixun_id => @new_shixun.id)
end
# 同步配置
logger.info("########-shixun_service_configs_count: #{@shixun.shixun_service_configs.pluck(:id, :shixun_id)}")
@shixun.shixun_service_configs.each do |config|
ShixunServiceConfig.create!(:shixun_id => @new_shixun.id,
:cpu_limit => config.cpu_limit,
:lower_cpu_limit => config.lower_cpu_limit,
:memory_limit => config.memory_limit,
:request_limit => config.request_limit,
:mirror_repository_id => config.mirror_repository_id)
end
# 同步高校限制
@shixun.shixun_schools.each do |school|
ShixunSchool.create!(shixun_id: @new_shixun.id, school_id: school.school_id)
end
# fork版本库
logger.info("###########fork_repo_path: ######{@repo_path}")
project_fork(@new_shixun, @repo_path, current_user.login)
ShixunMember.create!(:user_id => User.current.id, :shixun_id => @new_shixun.try(:id), :role => 1)
# 如果是jupyter先创建一个目录,为了挂载(因为后续数据集开启Pod后环境在没销毁前你上传数据集是挂载不上目录的因此要先创建目录方便中间层挂载)
if @new_shixun.is_jupyter?
folder = EduSetting.get('shixun_folder')
raise "存储目录未定义" unless folder.present?
path = "#{folder}/#{@new_shixun.identifier}"
FileUtils.mkdir_p(path, :mode => 0777) unless File.directory?(path)
# 复制数据集
save_path = File.join(folder, @shixun.identifier)
@shixun.data_sets.each do |set|
new_date_set = Attachment.new
new_date_set.attributes = set.attributes.dup.except("id", "container_id", "disk_directory")
new_date_set.container_id = @new_shixun.id
new_date_set.disk_directory = @new_shixun.identifier
new_date_set.save!
FileUtils.cp("#{save_path}/#{set.relative_path_filename}", path)
end
end
# 同步复制关卡
if @shixun.challenges.present?
@shixun.challenges.each do |challenge|
new_challenge = Challenge.new
new_challenge.attributes = challenge.attributes.dup.except("id","shixun_id","user_id", "challenge_tags_count")
new_challenge.user_id = User.current.id
new_challenge.shixun_id = @new_shixun.id
new_challenge.save!(validate: false)
# 同步参考答案
challenge.challenge_answers.each do |answer|
new_answer = ChallengeAnswer.new
new_answer.attributes = answer.attributes.dup.except("id","challenge_id")
new_answer.challenge_id = new_challenge.id
new_answer.save!(validate: false)
end
if challenge.st == 0 # 评测题
# 同步测试集
if challenge.test_sets.present?
challenge.test_sets.each do |test_set|
new_test_set = TestSet.new
new_test_set.attributes = test_set.attributes.dup.except("id","challenge_id")
new_test_set.challenge_id = new_challenge.id
new_test_set.save!(validate: false)
end
end
# 同步关卡标签
challenge_tags = ChallengeTag.where("challenge_id =? and challenge_choose_id is null", challenge.id)
if challenge_tags.present?
challenge_tags.each do |challenge_tag|
ChallengeTag.create!(:challenge_id => new_challenge.id, :name => challenge_tag.try(:name))
end
end
elsif challenge.st == 1 # 选择题
if challenge.challenge_chooses.present?
challenge.challenge_chooses.each do |challenge_choose|
new_challenge_choose = ChallengeChoose.new
new_challenge_choose.attributes = challenge_choose.attributes.dup.except("id","challenge_id")
new_challenge_choose.challenge_id = new_challenge.id
new_challenge_choose.save!
# 每一题的选项
if challenge_choose.challenge_questions.present?
challenge_choose.challenge_questions.each do |challenge_question|
new_challenge_question = ChallengeQuestion.new
new_challenge_question.attributes = challenge_question.attributes.dup.except("id","challenge_choose_id")
new_challenge_question.challenge_choose_id = new_challenge_choose.id
new_challenge_question.save!
end
end
# 每一题的知识标签
st_challenge_tags = ChallengeTag.where(:challenge_id => challenge.id, :challenge_choose_id => challenge_choose.id)
if st_challenge_tags.present?
st_challenge_tags.each do |st_challenge_tag|
ChallengeTag.create!(:challenge_id => new_challenge.id, :name => st_challenge_tag.try(:name), :challenge_choose_id => new_challenge_choose.id)
end
end
end
end
end
end
end
# 将实训标志为该云上实验室建立
Laboratory.current.laboratory_shixuns.create!(shixun: @shixun, ownership: true)
rescue Exception => e
uid_logger_error("copy shixun failed ##{e.message}")
# 删除版本库
# 删除私密版本库
GitService.delete_repository(repo_path: "#{fork_repository_name}.git") if @new_shixun.shixun_secret_repository&.repo_name
GitService.delete_repository(repo_path: @new_shixun.repo_path) if @new_shixun.repo_path
tip_exception("实训Fork失败")
raise ActiveRecord::Rollback
end
end
end
#合作者
def collaborators
@user = current_user
## 分页参数
page = params[:page] || 1
limit = params[:limit] || 10
@member_count = @shixun.shixun_members.count
@members = @shixun.shixun_members.order("role = 1 desc, created_at asc").includes(:user).page(page).per(limit)
end
def fork_list
@shixuns = Shixun.where(:fork_from => @shixun.id)
@shixuns_count = @shixuns.count
## 分页参数
page = params[:page] || 1
limit = params[:limit] || 20
@shixuns = @shixuns.page(page).per(limit)
end
def new
@introduction_sample = PlatformSample.where(samples_type: ['introduction', 'knowledge']).pluck([:samples_type, :contents])
@main_type = shixun_main_type
@small_type = shixun_small_type
end
def create
@shixun = CreateShixunService.call(current_user, shixun_params, params)
end
# 保存jupyter到版本库
def update_jupyter
jupyter_save_with_shixun(@shixun, params[:jupyter_port])
end
def update
# 镜像方面
mirror_ids = MirrorRepository.where(id: params[:main_type])
.or( MirrorRepository.where(id: params[:sub_type])).pluck(:id).uniq
old_mirror_ids = @shixun.shixun_mirror_repositories
.where(mirror_repository_id: params[:main_type])
.or(@shixun.shixun_mirror_repositories.where(mirror_repository_id: params[:sub_type]))
.pluck(:mirror_repository_id).uniq
new_mirror_id = (mirror_ids - old_mirror_ids).map{|id| {mirror_repository_id: id}} # 转换成数组hash方便操作
logger.info("##########new_mirror_id: #{new_mirror_id}")
logger.info("##########old_mirror_ids: #{old_mirror_ids}")
logger.info("##########mirror_ids: #{mirror_ids}")
# 服务配置方面
service_create_params = service_config_params[:shixun_service_configs]
.select{|config| !old_mirror_ids.include?(config[:mirror_repository_id]) &&
MirrorRepository.find(config[:mirror_repository_id]).name.present?}
service_update_params = service_config_params[:shixun_service_configs]
.select{|config| old_mirror_ids.include?(config[:mirror_repository_id])}
logger.info("#########service_create_params: #{service_create_params}")
logger.info("#########service_update_params: #{service_update_params}")
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes!(shixun_params)
@shixun.shixun_info.update_attributes!(shixun_info_params)
# 镜像变动
@shixun.shixun_mirror_repositories.where.not(mirror_repository_id: old_mirror_ids).destroy_all
@shixun.shixun_mirror_repositories.create!(new_mirror_id) if new_mirror_id.present?
# 镜像变动要更换服务配置
@shixun.shixun_service_configs.where.not(mirror_repository_id: old_mirror_ids).destroy_all
@shixun.shixun_service_configs.create!(service_create_params) if service_create_params.present?
service_update_params&.map do |service|
smr = @shixun.shixun_service_configs.find_by(mirror_repository_id: service[:mirror_repository_id])
logger.info("########smr: #{smr}")
smr.update_attributes(service) if smr.present?
end
# 添加第二仓库(管理员权限)
if params[:is_secret_repository]
add_secret_repository if @shixun.shixun_secret_repository.blank?
else
# 如果有仓库,就要删
if @shixun.shixun_secret_repository&.repo_name
@shixun.shixun_secret_repository.lock!
GitService.delete_repository(repo_path: @shixun.shixun_secret_repository.repo_path)
@shixun.shixun_secret_repository.destroy
end
end
end
rescue => e
uid_logger_error(e.message)
tip_exception("基本信息更新失败:#{e.message}")
end
end
# 实训权限设置
def update_permission_setting
# 查找需要增删的高校id
school_id = School.where(:name => params[:scope_partment]).pluck(:id)
old_school_ids = @shixun.shixun_schools.pluck(:school_id)
school_params = (school_id - old_school_ids).map{|id| {school_id: id}}
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes!(shixun_params)
@shixun.shixun_schools.where.not(school_id: school_id).destroy_all if school_id.present?
@shixun.shixun_schools.create!(school_params)
end
rescue => e
uid_logger_error("实训权限设置失败--------#{e.message}")
tip_exception("实训权限设置失败")
end
end
# 实训学习页面设置
def update_learn_setting
begin
ActiveRecord::Base.transaction do
update_params =
if params[:shixun][:vnc]
shixun_params.merge(vnc_evaluate: 1)
else
shixun_params.merge(vnc_evaluate: 0)
end
@shixun.update_attributes!(update_params)
end
rescue => e
uid_logger_error("实训学习页面设置失败--------#{e.message}")
tip_exception("实训学习页面设置失败")
end
end
# Jupyter数据集
def get_data_sets
page = params[:page] || 1
limit = params[:limit] || 10
data_sets = @shixun.data_sets
@data_count = data_sets.count
@data_sets= data_sets.order("created_on desc").page(page).per(limit)
@absolute_folder = edu_setting('shixun_folder')
end
# 实训测试集附件
def upload_data_sets
begin
upload_file = params["file"]
raise "未上传文件" unless upload_file
folder = edu_setting('shixun_folder')
raise "存储目录未定义" unless folder.present?
rep_name = @shixun.data_sets.pluck(:filename).include?(upload_file.original_filename)
raise "文件名已经存在\"#{upload_file.original_filename}\", 请删除后再上传" if rep_name
tpm_folder = params[:identifier] # 这个是实训的identifier
save_path = File.join(folder, tpm_folder)
ext = file_ext(upload_file.original_filename)
local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext)
content_type = upload_file.content_type.presence || 'application/octet-stream'
disk_filename = local_path[save_path.size + 1, local_path.size]
@attachment = Attachment.where(disk_filename: disk_filename,
author_id: current_user.id).first
if @attachment.blank?
@attachment = Attachment.new
@attachment.filename = upload_file.original_filename
@attachment.disk_filename = local_path[save_path.size + 1, local_path.size]
@attachment.filesize = upload_file.tempfile.size
@attachment.content_type = content_type
@attachment.digest = digest
@attachment.author_id = current_user.id
@attachment.disk_directory = tpm_folder
@attachment.container_id = @shixun.id
@attachment.container_type = @shixun.class.name
@attachment.attachtype = 2
@attachment.save!
else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
end
render_ok
rescue => e
uid_logger_error(e.message)
tip_exception(e.message)
end
end
# 多文件删除
def destroy_data_sets
files = Attachment.where(id: params[:id])
shixun_folder= edu_setting("shixun_folder")
begin
files.each do |file|
file_path = "#{shixun_folder}/#{file.relative_path_filename}"
delete_file(file_path)
end
files.destroy_all
render_ok
rescue => e
uid_logger_error(e.message)
tip_exception(e.message)
end
end
def apply_shixun_mirror
form_params = params.permit(*%i[language runtime run_method attachment_id])
form = ApplyShixunMirrorForm.new(form_params)
form.validate!
tiding = Tiding.new(
user_id: 1,
trigger_user_id: current_user.id,
container_type: 'SendMessage',
viewed: 0,
tiding_type: 'Apply',
extra: form.to_json
)
ActiveRecord::Base.transaction do
# TODO: 由于tiding是多态,而SendMessage却没有对应的model,因此,如果不跳过验证会报 container must exist的错.
tiding.save(validate: false)
form.attachment.update!(container: tiding)
end
sucess_status
rescue ActiveModel::ValidationError => ex
tip_exception(ex.message)
rescue Exception => e
uid_logger_error(e.message)
tip_exception("申请失败")
end
# 永久关闭实训
def close
@shixun.update_attributes(status: 3, closer_id: current_user.id, end_time: Time.now)
sucess_status
end
def propaedeutics
@content = Shixun.find_by_identifier!(params[:identifier]).propaedeutics
end
# 更新背景知识
def update_propaedeutics
@shixun.shixun_info.update_column(:propaedeutics, params[:content])
end
# 获取推荐实训接口 2个热门实训 + 2个最新实训
def get_recommend_shixuns
hot_shixuns = Shixun.field_for_recommend.published.order("myshixuns_count desc").limit(2)
newest_shixuns = Shixun.field_for_recommend.published.order("created_at desc").limit(2)
@recommend_shixuns = hot_shixuns + newest_shixuns
end
def settings
@choice_main_type = @shixun.main_mirror_id
@choice_small_type = @shixun.small_mirror_id
@main_type = shixun_main_type
@small_type = shixun_small_type
@configs = @shixun.shixun_service_configs
#@mirror_script = MirrorScript.select([:id, :script_type]).find(@shixun.mirror_script_id).attributes if @shixun.mirror_script_id && @shixun.mirror_script_id != 0
# @shixun_main_mirror = @shixun.show_shixun_mirror
# @script_type = @shixun.script_tag.try(:script_type) || "无"
# @evaluate_scirpt = @shixun.evaluate_script || "无"
end
# 获取脚本内容
def get_script_contents
mirrir_script = MirrorScript.find(params[:script_id])
script = mirrir_script.try(:script)
@description = mirrir_script.try(:description)
@script = modify_shixun_script @shixun, script
end
def get_custom_script
shixun_script = PlatformSample.where(:samples_type => "script").first.try(:contents)
compile = params[:compile]
execute = params[:executive]
if shixun_script
shixun_script = compile.blank? ? shixun_script.gsub("COMPILEFUNCTION", "").gsub("CHALLENGEFIELPATH", "") :
shixun_script.gsub("COMPILEFUNCTION", "#{compile_command}").gsub("COMPILECOMMAND", "#{compile}")
shixun_script = execute.blank? ? shixun_script.gsub("EXECUTEFUNCTION", "") : shixun_script.gsub("EXECUTECOMMAND", "#{execute}")
shixun_script = modify_shixun_script @shixun, shixun_script
end
@shixun_script = shixun_script
end
def departments
@scope = []
q = params[:q]
if q && q.strip.present?
@scope = School.where("name like ?", "%#{q.strip}%").pluck(:name)
end
end
def get_mirror_script
mirror = MirrorRepository.find(params[:mirror_id])
@script = mirror.mirror_scripts
end
# TODO: 目前实训只做软删除.
def destroy
apply_records = ApplyAction.where(container_id: @shixun.id, container_type: "ApplyShixun")
apply_records.delete_all if apply_records
# HomeworkCommonShixuns.where(shixun_id: @shixun).delete_all
# @shixun.destroy
@shixun.update_column(:status, -1)
end
# 开启挑战
# 以前在开启挑战的时候检测实训是否更新更新则重置觉得应该放在TPI更好
# 中间需要一个过渡动画
# TODO: 第一次开启实训都会去判断是否是纯选择题类型,感觉做成在创建关卡的时候就判断该实训是否是纯选择题更加合适
def shixun_exec
if is_shixun_opening?
tip_show_exception(-3, "#{@shixun.opening_time.strftime('%Y-%m-%d %H:%M:%S')}")
end
current_myshixun = @shixun.current_myshixun(current_user.id)
min_challenges = @shixun.challenges.pluck(:id , :st)
Rails.logger.info("11111111112#{current_myshixun.try(:id)}")
Rails.logger.info("111111111102#{params[:reset] != 1}")
# 因为读写分离有延迟所以如果是重置来的请求可以先跳过重置过来的params[:reset]为1
if current_myshixun && params[:reset] != "1"
games = current_myshixun.games
# 如果TPM和TPI的管卡数不相等或者关卡顺序错了说明实训被极大的改动需要重置,实训发布前打过的实训都需要重置
if is_shixun_reset?(games, min_challenges, current_myshixun)
# 这里页面弹框要收到 当前用户myshixun的identifier.
tip_show_exception("/myshixuns/#{current_myshixun.try(:identifier)}/reset_my_game")
end
# 如果存在实训,则直接进入实训
# 如果实训允许跳关传参params[:challenge_id]跳入具体的关卡
@current_task =
if params[:challenge_id]
game = games.where(challenge_id: params[:challenge_id]).take
if @shixun.task_pass || game.status != 3
game
else
current_myshixun.current_task(games)
end
else
current_myshixun.current_task(games)
end
else
# 如果未创建关卡一定不能开启实训否则TPI没法找到当前的关卡
if @shixun.challenges_count == 0
tip_exception("开启实战前请先创建实训关卡")
end
# 判断实训是否全为选择题
is_choice_type = (min_challenges.size == min_challenges.select{|challenge| challenge.last == 1}.count)
if !is_choice_type
commit = GitService.commits(repo_path: @repo_path).try(:first)
uid_logger("First comit########{commit}")
tip_exception("开启实战前请先在版本库中提交代码") if commit.blank?
commit_id = commit["id"]
end
# 如果该实训是金课中的实训,则将当前用户加入到当期开课的课堂
if StageShixun.exists?(shixun_id: @shixun.id, subject_id: Subject.where(excellent: 1))
subject = Subject.find_by(id: StageShixun.where(shixun_id: @shixun.id).pluck(:subject_id), excellent: 1)
course = subject.courses.where("start_date is not null and start_date <= '#{Date.today}' and end_date is not null and end_date >= '#{Date.today}'").take
if course.present? && !CourseMember.exists?(course_id: course.id, user_id: current_user.id)
# 为了不影响后续操作用create而不是create!
CourseMember.create(course_id: course.id, user_id: current_user.id, role: 4)
CourseAddStudentCreateWorksJob.perform_later(course.id, [current_user.id])
end
end
ActiveRecord::Base.transaction do
begin
cloud_bridge = edu_setting('cloud_bridge')
myshixun_identifier = generate_identifier Myshixun, 10
myshixun = @shixun.myshixuns.create!(user_id: current_user.id, identifier: myshixun_identifier,
modify_time: @shixun.modify_time, reset_time: @shixun.reset_time,
onclick_time: Time.now, commit_id: commit_id, repo_name: (is_choice_type ? "-1" : nil))
uid_logger("myshixun_id is #{myshixun.id}")
# 其它创建关卡等操作
challenges = @shixun.challenges
# 之所以增加user_id是为了方便统计查询性能
game_attrs = %i[challenge_id myshixun_id status user_id open_time identifier modify_time created_at updated_at]
Game.bulk_insert(*game_attrs) do |worker|
base_attr = { myshixun_id: myshixun.id, user_id: myshixun.user_id }
challenges.each_with_index do |challenge, index|
status = (index == 0 ? 0 : 3)
game_identifier = generate_identifier(Game, 12)
worker.add(base_attr.merge(challenge_id: challenge.id, status: status,
identifier: game_identifier, modify_time: challenge.modify_time))
end
end
# 如果实训是纯选择题则不需要去fork仓库以及中间层的相关操作了
unless is_choice_type
# fork仓库
project_fork(myshixun, @repo_path, current_user.login)
rep_url = Base64.urlsafe_encode64(repo_ip_url @repo_path )
uid_logger("start openGameInstance")
uri = "#{cloud_bridge}/bridge/game/openGameInstance"
logger.info("end openGameInstance")
params = {tpiID: "#{myshixun.id}", tpmGitURL:rep_url, tpiRepoName: myshixun.repo_name.split("/").last}
uid_logger("openGameInstance params is #{params}")
interface_post uri, params, 83, "实训云平台繁忙繁忙等级83"
end
@current_task = myshixun.current_task(myshixun.games)
uid_logger("## shixun exec: myshixun id is #{myshixun.id}")
rescue Exception => e
uid_logger_error(e.message)
tip_exception("实训云平台繁忙繁忙等级81")
raise ActiveRecord::Rollback
end
end
end
end
# jupyter开启挑战
def jupyter_exec
if is_shixun_opening?
tip_show_exception(-3, "#{@shixun.opening_time.strftime('%Y-%m-%d %H:%M:%S')}")
end
current_myshixun = @shixun.current_myshixun(current_user.id)
if current_myshixun
@myshixun = current_myshixun
else
commit = GitService.commits(repo_path: @repo_path).try(:first)
uid_logger("First comit########{commit}")
tip_exception("开启挑战前请先在Jupyter中填写内容并保存") if commit.blank?
commit_id = commit["id"]
cloud_bridge = edu_setting('cloud_bridge')
myshixun_identifier = generate_identifier Myshixun, 10
begin
ActiveRecord::Base.transaction do
@myshixun = @shixun.myshixuns.create!(user_id: current_user.id, identifier: myshixun_identifier,
modify_time: @shixun.modify_time, reset_time: @shixun.reset_time,
onclick_time: Time.now, commit_id: commit_id)
# fork仓库
project_fork(@myshixun, @repo_path, current_user.login)
rep_url = Base64.urlsafe_encode64(repo_ip_url @repo_path)
uri = "#{cloud_bridge}/bridge/game/openGameInstance"
params = {tpiID: "#{@myshixun.id}", tpmGitURL: rep_url, tpiRepoName: @myshixun.repo_name.split("/").last}
interface_post uri, params, 83, "服务器出现问题,请重置环境"
end
rescue => e
uid_logger_error(e.message)
tip_exception("服务器出现问题,请重置环境")
end
end
end
def publish
@status = 0
@position = []
begin
unless @shixun.is_jupyter?
if @shixun.challenges.count == 0
@status = 4
else
@shixun.challenges.each do |challenge|
if challenge.challenge_tags.count == 0
@status = 3
@position << challenge.position
end
end
unfinish_challenge = @shixun.challenges.where(:st => 0, :path => nil)
if unfinish_challenge.count > 0 && !@shixun.is_choice_type?
@status = 2
@pos = []
unfinish_challenge.each do |challenge|
@pos << challenge.position
end
end
end
end
if @status == 0
@shixun.update_attributes!(:status => 2)
end
rescue Exception => e
logger.error("pushlish game #{e}")
end
end
def apply_public
tip_exception(-1, "请先发布实训再申请公开") if @shixun.status != 2
ActiveRecord::Base.transaction do
@shixun.update_attributes!(public: 1)
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
@status = 0
else
ApplyAction.create(:container_type => "ApplyShixun", :container_id => @shixun.id, :user_id => current_user.id, :status => 0)
end
end
normal_status(0, "申请成功")
end
# 设置私密版本库的在tpm中的目录
def set_secret_dir
raise("设置路径不能为空") if params[:secret_dir_path].blank?
raise("请先配置私密版本库") if @shixun.shixun_secret_repository.blank?
@shixun.shixun_secret_repository.update_attributes(:secret_dir_path => params[:secret_dir_path])
normal_status("设置成功")
end
def secret_repository
begin
@repo_path = @shixun.shixun_secret_repository&.repo_path
@repo_url = repo_url @repo_path
@trees = GitService.file_tree(repo_path: @repo_path, path: params[:path])
logger.info("#11@@#@#@#@111#@@@@###{@trees}")
if @trees
logger.info("#@@#@#@#@#@@@@###{@trees.try(:count)}")
@latest_commit = [GitService.commits(repo_path: @repo_path).first]
Rails.logger.info("########## #{@latest_commit}")
end
rescue Exception => e
logger.error(e.message)
end
end
def update_file
content = params[:content]
author_name = current_user.real_name
author_email = current_user.git_mail
@content = update_file_content content, @repo_path, @path, author_email, author_name, "Edit by browser"
end
def upload_git_file
upload_file = params["file"]
uid_logger("#########################file_params##: #{params["file"]}")
raise "未上传文件" unless upload_file
content = upload_file.tempfile.read
uid_logger("#########################content####{content}")
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:message] || "upload file by browser"
uid_logger("-----author_email: #{author_email}")
path = @path.present? ? "#{@path}/#{upload_file.original_filename}" : "#{upload_file.original_filename}"
update_file_base64_content(content, @repo_path, path, author_email, author_name, message)
render_ok
end
# 上传目录
def upload_git_folder
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:message] || "upload folder by browser"
git_add_folder(@repo_path, @path, author_name, author_email, message)
render_ok
end
def delete_git_file
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:message] || "delete file by browser"
git_delete_file(@repo_path, @path, author_name, author_email, message)
render_ok
end
def add_collaborators
member_ids = "(" + @shixun.shixun_members.map(&:user_id).join(',') + ")"
user_name = "%#{params[:user_name].to_s.strip}%"
school_name = "%#{params[:school_name].to_s.strip}%"
if user_name.present? || school_name.present?
@users = User.where("users.id not in #{member_ids} AND users.status = 1 AND
(LOWER(concat(users.lastname, users.firstname)) LIKE ? or users.phone like ?)",
user_name, user_name)
@users = @users.joins(user_extension: :school).where("schools.name like '%#{school_name}%'") if params[:school_name].present?
else
@users = User.none
end
@users = @users.where(laboratory_id: current_laboratory.id) unless current_laboratory.main_site?
page = params[:page] || 1
limit = params[:limit] || 20
@user_count = @users.count
@users = @users.page(page).per(20)
end
def shixun_members_added
raise("user_ids 不能为空!") if params[:user_ids].blank?
memberships = params[:user_ids]
memberships.each do |member|
ShixunMember.create!(:user_id => member, :shixun_id => @shixun.id, :role => 2)
end
end
def change_manager
# 搜索成员
if request.get?
@collaborators = @shixun.shixun_members.where("user_id != #{@shixun.user_id}")
else
begin
raise("请先选择成员") if params[:user_id].blank?
man_member = ShixunMember.where(:shixun_id => @shixun.id, :user_id => @shixun.user_id).first
cha_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id).first
if man_member && cha_member
man_member.update_attribute(:role, 2)
cha_member.update_attribute(:role, 1)
@shixun.update_attribute(:user_id, cha_member.user_id)
end
rescue => e
logger.error("######change_manager_error: #{e.message}")
render_error(e.message)
end
end
end
# 刪除合作者
def collaborators_delete
raise("user_id不能为空") if params[:user_id].blank?
shixun_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id, :role => 2).first
shixun_member.delete
end
# 实训的发送至课堂:搜索课堂
def search_user_courses
## 分页参数
page = params[:page] || 1
limit = params[:limit] || 20
if params[:search]
search = "%#{params[:search].to_s.strip.downcase}%"
course_ids = Course.find_by_sql("SELECT c.id FROM courses c, course_members m
WHERE m.course_id = c.id AND m.role in (1,2,3)
AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0
AND c.name like '#{search}' ").map(&:id)
else
course_ids = Course.find_by_sql("SELECT c.id, c.name FROM courses c, course_members m
WHERE m.course_id = c.id AND m.role in (1,2,3)
AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0").map(&:id)
end
@courses = Course.where(:id => course_ids)
## 云上实验室过滤
@courses = @courses.where(id: current_laboratory.all_courses)
@course_count = @courses.count
@courses = @courses.page(page).per(10)
end
# 将实训发送到课程
def send_to_course
@course = Course.find(params[:course_id])
homework = HomeworksService.new.create_homework @shixun, @course, nil, current_user
CreateStudentWorkJob.perform_later(homework.id)
end
# 批量发送
def batch_send_to_course
@course = Course.find_by!(id: params[:course_id])
shixuns = Shixun.where(id: params[:shixun_ids]).unhidden
shixuns.each do |shixun|
homework = HomeworksService.new.create_homework shixun, @course, nil, current_user
CreateStudentWorkJob.perform_later(homework.id)
end
end
# 二维码扫描下载
def download_file
file_path = params[:file_name]
send_file "#{Rails.root}/#{file_path}", :filename => "#{file_path}",
:type => 'shixun',
:disposition => 'attachment' #inline can open in browser
end
# 撤销申请公开
def cancel_apply_public
tip_exception("实训已经公开,无法撤销") if @shixun.public == 2
ActiveRecord::Base.transaction do
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
apply.update_attributes!(status: 3)
apply.tidings&.destroy_all
end
@shixun.update_column(:public, 0)
end
normal_status(0, "成功撤销申请")
end
# 撤销发布
def cancel_publish
tip_exception("请先撤销申请公开,再撤销发布") if @shixun.public == 1
tip_exception("实训已经公开,无法撤销") if @shixun.public == 2
@shixun.update_column(:status, 0)
end
# 创建实训审核
def review_shixun
validate_review_shixun_params
# 没有记录就创建记录, 如果有记录就
@shixun.shixun_reviews.create!(user_id: current_user.id, status: params[:status],
review_type: params[:review_type], evaluate_content: params[:evaluate_content])
normal_status("审核完成")
end
# 实训审核最新记录
def review_newest_record
@content_record = @shixun.shixun_reviews.where(review_type: "Content").first
@perfer_record = @shixun.shixun_reviews.where(review_type: "Performance").first
end
private
def shixun_params
params.require(:shixun).permit(:name, :trainee, :webssh, :can_copy, :use_scope, :vnc, :test_set_permission,
:task_pass, :multi_webssh, :opening_time, :mirror_script_id, :code_hidden,
:hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission, :is_jupyter)
end
def validate_review_shixun_params
tip_exception("只有平台管理员或运营人员才能审核") if !admin_or_business?
tip_exception("审核类型参数不对") unless ["Content", "Performance"].include?(params[:review_type])
end
def shixun_info_params
params.require(:shixun_info).permit(:description, :evaluate_script)
end
def service_config_params
params.permit(shixun_service_configs: [:cpu_limit, :lower_cpu_limit, :memory_limit, :request_limit, :mirror_repository_id])
end
def find_shixun
@shixun = Shixun.find_by_identifier(params[:identifier])
if @shixun.blank?
normal_status(404, "...")
return
end
end
def find_repo_name
# 有私密版本库的参数时,需要拿私密仓库
@repo_path = if params[:secret_repository]
@shixun.shixun_secret_repository&.repo_path
else
@shixun.repo_path
end
@path = params[:path]
end
def allowed
unless current_user.manager_of_shixun?(@shixun)
tip_exception(403, "..")
end
end
def portion_allowed
if current_user.shixun_identity(@shixun) > User::EDU_CERTIFICATION_TEACHER
raise Educoder::TipException.new(403, "..")
end
end
def special_allowed
if @shixun.status != 2
tip_exception(403, "..")
end
end
# 实训是否需要开启
def is_shixun_opening?
@shixun.opening_time.present? &&
@shixun.opening_time > Time.now &&
current_user.shixun_identity(@shixun) > User::EDU_SHIXUN_MEMBER
end
# 实训是否需要重置
def is_shixun_reset?(games, min_challenges, current_myshixun)
# 用户在申请发布之前,是否玩过实训 TODO: 重置的字段应该迁移到myshixuns表比较合适
modify_shixun = ShixunModify.exists?(:myshixun_id => current_myshixun.id, :shixun_id => @shixun.id, :status => 1)
games.size != min_challenges.size || modify_shixun
end
# 添加私密仓库
def add_secret_repository
# 防止跟tpm版本库重名加了前缀secret
repo_path = repo_namespace(current_user.login, "secret_#{@shixun.identifier}")
GitService.add_repository(repo_path: repo_path)
ShixunSecretRepository.create!(repo_name: repo_path.split(".")[0], shixun_id: @shixun.id)
end
def file_save_to_local(save_path, temp_file, ext)
unless Dir.exists?(save_path)
FileUtils.mkdir_p(save_path) ##不成功这里会抛异常
end
digest = md5_file(temp_file)
digest = "#{digest}_#{(Time.now.to_f * 1000).to_i}"
local_file_path = File.join(save_path, digest) + ext
save_temp_file(temp_file, local_file_path)
[local_file_path, digest]
end
def save_temp_file(temp_file, save_file_path)
File.open(save_file_path, 'wb') do |f|
temp_file.rewind
while (buffer = temp_file.read(8192))
f.write(buffer)
end
end
end
def file_ext(file_name)
ext = ''
exts = file_name.split(".")
if exts.size > 1
ext = ".#{exts.last}"
end
ext
end
def delete_file(file_path)
File.delete(file_path) if File.exist?(file_path)
end
def md5_file(temp_file)
md5 = Digest::MD5.new
temp_file.rewind
while (buffer = temp_file.read(8192))
md5.update(buffer)
end
md5.hexdigest
end
# def validate_wachat_support
#
# if (params[:wechat].present? && !@shixun.is_wechat_support?)
# tip_exception(-5, "..")
# end
# end
end