chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user