class User < ApplicationRecord include Watchable # TODO: ES feature # 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