Files
YwxAppThink/ywxapp/model/MemberUser.php
T

202 lines
5.9 KiB
PHP
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.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
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);
}
}
}