257 lines
9.8 KiB
PHP
257 lines
9.8 KiB
PHP
<?php
|
||
namespace App\Controllers\Subsys;
|
||
|
||
use App\Controllers\Controller;
|
||
use App\Models\AdminUser;
|
||
use Core\Db;
|
||
|
||
/**
|
||
* 分系统用户管理(CRM/PSI 共用)。
|
||
* 仅该系统管理员可访问:管理「属于本系统的用户」及其页面权限。
|
||
* 普通用户仅能看到/访问自己被授权的页面(由 subsys_page_can 控制)。
|
||
*/
|
||
abstract class UsersController extends Controller
|
||
{
|
||
/** 子类返回 'crm' | 'psi' */
|
||
abstract protected function sys(): string;
|
||
|
||
private function sysName(): string
|
||
{
|
||
return $this->sys() === 'psi' ? 'PSI 进销存' : 'CRM 客户管理';
|
||
}
|
||
|
||
private function nav(): array
|
||
{
|
||
return \subsys_nav($this->sys());
|
||
}
|
||
|
||
public function index()
|
||
{
|
||
$sys = $this->sys();
|
||
$other = $sys === 'crm' ? 'psi' : 'crm';
|
||
$col = $sys . '_role';
|
||
$rows = Db::query("SELECT * FROM admin_users WHERE `{$col}` != 'none' ORDER BY id ASC")->fetchAll();
|
||
// 子系统管理员仅管理「本系统的用户」,严格执行边界隔离:
|
||
// - 不可越权操作后台超管 / 全局管理员;
|
||
// - 不可跨界看到 / 管理另一系统(CRM 看不到 PSI,PSI 看不到 CRM)。
|
||
if (admin_role() !== 'super_admin') {
|
||
$rows = array_filter($rows, static function ($r) use ($other) {
|
||
// 排除后台超管 / 全局管理员
|
||
if (in_array($r['role'] ?? 'none', ['super_admin', 'admin'], true)) return false;
|
||
// 排除另一系统的管理员(防跨界)
|
||
if (($r[$other . '_role'] ?? 'none') === 'admin') return false;
|
||
return true;
|
||
});
|
||
}
|
||
return $this->renderSubsys($sys, 'subsys/users', [
|
||
'users' => $rows,
|
||
'sysName' => $this->sysName(),
|
||
'pages' => \subsys_pages($sys),
|
||
'sys' => $sys,
|
||
], $this->nav(), 'users');
|
||
}
|
||
|
||
public function create()
|
||
{
|
||
$sys = $this->sys();
|
||
$user = [
|
||
'id' => 0, 'username' => '', 'name' => '', 'status' => 1,
|
||
$sys . '_role' => 'user',
|
||
$sys . '_perms' => '',
|
||
];
|
||
return $this->renderSubsys($sys, 'subsys/user_form', [
|
||
'user' => $user,
|
||
'sysName'=> $this->sysName(),
|
||
'pages' => \subsys_pages($sys),
|
||
'sys' => $sys,
|
||
'edit' => false,
|
||
], $this->nav(), 'users');
|
||
}
|
||
|
||
public function store()
|
||
{
|
||
$sys = $this->sys();
|
||
$upper = strtoupper($sys);
|
||
if (!csrf_check()) {
|
||
$this->flash('表单已过期,请重试', 'err');
|
||
return $this->redirect($upper . '/users/create');
|
||
}
|
||
$username = trim($this->post('username'));
|
||
$name = trim($this->post('name'));
|
||
$pwd = $this->post('password');
|
||
$role = $this->post($sys . '_role') === 'admin' ? 'admin' : 'user';
|
||
$perms = $this->collectPerms($sys);
|
||
$status = $this->post('status') === '0' ? 0 : 1;
|
||
|
||
if ($username === '' || $pwd === '') {
|
||
$this->flash('用户名和密码不能为空', 'err');
|
||
return $this->redirect($upper . '/users/create');
|
||
}
|
||
if (!preg_match('/^[a-zA-Z0-9_]{3,30}$/', $username)) {
|
||
$this->flash('账号须为 3-30 位字母/数字/下划线', 'err');
|
||
return $this->redirect($upper . '/users/create');
|
||
}
|
||
if (strlen($pwd) < 6) {
|
||
$this->flash('密码至少 6 位', 'err');
|
||
return $this->redirect($upper . '/users/create');
|
||
}
|
||
if ((new AdminUser())->byUsername($username)) {
|
||
$this->flash('用户名已存在', 'err');
|
||
return $this->redirect($upper . '/users/create');
|
||
}
|
||
|
||
(new AdminUser())->insert([
|
||
'username' => $username,
|
||
'password' => password_hash($pwd, PASSWORD_DEFAULT),
|
||
'name' => $name,
|
||
'role' => 'none', // 子系统账号:全局后台角色为 none
|
||
$sys . '_role' => $role,
|
||
$sys . '_perms' => json_encode($perms, JSON_UNESCAPED_UNICODE),
|
||
'status' => $status,
|
||
'created_at' => date('Y-m-d H:i:s'),
|
||
]);
|
||
$this->flash('用户已创建', 'ok');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
|
||
public function edit($id)
|
||
{
|
||
$sys = $this->sys();
|
||
$user = (new AdminUser())->find($id);
|
||
if (!$user || !$this->manageable($user)) {
|
||
return $this->redirect(strtoupper($sys) . '/users');
|
||
}
|
||
return $this->renderSubsys($sys, 'subsys/user_form', [
|
||
'user' => $user,
|
||
'sysName'=> $this->sysName(),
|
||
'pages' => \subsys_pages($sys),
|
||
'sys' => $sys,
|
||
'edit' => true,
|
||
], $this->nav(), 'users');
|
||
}
|
||
|
||
public function update($id)
|
||
{
|
||
$sys = $this->sys();
|
||
$upper = strtoupper($sys);
|
||
if (!csrf_check()) {
|
||
$this->flash('表单已过期,请重试', 'err');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
$user = (new AdminUser())->find($id);
|
||
if (!$user || !$this->manageable($user)) {
|
||
$this->flash('无权操作该用户', 'err');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
$username = trim($this->post('username'));
|
||
$name = trim($this->post('name'));
|
||
$pwd = $this->post('password');
|
||
$role = $this->post($sys . '_role') === 'admin' ? 'admin' : 'user';
|
||
$perms = $this->collectPerms($sys);
|
||
$status = $this->post('status') === '0' ? 0 : 1;
|
||
|
||
if ($username === '' || !preg_match('/^[a-zA-Z0-9_]{3,30}$/', $username)) {
|
||
$this->flash('账号不合法', 'err');
|
||
return $this->redirect($upper . '/users/edit/' . $id);
|
||
}
|
||
$existing = (new AdminUser())->byUsername($username);
|
||
if ($existing && (int)$existing['id'] !== (int)$id) {
|
||
$this->flash('用户名已存在', 'err');
|
||
return $this->redirect($upper . '/users/edit/' . $id);
|
||
}
|
||
if ($pwd !== '' && strlen($pwd) < 6) {
|
||
$this->flash('密码至少 6 位', 'err');
|
||
return $this->redirect($upper . '/users/edit/' . $id);
|
||
}
|
||
|
||
$data = [
|
||
'username' => $username,
|
||
'name' => $name,
|
||
$sys . '_role' => $role,
|
||
$sys . '_perms' => json_encode($perms, JSON_UNESCAPED_UNICODE),
|
||
'status' => $status,
|
||
];
|
||
if ($pwd !== '') {
|
||
$data['password'] = password_hash($pwd, PASSWORD_DEFAULT);
|
||
}
|
||
(new AdminUser())->update($id, $data);
|
||
$this->flash('用户已更新', 'ok');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
|
||
public function destroy($id)
|
||
{
|
||
$sys = $this->sys();
|
||
$upper = strtoupper($sys);
|
||
if (!csrf_check()) {
|
||
$this->flash('操作已失效,请重试', 'err');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
$user = (new AdminUser())->find($id);
|
||
if (!$user || !$this->manageable($user) || (int)$user['id'] === (int)admin_uid()) {
|
||
$this->flash('无法删除该用户', 'err');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
(new AdminUser())->delete($id);
|
||
$this->flash('用户已删除', 'ok');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
|
||
/** 管理员为该用户重置密码 */
|
||
public function reset($id)
|
||
{
|
||
$sys = $this->sys();
|
||
$upper = strtoupper($sys);
|
||
$user = (new AdminUser())->find($id);
|
||
if (!$user || !$this->manageable($user)) {
|
||
$this->flash('无权操作该用户', 'err');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
if (!csrf_check()) {
|
||
$this->flash('表单已过期,请重试', 'err');
|
||
return $this->redirect($upper . '/users/reset/' . $id);
|
||
}
|
||
$pwd = $this->post('password');
|
||
if ($pwd === '' || strlen($pwd) < 6) {
|
||
$this->flash('密码至少 6 位', 'err');
|
||
return $this->redirect($upper . '/users/reset/' . $id);
|
||
}
|
||
(new AdminUser())->update($id, ['password' => password_hash($pwd, PASSWORD_DEFAULT)]);
|
||
$this->flash('密码已重置', 'ok');
|
||
return $this->redirect($upper . '/users');
|
||
}
|
||
return $this->renderSubsys($sys, 'subsys/user_reset', [
|
||
'user' => $user,
|
||
'sysName'=> $this->sysName(),
|
||
'sys' => $sys,
|
||
], $this->nav(), 'users');
|
||
}
|
||
|
||
/** 收集页面权限:返回 {page: bool} 形式(与后台 UserController 一致,登录时按 JSON 解码) */
|
||
private function collectPerms(string $sys): array
|
||
{
|
||
$out = [];
|
||
foreach (\subsys_pages($sys) as $p) {
|
||
if ($p === 'dashboard') continue;
|
||
$out[$p] = $this->post('perm_' . $p) ? true : false;
|
||
}
|
||
return $out;
|
||
}
|
||
|
||
/** 当前登录管理员是否可管理该目标用户(严格边界隔离:只管本系统用户,禁止跨界) */
|
||
private function manageable(array $target): bool
|
||
{
|
||
if (admin_role() === 'super_admin') return true;
|
||
$sys = $this->sys();
|
||
$other = $sys === 'crm' ? 'psi' : 'crm';
|
||
// 不能管理后台超管 / 全局管理员
|
||
if (in_array($target['role'] ?? 'none', ['super_admin', 'admin'], true)) return false;
|
||
// 只能管理本系统用户
|
||
if (($target[$sys . '_role'] ?? 'none') === 'none') return false;
|
||
// 不能跨界管理另一系统的管理员(CRM 管不了 PSI,PSI 管不了 CRM)
|
||
if (($target[$other . '_role'] ?? 'none') === 'admin') return false;
|
||
return true;
|
||
}
|
||
}
|