初始化
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
namespace app\models;
|
||||
|
||||
use core\base\Model;
|
||||
|
||||
/**
|
||||
* 员工模型
|
||||
*/
|
||||
class Employee extends Model
|
||||
{
|
||||
protected $table = 'employee';
|
||||
|
||||
// 根据员工编号查找
|
||||
public function findByEmpNo($emp_no)
|
||||
{
|
||||
return $this->where(['emp_no = :emp_no'], [':emp_no' => $emp_no])->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证登录(支持 bcrypt 和旧版 MD5 兼容)
|
||||
*/
|
||||
public function validateLogin($emp_no, $password)
|
||||
{
|
||||
// 先按工号查找用户
|
||||
$user = $this->where(['emp_no = :emp_no'], [':emp_no' => $emp_no])->fetch();
|
||||
if (!$user) {
|
||||
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? substr($_SERVER['HTTP_USER_AGENT'], 0, 150) : 'unknown';
|
||||
$sess = session_id() ? hash('sha256', session_id()) : 'none';
|
||||
error_log("[LOGIN_DIAG] emp_no_len=" . strlen($emp_no) . " emp_no_raw='" . $emp_no . "' found_user=0 ua={$ua} sess_hash={$sess}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断密码是否为 bcrypt 格式(bcrypt 哈希总是以 $2y$ 开头)
|
||||
$isBcrypt = (substr($user['password'], 0, 4) === '$2y$');
|
||||
$pwdFmt = $isBcrypt ? 'bcrypt' : (strlen($user['password']) === 32 ? 'md5' : 'other');
|
||||
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? substr($_SERVER['HTTP_USER_AGENT'], 0, 150) : 'unknown';
|
||||
$sess = session_id() ? hash('sha256', session_id()) : 'none';
|
||||
$pwdLen = strlen($password);
|
||||
error_log("[LOGIN_DIAG] emp_no_len=" . strlen($emp_no) . " emp_no_raw='" . $emp_no . "' found_user=1 pwd_fmt={$pwdFmt} pwd_len={$pwdLen} ua={$ua} sess_hash={$sess}");
|
||||
|
||||
if ($isBcrypt) {
|
||||
if (password_verify($password, $user['password'])) {
|
||||
// 如果算法需要升级,自动更新哈希
|
||||
if (password_needs_rehash($user['password'], PASSWORD_BCRYPT)) {
|
||||
$this->updatePasswordHash($user['id'], $password);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
error_log("[LOGIN_DIAG] PWD_VERIFY_FAIL pwd_len={$pwdLen} pwd_hash_len=" . strlen($user['password']) . " ua={$ua} sess_hash={$sess}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 兼容旧版 MD5 密码
|
||||
if (md5($password) === $user['password']) {
|
||||
// 自动升级为 bcrypt
|
||||
$this->updatePasswordHash($user['id'], $password);
|
||||
return $user;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新密码哈希为 bcrypt
|
||||
*/
|
||||
private function updatePasswordHash($id, $password)
|
||||
{
|
||||
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
|
||||
$this->where(['id = :id'], [':id' => $id])->update(['password' => $hash]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成密码哈希
|
||||
*/
|
||||
public function hashPassword($password)
|
||||
{
|
||||
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码复杂度校验(P1:强制强密码策略)
|
||||
* 要求:≥12 位,且同时包含大写字母、小写字母、数字、特殊字符
|
||||
* @throws \RuntimeException 不满足时抛出异常
|
||||
*/
|
||||
public function validatePasswordStrength($password)
|
||||
{
|
||||
if (!is_string($password) || $password === '') {
|
||||
throw new \RuntimeException('密码不能为空');
|
||||
}
|
||||
if (mb_strlen($password) < 12) {
|
||||
throw new \RuntimeException('密码长度至少 12 位');
|
||||
}
|
||||
if (!preg_match('/[A-Z]/', $password)) {
|
||||
throw new \RuntimeException('密码必须包含至少一个大写字母');
|
||||
}
|
||||
if (!preg_match('/[a-z]/', $password)) {
|
||||
throw new \RuntimeException('密码必须包含至少一个小写字母');
|
||||
}
|
||||
if (!preg_match('/[0-9]/', $password)) {
|
||||
throw new \RuntimeException('密码必须包含至少一个数字');
|
||||
}
|
||||
if (!preg_match('/[^A-Za-z0-9]/', $password)) {
|
||||
throw new \RuntimeException('密码必须包含至少一个特殊字符(如 !@#$%^&*)');
|
||||
}
|
||||
// 拒绝常见弱密码
|
||||
$weak = ['123456', 'password', 'admin123', 'qwerty', '111111', '12345678', 'abc123'];
|
||||
if (in_array(strtolower($password), $weak, true)) {
|
||||
throw new \RuntimeException('密码过于常见,请更换');
|
||||
}
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
public function resetPassword($id, $new_password)
|
||||
{
|
||||
$this->validatePasswordStrength($new_password);
|
||||
$hash = $this->hashPassword($new_password);
|
||||
return $this->where(['id = :id'], [':id' => $id])->update(['password' => $hash]);
|
||||
}
|
||||
|
||||
// 获取所有员工
|
||||
public function getAll()
|
||||
{
|
||||
return $this->order(['id ASC'])->fetchAll();
|
||||
}
|
||||
|
||||
// 添加员工
|
||||
public function addEmployee($data)
|
||||
{
|
||||
if (!isset($data['password']) || empty(trim($data['password']))) {
|
||||
// 生成随机强密码
|
||||
$randomPwd = bin2hex(random_bytes(8)); // 16位随机密码
|
||||
$data['password'] = $this->hashPassword($randomPwd);
|
||||
// 返回生成的密码供管理员告知用户
|
||||
$data['_generated_password'] = $randomPwd;
|
||||
} else {
|
||||
// P1:密码复杂度校验(≥12位,含大小写+数字+特殊字符)
|
||||
$this->validatePasswordStrength($data['password']);
|
||||
$data['password'] = $this->hashPassword($data['password']);
|
||||
}
|
||||
// 移除 _generated_password 再写入
|
||||
$generatedPwd = $data['_generated_password'] ?? null;
|
||||
unset($data['_generated_password']);
|
||||
$id = $this->add($data);
|
||||
if ($id && $generatedPwd) {
|
||||
// 返回生成的密码
|
||||
$this->_lastGeneratedPwd = $generatedPwd;
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
// 获取最后生成的密码
|
||||
public function getLastGeneratedPwd()
|
||||
{
|
||||
return $this->_lastGeneratedPwd ?? null;
|
||||
}
|
||||
private $_lastGeneratedPwd = null;
|
||||
|
||||
// 更新员工
|
||||
public function updateEmployee($id, $data)
|
||||
{
|
||||
unset($data['password']); // 不允许直接更新密码
|
||||
return $this->where(['id = :id'], [':id' => $id])->update($data);
|
||||
}
|
||||
|
||||
// 根据类目获取员工列表
|
||||
public function getByCategory($category)
|
||||
{
|
||||
if ($category === 'all') {
|
||||
return $this->order(['id ASC'])->fetchAll();
|
||||
}
|
||||
return $this->where(['category = :cat OR role = :super'], [
|
||||
':cat' => $category,
|
||||
':super' => 'super_admin'
|
||||
])->order(['id ASC'])->fetchAll();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user