| @@ -1,332 +1,365 @@ | |||||
| class UsersController < ApplicationController | |||||
| include ApplicationHelper | |||||
| include Ci::DbConnectable | |||||
| before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard] | |||||
| before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard] | |||||
| before_action :require_login, only: %i[me sync_user_info] | |||||
| before_action :connect_to_ci_db, only: [:get_user_info] | |||||
| before_action :convert_image!, only: [:update, :update_image] | |||||
| skip_before_action :check_sign, only: [:attachment_show] | |||||
| def connect_to_ci_db(options={}) | |||||
| if !(current_user && !current_user.is_a?(AnonymousUser) && current_user.devops_certification?) | |||||
| return | |||||
| end | |||||
| if current_user.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE | |||||
| connect_to_trustie_ci_database(options) | |||||
| else | |||||
| connect_to_ci_database(options) | |||||
| end | |||||
| end | |||||
| def list | |||||
| scope = User.active.recent.like(params[:search]).includes(:user_extension) | |||||
| @total_count = scope.size | |||||
| @users = paginate(scope) | |||||
| end | |||||
| def show | |||||
| #待办事项,现在未做 | |||||
| if User.current.admin? || User.current.login == @user.login | |||||
| @waiting_applied_messages = @user.applied_messages.waiting | |||||
| @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common | |||||
| @common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common | |||||
| #@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size | |||||
| @undo_events = @common_applied_transfer_projects.size + @common_applied_projects.size | |||||
| else | |||||
| @waiting_applied_messages = AppliedMessage.none | |||||
| @common_applied_transfer_projects = AppliedTransferProject.none | |||||
| @common_applied_projects = AppliedProject.none | |||||
| @undo_events = 0 | |||||
| end | |||||
| #用户的组织数量 | |||||
| # @user_composes_count = @user.composes.size | |||||
| @user_composes_count = 0 | |||||
| user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common") | |||||
| @user_org_count = user_organizations.size | |||||
| normal_projects = Project.members_projects(@user.id).to_sql | |||||
| org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql | |||||
| projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct | |||||
| user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible | |||||
| @projects_common_count = user_projects.common.size | |||||
| @projects_mirrior_count = user_projects.mirror.size | |||||
| @projects_sync_mirrior_count = user_projects.sync_mirror.size | |||||
| # 为了缓存活跃用户的基本信息,后续删除 | |||||
| Cache::V2::OwnerCommonService.new(@user.id).read | |||||
| end | |||||
| def watch_users | |||||
| watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc") | |||||
| if params[:search].present? | |||||
| search_user_ids = User.where(id: watchers.pluck(:watchable_id)).like(params[:search]).pluck(:id) | |||||
| watchers = watchers.where(watchable_id: search_user_ids) | |||||
| end | |||||
| @watchers_count = watchers.size | |||||
| @watchers = paginate(watchers) | |||||
| end | |||||
| def fan_users | |||||
| watchers = @user.watchers.includes(:user).order("watchers.created_at desc") | |||||
| watchers = watchers.joins(:user).merge(User.like(params[:search])) | |||||
| @watchers_count = watchers.size | |||||
| @watchers = paginate(watchers) | |||||
| end | |||||
| def hovercard | |||||
| end | |||||
| def update | |||||
| return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) | |||||
| return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| Util.write_file(@image, avatar_path(@user)) if user_params[:image].present? | |||||
| @user.attributes = user_params.except(:image) | |||||
| unless @user.save | |||||
| render_error(-1, @user.errors.full_messages.join(", ")) | |||||
| end | |||||
| end | |||||
| def update_image | |||||
| return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) | |||||
| return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| Util.write_file(@image, avatar_path(@user)) | |||||
| return render_ok({message: '头像修改成功'}) | |||||
| rescue Exception => e | |||||
| uid_logger_error(e.message) | |||||
| render_error(-1, '头像修改失败!') | |||||
| end | |||||
| def get_image | |||||
| return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) | |||||
| return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| redirect_to Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(@user).to_s | |||||
| end | |||||
| def me | |||||
| @user = current_user | |||||
| end | |||||
| # 贴吧获取用户信接口 | |||||
| def get_user_info | |||||
| begin | |||||
| @user = current_user | |||||
| begin | |||||
| result = Notice::Read::CountService.call(current_user.id) | |||||
| @message_unread_total = result.nil? ? 0 : result[2]["unread_notification"] | |||||
| rescue | |||||
| @message_unread_total = 0 | |||||
| end | |||||
| # TODO 等消息上线再打开注释 | |||||
| #@tidding_count = unviewed_tiddings(current_user) if current_user.present? | |||||
| rescue Exception => e | |||||
| uid_logger_error(e.message) | |||||
| missing_template | |||||
| end | |||||
| end | |||||
| def attachment_show | |||||
| file_name = params[:file_name] | |||||
| path = params[:path] || file_storage_directory | |||||
| send_file "#{path}/#{file_name}", :filename => "#{file_name}", | |||||
| :type => 'game', | |||||
| :disposition => 'attachment' #inline can open in browser | |||||
| end | |||||
| def html_show | |||||
| @contents = File.read("#{params[:path]}") | |||||
| respond_to do |format| | |||||
| format.html {render :layout => false} | |||||
| end | |||||
| end | |||||
| # Redo: 消息总数缓存 | |||||
| def get_navigation_info | |||||
| # @old_domain = edu_setting('old_edu_host') | |||||
| # @user = current_user | |||||
| # # 新消息数 | |||||
| # @new_message = @user.tidings.where("created_at > '#{@user.click_time}'").count > 0 || @user.private_messages.where("created_at > '#{@user.click_time}'").count > 0 | |||||
| # | |||||
| # @user_url = "/users/#{@user.login}" | |||||
| # @career = Career.where(status: true).order("created_at asc").pluck(:id, :name) | |||||
| # @auth = User.current.ec_school.present? ? "#{@old_domain}/ecs/department?school_id=#{User.current.ec_school}" : nil | |||||
| end | |||||
| # 用户回复功能 | |||||
| def reply_message | |||||
| message = JournalsForMessage.new(reply_message_params) | |||||
| message.user_id = current_user.id | |||||
| message.save! | |||||
| render_ok(id: message.id) | |||||
| end | |||||
| # 搜索用户具有管理员角色的项目 | |||||
| def search_user_projects | |||||
| projects = Project.where.not(status: 9) | |||||
| projects = projects.joins(members: :member_roles).where(member_roles: { role_id: 3 }) | |||||
| projects = projects.where(members: { user_id: current_user.id }) | |||||
| search = params[:search].to_s.strip | |||||
| projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present? | |||||
| @projects = projects.select(:id, :name) | |||||
| end | |||||
| #TODO 个人主页信息,forge上弃用-hs, 0602 | |||||
| def homepage_info | |||||
| #待办事项,现在未做 | |||||
| @undo_events = 10 | |||||
| #用户的组织数量 | |||||
| # @user_composes_count = @user.composes.size | |||||
| @user_composes_count = 10 | |||||
| end | |||||
| def brief_introduction | |||||
| content = params[:content].to_s.strip | |||||
| current_user.user_extension.update!(brief_introduction: content) | |||||
| render_ok | |||||
| end | |||||
| def attendance | |||||
| attendance = Users::AttendanceService.call(current_user) | |||||
| render_ok(grade: current_user.grade, next_gold: attendance.next_gold) | |||||
| rescue Users::AttendanceService::Error => ex | |||||
| render_error(ex.message) | |||||
| end | |||||
| # 其他平台登录后,必须将token同步到forge平台,实现sso登录功能 | |||||
| def sync_token | |||||
| return render_error('未找相关用户!') unless @user | |||||
| token = Token.get_or_create_permanent_login_token(@user, 'autologin') | |||||
| token.update_column(:value, params[:token]) | |||||
| render_ok | |||||
| end | |||||
| def trustie_related_projects | |||||
| projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc") | |||||
| projects_json = [] | |||||
| domain_url = EduSetting.get('host_name') | |||||
| if projects.present? | |||||
| projects.each do |p| | |||||
| project_url = "/#{p.owner.login}/#{p.identifier}" | |||||
| pj = { | |||||
| id: p.id, | |||||
| name: p.name, | |||||
| is_public: p.is_public, | |||||
| updated_on: p.updated_on.strftime("%Y-%m-%d"), | |||||
| status: p.status, | |||||
| is_member: p.member?(current_user.try(:id)), | |||||
| owner: { | |||||
| name: p.owner.try(:show_real_name), | |||||
| login: p.owner.login | |||||
| }, | |||||
| members_count: p&.members.size, | |||||
| issues_count: p.issues_count - p.pull_requests_count, | |||||
| commits_count: p&.project_score&.changeset_num.to_i, | |||||
| http_url: domain_url + project_url, | |||||
| http_collaborator_url: domain_url + project_url + "/setting/collaborator", | |||||
| http_issues_url: domain_url + project_url + "/issues", | |||||
| http_commits_url: domain_url + project_url + "/commits", | |||||
| project_score: p&.project_score.present? ? p&.project_score&.as_json(:except=>[:created_at, :updated_at]).merge!(commit_time: format_time(p&.project_score&.commit_time)) : {} | |||||
| } | |||||
| projects_json.push(pj) | |||||
| end | |||||
| end | |||||
| Rails.logger.info("==========projects_json========+########{projects_json}") | |||||
| render json: { projects: projects_json.present? ? projects_json : {} } | |||||
| end | |||||
| def trustie_projects | |||||
| user_id = User.select(:id, :login).where(login: params[:login])&.first&.id | |||||
| projects = Project.visible | |||||
| projects = projects.joins(:members).where(members: { user_id: user_id }) | |||||
| search = params[:search].to_s.strip | |||||
| projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present? | |||||
| projects = projects.select(:id, :name).limit(10).as_json | |||||
| render json: { projects: projects } | |||||
| end | |||||
| def projects | |||||
| is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| scope = Projects::ListMyQuery.call(params, @user,is_current_admin_user) | |||||
| @total_count = scope.size | |||||
| @projects = paginate(scope) | |||||
| end | |||||
| # TODO 其他平台登录时同步修改gitea平台对应用户的密码 | |||||
| # 该方法主要用于:别的平台初次部署对接forge平台,同步用户后,gitea平台对应的用户密码与forge平台用户密码不一致是问题 | |||||
| def sync_gitea_pwd | |||||
| return render_error("未找到相关的用户") if @user.blank? | |||||
| flag = sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) | |||||
| flag ? render_ok : render_error('同步失败!') | |||||
| end | |||||
| # TODO | |||||
| # 同步trusite平台用户的salt信息,只需同步一次,同步完成后,该方法可以删除 | |||||
| def sync_salt | |||||
| user = User.find_by_login params[:login] | |||||
| return if user.blank? | |||||
| user.update_column(:salt, params[:salt]) | |||||
| render_ok | |||||
| end | |||||
| def sync_user_info | |||||
| user = User.find_by_login params[:login] | |||||
| return render_forbidden unless user === current_user | |||||
| sync_params = { | |||||
| email: params[:email], | |||||
| password: params[:password] | |||||
| } | |||||
| Users::UpdateInfoForm.new(sync_params.merge(login: params[:login])).validate! | |||||
| interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params) | |||||
| if interactor.success? | |||||
| user.update!(password: params[:password], mail: params[:email], status: User::STATUS_ACTIVE) | |||||
| render_ok | |||||
| else | |||||
| render_error(interactor.error) | |||||
| end | |||||
| end | |||||
| private | |||||
| def load_user | |||||
| @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id]) | |||||
| end | |||||
| def user_params | |||||
| params.require(:user).permit(:nickname, :image, | |||||
| user_extension_attributes: [ | |||||
| :gender, :location, :location_city, | |||||
| :occupation, :technical_title, | |||||
| :school_id, :department_id, :province, :city, | |||||
| :custom_department, :identity, :student_id, :description, | |||||
| :show_super_description, :super_description, | |||||
| :show_email, :show_location, :show_department] | |||||
| ) | |||||
| end | |||||
| def reply_message_params | |||||
| normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? || | |||||
| params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil? | |||||
| params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id) | |||||
| end | |||||
| def check_user_exist | |||||
| return if @user.present? | |||||
| render_not_found | |||||
| end | |||||
| class UsersController < ApplicationController | |||||
| include ApplicationHelper | |||||
| include Ci::DbConnectable | |||||
| before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard] | |||||
| before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard] | |||||
| before_action :require_login, only: %i[me sync_user_info] | |||||
| before_action :connect_to_ci_db, only: [:get_user_info] | |||||
| before_action :convert_image!, only: [:update, :update_image] | |||||
| skip_before_action :check_sign, only: [:attachment_show] | |||||
| # before_action :sso_login, only: [:get_user_info] | |||||
| def connect_to_ci_db(options={}) | |||||
| if !(current_user && !current_user.is_a?(AnonymousUser) && current_user.devops_certification?) | |||||
| return | |||||
| end | |||||
| if current_user.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE | |||||
| connect_to_trustie_ci_database(options) | |||||
| else | |||||
| connect_to_ci_database(options) | |||||
| end | |||||
| end | |||||
| def list | |||||
| scope = User.active.recent.like(params[:search]).includes(:user_extension) | |||||
| @total_count = scope.size | |||||
| @users = paginate(scope) | |||||
| end | |||||
| def show | |||||
| #待办事项,现在未做 | |||||
| if User.current.admin? || User.current.login == @user.login | |||||
| @waiting_applied_messages = @user.applied_messages.waiting | |||||
| @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common | |||||
| @common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common | |||||
| #@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size | |||||
| @undo_events = @common_applied_transfer_projects.size + @common_applied_projects.size | |||||
| else | |||||
| @waiting_applied_messages = AppliedMessage.none | |||||
| @common_applied_transfer_projects = AppliedTransferProject.none | |||||
| @common_applied_projects = AppliedProject.none | |||||
| @undo_events = 0 | |||||
| end | |||||
| #用户的组织数量 | |||||
| # @user_composes_count = @user.composes.size | |||||
| @user_composes_count = 0 | |||||
| user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common") | |||||
| @user_org_count = user_organizations.size | |||||
| normal_projects = Project.members_projects(@user.id).to_sql | |||||
| org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql | |||||
| projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct | |||||
| user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible | |||||
| @projects_common_count = user_projects.common.size | |||||
| @projects_mirrior_count = user_projects.mirror.size | |||||
| @projects_sync_mirrior_count = user_projects.sync_mirror.size | |||||
| # 为了缓存活跃用户的基本信息,后续删除 | |||||
| Cache::V2::OwnerCommonService.new(@user.id).read | |||||
| end | |||||
| def watch_users | |||||
| watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc") | |||||
| if params[:search].present? | |||||
| search_user_ids = User.where(id: watchers.pluck(:watchable_id)).like(params[:search]).pluck(:id) | |||||
| watchers = watchers.where(watchable_id: search_user_ids) | |||||
| end | |||||
| @watchers_count = watchers.size | |||||
| @watchers = paginate(watchers) | |||||
| end | |||||
| def fan_users | |||||
| watchers = @user.watchers.includes(:user).order("watchers.created_at desc") | |||||
| watchers = watchers.joins(:user).merge(User.like(params[:search])) | |||||
| @watchers_count = watchers.size | |||||
| @watchers = paginate(watchers) | |||||
| end | |||||
| def hovercard | |||||
| end | |||||
| def update | |||||
| return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) | |||||
| return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| Util.write_file(@image, avatar_path(@user)) if user_params[:image].present? | |||||
| @user.attributes = user_params.except(:image) | |||||
| unless @user.save | |||||
| render_error(-1, @user.errors.full_messages.join(", ")) | |||||
| end | |||||
| end | |||||
| def update_image | |||||
| return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) | |||||
| return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| Util.write_file(@image, avatar_path(@user)) | |||||
| return render_ok({message: '头像修改成功'}) | |||||
| rescue Exception => e | |||||
| uid_logger_error(e.message) | |||||
| render_error(-1, '头像修改失败!') | |||||
| end | |||||
| def get_image | |||||
| return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) | |||||
| return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| redirect_to Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(@user).to_s | |||||
| end | |||||
| def me | |||||
| @user = current_user | |||||
| end | |||||
| # 贴吧获取用户信接口 | |||||
| def get_user_info | |||||
| begin | |||||
| @user = current_user | |||||
| begin | |||||
| result = Notice::Read::CountService.call(current_user.id) | |||||
| @message_unread_total = result.nil? ? 0 : result[2]["unread_notification"] | |||||
| rescue | |||||
| @message_unread_total = 0 | |||||
| end | |||||
| # TODO 等消息上线再打开注释 | |||||
| #@tidding_count = unviewed_tiddings(current_user) if current_user.present? | |||||
| rescue Exception => e | |||||
| uid_logger_error(e.message) | |||||
| missing_template | |||||
| end | |||||
| end | |||||
| def attachment_show | |||||
| file_name = params[:file_name] | |||||
| path = params[:path] || file_storage_directory | |||||
| send_file "#{path}/#{file_name}", :filename => "#{file_name}", | |||||
| :type => 'game', | |||||
| :disposition => 'attachment' #inline can open in browser | |||||
| end | |||||
| def html_show | |||||
| @contents = File.read("#{params[:path]}") | |||||
| respond_to do |format| | |||||
| format.html {render :layout => false} | |||||
| end | |||||
| end | |||||
| # Redo: 消息总数缓存 | |||||
| def get_navigation_info | |||||
| # @old_domain = edu_setting('old_edu_host') | |||||
| # @user = current_user | |||||
| # # 新消息数 | |||||
| # @new_message = @user.tidings.where("created_at > '#{@user.click_time}'").count > 0 || @user.private_messages.where("created_at > '#{@user.click_time}'").count > 0 | |||||
| # | |||||
| # @user_url = "/users/#{@user.login}" | |||||
| # @career = Career.where(status: true).order("created_at asc").pluck(:id, :name) | |||||
| # @auth = User.current.ec_school.present? ? "#{@old_domain}/ecs/department?school_id=#{User.current.ec_school}" : nil | |||||
| end | |||||
| # 用户回复功能 | |||||
| def reply_message | |||||
| message = JournalsForMessage.new(reply_message_params) | |||||
| message.user_id = current_user.id | |||||
| message.save! | |||||
| render_ok(id: message.id) | |||||
| end | |||||
| # 搜索用户具有管理员角色的项目 | |||||
| def search_user_projects | |||||
| projects = Project.where.not(status: 9) | |||||
| projects = projects.joins(members: :member_roles).where(member_roles: { role_id: 3 }) | |||||
| projects = projects.where(members: { user_id: current_user.id }) | |||||
| search = params[:search].to_s.strip | |||||
| projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present? | |||||
| @projects = projects.select(:id, :name) | |||||
| end | |||||
| #TODO 个人主页信息,forge上弃用-hs, 0602 | |||||
| def homepage_info | |||||
| #待办事项,现在未做 | |||||
| @undo_events = 10 | |||||
| #用户的组织数量 | |||||
| # @user_composes_count = @user.composes.size | |||||
| @user_composes_count = 10 | |||||
| end | |||||
| def brief_introduction | |||||
| content = params[:content].to_s.strip | |||||
| current_user.user_extension.update!(brief_introduction: content) | |||||
| render_ok | |||||
| end | |||||
| def attendance | |||||
| attendance = Users::AttendanceService.call(current_user) | |||||
| render_ok(grade: current_user.grade, next_gold: attendance.next_gold) | |||||
| rescue Users::AttendanceService::Error => ex | |||||
| render_error(ex.message) | |||||
| end | |||||
| # 其他平台登录后,必须将token同步到forge平台,实现sso登录功能 | |||||
| def sync_token | |||||
| return render_error('未找相关用户!') unless @user | |||||
| token = Token.get_or_create_permanent_login_token(@user, 'autologin') | |||||
| token.update_column(:value, params[:token]) | |||||
| render_ok | |||||
| end | |||||
| def trustie_related_projects | |||||
| projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc") | |||||
| projects_json = [] | |||||
| domain_url = EduSetting.get('host_name') | |||||
| if projects.present? | |||||
| projects.each do |p| | |||||
| project_url = "/#{p.owner.login}/#{p.identifier}" | |||||
| pj = { | |||||
| id: p.id, | |||||
| name: p.name, | |||||
| is_public: p.is_public, | |||||
| updated_on: p.updated_on.strftime("%Y-%m-%d"), | |||||
| status: p.status, | |||||
| is_member: p.member?(current_user.try(:id)), | |||||
| owner: { | |||||
| name: p.owner.try(:show_real_name), | |||||
| login: p.owner.login | |||||
| }, | |||||
| members_count: p&.members.size, | |||||
| issues_count: p.issues_count - p.pull_requests_count, | |||||
| commits_count: p&.project_score&.changeset_num.to_i, | |||||
| http_url: domain_url + project_url, | |||||
| http_collaborator_url: domain_url + project_url + "/setting/collaborator", | |||||
| http_issues_url: domain_url + project_url + "/issues", | |||||
| http_commits_url: domain_url + project_url + "/commits", | |||||
| project_score: p&.project_score.present? ? p&.project_score&.as_json(:except=>[:created_at, :updated_at]).merge!(commit_time: format_time(p&.project_score&.commit_time)) : {} | |||||
| } | |||||
| projects_json.push(pj) | |||||
| end | |||||
| end | |||||
| Rails.logger.info("==========projects_json========+########{projects_json}") | |||||
| render json: { projects: projects_json.present? ? projects_json : {} } | |||||
| end | |||||
| def trustie_projects | |||||
| user_id = User.select(:id, :login).where(login: params[:login])&.first&.id | |||||
| projects = Project.visible | |||||
| projects = projects.joins(:members).where(members: { user_id: user_id }) | |||||
| search = params[:search].to_s.strip | |||||
| projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present? | |||||
| projects = projects.select(:id, :name).limit(10).as_json | |||||
| render json: { projects: projects } | |||||
| end | |||||
| def projects | |||||
| is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == @user.id) | |||||
| scope = Projects::ListMyQuery.call(params, @user,is_current_admin_user) | |||||
| @total_count = scope.size | |||||
| @projects = paginate(scope) | |||||
| end | |||||
| # TODO 其他平台登录时同步修改gitea平台对应用户的密码 | |||||
| # 该方法主要用于:别的平台初次部署对接forge平台,同步用户后,gitea平台对应的用户密码与forge平台用户密码不一致是问题 | |||||
| def sync_gitea_pwd | |||||
| return render_error("未找到相关的用户") if @user.blank? | |||||
| flag = sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) | |||||
| flag ? render_ok : render_error('同步失败!') | |||||
| end | |||||
| # TODO | |||||
| # 同步trusite平台用户的salt信息,只需同步一次,同步完成后,该方法可以删除 | |||||
| def sync_salt | |||||
| user = User.find_by_login params[:login] | |||||
| return if user.blank? | |||||
| user.update_column(:salt, params[:salt]) | |||||
| render_ok | |||||
| end | |||||
| def sync_user_info | |||||
| user = User.find_by_login params[:login] | |||||
| return render_forbidden unless user === current_user | |||||
| sync_params = { | |||||
| email: params[:email], | |||||
| password: params[:password] | |||||
| } | |||||
| Users::UpdateInfoForm.new(sync_params.merge(login: params[:login])).validate! | |||||
| interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params) | |||||
| if interactor.success? | |||||
| user.update!(password: params[:password], mail: params[:email], status: User::STATUS_ACTIVE) | |||||
| render_ok | |||||
| else | |||||
| render_error(interactor.error) | |||||
| end | |||||
| end | |||||
| private | |||||
| def load_user | |||||
| @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id]) | |||||
| end | |||||
| def user_params | |||||
| params.require(:user).permit(:nickname, :image, | |||||
| user_extension_attributes: [ | |||||
| :gender, :location, :location_city, | |||||
| :occupation, :technical_title, | |||||
| :school_id, :department_id, :province, :city, | |||||
| :custom_department, :identity, :student_id, :description, | |||||
| :show_super_description, :super_description, | |||||
| :show_email, :show_location, :show_department] | |||||
| ) | |||||
| end | |||||
| def reply_message_params | |||||
| normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? || | |||||
| params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil? | |||||
| params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id) | |||||
| end | |||||
| def check_user_exist | |||||
| return if @user.present? | |||||
| render_not_found | |||||
| end | |||||
| def sso_login | |||||
| if params[:ticket].present? && !current_user.logged? && params[:websiteName].nil? | |||||
| info = Base64.decode64(params[:ticket]) || Base64.decode64(params[:info].gsub(" ", "+")).force_encoding("utf-8") | |||||
| # login 邮箱 手机号 姓名 学校/单位 | |||||
| user_info = info.split("&&") | |||||
| # Rails.logger.info("user_info====== #{info}") | |||||
| login = user_info[0] | |||||
| email = user_info[1] | |||||
| phone = user_info[2] | |||||
| real_name = user_info[3] | |||||
| department_name = user_info[4] | |||||
| # 没有登录时,新建用户并登录 | |||||
| if current_user.logged? | |||||
| user = current_user | |||||
| else | |||||
| user = User.where("login = ? or phone = ? or mail = ? ", "edu_#{login}", phone, email).first | |||||
| unless user | |||||
| ActiveRecord::Base.transaction do | |||||
| phone_rand = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].sample(4).join | |||||
| user_params = { status: 1, type: 'User', login: "e_#{login}", lastname: "#{real_name}", mail: "#{email}", | |||||
| nickname: "#{real_name}", professional_certification: 0, certification: 0, grade: 0, | |||||
| password: "12345678", phone: "#{phone_rand}", profile_completed: 1 } | |||||
| user = User.create!(user_params) | |||||
| UserExtension.create!(user_id: user.id, gender: 1, custom_department: "#{department_name}") | |||||
| end | |||||
| end | |||||
| successful_authentication(user) | |||||
| end | |||||
| end | |||||
| end | |||||
| end | end | ||||
| @@ -1,486 +1,486 @@ | |||||
| # 所有的方法请按首字母的顺序依次列出 | |||||
| module ApplicationHelper | |||||
| include Gitlink::I18n | |||||
| include GitHelper | |||||
| ONE_MINUTE = 60 * 1000 | |||||
| ONE_HOUR = 60 * ONE_MINUTE | |||||
| ONE_DAY = 24 * ONE_HOUR | |||||
| ONE_MONTH = 30 * ONE_DAY | |||||
| ONE_YEAR = 12 * ONE_MONTH | |||||
| # 全局参数配置 | |||||
| def edu_setting name | |||||
| EduSetting.get(name) | |||||
| end | |||||
| # xss共计问题 | |||||
| def content_safe content | |||||
| tags = %w( | |||||
| a abbr b bdo blockquote br caption cite code col colgroup dd del dfn dl | |||||
| dt em figcaption figure h1 h2 h3 h4 h5 h6 hgroup i img ins kbd li mark | |||||
| ol p pre q rp rt ruby s samp small strike strong sub sup table tbody td | |||||
| tfoot th thead time tr u ul var wbr div span | |||||
| ) | |||||
| attributes = %w(href src width height alt cite datetime title class name xml:lang abbr style) | |||||
| sanitize content, tags: tags, attributes: attributes | |||||
| end | |||||
| def graduation_navigation graduation | |||||
| graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务" | |||||
| end | |||||
| def graduation_navigation_id course | |||||
| course.course_modules.find_by(module_type: "graduation").try(:id) | |||||
| end | |||||
| # git用户 | |||||
| # git用户命名规则:login+"@educoder.net" | |||||
| def git_username(email) | |||||
| User.find_by_mail(email) || User.find_by_login(email.split("@").first) | |||||
| end | |||||
| # 不同的类型扩展不同的目录 | |||||
| def relative_path | |||||
| "avatars" | |||||
| end | |||||
| def replace_bytes_to_b(size_string) | |||||
| return size_string.gsub("Bytes", "B").gsub("bytes", "B").gsub("字节", "B") | |||||
| end | |||||
| def storage_path | |||||
| File.join(Rails.root, "public", "images", relative_path) | |||||
| end | |||||
| # 推荐实训 | |||||
| def recommend_shixun(shixun) | |||||
| tag_repertoire_id = shixun.tag_repertoires.first.present? ? shixun.tag_repertoires.first.try(:id) : 0 | |||||
| shixun_id = ShixunTagRepertoire.where("tag_repertoire_id = #{tag_repertoire_id} and | |||||
| shixun_id != #{shixun.id}").pluck(:shixun_id) | |||||
| shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",") | |||||
| Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id | |||||
| in(#{shixun_id})").unhidden.publiced.order("homepage_show asc, myshixuns_count desc").limit(3) | |||||
| end | |||||
| # shixun开启挑战对应的行为名及url | |||||
| def task_operation_url current_myshixun, shixun | |||||
| if current_myshixun.blank? | |||||
| name = shixun.status == 0 ? "模拟实战" : "开启挑战" | |||||
| url = "/shixuns/#{shixun.identifier}/shixun_exec" | |||||
| else | |||||
| identifier = current_myshixun.current_task(current_myshixun.games).try(:identifier) | |||||
| if current_myshixun.status == 1 | |||||
| name = "查看实战" | |||||
| else | |||||
| name = "继续挑战" | |||||
| end | |||||
| url = identifier | |||||
| end | |||||
| [name, url] | |||||
| end | |||||
| # 获取当前时间 | |||||
| def time_from_now(time) | |||||
| if String === time | |||||
| time = Time.parse(time) | |||||
| end | |||||
| lastUpdateTime = time.to_i*1000 | |||||
| currentTime = Time.now.to_i*1000 | |||||
| timePassed = currentTime - lastUpdateTime | |||||
| timeIntoFormat = 0 | |||||
| updateAtValue = "" | |||||
| if timePassed < 0 | |||||
| updateAtValue = "刚刚" | |||||
| elsif timePassed < ONE_MINUTE | |||||
| updateAtValue = "1分钟前" | |||||
| elsif timePassed < ONE_HOUR | |||||
| timeIntoFormat = timePassed / ONE_MINUTE | |||||
| updateAtValue = timeIntoFormat.to_s + "分钟前" | |||||
| elsif (timePassed < ONE_DAY) | |||||
| timeIntoFormat = timePassed / ONE_HOUR | |||||
| updateAtValue = timeIntoFormat.to_s + "小时前" | |||||
| elsif (timePassed < ONE_MONTH) | |||||
| timeIntoFormat = timePassed / ONE_DAY | |||||
| updateAtValue = timeIntoFormat.to_s + "天前" | |||||
| elsif (timePassed < ONE_YEAR) | |||||
| timeIntoFormat = timePassed / ONE_MONTH | |||||
| updateAtValue = timeIntoFormat.to_s + "个月前" | |||||
| else | |||||
| timeIntoFormat = timePassed / ONE_YEAR | |||||
| updateAtValue = timeIntoFormat.to_s + "年前" | |||||
| end | |||||
| updateAtValue | |||||
| end | |||||
| # 计算到结束还有多长时间 **天**小时**分 | |||||
| def how_much_time(time) | |||||
| if time.nil? || time < Time.now #6.21 -hs 增加小于time.now | |||||
| '' | |||||
| else | |||||
| result = ((time - Time.now.to_i).to_i / (24*60*60)).to_s + " 天 " | |||||
| result += (((time - Time.now.to_i).to_i % (24*60*60)) / (60*60)).to_s + " 小时 " | |||||
| result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + " 分 " | |||||
| end | |||||
| end | |||||
| def format_time(time) | |||||
| time.present? ? time.strftime("%Y-%m-%d %H:%M") : '' | |||||
| end | |||||
| # 用户图像url,如果不存在的话,source为匿名用户,即默认使用匿名用户图像 | |||||
| def url_to_avatar(source) | |||||
| if File.exist?(disk_filename(source&.class, source&.id)) | |||||
| ctime = File.ctime(disk_filename(source.class, source.id)).to_i | |||||
| if %w(User Organization).include?(source.class.to_s) | |||||
| File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" | |||||
| else | |||||
| File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" | |||||
| end | |||||
| elsif source.class.to_s == 'User' | |||||
| source.get_letter_avatar_url | |||||
| end | |||||
| end | |||||
| def url_to_avatar_with_platform_url(source) | |||||
| platform_url = Rails.application.config_for(:configuration)['platform_url'] | |||||
| if platform_url | |||||
| return Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(source).to_s | |||||
| else | |||||
| return url_to_avatar(source).to_s | |||||
| end | |||||
| end | |||||
| # 主页banner图 | |||||
| def banner_img(source_type) | |||||
| if File.exist?(disk_filename(source_type, "banner")) | |||||
| ctime = File.ctime(disk_filename(source_type, "banner")).to_i | |||||
| File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}" | |||||
| end | |||||
| end | |||||
| def disk_filename(source_type,source_id,image_file=nil) | |||||
| File.join(storage_path, "#{source_type}", "#{source_id}") | |||||
| end | |||||
| def disk_auth_filename(source_type, source_id, type) | |||||
| File.join(storage_path, "#{source_type}", "#{source_id}#{type}") | |||||
| end | |||||
| def disk_real_name_auth_filename(source_id) | |||||
| disk_auth_filename('UserAuthentication', source_id, 'ID') | |||||
| end | |||||
| def auth_file_url(source_type, source_id, type) | |||||
| File.join('/images', relative_path, source_type, "#{source_id}#{type}") | |||||
| end | |||||
| def real_name_auth_file_url(source_id) | |||||
| auth_file_url('UserAuthentication', source_id, 'ID') | |||||
| end | |||||
| def disk_professional_auth_filename(source_id) | |||||
| disk_auth_filename('UserAuthentication', source_id, 'PRO') | |||||
| end | |||||
| def professional_auth_file_url(source_id) | |||||
| auth_file_url('UserAuthentication', source_id, 'PRO') | |||||
| end | |||||
| def shixun_url_to_avatar(shixun) | |||||
| if File.exist?(disk_filename(shixun.class, shixun.id)) | |||||
| File.join("images/#{relative_path}", "#{shixun.class}", "#{shixun.id}") | |||||
| else | |||||
| File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg") | |||||
| end | |||||
| end | |||||
| # 选用实训的学校情况 | |||||
| def school_user_detail shixun | |||||
| user_ids = shixun.myshixuns.map(&:user_id).uniq # 走缓存取数据 | |||||
| school_ids = UserExtension.where(user_id:user_ids).pluck(:school_id).uniq | |||||
| school_names = School.where(id: school_ids[0..1]).pluck(:name) | |||||
| school_size = school_ids.size | |||||
| str = school_size > 0 ? "#{school_names.join("、")}等 #{school_size}所" : "0所" | |||||
| end | |||||
| # 普通/分组 作业作品状态数组 | |||||
| def student_work_status homework, user_id, course, work | |||||
| status = [] | |||||
| homework_setting = homework.homework_group_setting user_id, true | |||||
| work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id) | |||||
| late_time = homework.late_time || course.end_date | |||||
| if course.is_end && work && work.work_status > 0 | |||||
| status << "查看作品" | |||||
| elsif !course.is_end | |||||
| if homework_setting.publish_time && homework_setting.publish_time < Time.now | |||||
| # 作业未截止时 | |||||
| if homework_setting.end_time > Time.now | |||||
| if homework.homework_type == "group" && homework.homework_detail_group.base_on_project | |||||
| if work.project_id.nil? || work.project_id == 0 | |||||
| status << "创建项目" | |||||
| status << "关联项目" | |||||
| elsif work.work_status == 0 | |||||
| status << "取消关联" | |||||
| status << "提交作品" | |||||
| else | |||||
| status << "修改作品" | |||||
| end | |||||
| else | |||||
| if work.work_status == 0 | |||||
| status << "提交作品" | |||||
| else | |||||
| status << "修改作品" | |||||
| end | |||||
| end | |||||
| # 补交阶段 | |||||
| elsif homework.allow_late && (late_time.nil? || late_time > Time.now) | |||||
| if homework.homework_type == "group" && homework.homework_detail_group.base_on_project | |||||
| if work.project_id.nil? || work.project_id == 0 | |||||
| status << "创建项目" | |||||
| status << "关联项目" | |||||
| elsif work.work_status == 0 | |||||
| status << "取消关联" | |||||
| status << "补交作品" | |||||
| else | |||||
| status << "补交附件" | |||||
| status << "查看作品" | |||||
| end | |||||
| else | |||||
| if work.work_status == 0 | |||||
| status << "补交作品" | |||||
| else | |||||
| status << "补交附件" | |||||
| status << "查看作品" | |||||
| end | |||||
| end | |||||
| # 匿评阶段 | |||||
| elsif work.work_status != 0 | |||||
| if homework.homework_detail_manual.comment_status == 3 | |||||
| work_ids = homework.student_works.has_committed.pluck(:id) | |||||
| if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0 | |||||
| status << "匿评作品" | |||||
| end | |||||
| end | |||||
| status << "查看作品" | |||||
| end | |||||
| end | |||||
| end | |||||
| end | |||||
| def commit_des_status work, homework | |||||
| status = [] | |||||
| homework_setting = homework.homework_group_setting work.user_id | |||||
| # 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结 | |||||
| if homework_setting.publish_time && homework_setting.publish_time < Time.now && work.work_status > 0 && | |||||
| ((homework_setting.end_time && homework_setting.end_time > Time.now) || | |||||
| (homework.allow_late && homework.late_time && homework.late_time > Time.now)) | |||||
| work.description.present? ? status << "修改总结" : status << "提交总结" | |||||
| end | |||||
| end | |||||
| def download_url attachment,options={} | |||||
| attachment_path(attachment,options) | |||||
| end | |||||
| # 耗时:天、小时、分、秒 | |||||
| # 小于1分钟则不显示 | |||||
| def game_spend_time time | |||||
| day = time / 86400 | |||||
| hour = time % (24*60*60) / (60*60) | |||||
| min = time % (24*60*60) % (60*60) / 60 | |||||
| sec = time % (24*60*60) % (60*60) % 60 | |||||
| if day < 1 | |||||
| if hour < 1 | |||||
| if min < 1 | |||||
| if sec < 1 | |||||
| time = "--" | |||||
| else | |||||
| time = "#{sec}秒" | |||||
| end | |||||
| else | |||||
| time = "#{min}分钟 #{sec}秒" | |||||
| end | |||||
| else | |||||
| time = "#{hour}小时 #{min}分钟 #{sec}秒" | |||||
| end | |||||
| else | |||||
| time = "#{day}天 #{hour}小时 #{min}分钟 #{sec}秒" | |||||
| end | |||||
| return time | |||||
| end | |||||
| def absolute_path(file_path) | |||||
| file_root_directory + File.join(edu_setting('attachment_folder'), file_path) | |||||
| end | |||||
| def file_root_directory | |||||
| Rails.root.to_s | |||||
| end | |||||
| def file_storage_directory | |||||
| file_root_directory + edu_setting('attachment_folder') | |||||
| end | |||||
| def local_path(file) | |||||
| File.join(file.disk_directory, file.disk_filename) | |||||
| end | |||||
| def update_downloads(file) | |||||
| file.update_attributes(:downloads => file.downloads + 1) | |||||
| end | |||||
| # 项目信息, | |||||
| def project_info work, current_user, course_identity | |||||
| project = work.project | |||||
| if project | |||||
| if project.status == 9 | |||||
| {id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count} | |||||
| else | |||||
| project_score = project.project_score | |||||
| if project.is_public || current_user.manager_of_project?(project) || course_identity < Course::STUDENT | |||||
| {id: project.id, name: project.name, author: project.creator, member_count: project.project_members.count, | |||||
| all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score, | |||||
| attachment_score: project_score.attachment_score, message_score: project_score.message_score} | |||||
| else | |||||
| {id: -1, name: "#{project.name}", title: "该项目是私有的", author: project.creator, member_count: project.project_members.count, | |||||
| all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score, | |||||
| attachment_score: project_score.attachment_score, message_score: project_score.message_score} | |||||
| end | |||||
| end | |||||
| else | |||||
| {id: -1, name: "--", title: "--"} | |||||
| end | |||||
| end | |||||
| def message_content(content) | |||||
| content = (strip_html content).strip | |||||
| content = content.gsub(/\s+/, " ") | |||||
| if content.gsub(" ", "") == "" | |||||
| content = "[非文本消息]" | |||||
| end | |||||
| content | |||||
| end | |||||
| def strip_html(text, len=0, endss="...") | |||||
| ss = "" | |||||
| if !text.nil? && text.length>0 | |||||
| ss=text.gsub(/<\/?.*?>/, '').strip | |||||
| ss = ss.gsub(/ */, '') | |||||
| ss = ss.gsub(/\r\n/,'') #新增 | |||||
| ss = ss.gsub(/\n/,'') #新增 | |||||
| if len > 0 && ss.length > len | |||||
| ss = ss[0, len] + endss | |||||
| elsif len > 0 && ss.length <= len | |||||
| ss = ss | |||||
| #ss = truncate(ss, :length => len) | |||||
| end | |||||
| end | |||||
| ss | |||||
| end | |||||
| def strip_export_title(content) | |||||
| con_ = "" | |||||
| if content.length > 0 | |||||
| con_ = strip_tags(content) | |||||
| con_ = con_.gsub(/\r\n/,'').gsub(/ */, '').strip | |||||
| end | |||||
| con_ | |||||
| end | |||||
| def pdf_load_sources(*arg) | |||||
| arr = arg.map do |path| | |||||
| content_tag(:script) do | |||||
| File.open(Rails.root.join('public', path)).read.to_s.html_safe | |||||
| end | |||||
| end | |||||
| raw arr.join('') | |||||
| end | |||||
| # 导出pdf时,转化markdown为html | |||||
| def to_markdown(text,origin_url) | |||||
| return nil if text.blank? | |||||
| options = { | |||||
| :autolink => true, | |||||
| :no_intra_emphasis => true, | |||||
| :fenced_code_blocks => true, | |||||
| :lax_html_blocks => true, | |||||
| :strikethrough => true, | |||||
| :superscript => false, | |||||
| :tables => true | |||||
| } | |||||
| markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options) | |||||
| m_t = markdown.render(text) | |||||
| m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t | |||||
| end | |||||
| def shixun_status_class(shixun) | |||||
| case shixun.status | |||||
| when 0 then 'text-info' | |||||
| when 1 then 'text-warning' | |||||
| when 2 then 'text-success' | |||||
| when 3 then 'text-secondary' | |||||
| end | |||||
| end | |||||
| def render_unix_time(date) | |||||
| date.to_time.to_i | |||||
| end | |||||
| def find_user_by_login(login) | |||||
| User.find_by_login login | |||||
| end | |||||
| def find_user_by_login_and_mail(login, mail) | |||||
| User.find_by(login: login, mail: mail) | |||||
| end | |||||
| def find_user_by_gitea_uid(gitea_uid) | |||||
| User.find_by(gitea_uid: gitea_uid) | |||||
| end | |||||
| def find_user_in_redis_cache(login, email) | |||||
| $redis_cache.hgetall("v2-owner-common:#{login}-#{email}") | |||||
| end | |||||
| def find_user_in_redis_cache_by_id(id) | |||||
| $redis_cache.hgetall("v2-owner-common:#{id}") | |||||
| end | |||||
| def render_base64_decoded(str) | |||||
| return nil if str.blank? | |||||
| Base64.decode64 str | |||||
| end | |||||
| def render_admin_statistics_item | |||||
| url = Rails.application.config_for(:configuration)["admin_statistics_url"] | |||||
| return if url.blank? | |||||
| content_tag(:li) do | |||||
| sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root') | |||||
| end | |||||
| end | |||||
| # 1 手机类型;0 邮箱类型 | |||||
| # 注意新版的login是自动名生成的 | |||||
| def phone_mail_type value | |||||
| value =~ /^1\d{10}$/ ? 1 : 0 | |||||
| end | |||||
| def strip(str) | |||||
| str.to_s.strip.presence | |||||
| end | |||||
| end | |||||
| # 所有的方法请按首字母的顺序依次列出 | |||||
| module ApplicationHelper | |||||
| include Gitlink::I18n | |||||
| include GitHelper | |||||
| ONE_MINUTE = 60 * 1000 | |||||
| ONE_HOUR = 60 * ONE_MINUTE | |||||
| ONE_DAY = 24 * ONE_HOUR | |||||
| ONE_MONTH = 30 * ONE_DAY | |||||
| ONE_YEAR = 12 * ONE_MONTH | |||||
| # 全局参数配置 | |||||
| def edu_setting name | |||||
| EduSetting.get(name) | |||||
| end | |||||
| # xss共计问题 | |||||
| def content_safe content | |||||
| tags = %w( | |||||
| a abbr b bdo blockquote br caption cite code col colgroup dd del dfn dl | |||||
| dt em figcaption figure h1 h2 h3 h4 h5 h6 hgroup i img ins kbd li mark | |||||
| ol p pre q rp rt ruby s samp small strike strong sub sup table tbody td | |||||
| tfoot th thead time tr u ul var wbr div span | |||||
| ) | |||||
| attributes = %w(href src width height alt cite datetime title class name xml:lang abbr style) | |||||
| sanitize content, tags: tags, attributes: attributes | |||||
| end | |||||
| def graduation_navigation graduation | |||||
| graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务" | |||||
| end | |||||
| def graduation_navigation_id course | |||||
| course.course_modules.find_by(module_type: "graduation").try(:id) | |||||
| end | |||||
| # git用户 | |||||
| # git用户命名规则:login+"@educoder.net" | |||||
| def git_username(email) | |||||
| User.find_by_mail(email) || User.find_by_login(email.split("@").first) | |||||
| end | |||||
| # 不同的类型扩展不同的目录 | |||||
| def relative_path | |||||
| "avatars" | |||||
| end | |||||
| def replace_bytes_to_b(size_string) | |||||
| return size_string.gsub("Bytes", "B").gsub("bytes", "B").gsub("字节", "B") | |||||
| end | |||||
| def storage_path | |||||
| File.join(Rails.root, "public", "images", relative_path) | |||||
| end | |||||
| # 推荐实训 | |||||
| def recommend_shixun(shixun) | |||||
| tag_repertoire_id = shixun.tag_repertoires.first.present? ? shixun.tag_repertoires.first.try(:id) : 0 | |||||
| shixun_id = ShixunTagRepertoire.where("tag_repertoire_id = #{tag_repertoire_id} and | |||||
| shixun_id != #{shixun.id}").pluck(:shixun_id) | |||||
| shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",") | |||||
| Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id | |||||
| in(#{shixun_id})").unhidden.publiced.order("homepage_show asc, myshixuns_count desc").limit(3) | |||||
| end | |||||
| # shixun开启挑战对应的行为名及url | |||||
| def task_operation_url current_myshixun, shixun | |||||
| if current_myshixun.blank? | |||||
| name = shixun.status == 0 ? "模拟实战" : "开启挑战" | |||||
| url = "/shixuns/#{shixun.identifier}/shixun_exec" | |||||
| else | |||||
| identifier = current_myshixun.current_task(current_myshixun.games).try(:identifier) | |||||
| if current_myshixun.status == 1 | |||||
| name = "查看实战" | |||||
| else | |||||
| name = "继续挑战" | |||||
| end | |||||
| url = identifier | |||||
| end | |||||
| [name, url] | |||||
| end | |||||
| # 获取当前时间 | |||||
| def time_from_now(time) | |||||
| if String === time | |||||
| time = Time.parse(time) | |||||
| end | |||||
| lastUpdateTime = time.to_i*1000 | |||||
| currentTime = Time.now.to_i*1000 | |||||
| timePassed = currentTime - lastUpdateTime | |||||
| timeIntoFormat = 0 | |||||
| updateAtValue = "" | |||||
| if timePassed < 0 | |||||
| updateAtValue = "刚刚" | |||||
| elsif timePassed < ONE_MINUTE | |||||
| updateAtValue = "1分钟前" | |||||
| elsif timePassed < ONE_HOUR | |||||
| timeIntoFormat = timePassed / ONE_MINUTE | |||||
| updateAtValue = timeIntoFormat.to_s + "分钟前" | |||||
| elsif (timePassed < ONE_DAY) | |||||
| timeIntoFormat = (timePassed.to_f / ONE_HOUR).ceil | |||||
| updateAtValue = timeIntoFormat.to_s + "小时前" | |||||
| elsif (timePassed < ONE_MONTH) | |||||
| timeIntoFormat = (timePassed.to_f / ONE_DAY).ceil | |||||
| updateAtValue = timeIntoFormat.to_s + "天前" | |||||
| elsif (timePassed < ONE_YEAR) | |||||
| timeIntoFormat = (timePassed.to_f / ONE_MONTH).ceil | |||||
| updateAtValue = timeIntoFormat.to_s + "个月前" | |||||
| else | |||||
| timeIntoFormat = timePassed / ONE_YEAR | |||||
| updateAtValue = timeIntoFormat.to_s + "年前" | |||||
| end | |||||
| updateAtValue | |||||
| end | |||||
| # 计算到结束还有多长时间 **天**小时**分 | |||||
| def how_much_time(time) | |||||
| if time.nil? || time < Time.now #6.21 -hs 增加小于time.now | |||||
| '' | |||||
| else | |||||
| result = ((time - Time.now.to_i).to_i / (24*60*60)).to_s + " 天 " | |||||
| result += (((time - Time.now.to_i).to_i % (24*60*60)) / (60*60)).to_s + " 小时 " | |||||
| result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + " 分 " | |||||
| end | |||||
| end | |||||
| def format_time(time) | |||||
| time.present? ? time.strftime("%Y-%m-%d %H:%M") : '' | |||||
| end | |||||
| # 用户图像url,如果不存在的话,source为匿名用户,即默认使用匿名用户图像 | |||||
| def url_to_avatar(source) | |||||
| if File.exist?(disk_filename(source&.class, source&.id)) | |||||
| ctime = File.ctime(disk_filename(source.class, source.id)).to_i | |||||
| if %w(User Organization).include?(source.class.to_s) | |||||
| File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" | |||||
| else | |||||
| File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" | |||||
| end | |||||
| elsif source.class.to_s == 'User' | |||||
| source.get_letter_avatar_url | |||||
| end | |||||
| end | |||||
| def url_to_avatar_with_platform_url(source) | |||||
| platform_url = Rails.application.config_for(:configuration)['platform_url'] | |||||
| if platform_url | |||||
| return Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(source).to_s | |||||
| else | |||||
| return url_to_avatar(source).to_s | |||||
| end | |||||
| end | |||||
| # 主页banner图 | |||||
| def banner_img(source_type) | |||||
| if File.exist?(disk_filename(source_type, "banner")) | |||||
| ctime = File.ctime(disk_filename(source_type, "banner")).to_i | |||||
| File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}" | |||||
| end | |||||
| end | |||||
| def disk_filename(source_type,source_id,image_file=nil) | |||||
| File.join(storage_path, "#{source_type}", "#{source_id}") | |||||
| end | |||||
| def disk_auth_filename(source_type, source_id, type) | |||||
| File.join(storage_path, "#{source_type}", "#{source_id}#{type}") | |||||
| end | |||||
| def disk_real_name_auth_filename(source_id) | |||||
| disk_auth_filename('UserAuthentication', source_id, 'ID') | |||||
| end | |||||
| def auth_file_url(source_type, source_id, type) | |||||
| File.join('/images', relative_path, source_type, "#{source_id}#{type}") | |||||
| end | |||||
| def real_name_auth_file_url(source_id) | |||||
| auth_file_url('UserAuthentication', source_id, 'ID') | |||||
| end | |||||
| def disk_professional_auth_filename(source_id) | |||||
| disk_auth_filename('UserAuthentication', source_id, 'PRO') | |||||
| end | |||||
| def professional_auth_file_url(source_id) | |||||
| auth_file_url('UserAuthentication', source_id, 'PRO') | |||||
| end | |||||
| def shixun_url_to_avatar(shixun) | |||||
| if File.exist?(disk_filename(shixun.class, shixun.id)) | |||||
| File.join("images/#{relative_path}", "#{shixun.class}", "#{shixun.id}") | |||||
| else | |||||
| File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg") | |||||
| end | |||||
| end | |||||
| # 选用实训的学校情况 | |||||
| def school_user_detail shixun | |||||
| user_ids = shixun.myshixuns.map(&:user_id).uniq # 走缓存取数据 | |||||
| school_ids = UserExtension.where(user_id:user_ids).pluck(:school_id).uniq | |||||
| school_names = School.where(id: school_ids[0..1]).pluck(:name) | |||||
| school_size = school_ids.size | |||||
| str = school_size > 0 ? "#{school_names.join("、")}等 #{school_size}所" : "0所" | |||||
| end | |||||
| # 普通/分组 作业作品状态数组 | |||||
| def student_work_status homework, user_id, course, work | |||||
| status = [] | |||||
| homework_setting = homework.homework_group_setting user_id, true | |||||
| work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id) | |||||
| late_time = homework.late_time || course.end_date | |||||
| if course.is_end && work && work.work_status > 0 | |||||
| status << "查看作品" | |||||
| elsif !course.is_end | |||||
| if homework_setting.publish_time && homework_setting.publish_time < Time.now | |||||
| # 作业未截止时 | |||||
| if homework_setting.end_time > Time.now | |||||
| if homework.homework_type == "group" && homework.homework_detail_group.base_on_project | |||||
| if work.project_id.nil? || work.project_id == 0 | |||||
| status << "创建项目" | |||||
| status << "关联项目" | |||||
| elsif work.work_status == 0 | |||||
| status << "取消关联" | |||||
| status << "提交作品" | |||||
| else | |||||
| status << "修改作品" | |||||
| end | |||||
| else | |||||
| if work.work_status == 0 | |||||
| status << "提交作品" | |||||
| else | |||||
| status << "修改作品" | |||||
| end | |||||
| end | |||||
| # 补交阶段 | |||||
| elsif homework.allow_late && (late_time.nil? || late_time > Time.now) | |||||
| if homework.homework_type == "group" && homework.homework_detail_group.base_on_project | |||||
| if work.project_id.nil? || work.project_id == 0 | |||||
| status << "创建项目" | |||||
| status << "关联项目" | |||||
| elsif work.work_status == 0 | |||||
| status << "取消关联" | |||||
| status << "补交作品" | |||||
| else | |||||
| status << "补交附件" | |||||
| status << "查看作品" | |||||
| end | |||||
| else | |||||
| if work.work_status == 0 | |||||
| status << "补交作品" | |||||
| else | |||||
| status << "补交附件" | |||||
| status << "查看作品" | |||||
| end | |||||
| end | |||||
| # 匿评阶段 | |||||
| elsif work.work_status != 0 | |||||
| if homework.homework_detail_manual.comment_status == 3 | |||||
| work_ids = homework.student_works.has_committed.pluck(:id) | |||||
| if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0 | |||||
| status << "匿评作品" | |||||
| end | |||||
| end | |||||
| status << "查看作品" | |||||
| end | |||||
| end | |||||
| end | |||||
| end | |||||
| def commit_des_status work, homework | |||||
| status = [] | |||||
| homework_setting = homework.homework_group_setting work.user_id | |||||
| # 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结 | |||||
| if homework_setting.publish_time && homework_setting.publish_time < Time.now && work.work_status > 0 && | |||||
| ((homework_setting.end_time && homework_setting.end_time > Time.now) || | |||||
| (homework.allow_late && homework.late_time && homework.late_time > Time.now)) | |||||
| work.description.present? ? status << "修改总结" : status << "提交总结" | |||||
| end | |||||
| end | |||||
| def download_url attachment,options={} | |||||
| attachment_path(attachment,options) | |||||
| end | |||||
| # 耗时:天、小时、分、秒 | |||||
| # 小于1分钟则不显示 | |||||
| def game_spend_time time | |||||
| day = time / 86400 | |||||
| hour = time % (24*60*60) / (60*60) | |||||
| min = time % (24*60*60) % (60*60) / 60 | |||||
| sec = time % (24*60*60) % (60*60) % 60 | |||||
| if day < 1 | |||||
| if hour < 1 | |||||
| if min < 1 | |||||
| if sec < 1 | |||||
| time = "--" | |||||
| else | |||||
| time = "#{sec}秒" | |||||
| end | |||||
| else | |||||
| time = "#{min}分钟 #{sec}秒" | |||||
| end | |||||
| else | |||||
| time = "#{hour}小时 #{min}分钟 #{sec}秒" | |||||
| end | |||||
| else | |||||
| time = "#{day}天 #{hour}小时 #{min}分钟 #{sec}秒" | |||||
| end | |||||
| return time | |||||
| end | |||||
| def absolute_path(file_path) | |||||
| file_root_directory + File.join(edu_setting('attachment_folder'), file_path) | |||||
| end | |||||
| def file_root_directory | |||||
| Rails.root.to_s | |||||
| end | |||||
| def file_storage_directory | |||||
| file_root_directory + edu_setting('attachment_folder') | |||||
| end | |||||
| def local_path(file) | |||||
| File.join(file.disk_directory, file.disk_filename) | |||||
| end | |||||
| def update_downloads(file) | |||||
| file.update_attributes(:downloads => file.downloads + 1) | |||||
| end | |||||
| # 项目信息, | |||||
| def project_info work, current_user, course_identity | |||||
| project = work.project | |||||
| if project | |||||
| if project.status == 9 | |||||
| {id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count} | |||||
| else | |||||
| project_score = project.project_score | |||||
| if project.is_public || current_user.manager_of_project?(project) || course_identity < Course::STUDENT | |||||
| {id: project.id, name: project.name, author: project.creator, member_count: project.project_members.count, | |||||
| all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score, | |||||
| attachment_score: project_score.attachment_score, message_score: project_score.message_score} | |||||
| else | |||||
| {id: -1, name: "#{project.name}", title: "该项目是私有的", author: project.creator, member_count: project.project_members.count, | |||||
| all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score, | |||||
| attachment_score: project_score.attachment_score, message_score: project_score.message_score} | |||||
| end | |||||
| end | |||||
| else | |||||
| {id: -1, name: "--", title: "--"} | |||||
| end | |||||
| end | |||||
| def message_content(content) | |||||
| content = (strip_html content).strip | |||||
| content = content.gsub(/\s+/, " ") | |||||
| if content.gsub(" ", "") == "" | |||||
| content = "[非文本消息]" | |||||
| end | |||||
| content | |||||
| end | |||||
| def strip_html(text, len=0, endss="...") | |||||
| ss = "" | |||||
| if !text.nil? && text.length>0 | |||||
| ss=text.gsub(/<\/?.*?>/, '').strip | |||||
| ss = ss.gsub(/ */, '') | |||||
| ss = ss.gsub(/\r\n/,'') #新增 | |||||
| ss = ss.gsub(/\n/,'') #新增 | |||||
| if len > 0 && ss.length > len | |||||
| ss = ss[0, len] + endss | |||||
| elsif len > 0 && ss.length <= len | |||||
| ss = ss | |||||
| #ss = truncate(ss, :length => len) | |||||
| end | |||||
| end | |||||
| ss | |||||
| end | |||||
| def strip_export_title(content) | |||||
| con_ = "" | |||||
| if content.length > 0 | |||||
| con_ = strip_tags(content) | |||||
| con_ = con_.gsub(/\r\n/,'').gsub(/ */, '').strip | |||||
| end | |||||
| con_ | |||||
| end | |||||
| def pdf_load_sources(*arg) | |||||
| arr = arg.map do |path| | |||||
| content_tag(:script) do | |||||
| File.open(Rails.root.join('public', path)).read.to_s.html_safe | |||||
| end | |||||
| end | |||||
| raw arr.join('') | |||||
| end | |||||
| # 导出pdf时,转化markdown为html | |||||
| def to_markdown(text,origin_url) | |||||
| return nil if text.blank? | |||||
| options = { | |||||
| :autolink => true, | |||||
| :no_intra_emphasis => true, | |||||
| :fenced_code_blocks => true, | |||||
| :lax_html_blocks => true, | |||||
| :strikethrough => true, | |||||
| :superscript => false, | |||||
| :tables => true | |||||
| } | |||||
| markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options) | |||||
| m_t = markdown.render(text) | |||||
| m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t | |||||
| end | |||||
| def shixun_status_class(shixun) | |||||
| case shixun.status | |||||
| when 0 then 'text-info' | |||||
| when 1 then 'text-warning' | |||||
| when 2 then 'text-success' | |||||
| when 3 then 'text-secondary' | |||||
| end | |||||
| end | |||||
| def render_unix_time(date) | |||||
| date.to_time.to_i | |||||
| end | |||||
| def find_user_by_login(login) | |||||
| User.find_by_login login | |||||
| end | |||||
| def find_user_by_login_and_mail(login, mail) | |||||
| User.find_by(login: login, mail: mail) | |||||
| end | |||||
| def find_user_by_gitea_uid(gitea_uid) | |||||
| User.find_by(gitea_uid: gitea_uid) | |||||
| end | |||||
| def find_user_in_redis_cache(login, email) | |||||
| $redis_cache.hgetall("v2-owner-common:#{login}-#{email}") | |||||
| end | |||||
| def find_user_in_redis_cache_by_id(id) | |||||
| $redis_cache.hgetall("v2-owner-common:#{id}") | |||||
| end | |||||
| def render_base64_decoded(str) | |||||
| return nil if str.blank? | |||||
| Base64.decode64 str | |||||
| end | |||||
| def render_admin_statistics_item | |||||
| url = Rails.application.config_for(:configuration)["admin_statistics_url"] | |||||
| return if url.blank? | |||||
| content_tag(:li) do | |||||
| sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root') | |||||
| end | |||||
| end | |||||
| # 1 手机类型;0 邮箱类型 | |||||
| # 注意新版的login是自动名生成的 | |||||
| def phone_mail_type value | |||||
| value =~ /^1\d{10}$/ ? 1 : 0 | |||||
| end | |||||
| def strip(str) | |||||
| str.to_s.strip.presence | |||||
| end | |||||
| end | |||||
| @@ -22,7 +22,7 @@ module RepositoriesHelper | |||||
| def is_readme?(type, str) | def is_readme?(type, str) | ||||
| return false if type != 'file' || str.blank? | return false if type != 'file' || str.blank? | ||||
| readme_types = ["readme.md", "readme", "readme_en.md", "readme_zh.md", "readme_en", "readme_zh"] | readme_types = ["readme.md", "readme", "readme_en.md", "readme_zh.md", "readme_en", "readme_zh"] | ||||
| readme_types.include?(str.to_s.downcase) | |||||
| readme_types.include?(str.to_s.downcase) || str =~ CustomRegexp::MD_REGEX | |||||
| end | end | ||||
| def render_commit_author(author_json) | def render_commit_author(author_json) | ||||
| @@ -1,7 +1,7 @@ | |||||
| module CustomRegexp | module CustomRegexp | ||||
| PHONE = /1\d{10}/ | PHONE = /1\d{10}/ | ||||
| EMAIL = /\A[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/ | EMAIL = /\A[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/ | ||||
| LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]{4,15}/ #只含有数字、字母、下划线不能以下划线开头和结尾 | |||||
| LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]{4,15}\z/ #只含有数字、字母、下划线不能以下划线开头和结尾 | |||||
| LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/ | LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/ | ||||
| NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/ | NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/ | ||||
| PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/ | PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/ | ||||
| @@ -11,5 +11,5 @@ module CustomRegexp | |||||
| URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i | URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i | ||||
| REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 | REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 | ||||
| MD_REGEX = /^.+(\.[m|M][d|D])$/ | |||||
| end | end | ||||