本文目录导读:

在Laravel中加密模型属性有多种方法,我将介绍几种常用的方案:
使用Laravel内置的加密功能
基础方法 - 手动加密/解密
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
class User extends Model
{
protected $fillable = ['name', 'email', 'phone', 'address'];
// 手动加密存储
public function setPhoneAttribute($value)
{
$this->attributes['phone'] = Crypt::encryptString($value);
}
// 手动解密读取
public function getPhoneAttribute($value)
{
if ($value) {
return Crypt::decryptString($value);
}
return null;
}
// 加密地址
public function setAddressAttribute($value)
{
$this->attributes['address'] = Crypt::encryptString($value);
}
public function getAddressAttribute($value)
{
if ($value) {
return Crypt::decryptString($value);
}
return null;
}
}
使用Trait进行批量加密
创建一个可复用的Trait:
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Crypt;
trait Encryptable
{
public function getAttribute($key)
{
$value = parent::getAttribute($key);
if (in_array($key, $this->encryptable ?? []) && !empty($value)) {
try {
return Crypt::decryptString($value);
} catch (\Exception $e) {
// 如果是未加密的数据,直接返回
return $value;
}
}
return $value;
}
public function setAttribute($key, $value)
{
if (in_array($key, $this->encryptable ?? []) && !empty($value)) {
$value = Crypt::encryptString($value);
}
return parent::setAttribute($key, $value);
}
}
使用Trait:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\Encryptable;
class User extends Model
{
use Encryptable;
protected $fillable = ['name', 'email', 'phone', 'ssn'];
// 指定需要加密的字段
protected $encryptable = [
'phone',
'ssn',
'bank_account'
];
}
使用第三方包 - spatie/laravel-encryptable
安装包:
composer require spatie/laravel-encryptable
使用方式:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\Encryptable\Encryptable;
class User extends Model
{
use Encryptable;
protected $fillable = ['name', 'email', 'token'];
// 指定加密字段
protected $encryptable = [
'token',
'private_key'
];
}
高级用法 - 完整的加密模型示例
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
class Patient extends Model
{
protected $table = 'patients';
protected $fillable = [
'name',
'email',
'phone',
'medical_record',
'insurance_number'
];
// 需要加密的字段配置
protected $encryptable = [
'name' => 'string',
'phone' => 'string',
'medical_record' => 'json',
'insurance_number' => 'string'
];
protected $casts = [
'is_active' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
// 自动处理加密字段
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->encryptFields();
});
static::updating(function ($model) {
$model->encryptFields();
});
}
protected function encryptFields()
{
foreach ($this->encryptable as $field => $type) {
if (isset($this->attributes[$field]) && !empty($this->attributes[$field])) {
$value = $this->attributes[$field];
// 根据类型处理值
if ($type === 'json') {
$value = json_encode($value);
}
// 加密
$this->attributes[$field] = Crypt::encryptString($value);
}
}
}
// 解密单个字段
public function decryptField($field)
{
if (isset($this->attributes[$field]) && in_array($field, array_keys($this->encryptable))) {
try {
$decrypted = Crypt::decryptString($this->attributes[$field]);
if ($this->encryptable[$field] === 'json') {
return json_decode($decrypted, true);
}
return $decrypted;
} catch (\Exception $e) {
// 返回原始值
return $this->attributes[$field];
}
}
return null;
}
// 获取解密后的所有字段
public function getDecryptedAttributes()
{
$data = [];
foreach ($this->encryptable as $field => $type) {
$data[$field] = $this->decryptField($field);
}
return $data;
}
// 重写toArray方法
public function toArray()
{
$attributes = parent::toArray();
foreach ($this->encryptable as $field => $type) {
if (isset($attributes[$field])) {
$attributes[$field] = $this->decryptField($field);
}
}
return $attributes;
}
}
// 使用示例
$patient = Patient::create([
'name' => '张三',
'phone' => '13800138000',
'medical_record' => ['诊断', '药物', '过敏史'],
'insurance_number' => 'ABC123456'
]);
// 读取自动解密
echo $patient->name; // 张三(自动解密)
echo $patient->phone; // 13800138000
// 获取解密后的JSON字段
$medical = $patient->decryptField('medical_record');
print_r($medical);
// 获取所有解密字段
$allData = $patient->getDecryptedAttributes();
注意事项和建议
搜索加密字段
// 数据库无法直接搜索加密字段,需要特殊处理
public static function searchByEncryptedField($field, $value)
{
$records = self::all();
return $records->filter(function ($record) use ($field, $value) {
try {
return $record->decryptField($field) === $value;
} catch (\Exception $e) {
return false;
}
});
}
索引和性能考虑
// 为搜索创建单独的明文索引字段
protected $fillable = [
'email', // 明文存储用于索引
'email_hash', // 存储哈希值用于精确查找
'phone_encrypted' // 加密存储
];
public function setPhoneAttribute($value)
{
$this->attributes['phone_encrypted'] = Crypt::encryptString($value);
$this->attributes['phone_hash'] = hash('sha256', $value);
}
public function findByPhone($phone)
{
return self::where('phone_hash', hash('sha256', $phone))->first();
}
批量数据处理
// 批量加密现有数据
public static function encryptExistingRecords()
{
self::chunk(100, function ($records) {
foreach ($records as $record) {
foreach ((new static)->encryptable as $field => $type) {
if (!empty($record->attributes[$field])) {
$record->{$field} = $record->attributes[$field];
$record->save();
}
}
}
});
}
这些方法各有优劣,建议根据项目需求选择合适的方式,如果需要简单的加密功能,直接使用Laravel自带的Crypt即可;如果项目中有多个模型需要加密,建议使用Trait或第三方包的形式。