forgeplus/app/models/user.rb

810 lines
22 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class User < ApplicationRecord
include Watchable
include Likeable
include BaseModel
include ProjectOperable
include Searchable::Dependents::User
# Account statuses
STATUS_ANONYMOUS = 0
STATUS_ACTIVE = 1
STATUS_REGISTERED = 2
STATUS_LOCKED = 3
# tpi tpm权限控制
EDU_ADMIN = 1 # 超级管理员
EDU_BUSINESS = 2 # 运营人员
EDU_SHIXUN_MANAGER = 3 # 实训管理员
EDU_SHIXUN_MEMBER = 4 # 实训成员
EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师
EDU_GAME_MANAGER = 6 # TPI的创建者
EDU_TEACHER = 7 # 平台老师,但是未认证
EDU_NORMAL = 8 # 普通用户
VALID_EMAIL_REGEX = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i
# VALID_PHONE_REGEX = /^1\d{10}$/
# 身份证
VALID_NUMBER_REGEX = /(^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^([A-Z]\d{6,10}(\(\w{1}\))?)$)/
LOGIN_LENGTH_LIMIT = 30
MAIL_LENGTH_LMIT = 60
MIX_PASSWORD_LIMIT = 8
LOGIN_CHARS = %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).freeze
# FIX Invalid single-table inheritance type
self.inheritance_column = nil
# educoder: 来自Educoder平台
# trustie: 来自Trustie平台
# forge: 平台本身注册的用户
enum platform: [:forge, :educoder, :trustie]
belongs_to :laboratory, optional: true
has_one :user_extension, dependent: :destroy
has_many :open_users, dependent: :destroy
has_one :wechat_open_user, class_name: 'OpenUsers::Wechat'
has_one :qq_open_user, class_name: 'OpenUsers::QQ'
accepts_nested_attributes_for :user_extension, update_only: true
has_many :memos, foreign_key: 'author_id'
has_many :created_shixuns, class_name: 'Shixun'
has_many :shixun_members, :dependent => :destroy
has_many :shixuns, :through => :shixun_members
has_many :myshixuns, :dependent => :destroy
has_many :games, :dependent => :destroy
has_many :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训
has_many :course_messages
has_many :courses, foreign_key: 'tea_id', dependent: :destroy
has_many :versions
has_many :issue_times, :dependent => :destroy
#试卷
has_many :exercise_banks, :dependent => :destroy
has_many :exercise_users, :dependent => :destroy
has_many :exercise_answers, :dependent => :destroy #针对每个题目学生的答案
has_many :exercise_shixun_answers, :dependent => :destroy #针对每个实训题目学生的答案
has_many :exercise_answer_comments, :dependent => :destroy
has_many :exercises, :dependent => :destroy #创建的试卷
has_many :homework_banks, dependent: :destroy
has_many :graduation_works, dependent: :destroy
has_many :students_for_courses, foreign_key: :student_id, dependent: :destroy
has_one :onclick_time, :dependent => :destroy
# 新版私信
has_many :private_messages, dependent: :destroy
has_many :recent_contacts, through: :private_messages, source: :target
has_many :tidings, :dependent => :destroy
has_many :games, :dependent => :destroy
has_many :created_subjects, foreign_key: :user_id, class_name: 'Subject'
has_many :subject_members, :dependent => :destroy
has_many :subjects, :through => :subject_members
has_many :grades, :dependent => :destroy
has_many :experiences, :dependent => :destroy
has_many :student_works, :dependent => :destroy
has_many :student_works_scores
has_many :student_works_evaluation_distributions
# 毕业设计
has_many :graduation_topics, :dependent => :destroy
has_many :student_graduation_topics, :dependent => :destroy
# 题库
has_many :question_banks, :dependent => :destroy
# 毕设任务题库
has_many :gtask_banks, dependent: :destroy
has_many :gtopic_banks, dependent: :destroy
#问卷
has_many :course_members, :dependent => :destroy
has_many :poll_votes, :dependent => :destroy
has_many :poll_users, :dependent => :destroy
has_many :messages,foreign_key: 'author_id',:dependent => :destroy
has_many :journals_for_messages, :as => :jour, :dependent => :destroy
has_many :teacher_course_groups, :dependent => :destroy
has_many :attachments,foreign_key: :author_id, :dependent => :destroy
# 工程认证
has_many :ec_school_users,:dependent => :destroy
has_many :schools, :through => :ec_school_users
has_many :ec_major_school_users, :dependent => :destroy
has_many :ec_major_schools, :through => :ec_major_school_users
has_many :ec_course_users
has_many :department_members, dependent: :destroy #部门管理员
# 课堂
has_many :student_course_members, -> { course_students }, class_name: 'CourseMember'
has_many :as_student_courses, through: :student_course_members, source: :course
has_many :manage_course_members, -> { teachers_and_admin }, class_name: 'CourseMember'
has_many :manage_courses, through: :manage_course_members, source: :course
# 关注
has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注
has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户
# 认证
has_many :apply_user_authentication
has_one :process_real_name_apply, -> { processing.real_name_auth.order(created_at: :desc) }, class_name: 'ApplyUserAuthentication'
has_one :process_professional_apply, -> { processing.professional_auth.order(created_at: :desc) }, class_name: 'ApplyUserAuthentication'
has_many :apply_actions, dependent: :destroy
has_many :trail_auth_apply_actions, -> { where(container_type: 'TrialAuthorization') }, class_name: 'ApplyAction'
has_many :attendances
# 兴趣
has_many :user_interests, dependent: :delete_all
has_many :interests, through: :user_interests, source: :repertoire
# 众包
has_many :project_packages, foreign_key: :creator_id, dependent: :destroy
has_many :bidding_users, dependent: :destroy
has_many :bidden_project_packages, through: :bidding_users, source: :project_package
# 项目
has_many :applied_projects, dependent: :destroy
has_many :projects, dependent: :destroy
has_many :repositories, dependent: :destroy
# 教学案例
has_many :libraries, dependent: :destroy
# 视频
has_many :videos, dependent: :destroy
# 客户管理
has_many :partner_managers, dependent: :destroy
# OJ编程题
has_many :hacks, dependent: :destroy
has_many :hack_user_lastest_codes, dependent: :destroy
has_many :composes, dependent: :destroy
has_many :compose_users, dependent: :destroy
has_many :project_trends, dependent: :destroy
# 题库
has_many :item_banks, dependent: :destroy
has_many :item_baskets, -> { order("item_baskets.position ASC") }, dependent: :destroy
has_many :examination_banks, dependent: :destroy
has_many :examination_intelligent_settings, dependent: :destroy
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
scope :like, lambda { |keywords|
where("LOWER(concat(lastname, firstname, login)) LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank?
}
attr_accessor :password, :password_confirmation
delegate :gender, :department_id, :school_id, :location, :location_city, :technical_title, to: :user_extension, allow_nil: true
before_save :update_hashed_password
after_create do
SyncTrustieJob.perform_later("user", 1) if allow_sync_to_trustie?
end
#
# validations
#
validates :platform, inclusion: { in: %w(forge educoder trustie) }
validates_presence_of :login, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }, case_sensitive: false
validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, case_sensitive: false
validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, case_sensitive: false
# validates_uniqueness_of :phone, :if => Proc.new { |user| user.phone_changed? && user.phone.present? }, case_sensitive: false
validates_length_of :login, maximum: LOGIN_LENGTH_LIMIT
validates_length_of :mail, maximum: MAIL_LENGTH_LMIT
validate :validate_sensitive_string
validate :validate_password_length
# 删除自动登录的token一旦退出下次会提示需要登录
def delete_autologin_token(value)
Token.where(:user_id => id, :action => 'autologin', :value => value).delete_all
end
def delete_session_token(value)
Token.where(:user_id => id, :action => 'session', :value => value).delete_all
end
def git_mail
mail.blank? ? "#{login}@educoder.net" : mail
end
def project_manager?(project)
project.manager_members.exists?(user: self) || self.admin?
end
# 学号
def student_id
self.user_extension.try(:student_id)
end
# 关注数
def follow_count
Watcher.where(user_id: id, watchable_type: %w(Principal User)).count
# User.watched_by(id).count
end
# 粉丝数
def fan_count
Watcher.where(watchable_type: %w(Principal User), watchable_id: id).count
# watchers.count
end
# 判断当前用户是否为老师
def is_teacher?
self.user_extension.teacher?
end
# 平台认证的老师
def is_certification_teacher
self.user_extension.teacher? && self.professional_certification
end
def certification_teacher?
professional_certification? && user_extension.teacher?
end
# 判断用户的身份
def identity
ue = self.user_extension
unless ue.blank?
if ue.teacher?
ue.technical_title ? ue.technical_title : "老师"
elsif ue.student?
"学生"
else
ue.technical_title ? ue.technical_title : "专业人士"
end
end
end
# 实名认证状态
def auth_status
status = if authentication
"已认证"
elsif process_real_name_apply.present?
"待审核"
else
"未认证"
end
end
# 职业认证状态
def pro_status
status = if professional_certification
"已认证"
elsif process_professional_apply.present?
"待审核"
else
"未认证"
end
end
# 判断当前用户是否通过职业认证
def pro_certification?
professional_certification
end
# 学校所在的地区
def school_province
user_extension&.school&.province || ''
end
# 用户的学校名称
def school_name
user_extension&.school&.name || ''
end
# 用户的学院名称
def department_name
user_extension&.department&.name || ''
end
# 课堂的所有身份
def course_role course
course.course_members.where(user_id: id).pluck(:role)
end
# 课堂的老师(创建者、老师、助教)
def teacher_of_course?(course)
course.course_members.exists?(user_id: id, role: [1,2,3], is_active: 1) || admin? || business?
end
# 课堂的老师(创建者、老师、助教),不考虑超管和运营人员
def none_admin_teacher_of_course?(course)
course.course_members.exists?(user_id: id, role: [1,2,3], is_active: 1)
end
# 课堂的老师(创建者、老师、助教),不用考虑当前身份
def teacher_of_course_non_active?(course)
course.course_members.exists?(user_id: id, role: [1,2,3])
end
# 是否是教师,课堂管理员或者超级管理员
def teacher_or_admin?(course)
course.course_members.exists?(user_id: id, role: [1,2], is_active: 1) || admin? || business?
end
# 课堂的创建者(考虑到多重身份的用户)
def creator_of_course?(course)
course.course_members.exists?(user_id: id, role: 1, is_active: 1) || admin? || business?
end
# 课堂的学生
def student_of_course?(course)
course.course_members.exists?(user_id: id, role: %i[STUDENT], is_active: 1)
end
# 课堂成员
def member_of_course?(course)
course&.course_members.exists?(user_id: id)
end
# 实训路径管理员
def creator_of_subject?(subject)
subject.user_id == id || admin?
end
# 实训路径合作者、admin
def manager_of_subject?(subject)
subject.subject_members.exists?(user_id: id, role: [1,2]) || admin? || business?
end
# 实训管理员实训合作者、admin
def manager_of_shixun?(shixun)
logger.info("############id: #{id}")
shixun.shixun_members.exists?(role: [1,2], user_id: id) || admin? || business?
end
# 实训管理员
def creator_of_shixun?(shixun)
id == shixun.user_id
end
# 实训的合作者
def member_of_shixun?(shixun)
#self.shixun_members.where(:role => 2, :shixun_id => shixun.id).present?
shixun.shixun_members.exists?(role: 2, user_id: id)
end
# TPI的创建者
def creator_of_game?(game)
id == game.user_id
end
# 用户账号状态
def active?
status == STATUS_ACTIVE
end
def registered?
status == STATUS_REGISTERED
end
def locked?
status == STATUS_LOCKED
end
def activate
self.status = STATUS_ACTIVE
end
def register
self.status = STATUS_REGISTERED
end
def lock
self.status = STATUS_LOCKED
end
def activate!
update_attribute(:status, STATUS_ACTIVE)
end
def register!
update_attribute(:status, STATUS_REGISTERED)
end
def lock!
update_attribute(:status, STATUS_LOCKED)
end
# 课程用户身份
def course_identity(course)
if !logged?
Course::Anonymous
elsif admin?
Course::ADMIN
elsif business?
Course::BUSINESS
else
role = course&.course_members&.find_by(user_id: id, is_active: 1)&.role
case role
when nil then Course::NORMAL
when 'CREATOR' then Course::CREATOR
when 'PROFESSOR' then Course::PROFESSOR
when 'STUDENT' then Course::STUDENT
when 'ASSISTANT_PROFESSOR' then Course::ASSISTANT_PROFESSOR
end
end
end
# 实训用户身份
def shixun_identity(shixun)
@identity =
if admin?
User::EDU_ADMIN
elsif business?
User::EDU_BUSINESS
elsif creator_of_shixun?(shixun)
User::EDU_SHIXUN_MANAGER
elsif member_of_shixun?(shixun)
User::EDU_SHIXUN_MEMBER
elsif is_certification_teacher
User::EDU_CERTIFICATION_TEACHER
elsif is_teacher?
User::EDU_TEACHER
else
User::EDU_NORMAL
end
return @identity
end
# tpi的用户身份
def game_identity(game)
shixun = game.myshixun.shixun
@identity =
if admin?
User::EDU_ADMIN
elsif business?
User::EDU_BUSINESS
elsif creator_of_shixun?(shixun)
User::EDU_SHIXUN_MANAGER
elsif member_of_shixun?(shixun)
User::EDU_SHIXUN_MEMBER
elsif is_certification_teacher
User::EDU_CERTIFICATION_TEACHER
elsif creator_of_game?(game)
User::EDU_GAME_MANAGER
elsif is_teacher?
User::EDU_TEACHER
else
User::EDU_NORMAL
end
return @identity
end
# 我的实训
def my_shixuns
shixun_ids = shixun_members.pluck(:shixun_id) + myshixuns.pluck(:shixun_id)
Shixun.where(:id => shixun_ids).visible
end
# 用户是否有权限查看实训
# 1、实训删除只有管理员能看到
# 2、实训隐藏了只有管理员、实训合作者能看到
# 3、如果有限制学校范围则学校的用户、管理员、实训合作者能看到
def shixun_permission(shixun)
case shixun.status
when -1 # 软删除只有管理员能访问
admin?
when 0, 1, 3 # 申请发布或者已关闭的实训,只有实训管理员可以访问
manager_of_shixun?(shixun)
when 2
if shixun.hidden
manager_of_shixun?(shixun)
else
shixun.use_scope == 0 || manager_of_shixun?(shixun) || shixun.shixun_schools.exists?(school_id: school_id)
end
end
end
# 用户在平台名称的显示方式
def full_name
return '游客' unless logged?
name = show_realname? ? lastname + firstname : nickname
name.blank? ? (nickname.blank? ? login : nickname) : name
end
# 用户的真实姓名(不考虑用户是否隐藏了真实姓名,课堂模块都用真实姓名)
def real_name
return '游客' unless logged?
name = lastname + firstname
name = name.blank? ? (nickname.blank? ? login : nickname) : name
name.gsub(/\s+/, '').strip #6.11 -hs
end
def only_real_name
"#{lastname}#{firstname}"
end
# 用户是否选题毕设课题
def selected_topic?(topic)
student_graduation_topics.where(graduation_topic_id: topic.id).last.try(:status)
end
def click_time
click_time = OnclickTime.find_by(user_id: id) || OnclickTime.create(user_id: id, onclick_time: created_on)
click_time.onclick_time
end
def manager_of_memo?(memo)
id == memo.author_id || admin? || business?
end
# 是否是项目管理者
def manager_of_project?(project)
project.project_members.where(user_id: id).count > 0
end
def logged?
true
end
def active?
status == STATUS_ACTIVE
end
def locked?
status == STATUS_LOCKED
end
def phone_binded?
phone.present?
end
def email_binded?
mail.present?
end
# def self.current=(user)
# Thread.current[:current_user] = user
# end
#
# def self.current
# Thread.current[:current_user] ||= User.anonymous
# end
def self.current=(user)
RequestStore.store[:current_user] = user
end
def self.current
RequestStore.store[:current_user] ||= User.anonymous
end
def self.anonymous
anonymous_user = AnonymousUser.unscoped.take
if anonymous_user.nil?
anonymous_user = AnonymousUser.unscoped.create(lastname: 'Anonymous', firstname: '', login: '',
mail: '358551897@qq.com', phone: '13333333333', status: 0, platform: User.platforms[:forge])
raise "Unable to create the anonymous user error_info:#{anonymous_user.errors.messages}" if anonymous_user.new_record?
end
anonymous_user
end
# Returns the user who matches the given autologin +key+ or nil
def self.try_to_autologin(key)
user = Token.find_active_user('autologin', key)
user.update(last_login_on: Time.now) if user
user
end
def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "")
end
def check_password?(clear_password)
# Preventing Timing Attack
ActiveSupport::SecurityUtils.secure_compare(
User.hash_password("#{salt}#{User.hash_password clear_password}"),
hashed_password
)
end
# 工程认证的学校
def ec_school
school_id = self.ec_school_users.pluck(:school_id).first ||
self.ec_major_schools.pluck(:school_id).first ||
(self.ec_course_users.first && self.ec_course_users.first.try(:ec_course).try(:ec_year).try(:ec_major_school).try(:school_id))
end
# 登录,返回用户名与密码匹配的用户
def self.try_to_login(login, password)
login = login.to_s.strip
password = password.to_s
# Make sure no one can sign in with an empty login or password
return nil if login.empty? || password.empty?
if (login =~ VALID_EMAIL_REGEX)
user = find_by_mail(login)
elsif (login =~ VALID_PHONE_REGEX)
user = find_by_phone(login)
else
user = find_by_login(login)
end
user
rescue => text
raise text
end
def show_real_name
name = lastname + firstname
if name.blank?
nickname.blank? ? login : nickname
else
name
end
end
def update_hashed_password
if password
salt_password(password)
end
end
def salt_password(clear_password)
self.salt = User.generate_salt
self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
end
def self.generate_salt
Educoder::Utils.random_hex(16)
end
# 全部已认证
def all_certified?
authentication? && professional_certification?
end
# 是否绑定邮箱
def email_binded?
mail.present?
end
# 手机号123***123
def hidden_phone
Util.conceal(phone, :phone).to_s
end
# 邮箱w***l@qq.com
def hidden_mail
Util.conceal(mail, :email).to_s
end
# 学院的url标识
def college_identifier
Department.find_by_id(department_members.pluck(:department_id).first)&.identifier
end
# 是否能申请试用
def can_apply_trial?
return false if certification == 1
apply = ApplyAction.order(created_at: :desc).find_by(user_id: id, container_type: 'TrialAuthorization')
apply.present? && !apply.status.zero?
end
# 是否已经签到
def attendance_signed?
attendance = Attendance.find_by(user_id: id)
attendance.present? && Util.days_between(Time.zone.now, attendance.created_at).zero?
end
# 明日签到金币
def tomorrow_attendance_gold
Attendance.find_by(user_id: id)&.next_gold || 60 # 基础50连续签到+10
end
def admin_or_business?
admin? || business?
end
def self.generate_login(prefix)
login = prefix + LOGIN_CHARS.sample(8).join('')
while User.exists?(login: login)
login = prefix + LOGIN_CHARS.sample(8).join('')
end
login
end
def bind_open_user?(type)
case type
when 'wechat' then wechat_open_user.present?
when 'qq' then qq_open_user.present?
else false
end
end
def reset_login_times!
LimitForbidControl::UserLogin.new(self).clear
end
def from_sub_site?
laboratory_id.present? && laboratory_id != 1
end
protected
def validate_password_length
# 管理员的初始密码是5位
if password.present? && password.size < MIX_PASSWORD_LIMIT && !User.current.admin?
raise("密码长度不能低于#{MIX_PASSWORD_LIMIT}")
end
if password.present? && password.size > 16
raise('密码长度不能超过16位')
end
end
def validate_sensitive_string
raise("真实姓名包含敏感词汇,请重新输入") if lastname && !HarmoniousDictionary.clean?(lastname)
raise("昵称包含敏感词汇,请重新输入") if nickname && !HarmoniousDictionary.clean?(nickname)
end
def set_laboratory
return unless new_record?
self.laboratory = Laboratory.current if laboratory_id.blank?
end
end
class AnonymousUser < User
validate :validate_anonymous_uniqueness, :on => :create
def validate_anonymous_uniqueness
# There should be only one AnonymousUser in the database
errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
end
def available_custom_fields
[]
end
# Overrides a few properties
def logged?; false end
def admin; false end
def name(*args); I18n.t(:label_user_anonymous) end
# def mail=(*args); nil end
# def mail; nil end
def time_zone; nil end
def rss_key; nil end
def membership(*args)
nil
end
def member_of?(*args)
false
end
# Anonymous user can not be destroyed
def destroy
false
end
protected
def instantiate_email_address
end
end