mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
feat: 新增MCP工具集合,包含用户、财务类业务接口
本次提交新增12个MCP工具,覆盖用户管理与财务统计场景: - 用户类:用户标签列表、标签创建/删除/设置、分组设置、会员等级/时长赠送、用户资料/备注修改 - 财务类:充值记录/统计、余额流水、佣金流水、提现申请/统计、发票记录 所有工具均实现完整的入参校验、业务逻辑与脱敏处理,符合管理后台同链路操作逻辑。
This commit is contained in:
parent
3b5bd37bfb
commit
a30565b3a0
412
backend/modules/service/mcp/tools/CouponCreateTool.php
Normal file
412
backend/modules/service/mcp/tools/CouponCreateTool.php
Normal file
@ -0,0 +1,412 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\product\CenterProductDao;
|
||||
use app\dao\product\product\StoreCategoryDao;
|
||||
use app\services\activity\coupon\StoreCouponIssueServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:添加优惠券(创建发行配置)。
|
||||
*
|
||||
* 与管理后台「添加优惠券」同链路:组装与 StoreCouponIssueController::saveCoupon
|
||||
* 一致的数据结构后调用 StoreCouponIssueServices::saveCoupon;参数预检在工具层
|
||||
* 完成(McpException 消息可透传),服务层业务校验不因 AI 调用而豁免。
|
||||
*/
|
||||
class CouponCreateTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 优惠券发行服务
|
||||
* @var StoreCouponIssueServices
|
||||
*/
|
||||
private StoreCouponIssueServices $services;
|
||||
|
||||
/**
|
||||
* 商品分类 DAO(品类券 ID 存在性校验)
|
||||
* @var StoreCategoryDao
|
||||
*/
|
||||
private StoreCategoryDao $categoryDao;
|
||||
|
||||
/**
|
||||
* 中台商品 DAO(商品券 ID 存在性校验)
|
||||
* @var CenterProductDao
|
||||
*/
|
||||
private CenterProductDao $productDao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param StoreCouponIssueServices $services 优惠券发行服务
|
||||
* @param StoreCategoryDao $categoryDao 商品分类 DAO
|
||||
* @param CenterProductDao $productDao 中台商品 DAO
|
||||
*/
|
||||
public function __construct(
|
||||
StoreCouponIssueServices $services,
|
||||
StoreCategoryDao $categoryDao,
|
||||
CenterProductDao $productDao
|
||||
) {
|
||||
$this->services = $services;
|
||||
$this->categoryDao = $categoryDao;
|
||||
$this->productDao = $productDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_coupon_create';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:创建优惠券会新增营销配置并可能被用户领取,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '创建一张新的优惠券发行配置,等同管理后台「添加优惠券」,成功返回新建的 coupon_id;'
|
||||
. '不能编辑或删除已有优惠券,创建后的停用、开启、删除需在管理后台操作。'
|
||||
. '必填参数:coupon_title 优惠券名称(18 字以内);coupon_price 面值(元,必须大于 0);'
|
||||
. 'use_min_price 使用门槛(元,0=无门槛,默认 0)。'
|
||||
. 'type 优惠券类型:0=通用券(默认)、1=品类券(必须传 category_id 品类 ID 数组)、2=商品券(必须传 product_id 商品 ID 数组);'
|
||||
. '品类/商品 ID 必须真实存在(工具会校验),创建前先通过 crmeb_category_list 查品类、crmeb_product_list 查商品获取有效 ID。'
|
||||
. '有效期二选一:coupon_time 领取后有效天数(正整数,默认 0),coupon_time=0 时必须传 start_use_time 与 end_use_time 时间段'
|
||||
. '(格式 YYYY-MM-DD HH:mm:ss,开始时间不能早于今天且不能早于领取开始时间,结束时间不能早于开始时间;coupon_time 大于 0 时忽略时间段参数)。'
|
||||
. 'user_type 适用用户:1=普通用户(默认)、2=付费会员专享(此时 receive_type 参数被忽略,服务端固定为会员领取)。'
|
||||
. 'receive_type 发送方式:1=用户主动领取(默认)、3=系统赠送(固定不限量,忽略发布数量设置)。'
|
||||
. '发布数量:is_permanent=1 不限量(默认);is_permanent=0 限量时 total_count 发布张数(≥1)必须 ≥ receive_limit 每人限领张数(默认 1)。'
|
||||
. '领取时间可选:start_time 与 end_time 必须成对提供(YYYY-MM-DD HH:mm:ss,开始时间不能早于今天,结束时间不能早于开始时间),不传表示不限时;'
|
||||
. '使用有效期开始时间不能早于领取开始时间,若两者同日请将使用开始时间设置为不早于领取开始时刻。'
|
||||
. 'sort 排序值(默认 0);status:1=开启(默认)、0=关闭。'
|
||||
. '创建后可通过 crmeb_coupon_list 查询配置,用户领取记录见 crmeb_coupon_claim_list。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '优惠券创建';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '营销';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['coupon_title', 'coupon_price'],
|
||||
'properties' => [
|
||||
'coupon_title' => ['type' => 'string', 'maxLength' => 18, 'description' => '优惠券名称,18 字以内'],
|
||||
'coupon_price' => ['type' => 'number', 'exclusiveMinimum' => 0, 'description' => '优惠券面值(元),必须大于 0'],
|
||||
'use_min_price' => ['type' => 'number', 'minimum' => 0, 'description' => '使用门槛(最低消费金额,元),0=无门槛,默认 0'],
|
||||
'type' => ['type' => 'integer', 'enum' => [0, 1, 2], 'description' => '0=通用券(默认),1=品类券,2=商品券'],
|
||||
'category_id' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => '品类券适用的品类 ID 数组(任意层级均可),type=1 时必填,ID 需真实存在,可先通过 crmeb_category_list 查询'],
|
||||
'product_id' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => '商品券适用的中台商品 ID 数组,type=2 时必填,ID 需真实存在,可先通过 crmeb_product_list 查询'],
|
||||
'coupon_time' => ['type' => 'integer', 'minimum' => 0, 'description' => '领取后有效天数;0=改用 start_use_time/end_use_time 时间段(默认 0),大于 0 时忽略时间段'],
|
||||
'start_use_time' => ['type' => 'string', 'description' => '使用有效期开始时间,YYYY-MM-DD HH:mm:ss,coupon_time=0 时必填,不能早于今天及领取开始时间'],
|
||||
'end_use_time' => ['type' => 'string', 'description' => '使用有效期结束时间,YYYY-MM-DD HH:mm:ss,coupon_time=0 时必填,不能早于开始时间'],
|
||||
'user_type' => ['type' => 'integer', 'enum' => [1, 2], 'description' => '1=普通用户可领(默认),2=付费会员专享'],
|
||||
'receive_type' => ['type' => 'integer', 'enum' => [1, 3], 'description' => '1=用户主动领取(默认),3=系统赠送;user_type=2 时被忽略'],
|
||||
'is_permanent' => ['type' => 'integer', 'enum' => [0, 1], 'description' => '1=不限量(默认),0=限量发布'],
|
||||
'total_count' => ['type' => 'integer', 'minimum' => 1, 'description' => '发布数量(张),is_permanent=0 时必须 ≥ receive_limit,默认 1'],
|
||||
'receive_limit' => ['type' => 'integer', 'minimum' => 1, 'description' => '每人限领数量(张),默认 1'],
|
||||
'start_time' => ['type' => 'string', 'description' => '领取开始时间,YYYY-MM-DD HH:mm:ss,与 end_time 成对提供,不能早于今天'],
|
||||
'end_time' => ['type' => 'string', 'description' => '领取结束时间,YYYY-MM-DD HH:mm:ss,与 start_time 成对提供,不能早于领取开始时间'],
|
||||
'sort' => ['type' => 'integer', 'description' => '排序值,默认 0'],
|
||||
'status' => ['type' => 'integer', 'enum' => [0, 1], 'description' => '1=开启(默认),0=关闭'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 优惠券配置参数
|
||||
* @return array 新建优惠券 ID 与生效配置摘要
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$title = trim((string)($args['coupon_title'] ?? ''));
|
||||
if ($title === '') {
|
||||
throw new McpException('coupon_title 不能为空');
|
||||
}
|
||||
if (mb_strlen($title) > 18) {
|
||||
throw new McpException('coupon_title 不能超过 18 个字');
|
||||
}
|
||||
$couponPrice = self::numberArg($args, 'coupon_price', 0.0);
|
||||
if ($couponPrice <= 0) {
|
||||
throw new McpException('coupon_price 必须大于 0');
|
||||
}
|
||||
$useMinPrice = self::numberArg($args, 'use_min_price', 0.0);
|
||||
if ($useMinPrice < 0) {
|
||||
throw new McpException('use_min_price 不能小于 0');
|
||||
}
|
||||
|
||||
$type = self::integerArg($args, 'type', 0, 0, 2);
|
||||
$categoryId = [];
|
||||
$productId = [];
|
||||
if ($type === 1) {
|
||||
$categoryId = self::idArrayArg($args, 'category_id');
|
||||
if (!$categoryId) {
|
||||
throw new McpException('品类券(type=1)必须提供 category_id 品类 ID 数组');
|
||||
}
|
||||
$this->validateCategoryIds($categoryId);
|
||||
} elseif ($type === 2) {
|
||||
$productId = self::idArrayArg($args, 'product_id');
|
||||
if (!$productId) {
|
||||
throw new McpException('商品券(type=2)必须提供 product_id 商品 ID 数组');
|
||||
}
|
||||
$this->validateProductIds($productId);
|
||||
}
|
||||
|
||||
// 领取时间可选,成对提供;user_type=2 时服务层强制会员领取,这里对齐后台传值
|
||||
$userType = self::integerArg($args, 'user_type', 1, 1, 2);
|
||||
$receiveType = $userType === 2 ? 1 : self::integerArg($args, 'receive_type', 1, 1, 3);
|
||||
$startTime = self::dateTimeArg($args, 'start_time');
|
||||
$endTime = self::dateTimeArg($args, 'end_time');
|
||||
if (($startTime === '') !== ($endTime === '')) {
|
||||
throw new McpException('start_time 与 end_time 必须同时提供');
|
||||
}
|
||||
if ($startTime !== '') {
|
||||
if (strtotime($startTime) < strtotime(date('Y-m-d 00:00:00'))) {
|
||||
throw new McpException('start_time 领取开始时间不能早于当前时间');
|
||||
}
|
||||
if (strtotime($endTime) < strtotime($startTime)) {
|
||||
throw new McpException('end_time 领取结束时间不能早于领取开始时间');
|
||||
}
|
||||
}
|
||||
|
||||
// 使用有效期:整天数模式忽略时间段;时间段模式校验成对、不早于今天与先后顺序
|
||||
$couponTime = self::integerArg($args, 'coupon_time', 0, 0);
|
||||
$startUseTime = $endUseTime = '';
|
||||
if ($couponTime === 0) {
|
||||
$startUseTime = self::dateTimeArg($args, 'start_use_time');
|
||||
$endUseTime = self::dateTimeArg($args, 'end_use_time');
|
||||
if ($startUseTime === '' || $endUseTime === '') {
|
||||
throw new McpException('coupon_time 为 0 时必须提供 start_use_time 与 end_use_time(YYYY-MM-DD HH:mm:ss)');
|
||||
}
|
||||
if (strtotime($startUseTime) < strtotime(date('Y-m-d 00:00:00'))) {
|
||||
throw new McpException('start_use_time 开始使用时间不能早于当前时间');
|
||||
}
|
||||
if (strtotime($endUseTime) < strtotime($startUseTime)) {
|
||||
throw new McpException('end_use_time 结束使用时间不能早于开始使用时间');
|
||||
}
|
||||
// 与服务层规则一致:使用开始不能早于领取开始
|
||||
if ($startTime !== '' && strtotime($startUseTime) < strtotime($startTime)) {
|
||||
throw new McpException('start_use_time 使用开始时间不能早于 start_time 领取开始时间');
|
||||
}
|
||||
}
|
||||
|
||||
// 数量规则:系统赠送固定不限量;限量时每人限领不得大于发布数量
|
||||
$receiveLimit = self::integerArg($args, 'receive_limit', 1, 1);
|
||||
$isPermanent = self::integerArg($args, 'is_permanent', 1, 0, 1);
|
||||
if ($receiveType === 3) {
|
||||
$isPermanent = 1;
|
||||
}
|
||||
$totalCount = self::integerArg($args, 'total_count', 1, 1);
|
||||
if ($isPermanent === 1) {
|
||||
$totalCount = 0;
|
||||
} elseif ($receiveLimit > $totalCount) {
|
||||
throw new McpException('receive_limit 每人限领数量不能大于 total_count 发布数量');
|
||||
}
|
||||
|
||||
$status = self::integerArg($args, 'status', 1, 0, 1);
|
||||
$sort = self::integerArg($args, 'sort', 0);
|
||||
|
||||
// 与 StoreCouponIssueController::saveCoupon 的 postMore 结构保持一致,由服务层完成入库
|
||||
$data = [
|
||||
'id' => 0,
|
||||
'coupon_title' => $title,
|
||||
'coupon_price' => $couponPrice,
|
||||
'use_min_price' => $useMinPrice,
|
||||
'coupon_time' => $couponTime,
|
||||
'start_use_time' => $startUseTime === '' ? 0 : $startUseTime,
|
||||
'end_use_time' => $endUseTime === '' ? 0 : $endUseTime,
|
||||
'start_time' => $startTime === '' ? 0 : $startTime,
|
||||
'end_time' => $endTime === '' ? 0 : $endTime,
|
||||
'receive_type' => $receiveType,
|
||||
'is_permanent' => $isPermanent,
|
||||
'total_count' => $totalCount,
|
||||
'product_id' => $productId ? implode(',', $productId) : '',
|
||||
'category_id' => $categoryId,
|
||||
'type' => $type,
|
||||
'sort' => $sort,
|
||||
'status' => $status,
|
||||
'receive_limit' => $receiveLimit,
|
||||
'user_type' => $userType,
|
||||
];
|
||||
|
||||
try {
|
||||
$couponId = $this->services->saveCoupon($data);
|
||||
} catch (AdminException $e) {
|
||||
// 服务层业务校验失败:转 McpException 让消息透传给 AI 客户端
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'coupon_id' => (int)$couponId,
|
||||
'coupon_title' => $title,
|
||||
'coupon_price' => $couponPrice,
|
||||
'use_min_price' => $useMinPrice,
|
||||
'type' => $type,
|
||||
'receive_type' => $userType === 2 ? 4 : $receiveType,
|
||||
'is_permanent' => $isPermanent,
|
||||
'total_count' => $totalCount,
|
||||
'receive_limit' => $receiveLimit,
|
||||
'status' => $status,
|
||||
'message' => '优惠券创建成功,可通过 crmeb_coupon_list 查询',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @param float $default 默认值
|
||||
* @return float 数值
|
||||
* @throws McpException 非数字
|
||||
*/
|
||||
private static function numberArg(array $args, string $name, float $default): float
|
||||
{
|
||||
$value = $args[$name] ?? $default;
|
||||
if (is_int($value) || is_float($value)) {
|
||||
return (float)$value;
|
||||
}
|
||||
if (is_string($value) && $value !== '' && is_numeric($value)) {
|
||||
return (float)$value;
|
||||
}
|
||||
throw new McpException($name . ' 必须是数字');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @param int $default 默认值
|
||||
* @param int|null $min 最小值
|
||||
* @param int|null $max 最大值
|
||||
* @return int 整数
|
||||
* @throws McpException 非整数或超出范围
|
||||
*/
|
||||
private static function integerArg(array $args, string $name, int $default, ?int $min = null, ?int $max = null): int
|
||||
{
|
||||
$value = $args[$name] ?? $default;
|
||||
if (!is_int($value) && (!is_string($value) || !ctype_digit($value))) {
|
||||
throw new McpException($name . ' 必须是整数');
|
||||
}
|
||||
$options = array_filter(['min_range' => $min, 'max_range' => $max], static fn($v) => $v !== null);
|
||||
$number = filter_var($value, FILTER_VALIDATE_INT, ['options' => $options]);
|
||||
if ($number === false) {
|
||||
throw new McpException($name . ' 取值超出允许范围');
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @return int[] 去重后的正整数 ID 列表
|
||||
* @throws McpException 不是 ID 数组
|
||||
*/
|
||||
private static function idArrayArg(array $args, string $name): array
|
||||
{
|
||||
$value = $args[$name] ?? [];
|
||||
if (is_int($value) || (is_string($value) && $value !== '')) {
|
||||
$value = [$value];
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
throw new McpException($name . ' 必须是 ID 数组');
|
||||
}
|
||||
$ids = [];
|
||||
foreach ($value as $item) {
|
||||
$valid = is_int($item) || (is_string($item) && ctype_digit($item));
|
||||
if (!$valid || (int)$item <= 0) {
|
||||
throw new McpException($name . ' 数组内必须全部是大于 0 的整数 ID');
|
||||
}
|
||||
$ids[] = (int)$item;
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验品类 ID 均真实存在(服务层不校验,防止创建出永远匹配不到商品的无效品类券)
|
||||
* @param int[] $categoryIds 品类 ID 列表
|
||||
* @return void
|
||||
* @throws McpException 存在不存在的品类 ID
|
||||
*/
|
||||
private function validateCategoryIds(array $categoryIds): void
|
||||
{
|
||||
$exist = (array)$this->categoryDao->getColumn([['id', 'in', $categoryIds]], 'id');
|
||||
$missing = array_diff($categoryIds, array_map('intval', $exist));
|
||||
if ($missing) {
|
||||
throw new McpException('category_id 中以下品类 ID 不存在:' . implode('、', $missing) . ',请先通过 crmeb_category_list 查询有效品类 ID');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验商品 ID 均真实存在(服务层不校验,防止创建出无法关联商品的无效商品券)
|
||||
* @param int[] $productIds 商品 ID 列表
|
||||
* @return void
|
||||
* @throws McpException 存在不存在的商品 ID
|
||||
*/
|
||||
private function validateProductIds(array $productIds): void
|
||||
{
|
||||
$exist = (array)$this->productDao->getColumn([['id', 'in', $productIds]], 'id');
|
||||
$missing = array_diff($productIds, array_map('intval', $exist));
|
||||
if ($missing) {
|
||||
throw new McpException('product_id 中以下商品 ID 不存在:' . implode('、', $missing) . ',请先通过 crmeb_product_list 查询有效商品 ID');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @return string 可解析的时间字符串,未传返回空串
|
||||
* @throws McpException 时间格式无效
|
||||
*/
|
||||
private static function dateTimeArg(array $args, string $name): string
|
||||
{
|
||||
$value = $args[$name] ?? '';
|
||||
if ($value === '' || $value === 0 || $value === '0') {
|
||||
return '';
|
||||
}
|
||||
if ((!is_string($value) && !is_int($value)) || strtotime((string)$value) === false) {
|
||||
throw new McpException($name . ' 必须是有效的时间字符串,格式 YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
return (string)$value;
|
||||
}
|
||||
}
|
||||
167
backend/modules/service/mcp/tools/ExtractListTool.php
Normal file
167
backend/modules/service/mcp/tools/ExtractListTool.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserExtractDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\SensitiveData;
|
||||
|
||||
/**
|
||||
* MCP 工具:提现申请列表
|
||||
*
|
||||
* 银行账号、支付宝账号、姓名、微信号等敏感信息脱敏后返回。
|
||||
*/
|
||||
class ExtractListTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 提现 DAO
|
||||
* @var UserExtractDao
|
||||
*/
|
||||
private UserExtractDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserExtractDao $dao 提现 DAO
|
||||
*/
|
||||
public function __construct(UserExtractDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_extract_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询用户佣金提现申请列表,支持按状态、提现方式、用户 UID 筛选;'
|
||||
. 'status:0 审核中、1 已提现、-1 未通过(未通过原因见 fail_msg);'
|
||||
. 'extract_type:bank 银行卡、alipay 支付宝、weixin 微信、balance 转余额;'
|
||||
. 'balance 字段为提现前佣金余额。银行账号、支付宝账号、姓名、微信号已脱敏。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '提现申请列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'page' => ['type' => 'number', 'description' => '页码'],
|
||||
'limit' => ['type' => 'number', 'description' => '每页数量'],
|
||||
'uid' => ['type' => 'number', 'description' => '用户 UID'],
|
||||
'status' => ['type' => 'number', 'description' => '状态:0 审核中、1 已提现、-1 未通过'],
|
||||
'extract_type' => ['type' => 'string', 'description' => '提现方式:bank/alipay/weixin/balance'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(page/limit/uid/status/extract_type)
|
||||
* @return array 提现申请列表和总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$page = max(1, (int)($args['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int)($args['limit'] ?? 10)));
|
||||
|
||||
$where = [];
|
||||
if (!empty($args['uid'])) {
|
||||
$where[] = ['uid', '=', (int)$args['uid']];
|
||||
}
|
||||
if (isset($args['status']) && $args['status'] !== '') {
|
||||
$where[] = ['status', '=', (int)$args['status']];
|
||||
}
|
||||
if (isset($args['extract_type']) && $args['extract_type'] !== '') {
|
||||
$where[] = ['extract_type', '=', (string)$args['extract_type']];
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
->field('id,uid,real_name,extract_type,bank_code,alipay_code,wechat,extract_price,extract_fee,balance,mark,status,fail_msg,add_time')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['real_name'] = self::maskName((string)($item['real_name'] ?? ''));
|
||||
$item['bank_code'] = self::maskAccount((string)($item['bank_code'] ?? ''));
|
||||
$item['alipay_code'] = self::maskAccount((string)($item['alipay_code'] ?? ''));
|
||||
$item['wechat'] = self::maskAccount((string)($item['wechat'] ?? ''));
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
|
||||
/**
|
||||
* 姓名脱敏(仅保留首字符)
|
||||
* @param string $value 姓名
|
||||
* @return string
|
||||
*/
|
||||
private static function maskName(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
return mb_substr($value, 0, 1) . str_repeat('*', max(0, mb_strlen($value) - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号类信息脱敏(保留后 4 位)
|
||||
* @param string $value 账号
|
||||
* @return string
|
||||
*/
|
||||
private static function maskAccount(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
if (mb_strlen($value) <= 4) {
|
||||
return '******';
|
||||
}
|
||||
return str_repeat('*', mb_strlen($value) - 4) . mb_substr($value, -4);
|
||||
}
|
||||
}
|
||||
129
backend/modules/service/mcp/tools/ExtractStatisticsTool.php
Normal file
129
backend/modules/service/mcp/tools/ExtractStatisticsTool.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserExtractDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\ReportPeriod;
|
||||
|
||||
/**
|
||||
* MCP 工具:提现统计
|
||||
*
|
||||
* 按提现申请时间统计各状态笔数与金额。
|
||||
*/
|
||||
class ExtractStatisticsTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 提现 DAO
|
||||
* @var UserExtractDao
|
||||
*/
|
||||
private UserExtractDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserExtractDao $dao 提现 DAO
|
||||
*/
|
||||
public function __construct(UserExtractDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_extract_statistics';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '按提现申请时间(add_time)统计指定时间段内的提现情况:'
|
||||
. '已提现(成功)笔数、金额与手续费合计,审核中笔数与金额,未通过笔数。'
|
||||
. '不按审核/打款时间统计,金额为申请金额。默认近7天,最多31天。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '提现统计';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'start_date' => ['type' => 'string', 'format' => 'date', 'description' => '开始日期 YYYY-MM-DD'],
|
||||
'end_date' => ['type' => 'string', 'format' => 'date', 'description' => '结束日期 YYYY-MM-DD,包含结束日'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(start_date/end_date)
|
||||
* @return array 提现统计结果
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$period = ReportPeriod::fromArgs($args, '提现统计');
|
||||
$range = [$period['start_time'], $period['end_time']];
|
||||
|
||||
$sumFields = ['COUNT(id) AS cnt', 'COALESCE(SUM(extract_price),0) AS amount', 'COALESCE(SUM(extract_fee),0) AS fee'];
|
||||
$success = $this->dao->search([['status', '=', 1]])
|
||||
->whereBetween('add_time', $range)->field($sumFields)->find();
|
||||
$success = $success ? $success->toArray() : [];
|
||||
$auditing = $this->dao->search([['status', '=', 0]])
|
||||
->whereBetween('add_time', $range)->field($sumFields)->find();
|
||||
$auditing = $auditing ? $auditing->toArray() : [];
|
||||
$fail = $this->dao->search([['status', '=', -1]])
|
||||
->whereBetween('add_time', $range)->field(['COUNT(id) AS cnt'])->find();
|
||||
$fail = $fail ? $fail->toArray() : [];
|
||||
|
||||
return [
|
||||
'start_date' => $period['start_date'],
|
||||
'end_date' => $period['end_date'],
|
||||
'metrics' => [
|
||||
'success_count' => (int)($success['cnt'] ?? 0),
|
||||
'success_amount' => (string)($success['amount'] ?? '0.00'),
|
||||
'success_fee' => (string)($success['fee'] ?? '0.00'),
|
||||
'auditing_count' => (int)($auditing['cnt'] ?? 0),
|
||||
'auditing_amount' => (string)($auditing['amount'] ?? '0.00'),
|
||||
'fail_count' => (int)($fail['cnt'] ?? 0),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
207
backend/modules/service/mcp/tools/FinanceOverviewTool.php
Normal file
207
backend/modules/service/mcp/tools/FinanceOverviewTool.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserBrokerageDao;
|
||||
use app\dao\user\UserExtractDao;
|
||||
use app\dao\user\UserMoneyDao;
|
||||
use app\dao\user\UserRechargeDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\ReportPeriod;
|
||||
|
||||
/**
|
||||
* MCP 工具:财务总览统计
|
||||
*
|
||||
* 汇总指定时间段内平台核心资金数据:充值、提现、余额流水、佣金流水。
|
||||
*/
|
||||
class FinanceOverviewTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 充值 DAO
|
||||
* @var UserRechargeDao
|
||||
*/
|
||||
private UserRechargeDao $rechargeDao;
|
||||
|
||||
/**
|
||||
* 提现 DAO
|
||||
* @var UserExtractDao
|
||||
*/
|
||||
private UserExtractDao $extractDao;
|
||||
|
||||
/**
|
||||
* 余额流水 DAO
|
||||
* @var UserMoneyDao
|
||||
*/
|
||||
private UserMoneyDao $moneyDao;
|
||||
|
||||
/**
|
||||
* 佣金流水 DAO
|
||||
* @var UserBrokerageDao
|
||||
*/
|
||||
private UserBrokerageDao $brokerageDao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserRechargeDao $rechargeDao 充值 DAO
|
||||
* @param UserExtractDao $extractDao 提现 DAO
|
||||
* @param UserMoneyDao $moneyDao 余额流水 DAO
|
||||
* @param UserBrokerageDao $brokerageDao 佣金流水 DAO
|
||||
*/
|
||||
public function __construct(
|
||||
UserRechargeDao $rechargeDao,
|
||||
UserExtractDao $extractDao,
|
||||
UserMoneyDao $moneyDao,
|
||||
UserBrokerageDao $brokerageDao
|
||||
) {
|
||||
$this->rechargeDao = $rechargeDao;
|
||||
$this->extractDao = $extractDao;
|
||||
$this->moneyDao = $moneyDao;
|
||||
$this->brokerageDao = $brokerageDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_finance_overview';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '汇总统计指定时间段内平台核心资金数据:'
|
||||
. '充值(仅已支付,按支付时间统计笔数、金额、赠送金额);'
|
||||
. '提现(按申请时间统计已提现/审核中/未通过的笔数与金额及成功手续费);'
|
||||
. '余额流水(按记账时间统计有效记录的收入与支出笔数、金额);'
|
||||
. '佣金流水(按记账时间统计有效记录的收入与支出笔数、金额,收入含冻结中佣金)。'
|
||||
. '不包含积分/经验变动,金额单位为元。默认近7天,最多31天。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '财务总览';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'start_date' => ['type' => 'string', 'format' => 'date', 'description' => '开始日期 YYYY-MM-DD'],
|
||||
'end_date' => ['type' => 'string', 'format' => 'date', 'description' => '结束日期 YYYY-MM-DD,包含结束日'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(start_date/end_date)
|
||||
* @return array 财务总览统计结果
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$period = ReportPeriod::fromArgs($args, '财务总览');
|
||||
$range = [$period['start_time'], $period['end_time']];
|
||||
|
||||
// 充值:仅已支付,按支付时间
|
||||
$recharge = $this->rechargeDao->search([['paid', '=', 1]])
|
||||
->whereBetween('pay_time', $range)
|
||||
->field(['COUNT(id) AS cnt', 'COALESCE(SUM(price),0) AS amount', 'COALESCE(SUM(give_price),0) AS give_amount'])
|
||||
->find();
|
||||
$recharge = $recharge ? $recharge->toArray() : [];
|
||||
|
||||
// 提现:按申请时间,分状态统计
|
||||
$extractFields = ['COUNT(id) AS cnt', 'COALESCE(SUM(extract_price),0) AS amount', 'COALESCE(SUM(extract_fee),0) AS fee'];
|
||||
$extractSuccess = $this->extractDao->search([['status', '=', 1]])
|
||||
->whereBetween('add_time', $range)->field($extractFields)->find();
|
||||
$extractSuccess = $extractSuccess ? $extractSuccess->toArray() : [];
|
||||
$extractAudit = $this->extractDao->search([['status', '=', 0]])
|
||||
->whereBetween('add_time', $range)->field($extractFields)->find();
|
||||
$extractAudit = $extractAudit ? $extractAudit->toArray() : [];
|
||||
$extractFail = $this->extractDao->search([['status', '=', -1]])
|
||||
->whereBetween('add_time', $range)->field(['COUNT(id) AS cnt'])->find();
|
||||
$extractFail = $extractFail ? $extractFail->toArray() : [];
|
||||
|
||||
// 余额流水:仅有效记录,按记账时间统计收/支
|
||||
$flowFields = ['COUNT(id) AS cnt', 'COALESCE(SUM(number),0) AS amount'];
|
||||
$moneyIn = $this->moneyDao->search([['pm', '=', 1], ['status', '=', 1]])
|
||||
->whereBetween('add_time', $range)->field($flowFields)->find();
|
||||
$moneyIn = $moneyIn ? $moneyIn->toArray() : [];
|
||||
$moneyOut = $this->moneyDao->search([['pm', '=', 0], ['status', '=', 1]])
|
||||
->whereBetween('add_time', $range)->field($flowFields)->find();
|
||||
$moneyOut = $moneyOut ? $moneyOut->toArray() : [];
|
||||
|
||||
// 佣金流水:仅有效记录,按记账时间统计收/支(收入含冻结中佣金)
|
||||
$brokerageIn = $this->brokerageDao->search([['pm', '=', 1], ['status', '=', 1]])
|
||||
->whereBetween('add_time', $range)->field($flowFields)->find();
|
||||
$brokerageIn = $brokerageIn ? $brokerageIn->toArray() : [];
|
||||
$brokerageOut = $this->brokerageDao->search([['pm', '=', 0], ['status', '=', 1]])
|
||||
->whereBetween('add_time', $range)->field($flowFields)->find();
|
||||
$brokerageOut = $brokerageOut ? $brokerageOut->toArray() : [];
|
||||
|
||||
return [
|
||||
'start_date' => $period['start_date'],
|
||||
'end_date' => $period['end_date'],
|
||||
'metrics' => [
|
||||
'recharge' => [
|
||||
'count' => (int)($recharge['cnt'] ?? 0),
|
||||
'amount' => (string)($recharge['amount'] ?? '0.00'),
|
||||
'give_amount' => (string)($recharge['give_amount'] ?? '0.00'),
|
||||
],
|
||||
'extract' => [
|
||||
'success_count' => (int)($extractSuccess['cnt'] ?? 0),
|
||||
'success_amount' => (string)($extractSuccess['amount'] ?? '0.00'),
|
||||
'success_fee' => (string)($extractSuccess['fee'] ?? '0.00'),
|
||||
'auditing_count' => (int)($extractAudit['cnt'] ?? 0),
|
||||
'auditing_amount' => (string)($extractAudit['amount'] ?? '0.00'),
|
||||
'fail_count' => (int)($extractFail['cnt'] ?? 0),
|
||||
],
|
||||
'user_money' => [
|
||||
'in_count' => (int)($moneyIn['cnt'] ?? 0),
|
||||
'in_amount' => (string)($moneyIn['amount'] ?? '0.00'),
|
||||
'out_count' => (int)($moneyOut['cnt'] ?? 0),
|
||||
'out_amount' => (string)($moneyOut['amount'] ?? '0.00'),
|
||||
],
|
||||
'user_brokerage' => [
|
||||
'in_count' => (int)($brokerageIn['cnt'] ?? 0),
|
||||
'in_amount' => (string)($brokerageIn['amount'] ?? '0.00'),
|
||||
'out_count' => (int)($brokerageOut['cnt'] ?? 0),
|
||||
'out_amount' => (string)($brokerageOut['amount'] ?? '0.00'),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
136
backend/modules/service/mcp/tools/InvoiceListTool.php
Normal file
136
backend/modules/service/mcp/tools/InvoiceListTool.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\order\OrderInvoiceDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\SensitiveData;
|
||||
|
||||
/**
|
||||
* MCP 工具:发票记录列表
|
||||
*
|
||||
* 查询订单开票记录,开票人手机号脱敏后返回。
|
||||
*/
|
||||
class InvoiceListTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 订单发票 DAO
|
||||
* @var OrderInvoiceDao
|
||||
*/
|
||||
private OrderInvoiceDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param OrderInvoiceDao $dao 订单发票 DAO
|
||||
*/
|
||||
public function __construct(OrderInvoiceDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_invoice_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询订单开票记录列表,支持按开票状态、订单 ID、用户 UID 筛选;'
|
||||
. 'header_type 抬头类型:1 个人、2 企业;type 发票类型:1 普通发票、2 专用发票;'
|
||||
. 'is_pay 是否支付、is_refund 订单是否退款、is_invoice 是否已开票;'
|
||||
. 'invoice_number 为开票票号。开票人手机号与注册电话已脱敏。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '发票记录列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'page' => ['type' => 'number', 'description' => '页码'],
|
||||
'limit' => ['type' => 'number', 'description' => '每页数量'],
|
||||
'uid' => ['type' => 'number', 'description' => '用户 UID'],
|
||||
'order_id' => ['type' => 'number', 'description' => '订单 ID'],
|
||||
'is_invoice' => ['type' => 'number', 'description' => '开票状态:1 已开票、0 未开票'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(page/limit/uid/order_id/is_invoice)
|
||||
* @return array 发票记录列表和总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$page = max(1, (int)($args['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int)($args['limit'] ?? 10)));
|
||||
|
||||
$where = [['is_del', '=', 0]];
|
||||
if (!empty($args['uid'])) {
|
||||
$where[] = ['uid', '=', (int)$args['uid']];
|
||||
}
|
||||
if (!empty($args['order_id'])) {
|
||||
$where[] = ['order_id', '=', (int)$args['order_id']];
|
||||
}
|
||||
if (isset($args['is_invoice']) && $args['is_invoice'] !== '') {
|
||||
$where[] = ['is_invoice', '=', (int)$args['is_invoice'] == 1 ? 1 : 0];
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
->field('id,uid,order_id,header_type,type,name,duty_number,drawer_phone,email,tell,is_pay,is_refund,is_invoice,invoice_number,invoice_time,add_time')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['drawer_phone'] = SensitiveData::phone((string)($item['drawer_phone'] ?? ''));
|
||||
$item['tell'] = SensitiveData::phone((string)($item['tell'] ?? ''));
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
}
|
||||
128
backend/modules/service/mcp/tools/RechargeListTool.php
Normal file
128
backend/modules/service/mcp/tools/RechargeListTool.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserRechargeDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
|
||||
/**
|
||||
* MCP 工具:充值记录列表
|
||||
*
|
||||
* 查询用户余额充值记录。
|
||||
*/
|
||||
class RechargeListTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 充值 DAO
|
||||
* @var UserRechargeDao
|
||||
*/
|
||||
private UserRechargeDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserRechargeDao $dao 充值 DAO
|
||||
*/
|
||||
public function __construct(UserRechargeDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_recharge_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询用户余额充值记录列表,支持按支付状态、充值(支付)方式、用户 UID 筛选,'
|
||||
. '返回充值订单号、充值金额、赠送金额、退款金额与支付时间;'
|
||||
. 'paid:1 已支付、0 未支付;recharge_type 为实际支付渠道:weixin 微信、alipay 支付宝、balance 余额。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '充值记录列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'page' => ['type' => 'number', 'description' => '页码'],
|
||||
'limit' => ['type' => 'number', 'description' => '每页数量'],
|
||||
'uid' => ['type' => 'number', 'description' => '用户 UID'],
|
||||
'paid' => ['type' => 'number', 'description' => '支付状态:1 已支付、0 未支付'],
|
||||
'recharge_type' => ['type' => 'string', 'description' => '充值(支付)方式:weixin/alipay/balance'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(page/limit/uid/paid/recharge_type)
|
||||
* @return array 充值记录列表和总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$page = max(1, (int)($args['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int)($args['limit'] ?? 10)));
|
||||
|
||||
$where = [];
|
||||
if (!empty($args['uid'])) {
|
||||
$where[] = ['uid', '=', (int)$args['uid']];
|
||||
}
|
||||
if (isset($args['paid']) && $args['paid'] !== '') {
|
||||
$where[] = ['paid', '=', (int)$args['paid'] == 1 ? 1 : 0];
|
||||
}
|
||||
if (isset($args['recharge_type']) && $args['recharge_type'] !== '') {
|
||||
$where[] = ['recharge_type', '=', (string)$args['recharge_type']];
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
->field('id,uid,order_id,price,give_price,recharge_type,paid,pay_time,refund_price,channel_type,add_time')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
}
|
||||
129
backend/modules/service/mcp/tools/RechargeStatisticsTool.php
Normal file
129
backend/modules/service/mcp/tools/RechargeStatisticsTool.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserRechargeDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\ReportPeriod;
|
||||
|
||||
/**
|
||||
* MCP 工具:充值统计
|
||||
*
|
||||
* 按支付时间统计已支付充值,与后台财务统计口径一致。
|
||||
*/
|
||||
class RechargeStatisticsTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 充值 DAO
|
||||
* @var UserRechargeDao
|
||||
*/
|
||||
private UserRechargeDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserRechargeDao $dao 充值 DAO
|
||||
*/
|
||||
public function __construct(UserRechargeDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_recharge_statistics';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '按支付时间(pay_time)统计指定时间段内已支付(paid=1)的余额充值:'
|
||||
. '充值笔数、充值金额、充值活动赠送金额,以及该时段内充值订单的累计退款金额。'
|
||||
. '未支付记录不计入。默认近7天,最多31天。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '充值统计';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'start_date' => ['type' => 'string', 'format' => 'date', 'description' => '开始日期 YYYY-MM-DD'],
|
||||
'end_date' => ['type' => 'string', 'format' => 'date', 'description' => '结束日期 YYYY-MM-DD,包含结束日'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(start_date/end_date)
|
||||
* @return array 充值统计结果
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$period = ReportPeriod::fromArgs($args, '充值统计');
|
||||
$range = [$period['start_time'], $period['end_time']];
|
||||
|
||||
$paid = $this->dao->search([['paid', '=', 1]])
|
||||
->whereBetween('pay_time', $range)
|
||||
->field(['COUNT(id) AS cnt', 'COALESCE(SUM(price),0) AS amount', 'COALESCE(SUM(give_price),0) AS give_amount'])
|
||||
->find();
|
||||
$paid = $paid ? $paid->toArray() : [];
|
||||
|
||||
$refunded = $this->dao->search([['paid', '=', 1], ['refund_price', '>', 0]])
|
||||
->whereBetween('pay_time', $range)
|
||||
->field(['COUNT(id) AS cnt', 'COALESCE(SUM(refund_price),0) AS amount'])
|
||||
->find();
|
||||
$refunded = $refunded ? $refunded->toArray() : [];
|
||||
|
||||
return [
|
||||
'start_date' => $period['start_date'],
|
||||
'end_date' => $period['end_date'],
|
||||
'metrics' => [
|
||||
'recharge_count' => (int)($paid['cnt'] ?? 0),
|
||||
'recharge_amount' => (string)($paid['amount'] ?? '0.00'),
|
||||
'give_amount' => (string)($paid['give_amount'] ?? '0.00'),
|
||||
'refund_count' => (int)($refunded['cnt'] ?? 0),
|
||||
'refund_amount' => (string)($refunded['amount'] ?? '0.00'),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
135
backend/modules/service/mcp/tools/UserBrokerageListTool.php
Normal file
135
backend/modules/service/mcp/tools/UserBrokerageListTool.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserBrokerageDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
|
||||
/**
|
||||
* MCP 工具:佣金流水列表
|
||||
*
|
||||
* v8 起分销佣金变动记于独立账单表 user_brokerage。
|
||||
*/
|
||||
class UserBrokerageListTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 佣金流水 DAO
|
||||
* @var UserBrokerageDao
|
||||
*/
|
||||
private UserBrokerageDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserBrokerageDao $dao 佣金流水 DAO
|
||||
*/
|
||||
public function __construct(UserBrokerageDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_brokerage_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询用户分销佣金流水列表(v8 独立佣金账单表),支持按用户 UID、类型、收/支方向、状态筛选,'
|
||||
. '返回变动金额、变动后佣金、冻结到期时间与备注;'
|
||||
. 'type 常见值:self_brokerage 自购订单佣金、one_brokerage/two_brokerage 一/二级推广订单佣金、'
|
||||
. 'brokerage_user 推广用户佣金、extract 佣金提现、extract_fail 提现失败退回、extract_money 提现到余额、'
|
||||
. 'refund 退款退回佣金、pink_master_brokerage 拼团团长佣金;'
|
||||
. 'pm:1 收入、0 支出;status:1 有效、0 待确定、-1 无效,冻结中的佣金以 frozen_time 大于当前时间判定。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '佣金流水列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'page' => ['type' => 'number', 'description' => '页码'],
|
||||
'limit' => ['type' => 'number', 'description' => '每页数量'],
|
||||
'uid' => ['type' => 'number', 'description' => '用户 UID'],
|
||||
'type' => ['type' => 'string', 'description' => '变动类型,如 one_brokerage/extract/refund'],
|
||||
'pm' => ['type' => 'number', 'description' => '收/支方向:1 收入、0 支出'],
|
||||
'status' => ['type' => 'number', 'description' => '状态:1 有效、0 待确定、-1 无效'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(page/limit/uid/type/pm/status)
|
||||
* @return array 佣金流水列表和总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$page = max(1, (int)($args['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int)($args['limit'] ?? 10)));
|
||||
|
||||
$where = [];
|
||||
if (!empty($args['uid'])) {
|
||||
$where[] = ['uid', '=', (int)$args['uid']];
|
||||
}
|
||||
if (isset($args['type']) && $args['type'] !== '') {
|
||||
$where[] = ['type', '=', (string)$args['type']];
|
||||
}
|
||||
if (isset($args['pm']) && $args['pm'] !== '') {
|
||||
$where[] = ['pm', '=', (int)$args['pm'] == 1 ? 1 : 0];
|
||||
}
|
||||
if (isset($args['status']) && $args['status'] !== '') {
|
||||
$where[] = ['status', '=', (int)$args['status']];
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
->field('id,uid,link_id,type,title,number,balance,pm,mark,status,frozen_time,add_time')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
}
|
||||
170
backend/modules/service/mcp/tools/UserEditPayload.php
Normal file
170
backend/modules/service/mcp/tools/UserEditPayload.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelRelationServices;
|
||||
use app\services\user\UserServices;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* 用户编辑数据组装辅助(MCP 内部共享,非工具类)
|
||||
*
|
||||
* UserServices::updateInfo 走基本信息分支时按全字段覆盖写库,并以 label_id
|
||||
* 全量重设用户标签;本类负责先读用户当前值,合并工具显式传入的修改项后
|
||||
* 构造 updateInfo 所需的完整 $data,保证「只改一项」不会清空其他资料,
|
||||
* 与管理后台编辑表单(读出当前值 → 改一项 → 提交)链路一致。
|
||||
*
|
||||
* 文件名不以 Tool.php 结尾,不会被注册中心自动发现,仅供用户编辑类工具复用。
|
||||
*/
|
||||
final class UserEditPayload
|
||||
{
|
||||
/**
|
||||
* 构造 UserServices::updateInfo 所需的全量编辑数据
|
||||
*
|
||||
* @param UserServices $services 用户服务(用于读取用户与手机号唯一性校验)
|
||||
* @param UserLabelRelationServices $labelServices 用户标签关联服务(读取当前标签)
|
||||
* @param int $uid 用户 ID
|
||||
* @param array $overrides 显式要修改的字段(仅支持 real_name/phone/email/card_id/birthday/addres/mark/status/group_id/label_ids)
|
||||
* @return array updateInfo 完整入参
|
||||
* @throws McpException 用户不存在或字段校验失败
|
||||
*/
|
||||
public static function build(UserServices $services, UserLabelRelationServices $labelServices, int $uid, array $overrides): array
|
||||
{
|
||||
$user = $services->getUserInfo($uid);
|
||||
if (!$user) {
|
||||
throw new McpException('用户不存在');
|
||||
}
|
||||
$user = is_object($user) ? $user->toArray() : (array)$user;
|
||||
|
||||
// 真实姓名(对齐后台必填与 eb_user.real_name varchar(25))
|
||||
$realName = trim((string)($overrides['real_name'] ?? $user['real_name'] ?? ''));
|
||||
if ($realName === '') {
|
||||
throw new McpException('real_name 真实姓名不能为空');
|
||||
}
|
||||
if (mb_strlen($realName) > 25) {
|
||||
throw new McpException('real_name 真实姓名不能超过 25 个字');
|
||||
}
|
||||
|
||||
// 手机号:必填、格式与全库唯一(对齐 UserController::update 校验)
|
||||
$phone = trim((string)($overrides['phone'] ?? $user['phone'] ?? ''));
|
||||
if ($phone === '') {
|
||||
throw new McpException('phone 手机号不能为空');
|
||||
}
|
||||
if (!preg_match('/^1[3456789]\d{9}$/', $phone)) {
|
||||
throw new McpException('phone 手机号格式错误');
|
||||
}
|
||||
if ($phone !== (string)($user['phone'] ?? '')
|
||||
&& $services->count(['phone' => $phone, 'is_del' => 0, 'not_uid' => $uid])) {
|
||||
throw new McpException('phone 手机号已被其他用户使用');
|
||||
}
|
||||
|
||||
// 邮箱:可空,填了必须合法
|
||||
$email = trim((string)($overrides['email'] ?? $user['email'] ?? ''));
|
||||
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new McpException('email 邮箱格式错误');
|
||||
}
|
||||
|
||||
// 身份证:可空,填了必须合法(全局辅助函数校验)
|
||||
$cardId = trim((string)($overrides['card_id'] ?? $user['card_id'] ?? ''));
|
||||
if ($cardId !== '' && !check_card($cardId)) {
|
||||
throw new McpException('card_id 身份证号码不正确');
|
||||
}
|
||||
|
||||
// 生日:传日期字符串(YYYY-MM-DD);未传时回填当前值(时间戳 0 回填纪元日,strtotime 后仍近似为空)
|
||||
if (array_key_exists('birthday', $overrides)) {
|
||||
$birthday = trim((string)$overrides['birthday']);
|
||||
if ($birthday !== '' && strtotime($birthday) === false) {
|
||||
throw new McpException('birthday 必须是有效日期,格式 YYYY-MM-DD');
|
||||
}
|
||||
} else {
|
||||
$birthdayTimestamp = (int)($user['birthday'] ?? 0);
|
||||
$birthday = $birthdayTimestamp > 0 ? date('Y-m-d H:i:s', $birthdayTimestamp) : '1970-01-01 08:00:00';
|
||||
}
|
||||
|
||||
// 状态:1=开启,0=锁定
|
||||
$status = (int)($overrides['status'] ?? $user['status'] ?? 1);
|
||||
if (!in_array($status, [0, 1], true)) {
|
||||
throw new McpException('status 只能为 0(锁定)或 1(开启)');
|
||||
}
|
||||
|
||||
// 备注(eb_user.mark varchar(255))
|
||||
$mark = trim((string)($overrides['mark'] ?? $user['mark'] ?? ''));
|
||||
if (mb_strlen($mark) > 255) {
|
||||
throw new McpException('mark 备注不能超过 255 个字');
|
||||
}
|
||||
|
||||
// 详细地址
|
||||
$addres = trim((string)($overrides['addres'] ?? $user['addres'] ?? ''));
|
||||
if (mb_strlen($addres) > 255) {
|
||||
throw new McpException('addres 详细地址不能超过 255 个字');
|
||||
}
|
||||
|
||||
// 分组:0 表示未分组
|
||||
$groupId = max(0, (int)($overrides['group_id'] ?? $user['group_id'] ?? 0));
|
||||
|
||||
// 标签:未显式传入时回填当前标签,避免 updateInfo 以空数组全量清空
|
||||
if (array_key_exists('label_ids', $overrides)) {
|
||||
$labelIds = self::idArray($overrides['label_ids'], 'label_ids');
|
||||
} else {
|
||||
$labelIds = array_map('intval', (array)$labelServices->getUserLabels($uid));
|
||||
}
|
||||
|
||||
// updateInfo 基本信息分支要求的全量结构;等级/余额/积分不在编辑范围,
|
||||
// 保持当前值避免触发分支逻辑,密码固定空串即不修改
|
||||
return [
|
||||
'money_status' => 0,
|
||||
'money' => '0',
|
||||
'integration_status' => 0,
|
||||
'integration' => '0',
|
||||
'is_other' => false,
|
||||
'label_id' => $labelIds,
|
||||
'pwd' => '',
|
||||
'status' => $status,
|
||||
'real_name' => $realName,
|
||||
'card_id' => $cardId,
|
||||
'birthday' => $birthday,
|
||||
'mark' => $mark,
|
||||
'level' => (int)($user['level'] ?? 0),
|
||||
'phone' => $phone,
|
||||
'addres' => $addres,
|
||||
'email' => $email,
|
||||
'group_id' => $groupId,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并规范化正整数 ID 数组(去重)
|
||||
*
|
||||
* @param mixed $value 原始值
|
||||
* @param string $name 参数名称(报错用)
|
||||
* @return int[] ID 列表(允许为空数组)
|
||||
* @throws McpException 不是合法 ID 数组
|
||||
*/
|
||||
public static function idArray(mixed $value, string $name): array
|
||||
{
|
||||
if (is_int($value) || (is_string($value) && $value !== '')) {
|
||||
$value = [$value];
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
throw new McpException($name . ' 必须是 ID 数组');
|
||||
}
|
||||
$ids = [];
|
||||
foreach ($value as $item) {
|
||||
if (!is_int($item) && (!is_string($item) || !ctype_digit($item))) {
|
||||
throw new McpException($name . ' 数组内必须全部是整数 ID');
|
||||
}
|
||||
$ids[] = (int)$item;
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
}
|
||||
141
backend/modules/service/mcp/tools/UserGroupSetTool.php
Normal file
141
backend/modules/service/mcp/tools/UserGroupSetTool.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:设置用户分组
|
||||
*
|
||||
* 与管理后台「设置分组」同链路:调用 UserServices::saveSetGroup 批量
|
||||
* 将用户调整为指定分组;分组存在性与写入结果由服务层校验。
|
||||
*/
|
||||
class UserGroupSetTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户服务
|
||||
* @var UserServices
|
||||
*/
|
||||
private UserServices $services;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserServices $services 用户服务
|
||||
*/
|
||||
public function __construct(UserServices $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_group_set';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:调整用户分组会变更用户数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '把一个或多个用户设置到指定用户分组,等同管理后台「设置分组」,成功返回本次调整的分组与用户数。'
|
||||
. '必填参数:uids 用户ID数组(支持批量);group_id 分组ID(大于 0,必须是已存在的用户分组,分组清单在管理后台「用户 → 用户分组」维护,'
|
||||
. '目前没有查询分组的 MCP 工具,ID 不确定时先向运营确认)。'
|
||||
. '本工具是覆盖式设置:执行后所选用户的分组统一变为 group_id,原分组被替换。'
|
||||
. '不能创建或删除分组,不能把用户移出分组(设为其他分组即是替换);设置用户标签用 crmeb_user_label_set。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '分组设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['uids', 'group_id'],
|
||||
'properties' => [
|
||||
'uids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => '用户ID数组,支持批量设置'],
|
||||
'group_id' => ['type' => 'integer', 'minimum' => 1, 'description' => '目标分组ID,必须是已存在的用户分组'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(uids、group_id)
|
||||
* @return array 本次分组调整结果
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$uids = UserEditPayload::idArray($args['uids'] ?? [], 'uids');
|
||||
$uids = array_values(array_filter($uids, static fn(int $id) => $id > 0));
|
||||
if (!$uids) {
|
||||
throw new McpException('uids 必须是至少一个大于 0 的用户ID数组');
|
||||
}
|
||||
$groupId = (int)($args['group_id'] ?? 0);
|
||||
if ($groupId <= 0) {
|
||||
throw new McpException('group_id 必须是大于 0 的分组ID');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->services->saveSetGroup($uids, $groupId);
|
||||
} catch (AdminException $e) {
|
||||
// 「该分组不存在」「设置分组失败或无改动」等业务错误透传
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'group_id' => $groupId,
|
||||
'uid_count' => count($uids),
|
||||
'uids' => $uids,
|
||||
'message' => '已把 ' . count($uids) . ' 个用户设置到分组 ' . $groupId,
|
||||
];
|
||||
}
|
||||
}
|
||||
182
backend/modules/service/mcp/tools/UserLabelCreateTool.php
Normal file
182
backend/modules/service/mcp/tools/UserLabelCreateTool.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelCateServices;
|
||||
use app\services\user\UserLabelServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:创建用户标签
|
||||
*
|
||||
* 与管理后台「用户标签」同链路:调用 UserLabelServices::save 新建标签;
|
||||
* 参数预检在工具层完成(McpException 消息可透传),服务层业务校验不因 AI 调用而豁免。
|
||||
*/
|
||||
class UserLabelCreateTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户标签服务
|
||||
* @var UserLabelServices
|
||||
*/
|
||||
private UserLabelServices $services;
|
||||
|
||||
/**
|
||||
* 标签分类服务
|
||||
* @var UserLabelCateServices
|
||||
*/
|
||||
private UserLabelCateServices $cateServices;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserLabelServices $services 用户标签服务
|
||||
* @param UserLabelCateServices $cateServices 标签分类服务
|
||||
*/
|
||||
public function __construct(UserLabelServices $services, UserLabelCateServices $cateServices)
|
||||
{
|
||||
$this->services = $services;
|
||||
$this->cateServices = $cateServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_label_create';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:创建标签会新增系统配置数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '创建一个用户标签(管理后台「用户标签」体系),成功后即可通过后台给用户打标;返回创建结果摘要;'
|
||||
. '必填参数:label_name 标签名称(非空,50 字以内);label_cate 标签分类 ID(大于 0 的整数,'
|
||||
. '必须是已存在的分类,可用分类见 crmeb_user_label_list 返回的 cates 字段);'
|
||||
. '同一分类下不允许与既有标签重名;创建成功后可通过 crmeb_user_label_list 查询确认,'
|
||||
. '修改已有标签用 crmeb_user_label_update,删除标签用 crmeb_user_label_delete。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '标签创建';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['label_name', 'label_cate'],
|
||||
'properties' => [
|
||||
'label_name' => ['type' => 'string', 'maxLength' => 50, 'description' => '标签名称,非空,50 字以内'],
|
||||
'label_cate' => ['type' => 'integer', 'minimum' => 1, 'description' => '标签分类 ID,必须是已存在的分类,可用分类见 crmeb_user_label_list 返回的 cates'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(label_name/label_cate)
|
||||
* @return array 创建结果摘要
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$name = trim((string)($args['label_name'] ?? ''));
|
||||
if ($name === '') {
|
||||
throw new McpException('label_name 不能为空');
|
||||
}
|
||||
if (mb_strlen($name) > 50) {
|
||||
throw new McpException('label_name 不能超过 50 个字');
|
||||
}
|
||||
$cateId = self::positiveIntegerArg($args, 'label_cate');
|
||||
if (!$this->cateExists($cateId)) {
|
||||
throw new McpException('label_cate 标签分类不存在,可用分类见 crmeb_user_label_list 返回的 cates');
|
||||
}
|
||||
|
||||
// 与 UserLabelController::save 的 postMore 结构保持一致,由服务层完成入库与查重
|
||||
try {
|
||||
$this->services->save(0, ['id' => 0, 'label_cate' => $cateId, 'label_name' => $name]);
|
||||
} catch (AdminException $e) {
|
||||
// 服务层业务校验失败:转 McpException 让消息透传给 AI 客户端
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'label_name' => $name,
|
||||
'label_cate' => $cateId,
|
||||
'message' => '用户标签创建成功,可通过 crmeb_user_label_list 查询确认',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验标签分类是否存在
|
||||
* @param int $cateId 分类 ID
|
||||
* @return bool 是否存在
|
||||
*/
|
||||
private function cateExists(int $cateId): bool
|
||||
{
|
||||
$cateIds = array_map('intval', array_column($this->cateServices->getLabelCateAll(), 'id'));
|
||||
return in_array($cateId, $cateIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @return int 大于 0 的整数
|
||||
* @throws McpException 非整数或小于等于 0
|
||||
*/
|
||||
private static function positiveIntegerArg(array $args, string $name): int
|
||||
{
|
||||
$value = $args[$name] ?? null;
|
||||
if (!is_int($value) && (!is_string($value) || !ctype_digit($value))) {
|
||||
throw new McpException($name . ' 必须是大于 0 的整数');
|
||||
}
|
||||
$number = (int)$value;
|
||||
if ($number <= 0) {
|
||||
throw new McpException($name . ' 必须是大于 0 的整数');
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
}
|
||||
157
backend/modules/service/mcp/tools/UserLabelDeleteTool.php
Normal file
157
backend/modules/service/mcp/tools/UserLabelDeleteTool.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:删除用户标签
|
||||
*
|
||||
* 与管理后台「用户标签」同链路:调用 UserLabelServices::delLabel 删除标签;
|
||||
* 参数预检在工具层完成(McpException 消息可透传),服务层业务校验不因 AI 调用而豁免。
|
||||
*/
|
||||
class UserLabelDeleteTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户标签服务
|
||||
* @var UserLabelServices
|
||||
*/
|
||||
private UserLabelServices $services;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserLabelServices $services 用户标签服务
|
||||
*/
|
||||
public function __construct(UserLabelServices $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_label_delete';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:删除标签会不可逆地移除系统配置数据,需用户确认后才真实执行;
|
||||
* delete=删除类写操作,后台权限勾选与工具列表按「删」标注展示
|
||||
* @return string delete=删除类,需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'delete';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '删除一个用户标签,删除后该标签从标签列表消失且不可恢复;'
|
||||
. '必填参数:id 标签 ID(可通过 crmeb_user_label_list 查询);'
|
||||
. '仅删除标签本身,不清理用户与该标签的既有关联记录(与管理后台删除行为一致),'
|
||||
. '删除前建议先用 crmeb_user_list 按 label_id 筛选确认受影响用户;'
|
||||
. '创建标签用 crmeb_user_label_create,修改标签用 crmeb_user_label_update。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '标签删除';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['id'],
|
||||
'properties' => [
|
||||
'id' => ['type' => 'integer', 'minimum' => 1, 'description' => '待删除的标签 ID,可通过 crmeb_user_label_list 查询'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(id)
|
||||
* @return array 删除结果摘要
|
||||
* @throws McpException 参数非法、标签不存在或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$id = self::positiveIntegerArg($args, 'id');
|
||||
|
||||
$label = $this->services->getLable($id);
|
||||
if (!$label) {
|
||||
throw new McpException('标签不存在,可通过 crmeb_user_label_list 查询有效标签 ID');
|
||||
}
|
||||
$name = (string)$label->getData('label_name');
|
||||
|
||||
try {
|
||||
$this->services->delLabel($id);
|
||||
} catch (AdminException $e) {
|
||||
// 服务层业务校验失败:转 McpException 让消息透传给 AI 客户端
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'label_name' => $name,
|
||||
'message' => '用户标签删除成功,可通过 crmeb_user_label_list 查询确认',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @return int 大于 0 的整数
|
||||
* @throws McpException 非整数或小于等于 0
|
||||
*/
|
||||
private static function positiveIntegerArg(array $args, string $name): int
|
||||
{
|
||||
$value = $args[$name] ?? null;
|
||||
if (!is_int($value) && (!is_string($value) || !ctype_digit($value))) {
|
||||
throw new McpException($name . ' 必须是大于 0 的整数');
|
||||
}
|
||||
$number = (int)$value;
|
||||
if ($number <= 0) {
|
||||
throw new McpException($name . ' 必须是大于 0 的整数');
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
}
|
||||
125
backend/modules/service/mcp/tools/UserLabelListTool.php
Normal file
125
backend/modules/service/mcp/tools/UserLabelListTool.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelCateServices;
|
||||
use app\services\user\UserLabelServices;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
|
||||
/**
|
||||
* MCP 工具:用户标签列表
|
||||
*
|
||||
* 标签字典查询:返回全部用户标签及所属分类,供 crmeb_user_list 按标签筛选时取标签 ID。
|
||||
*/
|
||||
class UserLabelListTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户标签服务
|
||||
* @var UserLabelServices
|
||||
*/
|
||||
private UserLabelServices $services;
|
||||
|
||||
/**
|
||||
* 标签分类服务
|
||||
* @var UserLabelCateServices
|
||||
*/
|
||||
private UserLabelCateServices $cateServices;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserLabelServices $services 用户标签服务
|
||||
* @param UserLabelCateServices $cateServices 标签分类服务
|
||||
*/
|
||||
public function __construct(UserLabelServices $services, UserLabelCateServices $cateServices)
|
||||
{
|
||||
$this->services = $services;
|
||||
$this->cateServices = $cateServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_label_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询全部用户标签字典,返回标签 ID、标签名称、所属分类 ID 与分类名,并附全部标签分类列表(cates 字段);'
|
||||
. 'label_cate 可选,按标签分类 ID 筛选标签,0 或不传返回全部分类下的标签;'
|
||||
. '不分页全量返回,不包含各标签下的打标用户数;'
|
||||
. '标签 ID 用于 crmeb_user_list 的 label_id 参数按标签筛选用户。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '标签列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'label_cate' => ['type' => 'integer', 'minimum' => 0, 'description' => '标签分类 ID,按分类筛选标签;0 或不传返回全部'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(label_cate)
|
||||
* @return array 标签列表、分类列表与总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$where = [];
|
||||
$cateId = (int)($args['label_cate'] ?? 0);
|
||||
if ($cateId > 0) {
|
||||
$where['label_cate'] = $cateId;
|
||||
}
|
||||
|
||||
$list = $this->services->getLabelList($where, ['id', 'label_name', 'label_cate']);
|
||||
|
||||
$cates = [];
|
||||
foreach ($this->cateServices->getLabelCateAll() as $cate) {
|
||||
$cates[] = ['id' => (int)($cate['id'] ?? 0), 'name' => (string)($cate['name'] ?? '')];
|
||||
}
|
||||
|
||||
return ['list' => $list, 'count' => count($list), 'cates' => $cates];
|
||||
}
|
||||
}
|
||||
165
backend/modules/service/mcp/tools/UserLabelSetTool.php
Normal file
165
backend/modules/service/mcp/tools/UserLabelSetTool.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:设置用户标签
|
||||
*
|
||||
* 与管理后台「设置标签」同链路:调用 UserServices::saveSetLabel 批量
|
||||
* 覆盖 / 追加 / 移除用户标签;标签存在性与写入结果由服务层校验。
|
||||
*/
|
||||
class UserLabelSetTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 标签操作类型:清空原有后设置(覆盖)
|
||||
*/
|
||||
private const LABEL_TYPE_REPLACE = 0;
|
||||
|
||||
/**
|
||||
* 标签操作类型:保留原有叠加(追加)
|
||||
*/
|
||||
private const LABEL_TYPE_APPEND = 1;
|
||||
|
||||
/**
|
||||
* 标签操作类型:移除指定标签
|
||||
*/
|
||||
private const LABEL_TYPE_REMOVE = 2;
|
||||
|
||||
/**
|
||||
* 用户服务
|
||||
* @var UserServices
|
||||
*/
|
||||
private UserServices $services;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserServices $services 用户服务
|
||||
*/
|
||||
public function __construct(UserServices $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_label_set';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:调整用户标签会变更用户数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '为一个或多个用户设置用户标签,等同管理后台「设置标签」,支持覆盖、追加、移除三种方式,成功返回本次操作的标签与用户数。'
|
||||
. '必填参数:uids 用户ID数组(支持批量);label_ids 标签ID数组。'
|
||||
. 'label_type 操作方式:0=覆盖(默认,清空用户原有标签后设置 label_ids)、1=追加(保留原有标签叠加 label_ids)、'
|
||||
. '2=移除(从用户身上移除 label_ids 指定的标签)。'
|
||||
. 'label_ids 必须是已存在的用户标签,标签 ID 与分类清单可用 crmeb_user_label_list 查询(创建新标签用 crmeb_user_label_create),'
|
||||
. '标签不存在时服务层会整体拒绝。'
|
||||
. '不能创建或删除标签本身;改用户分组用 crmeb_user_group_set。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '标签设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['uids', 'label_ids'],
|
||||
'properties' => [
|
||||
'uids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => '用户ID数组,支持批量设置'],
|
||||
'label_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => '标签ID数组;label_type=2 时为要移除的标签'],
|
||||
'label_type' => ['type' => 'integer', 'enum' => [0, 1, 2], 'description' => '0=覆盖原有标签(默认),1=追加标签,2=移除指定标签'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(uids、label_ids、label_type)
|
||||
* @return array 本次标签操作结果
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$uids = UserEditPayload::idArray($args['uids'] ?? [], 'uids');
|
||||
$uids = array_values(array_filter($uids, static fn(int $id) => $id > 0));
|
||||
if (!$uids) {
|
||||
throw new McpException('uids 必须是至少一个大于 0 的用户ID数组');
|
||||
}
|
||||
$labelIds = UserEditPayload::idArray($args['label_ids'] ?? [], 'label_ids');
|
||||
if (!$labelIds) {
|
||||
throw new McpException('label_ids 必须是至少一个标签ID');
|
||||
}
|
||||
$labelType = (int)($args['label_type'] ?? self::LABEL_TYPE_REPLACE);
|
||||
if (!in_array($labelType, [self::LABEL_TYPE_REPLACE, self::LABEL_TYPE_APPEND, self::LABEL_TYPE_REMOVE], true)) {
|
||||
throw new McpException('label_type 只能是 0(覆盖)、1(追加)或 2(移除)');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->services->saveSetLabel($uids, $labelIds, $labelType);
|
||||
} catch (AdminException $e) {
|
||||
// 「有标签不存在或被删除」「设置标签失败」等业务错误透传
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
$actionText = [self::LABEL_TYPE_REPLACE => '覆盖为', self::LABEL_TYPE_APPEND => '追加', self::LABEL_TYPE_REMOVE => '移除'][$labelType];
|
||||
return [
|
||||
'label_type' => $labelType,
|
||||
'label_ids' => $labelIds,
|
||||
'uid_count' => count($uids),
|
||||
'uids' => $uids,
|
||||
'message' => '已把 ' . count($uids) . ' 个用户的标签' . $actionText . ' ' . implode(',', $labelIds),
|
||||
];
|
||||
}
|
||||
}
|
||||
199
backend/modules/service/mcp/tools/UserLabelUpdateTool.php
Normal file
199
backend/modules/service/mcp/tools/UserLabelUpdateTool.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelCateServices;
|
||||
use app\services\user\UserLabelServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:修改用户标签
|
||||
*
|
||||
* 与管理后台「用户标签」同链路:调用 UserLabelServices::save 更新已有标签;
|
||||
* 未提供的参数沿用原值;参数预检在工具层完成(McpException 消息可透传),
|
||||
* 服务层业务校验不因 AI 调用而豁免。
|
||||
*/
|
||||
class UserLabelUpdateTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户标签服务
|
||||
* @var UserLabelServices
|
||||
*/
|
||||
private UserLabelServices $services;
|
||||
|
||||
/**
|
||||
* 标签分类服务
|
||||
* @var UserLabelCateServices
|
||||
*/
|
||||
private UserLabelCateServices $cateServices;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserLabelServices $services 用户标签服务
|
||||
* @param UserLabelCateServices $cateServices 标签分类服务
|
||||
*/
|
||||
public function __construct(UserLabelServices $services, UserLabelCateServices $cateServices)
|
||||
{
|
||||
$this->services = $services;
|
||||
$this->cateServices = $cateServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_label_update';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:修改标签会变更系统配置数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '修改一个已有用户标签的名称或所属分类,未提供的字段沿用原值,返回修改后的标签信息;'
|
||||
. '必填参数:id 标签 ID(可通过 crmeb_user_label_list 查询);'
|
||||
. '可选参数:label_name 新名称(非空,50 字以内)、label_cate 新分类 ID(必须是已存在的分类,'
|
||||
. '可用分类见 crmeb_user_label_list 返回的 cates 字段),两者至少提供一个;'
|
||||
. '同一分类下不允许与其他标签重名;创建标签用 crmeb_user_label_create,删除标签用 crmeb_user_label_delete。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '标签修改';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['id'],
|
||||
'properties' => [
|
||||
'id' => ['type' => 'integer', 'minimum' => 1, 'description' => '标签 ID,可通过 crmeb_user_label_list 查询'],
|
||||
'label_name' => ['type' => 'string', 'maxLength' => 50, 'description' => '新标签名称,非空,50 字以内;不传沿用原值'],
|
||||
'label_cate' => ['type' => 'integer', 'minimum' => 1, 'description' => '新标签分类 ID,必须是已存在的分类;不传沿用原值'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(id,label_name/label_cate 至少其一)
|
||||
* @return array 修改后的标签信息
|
||||
* @throws McpException 参数非法、标签不存在或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$id = self::positiveIntegerArg($args, 'id');
|
||||
if (!isset($args['label_name']) && !isset($args['label_cate'])) {
|
||||
throw new McpException('label_name 与 label_cate 至少提供一个');
|
||||
}
|
||||
|
||||
$label = $this->services->getLable($id);
|
||||
if (!$label) {
|
||||
throw new McpException('标签不存在,可通过 crmeb_user_label_list 查询有效标签 ID');
|
||||
}
|
||||
|
||||
$name = trim((string)($args['label_name'] ?? $label->getData('label_name')));
|
||||
if ($name === '') {
|
||||
throw new McpException('label_name 不能为空');
|
||||
}
|
||||
if (mb_strlen($name) > 50) {
|
||||
throw new McpException('label_name 不能超过 50 个字');
|
||||
}
|
||||
|
||||
$cateId = (int)$label->getData('label_cate');
|
||||
if (isset($args['label_cate'])) {
|
||||
$cateId = self::positiveIntegerArg($args, 'label_cate');
|
||||
if (!$this->cateExists($cateId)) {
|
||||
throw new McpException('label_cate 标签分类不存在,可用分类见 crmeb_user_label_list 返回的 cates');
|
||||
}
|
||||
}
|
||||
|
||||
// 与 UserLabelController::save 的 postMore 结构保持一致,由服务层完成入库与查重
|
||||
try {
|
||||
$this->services->save($id, ['id' => $id, 'label_cate' => $cateId, 'label_name' => $name]);
|
||||
} catch (AdminException $e) {
|
||||
// 服务层业务校验失败:转 McpException 让消息透传给 AI 客户端
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'label_name' => $name,
|
||||
'label_cate' => $cateId,
|
||||
'message' => '用户标签修改成功,可通过 crmeb_user_label_list 查询确认',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验标签分类是否存在
|
||||
* @param int $cateId 分类 ID
|
||||
* @return bool 是否存在
|
||||
*/
|
||||
private function cateExists(int $cateId): bool
|
||||
{
|
||||
$cateIds = array_map('intval', array_column($this->cateServices->getLabelCateAll(), 'id'));
|
||||
return in_array($cateId, $cateIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args 工具参数
|
||||
* @param string $name 参数名称
|
||||
* @return int 大于 0 的整数
|
||||
* @throws McpException 非整数或小于等于 0
|
||||
*/
|
||||
private static function positiveIntegerArg(array $args, string $name): int
|
||||
{
|
||||
$value = $args[$name] ?? null;
|
||||
if (!is_int($value) && (!is_string($value) || !ctype_digit($value))) {
|
||||
throw new McpException($name . ' 必须是大于 0 的整数');
|
||||
}
|
||||
$number = (int)$value;
|
||||
if ($number <= 0) {
|
||||
throw new McpException($name . ' 必须是大于 0 的整数');
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
}
|
||||
141
backend/modules/service/mcp/tools/UserLevelGiveTool.php
Normal file
141
backend/modules/service/mcp/tools/UserLevelGiveTool.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:赠送会员等级
|
||||
*
|
||||
* 与管理后台「赠送等级」同链路:调用 UserServices::saveGiveLevel 给用户
|
||||
* 赠送系统会员等级配置中的一个等级;等级存在性、重复赠送由服务层校验,
|
||||
* 赠送成功后用户的 level 字段同步为该等级 ID,原等级作废。
|
||||
*/
|
||||
class UserLevelGiveTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户服务
|
||||
* @var UserServices
|
||||
*/
|
||||
private UserServices $services;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserServices $services 用户服务
|
||||
*/
|
||||
public function __construct(UserServices $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_level_give';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:赠送会员等级会变更用户权益数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '给单个用户赠送系统会员等级,等同管理后台「赠送等级」,成功返回赠送的等级信息。'
|
||||
. '必填参数:uid 用户ID;level_id 等级ID(系统会员等级配置「会员 → 等级设置」的主键 ID,'
|
||||
. '不是等级序数;目前没有查询等级配置的 MCP 工具,ID 不确定时先向运营确认)。'
|
||||
. '行为口径:赠送后用户原会员等级记录被作废并替换为新等级,用户 level 字段同步为 level_id,'
|
||||
. '等级附带经验差异会自动增减;用户已拥有该等级且未失效时赠送失败;只能赠等级,不能清除等级或修改经验。'
|
||||
. '注意与付费会员是两套体系:延长付费会员到期时间用 crmeb_user_member_time_give。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '赠送等级';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['uid', 'level_id'],
|
||||
'properties' => [
|
||||
'uid' => ['type' => 'number', 'description' => '用户ID'],
|
||||
'level_id' => ['type' => 'integer', 'minimum' => 1, 'description' => '系统会员等级配置ID(会员等级设置列表的主键,非等级序数)'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(uid、level_id)
|
||||
* @return array 赠送结果
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$uid = (int)($args['uid'] ?? 0);
|
||||
if ($uid <= 0) {
|
||||
throw new McpException('参数错误:缺少 uid 或格式不正确');
|
||||
}
|
||||
$levelId = (int)($args['level_id'] ?? 0);
|
||||
if ($levelId <= 0) {
|
||||
throw new McpException('level_id 必须是大于 0 的等级ID');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->services->saveGiveLevel($uid, $levelId);
|
||||
} catch (AdminException $e) {
|
||||
// 「用户不存在」「等级不存在或被删除」「已有该等级」等业务错误透传
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'level_id' => $levelId,
|
||||
'message' => '会员等级赠送成功,用户当前等级已设置为 ' . $levelId . ',可通过 crmeb_user_detail 查看',
|
||||
];
|
||||
}
|
||||
}
|
||||
150
backend/modules/service/mcp/tools/UserMarkSetTool.php
Normal file
150
backend/modules/service/mcp/tools/UserMarkSetTool.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelRelationServices;
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:设置用户备注
|
||||
*
|
||||
* 与管理后台「编辑用户」同链路:读取用户当前值合并新备注后调用
|
||||
* UserServices::updateInfo 落库,只改 mark 一个字段,不影响其他资料。
|
||||
*/
|
||||
class UserMarkSetTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户服务
|
||||
* @var UserServices
|
||||
*/
|
||||
private UserServices $services;
|
||||
|
||||
/**
|
||||
* 用户标签关联服务(读取当前标签避免被清空)
|
||||
* @var UserLabelRelationServices
|
||||
*/
|
||||
private UserLabelRelationServices $labelServices;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserServices $services 用户服务
|
||||
* @param UserLabelRelationServices $labelServices 用户标签关联服务
|
||||
*/
|
||||
public function __construct(UserServices $services, UserLabelRelationServices $labelServices)
|
||||
{
|
||||
$this->services = $services;
|
||||
$this->labelServices = $labelServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_mark_set';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:写备注会变更用户数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '给单个用户写运营备注(mark 字段),等同管理后台「编辑用户」里的备注项;只修改备注,不影响昵称、手机号、分组、标签等其他资料,成功返回更新后的备注。'
|
||||
. '必填参数:uid 用户ID;mark 备注内容(255 字以内,传空字符串即清空备注)。'
|
||||
. '备注面向运营人员记录,用户端不可见;需要修改姓名、手机号等完整资料时用 crmeb_user_update。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '用户备注';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['uid', 'mark'],
|
||||
'properties' => [
|
||||
'uid' => ['type' => 'number', 'description' => '用户ID'],
|
||||
'mark' => ['type' => 'string', 'maxLength' => 255, 'description' => '备注内容,255 字以内,空字符串表示清空备注'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(uid、mark)
|
||||
* @return array 更新后的备注
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$uid = (int)($args['uid'] ?? 0);
|
||||
if ($uid <= 0) {
|
||||
throw new McpException('参数错误:缺少 uid 或格式不正确');
|
||||
}
|
||||
if (!array_key_exists('mark', $args) || !is_string($args['mark'])) {
|
||||
throw new McpException('参数错误:缺少 mark 或格式不正确');
|
||||
}
|
||||
$mark = trim($args['mark']);
|
||||
if (mb_strlen($mark) > 255) {
|
||||
throw new McpException('mark 备注不能超过 255 个字');
|
||||
}
|
||||
|
||||
$data = UserEditPayload::build($this->services, $this->labelServices, $uid, ['mark' => $mark]);
|
||||
|
||||
try {
|
||||
$this->services->updateInfo($uid, $data);
|
||||
} catch (AdminException $e) {
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'mark' => $mark,
|
||||
'message' => $mark === '' ? '用户备注已清空' : '用户备注已更新,可通过 crmeb_user_detail 查看',
|
||||
];
|
||||
}
|
||||
}
|
||||
173
backend/modules/service/mcp/tools/UserMemberTimeGiveTool.php
Normal file
173
backend/modules/service/mcp/tools/UserMemberTimeGiveTool.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:赠送付费会员时长
|
||||
*
|
||||
* 与管理后台「赠送付费会员时长」同链路:调用 UserServices::saveGiveLevelTime
|
||||
* 给用户增加付费会员有效期(或设为永久);服务层会写入会员赠送订单并更新
|
||||
* 会员到期时间(overdue_time)与永久会员标记(is_ever_level)。
|
||||
*/
|
||||
class UserMemberTimeGiveTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 天数取值:永久会员
|
||||
*/
|
||||
private const DAYS_FOREVER = -1;
|
||||
|
||||
/**
|
||||
* 用户服务
|
||||
* @var UserServices
|
||||
*/
|
||||
private UserServices $services;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserServices $services 用户服务
|
||||
*/
|
||||
public function __construct(UserServices $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_member_time_give';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:赠送付费会员会变更用户权益数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '给单个用户赠送付费会员时长(延长会员到期时间或设为永久会员),等同管理后台「赠送付费会员时长」,成功返回赠送后的会员到期时间。'
|
||||
. '必填参数:uid 用户ID;days 赠送天数:-1=设为永久会员,正整数=在当前到期时间基础上增加天数(未开通过付费会员的用户从当前时间起算)。'
|
||||
. 'days=0 或小于 -1 会被拒绝;已是永久会员时无需赠送,工具会直接返回提示。'
|
||||
. '赠送会写入一条后台赠送的会员订单记录。注意与会员等级是两套体系:调整会员等级用 crmeb_user_level_give;'
|
||||
. '本工具不能扣减时长(不支持负数天数)、不能关闭付费会员资格。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '赠送会员时长';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['uid', 'days'],
|
||||
'properties' => [
|
||||
'uid' => ['type' => 'number', 'description' => '用户ID'],
|
||||
'days' => ['type' => 'integer', 'description' => '赠送天数:-1=永久会员,正整数=增加的天数'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(uid、days)
|
||||
* @return array 赠送后的会员状态
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$uid = (int)($args['uid'] ?? 0);
|
||||
if ($uid <= 0) {
|
||||
throw new McpException('参数错误:缺少 uid 或格式不正确');
|
||||
}
|
||||
$days = $args['days'] ?? null;
|
||||
if (!is_int($days) && (!is_string($days) || !ctype_digit($days) && (string)$days !== '-1')) {
|
||||
throw new McpException('days 必须是整数(-1 表示永久,正整数为天数)');
|
||||
}
|
||||
$days = (int)$days;
|
||||
if ($days === 0 || $days < self::DAYS_FOREVER) {
|
||||
throw new McpException('days 只能是 -1(永久)或正整数天数,不能为 0 或小于 -1');
|
||||
}
|
||||
|
||||
// 服务层对永久会员直接跳过(返回 true 无动作),此处提前识别给出明确结果
|
||||
$user = $this->services->getUserInfo($uid, 'uid,is_ever_level,overdue_time,is_money_level');
|
||||
if (!$user) {
|
||||
throw new McpException('用户不存在');
|
||||
}
|
||||
if ((int)$user['is_ever_level'] === 1) {
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'days' => $days,
|
||||
'is_ever_level' => 1,
|
||||
'overdue_time' => 0,
|
||||
'message' => '该用户已是永久付费会员,无需赠送',
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$this->services->saveGiveLevelTime($uid, $days);
|
||||
} catch (AdminException $e) {
|
||||
// 「赠送天数不能为0」等业务错误透传
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
// 重新读取会员状态,返回赠送后的到期时间
|
||||
$user = $this->services->getUserInfo($uid, 'uid,is_ever_level,overdue_time');
|
||||
$overdueTime = (int)($user['overdue_time'] ?? 0);
|
||||
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'days' => $days,
|
||||
'is_ever_level' => (int)($user['is_ever_level'] ?? 0),
|
||||
'overdue_time' => $overdueTime,
|
||||
'overdue_date' => $overdueTime > 0 ? date('Y-m-d H:i:s', $overdueTime) : '',
|
||||
'message' => $days === self::DAYS_FOREVER
|
||||
? '已把该用户设为永久付费会员'
|
||||
: '已赠送 ' . $days . ' 天付费会员,到期时间 ' . ($overdueTime > 0 ? date('Y-m-d H:i:s', $overdueTime) : '未知'),
|
||||
];
|
||||
}
|
||||
}
|
||||
131
backend/modules/service/mcp/tools/UserMoneyListTool.php
Normal file
131
backend/modules/service/mcp/tools/UserMoneyListTool.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\dao\user\UserMoneyDao;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
|
||||
/**
|
||||
* MCP 工具:余额流水列表
|
||||
*
|
||||
* v8 起用户余额变动记于独立账单表 user_money,历史数据可能仍存于旧账单表。
|
||||
*/
|
||||
class UserMoneyListTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 余额流水 DAO
|
||||
* @var UserMoneyDao
|
||||
*/
|
||||
private UserMoneyDao $dao;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserMoneyDao $dao 余额流水 DAO
|
||||
*/
|
||||
public function __construct(UserMoneyDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_money_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询用户余额流水列表(v8 独立余额账单表),支持按用户 UID、变动类型、收/支方向筛选,'
|
||||
. '返回变动金额、变动后余额、标题与备注;'
|
||||
. 'type 常见值:recharge 充值入账、pay_product 余额支付购买商品、pay_member 余额购买会员、'
|
||||
. 'offline_scan 线下收银、pay_product_refund 订单退款到余额、recharge_refund 充值退款、'
|
||||
. 'extract 佣金提现到余额、lottery_add/lottery_use 抽奖、system_add/system_sub 系统调整、register_system_add 注册赠送;'
|
||||
. 'pm:1 收入、0 支出;status:1 有效、0 待确定、-1 无效。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '余额流水列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '财务';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'page' => ['type' => 'number', 'description' => '页码'],
|
||||
'limit' => ['type' => 'number', 'description' => '每页数量'],
|
||||
'uid' => ['type' => 'number', 'description' => '用户 UID'],
|
||||
'type' => ['type' => 'string', 'description' => '变动类型,如 recharge/pay_product/system_add'],
|
||||
'pm' => ['type' => 'number', 'description' => '收/支方向:1 收入、0 支出'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(page/limit/uid/type/pm)
|
||||
* @return array 余额流水列表和总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$page = max(1, (int)($args['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int)($args['limit'] ?? 10)));
|
||||
|
||||
$where = [];
|
||||
if (!empty($args['uid'])) {
|
||||
$where[] = ['uid', '=', (int)$args['uid']];
|
||||
}
|
||||
if (isset($args['type']) && $args['type'] !== '') {
|
||||
$where[] = ['type', '=', (string)$args['type']];
|
||||
}
|
||||
if (isset($args['pm']) && $args['pm'] !== '') {
|
||||
$where[] = ['pm', '=', (int)$args['pm'] == 1 ? 1 : 0];
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
->field('id,uid,link_id,type,title,number,balance,pm,mark,status,add_time')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
}
|
||||
171
backend/modules/service/mcp/tools/UserUpdateTool.php
Normal file
171
backend/modules/service/mcp/tools/UserUpdateTool.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\service\mcp\tools;
|
||||
|
||||
use app\services\user\UserLabelRelationServices;
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\interfaces\McpToolInterface;
|
||||
use modules\service\mcp\McpException;
|
||||
|
||||
/**
|
||||
* MCP 工具:修改用户资料
|
||||
*
|
||||
* 与管理后台「编辑用户」同链路:先读用户当前值合并显式传入的修改项,
|
||||
* 再调用 UserServices::updateInfo 全量落库;只改传入字段,不传的字段
|
||||
* 保持原值,不会清空其他资料。服务层业务校验不因 AI 调用而豁免。
|
||||
*/
|
||||
class UserUpdateTool implements McpToolInterface
|
||||
{
|
||||
/**
|
||||
* 用户服务
|
||||
* @var UserServices
|
||||
*/
|
||||
private UserServices $services;
|
||||
|
||||
/**
|
||||
* 用户标签关联服务(读取当前标签避免被清空)
|
||||
* @var UserLabelRelationServices
|
||||
*/
|
||||
private UserLabelRelationServices $labelServices;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param UserServices $services 用户服务
|
||||
* @param UserLabelRelationServices $labelServices 用户标签关联服务
|
||||
*/
|
||||
public function __construct(UserServices $services, UserLabelRelationServices $labelServices)
|
||||
{
|
||||
$this->services = $services;
|
||||
$this->labelServices = $labelServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return 'crmeb_user_update';
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互声明:修改用户资料会直接变更用户数据,需用户确认后才真实执行
|
||||
* @return string execute=需确认后执行
|
||||
*/
|
||||
public function interaction(): string
|
||||
{
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '修改单个用户的基础资料,等同管理后台「编辑用户」;只修改显式传入的字段,未传字段保持原值,成功返回修改后的资料摘要。'
|
||||
. '必填参数:uid 用户ID。'
|
||||
. '可选字段:real_name 真实姓名(必填校验,不能为空、25 字以内)、phone 手机号(必填校验,11 位大陆手机号且全库唯一)、'
|
||||
. 'email 邮箱(可空,填了须合法)、card_id 身份证号(可空,填了须合法)、birthday 生日(YYYY-MM-DD)、'
|
||||
. 'addres 详细地址(255 字以内)、status 用户状态(1=开启、0=锁定)、group_id 用户分组 ID(0=未分组)、'
|
||||
. 'label_ids 用户标签 ID 数组(覆盖语义:传入后用户标签被替换为该数组;只想改资料不要传此参数)、'
|
||||
. 'mark 备注(255 字以内,仅改备注建议用 crmeb_user_mark_set)。'
|
||||
. '至少传入一个可选字段。不能通过本工具修改余额、积分、会员等级与密码;改分组用 crmeb_user_group_set,'
|
||||
. '改标签用 crmeb_user_label_set,赠送等级用 crmeb_user_level_give。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具中文名(管理后台展示)
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return '资料修改';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
*/
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'required' => ['uid'],
|
||||
'properties' => [
|
||||
'uid' => ['type' => 'number', 'description' => '用户ID'],
|
||||
'real_name' => ['type' => 'string', 'maxLength' => 25, 'description' => '真实姓名,不能为空'],
|
||||
'phone' => ['type' => 'string', 'description' => '手机号,11 位大陆手机号,全库唯一'],
|
||||
'email' => ['type' => 'string', 'description' => '邮箱,可空'],
|
||||
'card_id' => ['type' => 'string', 'description' => '身份证号码,可空'],
|
||||
'birthday' => ['type' => 'string', 'description' => '生日,YYYY-MM-DD'],
|
||||
'addres' => ['type' => 'string', 'maxLength' => 255, 'description' => '详细地址'],
|
||||
'status' => ['type' => 'integer', 'enum' => [0, 1], 'description' => '用户状态:1=开启,0=锁定'],
|
||||
'group_id' => ['type' => 'integer', 'minimum' => 0, 'description' => '用户分组 ID,0=未分组'],
|
||||
'label_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => '用户标签 ID 数组,覆盖语义,传入后整体替换用户标签'],
|
||||
'mark' => ['type' => 'string', 'maxLength' => 255, 'description' => '备注,255 字以内'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(uid + 至少一个修改字段)
|
||||
* @return array 修改后的资料摘要
|
||||
* @throws McpException 参数非法或服务层业务校验失败
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
{
|
||||
$uid = (int)($args['uid'] ?? 0);
|
||||
if ($uid <= 0) {
|
||||
throw new McpException('参数错误:缺少 uid 或格式不正确');
|
||||
}
|
||||
|
||||
$editable = ['real_name', 'phone', 'email', 'card_id', 'birthday', 'addres', 'mark', 'status', 'group_id', 'label_ids'];
|
||||
$overrides = array_intersect_key($args, array_flip($editable));
|
||||
if (!$overrides) {
|
||||
throw new McpException('未提供任何要修改的字段,可选字段:real_name、phone、email、card_id、birthday、addres、status、group_id、label_ids、mark');
|
||||
}
|
||||
|
||||
$data = UserEditPayload::build($this->services, $this->labelServices, $uid, $overrides);
|
||||
|
||||
try {
|
||||
$this->services->updateInfo($uid, $data);
|
||||
} catch (AdminException $e) {
|
||||
// 服务层业务校验失败:转 McpException 让消息透传给 AI 客户端
|
||||
throw new McpException($e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'real_name' => $data['real_name'],
|
||||
'phone' => $data['phone'],
|
||||
'status' => $data['status'],
|
||||
'group_id' => $data['group_id'],
|
||||
'label_ids' => $data['label_id'],
|
||||
'mark' => $data['mark'],
|
||||
'message' => '用户资料修改成功,可通过 crmeb_user_detail 查看最新资料',
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user