一、架构设计:如何在有限资源下做技术选型?
1.1 为什么不是其他方案?
`// 这是我当时的技术选型对比表 const comparison = { "Spring Cloud微服务": { "适合场景": "大型互联网公司,日订单百万级", "我们的问题": "杀鸡用牛刀,3人团队hold不住", "成本估算": "50万+,6个月+" },
"Laravel + 原生开发": { "适合场景": "需要极致用户体验", "我们的问题": "多端开发成本翻3倍", "成本估算": "40万+,4个月+" },
"Node.js全栈": { "适合场景": "实时性要求高的IM系统", "我们的问题": "PHP程序员转Node有学习成本", "成本估算": "25万+,3个月+" },
"ThinkPHP6 + Uni-app": { "适合场景": "中小公司快速迭代,多端需求", "优势": "开发快、成本低、生态成熟", "最终选择": "✅ 就是它了!" } };`
1.2 整体架构图(实战版)
二、核心功能实现:从0到1的实战代码
2.1 多端登录的“坑”与“解”
问题:微信小程序、H5、APP登录机制完全不同 `// 后端统一登录接口设计 class AuthController { /** * 统一登录入口 * @param string platform) { $data = request()->post();
// 根据平台选择不同的验证方式
switch ($platform) {
case 'weixin':
// 微信小程序登录
return $this->weixinLogin($data['code']);
case 'h5':
// H5账号密码登录
return $this->passwordLogin(
$data['username'],
$data['password']
);
case 'app':
// APP设备号登录
return $this->deviceLogin(
$data['device_id'],
$data['token']
);
default:
throw new Exception('不支持的平台');
}
}
/**
* 微信小程序登录(最复杂的部分)
*/
private function weixinLogin($code)
{
// 1. 获取openid
$weixinApi = "https://api.weixin.qq.com/sns/jscode2session";
$params = [
'appid' => config('weixin.appid'),
'secret' => config('weixin.secret'),
'js_code' => $code,
'grant_type' => 'authorization_code'
];
$result = Http::get($weixinApi, $params);
$wxData = json_decode($result, true);
if (isset($wxData['errcode'])) {
throw new Exception('微信登录失败: ' . $wxData['errmsg']);
}
// 2. 查找或创建用户
$user = User::where('wx_openid', $wxData['openid'])->find();
if (!$user) {
// 新用户,需要获取用户信息
$userInfo = $this->getUserInfo($wxData['openid'], $wxData['session_key']);
$user = User::create([
'wx_openid' => $wxData['openid'],
'wx_unionid' => $wxData['unionid'] ?? '',
'nickname' => $userInfo['nickName'],
'avatar' => $userInfo['avatarUrl'],
'register_time' => time(),
'register_platform' => 'weixin'
]);
}
// 3. 生成JWT token
$token = JWT::encode([
'user_id' => $user->id,
'platform' => 'weixin',
'exp' => time() + 86400 * 7 // 7天有效期
], config('app.jwt_secret'));
return [
'token' => $token,
'user_info' => $user->toArray()
];
}
}`
2.2 订单状态机:从下单到结算的全流程管理
需求:一个订单有10+个状态,状态流转要严格管控 `// 订单状态机实现 class OrderStateMachine { // 状态定义 const STATUS_PENDING = 1; // 待支付 const STATUS_PAID = 2; // 已支付 const STATUS_WAITING = 3; // 待接单 const STATUS_GRABBED = 4; // 已接单 const STATUS_PROCESSING = 5; // 进行中 const STATUS_COMPLETED = 6; // 已完成 const STATUS_CANCELED = 7; // 已取消 const STATUS_REFUNDING = 8; // 退款中 const STATUS_REFUNDED = 9; // 已退款
// 状态流转规则
private static $transitions = [
self::STATUS_PENDING => [
'pay' => self::STATUS_PAID,
'cancel' => self::STATUS_CANCELED
],
self::STATUS_PAID => [
'timeout' => self::STATUS_REFUNDING,
'grab' => self::STATUS_GRABBED,
'cancel' => self::STATUS_REFUNDING
],
self::STATUS_GRABBED => [
'start' => self::STATUS_PROCESSING,
'cancel' => self::STATUS_REFUNDING
],
// ... 更多状态流转
];
// 状态流转检查
public static function canTransition($fromStatus, $toStatus)
{
if (!isset(self::$transitions[$fromStatus])) {
return false;
}
return in_array($toStatus, self::$transitions[$fromStatus]);
}
// 执行状态流转
public function transition($orderId, $action, $userId, $remark = '')
{
// 开启事务
Db::startTrans();
try {
// 查询订单(加锁)
$order = Order::where('id', $orderId)
->lock(true)
->find();
if (!$order) {
throw new Exception('订单不存在');
}
// 获取目标状态
$toStatus = self::$transitions[$order->status][$action] ?? null;
if (!$toStatus) {
throw new Exception('不允许的状态流转');
}
// 更新订单状态
$order->status = $toStatus;
$order->update_time = time();
$order->save();
// 记录状态变更日志
OrderLog::create([
'order_id' => $orderId,
'from_status' => $order->status,
'to_status' => $toStatus,
'action' => $action,
'user_id' => $userId,
'remark' => $remark,
'ip' => request()->ip(),
'create_time' => time()
]);
// 触发状态变更事件
Event::trigger('OrderStatusChanged', [
'order_id' => $orderId,
'from_status' => $order->status,
'to_status' => $toStatus,
'user_id' => $userId
]);
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
throw $e;
}
}
}`
2.3 实时抢单:WebSocket + Redis锁的完美组合
场景:100个打手同时抢1个订单,如何保证公平? `// 抢单服务核心代码 class GrabOrderService { // 抢单入口 public function grab(driverId) { lockKey = "grab_lock:{$orderId}";
// 1. 分布式锁:防止超卖
if (!$redis->set($lockKey, 1, ['nx', 'ex' => 3])) {
throw new BusyException('抢单人数过多,请稍后重试');
}
try {
// 2. 检查订单状态(加数据库锁)
$order = Order::where('id', $orderId)
->where('status', OrderStateMachine::STATUS_WAITING)
->lock(true)
->find();
if (!$order) {
throw new OrderGoneException('订单已被抢走');
}
// 3. 检查打手资格
if (!$this->checkDriverQualification($driverId, $order)) {
throw new NoQualificationException('您不符合接单要求');
}
// 4. 执行抢单
return $this->doGrab($order, $driverId);
} finally {
// 5. 必须释放锁!
$redis->del($lockKey);
}
}
// 实际抢单逻辑
private function doGrab($order, $driverId)
{
Db::startTrans();
try {
// 更新订单
$order->driver_id = $driverId;
$order->grab_time = time();
$order->status = OrderStateMachine::STATUS_GRABBED;
$order->save();
// 记录抢单日志
GrabLog::create([
'order_id' => $order->id,
'driver_id' => $driverId,
'grab_time' => time(),
'ip' => request()->ip()
]);
// 异步通知其他打手
$this->notifyOthers($order->id, $driverId);
Db::commit();
return [
'success' => true,
'order_id' => $order->id,
'message' => '抢单成功'
];
} catch (\Exception $e) {
Db::rollback();
throw $e;
}
}
}`
四、安全防护:与“黑产”斗智斗勇
4.1 打手防作弊系统
`class AntiCheatSystem { // 验证打手身份 public function verifyDriver(orderId) { $riskScore = 0;
// 1. 设备指纹验证
$deviceId = $this->generateDeviceFingerprint();
$historyDevices = $this->getDriverDevices($driverId);
if (!in_array($deviceId, $historyDevices)) {
$riskScore += 30; // 新设备加30分风险
$this->requireAdditionalAuth($driverId);
}
// 2. 位置验证
$driverLocation = $this->getDriverLocation($driverId);
$orderLocation = $this->getOrderLocation($orderId);
$distance = $this->calculateDistance($driverLocation, $orderLocation);
if ($distance > 100) { // 距离超过100公里
$riskScore += 50;
$this->flagForReview($orderId);
}
// 3. 行为分析
$behaviorData = $this->analyzeDriverBehavior($driverId);
if ($behaviorData['avg_complete_time'] < 300) {
// 平均完成时间小于5分钟,可疑
$riskScore += 40;
}
// 4. 社交关系检查
if ($this->checkSocialConnection($driverId, $orderId)) {
// 打手和客户有关联,可能刷单
$riskScore += 70;
}
return $riskScore < 80; // 风险分低于80才通过
}
}`
最后
这个项目让我深刻体会到:技术不是越新越好,而是越合适越好。TP6和Uni-app虽然不是最前沿的技术,但在这个场景下,它们是最佳选择。
如果你也在做类似系统,欢迎在评论区交流。有问题可以提,看到都会回。