// +---------------------------------------------------------------------- namespace ywxapp\model; use think\model\concern\SoftDelete; use ywxapp\model\BaseModel; /** * @author ywx <1790337789@qq.com> * @date 2021-07-06 * 用户模型 * @return \think\Model */ class MemberUser extends BaseModel { use SoftDelete; protected function getOptions(): array { return [ 'strict' => true, 'name' => 'member_user', 'autoWriteTimestamp' => 'int', 'readonly' => ['uid'], 'createTime' => 'create_at', 'updateTime' => 'update_at', 'deleteTime' => 'delete_at', 'hidden' => ['password', 'delete_at'], 'append' => ['status_text', 'last_login'], ]; } // 关联用户资料 public function profile() { return $this->hasOne(MemberProfile::class, 'uid', 'uid'); } // 关联用户组(会员组) public function groups() { return $this->belongsToMany(MemberGroup::class, MemberGroupAccess::class, 'gid', 'uid'); } /** * 获取用户有权访问的菜单树 */ public function getAccessibleMenus() { $permissions = $this->getAllPermissions(); if (empty($permissions)) { return []; } $flatMenus = MemberRule::field('id,title,name,pid,sort,route,icon,type') ->whereIn('id', $permissions) ->order('sort', 'asc') ->select() ->toArray(); return MemberRule::buildTree($flatMenus); } /** * 获取用户所有权限标识(字符串数组) */ public function getAllPermissions(): array { $roleIds = $this->groups->column('id'); if (empty($roleIds)) { return []; } return MemberGroupRule::where('gid', 'in', $roleIds) ->distinct(true) ->column('rid'); } // 密码加密 public function setPasswordAttr($value) { $this->salt = $this->generateSalt(); return password_hash($value . $this->salt, PASSWORD_DEFAULT); } // 生成随机盐 protected function generateSalt($length = 6) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_[]{}<>~`+=,.;:/?|'; $salt = ''; for ($i = 0; $i < $length; $i++) { $salt .= $chars[mt_rand(0, strlen($chars) - 1)]; } return $salt; } // 验证密码 public function checkPassword($password) { return password_verify($password . $this->salt, $this->password); } // 获取状态文本 public function getStatusTextAttr() { $statusMap = [0 => '禁用', 1 => '正常', 2 => '锁定']; return $statusMap[$this->status] ?? '未知'; } // 获取最后登录描述 public function getLastLoginAttr() { if (empty($this->login_time)) { return '从未登录'; } $diff = time() - strtotime($this->login_time); if ($diff < 60) { return '刚刚'; } elseif ($diff < 3600) { return floor($diff / 60) . '分钟前'; } elseif ($diff < 86400) { return floor($diff / 3600) . '小时前'; } else { return date('Y-m-d', strtotime($this->login_time)); } } // 检查账户是否被锁定(统一使用 user 表已有的 loginfailure / loginfailuretime 字段) // 复用该字段:失败次数 >=5 且最后一次失败距今不足 30 分钟则锁定。 public function isLocked() { if ($this->status == 0) { return true; // 已禁用 } if ($this->loginfailure >= 5 && $this->loginfailuretime > 0) { $lockUntil = $this->loginfailuretime + 1800; // 锁定 30 分钟 return time() < $lockUntil; } return false; } // 记录登录成功 public function recordLogin($ip) { $this->login_ip = $ip; $this->login_time = date('Y-m-d H:i:s'); $this->login_count += 1; $this->loginfailure = 0; // 重置失败计数 $this->loginfailuretime = 0; // 清除锁定时间 $this->save(); } // 记录登录失败(统一使用 loginfailure / loginfailuretime 字段) public function recordLoginFail($ip) { $this->loginfailure += 1; $this->loginfailuretime = time(); // 记录最后一次失败时间,满 5 次即锁定 30 分钟 $this->save(); } // 重置密码 public function resetPassword($newPassword) { $this->password = $newPassword; // 会触发setPasswordAttr自动加密 $this->need_reset = 0; // 标记无需重置 return $this->save(); } // public function getAvatarAttr($value, $data) // { // return '/static/common/images/avatar.jpg'; // } /** * 表结构自愈:建表 + 关键列兜底(install.sql 为事实源)。 * 查询/写入前调用,避免老库缺表导致 1146。 */ public static function ensureSchema(): void { $prefix = BaseModel::currentPrefix(); foreach (['member', 'member_bill', 'member_group', 'member_group_access', 'member_group_rule', 'member_log', 'member_profile', 'member_rule', 'member_wallets'] as $t) { BaseModel::ensureTableFromInstall($prefix, $t); } } }