PHP项目Laravel加密模型属性方法

wen PHP项目 6

本文目录导读:

PHP项目Laravel加密模型属性方法

  1. 使用Laravel内置的加密功能
  2. 使用Trait进行批量加密
  3. 使用第三方包 - spatie/laravel-encryptable
  4. 高级用法 - 完整的加密模型示例
  5. 注意事项和建议

在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或第三方包的形式。

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