Swoft AOP 记录用户操作日志

在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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!