1. TP5公共方法开发背景与需求分析
在ThinkPHP5框架的实际开发中,我们经常遇到一些需要重复使用的功能场景。比如记录调试日志、处理异常情况、生成图片URL以及计算地理位置距离等。这些功能如果每次都重新编写,不仅效率低下,而且难以保证代码一致性。
我最近在一个电商项目中就遇到了这样的需求:需要记录用户行为日志、处理支付异常、动态生成商品图片URL,以及根据用户位置计算与商家的距离。通过将这些功能封装成公共方法,不仅提高了开发效率,还使得代码维护更加方便。
2. 日志记录功能实现
2.1 日志记录方法设计
在TP5中,虽然框架自带了日志功能,但默认配置可能不符合我们的业务需求。我们需要一个更灵活的日志记录方法:
php复制/**
* 自定义日志记录
* @param string|array $content 日志内容
* @param string $level 日志级别
* @param string $file 日志文件名(不含后缀)
* @return bool
*/
public static function writeLog($content, $level = 'info', $file = '')
{
try {
if (empty($file)) {
$file = $level;
}
$logPath = \think\facade\Env::get('runtime_path') . 'log/' . date('Ym') . '/';
if (!is_dir($logPath)) {
mkdir($logPath, 0755, true);
}
$content = is_array($content) ? json_encode($content, JSON_UNESCAPED_UNICODE) : $content;
$logContent = '[' . date('Y-m-d H:i:s') . '] ' . strtoupper($level) . ': ' . $content . PHP_EOL;
return file_put_contents($logPath . $file . '.log', $logContent, FILE_APPEND);
} catch (\Exception $e) {
// 日志记录失败时静默处理
return false;
}
}
2.2 日志方法使用示例
php复制// 记录普通信息日志
self::writeLog('用户登录成功', 'info', 'user');
// 记录错误日志(数组会自动转为JSON)
self::writeLog([
'error' => '支付失败',
'order_no' => '20230801123456',
'user_id' => 1001
], 'error', 'payment');
2.3 日志记录最佳实践
- 日志分级:建议至少分为error、warning、info、debug四个级别
- 日志文件分割:按业务模块划分日志文件,如user.log、payment.log等
- 日志内容规范:包含时间戳、日志级别、业务标识等关键信息
- 性能考虑:高频日志建议使用异步写入方式
提示:对于高并发场景,可以考虑使用Swoole的协程日志组件或Redis队列缓冲日志
3. 异常处理封装
3.1 异常抛出方法实现
php复制/**
* 统一异常处理
* @param string $message 异常信息
* @param int $code 异常代码
* @param string $logFile 日志文件名
* @throws \Exception
*/
public static function throwException($message, $code = 0, $logFile = 'exception')
{
// 记录异常日志
self::writeLog([
'code' => $code,
'message' => $message,
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
], 'error', $logFile);
throw new \Exception($message, $code);
}
3.2 异常处理使用场景
php复制// 业务逻辑中抛出异常
if (!$user) {
self::throwException('用户不存在', 1001, 'user');
}
// API控制器中捕获异常
try {
// 业务代码
} catch (\Exception $e) {
return json([
'status' => 0,
'code' => $e->getCode(),
'message' => $e->getMessage()
]);
}
3.3 异常处理进阶技巧
- 自定义异常类:继承Exception实现业务异常类
- 多语言支持:异常信息支持多语言转换
- 异常分级:区分系统异常和业务异常
- 错误页面:根据异常类型返回不同错误页面
4. 图片URL生成方法
4.1 图片URL生成实现
php复制/**
* 生成图片完整URL
* @param string $path 图片路径(相对或绝对)
* @param string $default 默认图片
* @return string
*/
public static function imageUrl($path, $default = '')
{
if (empty($path)) {
return $default ?: config('app.default_image');
}
// 已经是完整URL直接返回
if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) {
return $path;
}
// 处理相对路径
if (strpos($path, '/') !== 0) {
$path = '/' . ltrim($path, '/');
}
$baseUrl = config('app.image_domain') ?: request()->domain();
return rtrim($baseUrl, '/') . $path;
}
4.2 图片URL使用示例
php复制// 生成商品图片URL
$productImage = self::imageUrl('/uploads/product/123.jpg');
// 使用默认图片
$avatar = self::imageUrl($user['avatar'], '/static/images/default_avatar.png');
4.3 图片处理进阶方案
- CDN支持:配置多个图片域名实现负载均衡
- 图片样式:支持缩略图、水印等处理
- 云存储:集成OSS、COS等云存储服务
- 图片懒加载:返回占位图实现延迟加载
5. 经纬度距离计算
5.1 距离计算方法实现
php复制/**
* 计算两个经纬度之间的距离(米)
* @param float $lat1 纬度1
* @param float $lng1 经度1
* @param float $lat2 纬度2
* @param float $lng2 经度2
* @return float
*/
public static function getDistance($lat1, $lng1, $lat2, $lng2)
{
// 地球半径(米)
$earthRadius = 6371000;
// 将角度转为弧度
$lat1 = deg2rad($lat1);
$lng1 = deg2rad($lng1);
$lat2 = deg2rad($lat2);
$lng2 = deg2rad($lng2);
// 使用Haversine公式计算
$dlat = $lat2 - $lat1;
$dlng = $lng2 - $lng1;
$a = sin($dlat / 2) * sin($dlat / 2) +
cos($lat1) * cos($lat2) * sin($dlng / 2) * sin($dlng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return round($earthRadius * $c);
}
5.2 距离计算使用示例
php复制// 用户位置
$userLat = 39.90469;
$userLng = 116.40717; // 北京
// 商家位置
$shopLat = 31.23037;
$shopLng = 121.47370; // 上海
// 计算距离
$distance = self::getDistance($userLat, $userLng, $shopLat, $shopLng);
echo '距离:' . ($distance > 1000 ?
round($distance/1000, 2) . '公里' : $distance . '米');
5.3 距离计算优化方案
- Redis GEO:使用Redis的GEO命令实现附近位置查询
- MySQL空间索引:利用ST_Distance_Sphere函数优化查询
- 批量计算:优化算法实现多个位置同时计算
- 缓存结果:对静态位置的距离计算结果进行缓存
6. 公共方法的集成与使用
6.1 创建公共工具类
建议将以上方法整合到一个工具类中,例如创建app/common/util/CommonUtil.php:
php复制<?php
namespace app\common\util;
class CommonUtil
{
// 整合前面实现的所有方法
public static function writeLog($content, $level = 'info', $file = '') {...}
public static function throwException($message, $code = 0, $logFile = 'exception') {...}
public static function imageUrl($path, $default = '') {...}
public static function getDistance($lat1, $lng1, $lat2, $lng2) {...}
}
6.2 在控制器中使用
php复制use app\common\util\CommonUtil;
class UserController extends Controller
{
public function login()
{
try {
// 记录日志
CommonUtil::writeLog('用户登录请求', 'info', 'user');
// 业务逻辑...
// 生成图片URL
$avatar = CommonUtil::imageUrl($user['avatar']);
return json(['avatar' => $avatar]);
} catch (\Exception $e) {
CommonUtil::writeLog($e->getMessage(), 'error');
return json(['error' => $e->getMessage()]);
}
}
}
6.3 在模型中使用
php复制use app\common\util\CommonUtil;
class ShopModel extends Model
{
public function getDistanceFromUser($userId, $shopId)
{
$user = UserModel::get($userId);
$shop = $this->where('id', $shopId)->find();
return CommonUtil::getDistance(
$user['latitude'],
$user['longitude'],
$shop['latitude'],
$shop['longitude']
);
}
}
7. 性能优化与注意事项
7.1 日志记录性能优化
- 异步写入:使用Swoole的协程或Redis队列实现异步日志
- 缓冲写入:积累一定量日志后批量写入
- 日志分级存储:不同级别日志存储到不同文件
- 定期归档:设置日志归档策略避免单个文件过大
7.2 异常处理注意事项
- 异常信息安全:生产环境隐藏敏感错误信息
- 异常代码规范:制定统一的异常代码规范
- 异常监控:集成Sentry等异常监控系统
- 友好提示:前端展示友好的错误提示
7.3 图片URL处理建议
- 域名配置:支持多域名自动切换
- HTTPS兼容:自动识别协议头
- 版本控制:支持URL带版本号避免缓存
- 图片处理:集成缩略图、水印等参数
7.4 距离计算精度问题
- 坐标系统一:确保所有坐标使用同一坐标系
- 地球曲率:长距离计算考虑地球曲率影响
- 性能考量:大量计算时考虑使用存储过程
- 近似算法:对精度要求不高时使用简化算法
在实际项目开发中,这些公共方法可以显著提高开发效率,保证代码一致性。根据具体业务需求,你可以进一步扩展这些方法,比如添加更多的日志处理选项、支持更多的异常类型、集成云存储服务等。
