如何用PHP项目实现物品领用?

wen java案例 1

PHP项目实战指南(含完整代码)

目录导读

  1. 为什么你需要一套物品领用系统?
  2. 系统架构设计:PHP + MySQL 核心里程碑
  3. 数据库表结构设计(含ER图逻辑)
  4. 核心功能代码实现(领用/归还/查询)
  5. 安全防护与并发处理技巧
  6. 常见问题FAQ与性能优化
  7. 部署上线与SEO友好型URL配置

为什么你需要一套物品领用系统?

场景痛点:公司行政部每月处理200+次文具/设备领用,纸质登记表常出现:

如何用PHP项目实现物品领用?

  • 物品去向不明(32%的企业存在资产流失)
  • 领用记录被涂改(纸质表修改无痕)
  • 库存数据滞后(管理员每天需手动核对)

PHP解决方案优势

  • 跨平台兼容性:任何浏览器均可访问
  • 低成本快速部署:主流虚拟主机即可运行
  • 与OA系统无缝集成:支持API对接钉钉/企业微信

关键数据:采用PHP开发的中小企业物资管理系统,平均减少63%的统计错误,领用效率提升2.8倍。


系统架构设计:PHP + MySQL 核心里程碑

技术栈选型原则

  • 后端:PHP 8.1+ (性能提升30%,支持命名参数)
  • 数据库:MySQL 8.0 (窗口函数用于排名)
  • 前端:Bootstrap 5 + jQuery (无需复杂前端框架)
  • 服务器:Nginx 1.24 (配置URL重写实现伪静态)

核心功能模块图

用户认证模块 → 物品管理模块 → 领用审批模块 → 库存预警模块
       ↓               ↓                ↓                ↓
  Session安全       CRUD操作        状态流转          阈值触发

数据库表结构设计(含ER图逻辑)

表1:users 用户表

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `department` varchar(100) NOT NULL,
  `role` enum('admin','user','auditor') DEFAULT 'user',
  `status` tinyint(1) DEFAULT 1,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

表2:items 物品库存表

CREATE TABLE `items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL COMMENT '物品名称',
  `category` varchar(50) DEFAULT NULL COMMENT '分类',
  `total_qty` int(11) NOT NULL DEFAULT 0,
  `available_qty` int(11) NOT NULL DEFAULT 0,
  `unit` varchar(10) DEFAULT '个',
  `threshold` int(11) DEFAULT 10 COMMENT '库存预警阈值',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

表3:borrow_records 领用记录表

CREATE TABLE `borrow_records` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `item_id` int(11) NOT NULL,
  `qty` int(11) NOT NULL,
  `status` enum('pending','approved','rejected','returned') DEFAULT 'pending',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  `returned_at` datetime DEFAULT NULL,
  `remark` text,
  PRIMARY KEY (`id`),
  KEY `idx_status` (`status`),
  KEY `idx_user` (`user_id`),
  FOREIGN KEY (`item_id`) REFERENCES `items` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

核心功能代码实现(领用/归还/查询)

功能1:物品领用申请(含库存校验)

// borrow.php
function applyBorrow($userId, $itemId, $qty) {
    $pdo = getDB();
    try {
        $pdo->beginTransaction();
        // 锁行检查库存(防止并发超领)
        $sql = "SELECT available_qty FROM items WHERE id = ? FOR UPDATE";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$itemId]);
        $item = $stmt->fetch();
        if ($item['available_qty'] < $qty) {
            throw new Exception("库存不足,当前可领:{$item['available_qty']}");
        }
        // 插入领用记录
        $sql = "INSERT INTO borrow_records (user_id, item_id, qty, status) VALUES (?,?,?,'pending')";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$userId, $itemId, $qty]);
        // 暂扣库存(审批通过后正式扣减)
        $sql = "UPDATE items SET available_qty = available_qty - ? WHERE id = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$qty, $itemId]);
        $pdo->commit();
        return ['code' => 200, 'msg' => '领用申请已提交'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['code' => 500, 'msg' => $e->getMessage()];
    }
}

功能2:管理员审批/归还逻辑

// approve.php
function approveBorrow($recordId, $action) {
    $pdo = getDB();
    $pdo->beginTransaction();
    $sql = "SELECT * FROM borrow_records WHERE id = ? FOR UPDATE";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$recordId]);
    $record = $stmt->fetch();
    if ($action === 'approved') {
        // 正式出库(已在领用时暂扣,无需二次扣减)
        $updateSql = "UPDATE borrow_records SET status = 'approved' WHERE id = ?";
    } elseif ($action === 'rejected') {
        // 归还库存
        $updateSql = "UPDATE borrow_records SET status = 'rejected' WHERE id = ?";
        $restoreSql = "UPDATE items SET available_qty = available_qty + ? WHERE id = ?";
        $stmt = $pdo->prepare($restoreSql);
        $stmt->execute([$record['qty'], $record['item_id']]);
    }
    $stmt = $pdo->prepare($updateSql);
    $stmt->execute([$recordId]);
    $pdo->commit();
}

功能3:实时库存查询(带搜索过滤)

// search.php
function searchItems($keyword, $category) {
    $pdo = getDB();
    $conditions = [];
    $params = [];
    if (!empty($keyword)) {
        $conditions[] = "name LIKE ?";
        $params[] = "%$keyword%";
    }
    if (!empty($category)) {
        $conditions[] = "category = ?";
        $params[] = $category;
    }
    $where = count($conditions) ? "WHERE " . implode(" AND ", $conditions) : "";
    $sql = "SELECT *, (total_qty - available_qty) as used_qty 
            FROM items $where ORDER BY available_qty ASC";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

安全防护与并发处理技巧

SQL注入防御

  • 必须使用PDO预处理(如上代码所示)
  • 禁止直接拼接 $_GET['id'] 到SQL

CSRF防护

// 生成token
session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
// 表单中嵌入
echo '<input type="hidden" name="csrf_token" value="'.$_SESSION['token'].'">';
// 验证
if (!hash_equals($_SESSION['token'], $_POST['csrf_token'])) {
    die('CSRF攻击检测');
}

并发库存控制

  • 使用 SELECT ... FOR UPDATE 行级锁
  • 设置事务隔离级别为 READ COMMITTED
  • 前端做按钮防重复点击(JS禁用)

敏感操作日志

CREATE TABLE `audit_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `action` varchar(50) NOT NULL,
  `target_id` int(11) DEFAULT NULL,
  `old_value` text,
  `new_value` text,
  `ip_address` varchar(45) DEFAULT NULL,
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

常见问题FAQ与性能优化

Q1:系统提示“库存不足”但实际还有余量?

A:检查 available_qty 字段是否有脏数据,建议每夜定时任务执行:

UPDATE items SET available_qty = total_qty - (
  SELECT IFNULL(SUM(qty),0) 
  FROM borrow_records 
  WHERE item_id = items.id 
  AND status IN ('approved','pending')
);

Q2:如何实现Excel批量导入/导出?

A:使用 PhpSpreadsheet 库:

// 导入示例
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('items.xlsx');
$sheet = $spreadsheet->getActiveSheet();
foreach ($sheet->getRowIterator() as $row) {
    // 逐行插入数据库
}

Q3:系统速度变慢怎么办?

  • borrow_records.item_idstatus字段创建复合索引
  • 使用Redis缓存热门物品库存(5分钟过期)
  • 分页查询务必使用 LIMIT+OFFSET 或游标分页

Q4:如何实现微信/钉钉推送通知?

A:在审批流程结束时调用:

function sendNotice($userId, $message) {
    $webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxx";
    $data = json_encode(['msgtype' => 'text','text' => ['content' => $message]]);
    // 使用curl发送POST请求
}

部署上线与SEO友好型URL配置

Nginx伪静态规则(提升SEO排名)

server {
    listen 80;
    root /var/www/html/;
    index index.php;
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    location ~ ^/borrow/(\d+)$ {
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/borrow.php;
        fastcgi_param QUERY_STRING record_id=$1;
    }
    # 物品详情页SEO优化
    location ~ ^/item/([a-z0-9-]+)$ {
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/item.php;
        fastcgi_param QUERY_STRING slug=$1;
    }
}

生成器URL规则示例

原URL:/borrow.php?record_id=123
优化后:/borrow/123
原URL:/search.php?keyword=笔记本
优化后:/search/笔记本

上线前检查清单

  • [ ] 关闭PHP错误显示(display_errors=Off
  • [ ] 设置MySQL max_connections=200 防崩溃
  • [ ] 配置每天凌晨3点自动备份数据库
  • [ ] 启用HTTPS(Let's Encrypt免费证书)

通过以上实现,你将获得一套完整的基于PHP的物品领用管理系统,核心代码可以直接用于中小企业内部部署,结合现有的OA系统二次开发,可进一步提升资产管理效率,建议在测试环境完成所有功能验证后,再迁移至生产服务器,对于更高并发场景,可考虑引入消息队列(RabbitMQ)处理领用申请,以及使用Elasticsearch实现物品全文搜索。

抱歉,评论功能暂时关闭!