Просмотр исходного кода

Merge branch 'dev-rewardPoints' into dev

bianjunhui 4 дней назад
Родитель
Сommit
98524744a7

+ 190 - 0
packages/Webkul/Shop/src/Http/Controllers/API/BearerAddressController.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace Webkul\Shop\Http\Controllers\API;
+
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Event;
+use Webkul\Customer\Repositories\CustomerAddressRepository;
+use Webkul\Shop\Http\Resources\AddressResource;
+
+class BearerAddressController extends APIController
+{
+    /**
+     * Create a new controller instance.
+     */
+    public function __construct(protected CustomerAddressRepository $customerAddressRepository) {}
+
+    /**
+     * 获取当前用户所有地址
+     */
+    public function index(): JsonResponse
+    {
+        $customer = auth()->guard('customer')->user();
+
+        $addresses = $customer->addresses;
+
+        return response()->json([
+            'success' => true,
+            'data'    => AddressResource::collection($addresses),
+        ]);
+    }
+
+    /**
+     * 新增地址
+     */
+    public function store(Request $request): JsonResponse
+    {
+        $customer = auth()->guard('customer')->user();
+
+        /** 参数校验 */
+        $validated = $request->validate([
+            'first_name'      => ['required', 'string', 'max:255'],
+            'last_name'       => ['required', 'string', 'max:255'],
+            'email'           => ['required', 'email', 'max:255'],
+            'phone'           => ['required', 'string', 'max:50'],
+            'address'         => ['required', 'array', 'min:1'],
+            'address.*'       => ['string', 'max:255'],
+            'city'            => ['required', 'string', 'max:255'],
+            'country'         => ['nullable', 'string', 'max:255'],
+            'state'           => ['nullable', 'string', 'max:255'],
+            'postcode'        => ['nullable', 'string', 'max:50'],
+            'company_name'    => ['nullable', 'string', 'max:255'],
+            'vat_id'          => ['nullable', 'string', 'max:50'],
+            'default_address' => ['nullable', 'boolean'],
+            'use_for_shipping'=> ['nullable', 'boolean'],
+        ]);
+
+        Event::dispatch('customer.addresses.create.before');
+
+        $data = [
+            'customer_id'     => $customer->id,
+            'address_type'    => 'customer',
+            'first_name'      => $validated['first_name'],
+            'last_name'       => $validated['last_name'],
+            'email'           => $validated['email'],
+            'phone'           => $validated['phone'],
+            'address'         => implode(PHP_EOL, array_filter($validated['address'])),
+            'city'            => $validated['city'],
+            'country'         => $validated['country'] ?? '',
+            'state'           => $validated['state'] ?? '',
+            'postcode'        => $validated['postcode'] ?? '',
+            'company_name'    => $validated['company_name'] ?? '',
+            'vat_id'          => $validated['vat_id'] ?? '',
+            'default_address' => $validated['default_address'] ?? false,
+            'use_for_shipping'=> $validated['use_for_shipping'] ?? false,
+        ];
+
+        /** 设为默认时,取消其他默认地址 */
+        if ($data['default_address']) {
+            $this->customerAddressRepository
+                ->where('customer_id', $customer->id)
+                ->where('default_address', 1)
+                ->update(['default_address' => 0]);
+        }
+
+        $address = $this->customerAddressRepository->create($data);
+
+        Event::dispatch('customer.addresses.create.after', $address);
+
+        return response()->json([
+            'success' => true,
+            'data'    => new AddressResource($address),
+            'message' => trans('shop::app.customers.account.addresses.index.create-success'),
+        ], 201);
+    }
+
+    /**
+     * 更新地址
+     */
+    public function update(Request $request, int $id): JsonResponse
+    {
+        $customer = auth()->guard('customer')->user();
+
+        $address = $this->customerAddressRepository->find($id);
+
+        if (! $address || $address->customer_id !== $customer->id) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Address not found',
+            ], 404);
+        }
+
+        /** 参数校验 */
+        $validated = $request->validate([
+            'first_name'      => ['sometimes', 'string', 'max:255'],
+            'last_name'       => ['sometimes', 'string', 'max:255'],
+            'email'           => ['sometimes', 'email', 'max:255'],
+            'phone'           => ['sometimes', 'string', 'max:50'],
+            'address'         => ['sometimes', 'array', 'min:1'],
+            'address.*'       => ['string', 'max:255'],
+            'city'            => ['sometimes', 'string', 'max:255'],
+            'country'         => ['nullable', 'string', 'max:255'],
+            'state'           => ['nullable', 'string', 'max:255'],
+            'postcode'        => ['nullable', 'string', 'max:50'],
+            'company_name'    => ['nullable', 'string', 'max:255'],
+            'vat_id'          => ['nullable', 'string', 'max:50'],
+            'default_address' => ['nullable', 'boolean'],
+            'use_for_shipping'=> ['nullable', 'boolean'],
+        ]);
+
+        Event::dispatch('customer.addresses.update.before', $id);
+
+        $data = ['customer_id' => $customer->id];
+
+        foreach (['first_name', 'last_name', 'email', 'phone', 'city', 'country', 'state', 'postcode', 'company_name', 'vat_id', 'default_address', 'use_for_shipping'] as $field) {
+            if (array_key_exists($field, $validated) && $validated[$field] !== null) {
+                $data[$field] = $validated[$field];
+            }
+        }
+
+        if (isset($validated['address'])) {
+            $data['address'] = implode(PHP_EOL, array_filter($validated['address']));
+        }
+
+        /** 设为默认时,取消其他默认地址 */
+        if (! empty($data['default_address'])) {
+            $this->customerAddressRepository
+                ->where('customer_id', $customer->id)
+                ->where('id', '!=', $id)
+                ->where('default_address', 1)
+                ->update(['default_address' => 0]);
+        }
+
+        $address = $this->customerAddressRepository->update($data, $id);
+
+        Event::dispatch('customer.addresses.update.after', $address);
+
+        return response()->json([
+            'success' => true,
+            'data'    => new AddressResource($address),
+            'message' => trans('shop::app.customers.account.addresses.index.update-success'),
+        ]);
+    }
+
+    /**
+     * 删除地址
+     */
+    public function destroy(int $id): JsonResponse
+    {
+        $customer = auth()->guard('customer')->user();
+
+        $address = $this->customerAddressRepository->find($id);
+
+        if (! $address || $address->customer_id !== $customer->id) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Address not found',
+            ], 404);
+        }
+
+        Event::dispatch('customer.addresses.delete.before', $id);
+        $address->delete();
+        Event::dispatch('customer.addresses.delete.after', $id);
+
+        return response()->json([
+            'success' => true,
+            'message' => trans('shop::app.customers.account.addresses.index.delete-success'),
+        ]);
+    }
+}

+ 78 - 0
packages/Webkul/Shop/src/Http/Middleware/BearerTokenAuth.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Webkul\Shop\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Laravel\Sanctum\PersonalAccessToken;
+use Webkul\Customer\Models\Customer;
+
+class BearerTokenAuth
+{
+    /**
+     * Handle an incoming request.
+     *
+     * 验证 Authorization: Bearer <token> 请求头中的 Sanctum Token
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        $token = $request->bearerToken();
+
+        if (! $token) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Authorization Bearer token is required',
+            ], 401);
+        }
+
+        try {
+            $accessToken = PersonalAccessToken::findToken($token);
+
+            if (! $accessToken) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Invalid or expired token',
+                ], 401);
+            }
+
+            /** 检查 Token 是否过期 */
+            if ($accessToken->expires_at && $accessToken->expires_at->isPast()) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Token has expired',
+                ], 401);
+            }
+
+            /** 确保 tokenable 是 Customer 实例 */
+            $tokenable = $accessToken->tokenable;
+            if (! $tokenable || ! ($tokenable instanceof Customer)) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Invalid token owner',
+                ], 401);
+            }
+
+            /** 检查用户状态 */
+            if (! $tokenable->status) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Customer account is not active',
+                ], 401);
+            }
+
+            /** 更新 token 最后使用时间 */
+            $accessToken->touch();
+
+            /** 将用户设置到 customer guard */
+            auth()->guard('customer')->setUser($tokenable);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Authentication failed',
+            ], 401);
+        }
+
+        return $next($request);
+    }
+}

+ 5 - 0
packages/Webkul/Shop/src/Providers/ShopServiceProvider.php

@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Route;
 use Illuminate\Support\ServiceProvider;
 use Webkul\Core\Http\Middleware\PreventRequestsDuringMaintenance;
 use Webkul\Shop\Http\Middleware\AuthenticateCustomer;
+use Webkul\Shop\Http\Middleware\BearerTokenAuth;
 use Webkul\Shop\Http\Middleware\CacheResponse;
 use Webkul\Shop\Http\Middleware\Currency;
 use Webkul\Shop\Http\Middleware\Locale;
@@ -40,10 +41,14 @@ class ShopServiceProvider extends ServiceProvider
         $router->aliasMiddleware('currency', Currency::class);
         $router->aliasMiddleware('cache.response', CacheResponse::class);
         $router->aliasMiddleware('customer', AuthenticateCustomer::class);
+        $router->aliasMiddleware('bearer.token', BearerTokenAuth::class);
 
         Route::middleware(['web', 'shop', PreventRequestsDuringMaintenance::class])->group(__DIR__.'/../Routes/web.php');
         Route::middleware(['web', 'shop', PreventRequestsDuringMaintenance::class])->group(__DIR__.'/../Routes/api.php');
 
+        /** Bearer Token 认证的 API 路由(无 web 中间件,无 Session/CSRF) */
+        Route::middleware(['api'])->group(__DIR__.'/../Routes/api-token.php');
+
         $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
 
         $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'shop');

+ 26 - 0
packages/Webkul/Shop/src/Routes/api-token.php

@@ -0,0 +1,26 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Webkul\Shop\Http\Controllers\API\BearerAddressController;
+
+/**
+ * Bearer Token 认证的 API 路由
+ *
+ * 认证方式: Authorization: Bearer <sanctum_token>
+ * 不受 web 中间件组影响(无 Session、无 CSRF、无 Cookie 加密)
+ */
+
+Route::group(['prefix' => 'api'], function () {
+
+    /** 客户地址管理(Bearer Token 认证) */
+    Route::controller(BearerAddressController::class)
+        ->middleware('bearer.token')
+        ->prefix('customer/token/address')
+        ->group(function () {
+            Route::get('', 'index')->name('shop.api.token.customer.addresses.index');
+            Route::post('', 'store')->name('shop.api.token.customer.addresses.store');
+            Route::put('{id}', 'update')->name('shop.api.token.customer.addresses.update');
+            Route::delete('{id}', 'destroy')->name('shop.api.token.customer.addresses.destroy');
+        });
+
+});