Ver código fonte

feat(module): 实现验证码发送和验证功能,并添加商品购买逻辑

- 在 CheckCodeHandler 中实现了验证码验证逻辑
- 在 SendSmsHandler 中实现了验证码发送逻辑
- 在 BuyHandler 中实现了商品购买逻辑
- 添加了参数验证、错误处理和日志记录
- 使用了事务来确保购买操作的数据一致性
Your Name 8 meses atrás
pai
commit
941a84d4b6

+ 80 - 1
app/Module/AppGame/Handler/Public/CheckCodeHandler.php

@@ -3,9 +3,14 @@
 namespace App\Module\AppGame\Handler\Public;
 
 use App\Module\AppGame\Handler\BaseHandler;
+use App\Module\Sms\Enums\CODE_TYPE;
+use App\Module\Sms\Services\SmsService;
 use Google\Protobuf\Internal\Message;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
 use Uraus\Kku\Request\RequestPublicCheckCode;
 use Uraus\Kku\Response\ResponsePublicCheckCode;
+use UCore\Exception\LogicException;
 
 /**
  * 处理验证码校验请求
@@ -29,7 +34,81 @@ class CheckCodeHandler extends BaseHandler
         // 创建响应对象
         $response = new ResponsePublicCheckCode();
 
-        // TODO: 实现具体逻辑
+        try {
+            // 获取请求参数
+            $code = $data->getCode();
+
+            // 参数验证
+            if (empty($code)) {
+                throw new LogicException("验证码不能为空");
+            }
+
+            // 从会话中获取手机号和验证码类型
+            $phone = session('verify_phone');
+            $type = session('verify_type');
+
+            if (empty($phone)) {
+                throw new LogicException("请先发送验证码");
+            }
+
+            if (empty($type)) {
+                throw new LogicException("验证码类型无效");
+            }
+
+            // 将type转换为CODE_TYPE枚举
+            $codeType = match ((int)$type) {
+                1 => CODE_TYPE::REGISTER,
+                2 => CODE_TYPE::LOGIN,
+                3 => CODE_TYPE::RESET_PASSWORD,
+                default => throw new LogicException("无效的验证码类型")
+            };
+
+            // 验证验证码
+            $smsService = app(SmsService::class);
+            $isValid = $smsService->verifyCode($codeType, $phone, $code);
+
+            if (!$isValid) {
+                throw new LogicException("验证码无效或已过期");
+            }
+
+            // 生成临时令牌
+            $token = Str::random(32);
+
+            // 将令牌与手机号关联并存储在会话中
+            session(['verify_token' => $token]);
+            session(['verified_phone' => $phone]);
+
+            // 设置响应
+            $response->setToken($token);
+            $this->response->setCode(0);
+            $this->response->setMsg('验证码验证成功');
+
+            // 记录日志
+            Log::info('验证码验证成功', [
+                'phone' => $phone,
+                'type' => $type,
+                'token' => $token
+            ]);
+
+        } catch (LogicException $e) {
+            // 设置错误响应
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('验证码验证失败', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        } catch (\Exception $e) {
+            // 设置错误响应
+            $this->response->setCode(500);
+            $this->response->setMsg('系统错误,请稍后再试');
+
+            Log::error('验证码验证异常', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        }
 
         return $response;
     }

+ 77 - 1
app/Module/AppGame/Handler/Public/SendSmsHandler.php

@@ -3,9 +3,14 @@
 namespace App\Module\AppGame\Handler\Public;
 
 use App\Module\AppGame\Handler\BaseHandler;
+use App\Module\Sms\Enums\CODE_TYPE;
+use App\Module\Sms\Services\SmsService;
 use Google\Protobuf\Internal\Message;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
 use Uraus\Kku\Request\RequestPublicSendSms;
 use Uraus\Kku\Response\ResponsePublicSendSms;
+use UCore\Exception\LogicException;
 
 /**
  * 处理发送验证码请求
@@ -29,7 +34,78 @@ class SendSmsHandler extends BaseHandler
         // 创建响应对象
         $response = new ResponsePublicSendSms();
 
-        // TODO: 实现具体逻辑
+        try {
+            // 获取请求参数
+            $type = $data->getType();
+            $mobile = $data->getMobile();
+
+            // 参数验证
+            if (empty($mobile)) {
+                throw new LogicException("手机号不能为空");
+            }
+
+            if (empty($type)) {
+                throw new LogicException("验证码类型不能为空");
+            }
+
+            // 验证手机号格式
+            if (!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
+                throw new LogicException("手机号格式不正确");
+            }
+
+            // 将type转换为CODE_TYPE枚举
+            $codeType = match ($type) {
+                1 => CODE_TYPE::REGISTER,
+                2 => CODE_TYPE::LOGIN,
+                3 => CODE_TYPE::RESET_PASSWORD,
+                default => throw new LogicException("无效的验证码类型")
+            };
+
+            // 生成临时令牌
+            $token = Str::random(32);
+
+            // 发送验证码
+            $smsService = app(SmsService::class);
+            $result = $smsService->sendCode($codeType, $mobile, $token);
+
+            if (!$result) {
+                throw new LogicException("验证码发送失败,请稍后再试");
+            }
+
+            // 将手机号和验证码类型存储在会话中,供验证时使用
+            session(['verify_phone' => $mobile]);
+            session(['verify_type' => $type]);
+
+            // 设置响应
+            $this->response->setCode(0);
+            $this->response->setMsg('验证码发送成功');
+
+            // 记录日志
+            Log::info('验证码发送成功', [
+                'mobile' => $mobile,
+                'type' => $type,
+                'token' => $token
+            ]);
+
+        } catch (LogicException $e) {
+            // 设置错误响应
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('验证码发送失败', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        } catch (\Exception $e) {
+            // 设置错误响应
+            $this->response->setCode(500);
+            $this->response->setMsg('系统错误,请稍后再试');
+
+            Log::error('验证码发送异常', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        }
 
         return $response;
     }

+ 145 - 1
app/Module/AppGame/Handler/Shop/BuyHandler.php

@@ -3,9 +3,18 @@
 namespace App\Module\AppGame\Handler\Shop;
 
 use App\Module\AppGame\Handler\BaseHandler;
+use App\Module\Fund\Enums\LOG_TYPE;
+use App\Module\Fund\Service\User as FundUser;
+use App\Module\GameItems\Services\ItemService;
+use App\Module\Shop\Models\ShopItem;
 use Google\Protobuf\Internal\Message;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Uraus\Kku\Common\DataItem;
+use Uraus\Kku\Common\LastData;
 use Uraus\Kku\Request\RequestShopBuy;
 use Uraus\Kku\Response\ResponseShopBuy;
+use UCore\Exception\LogicException;
 
 /**
  * 处理商品购买请求
@@ -29,7 +38,142 @@ class BuyHandler extends BaseHandler
         // 创建响应对象
         $response = new ResponseShopBuy();
 
-        // TODO: 实现具体逻辑
+        try {
+            // 获取请求参数
+            $goodId = $data->getGoodId();
+            $number = $data->getNumber();
+            $userId = $this->user_id;
+
+            // 参数验证
+            if ($goodId <= 0) {
+                throw new LogicException("商品ID无效");
+            }
+
+            if ($number <= 0) {
+                throw new LogicException("购买数量必须大于0");
+            }
+
+            // 获取商品信息
+            $shopItem = ShopItem::findOrFail($goodId);
+
+            if (!$shopItem->is_active) {
+                throw new LogicException("该商品已下架");
+            }
+
+            // 检查购买限制
+            if ($shopItem->max_buy > 0) {
+                // 获取用户已购买数量
+                $boughtCount = $shopItem->getUserBoughtCount($userId);
+
+                if ($boughtCount + $number > $shopItem->max_buy) {
+                    throw new LogicException("超出购买限制,最多还能购买" . ($shopItem->max_buy - $boughtCount) . "个");
+                }
+            }
+
+            // 计算总价
+            $totalPrice = $shopItem->price * $number;
+
+            // 开始事务
+            DB::beginTransaction();
+
+            // 扣除用户货币
+            $fundResult = FundUser::handle(
+                $userId,
+                $shopItem->currency_id,
+                -$totalPrice,
+                LOG_TYPE::TRADE,
+                $goodId,
+                "购买商品:{$shopItem->name} x {$number}"
+            );
+
+            if (is_string($fundResult)) {
+                throw new LogicException("购买失败:" . $fundResult);
+            }
+
+            // 添加物品到用户背包
+            ItemService::addItem(
+                $userId,
+                $shopItem->item_id,
+                $number * $shopItem->item_quantity,
+                [
+                    'source_type' => 'shop_buy',
+                    'source_id' => $goodId,
+                    'details' => [
+                        'shop_item_id' => $goodId,
+                        'shop_item_name' => $shopItem->name,
+                        'price' => $shopItem->price,
+                        'quantity' => $number
+                    ]
+                ]
+            );
+
+            // 记录购买记录
+            $shopItem->recordPurchase($userId, $number, $totalPrice);
+
+            // 提交事务
+            DB::commit();
+
+            // 创建LastData对象,用于返回物品信息
+            $lastData = new LastData();
+            $itemList = [];
+
+            // 创建物品数据
+            $dataItem = new DataItem();
+            $dataItem->setItemId($shopItem->item_id);
+            $dataItem->setQuantity($number * $shopItem->item_quantity);
+            $itemList[] = $dataItem;
+
+            // 设置物品列表到LastData
+            $lastData->setItems($itemList);
+
+            // 设置LastData到响应
+            $this->response->setLastData($lastData);
+
+            // 设置响应状态
+            $this->response->setCode(0);
+            $this->response->setMsg('购买成功');
+
+            // 记录日志
+            Log::info('用户购买商品成功', [
+                'user_id' => $userId,
+                'good_id' => $goodId,
+                'number' => $number,
+                'total_price' => $totalPrice,
+                'item_id' => $shopItem->item_id,
+                'item_quantity' => $shopItem->item_quantity
+            ]);
+
+        } catch (LogicException $e) {
+            // 回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
+            // 设置错误响应
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('用户购买商品失败', [
+                'user_id' => $this->user_id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        } catch (\Exception $e) {
+            // 回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
+            // 设置错误响应
+            $this->response->setCode(500);
+            $this->response->setMsg('系统错误,请稍后再试');
+
+            Log::error('购买商品操作异常', [
+                'user_id' => $this->user_id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        }
 
         return $response;
     }

+ 129 - 0
app/Module/Shop/Models/ShopItem.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace App\Module\Shop\Models;
+
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Support\Facades\DB;
+use UCore\ModelCore;
+use App\Module\GameItems\Models\Item;
+
+/**
+ * 商店物品模型
+ *
+ * field start 
+ * @property   int  $id  商品ID,主键
+ * @property   string  $name  商品名称
+ * @property   string  $description  商品描述
+ * @property   int  $item_id  关联的物品ID,外键关联kku_item_items表
+ * @property   int  $item_quantity  物品数量
+ * @property   int  $price  价格
+ * @property   int  $currency_id  货币类型ID
+ * @property   int  $max_buy  最大购买数量(0表示无限制)
+ * @property   int  $is_active  是否激活(0:否, 1:是)
+ * @property   int  $sort_order  排序权重
+ * @property   string  $start_time  上架时间
+ * @property   string  $end_time  下架时间
+ * @property   \Carbon\Carbon  $created_at  创建时间
+ * @property   \Carbon\Carbon  $updated_at  更新时间
+ * field end
+ */
+class ShopItem extends ModelCore
+{
+    /**
+     * 与模型关联的表名
+     *
+     * @var string
+     */
+    protected $table = 'shop_items';
+
+    /**
+     * 可批量赋值的属性
+     *
+     * @var array
+     */
+    protected $fillable = [
+        'name',
+        'description',
+        'item_id',
+        'item_quantity',
+        'price',
+        'currency_id',
+        'max_buy',
+        'is_active',
+        'sort_order',
+        'start_time',
+        'end_time',
+    ];
+
+    /**
+     * 应该被转换为日期的属性
+     *
+     * @var array
+     */
+    protected $dates = [
+        'start_time',
+        'end_time',
+        'created_at',
+        'updated_at',
+    ];
+
+    /**
+     * 应该被转换为原生类型的属性
+     *
+     * @var array
+     */
+    protected $casts = [
+        'is_active' => 'boolean',
+    ];
+
+    /**
+     * 获取关联的物品
+     *
+     * @return BelongsTo
+     */
+    public function item(): BelongsTo
+    {
+        return $this->belongsTo(Item::class, 'item_id');
+    }
+
+    /**
+     * 获取用户已购买数量
+     *
+     * @param int $userId 用户ID
+     * @return int 已购买数量
+     */
+    public function getUserBoughtCount(int $userId): int
+    {
+        return ShopPurchaseLog::where('user_id', $userId)
+            ->where('shop_item_id', $this->id)
+            ->sum('quantity');
+    }
+
+    /**
+     * 记录购买记录
+     *
+     * @param int $userId 用户ID
+     * @param int $quantity 购买数量
+     * @param int $totalPrice 总价
+     * @return ShopPurchaseLog 购买记录
+     */
+    public function recordPurchase(int $userId, int $quantity, int $totalPrice): ShopPurchaseLog
+    {
+        $log = new ShopPurchaseLog([
+            'user_id' => $userId,
+            'shop_item_id' => $this->id,
+            'item_id' => $this->item_id,
+            'quantity' => $quantity,
+            'price' => $this->price,
+            'total_price' => $totalPrice,
+            'currency_id' => $this->currency_id,
+            'purchase_time' => now(),
+            'ip_address' => request()->ip(),
+            'device_info' => request()->userAgent(),
+        ]);
+        
+        $log->save();
+        
+        return $log;
+    }
+}

+ 85 - 0
app/Module/Shop/Models/ShopPurchaseLog.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Module\Shop\Models;
+
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use UCore\ModelCore;
+use App\Module\GameItems\Models\Item;
+
+/**
+ * 商店购买记录模型
+ *
+ * field start 
+ * @property   int  $id  记录ID,主键
+ * @property   int  $user_id  用户ID
+ * @property   int  $shop_item_id  商品ID,外键关联kku_shop_items表
+ * @property   int  $item_id  物品ID,外键关联kku_item_items表
+ * @property   int  $quantity  购买数量
+ * @property   int  $price  单价
+ * @property   int  $total_price  总价
+ * @property   int  $currency_id  货币类型ID
+ * @property   string  $purchase_time  购买时间
+ * @property   string  $ip_address  购买IP地址
+ * @property   string  $device_info  设备信息
+ * @property   \Carbon\Carbon  $created_at  创建时间
+ * @property   \Carbon\Carbon  $updated_at  更新时间
+ * field end
+ */
+class ShopPurchaseLog extends ModelCore
+{
+    /**
+     * 与模型关联的表名
+     *
+     * @var string
+     */
+    protected $table = 'shop_purchase_logs';
+
+    /**
+     * 可批量赋值的属性
+     *
+     * @var array
+     */
+    protected $fillable = [
+        'user_id',
+        'shop_item_id',
+        'item_id',
+        'quantity',
+        'price',
+        'total_price',
+        'currency_id',
+        'purchase_time',
+        'ip_address',
+        'device_info',
+    ];
+
+    /**
+     * 应该被转换为日期的属性
+     *
+     * @var array
+     */
+    protected $dates = [
+        'purchase_time',
+        'created_at',
+        'updated_at',
+    ];
+
+    /**
+     * 获取关联的商品
+     *
+     * @return BelongsTo
+     */
+    public function shopItem(): BelongsTo
+    {
+        return $this->belongsTo(ShopItem::class, 'shop_item_id');
+    }
+
+    /**
+     * 获取关联的物品
+     *
+     * @return BelongsTo
+     */
+    public function item(): BelongsTo
+    {
+        return $this->belongsTo(Item::class, 'item_id');
+    }
+}