1. 企业内网开发的本质思考
第一次接手企业内网项目时,我犯了一个典型错误 - 照搬互联网那套微服务架构。结果部署后才发现,20人的财务部门根本不需要什么服务拆分,反而因为复杂的服务调用链导致排查一个简单问题要查5个系统日志。这个教训让我深刻认识到:内网开发不是互联网开发的简化版,而是一套完全不同的工程哲学。
企业内网系统的核心特征可以用三个关键词概括:
- 确定性:用户规模、业务流程、数据流向都是明确且有限的
- 可控性:网络环境、硬件配置、软件版本都在掌控范围内
- 长期性:系统生命周期往往以5-10年计
我整理的这个对比表最能说明问题:
| 维度 | 互联网应用 | 企业内网应用 |
|---|---|---|
| 用户行为 | 不可预测的流量高峰 | 固定的工作时间访问模式 |
| 技术债 | 可以接受短期技术债 | 必须控制长期技术债 |
| 升级频率 | 按天/周迭代 | 按季度/年升级 |
| 容错成本 | 出错可以快速回滚 | 出错可能导致业务停摆 |
提示:在内网项目中,选择技术方案时要问自己:这个设计5年后还好维护吗?而不是问:这个技术够不够新潮?
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模块化单体架构实战
2.1 为什么是模块化单体?
去年为某制造企业实施ERP系统时,我们评估了三种架构:
- 传统单体:所有代码混在一起,3个月后就难以维护
- 微服务:需要额外3台服务器做服务网格,运维成本翻倍
- 模块化单体:完美折中方案
最终选择的模块化单体架构目录结构如下:
code复制/app
├── Modules/
│ ├── Production/ # 生产模块
│ │ ├── Controllers/
│ │ ├── Models/
│ │ └── Routes.php # 模块自有路由
│ ├── Warehouse/ # 仓储模块
│ └── Quality/ # 质检模块
└── Core/
├── Auth/ # 统一认证
├── Notifications # 消息中心
└── Exceptions/ # 异常处理
关键实现技巧:
- 使用Laravel的包自动发现机制注册模块
- 每个模块有独立的service provider
- 模块间通过接口通信,禁止直接依赖
php复制// 在模块ServiceProvider中注册路由
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/Routes.php');
$this->loadViewsFrom(__DIR__.'/Views', 'warehouse');
}
2.2 数据库设计规范
经历过一次数据混乱的噩梦后,我制定了严格的数据库准则:
- 统一用户体系:全系统共用users表,避免多套账号体系
- 强外键约束:所有关联必须声明FOREIGN KEY
- 软删除标配:重要表必须包含deleted_at字段
- 审计字段:created_by, updated_at等字段由基类自动维护
sql复制CREATE TABLE material_requests (
id BIGINT PRIMARY KEY,
requester_id BIGINT NOT NULL,
approver_id BIGINT NULL,
status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
deleted_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (requester_id) REFERENCES users(id),
FOREIGN KEY (approver_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
注意:永远不要在业务代码中使用数据库级联删除!这会导致审计数据丢失。应该在应用层实现级联逻辑。
3. 坚不可摧的权限体系
3.1 RBAC+ABAC混合模型
某次安全审计中发现,纯RBAC(基于角色的访问控制)无法满足复杂场景。现在我们采用:
- RBAC控制功能权限(如"能否访问报表菜单")
- ABAC(基于属性的访问控制)控制数据权限(如"只能看本部门数据")
权限配置示例:
php复制// config/permissions.php
return [
'finance' => [
'view-all-invoices' => [
'label' => '查看所有发票',
'rules' => ['department' => 'finance'] // ABAC规则
],
'approve-payment' => [
'label' => '审批付款',
'roles' => ['finance_manager'] // RBAC角色
]
]
];
3.2 行级权限实现方案
经过多次迭代,我们总结出三种行级权限实现方式:
- 查询作用域(推荐)
php复制class PurchaseOrder extends Model
{
public function scopeAccessible($query)
{
$user = auth()->user();
if ($user->cannot('view-all-orders')) {
return $query->where('department_id', $user->department_id)
->orWhere('creator_id', $user->id);
}
return $query;
}
}
// 控制器中使用
PurchaseOrder::accessible()->get();
- 全局查询过滤器
php复制// 在AppServiceProvider中
Model::addGlobalScope('department', function ($builder) {
if (auth()->check() && !auth()->user()->isAdmin()) {
$builder->where('department_id', auth()->user()->department_id);
}
});
- 中间件过滤响应
php复制class FilterResponseMiddleware
{
public function handle($request, $next)
{
$response = $next($request);
$data = $response->getData();
$data = $this->filterData($data);
return $response->setData($data);
}
}
经验:对于新项目推荐方案1,灵活性最高;改造老项目可用方案3作为过渡。
4. 审计日志的工程实践
4.1 全链路审计方案
我们设计的审计系统包含三个层级:
- 操作日志(谁在什么时候做了什么)
php复制// 使用Laravel Auditing
class Order extends Model
{
use \OwenIt\Auditing\Auditable;
protected $auditEvents = [
'created',
'updated',
'deleted',
'restored'
];
}
- 数据变更(具体改了哪些字段)
php复制protected $auditInclude = [
'order_no',
'total_amount',
'status'
];
- 上下文信息(当时的环境参数)
php复制protected $auditTimestamps = true;
protected $auditIpAddress = true;
protected $auditUserAgent = true;
4.2 高性能审计技巧
当审计日志导致性能下降时,我们采用以下优化策略:
- 异步写入:使用队列处理审计日志
php复制// config/audit.php
'queue' => [
'enable' => true,
'connection' => 'redis',
'queue' => 'audit'
],
- 关键操作同步写:如权限变更、敏感操作
php复制$user->auditEvent = 'privilege-change';
$user->auditCustomOld = $oldRoles;
$user->auditCustomNew = $newRoles;
$user->isAuditing = true; // 标记同步审计
$user->save();
- 定期归档:将旧日志转移到冷存储
bash复制# 每月归档一次
0 3 1 * * php artisan audit:archive --months=3
5. 部署架构与性能调优
5.1 典型内网部署方案
经过多个项目验证的部署架构:
code复制 +-----------------+
| HAProxy |
| (负载均衡) |
+--------+--------+
|
+----------------+----------------+
| | |
+----------+-------+ +------+--------+ +-----+-----------+
| PHP-FPM Node 1 | | PHP-FPM Node 2 | | MySQL Master |
| (4C8G) | | (4C8G) | | (8C16G) |
+------------------+ +----------------+ +-------+--------+
| | |
+----------+-------+ +------+--------+ +--------+-------+
| Redis Cache | | MySQL Slave | | 备份服务器 |
| (2C4G) | | (4C8G) | | (NAS存储) |
+------------------+ +---------------+ +-----------------+
关键配置参数:
ini复制; php-fpm.conf
pm = static
pm.max_children = 50 ; (内存MB / 每个进程MB) * 1.2
request_terminate_timeout = 60s ; 内网请求不应超过60秒
; my.cnf
innodb_buffer_pool_size = 12G ; 物理内存的70-80%
innodb_log_file_size = 256M
max_connections = 200
5.2 零停机部署方案
通过以下脚本实现无缝更新:
bash复制#!/bin/bash
# deploy.sh
# 维护模式标记
touch /tmp/deploy.lock
# 同步代码
git pull origin main
# 安装依赖
composer install --no-dev --optimize-autoloader
# 执行迁移
php artisan migrate --force
# 清理缓存
php artisan optimize:clear
# 重启服务
sudo systemctl reload php-fpm
# 移除标记
rm /tmp/deploy.lock
配合Nginx的优雅重载:
bash复制sudo nginx -s reload # 不中断连接的重载配置
6. 持续维护的工程规范
6.1 文档即代码实践
我们强制要求的文档结构:
code复制/docs
├── ARCHITECTURE.md # 架构决策记录
├── ONBOARDING.md # 新人上手指南
├── PERMISSIONS.md # 权限矩阵
├── API_DESIGN.md # API规范
└── BUSINESS_FLOWS/ # 业务流程图
├── order_process.puml
└── approval_workflow.puml
文档更新流程:
- 任何代码变更如果影响文档必须同步更新
- 文档变更需要单独提交
- CI流水线会检查文档是否同步更新
6.2 静态分析与测试
我们的质量门禁标准:
yaml复制# .gitlab-ci.yml
stages:
- lint
- test
- deploy
phpstan:
stage: lint
script:
- vendor/bin/phpstan analyse --level=7 --memory-limit=1G
phpunit:
stage: test
script:
- vendor/bin/phpunit --coverage-text --colors=never
- bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN
coverage: '/^\s*Lines:\s*\d+.\d+\%/'
达标要求:
- PHPStan至少level 7
- 单元测试覆盖率≥80%
- 所有测试必须通过
- 代码风格符合PSR-12
7. 十年架构的生存法则
在维护一个8年历史的采购系统时,我总结了这些经验:
- 数据库兼容性:永远使用标准SQL,避免数据库特有语法
- 依赖管理:锁定主要依赖版本,如Laravel保持LTS版本
- 扩展性设计:预留10-20%的字段冗余
- 过时技术淘汰:建立技术雷达,定期评估老旧组件
技术债务管理表示例:
| 组件 | 当前版本 | 状态 | 计划升级时间 | 负责人 |
|---|---|---|---|---|
| Laravel | 8.x LTS | 健康 | 2024-Q1 | 张伟 |
| Vue | 2.6 | 风险 | 2023-Q3 | 李娜 |
| MySQL | 5.7 | 警告 | 2023-Q4 | 王强 |
最后记住:最好的内网系统不是用了多少炫酷技术,而是当5年后新人接手时,还能在一天内理解并安全地做出变更。这需要我们在今天做出正确的架构选择。
