夜校课程顾问mcp服务

This commit is contained in:
qinzexin 2025-06-26 18:25:08 +08:00
parent 4a78ad9041
commit 8a958aeda8
2 changed files with 511 additions and 0 deletions

View File

@ -0,0 +1,33 @@
<?php
namespace app\api\controller\mcp;
/**
* mcp服务demo
*/
class Base extends \app\api\controller\school\Base
{
public $input = null;
protected $noNeedLogin = '*';
protected $noNeedRight = '*';
/**
* 初始化操作
* @access protected
*/
protected function _initialize()
{
parent::_initialize();
}
}

View File

@ -0,0 +1,478 @@
<?php
namespace app\api\controller\mcp;
use app\common\model\school\classes\ClassesLib;
use app\common\model\school\classes\order\Order as OrderModel;
class School extends Base
{
// 工具定义常量
private const ADD_FUNCTION = [
[
'name' => 'list',
'description' => "查找多样青春夜校的所有课程列表信息
表结构sql为
`id` int(11) NOT NULL AUTO_INCREMENT,
`manystore_id` int(11) NOT NULL COMMENT '机构账号id',
`shop_id` int(11) NOT NULL COMMENT '机构店铺id',
`user_id` int(11) NOT NULL COMMENT '主讲师用户id',
`teacher_id` int(11) NOT NULL COMMENT '老师id',
`classes_cate_ids` varchar(255) NOT NULL COMMENT '平台课程分类ids',
`classes_label_ids` varchar(255) DEFAULT NULL COMMENT '平台课程类型ids',
`self_label_tag` varchar(1000) DEFAULT NULL COMMENT '机构特色标签',
`add_type` enum('1','2') NOT NULL DEFAULT '2' COMMENT '添加人类型:1=机构,2=总后台',
`add_id` int(11) NOT NULL DEFAULT '0' COMMENT '添加人id',
`title` varchar(255) NOT NULL COMMENT '标题',
`headimage` varchar(500) NOT NULL COMMENT '头图',
`images` varchar(1500) NOT NULL COMMENT '轮播图',
`type` enum('out','in') NOT NULL DEFAULT 'in' COMMENT '地点类型:out=户外,in=室内',
`classes_num` int(10) NOT NULL COMMENT '课时数',
`address_type` enum('1','2') NOT NULL DEFAULT '1' COMMENT '地址类型:1=按机构,2=独立位置',
`address_city` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '城市选择',
`province` int(11) DEFAULT NULL COMMENT '省编号',
`city` int(11) DEFAULT NULL COMMENT '市编号',
`district` int(11) DEFAULT NULL COMMENT '县区编号',
`address` varchar(200) CHARACTER SET utf8 DEFAULT NULL COMMENT '店铺地址',
`address_detail` varchar(200) CHARACTER SET utf8 DEFAULT '' COMMENT '店铺详细地址',
`longitude` varchar(30) CHARACTER SET utf8 DEFAULT '' COMMENT '经度',
`latitude` varchar(30) CHARACTER SET utf8 DEFAULT '' COMMENT '纬度',
`classes_date_text` varchar(500) NOT NULL COMMENT '上课日期',
`classes_time_text` varchar(500) NOT NULL COMMENT '上课时间',
`content` text NOT NULL COMMENT '课程详情',
`notice` text NOT NULL COMMENT '课程须知',
`virtual_num` int(11) DEFAULT '0' COMMENT '虚拟报名人数',
`sale` int(11) DEFAULT '0' COMMENT '总销量',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`underline_price` decimal(10,2) DEFAULT NULL COMMENT '划线价',
`virtual_collect` int(11) DEFAULT '0' COMMENT '虚拟收藏量',
`status` enum('1','2','3') DEFAULT '1' COMMENT '状态:1=上架,2=下架,3=平台下架',
`auth_status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '审核状态:0=待审核,1=审核通过,2=审核失败',
`reason` varchar(200) CHARACTER SET utf8 DEFAULT '' COMMENT '审核不通过原因',
`auth_time` int(11) DEFAULT '0' COMMENT '审核时间',
`admin_id` int(11) DEFAULT '0' COMMENT '审核管理员id',
`weigh` int(11) DEFAULT '0' COMMENT '权重',
`recommend` enum('0','1') DEFAULT '0' COMMENT '平台推荐:0=否,1=是',
`hot` enum('0','1') DEFAULT '0' COMMENT '平台热门:0=否,1=是',
`new` enum('0','1') DEFAULT '0' COMMENT '平台最新:0=否,1=是',
`selfhot` enum('0','1') DEFAULT '0' COMMENT '机构热门:0=否,1=是',
`createtime` bigint(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
`updatetime` bigint(11) unsigned DEFAULT '0' COMMENT '修改时间',
`deletetime` bigint(11) unsigned DEFAULT NULL COMMENT '删除时间',
`virtual_people` int(11) DEFAULT '0' COMMENT '虚拟参与人数',
`feel` enum('0','1') DEFAULT '0' COMMENT '是否免费:0=否,1=是',
`limit_num` int(11) DEFAULT '0' COMMENT '总限定人数0不限制',
`sign_num` int(11) DEFAULT '0' COMMENT '总已报名人数',
`verification_num` int(11) DEFAULT NULL COMMENT '总已核销人数',
`views` bigint(20) DEFAULT '0' COMMENT '浏览量',
`start_time` bigint(20) DEFAULT NULL COMMENT '开始时间',
`end_time` bigint(20) DEFAULT NULL COMMENT '结束时间',
`collect` bigint(20) DEFAULT NULL COMMENT '实际收藏量',
`classes_type` varchar(2000) DEFAULT NULL COMMENT '课程类型',
`classes_star` decimal(10,1) NOT NULL DEFAULT '5.0' COMMENT '课程评分',
`star_number` int(11) NOT NULL DEFAULT '0' COMMENT '评价数',
",
'inputSchema' => [
'type' => 'object',
'properties' => [
'keywords' => [
'description' => '搜索关键字',
'example' => "书法",
'type' => 'string',
],
'page' => [
'description' => '页数(必填)',
'example' => 1,
'type' => 'number',
],
'limit' => [
'description' => '每页数量(必填)',
'example' => 20,
'type' => 'number',
]
],
'required' => [ 'page', 'limit']
]
],
[
'name' => 'orderlist',
'description' => "
查找多样青春夜校当前课程的订单列表,想获取订单列表请先获取课程列表list工具信息中对应的课程里的id
表结构sql为
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` varchar(255) DEFAULT NULL COMMENT '订单号',
`pay_no` varchar(255) DEFAULT NULL COMMENT '微信支付单号',
`user_id` int(11) DEFAULT NULL COMMENT '下单人id',
`manystore_id` int(11) DEFAULT NULL COMMENT '机构账号id',
`shop_id` int(11) DEFAULT NULL COMMENT '机构店铺id',
`code` varchar(255) DEFAULT NULL COMMENT '核销码',
`codeimage` varchar(500) DEFAULT NULL COMMENT '核销二维码图片',
`codeoneimage` varchar(500) DEFAULT NULL COMMENT '核销一维码图片',
`classes_lib_id` int(11) DEFAULT NULL COMMENT '课程id',
`classes_order_detail_id` int(11) DEFAULT NULL COMMENT '订单课程id',
`beforeprice` decimal(10,2) DEFAULT NULL COMMENT '订单优惠前金额',
`totalprice` decimal(10,2) DEFAULT NULL COMMENT '订单应付金额',
`payprice` decimal(10,2) DEFAULT NULL COMMENT '订单实付金额',
`pay_type` enum('yue','wechat') DEFAULT 'wechat' COMMENT '支付方式:yue=余额,wechat=微信',
`status` enum('-3','0','3','4','6','9') DEFAULT '0' COMMENT '订单状态:-3=已取消,0=待支付,3=使用中,4=售后中,6=已退款,9=已完成',
`before_status` enum('-3','0','3','6','9') DEFAULT '0' COMMENT '售后前状态:-3=已取消,0=未售后,3=使用中,6=已退款,9=已完成',
`server_status` enum('0','3','6') DEFAULT '0' COMMENT '售后订单状态:0=正常,3=售后中,6=售后完成',
`reason` varchar(500) DEFAULT NULL COMMENT '售后申请原因',
`auth_manystore_id` int(11) DEFAULT NULL COMMENT '机构审核人id',
`auth_opinion` varchar(500) DEFAULT NULL COMMENT '机构审核意见',
`auth_file` varchar(500) DEFAULT NULL COMMENT '机构审核附件',
`admin_id` int(11) DEFAULT NULL COMMENT '平台审核人id',
`result_status` enum('0','3','6') DEFAULT '0' COMMENT '售后处理结果:0=未售后,3=退款,6=驳回',
`result_text` varchar(1000) DEFAULT NULL COMMENT '售后处理结果说明',
`result_file` varchar(500) DEFAULT NULL COMMENT '售后处理结果说明附件',
`canceltime` bigint(20) DEFAULT NULL COMMENT '取消时间',
`paytime` bigint(20) DEFAULT NULL COMMENT '支付时间',
`finishtime` bigint(20) DEFAULT NULL COMMENT '完成时间',
`refundtime` bigint(20) DEFAULT NULL COMMENT '退款时间',
`total_refundprice` decimal(10,2) DEFAULT NULL COMMENT '应退款金额',
`real_refundprice` decimal(10,2) DEFAULT NULL COMMENT '实际退款金额',
`sub_refundprice` decimal(10,2) DEFAULT NULL COMMENT '剩余未退金额',
`createtime` bigint(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
`updatetime` bigint(11) unsigned DEFAULT '0' COMMENT '修改时间',
`deletetime` bigint(11) unsigned DEFAULT NULL COMMENT '删除时间',
`pay_json` varchar(2000) DEFAULT NULL COMMENT '三方支付信息json',
`platform` varchar(255) DEFAULT NULL COMMENT '支付平台',
`classes_service_order_id` int(11) DEFAULT '0' COMMENT '售后订单id',
`classes_lib_spec_id` int(11) DEFAULT '0' COMMENT '快捷下单课时id',
`classes_evaluate_id` int(11) DEFAULT '0' COMMENT '课程反馈id',
",
'inputSchema' => [
'type' => 'object',
'properties' => [
'classes_lib_id' => [
'description' => '课程id通过课程列表信息获取的id字段',
'example' => 1202,
'type' => 'int',
],
'page' => [
'description' => '页数(必填)',
'example' => 1,
'type' => 'int',
],
'limit' => [
'description' => '每页数量(必填)',
'example' => 20,
'type' => 'int',
]
],
'required' => [ 'page', 'limit','classes_lib_id']
]
],
];
public function sse()
{
// 设置 CORS 头
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// 处理 OPTIONS 预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
// 设置 SSE 响应头
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
// 获取原始输入
$input = file_get_contents('php://input');
file_put_contents('mcp_debug.log', date('Y-m-d H:i:s').' Raw input: '.$input.PHP_EOL, FILE_APPEND);
// 解析 JSON-RPC 请求
$request = json_decode($input, true);
// 处理请求
if (isset($request['method'])) {
switch($request['method']) {
case 'initialize':
$this->handleInitializeRequest($request);
break;
case 'function':
case 'tools/call': // 新增支持tools/call方法
$this->handleFunctionRequest($request);
break;
case 'ping':
$this->sendJsonRpcResponse($request['id'] ?? null, ['result' => 'pong']);
break;
case 'notifications/initialized':
$this->sendJsonRpcResponse($request['id'] ?? null, ['result' => 'success']);
break;
case 'tools/list':
$this->sendJsonRpcResponse($request['id'] ?? null, ['tools' => self::ADD_FUNCTION]);
break;
default:
$this->handleOtherRequests($request);
}
} else {
$this->handleOtherRequests($request);
}
}
/**
* 处理初始化请求
*/
private function handleInitializeRequest($request)
{
$id = $request['id'] ?? null;
// 记录完整的初始化请求内容
file_put_contents('mcp_debug.log', "Received initialize request: " . json_encode($request) . PHP_EOL, FILE_APPEND);
// 发送初始化响应
$this->sendJsonRpcResponse($id, [
'capabilities' => [
'progress' => true,
'functions' => self::ADD_FUNCTION
],
'serverInfo' => [
'name' => 'Calculator Server',
'version' => '1.0.0',
'vendor' => 'MyCompany',
'license' => 'MIT'
],
'protocolVersion' => '2025-03-26'
]);
}
/**
* 处理函数调用请求
*/
public function handleFunctionRequest($request)
{
$id = $request['id'] ?? null;
$function = $request['params']['function'] ?? '';
$arguments = $request['params']['arguments'] ?? [];
$function = $request['params']['name'] ?? 'list';
switch ($function){
case 'list':
// 解构参数
$keywords = $arguments['keywords'] ?? 0;
$page = $arguments['page'] ?? 1;
$limit = $arguments['limit'] ?? 1;
$params = [
'keywords' => $keywords,
'page' => (int)$page,
'limit' => (int)$limit,
"order" => "normal",
"status" => "1,2,3",
"auth_status"=> "-1",
"is_expire" => "0"
];
// 发送进度通知
$this->sendNotification('progress', ['id' => $id, 'message' => '正在获取数据...']);
try {
$result = ClassesLib::getVaildList($params);
}catch ( \Exception $e){
$this->sendJsonRpcResponse($id, [
'error' => [
'code' => -32603,
'message' => 'Internal error',
'data' => $e->getMessage()
]
]);
}
$this->sendJsonRpcResponse($id, [
'content' => [
[
'type' => 'list',
'text' => $result // 返回格式化的时间和时区信息
]
]
]);
break;
case 'orderlist':
// 解构参数
$classes_lib_id = $arguments['classes_lib_id'] ?? 0;
$page = $arguments['page'] ?? 1;
$limit = $arguments['limit'] ?? 1;
// 发送进度通知
$this->sendNotification('progress', ['id' => $id, 'message' => '正在获取数据...']);
try {
$result = OrderModel::allList(0,(int)$page, (int)$limit,'',"0,3,4,6,9",(int)$classes_lib_id);
}catch ( \Exception $e){
$this->sendJsonRpcResponse($id, [
'error' => [
'code' => -32603,
'message' => 'Internal error',
'data' => $e->getMessage()
]
]);
}
$this->sendJsonRpcResponse($id, [
'content' => [
[
'type' => 'list',
'text' => $result // 返回格式化的时间和时区信息
]
]
]);
break;
}
}
/**
* 验证加法参数
* @param array $arguments
* @param mixed $id
* @return array|bool 返回true表示验证通过否则返回错误响应数组
*/
private function validateAddParameters(array $arguments, $id)
{
if (!isset($arguments['a'], $arguments['b'])) {
return [
'error' => [
'code' => -32602,
'message' => 'Invalid params',
'data' => 'Missing required parameters: a and b'
]
];
}
if (!is_numeric($arguments['a']) || !is_numeric($arguments['b'])) {
return [
'error' => [
'code' => -32602,
'message' => 'Invalid params',
'data' => 'Parameters must be numbers'
]
];
}
return true;
}
/**
* 处理其他类型的请求
*/
private function handleOtherRequests($request)
{
if (!$request || !isset($request['function'])) {
$this->sendJsonRpcResponse($request['id'] ?? null, ['error' => 'Invalid request format']);
return;
}
if ($request['function'] === 'add' || $request['function'] === 'tools/call') {
$a = $request['params']['a'] ?? 0;
$b = $request['params']['b'] ?? 0;
if (!is_numeric($a) || !is_numeric($b)) {
$this->sendEvent('error', 'Parameters must be numbers');
return;
}
// 模拟处理延迟
sleep(1);
$result = $this->calculateAdd($a, $b);
$this->sendEvent('result', [
'operation' => 'add',
'result' => $result,
'expression' => "$a + $b = $result"
]);
} else {
$this->sendEvent('error', 'Unsupported function: ' . $request['function']);
}
}
/**
* 执行加法计算
*/
private function calculateAdd(float $a, float $b): float
{
return $a + $b;
}
/**
* 发送 JSON-RPC 响应
*/
private function sendJsonRpcResponse($id, $resultOrError)
{
$response = [
'jsonrpc' => '2.0',
'id' => $id
];
if (isset($resultOrError['error'])) {
$response['error'] = $resultOrError['error'];
} else {
$response['result'] = $resultOrError;
}
$this->sendStreamData(json_encode($response, JSON_UNESCAPED_UNICODE));
}
/**
* 发送通知(用于进度更新等)
*/
private function sendNotification($method, $params)
{
$notification = [
'jsonrpc' => '2.0',
'method' => $method,
'params' => $params
];
$this->sendStreamData(json_encode($notification, JSON_UNESCAPED_UNICODE));
}
/**
* 发送标准 SSE 事件
*/
private function sendEvent($type, $data)
{
$jsonData = json_encode($data, JSON_UNESCAPED_UNICODE);
echo "event: $type\n";
echo "data: $jsonData\n\n";
$this->flushBuffers();
file_put_contents('mcp_debug.log', "Sent event: $type - $jsonData".PHP_EOL, FILE_APPEND);
}
/**
* 发送流数据
*/
private function sendStreamData(string $data)
{
echo "data: $data\n\n";
$this->flushBuffers();
file_put_contents('mcp_debug.log', "JSON-RPC Response: $data".PHP_EOL, FILE_APPEND);
}
/**
* 刷新输出缓冲区
*/
private function flushBuffers()
{
ob_flush();
flush();
}
}