初始化
This commit is contained in:
+205
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
namespace core;
|
||||
// 框架根目录
|
||||
defined('CORE_PATH') or define('CORE_PATH', __DIR__);
|
||||
|
||||
/**
|
||||
* fastphp 框架核心 - 最终修复版
|
||||
*/
|
||||
class Core
|
||||
{
|
||||
// 配置内容
|
||||
protected $config = [];
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
// 运行程序
|
||||
public function run()
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'));
|
||||
$this->setReporting();
|
||||
$this->removeMagicQuotes();
|
||||
$this->unregisterGlobals();
|
||||
$this->setDbConfig();
|
||||
$this->route();
|
||||
}
|
||||
|
||||
// 路由处理 - 最终修复版
|
||||
public function route()
|
||||
{
|
||||
$controllerName = 'Login';
|
||||
$actionName = $this->config['defaultAction'];
|
||||
$param = array();
|
||||
|
||||
// 获取请求 URI
|
||||
$requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||
|
||||
// 清除?之后的内容(查询字符串)
|
||||
$position = strpos($requestUri, '?');
|
||||
if ($position !== false) {
|
||||
$requestUri = substr($requestUri, 0, $position);
|
||||
}
|
||||
|
||||
// 解析 URL 路径
|
||||
$url = trim($requestUri, '/');
|
||||
|
||||
if (!empty($url)) {
|
||||
// 分割 URL
|
||||
$urlArray = explode('/', $url);
|
||||
|
||||
// 过滤:只移除空值和 index.php
|
||||
// 不再自动移除项目目录名,避免误判
|
||||
$urlArray = array_values(array_filter($urlArray, function($value) {
|
||||
$lowerValue = strtolower($value);
|
||||
return $value !== '' && $lowerValue !== 'index.php';
|
||||
}));
|
||||
|
||||
// 现在 urlArray 应该是 [控制器,动作,参数...]
|
||||
if (!empty($urlArray)) {
|
||||
// 获取控制器名(首字母大写)
|
||||
$controllerName = ucfirst(strtolower($urlArray[0]));
|
||||
|
||||
// 获取动作名
|
||||
if (isset($urlArray[1]) && !empty($urlArray[1])) {
|
||||
$actionName = $urlArray[1];
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
if (isset($urlArray[2])) {
|
||||
$param = array_slice($urlArray, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建控制器类名
|
||||
$controller = 'app\\controllers\\' . $controllerName . 'Controller';
|
||||
|
||||
// 检查控制器是否存在
|
||||
if (!class_exists($controller)) {
|
||||
if (APP_DEBUG) {
|
||||
exit('<h2>控制器不存在:' . htmlspecialchars($controllerName) . '</h2>' .
|
||||
'<p>访问的 URL: ' . htmlspecialchars($requestUri) . '</p>' .
|
||||
'<p>解析后的路径:' . htmlspecialchars(implode('/', $urlArray ?? [])) . '</p>' .
|
||||
'<p>请检查 URL 是否正确。</p>');
|
||||
}
|
||||
$this->notFound();
|
||||
}
|
||||
|
||||
|
||||
// 检查方法是否存在(如果控制器有 __call 魔术方法,允许动态方法通过)
|
||||
if (!method_exists($controller, $actionName) && !method_exists($controller, '__call')) {
|
||||
if (APP_DEBUG) {
|
||||
exit('<h2>方法不存在:' . htmlspecialchars($actionName) . '</h2>' .
|
||||
'<p>控制器:' . htmlspecialchars($controllerName) . 'Controller</p>' .
|
||||
'<p>可用方法:<pre>' . htmlspecialchars(implode(', ', get_class_methods($controller))) . '</pre></p>');
|
||||
}
|
||||
$this->notFound();
|
||||
}
|
||||
|
||||
// 实例化控制器并调用方法
|
||||
$dispatch = new $controller($controllerName, $actionName);
|
||||
call_user_func_array(array($dispatch, $actionName), $param);
|
||||
}
|
||||
|
||||
// 统一的 404 响应(生产环境不泄露任何细节)
|
||||
protected function notFound()
|
||||
{
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
}
|
||||
exit('404 Not Found');
|
||||
}
|
||||
|
||||
// 检测开发环境
|
||||
public function setReporting()
|
||||
{
|
||||
if (APP_DEBUG === true) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors','On');
|
||||
} else {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors','Off');
|
||||
ini_set('log_errors', 'On');
|
||||
// 自定义异常处理 — 防止敏感信息泄露
|
||||
set_exception_handler(function($e) {
|
||||
error_log('Unhandled exception: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
exit('系统内部错误,请稍后重试');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 删除敏感字符
|
||||
public function stripSlashesDeep($value)
|
||||
{
|
||||
$value = is_array($value) ? array_map(array($this, 'stripSlashesDeep'), $value) : stripslashes($value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
// 检测敏感字符并删除(已废弃)
|
||||
public function removeMagicQuotes()
|
||||
{
|
||||
// 不再处理
|
||||
}
|
||||
|
||||
// 检测自定义全局变量并移除
|
||||
public function unregisterGlobals()
|
||||
{
|
||||
if (ini_get('register_globals')) {
|
||||
$array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
|
||||
foreach ($array as $value) {
|
||||
foreach ($GLOBALS[$value] as $key => $var) {
|
||||
if ($var === $GLOBALS[$key]) {
|
||||
unset($GLOBALS[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 配置数据库信息
|
||||
public function setDbConfig()
|
||||
{
|
||||
if ($this->config['db']) {
|
||||
define('DB_HOST', $this->config['db']['host']);
|
||||
define('DB_NAME', $this->config['db']['dbname']);
|
||||
define('DB_USER', $this->config['db']['username']);
|
||||
define('DB_PASS', $this->config['db']['password']);
|
||||
}
|
||||
}
|
||||
|
||||
// 自动加载类
|
||||
public function loadClass($className)
|
||||
{
|
||||
$classMap = $this->classMap();
|
||||
if (isset($classMap[$className])) {
|
||||
$file = $classMap[$className];
|
||||
} elseif (strpos($className, '\\') !== false) {
|
||||
$file = APP_PATH . str_replace('\\', '/', $className) . '.php';
|
||||
if (!is_file($file)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
include $file;
|
||||
}
|
||||
|
||||
// 内核文件命名空间映射关系
|
||||
protected function classMap()
|
||||
{
|
||||
return [
|
||||
'core\base\Controller' => CORE_PATH . '/base/Controller.php',
|
||||
'core\base\Model' => CORE_PATH . '/base/Model.php',
|
||||
'core\base\View' => CORE_PATH . '/base/View.php',
|
||||
'core\db\Db' => CORE_PATH . '/db/Db.php',
|
||||
'core\db\Sql' => CORE_PATH . '/db/Sql.php',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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('视图文件不存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace core\db;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* 数据库操作类
|
||||
*/
|
||||
class Db
|
||||
{
|
||||
private static $pdo = null;
|
||||
|
||||
public static function pdo()
|
||||
{
|
||||
if (self::$pdo !== null) {
|
||||
return self::$pdo;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME);
|
||||
$option = array(
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
// 强制开启查询缓冲:避免「2014 Cannot execute queries while other
|
||||
// unbuffered queries are active」。该连接在多处被复用(单例),
|
||||
// 若不缓冲,前一条语句的结果集未取完就会阻塞后续查询。
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||
);
|
||||
|
||||
$pdo = new PDO($dsn, DB_USER, DB_PASS, $option);
|
||||
// 部分环境在构造选项里设置不生效,再显式 setAttribute 兜底
|
||||
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
|
||||
return self::$pdo = $pdo;
|
||||
} catch (PDOException $e) {
|
||||
error_log('DB connection error: ' . $e->getMessage());
|
||||
exit('数据库连接失败,请联系管理员');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace core\db;
|
||||
|
||||
/**
|
||||
* SQL 辅助类
|
||||
*/
|
||||
class Sql
|
||||
{
|
||||
// 构建 INSERT 语句
|
||||
public static function insert($table, $data)
|
||||
{
|
||||
$fields = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
return 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $placeholders . ')';
|
||||
}
|
||||
|
||||
// 构建 UPDATE 语句
|
||||
public static function update($table, $data, $where = '')
|
||||
{
|
||||
$set = array();
|
||||
foreach ($data as $key => $value) {
|
||||
$set[] = $key . ' = :' . $key;
|
||||
}
|
||||
$sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set);
|
||||
if ($where) {
|
||||
$sql .= ' WHERE ' . $where;
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// 构建 SELECT 语句
|
||||
public static function select($table, $fields = '*', $where = '', $order = '', $limit = '')
|
||||
{
|
||||
$sql = 'SELECT ' . $fields . ' FROM ' . $table;
|
||||
if ($where) {
|
||||
$sql .= ' WHERE ' . $where;
|
||||
}
|
||||
if ($order) {
|
||||
$sql .= ' ORDER BY ' . $order;
|
||||
}
|
||||
if ($limit) {
|
||||
$sql .= ' LIMIT ' . $limit;
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// 构建 DELETE 语句
|
||||
public static function delete($table, $where = '')
|
||||
{
|
||||
$sql = 'DELETE FROM ' . $table;
|
||||
if ($where) {
|
||||
$sql .= ' WHERE ' . $where;
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* MES2 全局辅助函数
|
||||
* 这些函数在全局命名空间中定义,可在视图和控制器中直接使用
|
||||
*/
|
||||
|
||||
/**
|
||||
* 安全输出函数(XSS 防护)
|
||||
* 用法: <?= h($var) ?> 或 <?= h($var, '默认值') ?>
|
||||
*/
|
||||
function h($str, $default = '')
|
||||
{
|
||||
return htmlspecialchars((string)($str ?? $default), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的 CSP nonce
|
||||
* 由 index.php 在每次请求时生成并存入 $GLOBALS['csp_nonce']。
|
||||
* 视图中为内联 <script>/<style> 或外部脚本打上 nonce 标记,
|
||||
* 配合 index.php 中 CSP 头的 'nonce-***' 策略,可逐步淘汰 'unsafe-inline'。
|
||||
* 用法:<script nonce="<?= csp_nonce() ?>"> ... </script>
|
||||
*/
|
||||
function csp_nonce()
|
||||
{
|
||||
return $GLOBALS['csp_nonce'] ?? '';
|
||||
}
|
||||
Reference in New Issue
Block a user