【苏州大学大数据】项目托管/协同开发新增申请加入审核功能(#10197)

This commit is contained in:
zhongdejun 2025-03-13 09:52:41 +08:00
parent a71c834fda
commit 2e8ca4ee9a
22 changed files with 296 additions and 29 deletions

View File

@ -29,7 +29,7 @@ class ApplicationController < ActionController::Base
DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
OPENKEY = "79e33abd4b6588941ab7622aed1e67e8"
helper_method :current_user, :base_url
helper_method :current_user, :base_url, :judge_file_type_by_suffix
# 所有请求必须合法签名
def check_sign
@ -883,4 +883,28 @@ class ApplicationController < ActionController::Base
HotSearchKeyword.add(keyword)
end
# 根据文件后缀判断文件类型,默认使用文本打开渲染
def judge_file_type_by_suffix filename
return "other" if filename.blank?
suffix = File.basename(filename).split(".").last
return "other" if suffix.blank?
case suffix.downcase
when "py", "h", "c", "cpp", "cc", "java", "php", "html", "css", "scss", "go", "r", "graphql", "swift", "xml", "yaml", "json", "lua",
"scheme", "less", "ini", "coffee", "litcoffee", "js", "cs", "kt", "md", "sql", "m", "mm", "pas", "perl", "perl", "ejs",
"pl", "rb", "rs", "rust", "sh", "makefile", "circ", "readme", "yml", "sml", "thy", "conf", "txt", "gitignore", "in", "cu",
"gemfile", "scala", "m", "net", "l", "v", "config", "properties", "log", "htm", "cnf", "hex", "bat", "asm", "hex", "bash",
"ts", "tsx", "sass", "jsx", "jsp", "gitkeep", "sv", "hql", "y", "jj", "pls", "sol", "ignore", "ctrl", "vue", "tex", "bib",
"cls", "bst", "log", "toc", "sty", "g4", "sy", "cfg", "s", "ll", "mml", "hpp","api", "vhdl", "lex" then "txt"
when "jpg", "jpeg", "png", "gif", "webp", "bmp" then "image"
when "avi", "mp4", "mov" then "video"
when "mp3", "wav", "ogg" then "audio"
when "pptm", "pptx", "ppt", "pot", "pps", "ppa", "potx", "ppsx", "ppam", "pptm", "potm", "ppsm", "doc", "docx",
"dot", "dotx", "docm", "dotm", "xls", "xlsx", "xlt", "xla", "xltx", "xlsm", "xltm", "xlam", "xlsb" then "office"
when "tex" then "latex"
when "pdf" then "pdf"
else
"other"
end
end
end

View File

@ -2,12 +2,58 @@ class AppliedProjectsController < ApplicationController
before_action :require_login
def create
@applied_project = Projects::ApplyJoinService.call(current_user, applied_params)
if @applied_project.class.to_s != "AppliedProject"
render_ok({data: @applied_project})
end
rescue Projects::ApplyJoinService::Error => ex
render_error(ex.message)
end
#审核列表
def index
project = Project.find_by(identifier: params[:project_identifier])
tip_exception(-1, "该项目不存在~") if project.blank?
status = params[:status] || 1
if status.to_i == 1
status = ["common"]
elsif status.to_i == 2
status = ["accepted", "refused"]
end
applied_projects = project.applied_projects
if status.present?
applied_projects = applied_projects.where(status: status)
end
keyword = params[:keyword].to_s.strip
if keyword.present?
applied_projects = applied_projects.joins(user: :user_extension).where("CONCAT(users.lastname, users.firstname) LIKE :keyword OR users.phone LIKE :keyword", keyword: "%#{params[:keyword].to_s.strip}%")
end
@count = applied_projects.size
@applied_projects = paginate applied_projects.order(updated_at: :DESC)
end
#审核
def verify
tip_exception(-1, "参数值有误") if !["agree","reject"].include?(params[:verify_status])
applied_project = AppliedProject.find_by(id: params[:id])
tip_exception(-1, '该数据不存在') if applied_project.blank?
tip_exception(-1, '该数据已审核,请勿重复操作') if applied_project.status != "common"
begin
ActiveRecord::Base.transaction do
if params[:verify_status] == "agree"
Projects::VerifyService.new(applied_project, current_user, params).agree
elsif params[:verify_status] == "reject"
Projects::VerifyService.new(applied_project, current_user, params).reject
end
render_ok
end
rescue => e
uid_logger_error(e.message)
tip_exception("审核失败:#{e.message}")
end
end
private
def applied_params
params.require(:applied_project).permit(:code, :role)
params.require(:applied_project).permit(:code, :role, :alert_remarks, :remarks, :attachment_id)
end
end

View File

@ -28,20 +28,20 @@ class ProjectsController < ApplicationController
end
def index
scope = Projects::ListQuery.call(params)
scope = Projects::ListQuery.call(params, current_user)
# @projects = kaminari_paginate(scope)
@projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units)
category_id = params[:category_id]
@total_count =
if category_id.blank?
ps = ProjectStatistic.first
ps.common_projects_count + ps.mirror_projects_count unless ps.blank?
else
cate = ProjectCategory.find_by(id: category_id)
cate&.projects_count || 0
end
@total_count = scope.size
@projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units, :applied_projects)
# category_id = params[:category_id]
# @total_count =
# if category_id.blank?
# ps = ProjectStatistic.first
# ps.common_projects_count + ps.mirror_projects_count unless ps.blank?
# else
# cate = ProjectCategory.find_by(id: category_id)
# cate&.projects_count || 0
# end
end
def create
@ -121,7 +121,7 @@ class ProjectsController < ApplicationController
@project.update(project_params)
else
validate_params = project_params.slice(:name, :description,
:project_category_id, :project_language_id, :private)
:project_category_id, :project_language_id, :private, :join_project_need_verify)
Projects::UpdateForm.new(validate_params).validate!
@ -223,7 +223,7 @@ class ProjectsController < ApplicationController
private
def project_params
params.permit(:user_id, :name, :description, :repository_name, :website, :lesson_url,
:project_category_id, :project_language_id, :license_id, :ignore_id, :private)
:project_category_id, :project_language_id, :license_id, :ignore_id, :private, :join_project_need_verify)
end
def mirror_params

View File

@ -1,7 +1,7 @@
class Projects::CreateForm < BaseForm
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
attr_accessor :user_id, :name, :description, :repository_name, :project_category_id,
:project_language_id, :ignore_id, :license_id, :private, :owner
:project_language_id, :ignore_id, :license_id, :private, :owner, :join_project_need_verify
validates :user_id, :name, :description,:repository_name,
:project_category_id, :project_language_id, presence: true
@ -10,6 +10,7 @@ class Projects::CreateForm < BaseForm
validates :name, length: { maximum: 50 }
validates :repository_name, length: { maximum: 100 }
validates :description, length: { maximum: 200 }
validates :join_project_need_verify, inclusion: { in: [true, false] }
validate :check_ignore, :check_license, :check_owner, :check_max_repo_creation
validate do

View File

@ -1,8 +1,9 @@
class Projects::UpdateForm < BaseForm
attr_accessor :name, :description, :project_category_id, :project_language_id, :private
attr_accessor :name, :description, :project_category_id, :project_language_id, :private, :join_project_need_verify
validates :name, :description, :project_category_id, :project_language_id, presence: true
validates :name, length: { maximum: 50 }
validates :description, length: { maximum: 200 }
validates :join_project_need_verify, inclusion: { in: [true, false] }
validate do
check_project_category(project_category_id)
check_project_language(project_language_id)

View File

@ -24,7 +24,22 @@ class AppliedProject < ApplicationRecord
has_many :applied_messages, as: :applied, dependent: :destroy
# has_many :forge_activities, as: :forge_act, dependent: :destroy
has_one :file, class_name: 'Attachment', as: :container
belongs_to :dealer_user, class_name: 'User' , foreign_key: :dealer, optional: true # 审批人
enum role: {manager: 3, developer: 4, reporter: 5}
enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝
def role_cn
role_cn = ""
if self.role == "manager"
role_cn = "管理员"
elsif self.role == "developer"
role_cn = "开发者"
elsif self.role == "reporter"
role_cn = "报告者"
end
role_cn
end
end

View File

@ -126,6 +126,7 @@ class Project < ApplicationRecord
has_many :pinned_projects, dependent: :destroy
has_many :has_pinned_users, through: :pinned_projects, source: :user
has_many :sonarqubes, dependent: :destroy
has_many :applied_projects, dependent: :destroy
after_save :check_project_members, :reset_cache_data
before_save :set_invite_code

View File

@ -1,22 +1,34 @@
class Projects::ListQuery < ApplicationQuery
include CustomSortable
attr_reader :params
attr_reader :params, :user
sort_columns :updated_on, :created_on, :forked_count, :praises_count, default_by: :updated_on, default_direction: :desc
def initialize(params)
def initialize(params, user=nil)
@params = params
@user = user
end
def call
q = Project.visible.by_name_or_identifier(params[:search])
# q = Project.visible.by_name_or_identifier(params[:search])
q = Project.visible
scope = q
.with_project_type(params[:project_type])
.with_project_category(params[:category_id])
.with_project_language(params[:language_id])
if params[:search].present?
scope = scope.joins(:managers).joins("JOIN users ON members.user_id = users.id").where("CONCAT(users.lastname, users.firstname) LIKE :search OR projects.name LIKE :search OR identifier LIKE :search", search: "%#{params[:search].to_s.strip}%")
end
#我管理的、我学习的
if params[:filter] == "manage"
scope = scope.joins(:managers).where(members:{user_id: user&.id})
elsif params[:filter] == "study"
scope = scope.joins(:except_owner_members).where(members:{user_id: user&.id})
end
scope = scope.distinct
sort = params[:sort_by] || "updated_on"
sort_direction = params[:sort_direction] || "desc"

View File

@ -18,8 +18,23 @@ class Projects::ApplyJoinService < ApplicationService
ActiveRecord::Base.transaction do
validate!
apply = user.applied_projects.create!(project: project, role: role_value)
apply
#如果需要审核
if project.join_project_need_verify.present?
if params[:alert_remarks].blank?
res = {
need_verify: true
}
return res
end
apply = user.applied_projects.create!(project: project, role: role_value, remarks: params[:remarks].to_s.strip)
Attachment.find_by(id: params[:attachment_id])&.update_attributes!(container_type: apply.class.to_s, container_id: apply.id)
deal_tiding!(apply)
return apply
else
apply = user.applied_projects.create!(project: project, role: role_value)
return apply
end
# apply.forge_activities.find_or_create_by!(user: user, project: project)
# notify_project_manager!(apply)
@ -75,11 +90,15 @@ class Projects::ApplyJoinService < ApplicationService
# params check
raise Error, '邀请码不能为空' if params[:code].blank?
raise Error, '请输入6位项目邀请码' unless valid_invite_code( params[:code])
# logical check
raise Error, '邀请码无效' if project.blank?
raise Error, '您已是项目成员' if project.member?(user)
raise Error, '您已经提交过申请' if user.applied_projects.common.exists?(project: project)
if params[:alert_remarks].present? && project.join_project_need_verify.present?
raise Error, '请输入个人情况描述' if params[:remarks].blank?
raise Error, '个人情况描述不能超过200个字符' if params[:remarks].size > 200
end
end
def valid_invite_code(str)
@ -88,4 +107,32 @@ class Projects::ApplyJoinService < ApplicationService
end
return false
end
def deal_tiding!(apply)
user_ids = Project.where(id: project.id).joins(:managers).pluck("members.user_id")
return if user_ids.blank?
columns = %i[user_id trigger_user_id container_id container_type belong_container_id belong_container_type parent_container_id parent_container_type tiding_type viewed extra created_at updated_at]
Tiding.bulk_insert(*columns) do |worker|
base_attr = {
trigger_user_id: user.id,
container_id: apply.id,
container_type: apply.class.to_s,
belong_container_id: project.id,
belong_container_type: project.class.to_s,
parent_container_id: project.id,
parent_container_type: project.class.to_s,
tiding_type: "Apply",
viewed: 0,
extra: "#{apply.role_cn}"
}
user_ids.each do |user_id|
worker.add(base_attr.merge(user_id: user_id))
end
end
# Tiding.create!(:user_id => @user.id, :trigger_user_id => 1, :container_id => @applied_project.id, :container_type => @applied_project.class.to_s, :belong_container_id => @project.id, :belong_container_type => @project.class.to_s, :tiding_type => "System", :viewed => 0, :extra => extra)
end
end

View File

@ -43,6 +43,7 @@ class Projects::CreateService < ApplicationService
ignore_id: params[:ignore_id],
license_id: params[:license_id],
website: params[:website],
join_project_need_verify: params[:join_project_need_verify],
identifier: params[:repository_name] #新增,hs
}
end

View File

@ -0,0 +1,48 @@
class Projects::VerifyService < ApplicationService
# include ApplicationHelper
attr_reader :applied_project, :user, :params
def initialize(applied_project, user, params)
@applied_project = applied_project
@user = user
@params = params
@project = @applied_project.project
end
def agree
@applied_project.update!(status: "accepted", dealer_id: @user.id)
member = @project.members.create!(user: @applied_project.user)
member.member_roles.create!(role_id: AppliedProject.roles[@applied_project.role])
# extra = "您申请加入的#{@project.name}项目(#{@applied_project.role_cn})已通过项目管理者审核。"
deal_tiding!(1)
end
def reject
@applied_project.update!(status: "refused", dealer_id: @user.id, reason: params[:reason].to_s.strip)
# extra = "您申请加入的#{@project.name}项目(#{@applied_project.role_cn})被管理者驳回,驳回原因:#{params[:reason].to_s.strip}"
deal_tiding!(2)
end
private
def deal_tiding!(status)
Tiding.create!(
:user_id => @applied_project.user_id,
:trigger_user_id => 1,
:container_id => @applied_project.id,
:container_type => @applied_project.class.to_s,
:belong_container_id => @project.id,
:belong_container_type => @project.class.to_s,
:parent_container_id => @project.id,
:parent_container_type => @project.class.to_s,
:tiding_type => "System",
:status => status,
:viewed => 0,
:extra => "#{@applied_project.role_cn}")
end
end

View File

@ -0,0 +1,27 @@
json.partial! "commons/success"
json.data do
json.count @count
json.list do
json.array! @applied_projects.each do |apply|
json.id apply.id
json.user_name apply.user&.real_name
json.applied_project apply.role
json.student_id apply.user&.student_id
json.mail apply.user&.mail
json.remarks apply.remarks
json.reason apply.reason
json.status apply.status
#附件
if apply&.file.present?
json.attachment_info do
json.partial! 'attachments/attachment_small', attachment: apply&.file
end
else
json.attachment_info [].to_h
end
end
end
end

View File

@ -18,6 +18,12 @@ json.forked_from_project_id project.forked_from_project_id
json.open_devops project.open_devops?
json.platform project.platform
json.is_pinned project.has_pinned_users.include?(current_user)
json.join_project_need_verify project.join_project_need_verify
json.pending_verify_count project.applied_projects.select{|x|x.status == "common"}.size
json.invite_code project.invite_code
json.is_manager project.manager?(current_user)
json.is_member project.member?(current_user)
json.author do
if project.educoder?
project_educoder = project.project_educoder

View File

@ -17,6 +17,11 @@ json.projects @projects do |project|
json.forked_from_project_id project.forked_from_project_id
json.open_devops project.open_devops?
json.platform project.platform
json.join_project_need_verify project.join_project_need_verify
json.pending_verify_count project.applied_projects.select{|x|x.status == "common"}.size
json.invite_code project.invite_code
json.is_manager project.manager?(current_user)
json.is_member project.member?(current_user)
json.author do
if project.educoder?
project_educoder = project.project_educoder

View File

@ -26,6 +26,7 @@ json.mirror_url @project&.repository.remote_mirror_url
json.mirror @project&.repository.mirror_url.present?
json.type @project.numerical_for_project_type
json.open_devops @project.open_devops?
json.pending_verify_count @project.applied_projects.select{|x|x.status == "common"}.size
unless @project.common?
json.mirror_status @repository.mirror_status

View File

@ -13,4 +13,6 @@ json.is_transfering @project.is_transfering
json.transfer do
json.partial! "/users/user_simple", locals: {user: @project&.applied_transfer_project&.owner}
end
json.is_pinned @project.has_pinned_users.include?(current_user)
json.is_pinned @project.has_pinned_users.include?(current_user)
json.pending_verify_count @project.applied_projects.select{|x|x.status == "common"}.size
json.join_project_need_verify @project.join_project_need_verify

View File

@ -1,7 +1,7 @@
json.username @user.full_name
json.real_name @user.real_name
json.gender @user.gender
json.login @user.login
json.login @user.logged? ? @user.login : ""
json.user_id @user.id
json.image_url url_to_avatar(@user)
json.admin @user.admin?

View File

@ -21,7 +21,7 @@ module Educoderplus
# Custom directories with classes and modules you want to be autoloadable.
config.active_record.default_timezone = :utc
config.active_record.default_timezone = :local
config.time_zone = 'Beijing'
# I18n

View File

@ -148,7 +148,13 @@ Rails.application.routes.draw do
resources :issue_depends, only: [:create, :destroy]
end
resources :applied_projects, only: [:create]
resources :applied_projects, only: [:create, :index] do
collection do
end
member do
post :verify
end
end
resources :project_categories, only: [:index, :show] do
get :group_list, on: :collection

View File

@ -0,0 +1,7 @@
class AddJoinProjectNeedVerifyToProject < ActiveRecord::Migration[5.2]
def change
unless column_exists?(:projects, :join_project_need_verify)
add_column :projects, :join_project_need_verify, :boolean, default: 0, comment:"用户加入项目是否需要审核"
end
end
end

View File

@ -0,0 +1,7 @@
class AddRemarksToForgeAppliedProject < ActiveRecord::Migration[5.2]
def change
unless column_exists?(:forge_applied_projects, :remarks)
add_column :forge_applied_projects, :remarks, :string
end
end
end

View File

@ -0,0 +1,10 @@
class AddReasonToForgeAppliedProject < ActiveRecord::Migration[5.2]
def change
unless column_exists?(:forge_applied_projects, :reason)
add_column :forge_applied_projects, :reason, :string
end
unless column_exists?(:forge_applied_projects, :dealer_id)
add_column :forge_applied_projects, :dealer_id, :integer
end
end
end