diff --git a/.gitignore b/.gitignore index 98965d6da..2d915d1cd 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ /config/secrets.yml /config/redis.yml /config/elasticsearch.yml +/config/aliyun_vod.yml public/upload.html /config/configuration.yml diff --git a/app/controllers/callbacks/aliyun_vods_controller.rb b/app/controllers/callbacks/aliyun_vods_controller.rb new file mode 100644 index 000000000..cd385995d --- /dev/null +++ b/app/controllers/callbacks/aliyun_vods_controller.rb @@ -0,0 +1,24 @@ +class Callbacks::AliyunVodsController < Callbacks::BaseController + before_action :check_signature_valid! + + def create + Videos::DispatchCallbackService.call(params) + render_ok + end + + private + + def check_signature_valid! + return if AliyunVod::Sign.verify?(header_signature, header_timestamp) + + render_not_acceptable + end + + def header_timestamp + request.headers['X-VOD-TIMESTAMP'] + end + + def header_signature + request.headers['X-VOD-SIGNATURE'] + end +end \ No newline at end of file diff --git a/app/controllers/callbacks/base_controller.rb b/app/controllers/callbacks/base_controller.rb new file mode 100644 index 000000000..42e1ae972 --- /dev/null +++ b/app/controllers/callbacks/base_controller.rb @@ -0,0 +1,5 @@ +class Callbacks::BaseController < ActionController::Base + include RenderHelper + + skip_before_action :verify_authenticity_token +end \ No newline at end of file diff --git a/app/controllers/concerns/render_helper.rb b/app/controllers/concerns/render_helper.rb index 5ea73e666..0307bd4c9 100644 --- a/app/controllers/concerns/render_helper.rb +++ b/app/controllers/concerns/render_helper.rb @@ -7,6 +7,10 @@ module RenderHelper render json: { status: -1, message: message } end + def render_not_acceptable(message = '请求已拒绝') + render json: { status: 406, message: message } + end + def render_not_found(message = I18n.t('error.record_not_found')) render json: { status: 404, message: message } # render status: 404, json: { errors: errors } diff --git a/app/controllers/users/base_controller.rb b/app/controllers/users/base_controller.rb index 969aca320..706b77802 100644 --- a/app/controllers/users/base_controller.rb +++ b/app/controllers/users/base_controller.rb @@ -21,7 +21,7 @@ class Users::BaseController < ApplicationController def private_user_resources! require_login - return if current_user.admin? || observed_logged_user? + return if current_user.admin_or_business? || observed_logged_user? render_forbidden end diff --git a/app/controllers/users/video_auths_controller.rb b/app/controllers/users/video_auths_controller.rb new file mode 100644 index 000000000..d7950f873 --- /dev/null +++ b/app/controllers/users/video_auths_controller.rb @@ -0,0 +1,26 @@ +class Users::VideoAuthsController < Users::BaseController + before_action :private_user_resources! + + def create + result = Videos::CreateAuthService.call(observed_user, create_params) + render_ok(data: result) + rescue Videos::CreateAuthService::Error => ex + render_error(ex.message) + end + + def update + video = observed_user.videos.find_by(uuid: params[:video_id]) + return render_error('该视频凭证不存在') if video.blank? + + result = AliyunVod::Service.refresh_upload_video(video.uuid) + render_ok(data: result) + rescue AliyunVod::Error => _ + render_error('刷新上传凭证失败') + end + + private + + def create_params + params.permit(:title, :file_name, :file_size, :description, :cover_url) + end +end \ No newline at end of file diff --git a/app/controllers/users/videos_controller.rb b/app/controllers/users/videos_controller.rb new file mode 100644 index 000000000..738b5c6da --- /dev/null +++ b/app/controllers/users/videos_controller.rb @@ -0,0 +1,58 @@ +class Users::VideosController < Users::BaseController + before_action :private_user_resources! + + helper_method :current_video + + def index + videos = Users::VideoQuery.call(observed_user, search_params) + + @count = videos.count + @videos = paginate videos + end + + def update + return render_error('该状态下不能编辑视频信息') unless current_video.published? + + current_video.update!(title: params[:title]) + + AliyunVod::Service.update_video_info(current_video.uuid, Title: current_video.title) rescue nil + end + + def cancel + video = observed_user.videos.find_by(uuid: params[:video_id]) + return render_not_found if video.blank? + return render_error('该状态下不能删除视频') unless video.pending? + + video.destroy! + AliyunVod::Service.delete_video([video.uuid]) rescue nil + + render_ok + end + + def review + params[:status] = 'processing' + videos = Users::VideoQuery.call(observed_user, params) + + @count = videos.count + @videos = paginate videos + end + + def batch_publish + Videos::BatchPublishService.call(observed_user, batch_publish_params) + render_ok + end + + private + + def current_video + @_current_video ||= observed_user.videos.find_by(id: params[:id]) + end + + def search_params + params.permit(:keyword, :sort_by, :sort_direction) + end + + def batch_publish_params + params.permit(videos: []) + end +end \ No newline at end of file diff --git a/app/decorators/tiding_decorator.rb b/app/decorators/tiding_decorator.rb index d1f6f08e6..ab9de4b76 100644 --- a/app/decorators/tiding_decorator.rb +++ b/app/decorators/tiding_decorator.rb @@ -372,4 +372,12 @@ module TidingDecorator I18n.t(locale_format(tiding_type)) % [container.try(:title) || extra] end end + + def video_content + if tiding_type == 'System' + I18n.t(locale_format(tiding_type, status), reason: extra) % container.try(:title) + else + I18n.t(locale_format(tiding_type)) % [container.try(:title) || extra] + end + end end diff --git a/app/decorators/video_decorator.rb b/app/decorators/video_decorator.rb new file mode 100644 index 000000000..904e78dbb --- /dev/null +++ b/app/decorators/video_decorator.rb @@ -0,0 +1,5 @@ +module VideoDecorator + extend ApplicationDecorator + + display_time_method :published_at, :created_at, :updated_at +end \ No newline at end of file diff --git a/app/jobs/batch_publish_video_notify_job.rb b/app/jobs/batch_publish_video_notify_job.rb new file mode 100644 index 000000000..01390dc2d --- /dev/null +++ b/app/jobs/batch_publish_video_notify_job.rb @@ -0,0 +1,23 @@ +# 批量发布视频 消息任务 +class BatchPublishVideoNotifyJob < ApplicationJob + queue_as :notify + + def perform(user_id, video_ids) + user = User.find_by(id: user_id) + return if user.blank? + + attrs = %i[user_id trigger_user_id container_id container_type tiding_type status created_at updated_at] + + same_attrs = { + user_id: 1, + trigger_user_id: user.id, + container_type: 'Video', + tiding_type: 'Apply', status: 0 + } + Tiding.bulk_insert(*attrs) do |worker| + user.videos.where(id: video_ids).each do |video| + worker.add same_attrs.merge(container_id: video.id) + end + end + end +end diff --git a/app/jobs/get_aliyun_video_info_job.rb b/app/jobs/get_aliyun_video_info_job.rb new file mode 100644 index 000000000..d93186458 --- /dev/null +++ b/app/jobs/get_aliyun_video_info_job.rb @@ -0,0 +1,17 @@ +# 获取阿里云视频信息 +class GetAliyunVideoInfoJob < ApplicationJob + queue_as :default + + def perform(vod_video_id) + video = Video.find_by(uuid: vod_video_id) + return if video.blank? || video.vod_uploading? + + result = AliyunVod::Service.get_play_info(video.uuid) + cover_url = result.dig('VideoBase', 'CoverURL') + file_url = (result.dig('PlayInfoList', 'PlayInfo') || []).first&.[]('PlayURL') + + video.cover_url = cover_url if cover_url.present? && video.cover_url.blank? + video.file_url = file_url if file_url.present? + video.save! + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod.rb b/app/libs/aliyun_vod.rb new file mode 100644 index 000000000..9ce44a87f --- /dev/null +++ b/app/libs/aliyun_vod.rb @@ -0,0 +1,5 @@ +module AliyunVod + class << self + attr_accessor :access_key_id, :access_key_secret, :base_url, :callback_url, :signature_key + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/error.rb b/app/libs/aliyun_vod/error.rb new file mode 100644 index 000000000..a34785774 --- /dev/null +++ b/app/libs/aliyun_vod/error.rb @@ -0,0 +1,2 @@ +class AliyunVod::Error < StandardError +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/service.rb b/app/libs/aliyun_vod/service.rb new file mode 100644 index 000000000..9350d2f43 --- /dev/null +++ b/app/libs/aliyun_vod/service.rb @@ -0,0 +1,8 @@ +module AliyunVod::Service + extend AliyunVod::Service::Base + + extend AliyunVod::Service::VideoUpload + extend AliyunVod::Service::VideoProcess + extend AliyunVod::Service::VideoManage + extend AliyunVod::Service::VideoPlay +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/service/base.rb b/app/libs/aliyun_vod/service/base.rb new file mode 100644 index 000000000..85f74fc6f --- /dev/null +++ b/app/libs/aliyun_vod/service/base.rb @@ -0,0 +1,46 @@ +module AliyunVod::Service::Base + def request(method, params) + params = AliyunVod::Sign.format_params(params.compact) # 多层hash需要预先处理,保证值为string + params[:Signature] = AliyunVod::Sign.generate(params, method: method.to_s.upcase) + + Rails.logger.info("[AliyunVod] request => method: #{method}, params: #{params}") + + response = Faraday.public_send(method, AliyunVod.base_url, params) + result = JSON.parse(response.body) + + Rails.logger.info("[AliyunVod] response => status: #{response.status}, result: #{result}") + + raise AliyunVod::Error, result['Code'] if response.status != 200 + + result + rescue => ex + ::Util.logger_error(ex) + raise AliyunVod::Error, ex.message + end + + def base_params + { + AccessKeyId: AliyunVod.access_key_id, + Format: 'JSON', + Version: '2017-03-21', + SignatureMethod: 'HMAC-SHA1', + SignatureVersion: '1.0', + SignatureNonce: signature_nonce, + Timestamp: timestamp, + UserData: user_data + } + end + + def user_data + { MessageCallback: { CallbackURL: AliyunVod.callback_url } } + end + + def timestamp + Time.now.utc.iso8601 + end + + def signature_nonce + chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + chars.sample(16).join('') + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/service/video_manage.rb b/app/libs/aliyun_vod/service/video_manage.rb new file mode 100644 index 000000000..703151841 --- /dev/null +++ b/app/libs/aliyun_vod/service/video_manage.rb @@ -0,0 +1,40 @@ +# 视频管理 +module AliyunVod::Service::VideoManage + # 修改视频信息 + def update_video_info(video_id, **opts) + params = { + Action: 'UpdateVideoInfo', + VideoId: video_id + }.merge(base_params) + + params = opts.merge(params) + + result = request(:post, params) + + result + end + + # 获取视频信息 + def get_video_info(video_id) + params = { + Action: 'GetVideoInfo', + VideoId: video_id + }.merge(base_params) + + result = request(:post, params) + + result + end + + # 删除视频信息 + def delete_video(video_ids) + params = { + Action: 'DeleteVideo', + VideoIds: video_ids.join(',') + }.merge(base_params) + + result = request(:post, params) + + result + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/service/video_play.rb b/app/libs/aliyun_vod/service/video_play.rb new file mode 100644 index 000000000..0c3a42abb --- /dev/null +++ b/app/libs/aliyun_vod/service/video_play.rb @@ -0,0 +1,17 @@ +# 视频播放 +module AliyunVod::Service::VideoPlay + # 获取视频播放地址 + # https://help.aliyun.com/document_detail/56124.html?spm=a2c4g.11186623.6.715.4d7e2d52dU1CTK + def get_play_info(video_id, **opts) + params = { + Action: 'GetPlayInfo', + VideoId: video_id + }.merge(base_params) + + params = opts.merge(params) + + result = request(:post, params) + + result + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/service/video_process.rb b/app/libs/aliyun_vod/service/video_process.rb new file mode 100644 index 000000000..eec029c20 --- /dev/null +++ b/app/libs/aliyun_vod/service/video_process.rb @@ -0,0 +1,17 @@ +# 视频处理 +module AliyunVod::Service::VideoProcess + # 提交媒体截图作业 + def submit_snapshot_job(video_id, **opts) + params = { + Action: 'SubmitSnapshotJob', + VideoId: video_id + }.merge(base_params) + params = opts.merge(params) + + result = request(:post, params) + + raise AliyunVod::Error, '提交媒体截图作业失败' if result['SnapshotJob'].blank? + + result + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/service/video_upload.rb b/app/libs/aliyun_vod/service/video_upload.rb new file mode 100644 index 000000000..a16c87c3f --- /dev/null +++ b/app/libs/aliyun_vod/service/video_upload.rb @@ -0,0 +1,33 @@ +# 视频上传 +module AliyunVod::Service::VideoUpload + # 获取视频上传地址和凭证 + def create_upload_video(title, filename, **opts) + params = { + Action: 'CreateUploadVideo', + Title: title, + FileName: filename + }.merge(base_params) + + params = opts.merge(params) + + result = request(:post, params) + + raise AliyunVod::Error, '获取上传凭证失败' if result['UploadAddress'].blank? + + result + end + + # 刷新视频上传凭证 + def refresh_upload_video(video_id) + params = { + Action: 'RefreshUploadVideo', + VideoId: video_id + }.merge(base_params) + + result = request(:post, params) + + raise AliyunVod::Error, '刷新上传凭证失败' if result['UploadAddress'].blank? + + result + end +end \ No newline at end of file diff --git a/app/libs/aliyun_vod/sign.rb b/app/libs/aliyun_vod/sign.rb new file mode 100644 index 000000000..9706cc910 --- /dev/null +++ b/app/libs/aliyun_vod/sign.rb @@ -0,0 +1,41 @@ +module AliyunVod::Sign + # https://help.aliyun.com/document_detail/44434.html?spm=a2c4g.11186623.2.16.354c7853oqlhMb&/#SignatureNonce + def self.generate(params, **opts) + method = opts[:method] || 'POST' + key = opts[:key] || AliyunVod.access_key_secret + '&' + digest = OpenSSL::Digest.new('sha1') + + str = params_to_string(params) + str = percent_encode(str) + str = "#{method}&%2F&#{str}" + + Base64.encode64(OpenSSL::HMAC.digest(digest, key, str)).gsub(/\n/, '') + end + + def self.verify?(signature, timestamp) + content = "#{AliyunVod.callback_url}|#{timestamp}|#{AliyunVod.signature_key}" + our_signature = Digest::MD5.hexdigest(content) + ActiveSupport::SecurityUtils.secure_compare(signature, our_signature) + end + + def self.params_to_string(params) + params.sort.map { |k, v| "#{percent_encode(k)}=#{percent_encode(v)}" }.join('&') + end + + def self.percent_encode(str) + return '' if str.blank? + CGI::escape(str.to_s).gsub('/\+/','%20').gsub('/\*/','%2A').gsub('/%7E/','~') + end + + def self.format_params(params) + params.each_with_object({}) do |arr, obj| + obj[arr[0]] = arr[1].is_a?(Hash) ? parse_hash_to_str(arr[1]) : arr[1] + end + end + + def self.parse_hash_to_str(hash) + hash.each_with_object({}) do |h, obj| + obj[h[0]] = h[1].is_a?(Hash) ? parse_hash_to_str(h[1].clone) : h[1].to_s + end.to_json + end +end \ No newline at end of file diff --git a/app/models/bidding_user.rb b/app/models/bidding_user.rb index b518fb45e..3a427a011 100644 --- a/app/models/bidding_user.rb +++ b/app/models/bidding_user.rb @@ -5,7 +5,7 @@ class BiddingUser < ApplicationRecord belongs_to :project_package, counter_cache: true aasm(:status) do - state :pending, initiali: true + state :pending, initial: true state :bidding_won state :bidding_lost diff --git a/app/models/library.rb b/app/models/library.rb index 3c7222140..e2bc20989 100644 --- a/app/models/library.rb +++ b/app/models/library.rb @@ -16,7 +16,7 @@ class Library < ApplicationRecord validates :uuid, presence: true, uniqueness: true aasm(:status) do - state :pending, initiali: true + state :pending, initial: true state :processing state :refused state :published diff --git a/app/models/library_apply.rb b/app/models/library_apply.rb index 13e195f34..0a7ec8aec 100644 --- a/app/models/library_apply.rb +++ b/app/models/library_apply.rb @@ -4,7 +4,7 @@ class LibraryApply < ApplicationRecord belongs_to :library aasm(:status) do - state :pending, initiali: true + state :pending, initial: true state :refused state :agreed diff --git a/app/models/project_package.rb b/app/models/project_package.rb index 219f60ca4..a8e134918 100644 --- a/app/models/project_package.rb +++ b/app/models/project_package.rb @@ -17,7 +17,7 @@ class ProjectPackage < ApplicationRecord scope :invisible, -> { where(status: %i[pending applying refused]) } aasm(:status) do - state :pending, initiali: true + state :pending, initial: true state :applying state :refused state :published diff --git a/app/models/project_package_apply.rb b/app/models/project_package_apply.rb index 5116f075f..0dd69c0cf 100644 --- a/app/models/project_package_apply.rb +++ b/app/models/project_package_apply.rb @@ -4,7 +4,7 @@ class ProjectPackageApply < ApplicationRecord belongs_to :project_package aasm(:status) do - state :pending, initiali: true + state :pending, initial: true state :refused state :agreed diff --git a/app/models/user.rb b/app/models/user.rb index e999ebdd8..eda7d296d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -135,6 +135,8 @@ class User < ApplicationRecord # 教学案例 has_many :libraries, dependent: :destroy + # 视频 + has_many :videos, dependent: :destroy # Groups and active users scope :active, lambda { where(status: STATUS_ACTIVE) } diff --git a/app/models/video.rb b/app/models/video.rb new file mode 100644 index 000000000..381db58a9 --- /dev/null +++ b/app/models/video.rb @@ -0,0 +1,36 @@ +class Video < ApplicationRecord + include AASM + + belongs_to :user + + has_many :video_applies, dependent: :destroy + has_one :processing_video_apply, -> { where(status: :pending) }, class_name: 'VideoApply' + + aasm(:status) do + state :pending, initial: true + state :processing + state :refused + state :published + + event :apply_publish do + transitions from: :pending, to: :processing + end + + event :refuse do + transitions from: :processing, to: :refused + end + + event :publish do + transitions from: :processing, to: :published, guard: :vod_uploaded? + end + end + + aasm(:vod_status, namespace: :vod) do + state :uploading, initial: true + state :uploaded + + event :upload_success do + transitions from: :uploading, to: :uploaded + end + end +end \ No newline at end of file diff --git a/app/models/video_apply.rb b/app/models/video_apply.rb new file mode 100644 index 000000000..694a448d7 --- /dev/null +++ b/app/models/video_apply.rb @@ -0,0 +1,19 @@ +class VideoApply < ApplicationRecord + include AASM + + belongs_to :video + + aasm(:status) do + state :pending, initial: true + state :refused + state :agreed + + event :refuse do + transitions from: :pending, to: :refused + end + + event :agree do + transitions from: :pending, to: :agreed + end + end +end \ No newline at end of file diff --git a/app/queries/application_query.rb b/app/queries/application_query.rb new file mode 100644 index 000000000..3a92cc6e8 --- /dev/null +++ b/app/queries/application_query.rb @@ -0,0 +1,3 @@ +class ApplicationQuery + include Callable +end \ No newline at end of file diff --git a/app/queries/users/video_query.rb b/app/queries/users/video_query.rb new file mode 100644 index 000000000..e692cb1e5 --- /dev/null +++ b/app/queries/users/video_query.rb @@ -0,0 +1,28 @@ +class Users::VideoQuery < ApplicationQuery + include CustomSortable + + sort_columns :published_at, :title, default_by: :published_at, default_direction: :desc + + attr_reader :user, :params + + def initialize(user, params) + @user = user + @params = params + end + + def call + videos = user.videos.published + + videos = + case params[:status] + when 'published' then videos.published + when 'processing' then videos.processing + else videos.published + end + + keyword = params[:keyword].to_s.strip + videos = videos.where('title LIKE ?', "%#{keyword}%") if keyword.present? + + custom_sort(videos, params[:sort_by], params[:sort_direction]) + end +end \ No newline at end of file diff --git a/app/services/concerns/elasticsearch_able.rb b/app/services/concerns/elasticsearch_able.rb index eb7d4de04..80ca5467e 100644 --- a/app/services/concerns/elasticsearch_able.rb +++ b/app/services/concerns/elasticsearch_able.rb @@ -22,14 +22,7 @@ module ElasticsearchAble fragment_size: EduSetting.get('es_highlight_fragment_size') || 30, tag: '', fields: { - name: { type: 'plain' }, - challenge_names: { type: 'plain' }, - challenge_tag_names: { type: 'plain' }, - description: { type: 'plain' }, - subject_stages: { type: 'plain' }, - content: { type: 'plain' }, - descendants_contents: { type: 'plain' }, - member_user_names: { type: 'plain' } + '*' => { type: 'plain', number_of_fragments: 3 } } } end @@ -48,4 +41,4 @@ module ElasticsearchAble def page params[:page].to_i <= 0 ? 1 : params[:page].to_i end -end \ No newline at end of file +end diff --git a/app/services/videos/agree_apply_service.rb b/app/services/videos/agree_apply_service.rb new file mode 100644 index 000000000..50791935a --- /dev/null +++ b/app/services/videos/agree_apply_service.rb @@ -0,0 +1,35 @@ +class Videos::AgreeApplyService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :video_apply, :video, :user + + def initialize(video_apply, user) + @video_apply = video_apply + @video = video_apply.video + @user = user + end + + def call + raise Error, '该状态下不能进行此操作' unless video_apply.may_agree? && video.may_publish? + + ActiveRecord::Base.transaction do + video_apply.agree! + + video.published_at = Time.now + video.publish + video.save! + + # 将消息改为已处理 + Tiding.where(container_id: video.id, container_type: 'Video', tiding_type: 'Apply', status: 0).update_all(status: 1) + notify_video_author! + end + end + + private + + def notify_video_author! + Tiding.create!(user_id: video.user_id, trigger_user_id: 1, + container_id: video.id, container_type: 'Video', + tiding_type: 'System', status: 1) + end +end \ No newline at end of file diff --git a/app/services/videos/batch_publish_service.rb b/app/services/videos/batch_publish_service.rb new file mode 100644 index 000000000..3ff16dc57 --- /dev/null +++ b/app/services/videos/batch_publish_service.rb @@ -0,0 +1,31 @@ +class Videos::BatchPublishService < ApplicationService + attr_reader :user, :params + + def initialize(user, params) + @user = user + @params = params + end + + def call + video_params = Array.wrap(params[:videos]).compact + return if video_params.blank? + + video_ids = [] + ActiveRecord::Base.transaction do + video_params.each do |param| + video = user.videos.find_by(uuid: param[:video_id]) + next if video.blank? || video.processing_video_apply.present? + + video.title = param[:title].to_s.strip.presence || video.title + video.apply_publish + video.save! + + video.video_applies.create! + + video_ids << video.id + end + end + + BatchPublishVideoNotifyJob.perform_later(user.id, video_ids) if video_ids.present? + end +end \ No newline at end of file diff --git a/app/services/videos/create_auth_service.rb b/app/services/videos/create_auth_service.rb new file mode 100644 index 000000000..8d83ca4e2 --- /dev/null +++ b/app/services/videos/create_auth_service.rb @@ -0,0 +1,41 @@ +class Videos::CreateAuthService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :user, :params + + def initialize(user, params) + @user = user + @params = params.clone + end + + def call + validate! + + result = upload_video_result + + Video.create!(user: user, uuid: result['VideoId'], title: title, cover_url: params[:cover_url]) + + result + end + + private + + def title + @_title ||= params.delete(:title).to_s.strip + end + + def filename + @_filename ||= params.delete(:file_name).to_s.strip + end + + def validate! + raise Error, '视频标题不能为空' if title.blank? + raise Error, '源文件名不能为空' if filename.blank? + end + + def upload_video_result + AliyunVod::Service.create_upload_video(title, filename, params) + rescue AliyunVod::Error => _ + raise Error, '获取视频上传凭证失败' + end +end \ No newline at end of file diff --git a/app/services/videos/dispatch_callback_service.rb b/app/services/videos/dispatch_callback_service.rb new file mode 100644 index 000000000..6121989c8 --- /dev/null +++ b/app/services/videos/dispatch_callback_service.rb @@ -0,0 +1,25 @@ +class Videos::DispatchCallbackService < ApplicationService + attr_reader :video, :params + + def initialize(params) + @video = Video.find_by(uuid: params[:VideoId]) + @params = params + end + + def call + return if video.blank? + + # TODO:: 拆分事件分发 + case params['EventType'] + when 'FileUploadComplete' then + video.file_url = params['FileUrl'] + video.upload_success + video.save! + + GetAliyunVideoInfoJob.perform_later(video.uuid) + end + + rescue => ex + Util.logger_error(ex) + end +end \ No newline at end of file diff --git a/app/services/videos/refuse_apply_service.rb b/app/services/videos/refuse_apply_service.rb new file mode 100644 index 000000000..5d796db79 --- /dev/null +++ b/app/services/videos/refuse_apply_service.rb @@ -0,0 +1,38 @@ +class Videos::RefuseApplyService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :video_apply, :video, :user, :params + + def initialize(video_apply, user, params) + @video_apply = video_apply + @video = video_apply.video + @user = user + @params = params + end + + def call + reason = params[:reason].to_s.strip + raise Error, '原因不能为空' if reason.blank? + raise Error, '该状态下不能进行此操作' unless video_apply.may_refuse? + + ActiveRecord::Base.transaction do + video_apply.reason = reason + video_apply.refuse + video_apply.save! + + video.refuse! + + # 将消息改为已处理 + Tiding.where(container_id: video.id, container_type: 'Video', tiding_type: 'Apply', status: 0).update_all(status: 1) + notify_video_author! + end + end + + private + + def notify_video_author! + Tiding.create!(user_id: video.user_id, trigger_user_id: 1, + container_id: video.id, container_type: 'Video', + tiding_type: 'System', status: 2, extra: video_apply.reason) + end +end \ No newline at end of file diff --git a/app/views/users/videos/_video.json.jbuilder b/app/views/users/videos/_video.json.jbuilder new file mode 100644 index 000000000..9b91c5e94 --- /dev/null +++ b/app/views/users/videos/_video.json.jbuilder @@ -0,0 +1,5 @@ +json.extract! video, :id, :title, :cover_url, :file_url + +json.published_at video.display_published_at +json.created_at video.display_created_at +json.updated_at video.display_updated_at \ No newline at end of file diff --git a/app/views/users/videos/index.json.jbuilder b/app/views/users/videos/index.json.jbuilder new file mode 100644 index 000000000..f4a34aa00 --- /dev/null +++ b/app/views/users/videos/index.json.jbuilder @@ -0,0 +1,2 @@ +json.count @count +json.videos @videos, partial: 'video', as: :video \ No newline at end of file diff --git a/app/views/users/videos/review.json.jbuilder b/app/views/users/videos/review.json.jbuilder new file mode 100644 index 000000000..f6e159c41 --- /dev/null +++ b/app/views/users/videos/review.json.jbuilder @@ -0,0 +1,7 @@ +json.count @count +json.videos do + json.array! @video.each do |video| + json.partial! 'video', video: video + json.file_url nil + end +end \ No newline at end of file diff --git a/app/views/users/videos/update.json.jbuilder b/app/views/users/videos/update.json.jbuilder new file mode 100644 index 000000000..ed69a174c --- /dev/null +++ b/app/views/users/videos/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'video', video: current_video \ No newline at end of file diff --git a/config/aliyun_vod.yml.example b/config/aliyun_vod.yml.example new file mode 100644 index 000000000..ec858982b --- /dev/null +++ b/config/aliyun_vod.yml.example @@ -0,0 +1,15 @@ +defaults: &defaults + access_key_id: 'test' + access_key_secret: 'test' + base_url: 'http://vod.cn-shanghai.aliyuncs.com' + callback_url: 'http://47.96.87.25:48080/api/callbacks/aliyun_vod.json' + signature_key: 'test12345678' + +development: + <<: *defaults + +test: + <<: *defaults + +production: + <<: *defaults \ No newline at end of file diff --git a/config/initializers/aliyun_vod_init.rb b/config/initializers/aliyun_vod_init.rb new file mode 100644 index 000000000..655ce3df6 --- /dev/null +++ b/config/initializers/aliyun_vod_init.rb @@ -0,0 +1,6 @@ +config = Rails.application.config_for(:aliyun_vod) +AliyunVod.access_key_id = config['access_key_id'] +AliyunVod.access_key_secret = config['access_key_secret'] +AliyunVod.base_url = config['base_url'] || 'http://vod.cn-shanghai.aliyuncs.com'.freeze +AliyunVod.callback_url = config['callback_url'] +AliyunVod.signature_key = config['signature_key'] diff --git a/config/locales/tidings/zh-CN.yml b/config/locales/tidings/zh-CN.yml index d00787a7f..a4d213161 100644 --- a/config/locales/tidings/zh-CN.yml +++ b/config/locales/tidings/zh-CN.yml @@ -220,3 +220,8 @@ BiddingEnd_end: "你发布的众包任务:%s,已进入选标阶段,请尽快进行选择确认!" BiddingWon_end: "恭喜,你应征的众包任务:%s,在评选环节中标了" BiddingLost_end: "很遗憾,你应征投稿的众包任务:%s,未中标" + Video: + Apply_end: "申请发布视频:%s" + System: + 1_end: "你提交的发布视频申请:%s,审核已通过" + 2_end: "你提交的发布视频申请:%s,审核未通过
原因:%{reason}" diff --git a/config/routes.rb b/config/routes.rb index b8db3afa6..9e115c3a9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -70,6 +70,16 @@ Rails.application.routes.draw do resources :recent_contacts, only: [:index] resource :private_message_details, only: [:show] resource :unread_message_info, only: [:show] + + # 视频 + resources :videos, only: [:index, :update] do + collection do + get :review + post :batch_publish + post :cancel + end + end + resource :video_auths, only: [:create, :update] end @@ -718,6 +728,8 @@ Rails.application.routes.draw do scope module: :projects do resources :project_applies, only: [:create] end + + post 'callbacks/aliyun_vod', to: 'callbacks/aliyun_vods#create' end #git 认证回调 diff --git a/db/migrate/20190808025851_create_videos.rb b/db/migrate/20190808025851_create_videos.rb new file mode 100644 index 000000000..e7d89f7eb --- /dev/null +++ b/db/migrate/20190808025851_create_videos.rb @@ -0,0 +1,20 @@ +class CreateVideos < ActiveRecord::Migration[5.2] + def change + create_table :videos do |t| + t.references :user, index: true, type: :integer + t.string :title + t.string :uuid, comment: 'Aliyun媒体ID' + t.string :cover_url, comment: '视频封面' + t.string :file_url, comment: '视频地址' + + t.string :status + t.string :vod_status, comment: 'Aliyun媒体状态' + + t.datetime :published_at, index: true + + t.timestamps + + t.index :uuid, unique: true + end + end +end diff --git a/db/migrate/20190809010636_create_video_applies.rb b/db/migrate/20190809010636_create_video_applies.rb new file mode 100644 index 000000000..e8b4037c5 --- /dev/null +++ b/db/migrate/20190809010636_create_video_applies.rb @@ -0,0 +1,10 @@ +class CreateVideoApplies < ActiveRecord::Migration[5.2] + def change + create_table :video_applies do |t| + t.references :video, index: true, type: :integer + t.string :status + t.string :reason + t.timestamps + end + end +end