关于塞浦路斯专业咖啡店的简单项目的结论。 first part专注于API微服务,前端站点上的second,第三个在Telegram Bot上。
机器人从简单的任务开始:
- /地图 - 显示咖啡店的地图
- /list - 显示咖啡店清单
- 展示咖啡店详细信息
- /Random - 显示随机咖啡店
- 搜索名称 的咖啡店
- 按位置或/最近的命令搜索最近的咖啡店
在实施过程中,发现电报无法显示带有多个标记的嵌入式地图,也不会在Web版本中发送位置。结果,我不得不将带有地图和存根消息的网站显示指向网站的链接,而不是在空位置上的响应。其他一切都已经完成。
project code开放,bot https://t.me/SpecialtyCoffeeCyBot。
建筑学
经过大量审议,将Nutgram选为机器人的基础:它是最轻巧,简单和现代的图书馆。配置完整的DI容器是一种奖励,使您可以避免手动服务初始化和向客户交付。使用PHP 8.1的实际版本使我编写较少的代码,同时实现性能稍好。通过促进属性,仅阅读属性和严格的键入使开发变得容易得多。
作曲家的设置很简单,如API。最终的composer.json文件。
电报更新是在Webhook端点接收的,并将其传输到命令和消息类型的处理程序。处理程序可以自行响应或请求REST API的数据。对于意外的,有后备,异常和Apierror处理程序。
使用简短的单次动力传输者允许bot's logic仅凝结成32行!
$bot = new Nutgram($_ENV['BOT_TOKEN'], [
'timeout' => $_ENV['CONNECT_TIMEOUT'],
'logger' => ConsoleLogger::class
]);
$bot->setRunningMode(Webhook::class);
$bot->middleware(AuthMiddleware::class);
$bot->fallback(FallbackHandler::class);
$bot->onException(ExceptionHandler::class);
$bot->onApiError(ApiErrorHandler::class);
$bot->onText(NearestCommand::SEND_TEXT, NotSupportedHandler::class);
$bot->onMessageType(MessageTypes::TEXT, SearchHandler::class)->middleware(SearchRequirementsMiddleware::class);
$bot->onMessageType(MessageTypes::LOCATION, LocationHandler::class);
$bot->onMessageType(MessageTypes::NEW_CHAT_MEMBERS, NullHandler::class);
$bot->onMessageType(MessageTypes::LEFT_CHAT_MEMBER, NullHandler::class);
$bot->onCommand(ListCommand::getName(), ListCommand::class)->description(ListCommand::getDescription());
$bot->onCommand(MapCommand::getName(), MapCommand::class)->description(MapCommand::getDescription());
$bot->onCommand(NearestCommand::getName(), NearestCommand::class)->description(NearestCommand::getDescription());
$bot->onCommand(RandomCommand::getName(), RandomCommand::class)->description(RandomCommand::getDescription());
$bot->onCommand(StartCommand::getName(), StartCommand::class)->description(StartCommand::getDescription());
$bot->registerMyCommands();
$http = new Client(['base_uri' => $_ENV['API_URL']]);
$bot->getContainer()->addShared(Client::class, $http);
$bot->run();
/nearest command9示例:
final class NearestCommand extends BaseCommand
{
public const SEND_TEXT = 'Send location';
public static function getName(): string
{
return 'nearest';
}
public static function getDescription(): string
{
return 'Show nearest specialty coffee shop';
}
public function getAnswer(): Answer
{
return new TextAnswer('Send your location to find the nearest coffee shop', [
'reply_markup' => ReplyKeyboardMarkup::make(resize_keyboard: true)->addRow(KeyboardButton::make(self::SEND_TEXT, request_location: true)),
]);
}
}
final class LocationHandler extends BaseHandler
{
private Location $location;
public function __construct(Sender $sender, private readonly ApiService $api)
{
parent::__construct($sender);
}
public function __invoke(Nutgram $bot): ?Message
{
$this->location = $bot->message()->location;
return $this->sender->send($this->getAnswer());
}
/** @inheritDoc */
public function getAnswer(): Answer|array
{
$cafe = $this->api->getNearest((string)$this->location->latitude, (string)$this->location->longitude);
return [
new TextAnswer(Formatter::item($cafe), ['parse_mode' => ParseMode::HTML]),
new VenueAnswer((float)$cafe->latitude, (float)$cafe->longitude, $cafe->name, '', [
'google_place_id' => $cafe->placeId,
'reply_to_message_id' => 0,
'reply_markup' => ['remove_keyboard' => true],
]),
];
}
public function getLocation(): Location
{
return $this->location;
}
public function setLocation(Location $location): LocationHandler
{
$this->location = $location;
return $this;
}
}
使用相同的简短而简洁的middleware来验证搜索数据以及检查消息的合法性。
配置
常见选项和秘密名称存储在.env文件中,而本地覆盖物存储在.env.local文件中。
测试
我很高兴Nutgram库为编写测试提供了足够的可能性。与简单的guzle模拟不同,项目的tests使用模拟的ApiClient,它返回预定义的响应无限的次数。
监视
与API微服务和前端中的Sentry相同的Sentry,在.env中,仅指定了Sentry_dsn的空值(为了清楚起见),然后将实际值写入秘密。
。部署
仍然是同一Fly.io平台,但现在具有300ms载的时间Machines。通常,它是FAA(无服务器),但就我而言,使用PHP服务器,它仍然是常规VM。
我使用了嵌入式PHP服务器,而不是PHP-FPM + NGINX/CADDY +主管的通常组合来加快项目的启动。 Docker图像当然变小,但是我不得不使用一个单独的路由器:
- 仅通过发布请求到bot处理程序
- 将Dev域重新定向。
- 分发静态(robots.txt,favicon.ico等)
- 阻止所有其他请求
final router和Dockerfile(与API部分相同)。
CI/CD
Github Action足够简单:更新机器flyctl deploy
并更新Webhook注册curl -sS ${{ secrets.APP_URL }}/setup.php
。
所有秘密都存储在托管平台上,并在github生产环境中部分复制了Webhook注册。
在此阶段,该机器人已直播,托管在制作中,并向所有用户使用。该项目已完全完成: - )
Bot repository,网站https://specialtycoffee.cy/
全部
整个项目的最终非关键任务:
- 健康检查是否有真正的服务响应,而不仅仅是端口“生存能力”。
- 用球童优化构建
- 尝试buildpack或nixpack
- 用更安全的东西替换内置的PHP服务器
- 添加严格的打字(Typescript)
- 添加API用法统计
- 添加机器人用法统计
- 扩展Google Analytics(分析)中的链接和事件跟踪
- 用更轻,更符合GDPR的东西替换Google Analytics( )