Files
2026-08-08 15:53:53 +08:00

257 lines
9.8 KiB
PHP
Raw Permalink 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
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;
}
}