首次提交 Laravel 專案

This commit is contained in:
2025-05-18 08:52:56 +07:00
commit 5e392ae581
264 changed files with 88756 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use App\Models\tg_groups;
use App\Models\tg_group_users;
use App\Models\tg_Msg;
use App\Models\TgGroups;
use App\Models\TgGroupUsers;
use App\Models\TgMsg;
use App\Services\TgApi;
use App\Services\TgComm;
use App\Services\UserFun;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class TgController extends Controller
{
public function Msg($token,Request $request)
{
// 立即回应 Telegram(避免超时)
response()->json(['ok' => true])->send();
flush(); // 立即把回应送出
// 接着执行后续处理(异步逻辑、写 log、存数据库等)
$data = $request->all();
$data='{"update_id": 250041637, "message_date": 1746710794, "message_text": "@winchen1688 +2000", "message_chat_id": -4661252768, "message_from_id": 5909523353, "message_chat_type": "group", "message_chat_title": "一路发测试群", "message_message_id": 8, "message_from_is_bot": false, "message_from_username": "winchen1688", "message_from_last_name": "阿江", "message_entities_0_type": "mention", "message_from_first_name": "流浪的", "message_entities_0_length": 12, "message_entities_0_offset": 0, "message_from_language_code": "zh-hans", "message_chat_all_members_are_administrators": true, "message_chat_accepted_gift_types_unique_gifts": false, "message_chat_accepted_gift_types_limited_gifts": false, "message_chat_accepted_gift_types_unlimited_gifts": false, "message_chat_accepted_gift_types_premium_subscription": false}';
$msg=UserFun::Json2Arr($data); //将JSON转换成一維扁平陣列
$msg['token']=$token;
$tgmsg=TgMsg::storeFromWebhookJson($msg); //写入对话消息
$UserResponse=TgApi::GetChatMember($msg); //获取用户资料
$TgGroupUsers=TgGroupUsers::updateOrCreateFromTelegram($UserResponse['raw'], $msg['message_chat_id']); //更新写入用户资料
$chatResponse=TgApi::GetChatInfo($msg); //获取群资料
if($chatResponse['ok'] && isset($chatResponse['result']['id'])){
$chatData = $chatResponse['result'];
$chatModel = TgGroups::updateOrCreateFromTelegram($chatData); //更新写入群资料
$isComm=TgComm::TgComm($msg); //判断是不是指令之一
if ($isComm['ok']){ //如果是指令进行指令的处理
$botMsg = TgComm::{'comm_' . $isComm['id']}($msg);
if ($botMsg) {
}
}
return true;
} else {
return true;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
/**
* 全局中间件
*/
protected $middleware = [
// 你可以在这里加全局中间件
];
/**
* 路由中间件群组
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use App\Services\UserFun;
use Illuminate\Database\Eloquent\Model;
class Ledger extends Model
{
// 显式指定表名
protected $table = 'ledger';
protected $guarded = ['id'];
// 时间字段类型自动转为 Carbon 实例
protected $casts = [
'date' => 'datetime',
'amount' => 'decimal:2',
];
public static function AddIncome($data){
$ledger= new Ledger();
$ledger->chat_id=$data['chat_id'];
$ledger->user_id=$data['user_id'];
$ledger->date=time();
$ledger->amount=$data['amount'];
if($ledger->save()){
return UserFun::success();
} else {
return UserFun::error('资料写入失败');
}
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TgBot extends Model
{
// 显式指定表名
protected $table = 'tg_bot';
protected $guarded = ['id'];
// 可批量赋值的字段
protected $fillable = [
'bot_id',
'bot_token',
'bot_username',
'subjection_username',
'subjection_mobile',
'subjection',
'webhook',
'memo',
];
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
class TgComm extends Model
{
// 显式指定表名
protected $table = 'tg_comm';
protected $guarded = ['id'];
//将所有的指令转换成数组文字档
public static function TgComm2Arr(){
$data = tg_comm::all()->toArray();
$export = '<?php return ' . var_export($data, true) . ';';
Storage::disk('dataconfig')->put('TgComm.php', $export);
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TgGroupUsers extends Model
{
protected $table = 'tg_group_users';
protected $guarded = ['id'];
// 时间字段类型自动转为 Carbon 实例
protected $casts = [
'raw_data' => 'array',
];
// Telegram status 常量定义
public const STATUS_CREATOR = 'creator'; // 群主
public const STATUS_ADMINISTRATOR = 'administrator'; // 管理员
public const STATUS_MEMBER = 'member'; // 普通成员
public const STATUS_RESTRICTED = 'restricted'; // 被限制的成员
public const STATUS_LEFT = 'left'; // 已离开
public const STATUS_KICKED = 'kicked'; // 被踢出
// 可选:提供一个状态中文对照表(静态方法)
public static function statusLabel(string $status): string
{
return match ($status) {
self::STATUS_CREATOR => '群主',
self::STATUS_ADMINISTRATOR => '管理员',
self::STATUS_MEMBER => '成员',
self::STATUS_RESTRICTED => '受限成员',
self::STATUS_LEFT => '已离开',
self::STATUS_KICKED => '被踢出',
default => '未知',
};
}
public static function updateOrCreateFromTelegram(array $chatMemberData, int|string $chatId): self
{
// 改进的安全判断方式
if (
!isset($chatMemberData['user']) ||
!is_array($chatMemberData['user']) ||
!isset($chatMemberData['user']['id']) ||
!isset($chatMemberData['status'])
) {
throw new \InvalidArgumentException('无效的 Telegram ChatMember 数据结构');
}
$user = $chatMemberData['user'];
return self::updateOrCreate(
[
'chat_id' => $chatId,
'user_id' => $user['id'],
],
[
'first_name' => $user['first_name'] ?? null,
'last_name' => $user['last_name'] ?? null,
'username' => $user['username'] ?? null,
'status' => $chatMemberData['status'],
'is_bot' => $user['is_bot'] ?? false,
'language_code' => $user['language_code'] ?? null,
'raw_data' => json_encode($chatMemberData, JSON_UNESCAPED_UNICODE),
]
);
}
public static function GetUsername($username){
$res=TgGroupUsers::where('username', $username)->first();
if($res){
return $res;
} else {
return false;
}
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TgGroups extends Model
{
protected $table = 'tg_groups';
public $incrementing = false; // Telegram 的 chat_id 是 bigint,可為負數
protected $keyType = 'string';
protected $guarded = ['id'];
protected $casts = [
'chat_id' => 'integer',
'type' => 'string',
'title' => 'string',
'username' => 'string',
'description' => 'string',
'invite_link' => 'string',
'photo_small_file_id' => 'string',
'photo_big_file_id' => 'string',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public static function updateOrCreateFromTelegram(array $chatData): self
{
return self::updateOrCreate(
['chat_id' => $chatData['id']],
[
'type' => $chatData['type'] ?? null,
'title' => $chatData['title'] ?? null,
'creator' => $chatData['creator'] ?? null,
'invite_link' => $chatData['invite_link'] ?? null,
'photo_small_file_id' => $chatData['photo']['small_file_id'] ?? null,
'photo_big_file_id' => $chatData['photo']['big_file_id'] ?? null,
]
);
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use App\Services\UserFun;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class TgMsg extends Model
{
protected $table = 'tg_msg';
protected $guarded = ['id'];
protected $casts = [
'message_date' => 'datetime',
'raw' => 'array',
];
public static function storeFromWebhookJson(string|array $json): self
{
// 當$json是 string 時才做 Json2Arr,否則直接當成已展平的資料用
$data = is_array($json) ? $json : UserFun::Json2Arr($json);
// 要儲存的欄位清單
$fields = collect($data)->only([
'message_message_id',
'message_from_id',
'message_from_is_bot',
'message_from_first_name',
'message_from_last_name',
'message_from_username',
'message_from_language_code',
'message_chat_id',
'message_chat_title',
'message_chat_type',
'message_chat_all_members_are_administrators',
'message_text',
'message_entities_0_offset',
'message_entities_0_length',
'message_entities_0_type',
'token',
])->toArray();
// 處理 message_date (timestamp -> datetime)
if (isset($data['message_date'])) {
$fields['message_date'] = date('Y-m-d H:i:s', $data['message_date']);
}
// 原始扁平化資料存入 raw 欄位
$fields['raw'] = $data;
return self::updateOrCreate(
['update_id' => $data['update_id']],
$fields
);
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Models;
use App\Services\UserFun;
use Illuminate\Database\Eloquent\Model;
class UserAccount extends Model
{
// 显式指定表名
protected $table = ' user_account';
protected $guarded = ['id'];
// 时间字段类型自动转为 Carbon 实例
protected $casts = [
'date' => 'datetime',
'amount' => 'decimal:2',
'balance' => 'decimal:2',
];
//检查账号是否存在
public static function CheckUserId($user){
return UserAccount::where([
'chat_id' => $user['chat_id'],
'user_id' => $user['user_id'],
])->exists();
}
//更新账号资料
public static function UpdateUser($user){
UserAccount::where([
'chat_id' => $user['chat_id'],
'user_id' => $user['user_id'],
])->increment('balance', $user['amount']);
$balance = UserAccount::where([
'chat_id' => $user['chat_id'],
'user_id' => $user['user_id'],
])->value('balance');
return $balance;
}
//新建账号
public static function CreatUser($user){
$UserAccount=new UserAccount();
$UserAccount->chat_id=$user['chat_id'];
$UserAccount->user_id=$user['user_id'];
$UserAccount->username=$user['username'];
$UserAccount->stime=now();
$UserAccount->balance=$user['amount'];
$UserAccount->subjection=$user['token'];
$UserAccount->save();
return $user['amount'];
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Schema::defaultStringLength(191); // 防止旧 MySQL 出现索引超长报错
// 强制所有表使用 utf8mb4_0900_ai_ci
// Schema::defaultCharset('utf8mb4');
// Schema::defaultCollation('utf8mb4_0900_ai_ci');
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Services;
class TgApi{
//获取群成员资料
public static function GetChatMember($msg){
$ApiUrl= "https://api.telegram.org/bot{$msg['token']}/getChatMember?chat_id={$msg['message_chat_id']}&user_id={$msg['message_from_id']}";
$ChatInfo=UserFun::CurlGet($ApiUrl);
if ($ChatInfo['ok'] && isset($ChatInfo['result'])) {
// 成功取得群成员资料
return [
'ok' => true,
'user_id' => $ChatInfo['result']['user']['id'],
'first_name' => $ChatInfo['result']['user']['first_name'] ?? null,
'username' => $ChatInfo['result']['user']['username'] ?? null,
'status' => $ChatInfo['result']['status'], // 例如 creator / administrator / member / left / kicked
'raw' => $ChatInfo['result'] // 可选:保留原始数据
];
} else {
// 获取失败,回传错误信息
return [
'ok' => false,
'error' => $ChatInfo['error'] ?? '未知错误',
'raw' => $ChatInfo
];
}
}
//获取群资料
public static function GetChatInfo($msg){
$ApiUrl= "https://api.telegram.org/bot{$msg['token']}/getChat?chat_id={$msg['message_chat_id']}";
$ChatInfo=UserFun::CurlGet($ApiUrl);
if($ChatInfo['ok'] && isset($ChatInfo['result']['id'])){
$ApiUrl= "https://api.telegram.org/bot{$msg['token']}/getChatAdministrators?chat_id={$ChatInfo['result']['id']}";
$AdminInfo=UserFun::CurlGet($ApiUrl);
if ($AdminInfo['ok'] && isset($AdminInfo['result'])) {
foreach ($AdminInfo['result'] as $admin) {
if (isset($admin['status']) && $admin['status'] === 'creator') {
// 找到群主,提取 user_id
$ChatInfo['result']['creator'] = $admin['user']['id'];
break;
}
}
}
}
return $ChatInfo;
}
//获取机器人资料
public static function getMe($msg){
$ApiUrl= "https://api.telegram.org/bot{$msg['token']}/getMe";
$BotInfo=UserFun::CurlGet($ApiUrl);
if($BotInfo['ok'] && isset($BotInfo['result']['id'])){
return [
'success' => true,
'bot_id' => $BotInfo['result']['id'],
'username' => $BotInfo['result']['username'] ?? null,
'first_name' => $BotInfo['result']['first_name'] ?? null,
'can_join_groups' => $BotInfo['result']['can_join_groups'] ?? null,
'can_read_all_group_messages' => $BotInfo['result']['can_read_all_group_messages'] ?? null,
'supports_inline_queries' => $BotInfo['result']['supports_inline_queries'] ?? null,
];
} else {
return [
'success' => false,
'error' => $BotInfo['description'] ?? 'Unknown error',
];
}
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Services;
use App\Models\Ledger;
use App\Models\tg_comm;
use App\Models\TgGroupUsers;
use App\Models\UserAccount;
use Illuminate\Support\Facades\Storage;
class TgComm
{
public static function TgComm($msg){
$tg_comm = include Storage::disk('dataconfig')->path('TgComm.php');
foreach ($tg_comm as $v) {
if (str_starts_with($msg['message_text'], $v['command'])){
$res['ok']= true;
$res['id']= $v['id'];
$res['command']= $v['command'];
return $res; //有的话 返回指令ID
}
}
}
public static function comm_1($msg){
}
//@’指令的处理
public static function comm_2($msg){
$res = self::parseLedgerMessage($msg['message_text']); //
if ($res) {
$TgGroupUsers=TgGroupUsers::GetUsername($res['usernamer']);
if ($TgGroupUsers) {
$res=array_merge($res, ['user_id' => $TgGroupUsers->user_id,'chat_id'=>$msg['message_chat_id'],'token'=>$msg['token']]); //加入用户和群组ID
$Ledger=Ledger::AddIncome($res); //写入收支记录
$CheckUser=UserAccount::CheckUserId($res); //检查该user_id是否有账户资料,有的话处理余额,没有的话添加相关账户资料
if ($CheckUser){
$balance=Ledger::UpdateUser($res); //更新账号余额
$data=array_merge($res,['comm' => 2,'balance' => $balance,'token' => $msg['token'],'chat_id'=>$msg['chat_id']]); //
$Msg=TgMsg::MakeMsg($data); //构成发送数据
return $Msg;
} else {
$balance=UserAccount::CreatUser($res); //新建用户账号,并写入充值金额
$res=array_merge($res,['balance' => $balance]);
//发送Tg通知
}
} else {
return $res['usernamer'].'-该用户不存在';
}
} else {
return false;
}
}
//拆解【@XXX +1000】字串
private static function parseLedgerMessage(string $text): ?array
{
if (preg_match('/@(\w+)\s*([+-])\s*(\d+(?:\.\d+)?)/', $text, $matches)) {
$usernamer = $matches[1];
$type = $matches[2] === '+' ? '0' : '1';
$amount = (float) str_replace(',', '', $matches[3]);
return compact('usernamer', 'type', 'amount');
}
return null; // 格式不符
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Services;
class TgMsg
{
public static function MakeMsg($data){
$url = "https://api.telegram.org/bot" . $data['token'] . "/sendMessage";
// $keyb['inline_keyboard'][0][0]['text'] = '点击查询账单';
// $keyb['inline_keyboard'][0][0]['url'] = 'https://'.$BotUrl.'/bill/' . $data['message_from_id'] . '/' . $data['message_chat_id'] . '/T/Inquiry';
// $keyb = json_encode($keyb);
$MsgTxt['chat_id'] = $data['chat_id'];
// $MsgTxt['reply_markup'] = $keyb;
$MsgTxt['text'] = self::{'comm_' . $data['comm']}($data);
}
public static function Comm_1($data) {
}
public static function Comm_2($data) {
$Msg = $data['username'].'-你已经成功充值 '.$data['amount'].',当前余额为'.$data['balance'];
return $Msg;
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Services;
class UserFun
{
public static function success($code = 200, $message = 'ok') {
return response()->json([
'code' => $code,
'message' => $message,
]);
}
public static function error($code = 500,$message = 'error') {
return response()->json([
'code' => $code,
'message' => $message,
]);
}
public static function Json2Arr($data, $prefix = '') {
// 如果傳進來的是字串,就 decode 一次
if (is_string($data)) {
$arr = json_decode($data, true);
if (!is_array($arr)) {
return []; // 無效 JSON,回傳空陣列或你可自訂錯誤處理
}
} elseif (is_array($data)) {
$arr = $data;
} else {
return []; // 不是 JSON 字串也不是陣列,回傳空
}
$result = [];
foreach ($arr as $key => $value) {
$fullKey = $prefix === '' ? $key : "{$prefix}_{$key}";
if (is_array($value)) {
$result += self::Json2Arr($value, $fullKey);
} else {
$result[$fullKey] = $value;
}
}
return $result;
}
public static function CurlGet(string $url): array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); // 设定请求网址
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 回传结果而不是直接输出
$response = curl_exec($ch); // 执行请求
$err = curl_error($ch); // 错误信息
curl_close($ch); // 关闭连接
if ($err) {
return ['ok' => false, 'error' => $err];
}
return json_decode($response, true);
}
public static function CurlPost(string $url, array $data = []): array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); // 設定請求網址
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 回傳內容不直接輸出
curl_setopt($ch, CURLOPT_POST, true); // 啟用 POST
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); // POST的資料
$response = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
return ['ok' => false, 'error' => $err];
}
return json_decode($response, true);
}
}