初始化

This commit is contained in:
2026-08-08 18:28:49 +08:00
parent 9bef4420e0
commit f082037a6e
854 changed files with 217171 additions and 11 deletions
+277
View File
@@ -0,0 +1,277 @@
<?php
namespace core\base;
/**
* 控制器基类
*/
class Controller
{
protected $_controller;
protected $_action;
protected $_view;
// ========== 角色常量定义 ==========
const ROLE_SUPER_ADMIN = 'super_admin'; // 超级管理员:可见所有信息+所有类目
const ROLE_ADMIN = 'admin'; // 管理员:由超管分配单一类目
const ROLE_OPERATOR = 'operator'; // 操作员:工位操作权限
// 角色层级数值(越大权限越高)
const ROLE_LEVEL = [
self::ROLE_SUPER_ADMIN => 3,
self::ROLE_ADMIN => 2,
self::ROLE_OPERATOR => 1,
];
// 角色中文名称
const ROLE_NAMES = [
self::ROLE_SUPER_ADMIN => '超级管理员',
self::ROLE_ADMIN => '管理员',
self::ROLE_OPERATOR => '操作员',
];
// ========== 管理类目常量定义 ==========
const CATEGORY_MES = 'mes'; // MES 制造执行
const CATEGORY_INVENTORY = 'inventory'; // 进销存
const CATEGORY_FINANCE = 'finance'; // 财务管理
const CATEGORY_GENERAL = 'general'; // 综合管理
const CATEGORY_ALL = 'all'; // 所有类目(仅超级管理员)
// 类目中文名称
const CATEGORY_NAMES = [
self::CATEGORY_MES => 'MES 制造执行',
self::CATEGORY_INVENTORY => '进销存管理',
self::CATEGORY_FINANCE => '财务管理',
self::CATEGORY_GENERAL => '综合管理',
];
// 类目图标
const CATEGORY_ICONS = [
self::CATEGORY_MES => 'fa-industry',
self::CATEGORY_INVENTORY => 'fa-cubes',
self::CATEGORY_FINANCE => 'fa-cny',
self::CATEGORY_GENERAL => 'fa-th-large',
];
// 类目对应的首页控制器/action
const CATEGORY_HOME = [
self::CATEGORY_MES => 'Admin/index',
self::CATEGORY_INVENTORY => 'Inventory/index',
self::CATEGORY_FINANCE => 'Finance/index',
self::CATEGORY_GENERAL => 'General/index',
];
/**
* 获取角色层级数值
*/
public static function getRoleLevel($role)
{
return self::ROLE_LEVEL[$role] ?? 0;
}
/**
* 检查当前用户角色是否不低于指定角色
*/
public static function roleAtLeast($role, $minRole)
{
return self::getRoleLevel($role) >= self::getRoleLevel($minRole);
}
/**
* 获取用户可访问的类目列表
* 超级管理员返回所有类目,管理员/操作员返回其分配的单一类目
*/
public static function getUserCategories($user)
{
if (($user['role'] ?? '') === self::ROLE_SUPER_ADMIN) {
return [self::CATEGORY_MES, self::CATEGORY_INVENTORY, self::CATEGORY_FINANCE, self::CATEGORY_GENERAL];
}
$cat = $user['category'] ?? self::CATEGORY_MES;
return [$cat];
}
/**
* 检查用户是否有指定类目的访问权限
*/
public static function canAccessCategory($user, $category)
{
if (($user['role'] ?? '') === self::ROLE_SUPER_ADMIN) {
return true;
}
return ($user['category'] ?? '') === $category;
}
/**
* 获取类目选择列表(用于创建管理员时选择分配类目)
*/
public static function getCategoryOptions()
{
return self::CATEGORY_NAMES;
}
public function __construct($controller, $action)
{
$this->_controller = $controller;
$this->_action = $action;
$this->_view = new View($controller, $action);
}
// 分配变量
public function assign($name, $value)
{
$this->_view->assign($name, $value);
}
// 渲染视图
public function render()
{
$this->_view->render();
}
// 检查登录状态
protected function checkLogin()
{
if (!isset($_SESSION['user_id']) || !isset($_SESSION['emp_no'])) {
header('Location: ' . BASE_URL . '/Login/index');
exit;
}
return true;
}
/**
* CSRF Token 生成
*/
protected function csrfToken()
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* CSRF Token 验证
* @param bool $graceful 为 true 时返回 false 而非直接 exit(供登录页优雅降级)
* @return bool
*/
protected function csrfVerify($graceful = false)
{
$token = $_POST['csrf_token'] ?? '';
$sessionToken = $_SESSION['csrf_token'] ?? '';
if (empty($sessionToken) || !hash_equals($sessionToken, $token)) {
// 诊断日志:记录失败原因和客户端信息,便于排查 Session 丢失问题
$reason = empty($sessionToken) ? 'session_token_empty' : 'token_mismatch';
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? substr($_SERVER['HTTP_USER_AGENT'], 0, 200) : 'unknown';
$sessionId = session_id() ? hash('sha256', session_id()) : 'none';
error_log("[CSRF_FAIL] reason={$reason} ip={$ip} session_hash={$sessionId} ua={$ua}");
if ($graceful) {
// 登录页优雅模式:清除旧 token,让页面重新生成
unset($_SESSION['csrf_token']);
return false;
}
// 其他页面保持原有行为:直接拒绝(CSRF 攻击风险)
// AJAX 请求返回 JSON,避免前端因无法解析响应而只能报“网络错误”
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => '安全验证失败,请刷新页面后重试']);
exit;
}
exit('安全验证失败,请刷新页面后重试');
}
return true;
}
/**
* 以 JSON 方式输出并终止;会先清空已缓冲的输出(警告/空白等),
* 避免响应被污染导致前端无法解析 JSON(表现为“网络错误”)。
*/
protected function jsonExit($data, $httpCode = 200)
{
if (ob_get_level() > 0) {
ob_end_clean();
}
if ($httpCode !== 200) {
http_response_code($httpCode);
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
/**
* 验证请求方法(用于删除等危险操作)
*/
protected function requireMethod($method)
{
if ($_SERVER['REQUEST_METHOD'] !== strtoupper($method)) {
exit('不允许的请求方法');
}
return true;
}
/**
* 安全转义输出(XSS防护)
*/
protected function h($str)
{
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
}
// 检测是否为 AJAX 请求
protected function isAjax()
{
return isset($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}
// 获取当前登录用户
protected function getCurrentUser()
{
return array(
'id' => isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0,
'emp_no' => isset($_SESSION['emp_no']) ? $_SESSION['emp_no'] : '',
'emp_name' => isset($_SESSION['emp_name']) ? $_SESSION['emp_name'] : '',
'role' => isset($_SESSION['role']) ? $_SESSION['role'] : self::ROLE_OPERATOR,
'category' => isset($_SESSION['category']) ? $_SESSION['category'] : self::CATEGORY_MES
);
}
/**
* 检查是否为后台管理角色(超级管理员或管理员)
*/
protected function requireAdminRole()
{
$user = $this->getCurrentUser();
$role = $user['role'];
if ($role !== self::ROLE_SUPER_ADMIN && $role !== self::ROLE_ADMIN) {
header('Location: ' . BASE_URL . '/Front/index');
exit;
}
return $user;
}
/**
* 获取指定角色的可管理角色列表
* 超级管理员可管理所有人;管理员只能管理操作员;操作员无管理权限
*/
public static function getManageableRoles($role)
{
if ($role === self::ROLE_SUPER_ADMIN) {
return [self::ROLE_SUPER_ADMIN, self::ROLE_ADMIN, self::ROLE_OPERATOR];
}
if ($role === self::ROLE_ADMIN) {
return [self::ROLE_ADMIN, self::ROLE_OPERATOR];
}
return [];
}
// 退出登录
public function loginout()
{
session_destroy();
header('Location: /');
exit;
}
}
+232
View File
@@ -0,0 +1,232 @@
<?php
namespace core\base;
/**
* 模型基类 - 修复版
*/
class Model
{
protected $table = '';
protected $pdo = null;
protected $where = array();
protected $params = array();
protected $order = '';
protected $limit = '';
protected $fields = '*';
protected $lastError = '';
/** 表前缀,统一为 DGZXY_ */
const TABLE_PREFIX = 'DGZXY_';
public function __construct()
{
$this->pdo = \core\db\Db::pdo();
}
/**
* 获取带前缀的完整表名
*/
public function getTable()
{
// 如果已包含前缀则不重复添加
if (strpos($this->table, self::TABLE_PREFIX) === 0) {
return $this->table;
}
return self::TABLE_PREFIX . $this->table;
}
// 设置表名(外部调用 table() 时传入不带前缀的原始表名)
public function table($table)
{
$this->table = $table;
return $this;
}
// 设置查询字段
public function field($fields)
{
$this->fields = $fields;
return $this;
}
// 设置 WHERE 条件
public function where($where = null, $params = array())
{
if ($where !== null) {
$this->where = $where;
$this->params = $params;
}
return $this;
}
// 设置排序 - 修复版
public function order($order)
{
if (is_array($order)) {
// 支持两种格式:
// 1. ['id DESC'] -> "id DESC"
// 2. ['id' => 'DESC'] -> "id DESC"
$parts = [];
foreach ($order as $key => $value) {
if (is_numeric($key)) {
// 格式:['id DESC']
$parts[] = $value;
} else {
// 格式:['id' => 'DESC']
$parts[] = $key . ' ' . $value;
}
}
$this->order = implode(', ', $parts);
} else {
// 字符串格式:'id DESC'
$this->order = $order;
}
return $this;
}
// 设置 LIMIT
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
// 获取单条记录
public function fetch()
{
$sql = $this->buildSelect();
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->params);
return $stmt->fetch();
}
// 获取所有记录
public function fetchAll()
{
$sql = $this->buildSelect();
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->params);
return $stmt->fetchAll();
}
// 构建 SELECT 语句
protected function buildSelect()
{
$sql = 'SELECT ' . $this->fields . ' FROM ' . $this->getTable();
if (!empty($this->where)) {
if (is_string($this->where)) {
$sql .= ' WHERE ' . $this->where;
} else if (is_array($this->where)) {
$sql .= ' WHERE ' . implode(' AND ', $this->where);
}
}
if (!empty($this->order)) {
$sql .= ' ORDER BY ' . $this->order;
}
if ($this->limit) {
$sql .= ' LIMIT ' . $this->limit;
}
return $sql;
}
// 获取最后错误信息
public function getError()
{
return $this->lastError;
}
// 添加记录
public function add($data)
{
$fields = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = 'INSERT INTO ' . $this->getTable() . ' (' . $fields . ') VALUES (' . $placeholders . ')';
$stmt = $this->pdo->prepare($sql);
$result = $stmt->execute($data);
if ($result === false) {
$err = $stmt->errorInfo();
$this->lastError = isset($err[2]) ? $err[2] : 'Unknown error';
}
return $result;
}
// 更新记录
public function update($data)
{
$set = array();
$params = array();
foreach ($data as $key => $value) {
$set[] = $key . ' = :' . $key;
$params[':' . $key] = $value;
}
$sql = 'UPDATE ' . $this->getTable() . ' SET ' . implode(', ', $set);
if (!empty($this->where)) {
if (is_string($this->where)) {
$sql .= ' WHERE ' . $this->where;
} else if (is_array($this->where)) {
$sql .= ' WHERE ' . implode(' AND ', $this->where);
}
}
// 合并 WHERE 参数
foreach ($this->params as $key => $value) {
$params[$key] = $value;
}
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
// 删除记录(按 id
public function delete($id)
{
$sql = 'DELETE FROM ' . $this->getTable() . ' WHERE id = :id';
$stmt = $this->pdo->prepare($sql);
return $stmt->execute(array(':id' => $id));
}
// 按 where 条件删除记录
public function deleteWhere()
{
if (empty($this->where)) {
return false;
}
$sql = 'DELETE FROM ' . $this->getTable();
if (is_string($this->where)) {
$sql .= ' WHERE ' . $this->where;
} else if (is_array($this->where)) {
$sql .= ' WHERE ' . implode(' AND ', $this->where);
}
$stmt = $this->pdo->prepare($sql);
$result = $stmt->execute($this->params);
// 清除 where 条件
$this->where = array();
$this->params = array();
return $result;
}
// 执行原生 SQL
public function query($sql, $params = array())
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
// 释放结果集游标,避免复用同一 PDO 连接时触发 2014 错误
$stmt->closeCursor();
return $rows;
}
// 执行原生 SQL(无返回)
public function execute($sql, $params = array())
{
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace core\base;
/**
* 视图类
*/
class View
{
protected $_controller;
protected $_action;
protected $_vars = array();
public function __construct($controller, $action)
{
$this->_controller = $controller;
$this->_action = $action;
}
// 分配变量
public function assign($name, $value)
{
$this->_vars[$name] = $value;
}
// 渲染视图
public function render()
{
extract($this->_vars);
$viewFile = APP_PATH . 'app/views/' . $this->_controller . '/' . $this->_action . '.php';
if (is_file($viewFile)) {
include $viewFile;
} else {
exit('视图文件不存在');
}
}
}