DiscuzX3.5发帖json api

参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客

简单改造了一下,适配我自己的需求

有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容

使用到的sql如下

CREATE TABLE `pre_forum_post_sync`  (
                                        `id` int(0) NOT NULL AUTO_INCREMENT,
                                        `foreign_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                                        `foreign2_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
                                        `foreign3_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
                                        `tid` int(0) NOT NULL,
                                        `pid` int(0) NOT NULL,
                                        `update_time` int(0) NULL DEFAULT NULL,
                                        PRIMARY KEY (`id`) USING BTREE,
                                        UNIQUE INDEX `foreign_id`(`foreign_id`) USING BTREE
) ENGINE = InnoDB  CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;


CREATE TABLE `pre_wechat_push_log` (
                                       `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                                       `tid` INT NOT NULL COMMENT '主题ID',
                                       `pid` INT NOT NULL COMMENT '帖子ID',
                                       `push_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '推送时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

php api代码

同步主站标题api

topic_only.php

 'error', 'message' => 'Missing AccessKey or AccessSecret']);
    exit;
}

if ($headers['accesskey'] !== ACCESS_KEY || $headers['accesssecret'] !== ACCESS_SECRET) {
    http_response_code(403);
    echo json_encode(['status' => 'error', 'message' => 'Invalid AccessKey or AccessSecret']);
    exit;
}

// 接收并解析 JSON 输入
$input = file_get_contents('php://input');
$data = json_decode($input, true);

// 检查必要字段
if (!isset($data['fid'], $data['title'], $data['content'], $data['foreign_id'])) {
    echo json_encode(['status' => 'error', 'message' => 'Missing required parameters']);
    exit;
}

$fid = $data['fid'];
$title = $data['title'];
$content = $data['content'];
$foreign_id = $data['foreign_id'];
$post_time = isset($data['post_time']) ? strtotime($data['post_time']) : time(); // 新增行


try {
    $test = new insert_content('1', 'admin', $post_time); // 使用传入时间
    $result = $test->sync_post($fid, $title, $content, $foreign_id);
    echo json_encode(['status' => 'success', 'data' => $result]);
} catch (Exception $e) {
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

class insert_content {
    private $_prefix = 'pre'; // Discuz 表前缀
    private $_con;
    private $_tid;
    private $_fid;
    private $_pid;
    private $_authorid;
    private $_author;
    private $_time;
    private $_title;
    private $_content;

    public function __construct($authorid, $author, $time = null) {
        $this->_authorid = $authorid;
        $this->_author = $author;
        $this->_time = $time ?? time();
        $this->_con = mysqli_connect('mysql', 'root', 'pass', 'ultrax', 3306);
        if (!$this->_con) {
            throw new Exception("Database connection failed");
        }
    }

    /**
     * 同步发帖方法
     *
     * @param $fid
     * @param $title
     * @param $content
     * @param $foreign_id
     * @return array
     * @throws Exception
     */
    public function sync_post($fid, $title, $content, $foreign_id) {
        $this->_fid = $fid;
        $this->_title = $title;
        $this->_content = $content;

        // 先检查是否已同步
        $sql = "SELECT tid, pid FROM {$this->_prefix}_forum_post_sync WHERE foreign_id='{$foreign_id}'";
        $res = mysqli_query($this->_con, $sql);

        if ($row = mysqli_fetch_assoc($res)) {
            // 已存在,仅更新标题
            $this->_tid = $row['tid'];
            $this->_pid = $row['pid'];

            // 仅更新主题表中的标题
            $sql = "UPDATE {$this->_prefix}_forum_thread SET subject='{$this->_title}', lastpost='{$this->_time}' WHERE tid='{$this->_tid}'";
            if (!mysqli_query($this->_con, $sql)) {
                throw new Exception("Update thread failed: " . mysqli_error($this->_con));
            }

            // 可选:更新同步记录时间
            $sql = "UPDATE {$this->_prefix}_forum_post_sync SET update_time='{$this->_time}' WHERE foreign_id='{$foreign_id}'";
            if (!mysqli_query($this->_con, $sql)) {
                throw new Exception("Update sync record failed: " . mysqli_error($this->_con));
            }

            return [
                'action' => 'updated',
                'tid' => $this->_tid,
                'pid' => $this->_pid
            ];
        } else {
            // 不存在,新建帖子和主题
            return $this->insert_new_post($foreign_id);
        }
    }


    private function insert_new_post($foreign_id) {
        // 第一步:插入主题表
        $sql = "INSERT INTO {$this->_prefix}_forum_thread SET
            fid='{$this->_fid}',
            authorid='{$this->_authorid}',
            author='{$this->_author}',
            subject='{$this->_title}',
            dateline='{$this->_time}', lastpost='{$this->_time}',
            lastposter='{$this->_author}'";
        if (!mysqli_query($this->_con, $sql)) {
            throw new Exception("Insert thread failed: " . mysqli_error($this->_con));
        }
        $this->_tid = mysqli_insert_id($this->_con);

        // 第二步:插入分表协调表
        if (!mysqli_query($this->_con, "INSERT INTO {$this->_prefix}_forum_post_tableid VALUES ()")) {
            throw new Exception("Insert post tableid failed: " . mysqli_error($this->_con));
        }
        $this->_pid = mysqli_insert_id($this->_con);

        // 第三步:获取 position 并插入帖子表
        $res = mysqli_query($this->_con, "SELECT MAX(position) AS max_pos FROM {$this->_prefix}_forum_post WHERE tid='{$this->_tid}'");
        $row = mysqli_fetch_assoc($res);
        $position = $row['max_pos'] ? $row['max_pos'] + 1 : 1;

        $sql = "INSERT INTO {$this->_prefix}_forum_post SET
            pid='{$this->_pid}',
            fid='{$this->_fid}',
            tid='{$this->_tid}',
            author='{$this->_author}',
            authorid='{$this->_authorid}',
            subject='{$this->_title}',
            dateline='{$this->_time}',
            message='{$this->_content}',
            premsg='',
            position={$position}";

        if (!mysqli_query($this->_con, $sql)) {
            throw new Exception("Insert post failed: " . mysqli_error($this->_con));
        }

        // 第四步:更新版块统计
        $sql = "UPDATE {$this->_prefix}_forum_forum SET posts=posts+1, threads=threads+1 WHERE fid='{$this->_fid}'";
        if (!mysqli_query($this->_con, $sql)) {
            throw new Exception("Update forum stats failed: " . mysqli_error($this->_con));
        }

        // 第五步:更新用户统计
        $sql = "UPDATE {$this->_prefix}_common_member_count SET posts=posts+1, threads=threads+1 WHERE uid='{$this->_authorid}'";
        if (!mysqli_query($this->_con, $sql)) {
            throw new Exception("Update user stats failed: " . mysqli_error($this->_con));
        }

        // 插入同步记录
        $sql = "INSERT INTO {$this->_prefix}_forum_post_sync SET
            foreign_id='{$foreign_id}',
            tid='{$this->_tid}',
            pid='{$this->_pid}',
            update_time='{$this->_time}'";

        if (!mysqli_query($this->_con, $sql)) {
            throw new Exception("Insert sync record failed: " . mysqli_error($this->_con));
        }

        return [
            'action' => 'created',
            'tid' => $this->_tid,
            'pid' => $this->_pid
        ];
    }

    public function __destruct() {
        if ($this->_con instanceof mysqli) {
            $this->_con->close();
        }
    }
}

使用方法

curl --location 'http://d.example.com/topic_only.php' \
--header 'AccessKey: my-access-key' \
--header 'AccessSecret: my-access-secret' \
--header 'Content-Type: application/json' \
--data '{
  "fid": "2",
  "title": "测试标题0816",
  "content": "这是一个测试内容。",
  "foreign_id": "9966",
  "post_time": "2024-08-16 10:00:00"
}
'

跟参考的代码相比,这里做了一个简单的鉴权,AccessKey AccessSecret与php代码里保持一致才可调用。本来像参考oss写个签名方法,还是先不搞这么复杂了。

由于采集站没有源站帖子id,则写一个根据标题更新内容的api

update_by_title.php

 'error', 'message' => 'Missing AccessKey or AccessSecret']);
    exit;
}

if ($headers['accesskey'] !== ACCESS_KEY || $headers['accesssecret'] !== ACCESS_SECRET) {
    http_response_code(403);
    echo json_encode(['status' => 'error', 'message' => 'Invalid AccessKey or AccessSecret']);
    exit;
}

// 接收并解析 JSON 输入
$input = file_get_contents('php://input');
$data = json_decode($input, true);

// 检查必要字段
   if (!isset($data['fid'], $data['title'], $data['content']) ||
       (!isset($data['foreign2_id']) && !isset($data['foreign3_id']))) {
       echo json_encode(['status' => 'error', 'message' => 'Missing required parameters: fid, title, content, and either foreign2_id or foreign3_id']);
       exit;
   }


$fid = $data['fid'];
$title = $data['title'];
$content = $data['content'];
$foreign2_id = $data['foreign2_id'] ?? null;
$foreign3_id = $data['foreign3_id'] ?? null;


try {
    // 初始化数据库连接
    $con = mysqli_connect('mysql', 'root', 'pass', 'ultrax', 3306);
    if (!$con) {
        throw new Exception("Database connection failed");
    }

    // 查找 title 对应的主题 ID(tid)
    $sql = "SELECT tid FROM pre_forum_thread WHERE subject = '{$title}' AND fid = '{$fid}' LIMIT 1";
    $res = mysqli_query($con, $sql);

    if (!$res || mysqli_num_rows($res) === 0) {
        throw new Exception("No thread found with the given title and fid");
    }

    $row = mysqli_fetch_assoc($res);
    $tid = $row['tid'];

    // 获取主帖 pid(通常 position = 1)
    $sql = "SELECT pid FROM pre_forum_post WHERE tid = '{$tid}' AND position = 1 ORDER BY dateline DESC LIMIT 1";
    $res = mysqli_query($con, $sql);

    if (!$res || mysqli_num_rows($res) === 0) {
        throw new Exception("Main post not found for the thread");
    }

    $row = mysqli_fetch_assoc($res);
    $pid = $row['pid'];

    // 更新帖子内容
    $sql = "UPDATE pre_forum_post SET message = '{$content}' WHERE pid = '{$pid}'";
    if (!mysqli_query($con, $sql)) {
        throw new Exception("Update post content failed: " . mysqli_error($con));
    }

    // 更新同步记录表中的 foreign2_id
    if (isset($data['foreign2_id'])) {
       // 更新 foreign2_id 字段
       $sql = "UPDATE pre_forum_post_sync
               SET foreign2_id = '{$foreign2_id}'
               WHERE tid = '{$tid}' AND pid = '{$pid}'";
    } elseif (isset($data['foreign3_id'])) {
       // 新增 foreign3_id 字段更新逻辑
       $sql = "UPDATE pre_forum_post_sync
               SET foreign3_id = '{$foreign3_id}'
               WHERE tid = '{$tid}' AND pid = '{$pid}'";
    }

    if (!mysqli_query($con, $sql)) {
       throw new Exception("Update sync record failed: " . mysqli_error($con));
    }

   $updateFields = [];
   if (isset($data['foreign2_id'])) {
       $updateFields[] = 'foreign2_id';
   }
   if (isset($data['foreign3_id'])) {
       $updateFields[] = 'foreign3_id';
   }

   echo json_encode([
       'status' => 'success',
       'data' => [
           'tid' => $tid,
           'pid' => $pid,
           'updated_fields' => $updateFields,
           'message' => 'Post updated successfully'
       ]
   ]);

} catch (Exception $e) {
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

使用方法

curl --location 'https://d.example.com/update_by_title.php' \
--header 'AccessKey: my-access-key' \
--header 'AccessSecret: my-access-secret' \
--header 'Content-Type: application/json' \
--data '{
  "fid": "2",
  "title": "测试标题",
  "content": "这是一个测试内容8888872",
  "foreign3_id": "123456"
}
'

 这里 "foreign3_id": "123456"也可以改成  "foreign2_id": "123456",当有多个采集站时扩充数据库字段修改代码即可。

推送新帖到企微群api

wechat_pusher.php

 'error', 'message' => 'Missing AccessKey or AccessSecret']));
}

define('ACCESS_KEY', 'my-access-key');
define('ACCESS_SECRET', 'my-access-secret');

if ($headers['accesskey'] !== ACCESS_KEY || $headers['accesssecret'] !== ACCESS_SECRET) {
    http_response_code(403);
    die(json_encode(['status' => 'error', 'message' => 'Invalid AccessKey or AccessSecret']));
}

// 微信机器人 Webhook 地址(替换为你的)
$webhook_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=youruuid';

// 数据库连接配置
$con = mysqli_connect('mysql', 'root', 'pass', 'ultrax', 3306);
if (!$con) {
    die(json_encode(['status' => 'error', 'message' => 'Database connection failed']));
}


// 获取当前时间和30分钟前的时间
$now = date('Y-m-d H:i:s');
$thirty_minutes_ago = date('Y-m-d H:i:s', strtotime('-30 minutes'));

// 查询最近30分钟发布的帖子(只取主帖 position = 1)
$sql = "
    SELECT
        p.tid,
        p.pid,
        t.subject AS title,
        p.message AS content
    FROM
        pre_forum_post p
    JOIN
        pre_forum_thread t ON p.tid = t.tid
    WHERE
        p.dateline >= UNIX_TIMESTAMP('$thirty_minutes_ago')
        AND p.position = 1
        AND NOT EXISTS (
            SELECT 1 FROM pre_wechat_push_log l
            WHERE l.tid = p.tid AND l.pid = p.pid
        )
";

$res = mysqli_query($con, $sql);

if (!$res) {
    die(json_encode(['status' => 'error', 'message' => 'Query failed: ' . mysqli_error($con)]));
}

while ($row = mysqli_fetch_assoc($res)) {
    $title = trim($row['title']);
    $content = trim($row['content']);

    // 屏蔽以“这是主题内容:”开头的帖子
    if (mb_strpos($content, '这是主题内容:') === 0) {
        continue; // 跳过该帖子
    }

    // 替换由反引号包裹的 [img] 标签,并转换为 [图片1](url) 的 Markdown 链接格式
    $imgCount = 0;
    $content = preg_replace_callback(
       '/`\[img\](.+?)\[\/img\]`/is',  // 匹配反引号内的 [img]标签[/img]
       function ($matches) use (&$imgCount) {
           $imgCount++;
           $url = htmlspecialchars($matches[1]);
           return "[图片{$imgCount}]({$url})";
       },
       $content
    );


    // 构造微信消息体(Markdown 格式)
    $message = "### 新帖子通知\n\n**标题:** {$title}\n\n**内容预览:**\n\n" . mb_substr($content, 0, 200);

    $postData = json_encode([
        'msgtype' => 'markdown',
        'markdown' => [
            'content' => $message
        ]
    ]);


    // 发送请求
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $webhook_url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);

    // 记录推送日志
    $insert_sql = "
        INSERT INTO pre_wechat_push_log (tid, pid)
        VALUES ({$row['tid']}, {$row['pid']})
    ";
    if (!mysqli_query($con, $insert_sql)) {
        error_log("Failed to log push record for tid={$row['tid']} pid={$row['pid']}: " . mysqli_error($con));
    }
}

echo json_encode(['status' => 'success', 'message' => 'Push job completed']);
?>

调用

curl --location --request POST 'https://d.example.com/wechat_pusher.php' \
--header 'AccessKey: my-access-key' \
--header 'AccessSecret: my-access-secret' \
--header 'Content-Type: application/json'

这里用post get都可以。

把这3个php文件防弹discuz安装目录下,后续就可以通过调用API实现帖子同步和新帖通知操作了。

你可能感兴趣的:(数据库,php,discuz)