diff --git a/admin/src/views/order/index.vue b/admin/src/views/order/index.vue index 4137d7cd..850d958e 100644 --- a/admin/src/views/order/index.vue +++ b/admin/src/views/order/index.vue @@ -182,9 +182,17 @@ > 同步 + + 确认接单 + { + this.$notify({ + title: "成功", + message: "接单成功", + type: "success", + }); + // this.getList(); + }) + .catch((err) => { + this.$notify.error({ + title: "接单失败", + message: err, + }); + }); + }, onOneClickYyHandle(item) { this.$axios .post("/admin/order/changeAppointmentStatus", { id: item.id }) diff --git a/admin/vue.config.js b/admin/vue.config.js index 2993d6e1..fd105045 100644 --- a/admin/vue.config.js +++ b/admin/vue.config.js @@ -40,8 +40,8 @@ module.exports = { "/dev-api": { // 接口地址 以 api开头的都走下面的配置 // target: 'https://www.szjinao.cn', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 - // target: "http://hex.jipinq.cn", // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 - target: "http://192.168.0.100:8787", + target: "http://hex.jipinq.cn", // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 + // target: "http://192.168.0.100:8787", ws: true, // 是否支持 websocket 请求 支持 changeOrigin: true, // 是否启用跨域 pathRewrite: { diff --git a/service/app/admin/controller/DataController.php b/service/app/admin/controller/DataController.php index 571bc100..98eeb6c6 100644 --- a/service/app/admin/controller/DataController.php +++ b/service/app/admin/controller/DataController.php @@ -119,16 +119,16 @@ class DataController extends base $totalArr[1]['month_write_num'] += $list[$k]['month_write_num']; } if (isset($totalArr[1]['orders']) && $totalArr[1]['orders'] > 0) { - $totalArr[1]['write_rate'] = $totalArr[1]['orders'] ?: (float)number_format(($totalArr[1]['assets']/$totalArr[1]['orders'])*100,2); + $totalArr[1]['write_rate'] = (float)number_format(($totalArr[1]['assets']/$totalArr[1]['orders'])*100,2); } if (isset($totalArr[1]['total_price']) && $totalArr[1]['total_price'] > 0) { - $totalArr[1]['write_rate_price'] = $totalArr[1]['total_price'] ?: (float)number_format(($totalArr[1]['asset_price']/$totalArr[1]['total_price'])*100,2); + $totalArr[1]['write_rate_price'] = (float)number_format(($totalArr[1]['asset_price']/$totalArr[1]['total_price'])*100,2); } if (isset($totalArr[1]['m_orders']) && $totalArr[1]['m_orders'] > 0) { - $totalArr[1]['month_write_rate'] = $totalArr[1]['m_orders'] ?: (float)number_format(($totalArr[1]['m_assets']/$totalArr[1]['m_orders'])*100,2); + $totalArr[1]['month_write_rate'] = (float)number_format(($totalArr[1]['m_assets']/$totalArr[1]['m_orders'])*100,2); } if (isset($totalArr[1]['m_total_price']) && $totalArr[1]['m_total_price'] > 0) { - $totalArr[1]['month_write_rate_price'] = $totalArr[1]['m_total_price'] ?: (float)number_format(($totalArr[1]['m_asset_price']/$totalArr[1]['m_total_price'])*100,2); + $totalArr[1]['month_write_rate_price'] = (float)number_format(($totalArr[1]['m_asset_price']/$totalArr[1]['m_total_price'])*100,2); } $list = array_merge($list->toArray(), $totalArr); diff --git a/service/app/admin/controller/LiveRoomController.php b/service/app/admin/controller/LiveRoomController.php index 2a8fd89b..bae42936 100644 --- a/service/app/admin/controller/LiveRoomController.php +++ b/service/app/admin/controller/LiveRoomController.php @@ -196,4 +196,4 @@ class LiveRoomController extends base { $data = LiveRoomWorks::staticOrder(3, 38, '2024-09-17 16:00:00', '2024-09-18 15:59:59'); return $this->success($data); } -} \ No newline at end of file +} diff --git a/service/app/admin/controller/OrderController.php b/service/app/admin/controller/OrderController.php index db2076ff..5765abeb 100644 --- a/service/app/admin/controller/OrderController.php +++ b/service/app/admin/controller/OrderController.php @@ -4,12 +4,12 @@ require_once(__DIR__.'/xlsxwriter.class.php'); use app\model\Admins; use app\model\Backs; -use app\model\Blacks; +use app\model\DyOrderProductAppointments; +use app\model\DyOrderProducts; use app\model\Follows; use app\model\Orders; use app\model\Logs; -use app\model\ThirdMobileLogs; -use app\server\SMS; +use app\server\DyApiService; use app\server\ThirdApiService; use app\server\Orders as ServerOrders; use stdClass; @@ -678,4 +678,119 @@ class OrderController extends base return $this->error(2006, '出错了:' . $e->getMessage()); } } + + /** + * 获取预约信息 + * @param Request $request + * @return \support\Response + */ + public function getAppointInfo(Request $request) { + try { + $id = $request->get('id', 0); + $order = Orders::where('id', $id)->find(); + if (empty($order)) { + return $this->error(2004, '记录没有找到.'); + } + + // 抖音预约信息 + $appoint = DyOrderProductAppointments::query()->where(['dy_order_id' => $order['sn']])->find(); + return $this->success($appoint); + }catch(\Exception $e) { + return $this->error(2006, '出错了:' . $e->getMessage()); + } + } + + /** + * 抖音确认订单 + * @param Request $request + * @return \support\Response + */ + public function dyOrderConfirm(Request $request) { + try { + $id = $request->post('id', 0); + $order = Orders::where('id', $id)->find(); + if (empty($order)) { + return $this->error(2004, '记录没有找到.'); + } + // 1:接单 2:拒单 +// if (!$request->post('confirm_result') || !in_array($request->post('confirm_result'), [1, 2])) { +// return $this->error(2005, '确认状态错误'); +// } + // 抖音预约信息 + $appoint = DyOrderProductAppointments::query()->where(['source_order_id' => $order['sn']])->find(); + if (empty($appoint)) { + return $this->error(2004, '该订单暂未预约,不可接单'); + } + $dyOrderProduct = DyOrderProducts::query()->where(['dy_order_id' => $order['sn']])->find(); + if (empty($dyOrderProduct)) { + return $this->error(2004, '订单商品未找到,不可接单'); + } + + $jnCategoryIds = [ + "18004001", "18004003", "18004004", "18004005", "18004006", "18004007", "18004008", "18004009", + "18004010", "18003004", "4015004", "18003001", "18003002", "18003003", "18010001", "32001001", + "18002001", "18011001", "18011002", "18012001", "18012002", "18010001", "32001001", "32006002", + "32007001", "32007002", "32007003", + ]; + $zyxCategoryIds = ['32002001', '18010002', '32006001']; + + $confirmData = [ + 'order_id' => $appoint->dy_order_id, + 'source_order_id' => $appoint->source_order_id, + 'confirm_info' => [ + 'confirm_result' => $request->post('confirm_result', 1), + 'reject_code' => $request->post('reject_code'), // 1: 库存已约满 2:商品需加价 3:无法满足顾客需求 +// 'hotel_info' => [], // 境内住宿类目/酒景套餐 必填 +// 'play_info' => [], // 境内游玩类目 必填 +// 'free_travel_info' => $freeTravelInfo, // 自由行类目 必填 + ], + ]; + // 自由行必填参数 + $dyApiService = new DyApiService(); + if (in_array($dyOrderProduct->category_id, $zyxCategoryIds)) { + $onedayTourList = []; + foreach ($dyOrderProduct->travel_details as $k => $travel_detail) { + array_push($onedayTourList, [ + 'sequence' => $k+1, + 'hotel_info_list' => [], + 'play_info_list' => [] + ]); + } + $confirmData['confirm_info']['free_travel_info']['oneday_tour_list'] = $onedayTourList; + } + if (in_array($dyOrderProduct->category_id, $jnCategoryIds)) { + // 获取poi信息 + $poiRes = $dyApiService->send(DyApiService::POI_QUERY, ['order_id' => $order->sn, 'account_id' => env('DY_ACCOUNT_ID')]); + $payInfo = [ + 'entrance_types' => [3], + 'poi_info' => $poiRes['data']['poi_list'] ?? [] + ]; + $showCerts = []; + if (isset($appoint['book_info'])) { + $bookInfo = json_decode(json_encode($appoint['book_info']), true); + foreach ($bookInfo['occupancies'] as $info) { + array_push($showCerts, [ + 'name' => $info['name'], + 'card_no' => $info['license_id'] ?? '', + 'cert_no' => $info['cert_no'] ?? '', + ]); + } + } + $payInfo['show_certs'] = $showCerts; + $confirmData['confirm_info']['play_info'] = $payInfo; + } + + $res = $dyApiService->send(DyApiService::ORDER_CONFIRM, $confirmData); + if ($res['flag'] == true) { + // 预约成功,更新预约状态 + $order->appointment_status = 2; + $order->save(); + + return $this->success($res); + } + return $this->error(2005, $res['data']['description']); + }catch(\Exception $e) { + return $this->error(2006, '出错了:' . $e->getMessage()); + } + } } diff --git a/service/app/api/controller/DyNotifyController.php b/service/app/api/controller/DyNotifyController.php new file mode 100644 index 00000000..c3d5bd78 --- /dev/null +++ b/service/app/api/controller/DyNotifyController.php @@ -0,0 +1,425 @@ +all(); + Log::info('orderCreate params:' . json_encode($params)); + if (!empty($order = DyOrders::query()->where(['dy_order_id' => $params['order_id']])->find())) { + return $this->success(['order_out_id' => $order->order_id, 'order_id' => $order->dy_order_id]); + } + + $orderId = getNewOrderId('Dy'); + $orderData = [ + 'order_id' => $orderId, + 'dy_order_id' => $params['order_id'], + 'biz_type' => $params['biz_type'] ?? '', + 'total_amount' => $params['total_amount'] ?? 0, + 'total_coupon_count' => $params['total_coupon_count'] ?? 0, + 'each_coupon_amount' => $params['each_coupon_amount'] ?? 0, + 'pay_amount' => $params['pay_amount'] ?? 0, + 'actual_amount' => $params['actual_amount'] ?? 0, + 'merchant_discount_amount' => $params['merchant_discount_amount'] ?? 0, + 'pay_time_unix' => $params['pay_info']['pay_time_unix'] ?? 0, + 'status' => 0, + 'phone' => $this->decrypt($params['buyer_info']['phone'], env('DY_APPSECRET')), + ]; + // 已支付 + if ($orderData['pay_time_unix']) { + $orderData['status'] = 10; + } + $res = DyOrders::create($orderData); + Log::info('res:' . json_encode($res)); + if (!empty($res) && isset($params['product_snap_shot']) && !empty($params['product_snap_shot'])) { + $orderProducts = []; + foreach ($params['product_snap_shot'] as $product) { + array_push($orderProducts, [ + 'dy_order_id' => $orderData['dy_order_id'], + 'sku_id' => $product['sku_id'] ?? '', + 'product_id' => $product['product_id'] ?? '', + 'out_product_id' => $product['out_product_id'] ?? '', + 'name' => $product['name'] ?? '', + 'category_id' => $product['category_id'] ?? '', + 'category_full_name' => $product['category_full_name'] ?? '', + 'travel_details' => json_encode($product['travel_details'] ?? []), + 'commodity' => json_encode($product['commodity'] ?? []), + 'is_superimposed_discounts' => $product['is_superimposed_discounts'] ? 1: 0, + 'date_add_price_rule' => isset($product['date_add_price_rule']) ? json_encode($product['date_add_price_rule']) : '', + 'user_add_price_rule' => isset($product['user_add_price_rule']) ? json_encode($product['user_add_price_rule']) : '', + 'room_add_price_rule' => isset($product['room_add_price_rule']) ? json_encode($product['room_add_price_rule']) : '', + 'room_upgrade_rule' => isset($product['room_upgrade_rule']) ? json_encode($product['room_upgrade_rule']) : '', + 'add_price_policy' => isset($product['add_price_policy']) ? json_encode($product['add_price_policy']) : '', + 'description_rich_text' => isset($product['description_rich_text']) ? json_encode($product['description_rich_text']) : '', + 'fee_include' => isset($product['fee_include']) ? json_encode($product['fee_include']) : '', + 'fee_not_include' => isset($product['fee_not_include']) ? json_encode($product['fee_not_include']) : '', + 'self_pay_expense_rule' => isset($product['self_pay_expense_rule']) ? json_encode($product['self_pay_expense_rule']) : '', + 'earliest_appointment' => isset($product['earliest_appointment']) ? json_encode($product['earliest_appointment']) : '', + 'appointment' => isset($product['appointment']) ? json_encode($product['appointment']) : '', + 'refund_rule' => isset($product['refund_rule']) ? json_encode($product['refund_rule']) : '', + 'use_date' => isset($product['use_date']) ? json_encode($product['use_date']) : '', + 'refund_description' => isset($product['refund_description']) ? json_encode($product['refund_description']) : '', + 'merchant_break_rule' => isset($product['merchant_break_rule']) ? json_encode($product['merchant_break_rule']) : '', + 'merchant_break_description' => isset($product['merchant_break_description']) ? json_encode($product['merchant_break_description']) : '', + 'presale_appointment_cancel_policy' => isset($product['presale_appointment_cancel_policy']) ? json_encode($product['presale_appointment_cancel_policy']) : '', + ]); + } + + Log::info('orderProducts:' . json_encode($orderProducts)); + // 批量插入 + /** @var DyOrderProducts $dyOrderProducts */ + $dyOrderProducts = (new DyOrderProducts()); + $dyOrderProducts->insertAll($orderProducts); + + if ($orderData['status'] == 10) { + $this->createPlatformOrder($orderData, $orderProducts[0]); + } + } + + // order_out_id - 第三方订单ID, order_id - 抖音订单号 + return $this->success(['order_out_id' => $orderId, 'order_id' => $params['order_id']]); + } catch (\Exception $exception) { + return $this->error('-1', $exception->getMessage()); + } + } + + // 通知支付结果 + public function pay(Request $request) { + try { + $params = $request->all(); +// Validator::input($params, [ +// 'order_id' => Validator::notEmpty()->setName('订单号'), +// 'biz_type' => Validator::notEmpty()->setName('旅行社预售券'), +// 'pay_amount' => Validator::notEmpty()->setName('支付金额'), +// 'pay_time_unix' => Validator::notEmpty()->setName('支付时间'), +// ]); + Log::info('pay params:' . json_encode($params)); + // » biz_type - 旅行社预售券:3011;旅行社预定订单:3012 + // » pay_amount 用户实付金额(分) + // » pay_time_unix 支付时间戳 + if (isset($params['biz_type']) && $params['biz_type'] == 3012) { + if (empty($order = DyOrderProductAppointments::query()->where(['dy_order_id' => $params['source_order_id']])->find())) { + return $this->error('-2', '订单信息未找到'); + } + $res = DyOrderProductAppointments::query()->where(['order_id' => $params['order_id']])->update(['pay_amount' => $params['pay_amount'] ?? 0]); + } else { + if (empty($order = DyOrders::query()->where(['dy_order_id' => $params['source_order_id']])->find())) { + return $this->error('-2', '订单信息未找到'); + } + $res = DyOrders::query()->where(['dy_order_id' => $params['order_id']])->update(['status' => 10, 'pay_amount' => $params['pay_amount'], 'pay_time_unix' => $params['pay_time_unix']]); + $orderProduct = DyOrderProducts::query()->where(['dy_order_id' => $params['order_id']])->find(); + if (!$res) { + $this->createPlatformOrder($order, $orderProduct); + } + } + if ($res) { + return $this->success([]); + } else { + return $this->error('-2', '操作失败'); + } + } catch (\Exception $exception) { + return $this->error('-1', $exception->getMessage()); + } + } + + /** + * @param $order + * @param $orderProduct + * @return Orders + */ + private function createPlatformOrder($order, $orderProduct) { + $item = Orders::query()->where(['sn' => $order['dy_order_id']])->find(); + if (!empty($item)) { + return $item; + } + // 查找用户归属 + $product = Products::query()->where('third_product_id', $orderProduct['product_id'])->find(); + $adminId = 0; + if (!empty($product)) { + $adminId = Admins::where('status', 1)->whereFindInSet('product_ids', $product->id)->value('id'); + } + + // 订单支付成功,写入平台订单表 + $item = new Orders(); + $item->os = 5; + $item->sn = $order['dy_order_id']; + $item->product_id = $orderProduct['product_id']; + $item->product_name = $orderProduct['name']; + $item->category_id = $orderProduct['category_id']; + $item->create_at = $order['pay_time_unix'] * 1000; + $item->mobile = $order['phone'] ?? null; + $item->unit_price = $order['pay_amount']; + $item->total_price = $order['pay_amount']; + $item->actual_price = $order['pay_amount']; + $item->quantity = $order['total_coupon_count']; + $item->order_status = 1; + $item->asset_status = 0; + $item->category_desc = $orderProduct['category_full_name']; + $item->asset_price = 0; + $item->is_refunded = 0; // 是否已退款(1-已退款,0-未退款) + $item->admin_id = $adminId; + $item->is_direct_mode = 1; // 是否直连模式(0-否,1-是) + $item->save(); + return $item; + } + + /** + * 处理预约信息 + * @param $params + * @return array + */ + private function handleBookInfo($params) { + $bookInfo = []; + if (isset($params['book_info']) && !empty($params['book_info'])) { + $bookInfo = $params['book_info']; + if (isset($bookInfo['occupancies']) && is_array($bookInfo['occupancies'])) { + foreach ($bookInfo['occupancies'] as &$occupancy) { + if (isset($occupancy['name']) && !empty($occupancy['name'])) { + $occupancy['name'] = $this->decrypt($occupancy['name'], env('DY_APPSECRET')); + } + if (isset($occupancy['license_id']) && !empty($occupancy['license_id'])) { + $occupancy['license_id'] = $this->decrypt($occupancy['license_id'], env('DY_APPSECRET')); + } + } + } + } + return $bookInfo; + } + + // 用户创建预约订单 + public function userMakeAppointment(Request $request) { + $params = $request->all(); + Log::info('userMakeAppointment params:' . json_encode($params)); + $bookInfo = $this->handleBookInfo($params); + $orderData = [ + 'dy_order_id' => $params['order_id'] ?? '', + 'source_order_id' => $params['source_order_id'] ?? '', + 'presale_coupon_id' => $params['presale_coupon_id'] ?? '', + 'out_presale_coupon_id' => $params['out_presale_coupon_id'] ?? '', + 'biz_type' => $params['biz_type'] ?? 0, + 'pay_amount' => $params['pay_amount'] ?? 0, + 'actual_amount' => $params['actual_amount'] ?? 0, + 'merchant_discount_amount' => $params['merchant_discount_amount'] ?? 0, + 'discount_amount' => $params['discount_amount'] ?? 0, + 'original_amount' => $params['original_amount'] ?? 0, + 'pay_info' => isset($params['pay_info']) ? json_encode($params['pay_info']) : '', + 'book_info' => $bookInfo, + 'buyer_info' => isset($params['buyer_info']) ? json_encode($params['buyer_info']) : '', + 'remark_from_guest' => $params['remark_from_guest'], + ]; + if (empty($order = DyOrders::query()->where(['dy_order_id' => $orderData['source_order_id']])->find())) { + return $this->error('-2', '订单信息未找到'); + } + $orderData['appoint_order_id'] = getNewOrderId('Yy'); + if (!empty($appoint = DyOrderProductAppointments::query()->where(['dy_order_id' => $orderData['dy_order_id']])->find())) { + return $this->success(['order_id' => $appoint->dy_order_id, 'order_out_id' => $appoint->appoint_order_id]); + } + + // biz_type - 旅行社预售券:3011;旅行社预定订单:3012 + $res = DyOrderProductAppointments::create($orderData); + if ($res) { + // 更新平台订单表 + Orders::query()->where('sn', $params['source_order_id'])->update(['appointment_status' => 1, 'travel_date' => $bookInfo['book_start_date']]); + return $this->success(['order_id' => $orderData['dy_order_id'], 'order_out_id' => $orderData['appoint_order_id']]); + } else { + return $this->error('-1'); + } + } + + // 用户取消订单 + public function userCancelOrder(Request $request) { + $params = $request->all(); + Log::info('userCancelOrder params:' . json_encode($params)); + if (!isset($params['order_id']) || empty($params['order_id'])) { + throw new \Exception('订单号缺失'); + } + if (isset($params['biz_type']) && $params['biz_type'] == 3012) { + // 取消预约 + $appoint = DyOrderProductAppointments::query()->where('dy_order_id', $params['order_id'])->find(); + Orders::query()->where('sn', $appoint['source_order_id'])->update(['appointment_status' => 0, 'travel_date' => null]); + return $this->success([]); + } + if (empty($order = Orders::query()->where(['sn' => $params['order_id']])->find())) { + return $this->error('-2', '订单信息未找到'); + } + + // » refund_type 枚举值:1、订单退款,2、补差价退款 + $res = Db::transaction(function () use ($order, $params) { + DyOrders::query()->where('dy_order_id', $params['order_id'])->update(['status' => 30]); + // 更新平台订单表 + if ($order->order_status < 3) { + Orders::query()->where('sn', $params['order_id'])->update(['order_status' => 3]); + } + return true; + }); + + if ($res) { + return $this->success([]); + } + + return $this->error('-1', '取消失败'); + } + + // 订单退款结果通知 + public function orderRefundNotify(Request $request) { + $params = $request->all(); + Log::info('orderRefundNotify params:' . json_encode($params)); + if (!isset($params['order_id']) || empty($params['order_id'])) { + throw new \Exception('订单号缺失'); + } + + if (isset($params['biz_type']) && $params['biz_type'] == 3012) { + // 取消预约退款, 暂不处理 + return $this->success([]); + } + + if (empty($order = DyOrders::query()->where(['dy_order_id' => $params['order_id']])->find())) { + return $this->error('-2', '订单信息未找到'); + } + + $res = Db::transaction(function () use ($order, $params) { + DyOrders::query()->where(['order_id' => $params['order_id']])->update(['status' => 40]); + // 更新平台订单表 + Orders::query()->where(['sn' => $params['order_id']])->update(['order_status' => 4, 'is_refunded' => 1, 'refund_status' => 3]); + return true; + }); + + if ($res) { + return $this->success([]); + } + + return $this->error('-1', '取消失败'); + } + + public function success($data,$mess = null, $ext = null) { + $res = [ + 'error_code' => 0, + 'description' => 'success', + ]; + if (isset($data['order_id'])) { + $res['order_id'] = $data['order_id']; + } + if (isset($data['order_out_id'])) { + $res['order_out_id'] = $data['order_out_id']; + } + return json([ + 'data' => $res + ]); + } + + public function error($code, $mess = null) { + $res = [ + 'error_code' => $code, + 'description' => $mess, + ]; + return json([ + 'data' => $res + ]); + } + + /** + 1.根据ClientKev找到ClientSecret,将ClientSecret向左右使用字符补齐32位/裁剪至32位,补齐:补位字符:#,先补左侧再补右侧再补左侧………直到补满32位。 + 裁剪:先裁剪左侧再裁右侧再裁左侧………直到剩余32位。(正常不需要补齐,secret默认为32位,此举是为了 + 以防万一) + 2.将ClientSecret作为Key,右侧16位为向量IV + 3.将密文进行base64解码。 + 4.使用AES-256-CBC模式解密解码后的密文,对齐使用PKCS5Padding方式 + * @param $cipherText + * @param $clientSecret + * @return false|string + */ + private function decrypt($cipherText, $clientSecret) { + Log::info('$cipherText:' . $cipherText); + // 补齐或裁剪 ClientSecret 为 32 位 + $clientSecret = $this->padOrTruncate($clientSecret); + Log::info('$cipherText step 2:' . $clientSecret); + + // 使用 ClientSecret 作为密钥 + $key = $clientSecret; + + // 右侧16位为初始化向量IV + $iv = substr($key, 16, 16); + + // 将密文 base64 解码 + $cipherTextDecoded = base64_decode($cipherText); + + // 使用 AES-256-CBC 解密 + return openssl_decrypt($cipherTextDecoded, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); + } + + /** + * @param $clientSecret + * @return false|string + */ + private function padOrTruncate($clientSecret) { + // 1. 如果 clientSecret 长度为32,直接返回 + if (strlen($clientSecret) == 32) { + return $clientSecret; + } + + // 2. 如果 length < 32,补齐至32位 + if (strlen($clientSecret) < 32) { + // 补齐过程:先补左侧,再补右侧,再补左侧,直到长度为32 + $paddingChar = '#'; + $padded = $clientSecret; + + // 左侧补齐 + $left = true; + while (strlen($padded) < 32) { + if ($left) { + $padded = $paddingChar . $padded; + $left = false; + } else { + $padded = $padded . $paddingChar; + $left = true; + } + } + + return substr($padded, 0, 32); // 确保返回32位 + } + + // 3. 如果 length > 32,裁剪至32位 + if (strlen($clientSecret) > 32) { + // 裁剪过程:先裁左侧,再裁右侧,再裁左侧,直到长度为32 + $cropped = $clientSecret; + + // 左侧裁剪 + while (strlen($cropped) > 32) { + $cropped = substr($cropped, 1); + } + + // 右侧裁剪 + while (strlen($cropped) > 32) { + $cropped = substr($cropped, 0, -1); + } + + // 左侧裁剪(再一次) + while (strlen($cropped) > 32) { + $cropped = substr($cropped, 1); + } + + return substr($cropped, 0, 32); // 确保返回32位 + } + + return $clientSecret; + } +} diff --git a/service/app/command/SpiderDy.php b/service/app/command/SpiderDy.php index 7650d336..4f255e97 100644 --- a/service/app/command/SpiderDy.php +++ b/service/app/command/SpiderDy.php @@ -28,8 +28,8 @@ class SpiderDy extends Command { protected static $defaultName = 'spider:dy'; protected static $defaultDescription = '抖音订单拉取器'; - protected $_users = []; + protected $page = 3; protected function configure() { @@ -39,7 +39,9 @@ class SpiderDy extends Command new InputDefinition(array( new InputOption('sn', 'sn', InputOption::VALUE_REQUIRED), new InputOption('os', 'os', InputOption::VALUE_REQUIRED), - new InputOption('day', 'day', InputOption::VALUE_REQUIRED) + new InputOption('day', 'day', InputOption::VALUE_REQUIRED), + // 最大页数 + new InputOption('max_page', 'max_page', InputOption::VALUE_OPTIONAL) )) ); } @@ -144,6 +146,9 @@ class SpiderDy extends Command if (empty($d)) { $d = 40; } + if ($input->getOption('max_page')) { + $this->page = $input->getOption('max_page'); + } if ($orderid) { $this->reloadStatus($orderid, $os, $output); @@ -301,7 +306,7 @@ class SpiderDy extends Command { foreach (Orders::OSS as $k => $os) { - $pages = 3; + $pages = $this->page; $page = 1; while (true) { diff --git a/service/app/command/SpiderMt.php b/service/app/command/SpiderMt.php index 2b2d78ab..c73451db 100644 --- a/service/app/command/SpiderMt.php +++ b/service/app/command/SpiderMt.php @@ -30,6 +30,7 @@ class SpiderMt extends Command protected $_users = []; protected $order_num = 99999999; + protected $page = 20; protected function configure() { @@ -39,7 +40,9 @@ class SpiderMt extends Command new InputDefinition(array( new InputOption('sn', 'sn', InputOption::VALUE_REQUIRED), new InputOption('os', 'os', InputOption::VALUE_REQUIRED), - new InputOption('day', 'day', InputOption::VALUE_REQUIRED) + new InputOption('day', 'day', InputOption::VALUE_REQUIRED), + // 最大页数 + new InputOption('max_page', 'max_page', InputOption::VALUE_OPTIONAL) )) ); } @@ -159,6 +162,9 @@ class SpiderMt extends Command if (empty($d)) { $d = 90; } + if ($input->getOption('max_page')) { + $this->page = $input->getOption('max_page'); + } if ($orderid) { $this->reloadStatus($orderid, $os, $output); @@ -317,7 +323,7 @@ class SpiderMt extends Command { foreach (Orders::OSS as $k => $os) { - $pages = 20; + $pages = $this->page; $page = 1; while (true) { diff --git a/service/app/functions.php b/service/app/functions.php index 31bb8368..41be0040 100644 --- a/service/app/functions.php +++ b/service/app/functions.php @@ -36,4 +36,35 @@ function isUrlInDomainWithPath($urlToCheck,$baseUrl): bool return true; } return false; -} \ No newline at end of file +} + +function get_week_days() { + // 获取当前日期 + $currentDate = new DateTime(); + + // 获取本周的周一(使用ISO-8601标准:一周的开始是周一) + $currentDate->modify('this week monday'); + + // 存储周一到周日的日期 + $weekDays = []; + + // 循环获取本周的每一天 + for ($i = 0; $i < 7; $i++) { + // 获取当前日期并添加到数组 + $weekDays[] = [ + 'date' => $currentDate->format('Y-m-d') + ]; + + // 将日期调整到下一个日期(例如,周二 -> 周三) + $currentDate->modify('+1 day'); + } + + return $weekDays; +} + +function getNewOrderId($type) { + list($msec, $sec) = explode(' ', microtime()); + $msectime = number_format((floatval($msec) + floatval($sec)) * 1000, 0, '', ''); + $orderId = $type . $msectime . mt_rand(10000, max(intval($msec * 10000) + 10000, 98369)); + return $orderId; +} diff --git a/service/app/model/DyOrderProductAppointments.php b/service/app/model/DyOrderProductAppointments.php new file mode 100644 index 00000000..a6324183 --- /dev/null +++ b/service/app/model/DyOrderProductAppointments.php @@ -0,0 +1,16 @@ +host = env('DY_API_URL'); + $this->app_id = env('DY_APPID'); + $this->app_secret = env('DY_APPSECRET'); + } + + /** + * @param $url + * @param array $data + * @param array $extra + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send($url, array $data = [], $extra = []): array { + $signData = [ + 'Api-App-Key' => $this->app_id, + 'Api-Time-Stamp' => time() * 1000, + ]; + // 组装请求数据 + $reqData = $data; + + // 请求头加上签名 + // $header['Api-Sign'] = $this->getSign($signData, $reqData); + $header['access-token'] = $this->getAccessToken(); + if ($this->app_id == 'sandbox289') { + $header['x-sandbox-token'] = 1; + } + + return $this->request($url, $reqData, $header, $extra); + } + + /** + * 获取token + * @return bool|mixed|string + * @throws Exception + * @throws \GuzzleHttp\Exception\GuzzleException + */ + private function getAccessToken() { + // 测试token + if ($this->app_id == 'sandbox289') { + return 'ka.F9tiX0EN5QB5plK43W22p3cDgji9O9ZTAigryyd1A7AlbDwTmDxEN0PwjfI9'; + } + $key = self::DY_TOKEN_KEY; + if ($token = Redis::get($key)) { + return $token; + } + + $reqData = [ + 'client_key' => $this->app_id, + 'client_secret' => $this->app_secret, + 'grant_type' => 'client_credential', + ]; + $res = $this->request(self::CLIENT_TOKEN, $reqData); + if (!$res['flag'] || !isset($res['data']['access_token'])) { + throw new Exception('获取token失败'); + } + Redis::setEx($key, $res['data']['expires_in'], $res['data']['access_token']); + return $res['data']['access_token']; + } + + /** + * 获取签名 + */ + public function getSign($signData, $postData): string { + // $signData = array_merge($signData, $postData); + $buff = ""; + ksort($signData); + // 将key和value按照顺序直接拼接到一起 + foreach ($signData as $k => $v){ + if (!empty($v)) { + if (is_array($v)) { + $v = json_encode(ksort($v), JSON_UNESCAPED_UNICODE); + } + $buff .= $k . $v; + } + } + + // 签名步骤三:在上一步的结果后直接拼secretKey + $str = $buff . $this->app_secret; + // 在上一步结果后直接拼接,经过去除空白字符(\s)的HTTP Body原始字符串 + $str .= preg_replace('/\s+/', '', json_encode($postData)); + // 对上一步结果进行sha1加密,得到16进制字符串 + $str = sha1($str); + // 对上一步结果进行md5加密,得到16进制字符串 + // 将上一步结果转为大写 + return strtoupper(md5($str)); + } + + /** + * @param string $url + * @param array $data + * @param array $header + * @param array $extra + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + private function request(string $url, array $data = [], $header = []): array { + $client = new \GuzzleHttp\Client(); + try { + $reqUrl = $this->host . $url; + $header['Content-Type'] = 'application/json'; + $header['charset'] = 'UTF-8'; + $options['headers'] = $header; + $options['json'] = $data; + $method = 'POST'; + + $response = $client->request($method, $reqUrl, $options); + Log::info(sprintf('dy url:%s, req:%s, response:%s', $reqUrl, json_encode($data), $response->getBody())) ; + $result = ['code' => $response->getStatusCode(), 'body' => json_decode($response->getBody(), true, 512, JSON_BIGINT_AS_STRING)]; + if (isset($result['code']) && $result['code'] == 200) { + if ($result['body']['data']['error_code'] == '0') { + return ['flag' => true, 'data' => $result['body']['data'] ?? [], 'message' => $result['body']['data']['description '] ?? '']; + } else { + return ['flag' => false, 'data' => $result['body']['data'] ?? [], 'message' => $result['body']['data']['description '] ?? '']; + } + } + + return ['flag' => false, 'message' => $result['body']['data']['description'] ?? '', 'data' => []]; + } catch (\Exception $exception) { + Log::info(sprintf('dy req fail:%s, req:%s,res:%s', $exception->getMessage(), $exception->getFile(), $exception->getLine())) ; + return ['flag' => false, 'message' => $exception->getMessage(), 'data' => []]; + } + } +} diff --git a/service/composer.json b/service/composer.json index 78b87a3f..354e6199 100644 --- a/service/composer.json +++ b/service/composer.json @@ -36,7 +36,8 @@ "webman/console": "^1.3", "mk-j/php_xlsxwriter": "^0.39.0", "workerman/crontab": "^1.0", - "workerman/validation": "^1.0" + "workerman/validation": "^1.0", + "guzzlehttp/guzzle": "^7.9" }, "suggest": { "ext-event": "For better performance. " diff --git a/service/ss.txt b/service/ss.txt index ebfb622c..c4e26ffe 100644 --- a/service/ss.txt +++ b/service/ss.txt @@ -37,3 +37,7 @@ ALTER TABLE `follows` ADD INDEX `idx_order_id`(`order_id`) USING HASH; update orders set verification_date =( SELECT FROM_UNIXTIME(create_time) FROM `follows` where status = 2 and order_id = orders.id limit 1) + +##### 2024-11-21 ##### +ALTER TABLE `orders` +ADD COLUMN `is_direct_mode` tinyint NOT NULL DEFAULT 0 COMMENT '是否直连模式(0-否,1-是)' AFTER `verification_date`;