1

善良,这是我的第一个问题,我的英语不是很好。

我可以毫无问题地处理来自 Nexmo API 的标准消息,并且我想像处理标准消息一样接收长 SMS(即在一个块中)。

从 Nexmo 接收的标准 SMS 数据示例:

    $_GET['msisdn'] ==> "33612345678" // "from"
    $_GET['to'] ==> "33687654321"
    $_GET['messageId'] ==> "02000000478EBE09"
    $_GET['text'] ==> "Hello world!"
    $_GET['type'] ==> "unicode"
    $_GET['keyword'] ==> "HELLO"
    $_GET['message-timestamp'] ==> "2014-11-25 14:06:58"

长一:(Nexmo一块一块发)

    $_GET['msisdn'] ==> "33612345678" // "from"
    $_GET['to'] ==> "33687654321"
    $_GET['messageId'] ==> "02000000478EBE09"
    $_GET['text'] ==> "the first part of a too long text..."
    $_GET['type'] ==> "unicode"
    $_GET['keyword'] ==> "THE"
    $_GET['message-timestamp'] ==> "2014-11-25 12:06:58"
    $_GET['concat'] ==> "true"
    $_GET['concat-ref'] ==> "108" // unique identifier for long SMS text
    $_GET['concat-total'] ==> "4" // or more, or less...
    $_GET['concat-part'] ==> "1" // index of the part, start at 1

查看更多关于 Nexmo 文档的信息:这里

所以,我从 github 上的一个库开始(Nexmo-PHP-lib)并这样做了:(很丑,但它是为了测试目的)

public function inboundText( $data=null ){
    if(!$data) $data = $_GET;

    if(!isset($data['text'], $data['msisdn'], $data['to'])) 
        return false;
    if(isset($data['concat']) && $data['concat'])
    {
        session_start();
        if ($data['concat-part'] > 1) // first part ?
        {
            if ($data['concat-total'] == $data['concat-part']) // last part ?
            {
                // last part ! stock the data in the text and unset now useless $_SESSION entry!
                $data['text'] = $_SESSION[(string)$data['concat-ref']] . $data['text'];
                unset($_SESSION[(string)$data['concat-ref']]);
            }
            else // not the first or the last, so concat !
            {
                // concat the new part in the entry named after the concat-ref
                $_SESSION[(string)$data['concat-ref']] .= $data['text'];
                return false;
            }
        }
        else // first part ! so creat a $_SESSION entry for that! (named after concat-ref)
        {
            $_SESSION[(string)$data['concat-ref']] = $data['text'];
            return false;
        }
    }
    // Get the relevant data
    $this->to = $data['to'];
    $this->from = $data['msisdn'];
    $this->text = $data['text'];
    $this->network = (isset($data['network-code'])) ? $data['network-code'] : '';
    $this->message_id = $data['messageId'];

    // Flag that we have an inbound message
    $this->inbound_message = true;

    return true;
}

它与本地测试完美配合,但当它托管在我的heroku服务器上时,$_SESSION 数组似乎在短信的每个部分都被重置了......

那么,您知道如何正确处理它吗?(并且没有丑陋的临时 SQL 表)。在我完全收到之前,如何检索消息的前一部分?

4

1 回答 1

3

仅当客户端和服务器之间使用标识会话的每个 HTTP 请求交换密钥时,使用会话来存储临时 sms 部分才有效。

在 PHP 中,当您创建会话并在文件中存储值时,会$_SESSION在服务器上创建一个文件来存储此数据(除非您使用 DB 会话处理程序,在这种情况下会话数据存储在数据库中)。该数据使用标识符(例如PHPSESSID=66dda288eb1947843c2341b4e470fa28)引用,该标识符通常作为 cookie 提供给客户端。当客户端在下一个 HTTP 请求返回时,标识符作为 cookie 值返回给服务器,服务器使用它来引用相同的会话数据。

虽然您的服务器在连接到您的端点 URL 时可能会向 Nexmo 客户端提供 cookie,但 Nexmo 可能不会存储 cookie 并在下一个请求中返回它。(这是我的一个假设,但我认为这是一个安全的假设——尽管我无法解释为什么它可以在您的本地计算机上运行,​​但在 Heroku 上却不行。无论如何,它很容易测试——只需检查 Nexmo 客户端是否提供$_COOKIE后续请求期间的任何值)。

底线:如果 Nexmo 客户端不使用 cookie 来保存请求之间的状态,则不能使用会话来保存临时 sms 部分。

在任何情况下,一个更好的选择(因为临时保存的消息部分会在服务器重新启动的情况下持续存在)是将每个临时部分保存到一个小型数据库表中。(别担心,这是一个漂亮的临时 SQL 表。;)这是一个使用 MySQL 的示例:

CREATE TABLE `response_concat_tbl` (
    `concat-ref` SMALLINT NOT NULL DEFAULT '0',
    `concat-total` TINYINT UNSIGNED NOT NULL DEFAULT '0',
    `concat-part` TINYINT UNSIGNED NOT NULL DEFAULT '0',
    `text` VARCHAR(160) NOT NULL DEFAULT '',
    `added_datetime` DATETIME DEFAULT NULL,
    UNIQUE KEY `concat-ref` (`concat-ref`, `concat-part`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='A place to temporarily store multipart SMS messages before they are combined and saved as a whole response.';

在这里,我定义了一个UNIQUE KEY,因此我们可以避免使用REPLACE INTO子句重复消息部分(以防 Nexmo 客户端尝试提交相同的部分两次)。可added_datetime用于在将来清理孤立的消息(以防消息的最后部分从未收到)。

现在让我们插入一些示例数据:

REPLACE INTO response_concat_tbl (`concat-ref`,`concat-total`,`concat-part`,text,added_datetime) VALUES ('101','4','1','This is',NOW());
REPLACE INTO response_concat_tbl (`concat-ref`,`concat-total`,`concat-part`,text,added_datetime) VALUES ('101','4','2','a multipart',NOW());
REPLACE INTO response_concat_tbl (`concat-ref`,`concat-total`,`concat-part`,text,added_datetime) VALUES ('101','4','4','for you!',NOW());
REPLACE INTO response_concat_tbl (`concat-ref`,`concat-total`,`concat-part`,text,added_datetime) VALUES ('101','4','3','sms message',NOW());

现在我们可以使用 MySQL 的GROUP_CONCAT函数在一个查询中获取所有部分。

SET SESSION group_concat_max_len = 1000000;
SELECT GROUP_CONCAT(text ORDER BY `concat-part` ASC SEPARATOR ' ') AS text
FROM response_concat_tbl
WHERE `concat-ref` = '101'
GROUP BY `concat-ref`;

我们设置了group_concat_max_len这样的设置,因此总字符串长度可能比默认的 1024 个字符长(尽管这已经是很多消息了)。结果如下:

+-------------------------------------------------------------+
| GROUP_CONCAT(text ORDER BY `concat-part` ASC SEPARATOR ' ') |
+-------------------------------------------------------------+
| This is a multipart sms message for you!                    |
+-------------------------------------------------------------+

REPLACE INTO如果您不使用 MySQL,则可能需要在不使用and的情况下做更多工作(一些重复检查,然后是循环)GROUP_CONCAT

以下是使用此技术的完整工作示例:

class SMS
{
    static public function processMultipart($sms)
    {
        $db =& DB::getInstance();

        if (isset($sms['concat']) && $sms['concat']) {
            // This sms is part of a multipart message, save it temporarily to the db.
            $sms = array_map('trim', $sms);
            $db->query("
                REPLACE INTO response_concat_tbl (
                    `concat-ref`,
                    `concat-total`,
                    `concat-part`,
                    `text`,
                    `added_datetime`
                ) VALUES (
                    '" . $db->escapeString($sms['concat-ref']) . "',
                    '" . $db->escapeString($sms['concat-total']) . "',
                    '" . $db->escapeString($sms['concat-part']) . "',
                    '" . $db->escapeString($sms['text']) . "',
                    NOW()
                )
            ");

            if ($sms['concat-total'] > $sms['concat-part']) {
                // Not all the parts have been received; return false to signal the fact we don't have a complete message yet.
                return false;
            }
            // Otherwise, it means the last part has just been received. Concatonate all the parts and return it.

            // Increase the max length returned by MySQL's GROUP_CONCAT function.
            $db->query("SET SESSION group_concat_max_len = 32000");

            // Group the sms responses by concat-ref and return them as a concatonated string.
            $qid = $db->query("
                SELECT GROUP_CONCAT(text ORDER BY `concat-part` ASC SEPARATOR ' ')
                FROM response_concat_tbl
                WHERE `concat-ref` = '" . $db->escapeString($sms['concat-ref']) . "'
                GROUP BY `concat-ref`
            ");
            list($sms['text']) = $db->fetch_row($qid);

            // Delete the temporary records.
            $db->query("
                DELETE FROM response_concat_tbl
                WHERE `concat-ref` = '" . $db->escapeString($sms['concat-ref']) . "'
            ");
        }

        // If this is not a multipart message, the original sms data is returned. If it is a multipart message, we're returning the fully-concatonated message.
        return $sms;
    }
}

以下是如何使用此功能:

// False is returned here if we need to wait for additional parts to arrive. Otherwise, $sms is populated with the final, usable data.
if (false === $sms = SMS::processMultipart($_GET)) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 200 OK', true, 200);
    die('Waiting for additional message parts');
}

// Do something with $sms['text'], e.g.,
SMSResponse::save($sms['text']);
于 2014-11-26T14:37:51.913 回答