feat(fsgx): 完成全部24项开发任务 Phase1-7

Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods

Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口

Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则

Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)

Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口

Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单

Made-with: Cursor
This commit is contained in:
apple
2026-03-23 22:32:19 +08:00
parent 788ee0c0c0
commit 434aa8c69d
13098 changed files with 2008990 additions and 961 deletions

View File

@@ -0,0 +1,13 @@
<?php
namespace think\annotation;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Inject
{
public function __construct(public ?string $abstract = null)
{
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace think\annotation;
use ReflectionObject;
use think\App;
/**
* Trait InteractsWithInject
* @package think\annotation\traits
* @property App $app
*/
trait InteractsWithInject
{
protected function autoInject()
{
if ($this->app->config->get('annotation.inject.enable', true)) {
$this->app->resolving(function ($object, $app) {
if ($this->isInjectClass(get_class($object))) {
$refObject = new ReflectionObject($object);
foreach ($refObject->getProperties() as $refProperty) {
if ($refProperty->isDefault() && !$refProperty->isStatic()) {
$attrs = $refProperty->getAttributes(Inject::class);
if (!empty($attrs)) {
if (!empty($attrs[0]->getArguments()[0])) {
$type = $attrs[0]->getArguments()[0];
} elseif ($refProperty->getType() && !$refProperty->getType()->isBuiltin()) {
$type = $refProperty->getType()->getName();
}
if (isset($type)) {
$value = $app->make($type);
if (!$refProperty->isPublic()) {
$refProperty->setAccessible(true);
}
$refProperty->setValue($object, $value);
}
}
}
}
if ($refObject->hasMethod('__injected')) {
$app->invokeMethod([$object, '__injected']);
}
}
});
}
}
protected function isInjectClass($name)
{
$namespaces = ['app\\'] + $this->app->config->get('annotation.inject.namespaces', []);
foreach ($namespaces as $namespace) {
$namespace = rtrim($namespace, '\\') . '\\';
if (0 === stripos(rtrim($name, '\\') . '\\', $namespace)) {
return true;
}
}
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace think\annotation;
use ReflectionClass;
use ReflectionMethod;
use think\annotation\model\Relation;
use think\annotation\model\relation\BelongsTo;
use think\annotation\model\relation\BelongsToMany;
use think\annotation\model\relation\HasMany;
use think\annotation\model\relation\HasManyThrough;
use think\annotation\model\relation\HasOne;
use think\annotation\model\relation\HasOneThrough;
use think\annotation\model\relation\MorphByMany;
use think\annotation\model\relation\MorphMany;
use think\annotation\model\relation\MorphOne;
use think\annotation\model\relation\MorphTo;
use think\annotation\model\relation\MorphToMany;
use think\App;
use think\helper\Str;
use think\ide\ModelGenerator;
use think\Model;
use think\model\Collection;
/**
* Trait InteractsWithModel
* @package think\annotation
*
* @property App $app
* @mixin Model
*/
trait InteractsWithModel
{
protected array $detected = [];
protected function detectModelAnnotations()
{
if ($this->app->config->get('annotation.model.enable', true)) {
Model::maker(function (Model $model) {
$className = get_class($model);
if (!isset($this->detected[$className])) {
$annotations = $this->reader->getAnnotations(new ReflectionClass($model), Relation::class);
foreach ($annotations as $annotation) {
$relation = function () use ($annotation) {
$refMethod = new ReflectionMethod($this, Str::camel(class_basename($annotation)));
$args = [];
foreach ($refMethod->getParameters() as $param) {
$args[] = $annotation->{$param->getName()};
}
return $refMethod->invokeArgs($this, $args);
};
call_user_func([$model, 'macro'], $annotation->name, $relation);
}
$this->detected[$className] = true;
}
});
$this->app->event->listen(ModelGenerator::class, function (ModelGenerator $generator) {
$annotations = $this->reader->getAnnotations($generator->getReflection(), Relation::class);
foreach ($annotations as $annotation) {
$property = Str::snake($annotation->name);
switch (true) {
case $annotation instanceof HasOne:
$generator->addMethod($annotation->name, \think\model\relation\HasOne::class, [], '');
$generator->addProperty($property, $annotation->model, true);
break;
case $annotation instanceof BelongsTo:
$generator->addMethod($annotation->name, \think\model\relation\BelongsTo::class, [], '');
$generator->addProperty($property, $annotation->model, true);
break;
case $annotation instanceof HasMany:
$generator->addMethod($annotation->name, \think\model\relation\HasMany::class, [], '');
$generator->addProperty($property, $annotation->model . '[]', true);
break;
case $annotation instanceof HasManyThrough:
$generator->addMethod($annotation->name, \think\model\relation\HasManyThrough::class, [], '');
$generator->addProperty($property, $annotation->model . '[]', true);
break;
case $annotation instanceof HasOneThrough:
$generator->addMethod($annotation->name, \think\model\relation\HasOneThrough::class, [], '');
$generator->addProperty($property, $annotation->model, true);
break;
case $annotation instanceof BelongsToMany:
$generator->addMethod($annotation->name, \think\model\relation\BelongsToMany::class, [], '');
$generator->addProperty($property, $annotation->model . '[]', true);
break;
case $annotation instanceof MorphOne:
$generator->addMethod($annotation->name, \think\model\relation\MorphOne::class, [], '');
$generator->addProperty($property, $annotation->model, true);
break;
case $annotation instanceof MorphMany:
$generator->addMethod($annotation->name, \think\model\relation\MorphMany::class, [], '');
$generator->addProperty($property, 'mixed', true);
break;
case $annotation instanceof MorphTo:
$generator->addMethod($annotation->name, \think\model\relation\MorphTo::class, [], '');
$generator->addProperty($property, 'mixed', true);
break;
case $annotation instanceof MorphToMany:
case $annotation instanceof MorphByMany:
$generator->addMethod($annotation->name, \think\model\relation\MorphToMany::class, [], '');
$generator->addProperty($property, Collection::class, true);
break;
}
}
});
}
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace think\annotation;
use Ergebnis\Classy\Constructs;
use ReflectionClass;
use ReflectionMethod;
use think\annotation\route\Group;
use think\annotation\route\Middleware;
use think\annotation\route\Model;
use think\annotation\route\Pattern;
use think\annotation\route\Resource;
use think\annotation\route\Route;
use think\annotation\route\Validate;
use think\App;
use think\event\RouteLoaded;
use think\helper\Arr;
use think\helper\Str;
/**
* Trait InteractsWithRoute
* @package think\annotation\traits
* @property App $app
*/
trait InteractsWithRoute
{
/**
* @var \think\Route
*/
protected $route;
protected $parsedClass = [];
protected $controllerDir;
protected $controllerSuffix;
protected function registerAnnotationRoute()
{
if ($this->app->config->get('annotation.route.enable', true)) {
$this->app->event->listen(RouteLoaded::class, function () {
$this->route = $this->app->route;
$this->controllerDir = realpath($this->app->getAppPath() . $this->app->config->get('route.controller_layer'));
$this->controllerSuffix = $this->app->config->get('route.controller_suffix') ? 'Controller' : '';
$dirs = array_merge(
$this->app->config->get('annotation.route.controllers', []),
[$this->controllerDir]
);
foreach ($dirs as $dir => $options) {
if (is_numeric($dir)) {
$dir = $options;
$options = [];
}
if (is_dir($dir)) {
$this->scanDir($dir, $options);
}
}
});
}
}
protected function scanDir($dir, $options = [])
{
$groups = [];
foreach (Constructs::fromDirectory($dir) as $construct) {
$class = $construct->name();
if (in_array($class, $this->parsedClass)) {
continue;
}
$this->parsedClass[] = $class;
$refClass = new ReflectionClass($class);
if ($refClass->isAbstract() || $refClass->isInterface() || $refClass->isTrait()) {
continue;
}
$filename = $construct->fileNames()[0];
$prefix = $class;
if (Str::startsWith($filename, $this->controllerDir)) {
//控制器
$filename = Str::substr($filename, strlen($this->controllerDir) + 1);
$prefix = str_replace($this->controllerSuffix . '.php', '', str_replace('/', '.', $filename));
}
$routes = [];
//方法
foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) {
if ($routeAnn = $this->reader->getAnnotation($refMethod, Route::class)) {
$routes[] = function () use ($routeAnn, $prefix, $refMethod) {
//注册路由
$rule = $this->route->rule($routeAnn->rule, "{$prefix}/{$refMethod->getName()}", $routeAnn->method);
$rule->option($routeAnn->options);
//变量规则
if (!empty($patternsAnn = $this->reader->getAnnotations($refMethod, Pattern::class))) {
foreach ($patternsAnn as $patternAnn) {
$rule->pattern([$patternAnn->name => $patternAnn->value]);
}
}
//中间件
if (!empty($middlewaresAnn = $this->reader->getAnnotations($refMethod, Middleware::class))) {
foreach ($middlewaresAnn as $middlewareAnn) {
$rule->middleware($middlewareAnn->value, ...$middlewareAnn->params);
}
}
//绑定模型,支持多个
if (!empty($modelsAnn = $this->reader->getAnnotations($refMethod, Model::class))) {
foreach ($modelsAnn as $modelAnn) {
$rule->model($modelAnn->var, $modelAnn->value, $modelAnn->exception);
}
}
//验证
if ($validateAnn = $this->reader->getAnnotation($refMethod, Validate::class)) {
$rule->validate($validateAnn->value, $validateAnn->scene, $validateAnn->message, $validateAnn->batch);
}
};
}
}
$groups[] = function () use ($routes, $refClass, $prefix) {
$groupName = '';
$groupOptions = [];
if ($groupAnn = $this->reader->getAnnotation($refClass, Group::class)) {
$groupName = $groupAnn->name;
$groupOptions = $groupAnn->options;
}
$group = $this->route->group($groupName, function () use ($refClass, $prefix, $routes) {
//注册路由
foreach ($routes as $route) {
$route();
}
if ($resourceAnn = $this->reader->getAnnotation($refClass, Resource::class)) {
//资源路由
$this->route->resource($resourceAnn->rule, $prefix)->option($resourceAnn->options);
}
});
$group->option($groupOptions);
//变量规则
if (!empty($patternsAnn = $this->reader->getAnnotations($refClass, Pattern::class))) {
foreach ($patternsAnn as $patternAnn) {
$group->pattern([$patternAnn->name => $patternAnn->value]);
}
}
//中间件
if (!empty($middlewaresAnn = $this->reader->getAnnotations($refClass, Middleware::class))) {
foreach ($middlewaresAnn as $middlewareAnn) {
$group->middleware($middlewareAnn->value, ...$middlewareAnn->params);
}
}
};
}
if (!empty($groups)) {
$name = Arr::pull($options, 'name', '');
$this->route->group($name, function () use ($groups) {
//注册路由
foreach ($groups as $group) {
$group();
}
})->option($options);
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace think\annotation;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
class Reader
{
/**
* @template T of object
* @param ReflectionClass|ReflectionMethod $ref
* @param class-string<T> $name
* @return array<T>
*/
public function getAnnotations($ref, $name)
{
return array_map(function (ReflectionAttribute $attribute) {
return $attribute->newInstance();
}, $ref->getAttributes($name, ReflectionAttribute::IS_INSTANCEOF));
}
/**
* @template T of object
* @param ReflectionClass|ReflectionMethod $ref
* @param class-string<T> $name
* @return T|null
*/
public function getAnnotation($ref, $name)
{
$attributes = $this->getAnnotations($ref, $name);
foreach ($attributes as $attribute) {
return $attribute;
}
return null;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace think\annotation;
class Service extends \think\Service
{
use InteractsWithInject, InteractsWithRoute, InteractsWithModel;
protected Reader $reader;
public function boot(Reader $reader)
{
$this->reader = $reader;
//自动注入
$this->autoInject();
//注解路由
$this->registerAnnotationRoute();
//模型注解方法提示
$this->detectModelAnnotations();
}
}

View File

@@ -0,0 +1,16 @@
<?php
return [
'inject' => [
'enable' => true,
'namespaces' => [],
],
'route' => [
'enable' => true,
'controllers' => [],
],
'model' => [
'enable' => true,
],
'ignore' => [],
];

View File

@@ -0,0 +1,8 @@
<?php
namespace think\annotation\model;
abstract class Relation
{
}

View File

@@ -0,0 +1,27 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class BelongsTo extends Relation
{
/**
* BELONGS TO 关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $foreignKey 关联外键
* @param string $localKey 关联主键
* @return BelongsTo
*/
public function __construct(
public string $name,
public string $model,
public string $foreignKey = '',
public string $localKey = ''
)
{
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class BelongsToMany extends Relation
{
/**
* BELONGS TO MANY 关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $middle 中间表/模型名
* @param string $foreignKey 关联外键
* @param string $localKey 当前模型关联键
*/
public function __construct(
public string $name,
public string $model,
public string $middle = '',
public string $foreignKey = '',
public string $localKey = ''
)
{
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class HasMany extends Relation
{
/**
* HAS MANY 关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $foreignKey 关联外键
* @param string $localKey 当前主键
*/
public function __construct(
public string $name,
public string $model,
public string $foreignKey = '',
public string $localKey = ''
)
{
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class HasManyThrough extends Relation
{
/**
* HAS MANY 远程关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $through 中间模型名
* @param string $foreignKey 关联外键
* @param string $throughKey 关联外键
* @param string $localKey 当前主键
* @param string $throughPk 中间表主键
*/
public function __construct(
public string $name,
public string $model,
public string $through,
public string $foreignKey = '',
public string $throughKey = '',
public string $localKey = '',
public string $throughPk = ''
)
{
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class HasOne extends Relation
{
/**
* HAS ONE 关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $foreignKey 关联外键
* @param string $localKey 当前主键
*/
public function __construct(
public string $name,
public string $model,
public string $foreignKey = '',
public string $localKey = ''
)
{
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class HasOneThrough extends Relation
{
/**
* HAS ONE 远程关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $through 中间模型名
* @param string $foreignKey 关联外键
* @param string $throughKey 关联外键
* @param string $localKey 当前主键
* @param string $throughPk 中间表主键
*/
public function __construct(
public string $name,
public string $model,
public string $through,
public string $foreignKey = '',
public string $throughKey = '',
public string $localKey = '',
public string $throughPk = ''
)
{
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class MorphByMany extends Relation
{
/**
* MORPH BY MANY关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $middle 中间表名/模型名
* @param string|array $morph 多态字段信息
* @param ?string $foreignKey 关联外键
*/
public function __construct(
public string $name,
public string $model,
public string $middle,
public $morph = null,
public ?string $foreignKey = null
)
{
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class MorphMany extends Relation
{
/**
* MORPH MANY 关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string|array $morph 多态字段信息
* @param string $type 多态类型
*/
public function __construct(
public string $name,
public string $model,
public $morph = null,
public string $type = ''
)
{
$this->morph = $morph ?? $name;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class MorphOne extends Relation
{
/**
* MORPH One 关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string|array $morph 多态字段信息
* @param string $type 多态类型
*/
public function __construct(
public string $name,
public string $model,
public $morph = null,
public string $type = ''
)
{
$this->morph = $morph ?? $name;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class MorphTo extends Relation
{
/**
* MORPH TO 关联定义
* @param string $name 关联名
* @param null $morph 多态字段信息
* @param array $alias 多态别名定义
*/
public function __construct(
public string $name,
public $morph = null,
public array $alias = []
)
{
$this->morph = $morph ?? $name;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace think\annotation\model\relation;
use Attribute;
use think\annotation\model\Relation;
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class MorphToMany extends Relation
{
/**
* MORPH TO MANY关联定义
* @param string $name 关联名
* @param string $model 模型名
* @param string $middle 中间表名/模型名
* @param null $morph 多态字段信息
* @param ?string $localKey 当前模型关联键
*/
public function __construct(
public string $name,
public string $model,
public string $middle,
public $morph = null,
public ?string $localKey = null
)
{
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Delete extends Route
{
public function __construct(
public string $rule,
public array $options = []
)
{
parent::__construct('DELETE', $rule, $options);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Get extends Route
{
public function __construct(
public string $rule,
public array $options = []
)
{
parent::__construct('GET', $rule, $options);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
final class Group
{
public function __construct(public string $name, public array $options = [])
{
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Middleware
{
public array $params;
public function __construct(public $value, ...$params)
{
$this->params = $params;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class Model
{
public function __construct(public string $value, public string $var = 'id', public bool $exception = true)
{
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Options extends Route
{
public function __construct(
public string $rule,
public array $options = []
)
{
parent::__construct('OPTIONS', $rule, $options);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Patch extends Route
{
public function __construct(
public string $rule,
public array $options = []
)
{
parent::__construct('PATCH', $rule, $options);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Pattern
{
public function __construct(public string $name, public string $value)
{
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Post extends Route
{
public function __construct(
public string $rule,
public array $options = []
)
{
parent::__construct('POST', $rule, $options);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Put extends Route
{
public function __construct(
public string $rule,
public array $options = []
)
{
parent::__construct('PUT', $rule, $options);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
final class Resource
{
public function __construct(public string $rule, public array $options = [])
{
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace think\annotation\route;
use Attribute;
use JetBrains\PhpStorm\ExpectedValues;
#[Attribute(Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
#[ExpectedValues(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', '*'])]
public string $method,
public string $rule,
public array $options = []
)
{
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace think\annotation\route;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
final class Validate
{
public function __construct(
public mixed $value,
public array $message = [],
public bool $batch = true,
public ?string $scene = null
)
{
}
}