在 yii2 中,一个接口,不希望暴露 SQL 错误在接口响应中,但是又希望暴露字段本身的验证错误在接口响应中,应该如何实现了?
1、现有的实现如下,存在字段本身的验证失败后,接口响应中永远是:数据库操作失败,请联系管理员,用户体验不友好。如图1
if ($animation->save()) { return [ 'code' => 10000, 'message' => '设置动画属性成功', 'data' => $animation, ]; } return [ 'code' => 10004, 'message' => '数据库操作失败,请联系管理员', ];
2、希望如下实现,1、不暴露数据库异常(如 SQL 错误、PDO 异常等) —— 避免安全风险。2、保留模型字段的验证错误(如 required、string 长度等) —— 给前端明确反馈。
3、实现 可复用的 BaseController::safeSave() 方法,可以帮助你在 Yii2 中统一处理模型保存,确保:字段验证错误会返回清晰提示;SQL 或系统异常不会暴露给前端,而是记录日志并返回友好消息。
/** * 安全保存模型:支持字段验证错误输出,系统异常捕获 * * @param ActiveRecord $model 模型实例 * @param array $options 额外参数支持: * - 'successMessage' 成功时返回的提示信息 * - 'failMessage' 验证失败时返回的提示信息 * - 'exceptionMessage' 异常时返回的提示信息 * - 'isUpdate' 是否更新操作(true/false) * * @return array */ protected function safeSave(ActiveRecord $model, array $options = []): array { $successMessage = $options['successMessage'] ?? '保存成功'; $failMessage = $options['failMessage'] ?? '字段验证失败'; $exceptionMessage = $options['exceptionMessage'] ?? '数据库操作失败,请联系管理员'; $isUpdate = $options['isUpdate'] ?? false; try { if (!$model->validate()) { return [ 'code' => 10200, 'message' => $failMessage . ': ' . ModelHelper::getFirstError($model), ]; } if (!$isUpdate) { $model->save(false); // insert } elseif ($model->getDirtyAttributes()) { // 如果是更新操作,检查是否变动再 save(false) $model->save(false); } return [ 'code' => 10000, 'message' => $successMessage, 'data' => $model, ]; } catch (Throwable $e) { Yii::error([ 'exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), ], 'application.model.save.exception'); return [ 'code' => 10500, 'message' => $exceptionMessage, ]; } }
/api/helpers/ModelHelper.php
<?php namespace api\helpers; use yii\base\Model; class ModelHelper { /** * 获取模型验证失败时的第一个错误信息 * @param Model $model * @return string */ public static function getFirstError(Model $model): string { // 验证失败:$errors 是一个包含错误信息的数组 $errors = $model->getErrors(); $firstMessage = '验证失败'; if (!empty($errors)) { // 获取第一个属性名 $firstAttribute = array_key_first($errors); $firstFieldErrors = $errors[$firstAttribute] ?? []; // 获取第一个属性的第一个错误 $firstMessage = is_array($firstFieldErrors) && isset($firstFieldErrors[0]) ? $firstFieldErrors[0] : $firstMessage; } return $firstMessage; } }
4、现有的实现如下,存在字段本身的验证失败后,接口响应:字段验证失败: 动画时长必须是一个数字。 符合预期。如图2
近期评论