- 18 张公共表更名(addon/attachment/configure/links/spider_log/spider_stat/sms/notice/ad/task/prop/medal/help/card -> common_*,member_wallets->member_wallet,score_rule/score_log -> member_*,addon_config->common_addonconf),模型全部对齐新表名,Db 直引用清零 - Attachment 模型补 表名绑定,修复富文本上传查 wxapp_attachment 1146 隐患 - install.sql + 迁移 SQL:backend_admin/backend_role delete_at 默认 0,修复软删除(NULL != 0)误过滤导致后台菜单为空 - AppService::boot() 支持插件 info.php 声明 commands 自动注册插件命令(psr-4 自动加载,坏类名自动跳过) - 各插件(haonav/mqttbroker/wxchat/articles/blog/forum 等)字段与配置同步调整 - 框架版本 1.1.0 -> 1.1.1
191 lines
5.8 KiB
PHP
191 lines
5.8 KiB
PHP
<?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,
|
|
'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');
|
|
}
|
|
|
|
// 密码加密:bcrypt 自带盐,无需独立 salt 列
|
|
// 兼容旧账户(若库里是 password_hash(value.salt) 的老哈希,
|
|
// 这里统一改为 password_hash(value),老账户需要先重置密码)。
|
|
public function setPasswordAttr($value)
|
|
{
|
|
return password_hash($value, PASSWORD_DEFAULT);
|
|
}
|
|
|
|
// 验证密码:bcrypt 哈希内已含盐,直接验证即可
|
|
public function checkPassword($password)
|
|
{
|
|
if (empty($this->password)) {
|
|
return false;
|
|
}
|
|
return password_verify($password, $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_user', 'member_bill', 'member_group', 'member_group_access', 'member_group_rule', 'member_log', 'member_profile', 'member_rule', 'member_wallet'] as $t) {
|
|
BaseModel::ensureTableFromInstall($prefix, $t);
|
|
}
|
|
}
|
|
}
|