惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Troy Hunt's Blog
P
Proofpoint News Feed
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
S
Securelist
L
Lohrmann on Cybersecurity
Security Latest
Security Latest
T
Threatpost
H
Heimdal Security Blog
W
WeLiveSecurity
A
Arctic Wolf
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
IT之家
IT之家
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
TaoSecurity Blog
TaoSecurity Blog
A
About on SuperTechFans
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
量子位
Stack Overflow Blog
Stack Overflow Blog
Know Your Adversary
Know Your Adversary
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
WordPress大学
WordPress大学
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
Vercel News
Vercel News
C
Cyber Attacks, Cyber Crime and Cyber Security
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security

博客园 - 刘俊涛的博客

qoder-体验分享 50+专家角色+多代理框架,一个人就能建起一支AI 公司 小米突然放大招!2亿 Token 免费领,最后几天冲刺! 这个项目把浏览器“干了30年的事”干掉了:Pretext 为什么突然爆火? 通用商品打标模块实现方案(基于位运算,适配多框架多语言) 最小阻力之路 U盘打不开 : 文件损坏 : 提示需要格式化?用CHKDSK轻松修复教程 祂看顧麻雀(His Eye Is on the Sparrow)鄭漢良 别再把一切都变成数组了:用迭代器让 JavaScript 既快又省心 Spark性能调优实战笔记 Spark神操作:轻松拿捏公司“历史烂数据”,一人干翻十年脏表 特定血型血液生成的研究进展:化学合成与微生物生产技术路线 禁止直接 update 业务关键字段 非暴力沟通 PHP 8.1+ 引入的 枚举(Enum) 类型 PHP 8.5 新特性速览:管道操作符、clone with、闭包增强与更多实用功能 从错误中学习 产品拆解到底怎么做?8步框架,新手产品经理必学干货! 《创作与爱:托芙·扬松传》读书笔记 MongoDB 端口安全加固方案 为什么软件反应特别慢?一次因版本架构错误导致的性能问题排查记录 【Go 语言神器】iota 到底是什么?为什么高手都爱用它?
后端工程中使用 Model + Trait 设计可复用的数据模型(完整实现示例)
刘俊涛的博客 · 2026-03-06 · via 博客园 - 刘俊涛的博客

后端工程中使用 Model + Trait 设计可复用的数据模型(完整实现示例)

在实际的后端项目开发中,例如 CRM、ERP 或用户系统,Model 很容易变得越来越臃肿。随着业务增加,很多通用能力会不断叠加,例如:

  • 自动生成 UUID
  • 软删除
  • 操作日志
  • 搜索封装
  • 审计字段(created_by、updated_by)

如果把这些功能全部写在 Model 里,最终很容易出现一个 2000~3000 行的 Model 文件,维护成本非常高。

一种在企业项目中非常常见的解决方案是:

Model + Trait 组合设计

Trait 用来承载可复用的横切功能(Cross-cutting concerns),Model 只负责数据结构和关系定义。

下面通过一个完整工程示例说明这种设计。


一、目标结构

假设有一个 Student 模型,需要具备以下能力:

1 自动生成 UUID

2 软删除

3 操作日志

4 搜索条件封装

5 审计字段自动记录

项目目录结构可以设计为:

app

├─ Models

│ └─ Student.php

│ └─ OperationLog.php

├─ Traits

│ ├─ SoftDeleteTrait.php

│ ├─ OperationLogTrait.php

│ ├─ UuidTrait.php

│ ├─ SearchTrait.php

│ └─ BlameableTrait.php


二、Student Model

Student 模型只负责数据结构和使用 Trait。

app/Models/Student.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Traits\SoftDeleteTrait;
use App\Traits\OperationLogTrait;
use App\Traits\UuidTrait;
use App\Traits\SearchTrait;
use App\Traits\BlameableTrait;

class Student extends Model
{
    use SoftDeleteTrait, OperationLogTrait, UuidTrait, SearchTrait, BlameableTrait;

    protected $table = 'students';

    protected $fillable = [
        'uuid',
        'name',
        'phone',
        'email',
        'created_by',
        'updated_by'
    ];
}

这里可以看到,Student 模型本身非常干净,只负责表结构定义。


三、软删除 Trait

SoftDeleteTrait 用来实现逻辑删除,而不是直接删除数据。

app/Traits/SoftDeleteTrait.php

<?php

namespace App\Traits;

trait SoftDeleteTrait
{
    public static function bootSoftDeleteTrait()
    {
        static::addGlobalScope('soft_delete', function ($builder) {
            $builder->whereNull('deleted_at');
        });
    }

    public function softDelete()
    {
        $this->deleted_at = now();
        $this->save();
    }

    public function restore()
    {
        $this->deleted_at = null;
        $this->save();
    }
}

数据库需要增加字段:

deleted_at datetime

这样删除数据时只会记录时间,而不会真正删除。


四、UUID 自动生成

很多系统希望主键或业务 ID 使用 UUID。

app/Traits/UuidTrait.php

<?php

namespace App\Traits;

use Illuminate\Support\Str;

trait UuidTrait
{
    public static function bootUuidTrait()
    {
        static::creating(function ($model) {

            if (empty($model->uuid)) {
                $model->uuid = (string) Str::uuid();
            }

        });
    }
}

数据库字段:

uuid varchar(36)

当创建数据时自动生成 UUID。


五、操作日志 Trait

在很多后台系统中,记录操作日志是非常重要的功能。

app/Traits/OperationLogTrait.php

<?php

namespace App\Traits;

use App\Models\OperationLog;

trait OperationLogTrait
{
    public static function bootOperationLogTrait()
    {
        static::created(function ($model) {

            OperationLog::create([
                'model' => get_class($model),
                'model_id' => $model->id,
                'action' => 'create',
                'content' => json_encode($model->toArray())
            ]);

        });

        static::updated(function ($model) {

            OperationLog::create([
                'model' => get_class($model),
                'model_id' => $model->id,
                'action' => 'update',
                'content' => json_encode($model->getDirty())
            ]);

        });

        static::deleted(function ($model) {

            OperationLog::create([
                'model' => get_class($model),
                'model_id' => $model->id,
                'action' => 'delete',
                'content' => ''
            ]);

        });
    }
}

日志表 operation_logs 示例字段:

id

model

model_id

action

content

created_at

这样所有模型的增删改都会自动记录。


六、搜索 Trait

很多列表页面都有搜索需求,可以把查询逻辑封装在 Trait 里。

app/Traits/SearchTrait.php

<?php

namespace App\Traits;

trait SearchTrait
{
    public function scopeSearch($query, $params)
    {
        if (!empty($params['name'])) {
            $query->where('name', 'like', '%' . $params['name'] . '%');
        }

        if (!empty($params['phone'])) {
            $query->where('phone', $params['phone']);
        }

        if (!empty($params['email'])) {
            $query->where('email', 'like', '%' . $params['email'] . '%');
        }

        return $query;
    }
}

使用方式:

Student::search($params)->paginate(20);

这样所有查询逻辑都集中在 Trait 中。


七、审计字段 Trait

很多企业系统需要记录是谁创建或修改了数据。

app/Traits/BlameableTrait.php

<?php

namespace App\Traits;

use Illuminate\Support\Facades\Auth;

trait BlameableTrait
{
    public static function bootBlameableTrait()
    {
        static::creating(function ($model) {

            if (Auth::check()) {
                $model->created_by = Auth::id();
            }

        });

        static::updating(function ($model) {

            if (Auth::check()) {
                $model->updated_by = Auth::id();
            }

        });
    }
}

数据库字段:

created_by

updated_by

这样系统可以自动记录数据是谁创建、谁修改。


八、使用示例

创建学生

Student::create([
    'name' => '张三',
    'phone' => '13800000000'
]);

自动触发:

UUID 生成

操作日志记录

created_by 自动写入

更新学生

$student = Student::find(1);
$student->name = '李四';
$student->save();

自动触发:

更新日志

updated_by 自动写入


九、这种架构的优点

第一,Model 变得非常干净

只负责数据结构和关系定义。

第二,功能高度复用

多个模型可以共享同一个 Trait。

第三,代码更易维护

横切逻辑统一管理。

第四,更符合大型系统架构

Trait 负责通用能力,Service 负责业务逻辑。

典型调用结构如下:

Controller

Service

Model + Traits

Database


十、总结

在大型后端系统中,Model 不应该承担过多业务逻辑。通过 Trait 可以把通用能力模块化,例如:

  • 软删除
  • 日志记录
  • UUID 生成
  • 搜索封装
  • 审计字段

这种 Model + Trait 的组合模式,可以显著降低代码耦合度,使系统结构更加清晰。

在实际项目中,很多成熟的 CRM、ERP 系统都会采用这种设计模式。