简介:openDCIM是一款基于PHP开发的开源数据中心基础设施管理(DCIM)应用程序,遵循GPL v3协议,适用于中小型企业、高校IT部门及科研机构的数据中心资产清点、机柜规划、设备生命周期跟踪与供电/网络拓扑可视化等核心运维场景,适合具备基础Web开发与Linux运维能力的中高级技术人员学习与部署。资源为完整源码包,压缩格式为zip,总大小31.29MB,虽文件总数未提供,但根据项目特性可推知包含PHP主程序模块、MySQL数据库结构脚本、前端HTML/CSS/JS界面文件、配置模板及安装文档等关键组件,结构清晰、开箱即用。已有568人学习下载,表明其在轻量级DCIM实践领域具备一定参考价值。读者可直接获取成熟可用的生产级DCIM系统源码,含完整权限管理、资产导入导出、机房热力图支持及API扩展接口,便于二次开发、本地化部署或作为教学案例深入理解数据中心管理系统的架构设计与业务逻辑实现。
1. 这不是另一个资产台账:openDCIM 是你机房里能“呼吸”的数字孪生体
某高校数据中心刚扩容完第三批GPU服务器,运维同学在Excel里手动更新了27张表、核对了43个机柜的U位占用,结果发现一台设备在“待上架”和“已运行”两个状态里同时存在——不是数据错了,是人眼已经跟不上物理世界的变更速度。openDCIM 就是为这种场景而生:它不只记录“这台服务器在几号机柜第几U”,而是把机房变成一个可查询、可联动、可审计的活系统——支持机柜三维视图拖拽布放、自动校验电源/散热约束、与SNMP设备联动采集实时温度、通过API对接CMDB同步资产生命周期。它用PHP+MySQL构建,但核心不是语言,而是把“物理空间-设备-连接-供电-冷却”五层关系全部建模进数据库,并暴露成RESTful接口。适合中小规模IDC、高校实验室、私有云团队——不需要Kubernetes级编排能力,但要求每次设备上下架、跳线变更、PDU重配都能被系统感知、留痕、告警。它不开源只是“能看”,而是开源到连数据库ER图、权限粒度控制逻辑、甚至机柜热力图渲染算法都摊开给你改。
2. 技术栈与架构解剖:为什么是PHP而不是Go或Python?
openDCIM 的技术选型不是历史包袱,而是对IDC管理场景的精准匹配:运维人员常需现场用浏览器快速查机柜、改状态、打标签,PHP的零配置部署、Apache/Nginx即装即用、模板引擎天然适配表单密集型操作,比启动一个Python FastAPI服务再配Nginx反向代理更贴近一线节奏。它没用微服务,所有模块跑在一个代码库下,但通过清晰的MVC分层隔离关注点。下面拆解其真实架构骨架:
2.1 目录结构即设计哲学:inc/是心脏,classes/是骨骼
项目根目录下没有src/或app/这类现代框架惯用名,而是直白的inc/(include)、classes/、images/、js/。这不是混乱,而是刻意为之的运维友好设计:
openDCIM/ ├── inc/ # 全局配置、数据库连接、权限检查、核心函数库 │ ├── config.php # 数据库连接参数、站点URL、默认时区、调试开关 │ ├── database.php # PDO封装,含prepare/execute防注入,错误日志写入syslog │ ├── auth.php # 基于session的RBAC实现,角色权限存于db,非硬编码 │ └── functions.php # 200+个工具函数:u2pos()将"U23"转坐标、getDevicePower()查PDU负载 ├── classes/ # 面向对象核心:每个类对应一个实体(Device, Rack, Port, Circuit) │ ├── Device.class.php # 设备全生命周期:create/update/delete + 关联机柜/U位/电源/网络端口 │ ├── Rack.class.php # 机柜模型:含深度/高度/U数/前后门/散热方向,支持3D坐标系 │ └── Port.class.php # 端口抽象:区分"device port"和"patch panel port",自动识别交叉连接 ├── pages/ # MVC中的View层:纯HTML+PHP混排,无JS框架,依赖jQuery 1.12(兼容IE11) │ ├── rackview.php # 机柜详情页:动态渲染U位表格,拖拽调整设备位置 │ └── deviceadd.php # 设备添加页:表单验证嵌入PHP后端逻辑(如MAC地址格式、序列号唯一性) └── api/ # RESTful接口入口:/api/v1/devices?rackid=5 返回JSON设备列表 └── index.php # 统一路由分发器,根据HTTP方法+路径调用对应class方法提示:
inc/functions.php里的validateMAC()函数会校验MAC地址是否符合XX:XX:XX:XX:XX:XX或XXXX.XXXX.XXXX格式,并拒绝广播地址(FF:FF:FF:FF:FF:FF)——这是血泪经验:曾有厂商固件把MAC写成全F,导致批量导入时设备互相覆盖。
2.2 数据库ER模型:五层关系如何落地为17张表?
openDCIM 的数据库设计是其灵魂。它没用ORM,而是手写SQL,但每张表都有明确语义边界。关键五层关系对应核心表:
| 表名 | 作用 | 关键字段 | 关系说明 |
|---|---|---|---|
Rack | 机柜实体 | id,name,height,depth,front_color,rear_color | height单位为U,depth单位为mm,支持不同规格机柜混管 |
Device | 设备实体 | id,name,serial_no,asset_tag,rack_id,u_position,u_height,status | u_position存整数(如23),u_height存占用U数(1/2/4),自动校验不越界 |
Port | 端口抽象 | id,device_id,name,type,label,connected_to | type值为'network'/'power'/'console',connected_to存另一Port.id实现链路追踪 |
Circuit | 电路抽象 | id,name,voltage,amperage,phase,pdu_id,circuit_number | 支持三相电建模,phase存'A'/'B'/'C',circuit_number对应PDU物理回路编号 |
RackObject | 机柜内对象关联 | rack_id,device_id,u_position,side | side='front'/'rear',解决前后U位复用问题(同一U位前后可放不同设备) |
注意:
Device.rack_id是外键,但RackObject表才是真实U位占用记录——这是为支持“设备跨机柜移动时不丢失历史U位”而设计。删除设备时,RackObject记录保留(带deleted_at时间戳),避免审计断链。
2.3 权限模型:RBAC不是摆设,而是细到“能否编辑某机柜的U位”
openDCIM 的权限控制不在前端隐藏按钮,而是在每个操作入口做后端校验。权限分三级:
- Role(角色):预置
admin/user/readonly,存于Role表 - Permission(权限项):如
edit_rack、delete_device、view_power,存于Permission表 - RolePermission(角色-权限映射):多对多关系表
但真正落地的是对象级权限:User表有site_id字段,Rack表也有site_id,用户只能操作同site_id下的机柜。更进一步,在rackview.php中加载机柜前,会执行:
// inc/auth.php 中的 checkRackAccess() 函数 function checkRackAccess($rack_id, $required_permission) { global $dbh; // 1. 检查用户是否有全局权限 $sql = "SELECT 1 FROM RolePermission rp JOIN User u ON u.role_id = rp.role_id WHERE u.id = ? AND rp.permission = ?"; $stmt = $dbh->prepare($sql); $stmt->execute([$_SESSION['user_id'], $required_permission]); if ($stmt->fetch()) return true; // 2. 检查对象级权限:用户是否被授权管理该机柜所在Site $sql = "SELECT 1 FROM SitePermission sp WHERE sp.user_id = ? AND sp.site_id = ( SELECT site_id FROM Rack WHERE id = ? ) AND sp.permission = ?"; $stmt = $dbh->prepare($sql); $stmt->execute([$_SESSION['user_id'], $rack_id, $required_permission]); return (bool)$stmt->fetch(); }这个函数在pages/rackview.php顶部被调用:checkRackAccess($_GET['id'], 'edit_rack')。如果返回false,直接header('Location: error.php?msg=No access')——前端JS再花哨也绕不过去。
3. 从零部署:三步跑通,避开90%新手翻车点
openDCIM 对环境要求极简,但几个关键点踩错就卡死在安装页。我按某公司IDC实测流程整理出最稳路径(Ubuntu 22.04 + Apache2 + PHP 8.1 + MySQL 8.0):
3.1 环境准备:PHP扩展不是可选项,而是启动锁
openDCIM 依赖三个PHP扩展,缺一不可,且版本有隐性要求:
# Ubuntu 22.04 默认PHP 8.1,需启用以下扩展 sudo apt install php-mysql php-xml php-gd php-curl php-zip # 验证是否生效(必须看到enabled) php -m | grep -E "(mysql|xml|gd|curl|zip)" # 输出应为: # mysql # xml # gd # curl # zip # 特别注意:openDCIM 的图片缩略图生成依赖GD库的freetype支持 # 若报错"Call to undefined function imagettftext()",需重装GD sudo apt install libfreetype6-dev libjpeg-dev libpng-dev sudo pecl install gd echo "extension=gd.so" | sudo tee /etc/php/8.1/apache2/conf.d/20-gd.ini sudo systemctl restart apache2提示:
phpinfo()页面里搜索gd,确认FreeType Support显示enabled,否则机柜热力图无法渲染。
3.2 数据库初始化:字符集必须是utf8mb4,否则中文设备名变问号
MySQL 8.0 默认字符集是utf8mb4_0900_ai_ci,但openDCIM安装脚本install/install.php会创建表时指定CHARSET=utf8,导致中文乱码。必须手动修正:
-- 创建数据库时指定正确字符集 CREATE DATABASE opendcim CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- 导入初始SQL前,先修改install/sql/structure.sql文件 -- 将所有 "ENGINE=InnoDB DEFAULT CHARSET=utf8" 替换为 "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" sed -i 's/DEFAULT CHARSET=utf8/DEFAULT CHARSET=utf8mb4/g' install/sql/structure.sql -- 执行导入 mysql -u root -p opendcim < install/sql/structure.sql mysql -u root -p opendcim < install/sql/data.sql3.3 Web服务配置:Apache重写规则是访问/api的命门
openDCIM 的REST API路径如/api/v1/devices,依赖Apache的mod_rewrite。若未启用,访问API会返回404而非JSON。配置步骤:
# 编辑Apache站点配置(如 /etc/apache2/sites-available/000-default.conf) <Directory "/var/www/html/opendcim"> Options Indexes FollowSymLinks AllowOverride All # 关键!允许.htaccess覆盖 Require all granted </Directory>然后在openDCIM根目录创建.htaccess(若不存在):
# .htaccess RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^api/(.*)$ api/index.php?path=$1 [QSA,L]最后重启服务:
sudo a2enmod rewrite sudo systemctl restart apache2验证API是否生效:
curl -I http://localhost/opendcim/api/v1/devices # 应返回 HTTP/1.1 200 OK,而非 4044. 避坑指南:那些让运维半夜爬起来的5个玄学问题
部署openDCIM最耗时的往往不是安装,而是解决那些文档没写、报错不明确、但真实存在的“玄学”问题。以下是我在三个IDC项目中踩出的血泪清单:
4.1 现象:机柜视图显示空白,U位表格不渲染,浏览器控制台无报错
原因:pages/rackview.php依赖js/jquery.min.js和js/rackview.js,但rackview.js中硬编码了/opendcim/路径前缀。若将openDCIM部署在子目录(如http://example.com/dcim/),JS请求的/opendcim/js/...会404,但jQuery错误被静默吞掉。
解决:修改pages/rackview.php第28行,将<script src="/opendcim/js/rackview.js"></script>改为相对路径<script src="js/rackview.js"></script>;同理修复所有/opendcim/硬编码。
4.2 现象:添加设备时提示“Serial Number already exists”,但数据库查无此序列号
原因:MySQL 8.0默认开启sql_mode=STRICT_TRANS_TABLES,NO_ZERO_DATE,...,而openDCIM的Device.insert()方法中,对空字符串''的序列号未做NULL转换,导致插入时被截断为NULL,而UNIQUE KEY serial_no (serial_no)索引将多个NULL视为不同值——但某些MySQL版本又认为NULL不参与唯一性校验,造成行为不一致。
解决:在classes/Device.class.php的insert()方法中,序列号为空时显式设为NULL:
// 修改前 $this->serial_no = $_POST['serial_no']; // 修改后 $this->serial_no = empty($_POST['serial_no']) ? null : trim($_POST['serial_no']);4.3 现象:机柜热力图颜色始终为蓝色,温度数据不更新
原因:热力图依赖inc/functions.php中的getRackTemperature()函数,该函数默认调用snmpget命令读取PDU的1.3.6.1.4.1.318.1.1.12.2.3.1.1.4OID(环境温度)。但多数国产PDU OID不同,且snmpget未安装或社区字符串未配置。
解决:
- 安装SNMP工具:
sudo apt install snmp snmpd - 在
inc/config.php中配置PDU信息:
define('PDU_SNMP_HOST', '192.168.10.100'); define('PDU_SNMP_COMMUNITY', 'public'); define('PDU_TEMP_OID', '1.3.6.1.4.1.2620.1.6.7.4.1.2'); // 示例:某品牌PDU温度OID- 修改
getRackTemperature()函数,用snmpget替换原exec()调用。
4.4 现象:用户登录后,点击任何菜单都跳转到login.php
原因:inc/auth.php中session_start()前有输出(如BOM头、空格、UTF-8签名),导致session cookie无法设置。常见于用Windows记事本保存的PHP文件。
解决:用file -i inc/auth.php检查编码,确保为utf-8无BOM;用vim打开,执行:set nobomb后保存;或用dos2unix inc/*.php批量清理。
4.5 现象:API返回JSON,但字段全是英文,想改成中文响应失败
原因:openDCIM的API层(api/index.php)未做国际化,所有错误消息硬编码在PHP中,如"Device not found"。直接改字符串会导致升级覆盖。
解决:在inc/config.php中定义语言包数组,api/index.php中根据Accept-Language头选择:
// inc/config.php 新增 $LANGUAGE = [ 'zh-CN' => [ 'device_not_found' => '设备未找到', 'invalid_parameter' => '参数错误' ], 'en-US' => [ 'device_not_found' => 'Device not found', 'invalid_parameter' => 'Invalid parameter' ] ]; // api/index.php 中 $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en-US'; $msg_key = 'device_not_found'; echo json_encode(['error' => $LANGUAGE[$lang][$msg_key] ?? $LANGUAGE['en-US'][$msg_key]]);5. 二次开发实战:给机柜视图加“设备健康状态灯”,30分钟上线
openDCIM 的机柜视图(pages/rackview.php)是运维最常打开的页面,但默认只显示设备名称和U位,无法一眼识别故障设备。我们来加一个“健康状态灯”:绿色(在线)、黄色(SNMP超时)、红色(离线),数据来自Zabbix API。这不是炫技,而是解决“巡检要切三个系统”的真实痛点。
5.1 数据源对接:用Zabbix API拉取设备可用性
Zabbix 6.0 API要求Bearer Token认证。我们在inc/zabbix.php中封装调用:
<?php // inc/zabbix.php function getZabbixHostStatus($host_ip) { $zabbix_url = 'https://zabbix.example.com/api_jsonrpc.php'; $token = '03e5b4a12c9d8e7f6a5b4c3d2e1f0a9b'; // 从Zabbix用户Token获取 $data = [ 'jsonrpc' => '2.0', 'method' => 'host.get', 'params' => [ 'filter' => ['ip' => $host_ip], 'output' => ['hostid', 'status'], 'selectInterfaces' => ['ip'] ], 'auth' => $token, 'id' => 1 ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $zabbix_url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response, true); if (isset($result['result'][0]['status'])) { return $result['result'][0]['status'] == 0 ? 'available' : 'unavailable'; } return 'unknown'; }注意:Zabbix中
status=0表示启用且监控中,status=1表示已禁用。我们还需查item状态,但为简化,先用主机状态。
5.2 前端渲染:在U位表格中注入状态灯图标
修改pages/rackview.php,在渲染设备行的循环中插入状态列:
<!-- 在 <table> 的 <thead> 中添加新列 --> <th width="5%">状态</th> <!-- 在 <tbody> 的 foreach 循环中(约第180行)添加 --> <td class="text-center"> <?php $ip = $device->ip_address ?? ''; $status = $ip ? getZabbixHostStatus($ip) : 'unknown'; $icon = match($status) { 'available' => '<i class="fa fa-circle text-success"></i>', 'unavailable' => '<i class="fa fa-circle text-danger"></i>', default => '<i class="fa fa-circle text-warning"></i>' }; echo $icon; ?> </td>同时引入Font Awesome图标(在<head>中):
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">5.3 性能优化:避免每行都调Zabbix API(N+1问题)
当前实现会让一个机柜20台设备触发20次API请求,页面加载慢。我们改用批量查询:
// 修改 inc/zabbix.php,新增批量函数 function getZabbixHostStatusBatch($ips) { // 构造Zabbix host.get的filter,用OR条件 $filters = []; foreach ($ips as $ip) { $filters[] = ['ip' => $ip]; } // Zabbix不支持OR filter,改用hostinterface.get + group by ip // 此处简化:用单次请求查所有IP,Zabbix 6.0+支持filter[ip]为数组 $data = [ 'jsonrpc' => '2.0', 'method' => 'hostinterface.get', 'params' => [ 'filter' => ['ip' => $ips], 'output' => ['ip', 'hostid'], 'selectHosts' => ['host', 'status'] ], 'auth' => $token, 'id' => 1 ]; // ... curl调用同上,返回关联数组 ['192.168.1.10' => 'available', ...] }然后在rackview.php顶部,先收集所有设备IP,再批量查询一次:
// 页面顶部 $all_ips = []; foreach ($devices as $device) { if ($device->ip_address) $all_ips[] = $device->ip_address; } $zabbix_status = $all_ips ? getZabbixHostStatusBatch($all_ips) : []; // 渲染时 $status = $zabbix_status[$device->ip_address] ?? 'unknown';5.4 验证与灰度:如何不中断业务地测试新功能
上线前必须验证,但不能影响生产。我的做法是:
- 本地沙箱验证:用Docker启动Zabbix测试实例(
zabbix/zabbix-server-pgsql:ubuntu-6.0-latest),导入测试设备,确认API返回正常; - 灰度开关:在
inc/config.php中加开关:
define('ENABLE_ZABBIX_STATUS', false); // 生产环境先关在rackview.php中包裹状态灯代码:
<?php if (ENABLE_ZABBIX_STATUS): ?> <!-- 状态灯HTML --> <?php endif; ?>- 日志埋点:在
getZabbixHostStatusBatch()中记录调用耗时:
file_put_contents('/var/log/opendcim/zabbix.log', date('Y-m-d H:i:s') . " Batch query for " . count($ips) . " IPs, took " . (microtime(true)-$start) . "s\n", FILE_APPEND);从那以后我每次给openDCIM加新功能,都强制走一遍“沙箱验证→灰度开关→日志埋点→监控告警”四步。不是怕代码错,而是怕它错得悄无声息——IDC里最贵的不是服务器,是那个没人发现的、持续三天的磁盘降速故障。希望帮到你。
本文还有配套的精品资源,点击获取