You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
educoder/app/models/user.rb

643 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

class User < ApplicationRecord
include Watchable
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}$/
LOGIN_LENGTH_LIMIT = 30
MAIL_LENGTH_LMIT = 60
MIX_PASSWORD_LIMIT = 8
has_one :user_extension, dependent: :destroy
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 :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训
has_many :course_messages
has_many :courses, 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 :tidings, :dependent => :destroy
has_many :games, :dependent => :destroy
has_many :created_subjects
has_many :subjects, :through => :subject_members
has_many :subject_members, :dependent => :destroy
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
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
attr_accessor :password, :password_confirmation
before_save :update_hashed_password
#
# validations
#
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
# validates_format_of :mail, with: VALID_EMAIL_REGEX, multiline: true
# validates_format_of :phone, with: VALID_PHONE_REGEX, multiline: true
validate :validate_password_length
# validates :nickname, presence: true, length: { maximum: 10 }
# validates :lastname, presence: true
# 删除自动登录的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 || "#{login}@educoder.net"
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 pro_certification?
professional_certification
end
# 用户的学校名称
def school_name
user_extension&.school&.name || ''
end
def school_id
user_extension&.school_id
end
# 课堂的老师(创建者、老师、助教)
def teacher_of_course?(course)
course.course_members.exists?(user_id: id, role: [1,2,3], is_active: 1) || admin?
end
# 课堂的老师(创建者、老师、助教),不用考虑当前身份
def teacher_of_course_non_active?(course)
course.course_members.exists?(user_id: id, role: [1,2,3]) || admin?
end
# 是否是教师,课堂管理员或者超级管理员
def teacher_or_admin?(course)
course.course_members.exists?(user_id: id, role: [1,2], is_active: 1) || admin?
end
# 课堂的创建者(考虑到多重身份的用户)
def creator_of_course?(course)
course.course_members.exists?(user_id: id, role: 1, is_active: 1) || admin?
end
# 课堂的学生
def student_of_course?(course)
course.course_members.exists?(user_id: id, role: %i[STUDENT])
end
# 课堂成员
def member_of_course?(course)
course.course_members.exists?(user_id: id)
end
# 实训路径管理员创建者或admin
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?
end
# 实训管理员实训合作者、admin
def manager_of_shixun?(shixun)
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 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.blank? ? (nickname.blank? ? login : nickname) : name
name.gsub(/\s+/, '').strip #6.11 -hs
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?
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 self.current=(user)
Thread.current[:current_user] = user
end
def self.current
Thread.current[: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)
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 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
if user
# user is already in local database
raise("账号已被注销,请联系管理员") if user.locked?
raise("密码错误") unless user.check_password?(password)
else
raise("账号未注册")
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 base_info_completed?
user_columns = %i[nickname lastname]
user_extension_columns = %i[gender location location_city identity school_id department]
user_columns.all? { |column| public_send(column).present? } &&
user_extension_columns.all? { |column| user_extension.send(column).present? }
end
# 全部已认证
def all_certified?
authentication? && professional_certification?
end
# 是否绑定邮箱
def email_binded?
mail.present?
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
protected
def validate_password_length
# 管理员的初始密码是5位
if password.present? && password.size < MIX_PASSWORD_LIMIT && !User.current.admin?
raise("密码长度不能低于#{MIX_PASSWORD_LIMIT}")
end
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