define('APP_ID', 'cli_xxxxxxxxxxxxxxxx'); // 飞书应用 ID
define('APP_SECRET', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'); // 飞书应用密钥
define('APP_TOKEN', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'); // 事件校验 Token(可选)
define('APP_BTABLE_APP_ID', 'xxxxxxxxxxxxxxxxxxxxxxxx'); // 多维表格 App ID
define('TABLE_ID', 'tblxxxxxxxxxxxxxx'); // 多维表格 Table ID
define('LOG_FILE', __DIR__.'/log.txt'); // 日志文件路径
define('DEBUG_MODE', true); // 是否开启调试模式
function log_write($msg) {
file_put_contents(LOG_FILE, date('Y-m-d H:i:s').' '.$msg.PHP_EOL, FILE_APPEND);
// 获取 tenant_access_token
$url = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/';
$post = json_encode(['app_id'=>APP_ID, 'app_secret'=>APP_SECRET]);
CURLOPT_POSTFIELDS => $post,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
log_write("token response: $res");
$data = json_decode($res, true);
return $data['tenant_access_token'] ?? '';
// 清理文本,去除所有@标签及尾部多余内容,返回纯文本
function clean_text($text) {
// 去除飞书的 <at ...>...</at> 标签
$text = preg_replace('/<at[^>]*>.*?<\/at>/s', '', $text);
// 去除尾部类似 @xxx 的残留(例如@_user_1)
$text = preg_replace('/@[\w_-]+$/', '', $text);
$text = trim(preg_replace('/\s+/', ' ', $text));
function parse_text($text) {
$parts = explode(' ', $text);
if (count($parts) < 4) return false;
'金额' => floatval($parts[1]),
function write_bitable($token, $fields) {
$url = "https://open.feishu.cn/open-apis/bitable/v1/apps/".APP_BTABLE_APP_ID."/tables/".TABLE_ID."/records";
$post = json_encode(['fields'=>$fields], JSON_UNESCAPED_UNICODE);
CURLOPT_POSTFIELDS => $post,
CURLOPT_RETURNTRANSFER => true,
'Authorization: Bearer '.$token,
'Content-Type: application/json',
log_write("bitable response: $res");
return json_decode($res,true);
function reply_msg($token, $chat_id, $root_id, $text) {
$url = 'https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id';
'receive_id' => $chat_id,
'content' => json_encode(['text' => $text], JSON_UNESCAPED_UNICODE),
], JSON_UNESCAPED_UNICODE);
CURLOPT_POSTFIELDS => $post,
CURLOPT_RETURNTRANSFER => true,
'Authorization: Bearer '.$token,
'Content-Type: application/json',
log_write("reply response: $res");
$input = file_get_contents('php://input');
log_write("raw input: $input");
$data = json_decode($input, true);
echo json_encode(['code'=>400, 'msg'=>'Invalid JSON']);
if (isset($data['type']) && $data['type'] === 'url_verification') {
echo json_encode(['challenge'=>$data['challenge']]);
if (($data['header']['event_type'] ?? '') === 'im.message.receive_v1') {
$open_id = $data['event']['sender']['sender_id']['open_id'] ?? '';
$chat_id = $data['event']['message']['chat_id'] ?? '';
$message_id = $data['event']['message']['message_id'] ?? '';
$msg_content = $data['event']['message']['content'] ?? '{}';
$msg_arr = json_decode($msg_content, true);
$text_raw = $msg_arr['text'] ?? '';
log_write("原始文本: $text_raw");
$text = clean_text($text_raw);
log_write("清理后文本: $text");
$fields = parse_text($text);
reply_msg($token, $chat_id, $message_id, "❗ 格式错误,请输入:名称 金额 用途 平台\n示例:电视 300 设备 京东");
echo json_encode(['code'=>0, 'msg'=>'ok']);
$res = write_bitable($token, $fields);
if (($res['code'] ?? 1) === 0) {
reply_msg($token, $chat_id, $message_id, "✅ 记账成功:{$fields['名称']} - {$fields['金额']} 元");
reply_msg($token, $chat_id, $message_id, "❌ 记账失败:" . ($res['msg'] ?? '未知错误'));
echo json_encode(['code'=>0, 'msg'=>'ok']);
echo json_encode(['code'=>0, 'msg'=>'ok']);