From 645a037126f395336fd817f97a01473b1a2a4607 Mon Sep 17 00:00:00 2001 From: Dev Date: Wed, 14 Oct 2026 16:26:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E8=B4=A7=E5=8F=8A=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/application/admin/controller/Ajax.php | 33 +----- backend/application/api/controller/Order.php | 10 ++ .../application/common/library/OssUpload.php | 102 ++++++++++++++++++ .../common/service/OrderService.php | 24 ++++- .../service/SubscribeMessageService.php | 1 + .../pages/order/order-confirm/index.js | 36 ++++--- 6 files changed, 162 insertions(+), 44 deletions(-) create mode 100644 backend/application/common/library/OssUpload.php diff --git a/backend/application/admin/controller/Ajax.php b/backend/application/admin/controller/Ajax.php index 453b1bf..9544701 100644 --- a/backend/application/admin/controller/Ajax.php +++ b/backend/application/admin/controller/Ajax.php @@ -4,9 +4,8 @@ namespace app\admin\controller; use app\common\controller\Backend; use app\common\exception\UploadException; +use app\common\library\OssUpload; use app\common\library\Upload; -use app\common\service\OssService; -use fast\Random; use think\addons\Service; use think\Cache; use think\Config; @@ -143,7 +142,6 @@ class Ajax extends Backend //必须还原upload配置,否则分片及cdnurl函数计算错误 Config::load(APP_PATH . 'extra/upload.php', 'upload'); - $ossService = OssService::instance(); $chunkid = $this->request->post("chunkid"); if ($chunkid) { if (!Config::get('upload.chunking')) { @@ -158,15 +156,12 @@ class Ajax extends Backend $attachment = null; //合并分片文件 try { - $upload = new Upload(); + $upload = new OssUpload(); $attachment = $upload->merge($chunkid, $chunkcount, $filename); } catch (UploadException $e) { $this->error($e->getMessage()); } - // 上传到OSS - $objectUrl = 'uploads/' . date('Ymd/') . Random::alnum(16) . '/' . $attachment->filename; - $this->uploadToOss($attachment, $ossService, $objectUrl); - $this->success(__('Uploaded successful'), '', ['url' => cdnurl('/' . $objectUrl), 'fullurl' => cdnurl('/' . $objectUrl)]); + $this->success(__('Uploaded successful'), '', ['url' => cdnurl($attachment->url), 'fullurl' => cdnurl($attachment->url)]); } elseif ($method == 'clean') { //删除冗余的分片文件 try { @@ -193,15 +188,12 @@ class Ajax extends Backend //默认普通上传文件 $file = $this->request->file('file'); try { - $upload = new Upload($file); + $upload = new OssUpload($file); $attachment = $upload->upload(); } catch (UploadException $e) { $this->error($e->getMessage()); } - // 上传到OSS - $objectUrl = 'uploads/' . date('Ymd/') . Random::alnum(16) . '/' . $attachment->filename; - $this->uploadToOss($attachment, $ossService, $objectUrl); - $this->success(__('Uploaded successful'), '', ['url' => cdnurl('/' . $objectUrl), 'fullurl' => cdnurl('/' . $objectUrl)]); + $this->success(__('Uploaded successful'), '', ['url' => cdnurl($attachment->url), 'fullurl' => cdnurl($attachment->url)]); } } @@ -398,19 +390,4 @@ class Ajax extends Backend return $response; } - /** - * @param $attachment - * @param OssService $ossService - * @return string - * @throws - */ - protected function uploadToOss($attachment, OssService $ossService, $objectUrl): string - { - $filePath = ROOT_PATH . 'public' . str_replace('/', DS, $attachment->url); - $url = $ossService->uploadFile($filePath, $objectUrl); - @unlink($filePath); - - return $url; - } - } diff --git a/backend/application/api/controller/Order.php b/backend/application/api/controller/Order.php index b295465..3eb393a 100644 --- a/backend/application/api/controller/Order.php +++ b/backend/application/api/controller/Order.php @@ -176,7 +176,17 @@ class Order extends Api 'limitGoodsList' => null, 'abnormalDeliveryGoodsList' => null, 'invoiceSupport' => 1, + // 订单类型:未传 orderNo 为新建软件订单;带 orderNo 为已有订单(需求首付款/尾款或软件订单重新支付) + 'orderType' => OrderService::ORDER_TYPE_SOFTWARE, ]; + $orderNo = $this->request->post('orderNo', ''); + if ($orderNo) { + $order = OrderService::getOne(['order_no' => $orderNo, 'user_id' => $this->auth->id]); + if (!$order) { + $this->error('订单不存在'); + } + $resp['orderType'] = (int)$order->order_type; + } $this->success('', $resp); } catch (\Exception $e) { if ($e instanceof HttpResponseException) { diff --git a/backend/application/common/library/OssUpload.php b/backend/application/common/library/OssUpload.php new file mode 100644 index 0000000..aa08791 --- /dev/null +++ b/backend/application/common/library/OssUpload.php @@ -0,0 +1,102 @@ +file)) { + throw new UploadException(__('No file upload or server upload limit exceeded')); + } + + $this->checkSize(); + $this->checkExecutable(); + $this->checkMimetype(); + $this->checkImage(); + + $tempDir = RUNTIME_PATH . 'temp' . DS; + if (!is_dir($tempDir) && !@mkdir($tempDir, 0775, true) && !is_dir($tempDir)) { + throw new UploadException('上传临时目录创建失败或无写权限:' . $tempDir); + } + $tempName = Random::alnum(16) . '.' . $this->fileInfo['suffix']; + $tempFile = $tempDir . $tempName; + + $sha1 = $this->file->hash(); + + if ($this->merging) { + if (!$this->file->check()) { + throw new UploadException($this->file->getError()); + } + $sourceFile = $this->file->getRealPath() ?: $this->file->getPathname(); + $info = $this->file->getInfo(); + $this->file = null; + if (!@rename($sourceFile, $tempFile)) { + throw new UploadException('分片文件合并失败'); + } + $file = new File($tempFile); + $file->setSaveName($tempName)->setUploadInfo($info); + } else { + $file = $this->file->move($tempDir, $tempName); + if (!$file) { + throw new UploadException($this->file->getError()); + } + } + $this->file = $file; + + $filename = mb_substr(htmlspecialchars(strip_tags($this->fileInfo['name'])), 0, 100); + $object = 'uploads/' . date('Ymd/') . Random::alnum(16) . '/' . $filename; + try { + OssService::instance()->uploadFile($tempFile, $object); + } catch (\Exception $e) { + throw new UploadException($e->getMessage()); + } finally { + @unlink($tempFile); + } + + $category = request()->post('category'); + $category = array_key_exists($category, config('site.attachmentcategory') ?? []) ? $category : ''; + $auth = Auth::instance(); + $params = [ + 'admin_id' => (int)session('admin.id'), + 'user_id' => (int)$auth->id, + 'filename' => $filename, + 'category' => $category, + 'filesize' => $this->fileInfo['size'], + 'imagewidth' => $this->fileInfo['imagewidth'], + 'imageheight' => $this->fileInfo['imageheight'], + 'imagetype' => $this->fileInfo['suffix'], + 'imageframes' => 0, + 'mimetype' => $this->fileInfo['type'], + 'url' => '/' . $object, + 'uploadtime' => time(), + 'storage' => 'oss', + 'sha1' => $sha1, + 'extparam' => '', + ]; + $attachment = new Attachment(); + $attachment->data(array_filter($params)); + $attachment->save(); + + Hook::listen('upload_after', $attachment); + return $attachment; + } +} diff --git a/backend/application/common/service/OrderService.php b/backend/application/common/service/OrderService.php index 5bdeb23..d8211e7 100644 --- a/backend/application/common/service/OrderService.php +++ b/backend/application/common/service/OrderService.php @@ -658,8 +658,12 @@ class OrderService */ public static function orderShip($id) { - Db::transaction(function () use ($id) { - $order = OrderService::getOne(['id' => $id], ['orderGoods']); + $order = Db::transaction(function () use ($id) { + //加锁读取,防止并发重复发货、重复分配激活码 + $order = OrderService::where(['id' => $id])->lock(true)->find(); + if (!$order) { + exception('订单不存在'); + } if ($order->order_status != OrderService::ORDER_STATUS_WAIT_SHIP) { exception('订单状态异常,不能发货'); } @@ -673,8 +677,15 @@ class OrderService if (!$saveRes) { exception('更新发货状态失败'); } - //发送订单发货通知 + + return $order; + }); + + //发送订单发货通知(事务提交后发送,失败只记日志,不影响发货结果) + try { $msgData = [ + 'page' => SubscribeMessageService::PAGE_ORDER_DETAIL, + 'param' => ['orderNo' => $order->order_no], 'values' => [ $order->order_no, mb_substr($order->orderGoods[0]->goods_name, 0, 20), @@ -683,7 +694,12 @@ class OrderService ] ]; (new EasyWeChatService())->sendMsg($order->user_id, SubscribeMessageService::MSG_TEMP_ORDER_SHIPPED, $msgData); - }); + } catch (\Exception $e) { + Log::error(__CLASS__ . ' 订单发货通知发送失败:' . json_encode([ + 'order_id' => $id, + 'error' => $e->getMessage(), + ], JSON_UNESCAPED_UNICODE)); + } } } \ No newline at end of file diff --git a/backend/application/common/service/SubscribeMessageService.php b/backend/application/common/service/SubscribeMessageService.php index f631e36..5794313 100644 --- a/backend/application/common/service/SubscribeMessageService.php +++ b/backend/application/common/service/SubscribeMessageService.php @@ -20,6 +20,7 @@ class SubscribeMessageService const PAGE_DEMAND_LIST = 'pages/demand/list/index'; const PAGE_DEMAND_DETAIL = 'pages/demand/detail/index'; const PAGE_ORDER_LIST = 'pages/order/order-list/index'; + const PAGE_ORDER_DETAIL = 'pages/order/order-detail/index'; const PAGE_HOME = 'pages/home/home'; const MSG_MAP = [ diff --git a/frontend/miniapp/pages/order/order-confirm/index.js b/frontend/miniapp/pages/order/order-confirm/index.js index 448d8ab..7d0d924 100644 --- a/frontend/miniapp/pages/order/order-confirm/index.js +++ b/frontend/miniapp/pages/order/order-confirm/index.js @@ -4,6 +4,10 @@ import { commitPay, wechatPayOrder } from './pay'; import { getAddressPromise } from '../../usercenter/address/list/util'; const stripeImg = `https://cdn-we-retail.ym.tencent.com/miniapp/order/stripe.png`; +// 与后端 OrderService::ORDER_TYPE_SOFTWARE 一致 +const ORDER_TYPE_SOFTWARE = 0; +// 订单发货通知模板,与后端 SubscribeMessageService::MSG_TEMP_ORDER_SHIPPED 一致 +const MSG_TEMP_ORDER_SHIPPED = 'jGxNQ11on1yD0d5tGwk4S8jP_CEdddfAkrp9-xVeeZY'; Page({ data: { @@ -73,8 +77,8 @@ Page({ }, // 处理不同情况下跳转到结算页时需要的参数 handleOptionsParams(options, couponList) { - console.log(options); - let orderNo = options?.orderNo; + //选优惠券、改数量等重新结算时不会再传 orderNo,沿用首次进入时的订单号 + const orderNo = options?.orderNo || this.data.orderNo; this.setData({ orderNo: orderNo }); @@ -112,6 +116,7 @@ Page({ storeInfoList, userAddressReq, couponList, + orderNo, }; fetchSettleDetail(params).then( (res) => { @@ -360,6 +365,23 @@ Page({ }, // 提交订单 submitOrder() { + const { settleDetailData } = this.data; + if (this.payLock || !settleDetailData.settleType || !settleDetailData.totalAmount) { + return; + } + // 软件订单需授权「订单发货」通知;requestSubscribeMessage 必须在点击事件中同步调用,不能放到接口回调里 + if (settleDetailData.orderType === ORDER_TYPE_SOFTWARE) { + wx.requestSubscribeMessage({ + tmplIds: [MSG_TEMP_ORDER_SHIPPED], + complete: () => { + this.doSubmitOrder(); + }, + }); + return; + } + this.doSubmitOrder(); + }, + doSubmitOrder() { const { settleDetailData, // userAddressReq, @@ -429,16 +451,6 @@ Page({ }); return; } - //订阅发货通知 - if (data.order_type == 0) { - wx.requestSubscribeMessage({ - tmplIds: ['jGxNQ11on1yD0d5tGwk4S8jP_CEdddfAkrp9-xVeeZY'], - complete: (re) => { - this.handlePay(data, settleDetailData); - } - }); - return; - } this.handlePay(data, settleDetailData); } else { Toast({