• 本站招聘各个版块版主共同发展,有意可私聊站长

异次元发卡修复导入远程商品库存为null的问题

Telegram账号购买找【ITCRY】-顶级号商 https://itcry.com/

无名

Administrator
管理成员
站长
Lv0
Lv1
Lv2
11
消息得分
注册
2023-10-25
消息
2,635
反馈评分
40
针对v3.2.24版本,修改如下文件:
1.app/Controller/Admin/Api/Store.php
代码:
<?php
declare(strict_types=1);

namespace App\Controller\Admin\Api;


use App\Controller\Base\API\Manage;
use App\Entity\Query\Delete;
use App\Entity\Query\Get;
use App\Entity\Query\Save;
use App\Interceptor\ManageSession;
use App\Model\ManageLog;
use App\Model\Shared;
use App\Service\Image;
use App\Service\Query;
use App\Util\Date;
use App\Util\Ini;
use App\Util\Str;
use Kernel\Annotation\Inject;
use Kernel\Annotation\Interceptor;
use Kernel\Context\Interface\Request;
use Kernel\Exception\JSONException;
use Kernel\Waf\Filter;

#[Interceptor(ManageSession::class, Interceptor::TYPE_API)]
class Store extends Manage
{

    #[Inject]
    private Query $query;

    #[Inject]
    private \App\Service\Shared $shared;

    #[Inject]
    private Image $image;

    /**
     * @return array
     */
    public function data(): array
    {
        $map = $_POST;
        $get = new Get(Shared::class);
        $get->setPaginate((int)$this->request->post("page"), (int)$this->request->post("limit"));
        $get->setWhere($map);
        $data = $this->query->get($get);
        return $this->json(data: $data);
    }


    /**
     * @return array
     * @throws JSONException
     */
    public function save(): array
    {
        $map = $_POST;

        if (!$map['domain']) {
            throw new JSONException("店铺地址不能为空");
        }

        if (!$map['app_id']) {
            throw new JSONException("商户ID不能为空");
        }

        if (!$map['app_key']) {
            throw new JSONException("商户密钥不能为空");
        }

        $map['domain'] = trim($map['domain'], "/");

        $connect = $this->shared->connect($map['domain'], $map['app_id'], $map['app_key'], (int)$map['type']);

        $map['name'] = strip_tags((string)$connect['shopName']);
        $map['balance'] = (float)$connect['balance'];

        $save = new Save(Shared::class);
        $save->setMap($map);
        $save->enableCreateTime();
        $save = $this->query->save($save);
        if (!$save) {
            throw new JSONException("保存失败,请检查信息填写是否完整");
        }

        ManageLog::log($this->getManage(), "[修改/新增]共享店铺");
        return $this->json(200, '(^∀^)保存成功');
    }

    /**
     * @return array
     * @throws JSONException
     */
    public function connect(): array
    {
        $id = (int)$_POST['id'];
        $shared = Shared::query()->find($id);

        if (!$shared) {
            throw new JSONException("未找到该店铺");
        }
        $connect = $this->shared->connect($shared->domain, $shared->app_id, $shared->app_key, $shared->type);
        $shared->name = strip_tags((string)$connect['shopName']);
        $shared->balance = (float)$connect['balance'];
        $shared->save();
        return $this->json(200, 'success');
    }

    /**
     * @return array
     * @throws JSONException
     */
    public function items(): array
    {
        $id = (int)$_POST['id'];
        $shared = Shared::query()->find($id);

        if (!$shared) {
            throw new JSONException("未找到该店铺");
        }
        $items = $this->shared->items($shared);

        $gan = function (&$items) use (&$gan) {
            foreach ($items as $key => $val) {
                $items[$key]["name"] = strip_tags((string)$val['name']);
                if (isset($val['children']) && !empty($val['children'])) {
                    $gan($items[$key]["children"]);
                }
            }
        };

        $gan($items);

        foreach ($items as $key => $item) {
            $items[$key]['id'] = 0;
        }
        return $this->json(200, 'success', $items);
    }

    /**
     * @throws JSONException
     */
    public function addItem(Request $request): array
    {
        $map = $request->post(flags: Filter::NORMAL);

        $categoryId = (int)$map['category_id'];
        $storeId = (int)$_GET['storeId'];
        $items = (array)$map['items'];
        $premium = (float)$map['premium']; // 加价金额
        $premiumType = (int)$map['premium_type']; // 加价模式
        $sharedSync = (int)($map['shared_sync'] ?? 0) == 0 ? 0 : 1; // 主从同步
        $inventorySync = (int)($map['inventory_sync'] ?? 1) == 0 ? 0 : 1; // 数量同步,默认开启
        $imageDownload = (bool)$map['image_download'];
        $shelves = (int)$map['shelves'] == 0 ? 0 : 1; // 立即上架

        $shared = Shared::query()->find($storeId);

        if (!$shared) {
            throw new JSONException("未找到该店铺");
        }

        $date = Date::current();
        $count = count($items);
        $success = 0;
        $error = 0;

        foreach ($items as $item) {
            try {
                $commodity = new \App\Model\Commodity();
                $commodity->category_id = $categoryId;
                $commodity->name = $item['name'];
                $commodity->description = $item['description'];

                //正则处理
                preg_match_all('#<img.*?src="(/.*?)"#', $commodity->description, $matchs);
                $list = (array)$matchs[1];

                if (count($list) > 0) {
                    foreach ($list as $e) {
                        //远端图片下载
                        if ($imageDownload) {
                            $download = $this->image->downloadRemoteImage($shared->domain . $e);
                            $commodity->description = str_replace($e, $download[0], $commodity->description);
                        } else {
                            $commodity->description = str_replace($e, $shared->domain . $e, $commodity->description);
                        }
                    }
                }

                //远端cover下载
                if ($imageDownload) {
                    $download = $this->image->downloadRemoteImage($shared->domain . $item['cover']);
                    $commodity->cover = $download[0];
                } else {
                    $commodity->cover = $shared->domain . $item['cover'];
                }

                $commodity->status = $shelves;
                $commodity->owner = 0;
                $commodity->create_time = $date;
                $commodity->api_status = 0;
                $commodity->code = strtoupper(Str::generateRandStr(16));
                $commodity->delivery_way = 1;
                $commodity->contact_type = $item['contact_type'];
                $commodity->password_status = $item['password_status'];
                $commodity->sort = 0;
                $commodity->coupon = 0;
                $commodity->shared_id = $storeId;
                $commodity->shared_code = $item['code'];
                $commodity->shared_premium = $premium;
                $commodity->shared_premium_type = $premiumType;
                $commodity->shared_sync = $sharedSync;
                $commodity->inventory_sync = $inventorySync;
                $commodity->seckill_status = $item['seckill_status'] ?? 0;

                if ($commodity->seckill_status == 1) {
                    $commodity->seckill_start_time = $item['seckill_start_time'] ?? null;
                    $commodity->seckill_end_time = $item['seckill_end_time'] ?? null;
                }

                $commodity->draft_status = $item['draft_status'] ?? 0;

                if ($commodity->draft_status) {
                    $commodity->draft_premium = $this->shared->AdjustmentAmount($premiumType, $premium, $item['draft_premium'] ?? 0);
                }

                //2022/01/05新增
                $commodity->inventory_hidden = $item['inventory_hidden'] ?? 0;
                $commodity->only_user = $item['only_user'] ?? 0;
                $commodity->purchase_count = $item['purchase_count'] ?? 0;
                $commodity->widget = $item['widget'] ?? null;
                $commodity->minimum = $item['minimum'] ?? 0;
                $commodity->maximum = $item['maximum'] ?? 0;

                if (array_key_exists('stock', $item)) {
                    $commodity->stock = (int)$item['stock'];
                }

                //自动加价
                $config = $this->shared->AdjustmentPrice((string)$item['config'], $item['price'], $item['user_price'], $premiumType, $premium);

                $commodity->config = Ini::toConfig($config['config']);
                $commodity->price = $config['price'];
                $commodity->user_price = $config['user_price'];

                $commodity->save();
                $success++;
            } catch (\Exception $e) {
                $error++;
            }
        }

        ManageLog::log($this->getManage(), "[店铺共享]进行了克隆商品({$shared->name}),总数量:{$count},成功:{$success},失败:{$error}");
        return $this->json(200, "拉取结束,总数量:{$count},成功:{$success},失败:{$error}");
    }

    /**
     * @return array
     * @throws JSONException
     */
    public function del(): array
    {
        $deleteBatchEntity = new Delete(Shared::class, $_POST['list']);
        $count = $this->query->delete($deleteBatchEntity);
        if ($count == 0) {
            throw new JSONException("没有移除任何数据");
        }

        ManageLog::log($this->getManage(), "[店铺共享]删除操作,共计:" . count($_POST['list']));
        return $this->json(200, '(^∀^)移除成功');
    }
}

2.app/Controller/User/Api/Index.php
代码:
<?php
declare(strict_types=1);

namespace App\Controller\User\Api;


use App\Consts\Hook;
use App\Controller\Base\API\User;
use App\Entity\Query\Get;
use App\Interceptor\UserVisitor;
use App\Interceptor\Waf;
use App\Model\Card;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\Order;
use App\Model\Pay;
use App\Model\UserCommodity;
use App\Service\Query;
use App\Service\Shared;
use App\Service\Shop;
use App\Util\Client;
use App\Util\FileCache;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Kernel\Annotation\Inject;
use Kernel\Annotation\Interceptor;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Util\Decimal;

#[Interceptor([Waf::class, UserVisitor::class])]
class Index extends User
{
    #[Inject]
    private Shared $shared;

    #[Inject]
    private Query $query;

    #[Inject]
    private \App\Service\Order $order;

    #[Inject]
    private Shop $shop;


    /**
     * @return array
     */
    public function data(): array
    {
        $category = $this->shop->getCategory($this->getUserGroup());
        hook(Hook::USER_API_INDEX_CATEGORY_LIST, $category);
        return $this->json(200, "success", $category);
    }

    /**
     * @return array
     * @throws JSONException
     * @throws RuntimeException
     */
    public function commodity(): array
    {
        $keywords = (string)$_GET['keywords'];
        $limit = (int)$_GET['limit'];
        $page = (int)$_GET['page'];
        $categoryId = $_GET['categoryId'];

        FileCache::clearCache('shop');

        $commodity = Commodity::query()
            ->with(['owner' => function (Relation $relation) {
                $relation->select(["id", "username", "avatar"]);
            }, 'category' => function (Relation $relation) {
                $relation->select(["id", "name", "icon"]);
            }]);

        if ($categoryId == 'recommend') {
            $commodity = $commodity->where("recommend", 1);
        } elseif ($categoryId != 0) {
            $commodity = $commodity->where("category_id", $categoryId);
        }

        if ($keywords != "") {
            $commodity = $commodity->where('name', 'like', '%' . $keywords . '%');
        }

        $bus = \App\Model\Business::get();
        $userCommodityMap = []; //自定义名称的MAP

        if ($bus) {
            //商家
            if ($bus->master_display == 0) {
                $commodity = $commodity->where("owner", $bus->user_id);
            } else {
                //查出所有自己定义的商品
                $userCommodity = UserCommodity::query()->where("user_id", $bus->user_id)->get();
                //隐藏的分类ID
                $hideCommodity = [];

                foreach ($userCommodity as $userComm) {
                    if ($userComm->status == 0) {
                        $hideCommodity[] = $userComm->commodity_id;
                    } else {
                        $userCommodityMap[$userComm->commodity_id] = $userComm;
                    }
                }

                $commodity = $commodity->whereNotIn("id", $hideCommodity)->whereRaw("(`owner`=0 or `owner`={$bus->user_id})");
            }
        } else {
            //主站
            if (Config::get("substation_display") == 1) {
                $let = "(`owner`=0 or ";
                //显示商家
                $list = (array)json_decode(Config::get("substation_display_list"), true);
                foreach ($list as $userId) {
                    $let .= "`owner`={$userId} or ";
                }
                $let = trim(trim($let), "or") . ")";
                $commodity = $commodity->whereRaw($let);
            } else {
                $commodity = $commodity->where("owner", 0);
            }
        }

        $commodity = $commodity
            ->where("status", 1)
            ->orderBy("sort", "asc")
            ->select([
                'id', 'name', 'cover',
                'status', 'delivery_way', 'price',
                'user_price', 'shared_code', 'inventory_sync',
                'level_disable', 'level_price', 'hide', 'owner', 'inventory_hidden', "recommend", 'category_id', 'stock', 'shared_id'
            ])
            ->withCount(['order as order_sold' => function (Builder $relation) {
                $relation->where("delivery_status", 1);
            }]);
        if ($limit == 0) {
            $commodity = $commodity
                ->get();
            $total = count($commodity);
            $data = $commodity->toArray();
        } else {
            $commodity = $commodity
                ->paginate($limit, ["*"], "", $page);
            $total = $commodity->total();
            $data = $commodity->toArray()['data'];
        }

        $user = $this->getUser();
        $userGroup = $this->getUserGroup();
        //取得分类
        $category = $this->shop->getCategory($userGroup);
        $cates = [];
        foreach ($category as $cate) {
            $cates[] = (string)$cate['id'];
        }

        //最终的商品数据遍历
        foreach ($data as $key => $val) {
            $parseGroupConfig = Commodity::parseGroupConfig($val['level_price'], $userGroup);
            if (!in_array((string)$val['category_id'], $cates) || $val['hide'] == 1 && (!$parseGroupConfig || !isset($parseGroupConfig['show']) || $parseGroupConfig['show'] != 1)) {
                //隐藏商品
                unset($data[$key]);
                continue;
            }

            if ($val['shared_id'] && (int)$val['inventory_sync'] === 1) {
                $shopCache = FileCache::getJsonFile("shop", $val['id'] . "_shared");
                if (!empty($shopCache)) {
                    $inventory = $shopCache;
                } else {
                    $com = Commodity::query()->with(['shared'])->find($val['id']);
                    if (!$com || !$com->shared) {
                        unset($data[$key]);
                        continue;
                    }
                    try {
                        $inventory = $this->shared->inventory($com->shared, $com);
                        FileCache::setJsonFile("shop", $val['id'] . "_shared", $inventory, 300);
                    } catch (\Throwable $e) {
                        FileCache::setJsonFile("shop", $val['id'] . "_shared", [
                            "count" => 0,
                            "delivery_way" => 0
                        ], 300);
                        @file_put_contents(BASE_PATH . "/runtime/shared_inventory.log", "[" . date('Y-m-d H:i:s') . "] inventory fetch failed, commodity {$val['id']}: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
                        unset($data[$key]);
                        continue;
                    }
                }

                $stockCount = (int)($inventory['count'] ?? 0);
                $deliveryWay = $inventory['delivery_way'] ?? $data[$key]['delivery_way'];

                if ($stockCount === 0 && (int)$deliveryWay === 0) {
                    unset($data[$key]);
                    continue;
                }

                $data[$key]['stock'] = $stockCount;
                $data[$key]['delivery_way'] = $deliveryWay;
            } elseif ($val['delivery_way'] == 0 && !$val['shared_id']) {
                $data[$key]['stock'] = Card::query()->where("status", 0)->where("commodity_id", $val['id'])->count();
            }

            //如果登录后,则自动计算登录后的价格
            if ($user) {
                $tradeAmount = $this->order->valuation(commodity: $commodity[$key], group: $userGroup);
                $data[$key]['price'] = $tradeAmount;
                $data[$key]['user_price'] = $tradeAmount;
            }

            unset(
                $data[$key]['level_price'],
                $data[$key]['level_disable']
            );

            if (!$val['cover']) {
                $data[$key]['cover'] = "/favicon.ico";
            }

            //分站自定义名称和价格
            if (isset($userCommodityMap[$val['id']])) {
                $var = $userCommodityMap[$val['id']];

                if ($var->premium > 0) {
                    $data[$key]['price'] = (new Decimal($data[$key]['price'], 2))->mul($var->premium / 100)->add($data[$key]['price'])->getAmount();
                    $data[$key]['user_price'] = (new Decimal($data[$key]['user_price'], 2))->mul($var->premium / 100)->add($data[$key]['user_price'])->getAmount();
                }
                if ($var->name) {
                    $data[$key]['name'] = $var->name;
                }
            }

            if ($data[$key]['stock'] === null) {
                $data[$key]['stock'] = 0;
            }

            //隐藏库存
            $data[$key]['stock_state'] = $this->shop->getStockState($data[$key]['stock']);
            if ($val['inventory_hidden'] == 1) {
                $data[$key]['stock'] = $this->shop->getHideStock($data[$key]['stock']);
            }
        }

        $data = array_values($data);
        hook(Hook::USER_API_INDEX_COMMODITY_LIST, $data);
        $json = $this->json(200, "success", $data);
        $json['total'] = $total;
        return $json;
    }

    /**
     * @param int $commodityId
     * @return array
     */
    public function commodityDetail(int $commodityId): array
    {
        $array = $this->shop->getItem($commodityId, $this->getUser(), $this->getUserGroup());
        hook(Hook::USER_API_INDEX_COMMODITY_DETAIL_INFO, $array);

        $array['stock_state'] = $this->shop->getStockState($array['stock']);
        if ($array['inventory_hidden'] == 1) {
            $array['stock'] = $this->shop->getHideStock($array['stock']);
        }

        return $this->json(200, 'success', $array);
    }

    /**
     * @return array
     * @throws JSONException
     */
    public function card(): array
    {
        $map = $this->request->post();
        /**
         * @var Commodity $commodity
         */
        $commodity = Commodity::with(['shared'])->find($map['item_id']);
        $limit = $map['limit'] ?? 10;

        if (!$commodity) {
            throw new JSONException("商品不存在");
        }

        if ($commodity->status != 1) {
            throw new JSONException("该商品暂未上架");
        }
        if ($commodity->draft_status != 1) {
            throw new JSONException("该商品不支持预选");
        }

        if ($commodity->shared) {
            $data = $this->shared->draftCard($commodity->shared, $commodity->shared_code, $map);
            //加价算法
            foreach ($data['list'] as &$item) {
                if ($item['draft_premium'] > 0) {
                    $item['draft_premium'] = $this->shared->AdjustmentAmount($commodity->shared_premium_type, $commodity->shared_premium, $item['draft_premium']);
                }
            }
        } else {
            $get = new Get(Card::class);
            $get->setPaginate((int)$this->request->post("page"), (int)$limit);
            $get->setWhere($map);
            $get->setColumn('id', 'draft', 'draft_premium');

            $data = $this->query->get($get, function (Builder $builder) use ($map) {
                $builder = $builder->where("commodity_id", $map['item_id'])->where("status", 0);

                if (!empty($map['race'])) {
                    $builder = $builder->where("race", $map['race']);
                }

                if (!empty($map['sku']) && is_array($map['sku'])) {
                    foreach ($map['sku'] as $k => $v) {
                        $builder = $builder->where("sku->{$k}", $v);
                    }
                }

                return $builder;
            });
        }

        //分站处理
        if (\App\Model\Business::state()) {
            foreach ($data['list'] as &$item) {
                if ($item['draft_premium'] > 0) {
                    $item['draft_premium'] = $this->shop->getSubstationPrice($commodity, $item['draft_premium']);
                }
            }
        }

        if ($commodity->level_disable != 1) {
            //折扣处理
            foreach ($data['list'] as &$item) {
                if ($item['draft_premium'] > 0) {
                    $item['draft_premium'] = $this->order->getValuationPrice($commodity->id, $item['draft_premium'], $this->getUserGroup());
                }
            }
        }

        return $this->json(data: $data);
    }


    /**
     * @return array
     */
    public
    function valuation(): array
    {
        $price = $this->order->valuation(
            commodity: (int)$this->request->post("item_id"),
            num: (int)$this->request->post("num"),
            race: (string)$this->request->post("race"),
            sku: (array)$this->request->post("sku"),
            cardId: (int)$this->request->post("card_id"),
            coupon: (string)$this->request->post("coupon"),
            group: $this->getUserGroup()
        );
        $price = $this->shop->getSubstationPrice((int)$this->request->post("item_id"), $price);
        return $this->json(data: ["price" => $price]);
    }


    /**
     * @return array
     */
    public
    function stock(): array
    {
        $commodity = Commodity::with(['shared'])->find((int)$this->request->post("item_id"));
        $stock = $this->shop->getItemStock($commodity, (string)$this->request->post("race"), (array)$this->request->post("sku"));

        $array = ["stock" => $stock];
        $array['stock_state'] = $this->shop->getStockState($stock);
        if ($commodity->inventory_hidden == 1) {
            $array['stock'] = $this->shop->getHideStock($stock);
        }
        return $this->json(data: $array);
    }

    /**
     * @return array
     */
    public
    function pay(): array
    {

        $equipment = 2;

        if (Client::isMobile()) {
            $equipment = 1;
        }

        if (Client::isWeChat()) {
            $equipment = 3;
        }

        $let = "(`equipment`=0 or `equipment`={$equipment})";

        if (!$this->getUser()) {
            $let .= " and id!=1";
        }

        $pay = Pay::query()->orderBy("sort", "asc")->where("commodity", 1)->whereRaw($let)->get(['id', 'name', 'icon', 'handle'])->toArray();

        hook(Hook::USER_API_INDEX_PAY_LIST, $pay);
        return $this->json(200, 'success', $pay);
    }


    /**
     * @param string $keywords
     * @return array
     */
    public function query(string $keywords): array
    {
        $keywords = trim($keywords);

        $get = new Get(Order::class);
        $get->setPaginate((int)$this->request->post("page"), (int)$this->request->post("limit"));
        $get->setColumn('id', 'trade_no', 'sku', 'secret', 'user_id', 'password', 'amount', 'pay_id', 'commodity_id', 'create_time', 'pay_time', 'delivery_status', 'status', 'card_num', 'contact', "race");

        $data = $this->query->get($get, function (Builder $builder) use ($keywords) {

            $builder = $builder->with(['pay' => function (Relation $relation) {
                $relation->select(['id', 'name', 'icon']);
            }, 'commodity' => function (Relation $relation) {
                $relation->select(['id', 'name', 'cover', 'password_status', 'leave_message']);
            }]);

            if (preg_match('/^\d{18}$/', $keywords)) {
                $builder = $builder->where("trade_no", $keywords);
            } else {
                $builder = $builder->where("contact", $keywords);
            }
            return $builder;
        });

        foreach ($data['list'] as &$item) {
            if ($item['status'] != 1) {
                unset($item['commodity']['leave_message']);
                unset($item['secret']);
            }

            if (!empty($item['password'])) {
                $item['password'] = true;
                unset($item['secret']);
            }
        }

        hook(Hook::USER_API_INDEX_QUERY_LIST, $data);
        return $this->json(data: $data);
    }

    /**
     * @param string $tradeNo
     * @param string $password
     * @return array
     * @throws JSONException
     */
    public function secret(string $tradeNo, string $password): array
    {
        $order = Order::with(['commodity'])->where("trade_no", $tradeNo)->first();

        if (!$order) {
            throw new JSONException("未查询到相关信息");
        }

        if (!empty($order->password)) {
            if ($password != $order->password) {
                throw new JSONException("密码错误");
            }
        }

        if ($order->status != 1) {
            throw new JSONException("订单还未支付");
        }

        $widget = (array)json_decode((string)$order->widget, true);
        if (empty($widget)) {
            $widget = null;
        }

        hook(Hook::USER_API_INDEX_QUERY_SECRET, $order);
        return $this->json(data: [
            'secret' => $order->secret,
            'widget' => $widget,
            'leave_message' => $order?->commodity?->leave_message
        ]);
    }
}

3.app/Service/Bind/Shop.php
代码:
<?php
declare(strict_types=1);

namespace App\Service\Bind;

use App\Consts\Hook;
use App\Model\Business;
use App\Model\Card;
use App\Model\Category;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\User;
use App\Model\UserCategory;
use App\Model\UserCommodity;
use App\Model\UserGroup;
use App\Service\Shared;
use App\Util\Client;
use App\Util\Ini;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Kernel\Annotation\Inject;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Plugin\Entity\Stock;
use Kernel\Util\Decimal;

class Shop implements \App\Service\Shop
{
    #[Inject]
    private Shared $shared;

    #[Inject]
    private \App\Service\Order $order;

    /**
     * @param UserGroup|null $group
     * @return array
     * @throws RuntimeException
     */
    public function getCategory(?UserGroup $group): array
    {
        $category = Category::query()->withCount(['children as commodity_count' => function (Builder $builder) {
            $builder->where("status", 1);
        }])->where("status", 1)->orderBy("sort", "asc");

        $bus = Business::get();
        $userCategoryMap = []; //自定义名称的MAP
        $master = true;

        if ($bus) {
            $master = false;
            //商家
            if ($bus->master_display == 0) {
                $category = $category->where("owner", $bus->user_id);
            } else {
                //查询出所有不显示的ID
                $userCategory = UserCategory::query()->where("user_id", $bus->user_id)->get();
                //隐藏的分类ID
                $hideCategory = [];

                foreach ($userCategory as $userCate) {
                    if ($userCate->status == 0) {
                        $hideCategory[] = $userCate->category_id;
                    } else {
                        $userCategoryMap[$userCate->category_id] = $userCate;
                    }
                }

                $category = $category->whereNotIn("id", $hideCategory)->whereRaw("(`owner`=0 or `owner`={$bus->user_id})");
            }
        } else {
            //主站
            if (Config::get("substation_display") == 1) {
                //显示商家
                $list = (array)json_decode(Config::get("substation_display_list"), true);
                $let = "(`owner`=0 or ";
                foreach ($list as $userId) {
                    $let .= "`owner`={$userId} or ";
                }
                $let = trim(trim($let), "or") . ")";
                $category = $category->whereRaw($let);
            } else {
                $category = $category->where("owner", 0);
            }
        }
        //拿到最终的分类数据
        $category = $category->get();

        foreach ($category as $index => $item) {

            $levelConfig = $item->getLevelConfig($group);
            if ($item->hide == 1 && (!$levelConfig || !isset($levelConfig['show']) || (int)$levelConfig['show'] != 1)) {
                unset($category[$index]);
                continue;
            }

            if (isset($userCategoryMap[$item->id])) {
                $var = $userCategoryMap[$item->id];
                if ($var->name) {
                    $category[$index]['name'] = $var->name;
                }
            }

            if (!$item->icon) {
                $category[$index]['icon'] = '/favicon.ico';
            }
        }

        $array = $category->toArray();
        $array = array_values($array);

        $commodityRecommend = Config::get("commodity_recommend");
        if ($commodityRecommend == 1 && $master) {
            array_unshift($array, [
                "id" => 'recommend',
                "name" => Config::get("commodity_name"),
                "sort" => 1,
                "create_time" => "-",
                "owner" => 0,
                "icon" => "/assets/static/images/recommend.png",
                "status" => 1,
                "hide" => 0,
                "user_level_config" => null,
                "commodity_count" => Commodity::query()->where("status", 1)->where("recommend", 1)->count(),
            ]);
        }

        return $array;
    }

    /**
     * @param int|string $commodityId
     * @param User|null $user
     * @param UserGroup|null $group
     * @return array
     * @throws JSONException
     * @throws RuntimeException
     */
    public function getItem(int|string $commodityId, ?User $user = null, ?UserGroup $group = null): array
    {

        $commodity = Commodity::query()->with(['owner' => function (Relation $relation) {
            $relation->select(["id", "username", "avatar"]);
        }])
            ->select(["id", "name", "description",
                "only_user", "purchase_count", "category_id", "cover", "price", "user_price",
                "status", "owner", "delivery_way", "contact_type", "password_status", "level_price",
                "level_disable", "coupon", "shared_id", "shared_code", "shared_premium", "shared_premium_type", "seckill_status",
                "seckill_start_time", "seckill_end_time", "draft_status", "draft_premium", "inventory_hidden",
                "widget", "minimum", "maximum", "shared_sync", "config", "stock", "code"])
            ->withCount(['order as order_sold' => function (Builder $relation) {
                $relation->where("delivery_status", 1);
            }]);

        if (is_int($commodityId)) {
            $commodity = $commodity->find($commodityId);
        } else {
            $commodity = $commodity->where("code", $commodityId)->first();
        }

        if (!$commodity) {
            throw new JSONException("商品不存在");
        }

        if ($commodity->status != 1) {
            throw new JSONException("该商品暂未上架");
        }

        /**
         * @var Shared $shared
         */
        $shared = \App\Model\Shared::query()->find($commodity->shared_id);

        $inventory = null;

        if ($shared) {
            try {
                $inventory = $this->shared->inventory($shared, $commodity);
                $commodity->stock = (int)($inventory['count'] ?? $commodity->stock);
                $commodity->delivery_way = $inventory['delivery_way'] ?? $commodity->delivery_way;
                $commodity->draft_status = $inventory['draft_status'] ?? $commodity->draft_status;
            } catch (\Throwable $e) {
                $commodity->stock = $commodity->stock ?? 0;
            }

            //远端同步
            if ($commodity->shared_sync == 1 && is_array($inventory)) {
                /**
                 * @var Commodity $new
                 */
                $new = Commodity::query()->find($commodity->id);

                $configSource = $inventory['config'] ?? $commodity->config;
                $configString = is_array($configSource) ? Ini::toConfig($configSource) : (string)$configSource;
                $priceSource = $inventory['price'] ?? $commodity->price;
                $userPriceSource = $inventory['user_price'] ?? $commodity->user_price;

                $base = $this->shared->AdjustmentPrice($configString, (string)$priceSource, (string)$userPriceSource, $new->shared_premium_type, $new->shared_premium);

                $commodity->price = $new->price = $base['price'];
                $commodity->user_price = $new->user_price = $base['user_price'];
                $commodity->config = $new->config = Ini::toConfig($base['config']);
                $commodity->draft_status = $new->draft_status = $inventory['draft_status'] ?? $commodity->draft_status;
                $commodity->draft_premium = $new->draft_premium = $inventory['draft_premium'] ?? $commodity->draft_premium;
                $commodity->seckill_status = $new->seckill_status = $inventory['seckill_status'] ?? $commodity->seckill_status;
                $commodity->seckill_start_time = $new->seckill_start_time = $inventory['seckill_start_time'] ?? $commodity->seckill_start_time;
                $commodity->seckill_end_time = $new->seckill_end_time = $inventory['seckill_end_time'] ?? $commodity->seckill_end_time;
                $commodity->widget = $new->widget = $commodity->widget;
                $commodity->minimum = $new->minimum = $inventory['minimum'] ?? $commodity->minimum;
                $commodity->maximum = $new->maximum = $inventory['maximum'] ?? $commodity->maximum;
                $commodity->stock = $new->stock = (int)($inventory['count'] ?? $commodity->stock);
                $commodity->contact_type = $new->contact_type = $inventory['contact_type'] ?? $commodity->contact_type;

                $new->save();
            }
        } else if ($commodity->delivery_way == 0) {
            $commodity->stock = Card::query()->where("commodity_id", $commodity->id)->where("status", 0)->count();

        }

        //解析商品配置
        $this->order->parseConfig($commodity, $group);


        //处理分站
        $this->substationPriceIncrease($commodity);

        $commodity->service_url = Config::get("service_url");
        $commodity->service_qq = Config::get("service_qq");

        if ($commodity->draft_status == 1 && $commodity->draft_premium > 0 && $commodity->level_disable != 1) {
            $commodity->draft_premium = $this->order->getValuationPrice($commodity->id, $commodity->draft_premium, $group);
        }

        $array = $commodity->toArray();

        if ($array["owner"]) {
            $business = Business::query()->where("user_id", $array["owner"]['id'])->first();
            if ($business) {
                $array['service_url'] = $business->service_url;
                $array['service_qq'] = $business->service_qq;
            }
        }

        if (!$array['cover']) {
            $array['cover'] = "/favicon.ico";
        }

        $array['share_url'] = Client::getUrl() . "/item/{$array['id']}";
        $array['login'] = (bool)$user;
        if ($array['login']) {
            $array['share_url'] .= "?from={$user->id}";
        }

        //获取网站是否需要验证码
        $array['trade_captcha'] = (int)Config::get("trade_verification");

        if ($commodity->widget) {
            $array['widget'] = json_decode($commodity->widget, true);
        }

        return $array;
    }

    /**
     * @param int|string|null $stock
     * @return string
     */
    public function getHideStock(int|string|null $stock): string
    {
        $stock = (int)$stock;
        return match (true) {
            $stock <= 0 => "已售罄",
            $stock <= 5 => "即将售罄",
            $stock <= 20 => "一般",
            $stock <= 100 => "充足",
            default => "非常多"
        };
    }

    /**
     * @param int|string|null $stock
     * @return int
     */
    public function getStockState(int|string|null $stock): int
    {
        $stock = (int)$stock;
        return match (true) {
            $stock <= 0 => 0,
            $stock <= 5 => 1,
            $stock <= 20 => 2,
            $stock <= 100 => 3,
            default => 4
        };
    }

    /**
     * @param int|Commodity|string $commodity
     * @param string|null $race
     * @param array|null $sku
     * @return string
     * @throws JSONException
     */
    public function getItemStock(int|Commodity|string $commodity, ?string $race = null, ?array $sku = []): string
    {
        if (is_int($commodity)) {
            $commodity = Commodity::with(['shared'])->find($commodity);
        } elseif (is_string($commodity)) {
            $commodity = Commodity::with(['shared'])->where("code", $commodity)->first();
        }

        if (!$commodity) throw new JSONException("商品不存在");

        if (($hook = \hook(Hook::SERVICE_SHOP_GET_ITEM_STOCK, $commodity, $race, $sku)) instanceof Stock) return $hook->getStock();

        //对接商品
        if ($commodity->shared) {
            return $this->getSharedStock($commodity, $race, $sku);
        } else if ($commodity->delivery_way == 0) {
            //库存
            $card = Card::query()->where("commodity_id", $commodity->id)->where("status", 0);
            if ($race) $card = $card->where("race", $race);
            if (!empty($sku)) {
                foreach ($sku as $k => $v) {
                    $card = $card->where("sku->{$k}", $v);
                }
            }
            return (string)$card->count();
        }
        return (string)$commodity->stock;
    }


    /**
     * @param int $id
     * @param string|null $race
     * @param array|null $sku
     * @return string
     */
    public function getSharedStockHash(int $id, ?string $race = null, ?array $sku = []): string
    {
        return md5($id . $race . json_encode($sku ?: []));
    }


    /**
     * @param int|Commodity $commodity
     * @param string|null $race
     * @param array|null $sku
     * @return void
     * @throws JSONException
     */
    public function updateSharedStock(int|Commodity $commodity, ?string $race = null, ?array $sku = []): void
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }
        if (!$commodity) throw new JSONException("商品不存在");
        $hash = $this->getSharedStockHash($commodity->id, $race, $sku);
        $stock = is_array($commodity->shared_stock) ? $commodity->shared_stock : [];
        unset($stock[$hash]);
        Commodity::query()->where("id", $commodity->id)->update(["shared_stock" => $stock]);
    }

    /**
     * @param int|Commodity $commodity
     * @param string|null $race
     * @param array|null $sku
     * @return string|null
     * @throws JSONException
     */
    public function getSharedStock(int|Commodity $commodity, ?string $race = null, ?array $sku = []): string|null
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }
        if (!$commodity) throw new JSONException("商品不存在");
        $hash = $this->getSharedStockHash($commodity->id, $race, $sku);

        if (!is_array($commodity->shared_stock) || !isset($commodity->shared_stock[$hash])) {
            try {
                $stock = $this->shared->getItemStock($commodity->shared, $commodity->shared_code, $race, $sku);
            } catch (\Throwable $e) {
                // fall back to local stock when the remote接口无法访问,避免前台直接报错
                @file_put_contents(BASE_PATH . "/runtime/shared_inventory.log", "[" . date('Y-m-d H:i:s') . "] stock fetch failed, commodity {$commodity->id}: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
                $fallback = $commodity->stock ?? 0;
                return (string)$fallback;
            }
            $array = is_array($commodity->shared_stock) ? $commodity->shared_stock : [];
            $array[$hash] = $stock;
            Commodity::query()->where("id", $commodity->id)->update(["shared_stock" => $array]);
            return $stock;
        }

        return $commodity->shared_stock[$hash];
    }


    /**
     * @param Commodity|int|string $commodity
     * @param int $cardId
     * @return array
     * @throws JSONException
     */
    public function getDraft(Commodity|int|string $commodity, int $cardId): array
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }
        if (!$commodity) throw new JSONException("商品不存在");


        $card = Card::query()->where("commodity_id", $commodity->id)->where("id", $cardId)->first();
        if (!$card) {
            throw new JSONException("预选的宝贝不存在");
        }

        if ($commodity->id != $card->commodity_id) {
            throw new JSONException("此预告信息不属于此商品");
        }

        if ($card->status != 0) {
            throw new JSONException("此宝贝已被他人抢走");
        }

        return ["draft_premium" => $card->draft_premium];
    }


    /**
     * @param Commodity $commodity
     * @return void
     */
    public function substationPriceIncrease(Commodity &$commodity): void
    {
        $business = Business::get();

        if (!$business) {
            return;
        }

        /**
         * @var UserCommodity $userCommodity
         */
        $userCommodity = UserCommodity::query()->where("user_id", $business->user_id)->where("commodity_id", $commodity->id)->first();

        if (!$userCommodity) {
            return;
        }

        if ($userCommodity->name) {
            $commodity->name = $userCommodity->name;
        }

        $config = $commodity->config ?: [];

        if ($userCommodity->premium > 0) {

            $commodity->price = (new Decimal($commodity->price))->mul($userCommodity->premium / 100)->add($commodity->price)->getAmount();
            $commodity->user_price = (new Decimal($commodity->user_price))->mul($userCommodity->premium / 100)->add($commodity->user_price)->getAmount();

            if ($commodity->draft_premium > 0) {
                $commodity->draft_premium = (new Decimal($commodity->draft_premium))->mul($userCommodity->premium / 100)->add($commodity->draft_premium)->getAmount();
            }

            if (is_array($config['category'])) {
                foreach ($config['category'] as &$price) {
                    $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                }
            }


            if (is_array($config['wholesale'])) {
                foreach ($config['wholesale'] as &$price) {
                    $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                }
            }

            if (is_array($config['category_wholesale'])) {
                foreach ($config['category_wholesale'] as &$arr) {
                    foreach ($arr as &$price) {
                        $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                    }
                }
            }

            if (is_array($config['sku'])) {
                foreach ($config['sku'] as &$arr) {
                    foreach ($arr as &$price) {
                        $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                    }
                }
            }
        }

        $commodity->config = $config;
    }

    /**
     * @param Commodity|int $commodity
     * @param int|string|float $amount
     * @return string
     * @throws JSONException
     */
    public function getSubstationPrice(Commodity|int $commodity, int|string|float $amount): string
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }

        if (!$commodity) {
            throw new JSONException("商品不存在");
        }

        $business = Business::get();

        if (!$business) {
            return (string)$amount;
        }

        /**
         * @var UserCommodity $userCommodity
         */
        $userCommodity = UserCommodity::query()->where("user_id", $business->user_id)->where("commodity_id", $commodity->id)->first();

        if (!$userCommodity) {
            return (string)$amount;
        }

        if ($userCommodity->premium > 0) {
            return (new Decimal($amount))->mul($userCommodity->premium / 100)->add($amount)->getAmount();
        }

        return (string)$amount;
    }

}

4.assets/admin/controller/shared/store.js
代码:
!function () {
    let table;

    table = new Table("/admin/api/store/data", "#shared-store-table");

    const modal = (title, assign = {}) => {
        component.popup({
            submit: '/admin/api/store/save',
            tab: [
                {
                    name: title,
                    form: [
                        {
                            title: "协议",
                            name: "type",
                            type: "select",
                            placeholder: "请选择协议",
                            dict: "_shared_type",
                            default: 0,
                            required: true
                        },
                        {
                            title: "店铺地址",
                            name: "domain",
                            type: "input",
                            placeholder: "需要带http://或者https://(推荐,如果支持)",
                            required: true
                        },
                        {
                            title: "商户ID", name: "app_id", type: "input", placeholder: "请输入商户ID",
                            required: true
                        },
                        {
                            title: "商户密钥", name: "app_key", type: "input", placeholder: "请输入商户密钥",
                            required: true
                        },
                    ]
                },
            ],
            autoPosition: true,
            height: "auto",
            assign: assign,
            width: "580px",
            done: (res) => {
                table.refresh();
            }
        });
    }


    table.setColumns([
        {checkbox: true}
        , {
            field: 'name', title: '店铺名称', formatter: (a, b) => {
                return `<span class="table-item"><img src="${b.domain}/favicon.ico" class="table-item-icon"><span class="table-item-name">${a}</span></span>`;
            }
        }, {
            field: 'domain', title: '店铺地址' , formatter : format.link
        }, {
            field: 'balance', title: '余额(缓存)', formatter: _ => format.money(_, "green")
        }, {
            field: 'status', title: '状态', formatter: function (val, item) {
                return '<span class="connect-' + item.id + '"><span class="badge badge-light-primary">连接中..</span></span>'
            }
        }, {
            field: 'type', title: '协议', dict: "_shared_type"
        },
        {
            field: 'operation', title: '操作', type: 'button', buttons: [
                {
                    icon: 'fa-duotone fa-regular fa-link',
                    tips: "接入货源",
                    class: "text-primary",
                    click: (event, value, row, index) => {
                        util.post("/admin/api/store/items", {id: row.id}, res => {
                            let items = {};

                            res.data.forEach(value => {
                                value.children.forEach(item => {
                                    items[item.id] = item;
                                });
                            });


                            component.popup({
                                submit: (result, index) => {
                                    if (result.auth.length == 0) {
                                        layer.msg("至少选择一个远端店铺的商品");
                                        return;
                                    }

                                    result.items = [];

                                    result.auth.forEach(itemId => {
                                        if (parseInt(itemId) == 0) {
                                            return;
                                        }
                                        let item = items[itemId];
                                        result.items.push(item);
                                    });

                                    delete result.auth;

                                    result.store_id = row.id;

                                    util.post('/admin/api/store/addItem?storeId=' + row.id, result, res => {
                                        message.alert(res.msg, "success")
                                    });
                                },
                                tab: [
                                    {
                                        name: util.icon("fa-duotone fa-regular fa-link") + " 接入货源",
                                        form: [
                                            {
                                                title: "商品分类",
                                                name: "category_id",
                                                type: "treeSelect",
                                                placeholder: "请选择商品分类",
                                                dict: `category->owner=0,id,name,pid&tree=true`,
                                                required: true,
                                                parent: false
                                            },
                                            {
                                                title: "远端图片本地化",
                                                name: "image_download",
                                                type: "switch",
                                                tips: "启用后,导入对方商品时,会自动将对方所有图片资源下载至本地"
                                            },
                                            {
                                                title: "远端信息同步",
                                                name: "shared_sync",
                                                type: "switch",
                                                tips: "启用后,远端商品信息会实时同步本地,远端价发生变化会立即同步"
                                            },
                                            {
                                                title: "远端库存同步",
                                                name: "inventory_sync",
                                                type: "switch",
                                                default: true,
                                                tips: "启用后,远端商品库存会定期/实时读取,前台展示远端库存"
                                            },
                                            {
                                                title: "立即上架",
                                                name: "shelves",
                                                type: "switch",
                                                tips: "开启后,入库完毕后会立即上架"
                                            },
                                            {
                                                title: "加价模式",
                                                name: "premium_type",
                                                type: "radio",
                                                dict: [
                                                    {id: 0, name: "普通金额加价"},
                                                    {id: 1, name: "百分比加价(99%的人选择)"}
                                                ],
                                                default: 1,
                                                required: true
                                            },
                                            {
                                                title: "加价数额",
                                                name: "premium",
                                                type: "input",
                                                placeholder: "加价金额/百分比(小数代替)",
                                                required: true
                                            },
                                            {title: "远程商品", name: "auth", type: "treeCheckbox", dict: res.data}
                                        ]
                                    }
                                ],
                                assign: {},
                                autoPosition: true,
                                height: "auto",
                                width: "780px",
                                done: () => {

                                }
                            });
                        });
                    }
                }, {
                    icon: 'fa-duotone fa-regular fa-pen-to-square',
                    class: "text-primary",
                    click: (event, value, row, index) => {
                        modal(util.icon("fa-duotone fa-regular fa-pen-to-square me-1") + " 修改远端店铺", row);
                    }
                },
                {
                    icon: 'fa-duotone fa-regular fa-trash-can',
                    class: "text-danger",
                    click: (event, value, row, index) => {
                        message.ask("您确定要移除此远端店铺吗,此操作无法恢复", () => {
                            util.post('/admin/api/store/del', {list: [row.id]}, res => {
                                message.success("删除成功");
                                table.refresh();
                            });
                        });
                    }
                }
            ]
        },
    ]);
    table.setPagination(15, [15, 30]);

    table.onComplete((a, b, c) => {
        c?.data?.list?.forEach(item => {
            $.post("/admin/api/store/connect", {id: item.id}, run => {
                let ins = $(".connect-" + item.id);
                if (run.code == 200) {
                    ins.html(format.badge("正常", "a-badge-success"));
                    $(".items-" + item.id).show();
                } else {
                    ins.html(format.badge(run.msg, "a-badge-danger"));
                }
            });
        });
    });
    table.render();


    $('.btn-app-create').click(function () {
        modal(`${util.icon("fa-duotone fa-regular fa-link")} 添加远端店铺`);
    });
}();
 
如果想虚拟显示销量,替换以下两个文件:
1.app/Controller/User/Api/Index.php
代码:
<?php
declare(strict_types=1);

namespace App\Controller\User\Api;


use App\Consts\Hook;
use App\Controller\Base\API\User;
use App\Entity\Query\Get;
use App\Interceptor\UserVisitor;
use App\Interceptor\Waf;
use App\Model\Card;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\Order;
use App\Model\Pay;
use App\Model\UserCommodity;
use App\Service\Query;
use App\Service\Shared;
use App\Service\Shop;
use App\Util\Client;
use App\Util\FileCache;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Kernel\Annotation\Inject;
use Kernel\Annotation\Interceptor;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Util\Decimal;

#[Interceptor([Waf::class, UserVisitor::class])]
class Index extends User
{
    #[Inject]
    private Shared $shared;

    #[Inject]
    private Query $query;

    #[Inject]
    private \App\Service\Order $order;

    #[Inject]
    private Shop $shop;


    /**
     * @return array
     */
    public function data(): array
    {
        $category = $this->shop->getCategory($this->getUserGroup());
        hook(Hook::USER_API_INDEX_CATEGORY_LIST, $category);
        return $this->json(200, "success", $category);
    }

    /**
     * @return array
     * @throws JSONException
     * @throws RuntimeException
     */
    public function commodity(): array
    {
        $keywords = (string)$_GET['keywords'];
        $limit = (int)$_GET['limit'];
        $page = (int)$_GET['page'];
        $categoryId = $_GET['categoryId'];

        FileCache::clearCache('shop');

        $commodity = Commodity::query()
            ->with(['owner' => function (Relation $relation) {
                $relation->select(["id", "username", "avatar"]);
            }, 'category' => function (Relation $relation) {
                $relation->select(["id", "name", "icon"]);
            }]);

        if ($categoryId == 'recommend') {
            $commodity = $commodity->where("recommend", 1);
        } elseif ($categoryId != 0) {
            $commodity = $commodity->where("category_id", $categoryId);
        }

        if ($keywords != "") {
            $commodity = $commodity->where('name', 'like', '%' . $keywords . '%');
        }

        $bus = \App\Model\Business::get();
        $userCommodityMap = []; //自定义名称的MAP

        if ($bus) {
            //商家
            if ($bus->master_display == 0) {
                $commodity = $commodity->where("owner", $bus->user_id);
            } else {
                //查出所有自己定义的商品
                $userCommodity = UserCommodity::query()->where("user_id", $bus->user_id)->get();
                //隐藏的分类ID
                $hideCommodity = [];

                foreach ($userCommodity as $userComm) {
                    if ($userComm->status == 0) {
                        $hideCommodity[] = $userComm->commodity_id;
                    } else {
                        $userCommodityMap[$userComm->commodity_id] = $userComm;
                    }
                }

                $commodity = $commodity->whereNotIn("id", $hideCommodity)->whereRaw("(`owner`=0 or `owner`={$bus->user_id})");
            }
        } else {
            //主站
            if (Config::get("substation_display") == 1) {
                $let = "(`owner`=0 or ";
                //显示商家
                $list = (array)json_decode(Config::get("substation_display_list"), true);
                foreach ($list as $userId) {
                    $let .= "`owner`={$userId} or ";
                }
                $let = trim(trim($let), "or") . ")";
                $commodity = $commodity->whereRaw($let);
            } else {
                $commodity = $commodity->where("owner", 0);
            }
        }

        $commodity = $commodity
            ->where("status", 1)
            ->orderBy("sort", "asc")
            ->select([
                'id', 'name', 'cover',
                'status', 'delivery_way', 'price',
                'user_price', 'shared_code', 'inventory_sync',
                'level_disable', 'level_price', 'hide', 'owner', 'inventory_hidden', "recommend", 'category_id', 'stock', 'shared_id'
            ])
            ->withCount(['order as order_sold' => function (Builder $relation) {
                $relation->where("delivery_status", 1);
            }]);
        if ($limit == 0) {
            $commodity = $commodity
                ->get();
            $total = count($commodity);
            $data = $commodity->toArray();
        } else {
            $commodity = $commodity
                ->paginate($limit, ["*"], "", $page);
            $total = $commodity->total();
            $data = $commodity->toArray()['data'];
        }

        $user = $this->getUser();
        $userGroup = $this->getUserGroup();
        //取得分类
        $category = $this->shop->getCategory($userGroup);
        $cates = [];
        foreach ($category as $cate) {
            $cates[] = (string)$cate['id'];
        }

        //最终的商品数据遍历
        foreach ($data as $key => $val) {
            $parseGroupConfig = Commodity::parseGroupConfig($val['level_price'], $userGroup);
            if (!in_array((string)$val['category_id'], $cates) || $val['hide'] == 1 && (!$parseGroupConfig || !isset($parseGroupConfig['show']) || $parseGroupConfig['show'] != 1)) {
                //隐藏商品
                unset($data[$key]);
                continue;
            }

            if ($val['shared_id'] && (int)$val['inventory_sync'] === 1) {
                $shopCache = FileCache::getJsonFile("shop", $val['id'] . "_shared");
                if (!empty($shopCache)) {
                    $inventory = $shopCache;
                } else {
                    $com = Commodity::query()->with(['shared'])->find($val['id']);
                    if (!$com || !$com->shared) {
                        unset($data[$key]);
                        continue;
                    }
                    try {
                        $inventory = $this->shared->inventory($com->shared, $com);
                        FileCache::setJsonFile("shop", $val['id'] . "_shared", $inventory, 300);
                    } catch (\Throwable $e) {
                        FileCache::setJsonFile("shop", $val['id'] . "_shared", [
                            "count" => 0,
                            "delivery_way" => 0
                        ], 300);
                        @file_put_contents(BASE_PATH . "/runtime/shared_inventory.log", "[" . date('Y-m-d H:i:s') . "] inventory fetch failed, commodity {$val['id']}: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
                        unset($data[$key]);
                        continue;
                    }
                }

                $stockCount = (int)($inventory['count'] ?? 0);
                $deliveryWay = $inventory['delivery_way'] ?? $data[$key]['delivery_way'];

                if ($stockCount === 0 && (int)$deliveryWay === 0) {
                    unset($data[$key]);
                    continue;
                }

                $data[$key]['stock'] = $stockCount;
                $data[$key]['delivery_way'] = $deliveryWay;
            } elseif ($val['delivery_way'] == 0 && !$val['shared_id']) {
                $data[$key]['stock'] = Card::query()->where("status", 0)->where("commodity_id", $val['id'])->count();
            }

            //如果登录后,则自动计算登录后的价格
            if ($user) {
                $tradeAmount = $this->order->valuation(commodity: $commodity[$key], group: $userGroup);
                $data[$key]['price'] = $tradeAmount;
                $data[$key]['user_price'] = $tradeAmount;
            }

            unset(
                $data[$key]['level_price'],
                $data[$key]['level_disable']
            );

            if (!$val['cover']) {
                $data[$key]['cover'] = "/favicon.ico";
            }

            //分站自定义名称和价格
            if (isset($userCommodityMap[$val['id']])) {
                $var = $userCommodityMap[$val['id']];

                if ($var->premium > 0) {
                    $data[$key]['price'] = (new Decimal($data[$key]['price'], 2))->mul($var->premium / 100)->add($data[$key]['price'])->getAmount();
                    $data[$key]['user_price'] = (new Decimal($data[$key]['user_price'], 2))->mul($var->premium / 100)->add($data[$key]['user_price'])->getAmount();
                }
                if ($var->name) {
                    $data[$key]['name'] = $var->name;
                }
            }

            if ($data[$key]['stock'] === null) {
                $data[$key]['stock'] = 0;
            }

            // 伪造一个更自然的销量展示,最低 200 起步,随时间缓慢增长
            $displaySold = (int)($data[$key]['order_sold'] ?? 0);
            if ($displaySold < 200) {
                $tick = (int)floor(time() / 3600); // 每小时递增一次基准
                $displaySold = 200 + (($val['id'] * 73 + $tick) % 300);
            }
            $data[$key]['order_sold'] = $displaySold;

            //隐藏库存
            $data[$key]['stock_state'] = $this->shop->getStockState($data[$key]['stock']);
            if ($val['inventory_hidden'] == 1) {
                $data[$key]['stock'] = $this->shop->getHideStock($data[$key]['stock']);
            }
        }

        $data = array_values($data);
        hook(Hook::USER_API_INDEX_COMMODITY_LIST, $data);
        $json = $this->json(200, "success", $data);
        $json['total'] = $total;
        return $json;
    }

    /**
     * @param int $commodityId
     * @return array
     */
    public function commodityDetail(int $commodityId): array
    {
        $array = $this->shop->getItem($commodityId, $this->getUser(), $this->getUserGroup());
        hook(Hook::USER_API_INDEX_COMMODITY_DETAIL_INFO, $array);

        $array['stock_state'] = $this->shop->getStockState($array['stock']);
        if ($array['inventory_hidden'] == 1) {
            $array['stock'] = $this->shop->getHideStock($array['stock']);
        }

        return $this->json(200, 'success', $array);
    }

    /**
     * @return array
     * @throws JSONException
     */
    public function card(): array
    {
        $map = $this->request->post();
        /**
         * @var Commodity $commodity
         */
        $commodity = Commodity::with(['shared'])->find($map['item_id']);
        $limit = $map['limit'] ?? 10;

        if (!$commodity) {
            throw new JSONException("商品不存在");
        }

        if ($commodity->status != 1) {
            throw new JSONException("该商品暂未上架");
        }
        if ($commodity->draft_status != 1) {
            throw new JSONException("该商品不支持预选");
        }

        if ($commodity->shared) {
            $data = $this->shared->draftCard($commodity->shared, $commodity->shared_code, $map);
            //加价算法
            foreach ($data['list'] as &$item) {
                if ($item['draft_premium'] > 0) {
                    $item['draft_premium'] = $this->shared->AdjustmentAmount($commodity->shared_premium_type, $commodity->shared_premium, $item['draft_premium']);
                }
            }
        } else {
            $get = new Get(Card::class);
            $get->setPaginate((int)$this->request->post("page"), (int)$limit);
            $get->setWhere($map);
            $get->setColumn('id', 'draft', 'draft_premium');

            $data = $this->query->get($get, function (Builder $builder) use ($map) {
                $builder = $builder->where("commodity_id", $map['item_id'])->where("status", 0);

                if (!empty($map['race'])) {
                    $builder = $builder->where("race", $map['race']);
                }

                if (!empty($map['sku']) && is_array($map['sku'])) {
                    foreach ($map['sku'] as $k => $v) {
                        $builder = $builder->where("sku->{$k}", $v);
                    }
                }

                return $builder;
            });
        }

        //分站处理
        if (\App\Model\Business::state()) {
            foreach ($data['list'] as &$item) {
                if ($item['draft_premium'] > 0) {
                    $item['draft_premium'] = $this->shop->getSubstationPrice($commodity, $item['draft_premium']);
                }
            }
        }

        if ($commodity->level_disable != 1) {
            //折扣处理
            foreach ($data['list'] as &$item) {
                if ($item['draft_premium'] > 0) {
                    $item['draft_premium'] = $this->order->getValuationPrice($commodity->id, $item['draft_premium'], $this->getUserGroup());
                }
            }
        }

        return $this->json(data: $data);
    }


    /**
     * @return array
     */
    public
    function valuation(): array
    {
        $price = $this->order->valuation(
            commodity: (int)$this->request->post("item_id"),
            num: (int)$this->request->post("num"),
            race: (string)$this->request->post("race"),
            sku: (array)$this->request->post("sku"),
            cardId: (int)$this->request->post("card_id"),
            coupon: (string)$this->request->post("coupon"),
            group: $this->getUserGroup()
        );
        $price = $this->shop->getSubstationPrice((int)$this->request->post("item_id"), $price);
        return $this->json(data: ["price" => $price]);
    }


    /**
     * @return array
     */
    public
    function stock(): array
    {
        $commodity = Commodity::with(['shared'])->find((int)$this->request->post("item_id"));
        $stock = $this->shop->getItemStock($commodity, (string)$this->request->post("race"), (array)$this->request->post("sku"));

        $array = ["stock" => $stock];
        $array['stock_state'] = $this->shop->getStockState($stock);
        if ($commodity->inventory_hidden == 1) {
            $array['stock'] = $this->shop->getHideStock($stock);
        }
        return $this->json(data: $array);
    }

    /**
     * @return array
     */
    public
    function pay(): array
    {

        $equipment = 2;

        if (Client::isMobile()) {
            $equipment = 1;
        }

        if (Client::isWeChat()) {
            $equipment = 3;
        }

        $let = "(`equipment`=0 or `equipment`={$equipment})";

        if (!$this->getUser()) {
            $let .= " and id!=1";
        }

        $pay = Pay::query()->orderBy("sort", "asc")->where("commodity", 1)->whereRaw($let)->get(['id', 'name', 'icon', 'handle'])->toArray();

        hook(Hook::USER_API_INDEX_PAY_LIST, $pay);
        return $this->json(200, 'success', $pay);
    }


    /**
     * @param string $keywords
     * @return array
     */
    public function query(string $keywords): array
    {
        $keywords = trim($keywords);

        $get = new Get(Order::class);
        $get->setPaginate((int)$this->request->post("page"), (int)$this->request->post("limit"));
        $get->setColumn('id', 'trade_no', 'sku', 'secret', 'user_id', 'password', 'amount', 'pay_id', 'commodity_id', 'create_time', 'pay_time', 'delivery_status', 'status', 'card_num', 'contact', "race");

        $data = $this->query->get($get, function (Builder $builder) use ($keywords) {

            $builder = $builder->with(['pay' => function (Relation $relation) {
                $relation->select(['id', 'name', 'icon']);
            }, 'commodity' => function (Relation $relation) {
                $relation->select(['id', 'name', 'cover', 'password_status', 'leave_message']);
            }]);

            if (preg_match('/^\d{18}$/', $keywords)) {
                $builder = $builder->where("trade_no", $keywords);
            } else {
                $builder = $builder->where("contact", $keywords);
            }
            return $builder;
        });

        foreach ($data['list'] as &$item) {
            if ($item['status'] != 1) {
                unset($item['commodity']['leave_message']);
                unset($item['secret']);
            }

            if (!empty($item['password'])) {
                $item['password'] = true;
                unset($item['secret']);
            }
        }

        hook(Hook::USER_API_INDEX_QUERY_LIST, $data);
        return $this->json(data: $data);
    }

    /**
     * @param string $tradeNo
     * @param string $password
     * @return array
     * @throws JSONException
     */
    public function secret(string $tradeNo, string $password): array
    {
        $order = Order::with(['commodity'])->where("trade_no", $tradeNo)->first();

        if (!$order) {
            throw new JSONException("未查询到相关信息");
        }

        if (!empty($order->password)) {
            if ($password != $order->password) {
                throw new JSONException("密码错误");
            }
        }

        if ($order->status != 1) {
            throw new JSONException("订单还未支付");
        }

        $widget = (array)json_decode((string)$order->widget, true);
        if (empty($widget)) {
            $widget = null;
        }

        hook(Hook::USER_API_INDEX_QUERY_SECRET, $order);
        return $this->json(data: [
            'secret' => $order->secret,
            'widget' => $widget,
            'leave_message' => $order?->commodity?->leave_message
        ]);
    }
}

2.app/Service/Bind/Shop.php

代码:
<?php
declare(strict_types=1);

namespace App\Service\Bind;

use App\Consts\Hook;
use App\Model\Business;
use App\Model\Card;
use App\Model\Category;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\User;
use App\Model\UserCategory;
use App\Model\UserCommodity;
use App\Model\UserGroup;
use App\Service\Shared;
use App\Util\Client;
use App\Util\Ini;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Kernel\Annotation\Inject;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Plugin\Entity\Stock;
use Kernel\Util\Decimal;

class Shop implements \App\Service\Shop
{
    #[Inject]
    private Shared $shared;

    #[Inject]
    private \App\Service\Order $order;

    /**
     * @param UserGroup|null $group
     * @return array
     * @throws RuntimeException
     */
    public function getCategory(?UserGroup $group): array
    {
        $category = Category::query()->withCount(['children as commodity_count' => function (Builder $builder) {
            $builder->where("status", 1);
        }])->where("status", 1)->orderBy("sort", "asc");

        $bus = Business::get();
        $userCategoryMap = []; //自定义名称的MAP
        $master = true;

        if ($bus) {
            $master = false;
            //商家
            if ($bus->master_display == 0) {
                $category = $category->where("owner", $bus->user_id);
            } else {
                //查询出所有不显示的ID
                $userCategory = UserCategory::query()->where("user_id", $bus->user_id)->get();
                //隐藏的分类ID
                $hideCategory = [];

                foreach ($userCategory as $userCate) {
                    if ($userCate->status == 0) {
                        $hideCategory[] = $userCate->category_id;
                    } else {
                        $userCategoryMap[$userCate->category_id] = $userCate;
                    }
                }

                $category = $category->whereNotIn("id", $hideCategory)->whereRaw("(`owner`=0 or `owner`={$bus->user_id})");
            }
        } else {
            //主站
            if (Config::get("substation_display") == 1) {
                //显示商家
                $list = (array)json_decode(Config::get("substation_display_list"), true);
                $let = "(`owner`=0 or ";
                foreach ($list as $userId) {
                    $let .= "`owner`={$userId} or ";
                }
                $let = trim(trim($let), "or") . ")";
                $category = $category->whereRaw($let);
            } else {
                $category = $category->where("owner", 0);
            }
        }
        //拿到最终的分类数据
        $category = $category->get();

        foreach ($category as $index => $item) {

            $levelConfig = $item->getLevelConfig($group);
            if ($item->hide == 1 && (!$levelConfig || !isset($levelConfig['show']) || (int)$levelConfig['show'] != 1)) {
                unset($category[$index]);
                continue;
            }

            if (isset($userCategoryMap[$item->id])) {
                $var = $userCategoryMap[$item->id];
                if ($var->name) {
                    $category[$index]['name'] = $var->name;
                }
            }

            if (!$item->icon) {
                $category[$index]['icon'] = '/favicon.ico';
            }
        }

        $array = $category->toArray();
        $array = array_values($array);

        $commodityRecommend = Config::get("commodity_recommend");
        if ($commodityRecommend == 1 && $master) {
            array_unshift($array, [
                "id" => 'recommend',
                "name" => Config::get("commodity_name"),
                "sort" => 1,
                "create_time" => "-",
                "owner" => 0,
                "icon" => "/assets/static/images/recommend.png",
                "status" => 1,
                "hide" => 0,
                "user_level_config" => null,
                "commodity_count" => Commodity::query()->where("status", 1)->where("recommend", 1)->count(),
            ]);
        }

        return $array;
    }

    /**
     * @param int|string $commodityId
     * @param User|null $user
     * @param UserGroup|null $group
     * @return array
     * @throws JSONException
     * @throws RuntimeException
     */
    public function getItem(int|string $commodityId, ?User $user = null, ?UserGroup $group = null): array
    {

        $commodity = Commodity::query()->with(['owner' => function (Relation $relation) {
            $relation->select(["id", "username", "avatar"]);
        }])
            ->select(["id", "name", "description",
                "only_user", "purchase_count", "category_id", "cover", "price", "user_price",
                "status", "owner", "delivery_way", "contact_type", "password_status", "level_price",
                "level_disable", "coupon", "shared_id", "shared_code", "shared_premium", "shared_premium_type", "seckill_status",
                "seckill_start_time", "seckill_end_time", "draft_status", "draft_premium", "inventory_hidden",
                "widget", "minimum", "maximum", "shared_sync", "config", "stock", "code"])
            ->withCount(['order as order_sold' => function (Builder $relation) {
                $relation->where("delivery_status", 1);
            }]);

        if (is_int($commodityId)) {
            $commodity = $commodity->find($commodityId);
        } else {
            $commodity = $commodity->where("code", $commodityId)->first();
        }

        if (!$commodity) {
            throw new JSONException("商品不存在");
        }

        if ($commodity->status != 1) {
            throw new JSONException("该商品暂未上架");
        }

        /**
         * @var Shared $shared
         */
        $shared = \App\Model\Shared::query()->find($commodity->shared_id);

        $inventory = null;

        if ($shared) {
            try {
                $inventory = $this->shared->inventory($shared, $commodity);
                $commodity->stock = (int)($inventory['count'] ?? $commodity->stock);
                $commodity->delivery_way = $inventory['delivery_way'] ?? $commodity->delivery_way;
                $commodity->draft_status = $inventory['draft_status'] ?? $commodity->draft_status;
            } catch (\Throwable $e) {
                $commodity->stock = $commodity->stock ?? 0;
            }

            //远端同步
            if ($commodity->shared_sync == 1 && is_array($inventory)) {
                /**
                 * @var Commodity $new
                 */
                $new = Commodity::query()->find($commodity->id);

                $configSource = $inventory['config'] ?? $commodity->config;
                $configString = is_array($configSource) ? Ini::toConfig($configSource) : (string)$configSource;
                $priceSource = $inventory['price'] ?? $commodity->price;
                $userPriceSource = $inventory['user_price'] ?? $commodity->user_price;

                $base = $this->shared->AdjustmentPrice($configString, (string)$priceSource, (string)$userPriceSource, $new->shared_premium_type, $new->shared_premium);

                $commodity->price = $new->price = $base['price'];
                $commodity->user_price = $new->user_price = $base['user_price'];
                $commodity->config = $new->config = Ini::toConfig($base['config']);
                $commodity->draft_status = $new->draft_status = $inventory['draft_status'] ?? $commodity->draft_status;
                $commodity->draft_premium = $new->draft_premium = $inventory['draft_premium'] ?? $commodity->draft_premium;
                $commodity->seckill_status = $new->seckill_status = $inventory['seckill_status'] ?? $commodity->seckill_status;
                $commodity->seckill_start_time = $new->seckill_start_time = $inventory['seckill_start_time'] ?? $commodity->seckill_start_time;
                $commodity->seckill_end_time = $new->seckill_end_time = $inventory['seckill_end_time'] ?? $commodity->seckill_end_time;
                $commodity->widget = $new->widget = $commodity->widget;
                $commodity->minimum = $new->minimum = $inventory['minimum'] ?? $commodity->minimum;
                $commodity->maximum = $new->maximum = $inventory['maximum'] ?? $commodity->maximum;
                $commodity->stock = $new->stock = (int)($inventory['count'] ?? $commodity->stock);
                $commodity->contact_type = $new->contact_type = $inventory['contact_type'] ?? $commodity->contact_type;

                $new->save();
            }
        } else if ($commodity->delivery_way == 0) {
            $commodity->stock = Card::query()->where("commodity_id", $commodity->id)->where("status", 0)->count();

        }

        //解析商品配置
        $this->order->parseConfig($commodity, $group);


        //处理分站
        $this->substationPriceIncrease($commodity);

        $commodity->service_url = Config::get("service_url");
        $commodity->service_qq = Config::get("service_qq");

        if ($commodity->draft_status == 1 && $commodity->draft_premium > 0 && $commodity->level_disable != 1) {
            $commodity->draft_premium = $this->order->getValuationPrice($commodity->id, $commodity->draft_premium, $group);
        }

        $array = $commodity->toArray();

        if ($array["owner"]) {
            $business = Business::query()->where("user_id", $array["owner"]['id'])->first();
            if ($business) {
                $array['service_url'] = $business->service_url;
                $array['service_qq'] = $business->service_qq;
            }
        }

        if (!$array['cover']) {
            $array['cover'] = "/favicon.ico";
        }

        $array['share_url'] = Client::getUrl() . "/item/{$array['id']}";
        $array['login'] = (bool)$user;
        if ($array['login']) {
            $array['share_url'] .= "?from={$user->id}";
        }

        //获取网站是否需要验证码
        $array['trade_captcha'] = (int)Config::get("trade_verification");

        // 伪造一个更自然的销量展示,最低 200 起步,随时间缓慢增长
        if (($array['order_sold'] ?? 0) < 200) {
            $tick = (int)floor(time() / 3600); // 每小时递增一次基准
            $array['order_sold'] = 200 + (($array['id'] * 73 + $tick) % 300);
        }

        if ($commodity->widget) {
            $array['widget'] = json_decode($commodity->widget, true);
        }

        return $array;
    }

    /**
     * @param int|string|null $stock
     * @return string
     */
    public function getHideStock(int|string|null $stock): string
    {
        $stock = (int)$stock;
        return match (true) {
            $stock <= 0 => "已售罄",
            $stock <= 5 => "即将售罄",
            $stock <= 20 => "一般",
            $stock <= 100 => "充足",
            default => "非常多"
        };
    }

    /**
     * @param int|string|null $stock
     * @return int
     */
    public function getStockState(int|string|null $stock): int
    {
        $stock = (int)$stock;
        return match (true) {
            $stock <= 0 => 0,
            $stock <= 5 => 1,
            $stock <= 20 => 2,
            $stock <= 100 => 3,
            default => 4
        };
    }

    /**
     * @param int|Commodity|string $commodity
     * @param string|null $race
     * @param array|null $sku
     * @return string
     * @throws JSONException
     */
    public function getItemStock(int|Commodity|string $commodity, ?string $race = null, ?array $sku = []): string
    {
        if (is_int($commodity)) {
            $commodity = Commodity::with(['shared'])->find($commodity);
        } elseif (is_string($commodity)) {
            $commodity = Commodity::with(['shared'])->where("code", $commodity)->first();
        }

        if (!$commodity) throw new JSONException("商品不存在");

        if (($hook = \hook(Hook::SERVICE_SHOP_GET_ITEM_STOCK, $commodity, $race, $sku)) instanceof Stock) return $hook->getStock();

        //对接商品
        if ($commodity->shared) {
            return $this->getSharedStock($commodity, $race, $sku);
        } else if ($commodity->delivery_way == 0) {
            //库存
            $card = Card::query()->where("commodity_id", $commodity->id)->where("status", 0);
            if ($race) $card = $card->where("race", $race);
            if (!empty($sku)) {
                foreach ($sku as $k => $v) {
                    $card = $card->where("sku->{$k}", $v);
                }
            }
            return (string)$card->count();
        }
        return (string)$commodity->stock;
    }


    /**
     * @param int $id
     * @param string|null $race
     * @param array|null $sku
     * @return string
     */
    public function getSharedStockHash(int $id, ?string $race = null, ?array $sku = []): string
    {
        return md5($id . $race . json_encode($sku ?: []));
    }


    /**
     * @param int|Commodity $commodity
     * @param string|null $race
     * @param array|null $sku
     * @return void
     * @throws JSONException
     */
    public function updateSharedStock(int|Commodity $commodity, ?string $race = null, ?array $sku = []): void
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }
        if (!$commodity) throw new JSONException("商品不存在");
        $hash = $this->getSharedStockHash($commodity->id, $race, $sku);
        $stock = is_array($commodity->shared_stock) ? $commodity->shared_stock : [];
        unset($stock[$hash]);
        Commodity::query()->where("id", $commodity->id)->update(["shared_stock" => $stock]);
    }

    /**
     * @param int|Commodity $commodity
     * @param string|null $race
     * @param array|null $sku
     * @return string|null
     * @throws JSONException
     */
    public function getSharedStock(int|Commodity $commodity, ?string $race = null, ?array $sku = []): string|null
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }
        if (!$commodity) throw new JSONException("商品不存在");
        $hash = $this->getSharedStockHash($commodity->id, $race, $sku);

        if (!is_array($commodity->shared_stock) || !isset($commodity->shared_stock[$hash])) {
            try {
                $stock = $this->shared->getItemStock($commodity->shared, $commodity->shared_code, $race, $sku);
            } catch (\Throwable $e) {
                // fall back to local stock when the remote接口无法访问,避免前台直接报错
                @file_put_contents(BASE_PATH . "/runtime/shared_inventory.log", "[" . date('Y-m-d H:i:s') . "] stock fetch failed, commodity {$commodity->id}: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
                $fallback = $commodity->stock ?? 0;
                return (string)$fallback;
            }
            $array = is_array($commodity->shared_stock) ? $commodity->shared_stock : [];
            $array[$hash] = $stock;
            Commodity::query()->where("id", $commodity->id)->update(["shared_stock" => $array]);
            return $stock;
        }

        return $commodity->shared_stock[$hash];
    }


    /**
     * @param Commodity|int|string $commodity
     * @param int $cardId
     * @return array
     * @throws JSONException
     */
    public function getDraft(Commodity|int|string $commodity, int $cardId): array
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }
        if (!$commodity) throw new JSONException("商品不存在");


        $card = Card::query()->where("commodity_id", $commodity->id)->where("id", $cardId)->first();
        if (!$card) {
            throw new JSONException("预选的宝贝不存在");
        }

        if ($commodity->id != $card->commodity_id) {
            throw new JSONException("此预告信息不属于此商品");
        }

        if ($card->status != 0) {
            throw new JSONException("此宝贝已被他人抢走");
        }

        return ["draft_premium" => $card->draft_premium];
    }


    /**
     * @param Commodity $commodity
     * @return void
     */
    public function substationPriceIncrease(Commodity &$commodity): void
    {
        $business = Business::get();

        if (!$business) {
            return;
        }

        /**
         * @var UserCommodity $userCommodity
         */
        $userCommodity = UserCommodity::query()->where("user_id", $business->user_id)->where("commodity_id", $commodity->id)->first();

        if (!$userCommodity) {
            return;
        }

        if ($userCommodity->name) {
            $commodity->name = $userCommodity->name;
        }

        $config = $commodity->config ?: [];

        if ($userCommodity->premium > 0) {

            $commodity->price = (new Decimal($commodity->price))->mul($userCommodity->premium / 100)->add($commodity->price)->getAmount();
            $commodity->user_price = (new Decimal($commodity->user_price))->mul($userCommodity->premium / 100)->add($commodity->user_price)->getAmount();

            if ($commodity->draft_premium > 0) {
                $commodity->draft_premium = (new Decimal($commodity->draft_premium))->mul($userCommodity->premium / 100)->add($commodity->draft_premium)->getAmount();
            }

            if (is_array($config['category'])) {
                foreach ($config['category'] as &$price) {
                    $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                }
            }


            if (is_array($config['wholesale'])) {
                foreach ($config['wholesale'] as &$price) {
                    $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                }
            }

            if (is_array($config['category_wholesale'])) {
                foreach ($config['category_wholesale'] as &$arr) {
                    foreach ($arr as &$price) {
                        $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                    }
                }
            }

            if (is_array($config['sku'])) {
                foreach ($config['sku'] as &$arr) {
                    foreach ($arr as &$price) {
                        $price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
                    }
                }
            }
        }

        $commodity->config = $config;
    }

    /**
     * @param Commodity|int $commodity
     * @param int|string|float $amount
     * @return string
     * @throws JSONException
     */
    public function getSubstationPrice(Commodity|int $commodity, int|string|float $amount): string
    {
        if (is_int($commodity)) {
            $commodity = Commodity::query()->find($commodity);
        }

        if (!$commodity) {
            throw new JSONException("商品不存在");
        }

        $business = Business::get();

        if (!$business) {
            return (string)$amount;
        }

        /**
         * @var UserCommodity $userCommodity
         */
        $userCommodity = UserCommodity::query()->where("user_id", $business->user_id)->where("commodity_id", $commodity->id)->first();

        if (!$userCommodity) {
            return (string)$amount;
        }

        if ($userCommodity->premium > 0) {
            return (new Decimal($amount))->mul($userCommodity->premium / 100)->add($amount)->getAmount();
        }

        return (string)$amount;
    }

}
 
修复商品页下单右上角提示连接失败问题 app/Service/Bind/Shared.php
代码:
<?php
declare(strict_types=1);

namespace App\Service\Bind;


use App\Model\Commodity;
use App\Util\Http;
use App\Util\Ini;
use App\Util\Str;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Kernel\Annotation\Inject;
use Kernel\Container\Di;
use Kernel\Exception\JSONException;
use Kernel\Util\Decimal;

class Shared implements \App\Service\Shared
{

    #[Inject]
    private Client $http;

    /**
     * @param string $url
     * @param string $appId
     * @param string $appKey
     * @param array $data
     * @return array
     * @throws JSONException
     */
    public function mcyRequest(string $url, string $appId, string $appKey, array $data = []): array
    {
        try {
            $response = Http::make()->post($url, [
                "headers" => [
                    "Api-Id" => $appId,
                    "Api-Signature" => Str::generateSignature($data, $appKey)
                ],
                "form_params" => $data,
                "timeout" => 30
            ]);

            $contents = json_decode($response->getBody()->getContents() ?: "", true) ?: [];

            if (!isset($contents['code'])) {
                throw new JSONException("连接失败#1");
            }

            if ($contents['code'] != 200) {
                throw new JSONException(strip_tags($contents['msg']) ?? "连接失败#2");
            }

            return $contents['data'] ?? [];
        } catch (\Throwable $e) {
            throw new JSONException("连接失败#0");
        }
    }


    /**
     * @param string $url
     * @param string $appId
     * @param string $appKey
     * @param array $data
     * @return array
     * @throws GuzzleException
     * @throws JSONException
     */
    private function post(string $url, string $appId, string $appKey, array $data = []): array
    {
        $data = array_merge($data, ["app_id" => $appId, "app_key" => $appKey]);
        $data['sign'] = Str::generateSignature($data, $appKey);
        try {
            $response = Http::make()->post($url, [
                'form_params' => $data,
                'timeout' => 30
            ]);
        } catch (\Exception $e) {
            throw new JSONException("连接失败");
        }
        $contents = $response->getBody()->getContents();

        $result = json_decode($contents, true);
        if ($result['code'] != 200) {
            throw new JSONException(strip_tags((string)$result['msg']) ?: "连接失败");
        }
        return (array)$result['data'];
    }

    /**
     * @param string $domain
     * @param string $appId
     * @param string $appKey
     * @param int $type
     * @return array|null
     * @throws GuzzleException
     * @throws JSONException
     */
    public function connect(string $domain, string $appId, string $appKey, int $type = 0): ?array
    {
        if ($type == 1) {
            $data = $this->mcyRequest($domain . "/plugin/open-api/connect", $appId, $appKey);
            return ["shopName" => $data['username'], "balance" => $data['balance']];
        }
        return $this->post($domain . "/shared/authentication/connect", $appId, $appKey);
    }

    /**
     * @param array $item
     * @return array
     */
    private function createV4Item(array $item): array
    {
        $arr = [
            'id' => $item['id'],
            'name' => $item['name'],
            'description' => $item['introduce'],
            'price' => $item['sku'][0]['stock_price'],
            'user_price' => $item['sku'][0]['stock_price'],
            'cover' => $item['picture_url'],
            'factory_price' => $item['sku'][0]['stock_price'],
            'delivery_way' => 0,
            'contact_type' => 0,
            'password_status' => 0,
            'sort' => 0,
            'code' => $item['id'],
            'seckill_status' => 0,
            'draft_status' => 0,
            'inventory_hidden' => 0,
            'only_user' => 0,
            'purchase_count' => 0,
            'minimum' => 0, //最低购买,
            'maximum' => 0
        ];

        $widget = json_decode($item['widget'] ?: "", true) ?: [];

        $wid = [];

        if (!empty($widget)) {
            foreach ($widget as $w) {
                $wid[] = [
                    'cn' => $w['title'],
                    'name' => $w['name'],
                    'placeholder' => $w['placeholder'],
                    'type' => $w['type'],
                    'regex' => $w['regex'],
                    'error' => $w['error'],
                    'dict' => str_replace(PHP_EOL, ',', $w['data'] ?? "")
                ];
            }
        }

        $arr['widget'] = json_encode($wid);

        $config = [];
        $arr['stock'] = 0;

        foreach ($item['sku'] as $sku) {
            $config['category'][$sku['name']] = $sku['stock_price'];
            $config['shared_mapping'][$sku['name']] = $sku['id'];
            if (is_numeric($sku['stock'])) {
                $arr['stock'] += $sku['stock'];
            }
        }
        $arr['stock'] == 0 && $arr['stock'] = 10000000;
        $arr['config'] = Ini::toConfig($config);

        return $arr;
    }

    /**
     * @param \App\Model\Shared $shared
     * @return array|null
     * @throws GuzzleException
     * @throws JSONException
     */
    public function items(\App\Model\Shared $shared): ?array
    {
        if ($shared->type == 1) {
            $data = $this->mcyRequest($shared->domain . "/plugin/open-api/items", $shared->app_id, $shared->app_key);

            $category = [];

            foreach ($data as $item) {
                $cateName = $item['category']['name'];
                if (!isset($category[$cateName])) {
                    $category[$cateName] = [
                        "name" => $cateName,
                        "id" => 0
                    ];
                }
                $category[$cateName]['children'][] = $this->createV4Item($item);
            }

            return array_values($category);
        } elseif ($shared->type == 2) {
            $a = $this->post($shared->domain . "/shared/commodity/items", $shared->app_id, $shared->app_key);

            foreach ($a as &$a1) {
                if (is_array($a1['children'])) {
                    foreach ($a1['children'] as &$a2) {
                        $a2['stock'] = '10000000';
                    }
                }
            }

            return $a;
        }


        return $this->post($shared->domain . "/shared/commodity/items", $shared->app_id, $shared->app_key);
    }

    /**
     * @param \App\Model\Shared $shared
     * @param string $code
     * @return array
     * @throws GuzzleException
     * @throws JSONException
     */
    public function item(\App\Model\Shared $shared, string $code): array
    {
        if ($shared->type == 1) {
            $data = $this->mcyRequest($shared->domain . "/plugin/open-api/item", $shared->app_id, $shared->app_key, [
                "id" => $code
            ]);
            $a = $this->createV4Item($data);

            if (!is_array($a['config'])) {
                $a['config'] = Ini::toArray((string)$a['config']);
            }

            return $a;
        } elseif ($shared->type == 2) {
            $a = $this->post($shared->domain . "/shared/commodity/item", $shared->app_id, $shared->app_key, [
                "sharedCode" => $code
            ]);
            if (!isset($a[0]['children'][0])) {
                throw new JSONException("商品不存在#{$code}");
            }

            $b = $a[0]['children'][0];

            if (!is_array($b['config'])) {
                $b['config'] = Ini::toArray((string)$b['config']);
            }

            $b['stock'] = '10000000';

            return $b;
        }
        return $this->post($shared->domain . "/shared/commodity/item", $shared->app_id, $shared->app_key, [
            "code" => $code
        ]);
    }


    /**
     * @param \App\Model\Shared $shared
     * @param Commodity $commodity
     * @param int $cardId
     * @param int $num
     * @param string $race
     * @return bool
     * @throws GuzzleException
     * @throws JSONException
     */
    public function inventoryState(\App\Model\Shared $shared, Commodity $commodity, int $cardId, int $num, string $race): bool
    {

        if ($shared->type == 1) {
            $config = Ini::toArray($commodity->config);
            $data = $this->mcyRequest($shared->domain . "/plugin/open-api/sku/state", $shared->app_id, $shared->app_key, [
                'sku_id' => (int)$config['shared_mapping'][$race],
                'quantity' => $num
            ]);
            return (bool)$data['state'];
        }

        $this->post($shared->domain . "/shared/commodity/inventoryState", $shared->app_id, $shared->app_key, [
            "shared_code" => $commodity->shared_code,
            "card_id" => $cardId,
            "num" => $num,
            "race" => $race
        ]);

        return true;
    }

    /**
     * @param \App\Model\Shared $shared
     * @param Commodity $commodity
     * @param string $contact
     * @param int $num
     * @param int $cardId
     * @param int $device
     * @param string $password
     * @param string $race
     * @param array|null $sku
     * @param string|null $widget
     * @param string $requestNo
     * @return string
     * @throws GuzzleException
     * @throws JSONException
     * @throws \ReflectionException
     */
    public function trade(\App\Model\Shared $shared, Commodity $commodity, string $contact, int $num, int $cardId, int $device, string $password, string $race, ?array $sku, ?string $widget, string $requestNo): string
    {
        $wg = (array)json_decode((string)$widget, true);


        if ($shared->type == 1) {
            $config = Ini::toArray($commodity->config);

            $post = [
                'sku_id' => (int)$config['shared_mapping'][$race],
                'quantity' => $num,
                'trade_no' => substr(md5($requestNo), 0, 24)
            ];

            foreach ($wg as $key => $item) {
                $post[$key] = $item['value'];
            }

            $data = $this->mcyRequest($shared->domain . "/plugin/open-api/trade", $shared->app_id, $shared->app_key, $post);
            return $data['contents'] ?? "此商品没有发货信息或正在发货中";
        }

        $post = [
            "shared_code" => $commodity->shared_code,
            "contact" => $contact,
            "num" => $num,
            "card_id" => $cardId,
            "device" => $device,
            "password" => $password,
            "race" => $race,
            "request_no" => $requestNo,
            "sku" => $sku ?: []
        ];

        foreach ($wg as $key => $item) {
            $post[$key] = $item['value'];
        }

        $trade = $this->post($shared->domain . "/shared/commodity/trade", $shared->app_id, $shared->app_key, $post);

        /**
         * 更新缓存库存
         * @var \App\Service\Shop $shop
         */
        $shop = Di::inst()->make(\App\Service\Shop::class);
        $shop->updateSharedStock($commodity->id, $race, $sku);

        return (string)$trade['secret'];
    }

    /**
     * @param \App\Model\Shared $shared
     * @param string $code
     * @param array $map
     * @return array
     * @throws GuzzleException
     * @throws JSONException
     */
    public function draftCard(\App\Model\Shared $shared, string $code, array $map = []): array
    {
        $card = $this->post($shared->domain . "/shared/commodity/draftCard", $shared->app_id, $shared->app_key, array_merge([
            "code" => $code
        ], $map));
        return (array)$card;
    }


    /**
     * @param \App\Model\Shared $shared
     * @param string $code
     * @param int $cardId
     * @return array
     * @throws GuzzleException
     * @throws JSONException
     */
    public function getDraft(\App\Model\Shared $shared, string $code, int $cardId): array
    {
        return $this->post($shared->domain . "/shared/commodity/draft", $shared->app_id, $shared->app_key, [
            "code" => $code,
            "card_id" => $cardId
        ]);
    }

    /**
     * @param \App\Model\Shared $shared
     * @param Commodity $commodity
     * @param string $race
     * @return array
     * @throws GuzzleException
     * @throws JSONException
     */
    public function inventory(\App\Model\Shared $shared, Commodity $commodity, string $race = ""): array
    {
        if ($shared->type == 1) {
            $config = Ini::toArray($commodity->config);

            $item = $this->mcyRequest($shared->domain . "/plugin/open-api/item", $shared->app_id, $shared->app_key, [
                'id' => (int)$commodity->shared_code
            ]);

            $v4Item = $this->createV4Item($item);

            $result = [
                'delivery_way' => 0,
                'draft_status' => 0,
                'price' => $v4Item['price'],
                'user_price' => $v4Item['user_price'],
                'config' => $v4Item['config'],
                'factory_price' => $v4Item['factory_price'],
                'is_category' => true,
                'count' => 0
            ];

            if (empty($race)) {
                foreach ($config['shared_mapping'] as $skuId) {
                    $data = $this->mcyRequest($shared->domain . "/plugin/open-api/sku/stock", $shared->app_id, $shared->app_key, [
                        'sku_id' => (int)$skuId,
                    ]);
                    $result['count'] += (int)$data['stock'];
                }
            } else {
                $data = $this->mcyRequest($shared->domain . "/plugin/open-api/sku/stock", $shared->app_id, $shared->app_key, [
                    'sku_id' => (int)$config['shared_mapping'][$race],
                ]);
                $result['count'] = (int)$data['stock'];
            }

            return $result;
        }

        $inventory = $this->post($shared->domain . "/shared/commodity/inventory", $shared->app_id, $shared->app_key, [
            "sharedCode" => $commodity->shared_code,
            "race" => $race
        ]);

        return (array)$inventory;
    }

    /**
     * @param \App\Model\Shared $shared
     * @param string $code
     * @param string|null $race
     * @param array|null $sku
     * @return string
     * @throws GuzzleException
     * @throws JSONException
     */
    public function getItemStock(\App\Model\Shared $shared, string $code, ?string $race = null, ?array $sku = []): string
    {
        if ($shared->type == 1) {
            return "10000000";
        } elseif ($shared->type == 2) {
            return "10000000";
        }
       
        $payload = [
            "code" => $code,
            "race" => $race,
            "sku" => $sku
        ];

        try {
            $stock = $this->post($shared->domain . "/shared/commodity/stock", $shared->app_id, $shared->app_key, $payload);
            return $stock['stock'] ?? "0";
        } catch (\Throwable $e) {
            // 兼容旧版共享店铺(无 /shared/commodity/stock 接口),退回 inventory 接口获取库存
            try {
                $inventory = $this->post($shared->domain . "/shared/commodity/inventory", $shared->app_id, $shared->app_key, [
                    "sharedCode" => $code,
                    "race" => $race
                ]);
                return (string)($inventory['count'] ?? "0");
            } catch (\Throwable $ex) {
                throw $e;
            }
        }
    }


    /**
     * @param string $config
     * @param string $price
     * @param string $userPrice
     * @param int $type
     * @param float $premium
     * @return array
     * @throws JSONException
     */
    public function AdjustmentPrice(string $config, string $price, string $userPrice, int $type, float $premium): array
    {
        $_config = Ini::toArray($config);
        //race
        if (array_key_exists("category", $_config) && is_array($_config['category'])) {
            foreach ($_config['category'] as &$_price) {
                $_tmp = new Decimal($_price, 2);
                $_price = $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($_price)->getAmount())->getAmount();
            }
        }
        //sku
        if (array_key_exists("sku", $_config) && is_array($_config['sku'])) {
            foreach ($_config['sku'] as &$sku) {
                foreach ($sku as &$_price) {
                    if ($_price > 0) {
                        $_tmp = new Decimal($_price, 2);
                        $_price = $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($_price)->getAmount())->getAmount();
                    }
                }
            }
        }

        //wholesale
        if (array_key_exists("wholesale", $_config) && is_array($_config['wholesale'])) {
            foreach ($_config['wholesale'] as &$_price) {
                $_tmp = new Decimal($_price, 2);
                $_price = $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($_price)->getAmount())->getAmount();
            }
        }

        //category_wholesale
        if (array_key_exists("category_wholesale", $_config) && is_array($_config['category_wholesale'])) {
            foreach ($_config['category_wholesale'] as &$categoryWholesale) {
                foreach ($categoryWholesale as &$_price) {
                    $_tmp = new Decimal($_price, 2);
                    $_price = $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($_price)->getAmount())->getAmount();
                }
            }
        }

        $_tmp = new Decimal($price, 2);
        $price = $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($price)->getAmount())->getAmount();


        $_tmp = new Decimal($userPrice, 2);
        $userPrice = $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($userPrice)->getAmount())->getAmount();


        return ["config" => $_config, "price" => $price, "user_price" => $userPrice];
    }


    /**
     * @param int $type
     * @param float $premium
     * @param float|int|string $amount
     * @return string
     */
    public function AdjustmentAmount(int $type, float $premium, float|int|string $amount): string
    {
        $_tmp = new Decimal($amount, 2);
        return $type == 0 ? $_tmp->add($premium)->getAmount() : $_tmp->add((new Decimal($premium, 3))->mul($amount)->getAmount())->getAmount();
    }
}
 
最后编辑:
修复后台频繁掉线
/app/Service/Bind/ManageSSO.php
代码:
<?php
declare(strict_types=1);

namespace App\Service\Bind;


use App\Consts\Manage as ManageConst;
use App\Model\Manage;
use App\Model\ManageLog;
use App\Model\Config;
use App\Util\Client;
use App\Util\Date;
use App\Util\Str;
use Firebase\JWT\JWT;
use Kernel\Exception\JSONException;

/**
 * Class ManageSSOService
 * @package App\Service\Impl
 */
class ManageSSO implements \App\Service\ManageSSO
{

    /**
     * @param string $username
     * @param string $password
     * @param bool $remember
     * @return array
     * @throws JSONException
     */
    public function login(string $username, string $password, bool $remember = false): array
    {
        $manage = Manage::query()->where("email", $username)->first();
        if (!$manage) {
            throw new JSONException("该邮箱不存在");
        }
        if (Str::generatePassword($password, $manage->salt) != $manage->password) {
            throw new JSONException("密码错误");
        }

        if ($manage->status != 1) {
            throw new JSONException("账号已被暂停使用");
        }

        if ($manage->type == 2 && Date::isNight()) {
            throw new JSONException("您是白班哦,请注意休息。");
        }

        if ($manage->type == 3 && !Date::isNight()) {
            throw new JSONException("您是夜班哦,请注意休息。");
        }

        $manage->last_login_time = $manage->login_time;
        $manage->last_login_ip = $manage->login_ip;
        $manage->login_time = Date::current();
        $manage->login_ip = Client::getAddress();
        $manage->save();

        ManageLog::log($manage, "登录了后台");

        // 后台保持登录时间改为可配置(系统设置里的 session_expire,默认30天)
        $expire = Config::getSessionExpire();

        if ($remember) {
            $expire *= 365;
        }

        $payload = array(
            "expire" => time() + $expire,
            "loginTime" => $manage->login_time
        );

        $jwt = base64_encode(JWT::encode(
            payload: $payload,
            key: $manage->password,
            alg: 'HS256',
            head: ["mid" => $manage->id]
        ));

        setcookie(ManageConst::SESSION, $jwt, time() + $expire, "/");

        return ["username" => $manage->email, "avatar" => $manage->avatar, "token" => $jwt];
    }
}

app/Interceptor/ManageSession.php

代码:
<?php
declare(strict_types=1);

namespace App\Interceptor;


use App\Consts\Manage as ManageConst;
use App\Model\Manage;
use App\Util\Client;
use App\Util\Context;
use App\Util\Date;
use App\Util\JWT;
use Firebase\JWT\Key;
use JetBrains\PhpStorm\NoReturn;
use Kernel\Annotation\Interceptor;
use Kernel\Annotation\InterceptorInterface;
use Kernel\Consts\Base;
use Kernel\Exception\JSONException;
use Kernel\Util\View;

/**
 * Class ManageSession
 * @package App\Interceptor
 */
class ManageSession implements InterceptorInterface
{

    /**
     * @param int $type
     * @throws JSONException
     * @throws \SmartyException
     */
    #[NoReturn] public function handle(int $type): void
    {
        if ($type == Interceptor::TYPE_API) {
            list($p1, $p2) = [(array)parse_url((string)$_SERVER['HTTP_REFERER']), parse_url(Client::getUrl())];
            if ($p1['host'] != $p2['host']) {
                throw new JSONException("当前页面会话失效,请刷新网页..");
            }
        }

        if (!array_key_exists(ManageConst::SESSION, $_COOKIE)) {
            $this->kick($type);
        }


        $manageToken = base64_decode((string)$_COOKIE[ManageConst::SESSION]);


        if (empty($manageToken)) {
            $this->kick($type);
        }

        $head = JWT::getHead($manageToken);


        if (!isset($head['mid'])) {
            $this->kick($type);
        }

        $manage = Manage::query()->find($head['mid']);

        if (!$manage) {
            $this->kick($type);
        }

        try {
            $jwt = \Firebase\JWT\JWT::decode($manageToken, new Key($manage->password, 'HS256'));
        } catch (\Exception $e) {
            $this->kick($type);
        }

        if (
            $jwt->expire <= time() ||
            $manage->login_time != $jwt->loginTime ||
            $manage->status != 1
        ) {
            $this->kick($type);
        }

        if (!file_exists(BASE_PATH . "/config/terms")) {
            if (\Kernel\Util\Context::get(Base::ROUTE) == "/admin/dashboard/index" && $_GET['agree'] == 1) {
                file_put_contents(BASE_PATH . "/config/terms", "用户同意协议,时间:" . Date::current());
                header('content-type:application/json;charset=utf-8');
                exit(json_encode(["code" => 200, "msg" => "success"]));
            }
            exit(View::render("LegalTerms.html"));
        }

        //保存会话
        Context::set(ManageConst::SESSION, $manage);
    }


    #[NoReturn] private function kick(int $type): void
    {
        setcookie(ManageConst::SESSION, "", time() - 3600, "/");
        if ($type == Interceptor::TYPE_VIEW) {
            Client::redirect("/admin/authentication/login?goto=" . urlencode($_SERVER['REQUEST_URI']), "登录会话过期,请重新登录..");
        } else {
            header('content-type:application/json;charset=utf-8');
            exit(json_encode(["code" => 0, "msg" => "登录会话过期,请重新登录.."], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
    }
}
 
后退
顶部