在Swoft框架中,使用AOP配合自定義註解可以方便的實現使用者操作的監控。
首先建立資料庫表和實體
在資料庫中建立一張t_log表,用於儲存使用者的操作日誌,資料庫採用mysql 5.7:
DROP TABLE IF EXISTS `t_log`;
CREATE TABLE `t_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日誌ID',
`username` varchar(50) DEFAULT NULL COMMENT '操作使用者',
`operation` text COMMENT '操作內容',
`time` decimal(11,0) DEFAULT NULL COMMENT '耗時',
`method` text COMMENT '操作方法',
`params` text COMMENT '方法引數',
`ip` varchar(64) DEFAULT NULL COMMENT '操作者IP',
`location` varchar(50) DEFAULT NULL COMMENT '操作地點',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1839 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
庫表對應的實體:
php bin/swoft entity:create t_log
自定義註解
定義一個方法級別的@Log註解,用於標註需要監控的方法:
namespace App\Annotation\Mapping;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* @since 2.0
*
* @Annotation
*
* @Target("METHOD")
*/
class Log
{
/**
* @var string
*/
private $value = '';
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
/**
* @param string $value
*/
public function setValue(string $value)
{
$this->value = $value;
}
public function __construct($value)
{
if (isset($value['value'])) {
$this->value = $value['value'];
}
}
}
定義解析器
namespace App\Annotation\Parser;
use App\Annotation\Mapping\Log;
use App\Exception\ApiException;
use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
use Swoft\Annotation\Annotation\Parser\Parser;
/**
* @since 2.0
*
* @AnnotationParser(Log::class)
*/
class LogParser extends Parser
{
/**
* @param int $type
* @param Log $annotationObject
*
* @return array
* @throws ApiException
*/
public function parse(int $type, $annotationObject): array
{
if ($type != self::TYPE_METHOD){
return [];
}
LogRegister::registerLogs($this->className, $this->methodName, $annotationObject);
return [];
}
}
定義暫存器
namespace App\Annotation\Parser;
use App\Annotation\Mapping\Log;
use App\Exception\ApiException;
/**
* @since 2.0
*/
class LogRegister
{
private static $logs = [];
/**
* @param string $className
* @param string $method
* @param Log $log
* @throws ApiException
*/
public static function registerLogs(
string $className,
string $method,
Log $log
) {
if (isset(self::$logs[$className][$method])) {
throw new ApiException(
sprintf('`@log` must be only one on method(%s->%s)!', $className, $method)
);
}
self::$logs[$className][$method] = [
'value' => $log->getValue(),
];
}
/**
* @param string $className
* @param string $method
*
* @return array
*/
public static function getLogs(string $className, string $method): array
{
return self::$logs[$className][$method];
}
}
切面和切點
定義一個LogAspect類,使用@Aspect標註讓其成為一個切面,切點為使用@Log註解標註的方法,使用@Around環繞通知:
namespace App\Aspect;
use App\Annotation\Parser\LogRegister;
use App\Exception\ApiException;
use App\Model\Entity\TLog;
use Swoft\Aop\Annotation\Mapping\Around;
use Swoft\Aop\Annotation\Mapping\Aspect;
use Swoft\Aop\Annotation\Mapping\Before;
use Swoft\Aop\Annotation\Mapping\PointAnnotation;
use App\Annotation\Mapping\Log;
use Swoft\Aop\Point\ProceedingJoinPoint;
use Swoft\Aop\Proxy;
use Swoft\Context\Context;
/**
* @Aspect(order=1)
* @PointAnnotation(
* include={Log::class}
* )
*/
class LogAspect
{
protected $start;
/**
* @Before()
*/
public function before()
{
$this->start = milliseconds();
}
/**
* @Around()
*
* @param ProceedingJoinPoint $joinPoint
*
* @return mixed
* @throws ApiException
*/
public function around(ProceedingJoinPoint $joinPoint)
{
// 執行方法
$result = $joinPoint->proceed();
// $args = $joinPoint->getArgs();
$target = $joinPoint->getTarget();
$method = $joinPoint->getMethod();
$className = get_class($target);
$className = Proxy::getOriginalClassName($className);
$value = LogRegister::getLogs($className, $method);
$request = Context::get()->getRequest();
$remoteAddress = $request->getServerParams()["remote_addr"];
// 執行時長(毫秒)
$time = milliseconds() - $this->start;
// 儲存日誌
$log = new TLog();
$log->setUsername('test');
$log->setOperation($value['value']);
$log->setIp($remoteAddress);
$log->setLocation('');
$log->setMethod($className . '::' . $method . '()');
$log->setTime($time);
$log->setParams(json_encode($request->input()));
$log->save();
return $result;
}
}
測試
namespace App\Http\Controller;
use App\Annotation\Mapping\Log;
use App\Exception\ApiException;
use Swoft\Co;
use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
/**
* @Controller(prefix="/test")
*/
class TestController
{
/**
* @Log("方法一")
* @RequestMapping(route="one")
* @return string
*/
public function methodOne(){
return 'one';
}
/**
* @Log(value="方法二")
* @RequestMapping(route="two")
* @return string
* @throws ApiException
*/
public function methodTwo(){
Co::sleep(0.2);
return 'two';
}
}
啟動專案,分別訪問:
http://localhost:18306/test/one?type=test1
http://localhost:18306/test/two
示例git地址:https://github.com/nilocsu/swoft-aop-log.g...
本作品採用《CC 協議》,轉載必須註明作者和本文連結