diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index 79047b516..22af22831 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -1,261 +1,261 @@ -#coding=utf-8 -# -# 文件上传 -class AttachmentsController < ApplicationController - before_action :require_login, :check_auth, except: [:show, :preview_attachment, :get_file] - before_action :find_file, only: %i[show destroy] - before_action :attachment_candown, only: [:show] - skip_before_action :check_sign, only: [:show, :create] - - include ApplicationHelper - - def show - # 1. 优先跳到cdn - # 2. 如果没有cdn,send_file - if @file.cloud_url.present? - update_downloads(@file) - redirect_to @file.cloud_url and return - end - - type_attachment = params[:disposition] || "attachment" - if type_attachment == "inline" - send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf' - elsif type_attachment == "MP4" - send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true - else - send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream') - end - update_downloads(@file) - end - - - def get_file - normal_status(-1, "参数缺失") if params[:download_url].blank? - url = URI.encode(params[:download_url].to_s.gsub("http:", "https:")) - if url.starts_with?(base_url) - domain = GiteaService.gitea_config[:domain] - api_url = GiteaService.gitea_config[:base_url] - url = url.split(base_url)[1].gsub("api", "repos").gsub('?filepath=', '/').gsub('&', '?') - request_url = [domain, api_url, url, "?ref=#{params[:ref]}&access_token=#{current_user&.gitea_token}"].join - response = Faraday.get(request_url) - filename = url.to_s.split("/").pop() - else - response = Faraday.get(url) - filename = params[:download_url].to_s.split("/").pop() - end - send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment') - end - - def create - # 1. 本地存储 - # 2. 上传到云 - begin - upload_file = params["file"] || params["#{params[:file_param_name]}"]# 这里的file_param_name是为了方便其他插件名称 - uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}") - raise "未上传文件" unless upload_file - - folder = file_storage_directory - raise "存储目录未定义" unless folder.present? - - month_folder = current_month_folder - save_path = File.join(folder, month_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' - - # remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type) - remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端 - - logger.info "local_path: #{local_path}" - logger.info "remote_path: #{remote_path}" - - - disk_filename = local_path[save_path.size + 1, local_path.size] - #存数据库 - # - @attachment = Attachment.where(disk_filename: disk_filename, - author_id: current_user.id, - cloud_url: remote_path).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 = month_folder - @attachment.cloud_url = remote_path - @attachment.save! - else - logger.info "文件已存在,id = #{@attachment.id}, filename = #{@attachment.filename}" - end - - render_json - rescue => e - uid_logger_error(e.message) - tip_exception(e.message) - end - end - - def destroy - begin - @file_path = absolute_path(local_path(@file)) - #return normal_status(403, "") unless @file.author == current_user - @file.destroy! - - delete_file(@file_path) - normal_status("删除成功") - rescue Exception => e - uid_logger_error(e.message) - tip_exception(e.message) - raise ActiveRecord::Rollback - end - end - - # 附件为视频时,点击播放 - def preview_attachment - attachment = Attachment.find_by(id: params[:id]) - dir_path = "#{Rails.root}/public/preview" - Dir.mkdir(dir_path) unless Dir.exist?(dir_path) - if params[:status] == "preview" - if system("cp -r #{absolute_path(local_path(attachment))} #{dir_path}/") - render json: {status: 1, url: "/preview/#{attachment.disk_filename}"} - else - normal_status(-1, "出现错误,请稍后重试") - end - else - if system("rm -rf #{dir_path}/#{attachment.disk_filename}") - normal_status(1, "操作成功") - else - normal_status(-1, "出现错误,请稍后重试") - end - end - end - - private - def find_file - @file = - if params[:type] == 'history' - AttachmentHistory.find params[:id] - else - Attachment.find params[:id] - end - end - - def delete_file(file_path) - File.delete(file_path) if File.exist?(file_path) - end - - def current_month_folder - date = Time.now - "#{date.year}/#{date.month.to_s.rjust(2, '0')}" - end - - def file_ext(file_name) - ext = '' - exts = file_name.split(".") - if exts.size > 1 - ext = ".#{exts.last}" - end - ext - 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 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 file_save_to_ucloud(path, file, content_type) - ufile = Gitlink::Ufile.new( - ucloud_public_key: edu_setting('public_key'), - ucloud_private_key: edu_setting('private_key'), - ucloud_public_read: true, - ucloud_public_bucket: edu_setting('public_bucket'), - ucloud_public_bucket_host: edu_setting('public_bucket_host'), - ucloud_public_cdn_host: edu_setting('public_cdn_host'), - ) - File.open(file) do |f| - ufile.put(path, f, 'Content-Type' => content_type) - end - edu_setting('public_cdn_host') + "/" + path - end - - def attachment_candown - unless current_user.admin? || current_user.business? - candown = true - unless params[:type] == 'history' - if @file.container && current_user.logged? - if @file.container.is_a?(Issue) - course = @file.container.project - candown = course.member?(current_user) || course.is_public - elsif @file.container.is_a?(Journal) - course = @file.container.issue.project - candown = course.member?(current_user) - else - course = nil - end - tip_exception(403, "您没有权限进入") if course.present? && !candown - tip_exception(403, "您没有权限进入") if @file.container.is_a?(ApplyUserAuthentication) - end - end - end - end - - def send_file_with_range(path, options = {}) - logger.info("########request.headers: #{request.headers}") - logger.info("########request.headers: #{File.exist?(path)}") - - if File.exist?(path) - size = File.size(path) - logger.info("########request.headers: #{request.headers}") - if !request.headers["Range"] - status_code = 200 # 200 OK - offset = 0 - length = File.size(path) - else - status_code = 206 # 206 Partial Content - bytes = Rack::Utils.byte_ranges(request.headers, size)[0] - offset = bytes.begin - length = bytes.end - bytes.begin - end - response.header["Accept-Ranges"] = "bytes" - response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes - response.header["status"] = status_code - - send_data IO.binread(path, length, offset), options - else - raise ActionController::MissingFile, "Cannot read file #{path}." - end - end - -end +#coding=utf-8 +# +# 文件上传 +class AttachmentsController < ApplicationController + before_action :require_login, :check_auth, except: [:show, :preview_attachment, :get_file] + before_action :find_file, only: %i[show destroy] + before_action :attachment_candown, only: [:show] + skip_before_action :check_sign, only: [:show, :create] + + include ApplicationHelper + + def show + # 1. 优先跳到cdn + # 2. 如果没有cdn,send_file + if @file.cloud_url.present? + update_downloads(@file) + redirect_to @file.cloud_url and return + end + + type_attachment = params[:disposition] || "attachment" + if type_attachment == "inline" + send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf' + elsif type_attachment == "MP4" + send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true + else + send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream') + end + update_downloads(@file) + end + + + def get_file + normal_status(-1, "参数缺失") if params[:download_url].blank? + url = URI.encode(params[:download_url].to_s.gsub("http:", "https:")) + if url.starts_with?(base_url) + domain = GiteaService.gitea_config[:domain] + api_url = GiteaService.gitea_config[:base_url] + url = url.split(base_url)[1].gsub("api", "repos").gsub('?filepath=', '/').gsub('&', '?') + request_url = [domain, api_url, url, "?ref=#{params[:ref]}&access_token=#{current_user&.gitea_token}"].join + response = Faraday.get(request_url) + filename = url.to_s.split("/").pop() + else + response = Faraday.get(url) + filename = params[:download_url].to_s.split("/").pop() + end + send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment') + end + + def create + # 1. 本地存储 + # 2. 上传到云 + begin + upload_file = params["file"] || params["#{params[:file_param_name]}"]# 这里的file_param_name是为了方便其他插件名称 + uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}") + raise "未上传文件" unless upload_file + + folder = file_storage_directory + raise "存储目录未定义" unless folder.present? + + month_folder = current_month_folder + save_path = File.join(folder, month_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' + + # remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type) + remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端 + + logger.info "local_path: #{local_path}" + logger.info "remote_path: #{remote_path}" + + + disk_filename = local_path[save_path.size + 1, local_path.size] + #存数据库 + # + @attachment = Attachment.where(disk_filename: disk_filename, + author_id: current_user.id, + cloud_url: remote_path).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 = month_folder + @attachment.cloud_url = remote_path + @attachment.save! + else + logger.info "文件已存在,id = #{@attachment.id}, filename = #{@attachment.filename}" + end + + render_json + rescue => e + uid_logger_error(e.message) + tip_exception(e.message) + end + end + + def destroy + begin + @file_path = absolute_path(local_path(@file)) + #return normal_status(403, "") unless @file.author == current_user + @file.destroy! + + delete_file(@file_path) + normal_status("删除成功") + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + raise ActiveRecord::Rollback + end + end + + # 附件为视频时,点击播放 + def preview_attachment + attachment = Attachment.find_by(id: params[:id]) + dir_path = "#{Rails.root}/public/preview" + Dir.mkdir(dir_path) unless Dir.exist?(dir_path) + if params[:status] == "preview" + if system("cp -r #{absolute_path(local_path(attachment))} #{dir_path}/") + render json: {status: 1, url: "/preview/#{attachment.disk_filename}"} + else + normal_status(-1, "出现错误,请稍后重试") + end + else + if system("rm -rf #{dir_path}/#{attachment.disk_filename}") + normal_status(1, "操作成功") + else + normal_status(-1, "出现错误,请稍后重试") + end + end + end + + private + def find_file + @file = + if params[:type] == 'history' + AttachmentHistory.find params[:id] + else + Attachment.find params[:id] + end + end + + def delete_file(file_path) + File.delete(file_path) if File.exist?(file_path) + end + + def current_month_folder + date = Time.now + "#{date.year}/#{date.month.to_s.rjust(2, '0')}" + end + + def file_ext(file_name) + ext = '' + exts = file_name.split(".") + if exts.size > 1 + ext = ".#{exts.last}" + end + ext + 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 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 file_save_to_ucloud(path, file, content_type) + ufile = Gitlink::Ufile.new( + ucloud_public_key: edu_setting('public_key'), + ucloud_private_key: edu_setting('private_key'), + ucloud_public_read: true, + ucloud_public_bucket: edu_setting('public_bucket'), + ucloud_public_bucket_host: edu_setting('public_bucket_host'), + ucloud_public_cdn_host: edu_setting('public_cdn_host'), + ) + File.open(file) do |f| + ufile.put(path, f, 'Content-Type' => content_type) + end + edu_setting('public_cdn_host') + "/" + path + end + + def attachment_candown + unless current_user.admin? || current_user.business? + candown = true + unless params[:type] == 'history' + if @file.container && current_user.logged? + if @file.container.is_a?(Issue) + course = @file.container.project + candown = course.member?(current_user) || course.is_public + elsif @file.container.is_a?(Journal) + course = @file.container.issue.project + candown = course.member?(current_user) || course.is_public + else + course = nil + end + tip_exception(403, "您没有权限进入") if course.present? && !candown + tip_exception(403, "您没有权限进入") if @file.container.is_a?(ApplyUserAuthentication) + end + end + end + end + + def send_file_with_range(path, options = {}) + logger.info("########request.headers: #{request.headers}") + logger.info("########request.headers: #{File.exist?(path)}") + + if File.exist?(path) + size = File.size(path) + logger.info("########request.headers: #{request.headers}") + if !request.headers["Range"] + status_code = 200 # 200 OK + offset = 0 + length = File.size(path) + else + status_code = 206 # 206 Partial Content + bytes = Rack::Utils.byte_ranges(request.headers, size)[0] + offset = bytes.begin + length = bytes.end - bytes.begin + end + response.header["Accept-Ranges"] = "bytes" + response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes + response.header["status"] = status_code + + send_data IO.binread(path, length, offset), options + else + raise ActionController::MissingFile, "Cannot read file #{path}." + end + end + +end