作者 郭文星

123

正在显示 53 个修改的文件 包含 4764 行增加0 行删除

要显示太多修改。

为保证性能只显示 53 of 53+ 个文件。

  1 +{"files":["application\\admin\\controller\\groupon\\activity\\Activity.php","application\\admin\\controller\\groupon\\Admin.php","application\\admin\\controller\\groupon\\Area.php","application\\admin\\controller\\groupon\\Base.php","application\\admin\\controller\\groupon\\Category.php","application\\admin\\controller\\groupon\\Config.php","application\\admin\\controller\\groupon\\Coupons.php","application\\admin\\controller\\groupon\\Decorate.php","application\\admin\\controller\\groupon\\Express.php","application\\admin\\controller\\groupon\\Faq.php","application\\admin\\controller\\groupon\\Feedback.php","application\\admin\\controller\\groupon\\goods\\Comment.php","application\\admin\\controller\\groupon\\goods\\Goods.php","application\\admin\\controller\\groupon\\goods\\Service.php","application\\admin\\controller\\groupon\\goods\\Sku.php","application\\admin\\controller\\groupon\\goods\\SkuPrice.php","application\\admin\\controller\\groupon\\goods\\StockWarning.php","application\\admin\\controller\\groupon\\Link.php","application\\admin\\controller\\groupon\\Notification.php","application\\admin\\controller\\groupon\\order\\Order.php","application\\admin\\controller\\groupon\\order\\OrderItem.php","application\\admin\\controller\\groupon\\order\\StoreExpress.php","application\\admin\\controller\\groupon\\Richtext.php","application\\admin\\controller\\groupon\\store\\Apply.php","application\\admin\\controller\\groupon\\store\\Dashboard.php","application\\admin\\controller\\groupon\\store\\Store.php","application\\admin\\controller\\groupon\\store\\TakeLog.php","application\\admin\\controller\\groupon\\Upload.php","application\\admin\\controller\\groupon\\user\\Group.php","application\\admin\\controller\\groupon\\user\\Rule.php","application\\admin\\controller\\groupon\\user\\User.php","application\\admin\\controller\\groupon\\UserWalletApply.php","application\\admin\\lang\\zh-cn\\groupon\\area.php","application\\admin\\lang\\zh-cn\\groupon\\category.php","application\\admin\\lang\\zh-cn\\groupon\\config.php","application\\admin\\lang\\zh-cn\\groupon\\coupons.php","application\\admin\\lang\\zh-cn\\groupon\\decorate.php","application\\admin\\lang\\zh-cn\\groupon\\faq.php","application\\admin\\lang\\zh-cn\\groupon\\feedback.php","application\\admin\\lang\\zh-cn\\groupon\\goods\\comment.php","application\\admin\\lang\\zh-cn\\groupon\\goods\\goods.php","application\\admin\\lang\\zh-cn\\groupon\\goods\\service.php","application\\admin\\lang\\zh-cn\\groupon\\goods\\sku.php","application\\admin\\lang\\zh-cn\\groupon\\goods\\sku_price.php","application\\admin\\lang\\zh-cn\\groupon\\link.php","application\\admin\\lang\\zh-cn\\groupon\\notification\\config.php","application\\admin\\lang\\zh-cn\\groupon\\notification.php","application\\admin\\lang\\zh-cn\\groupon\\order\\order.php","application\\admin\\lang\\zh-cn\\groupon\\order\\order_item.php","application\\admin\\lang\\zh-cn\\groupon\\order\\refund_log.php","application\\admin\\lang\\zh-cn\\groupon\\richtext.php","application\\admin\\lang\\zh-cn\\groupon\\share.php","application\\admin\\lang\\zh-cn\\groupon\\store\\apply.php","application\\admin\\lang\\zh-cn\\groupon\\upload.php","application\\admin\\lang\\zh-cn\\groupon\\user\\group.php","application\\admin\\lang\\zh-cn\\groupon\\user\\rule.php","application\\admin\\lang\\zh-cn\\groupon\\user\\user.php","application\\admin\\lang\\zh-cn\\groupon\\user_wallet_apply.php","application\\admin\\model\\groupon\\activity\\Activity.php","application\\admin\\model\\groupon\\Area.php","application\\admin\\model\\groupon\\Category.php","application\\admin\\model\\groupon\\Config.php","application\\admin\\model\\groupon\\Coupons.php","application\\admin\\model\\groupon\\Decorate.php","application\\admin\\model\\groupon\\DecorateContent.php","application\\admin\\model\\groupon\\DecoratePage.php","application\\admin\\model\\groupon\\dispatch\\ExpressCompany.php","application\\admin\\model\\groupon\\Express.php","application\\admin\\model\\groupon\\Faq.php","application\\admin\\model\\groupon\\Feedback.php","application\\admin\\model\\groupon\\goods\\Comment.php","application\\admin\\model\\groupon\\goods\\Goods.php","application\\admin\\model\\groupon\\goods\\Service.php","application\\admin\\model\\groupon\\goods\\Sku.php","application\\admin\\model\\groupon\\goods\\SkuPrice.php","application\\admin\\model\\groupon\\goods\\StockWarning.php","application\\admin\\model\\groupon\\Link.php","application\\admin\\model\\groupon\\notification\\Config.php","application\\admin\\model\\groupon\\Notification.php","application\\admin\\model\\groupon\\order\\Order.php","application\\admin\\model\\groupon\\order\\OrderAction.php","application\\admin\\model\\groupon\\order\\OrderExpress.php","application\\admin\\model\\groupon\\order\\OrderExpressLog.php","application\\admin\\model\\groupon\\order\\OrderItem.php","application\\admin\\model\\groupon\\order\\OrderStoreExpress.php","application\\admin\\model\\groupon\\order\\RefundLog.php","application\\admin\\model\\groupon\\Richtext.php","application\\admin\\model\\groupon\\Share.php","application\\admin\\model\\groupon\\store\\Apply.php","application\\admin\\model\\groupon\\store\\Recruit.php","application\\admin\\model\\groupon\\store\\Store.php","application\\admin\\model\\groupon\\store\\TakeLog.php","application\\admin\\model\\groupon\\Upload.php","application\\admin\\model\\groupon\\user\\Coupon.php","application\\admin\\model\\groupon\\user\\Favorite.php","application\\admin\\model\\groupon\\user\\Group.php","application\\admin\\model\\groupon\\user\\MoneyLog.php","application\\admin\\model\\groupon\\user\\Oauth.php","application\\admin\\model\\groupon\\user\\ScoreLog.php","application\\admin\\model\\groupon\\user\\User.php","application\\admin\\model\\groupon\\user\\View.php","application\\admin\\model\\groupon\\UserFake.php","application\\admin\\model\\groupon\\UserWalletApply.php","application\\admin\\model\\groupon\\wechat\\Fans.php","application\\admin\\model\\groupon\\wechat\\Wechat.php","application\\admin\\validate\\groupon\\activity\\Activity.php","application\\admin\\validate\\groupon\\Area.php","application\\admin\\validate\\groupon\\Category.php","application\\admin\\validate\\groupon\\Config.php","application\\admin\\validate\\groupon\\Coupons.php","application\\admin\\validate\\groupon\\Decorate.php","application\\admin\\validate\\groupon\\Faq.php","application\\admin\\validate\\groupon\\Feedback.php","application\\admin\\validate\\groupon\\Goods.php","application\\admin\\validate\\groupon\\GoodsComment.php","application\\admin\\validate\\groupon\\GoodsService.php","application\\admin\\validate\\groupon\\GoodsSku.php","application\\admin\\validate\\groupon\\GoodsSkuPrice.php","application\\admin\\validate\\groupon\\Link.php","application\\admin\\validate\\groupon\\notification\\Config.php","application\\admin\\validate\\groupon\\Notification.php","application\\admin\\validate\\groupon\\Notification_config.php","application\\admin\\validate\\groupon\\order\\Order.php","application\\admin\\validate\\groupon\\order\\OrderItem.php","application\\admin\\validate\\groupon\\Richtext.php","application\\admin\\validate\\groupon\\store\\Apply.php","application\\admin\\validate\\groupon\\Upload.php","application\\admin\\validate\\groupon\\UserWalletApply.php","application\\admin\\view\\groupon\\activity\\activity\\edit.html","application\\admin\\view\\groupon\\activity\\activity\\index.html","application\\admin\\view\\groupon\\activity\\activity\\recyclebin.html","application\\admin\\view\\groupon\\activity\\activity\\select.html","application\\admin\\view\\groupon\\area\\add.html","application\\admin\\view\\groupon\\area\\edit.html","application\\admin\\view\\groupon\\area\\index.html","application\\admin\\view\\groupon\\area\\select.html","application\\admin\\view\\groupon\\category\\index.html","application\\admin\\view\\groupon\\category\\select.html","application\\admin\\view\\groupon\\config\\add.html","application\\admin\\view\\groupon\\config\\edit.html","application\\admin\\view\\groupon\\config\\index.html","application\\admin\\view\\groupon\\config\\platform.html","application\\admin\\view\\groupon\\coupons\\add.html","application\\admin\\view\\groupon\\coupons\\edit.html","application\\admin\\view\\groupon\\coupons\\index.html","application\\admin\\view\\groupon\\coupons\\recyclebin.html","application\\admin\\view\\groupon\\coupons\\select.html","application\\admin\\view\\groupon\\decorate\\custom.html","application\\admin\\view\\groupon\\decorate\\dodecorate.html","application\\admin\\view\\groupon\\decorate\\lists.html","application\\admin\\view\\groupon\\decorate\\recyclebin.html","application\\admin\\view\\groupon\\decorate\\select.html","application\\admin\\view\\groupon\\express\\add.html","application\\admin\\view\\groupon\\express\\edit.html","application\\admin\\view\\groupon\\express\\index.html","application\\admin\\view\\groupon\\faq\\add.html","application\\admin\\view\\groupon\\faq\\edit.html","application\\admin\\view\\groupon\\faq\\index.html","application\\admin\\view\\groupon\\faq\\recyclebin.html","application\\admin\\view\\groupon\\feedback\\edit.html","application\\admin\\view\\groupon\\feedback\\index.html","application\\admin\\view\\groupon\\feedback\\recyclebin.html","application\\admin\\view\\groupon\\goods\\comment\\add.html","application\\admin\\view\\groupon\\goods\\comment\\edit.html","application\\admin\\view\\groupon\\goods\\comment\\index.html","application\\admin\\view\\groupon\\goods\\comment\\recyclebin.html","application\\admin\\view\\groupon\\goods\\goods\\add.html","application\\admin\\view\\groupon\\goods\\goods\\edit.html","application\\admin\\view\\groupon\\goods\\goods\\index.html","application\\admin\\view\\groupon\\goods\\goods\\recyclebin.html","application\\admin\\view\\groupon\\goods\\goods\\select.html","application\\admin\\view\\groupon\\goods\\goods\\sku.html","application\\admin\\view\\groupon\\goods\\goods\\skuedit.html","application\\admin\\view\\groupon\\goods\\service\\add.html","application\\admin\\view\\groupon\\goods\\service\\edit.html","application\\admin\\view\\groupon\\goods\\service\\index.html","application\\admin\\view\\groupon\\goods\\service\\recyclebin.html","application\\admin\\view\\groupon\\goods\\sku\\add.html","application\\admin\\view\\groupon\\goods\\sku\\edit.html","application\\admin\\view\\groupon\\goods\\sku\\index.html","application\\admin\\view\\groupon\\goods\\sku_price\\add.html","application\\admin\\view\\groupon\\goods\\sku_price\\edit.html","application\\admin\\view\\groupon\\goods\\sku_price\\index.html","application\\admin\\view\\groupon\\goods\\sku_price\\recyclebin.html","application\\admin\\view\\groupon\\goods\\stock_warning\\index.html","application\\admin\\view\\groupon\\goods\\stock_warning\\recyclebin.html","application\\admin\\view\\groupon\\link\\add.html","application\\admin\\view\\groupon\\link\\edit.html","application\\admin\\view\\groupon\\link\\index.html","application\\admin\\view\\groupon\\link\\recyclebin.html","application\\admin\\view\\groupon\\link\\select.html","application\\admin\\view\\groupon\\notification\\config.html","application\\admin\\view\\groupon\\order\\order\\detail.html","application\\admin\\view\\groupon\\order\\order\\index.html","application\\admin\\view\\groupon\\order\\order\\recyclebin.html","application\\admin\\view\\groupon\\order\\order_item\\add.html","application\\admin\\view\\groupon\\order\\order_item\\edit.html","application\\admin\\view\\groupon\\order\\order_item\\index.html","application\\admin\\view\\groupon\\order\\order_item\\recyclebin.html","application\\admin\\view\\groupon\\order\\store_express\\add.html","application\\admin\\view\\groupon\\order\\store_express\\detail.html","application\\admin\\view\\groupon\\order\\store_express\\index.html","application\\admin\\view\\groupon\\richtext\\add.html","application\\admin\\view\\groupon\\richtext\\edit.html","application\\admin\\view\\groupon\\richtext\\index.html","application\\admin\\view\\groupon\\richtext\\recyclebin.html","application\\admin\\view\\groupon\\richtext\\select.html","application\\admin\\view\\groupon\\store\\apply\\add.html","application\\admin\\view\\groupon\\store\\apply\\detail.html","application\\admin\\view\\groupon\\store\\apply\\edit.html","application\\admin\\view\\groupon\\store\\apply\\index.html","application\\admin\\view\\groupon\\store\\dashboard\\index.html","application\\admin\\view\\groupon\\store\\store\\add.html","application\\admin\\view\\groupon\\store\\store\\edit.html","application\\admin\\view\\groupon\\store\\store\\index.html","application\\admin\\view\\groupon\\store\\store\\recyclebin.html","application\\admin\\view\\groupon\\store\\take_log\\index.html","application\\admin\\view\\groupon\\user\\group\\add.html","application\\admin\\view\\groupon\\user\\group\\edit.html","application\\admin\\view\\groupon\\user\\group\\index.html","application\\admin\\view\\groupon\\user\\rule\\add.html","application\\admin\\view\\groupon\\user\\rule\\edit.html","application\\admin\\view\\groupon\\user\\rule\\index.html","application\\admin\\view\\groupon\\user\\user\\index.html","application\\admin\\view\\groupon\\user\\user\\money_recharge.html","application\\admin\\view\\groupon\\user\\user\\profile.html","application\\admin\\view\\groupon\\user\\user\\score_recharge.html","application\\admin\\view\\groupon\\user_wallet_apply\\index.html","public\\assets\\addons\\groupon\\img\\activity\\full_discount.png","public\\assets\\addons\\groupon\\img\\activity\\full_gift.png","public\\assets\\addons\\groupon\\img\\activity\\full_reduce.png","public\\assets\\addons\\groupon\\img\\category\\edit.png","public\\assets\\addons\\groupon\\img\\category\\img-1.png","public\\assets\\addons\\groupon\\img\\category\\img-2.png","public\\assets\\addons\\groupon\\img\\category\\img-3.png","public\\assets\\addons\\groupon\\img\\category\\img-4.png","public\\assets\\addons\\groupon\\img\\category\\selected.png","public\\assets\\addons\\groupon\\img\\config\\alipay-icon.png","public\\assets\\addons\\groupon\\img\\config\\App-icon.png","public\\assets\\addons\\groupon\\img\\config\\apple-icon.png","public\\assets\\addons\\groupon\\img\\config\\chat-icon.png","public\\assets\\addons\\groupon\\img\\config\\groupon-icon.png","public\\assets\\addons\\groupon\\img\\config\\h5-icon.png","public\\assets\\addons\\groupon\\img\\config\\order-icon.png","public\\assets\\addons\\groupon\\img\\config\\score-icon.png","public\\assets\\addons\\groupon\\img\\config\\services-icon.png","public\\assets\\addons\\groupon\\img\\config\\share-icon.png","public\\assets\\addons\\groupon\\img\\config\\store-icon.png","public\\assets\\addons\\groupon\\img\\config\\user-icon.png","public\\assets\\addons\\groupon\\img\\config\\wallet-icon.png","public\\assets\\addons\\groupon\\img\\config\\wechat-icon.png","public\\assets\\addons\\groupon\\img\\config\\withdraw-icon.png","public\\assets\\addons\\groupon\\img\\config\\wxMiniProgram-icon.png","public\\assets\\addons\\groupon\\img\\config\\wxOfficialAccount-icon.png","public\\assets\\addons\\groupon\\img\\decorate\\activity-fire.png","public\\assets\\addons\\groupon\\img\\decorate\\adv-default-2.png","public\\assets\\addons\\groupon\\img\\decorate\\adv.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_01.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_02.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_03.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_04.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_05.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_06.png","public\\assets\\addons\\groupon\\img\\decorate\\adv_07.png","public\\assets\\addons\\groupon\\img\\decorate\\banner.png","public\\assets\\addons\\groupon\\img\\decorate\\banner_default.png","public\\assets\\addons\\groupon\\img\\decorate\\cat.png","public\\assets\\addons\\groupon\\img\\decorate\\category_tabs.png","public\\assets\\addons\\groupon\\img\\decorate\\coupons.png","public\\assets\\addons\\groupon\\img\\decorate\\coupons_bg.png","public\\assets\\addons\\groupon\\img\\decorate\\float-button.png","public\\assets\\addons\\groupon\\img\\decorate\\goods-group.png","public\\assets\\addons\\groupon\\img\\decorate\\goods-list.png","public\\assets\\addons\\groupon\\img\\decorate\\header.png","public\\assets\\addons\\groupon\\img\\decorate\\hot-label.png","public\\assets\\addons\\groupon\\img\\decorate\\image-default.png","public\\assets\\addons\\groupon\\img\\decorate\\image-default2.png","public\\assets\\addons\\groupon\\img\\decorate\\image-default3.png","public\\assets\\addons\\groupon\\img\\decorate\\limit-time.png","public\\assets\\addons\\groupon\\img\\decorate\\menu.png","public\\assets\\addons\\groupon\\img\\decorate\\menu_default.gif","public\\assets\\addons\\groupon\\img\\decorate\\popup.png","public\\assets\\addons\\groupon\\img\\decorate\\special-image.png","public\\assets\\addons\\groupon\\img\\decorate\\store-image.jpg","public\\assets\\addons\\groupon\\img\\decorate\\store.png","public\\assets\\addons\\groupon\\img\\decorate\\tabs-active.png","public\\assets\\addons\\groupon\\img\\decorate\\template_name.png","public\\assets\\addons\\groupon\\img\\decorate\\title-block.png","public\\assets\\addons\\groupon\\img\\decorate\\title1.png","public\\assets\\addons\\groupon\\img\\decorate\\title2.png","public\\assets\\addons\\groupon\\img\\decorate\\title3.png","public\\assets\\addons\\groupon\\img\\decorate\\title4.png","public\\assets\\addons\\groupon\\img\\decorate\\title5.png","public\\assets\\addons\\groupon\\img\\decorate\\wxMiniProgram.png","public\\assets\\addons\\groupon\\img\\decorate\\zujian.png","public\\assets\\addons\\groupon\\img\\default-avatar.png","public\\assets\\addons\\groupon\\img\\derocate_down.png","public\\assets\\addons\\groupon\\img\\derocate_up.png","public\\assets\\addons\\groupon\\img\\goods\\active.png","public\\assets\\addons\\groupon\\img\\goods\\batch.png","public\\assets\\addons\\groupon\\img\\goods\\close.png","public\\assets\\addons\\groupon\\img\\goods\\hover.png","public\\assets\\addons\\groupon\\img\\goods\\move.png","public\\assets\\addons\\groupon\\img\\goods\\selected.png","public\\assets\\addons\\groupon\\img\\notification\\wxMiniProgram.png","public\\assets\\addons\\groupon\\img\\sku_close.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\aftersale.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\allordernum.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\noget.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\nosend.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\orderpersonnum.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\payed.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\paymoney.png","public\\assets\\addons\\groupon\\img\\store\\dashboard\\refund.png","public\\assets\\addons\\groupon\\img\\store\\position-icon.png","public\\assets\\addons\\groupon\\img\\user_wallet_apply\\alipay.png","public\\assets\\addons\\groupon\\img\\user_wallet_apply\\bank.png","public\\assets\\addons\\groupon\\img\\user_wallet_apply\\wechat.png","public\\assets\\addons\\groupon\\libs\\antv.js","public\\assets\\addons\\groupon\\libs\\clipboard.min.js","public\\assets\\addons\\groupon\\libs\\color-thief.js","public\\assets\\addons\\groupon\\libs\\common.css","public\\assets\\addons\\groupon\\libs\\element\\element.css","public\\assets\\addons\\groupon\\libs\\element\\element.js","public\\assets\\addons\\groupon\\libs\\element\\fonts\\element-icons.ttf","public\\assets\\addons\\groupon\\libs\\element\\fonts\\element-icons.woff","public\\assets\\addons\\groupon\\libs\\groupon.js","public\\assets\\addons\\groupon\\libs\\moment.js","public\\assets\\addons\\groupon\\libs\\require-text.js","public\\assets\\addons\\groupon\\libs\\Sortable.min.js","public\\assets\\addons\\groupon\\libs\\vue-dev.js","public\\assets\\addons\\groupon\\libs\\vue.js","public\\assets\\addons\\groupon\\libs\\vuedraggable.js","public\\assets\\js\\backend\\groupon\\activity\\activity.js","public\\assets\\js\\backend\\groupon\\area.js","public\\assets\\js\\backend\\groupon\\category.js","public\\assets\\js\\backend\\groupon\\config.js","public\\assets\\js\\backend\\groupon\\coupons.js","public\\assets\\js\\backend\\groupon\\decorate.js","public\\assets\\js\\backend\\groupon\\express.js","public\\assets\\js\\backend\\groupon\\faq.js","public\\assets\\js\\backend\\groupon\\feedback.js","public\\assets\\js\\backend\\groupon\\goods\\comment.js","public\\assets\\js\\backend\\groupon\\goods\\goods.js","public\\assets\\js\\backend\\groupon\\goods\\service.js","public\\assets\\js\\backend\\groupon\\goods\\sku.js","public\\assets\\js\\backend\\groupon\\goods\\sku_price.js","public\\assets\\js\\backend\\groupon\\goods\\stock_warning.js","public\\assets\\js\\backend\\groupon\\link.js","public\\assets\\js\\backend\\groupon\\notification.js","public\\assets\\js\\backend\\groupon\\order\\order.js","public\\assets\\js\\backend\\groupon\\order\\order_item.js","public\\assets\\js\\backend\\groupon\\order\\store_express.js","public\\assets\\js\\backend\\groupon\\richtext.js","public\\assets\\js\\backend\\groupon\\store\\apply.js","public\\assets\\js\\backend\\groupon\\store\\dashboard.js","public\\assets\\js\\backend\\groupon\\store\\store.js","public\\assets\\js\\backend\\groupon\\store\\take_log.js","public\\assets\\js\\backend\\groupon\\upload.js","public\\assets\\js\\backend\\groupon\\user\\group.js","public\\assets\\js\\backend\\groupon\\user\\user.js","public\\assets\\js\\backend\\groupon\\user_wallet_apply.js"],"license":"regular","licenseto":"44234","licensekey":"oO7jRk8tbSxzT2hJ QYIUbhnu2J0xrMQyJb0sfQ==","domains":[],"licensecodes":[],"validations":[],"menus":["groupon","groupon\/config","groupon\/config\/index","groupon\/config\/add","groupon\/config\/edit","groupon\/config\/del","groupon\/config\/multi","groupon\/config\/platform","groupon\/decorate","groupon\/decorate\/lists","groupon\/decorate\/add","groupon\/decorate\/edit","groupon\/decorate\/del","groupon\/decorate\/multi","groupon\/decorate\/recyclebin","groupon\/decorate\/destroy","groupon\/decorate\/restore","groupon\/decorate\/dodecorate","groupon\/decorate\/preview","groupon\/decorate\/custom","groupon\/decorate\/publish","groupon\/decorate\/dodecorate_save","groupon\/decorate\/copy","groupon\/decorate\/index","groupon\/decorate\/down","groupon\/coupons","groupon\/coupons\/index","groupon\/coupons\/recyclebin","groupon\/coupons\/add","groupon\/coupons\/edit","groupon\/coupons\/del","groupon\/coupons\/destroy","groupon\/coupons\/restore","groupon\/coupons\/multi","groupon\/coupons\/select","groupon\/product","groupon\/category","groupon\/category\/index","groupon\/category\/add","groupon\/category\/edit","groupon\/category\/del","groupon\/category\/multi","groupon\/category\/select","groupon\/category\/update","groupon\/goods\/goods","groupon\/goods\/goods\/index","groupon\/goods\/goods\/recyclebin","groupon\/goods\/goods\/add","groupon\/goods\/goods\/edit","groupon\/goods\/goods\/del","groupon\/goods\/goods\/destroy","groupon\/goods\/goods\/restore","groupon\/goods\/goods\/multi","groupon\/goods\/goods\/select","groupon\/goods\/goods\/detail","groupon\/goods\/goods\/setstatus","groupon\/goods\/service","groupon\/goods\/service\/index","groupon\/goods\/service\/recyclebin","groupon\/goods\/service\/add","groupon\/goods\/service\/edit","groupon\/goods\/service\/del","groupon\/goods\/service\/destroy","groupon\/goods\/service\/restore","groupon\/goods\/service\/multi","groupon\/goods\/comment","groupon\/goods\/comment\/index","groupon\/goods\/comment\/recyclebin","groupon\/goods\/comment\/add","groupon\/goods\/comment\/edit","groupon\/goods\/comment\/del","groupon\/goods\/comment\/destroy","groupon\/goods\/comment\/restore","groupon\/goods\/comment\/multi","groupon\/goods\/stock_warning","groupon\/goods\/stock_warning\/index","groupon\/goods\/stock_warning\/recyclebin","groupon\/goods\/stock_warning\/addstock","groupon\/goods\/stock_warning\/destroy","groupon\/material","groupon\/express","groupon\/express\/index","groupon\/express\/add","groupon\/express\/edit","groupon\/express\/del","groupon\/express\/multi","groupon\/express\/select","groupon\/richtext","groupon\/richtext\/index","groupon\/richtext\/recyclebin","groupon\/richtext\/add","groupon\/richtext\/edit","groupon\/richtext\/del","groupon\/richtext\/destroy","groupon\/richtext\/restore","groupon\/richtext\/multi","groupon\/richtext\/select","groupon\/link","groupon\/link\/index","groupon\/link\/recyclebin","groupon\/link\/add","groupon\/link\/edit","groupon\/link\/del","groupon\/link\/destroy","groupon\/link\/restore","groupon\/link\/multi","groupon\/link\/select","groupon\/area","groupon\/area\/index","groupon\/area\/add","groupon\/area\/edit","groupon\/area\/del","groupon\/area\/multi","groupon\/area\/select","groupon\/faq","groupon\/faq\/index","groupon\/faq\/recyclebin","groupon\/faq\/add","groupon\/faq\/edit","groupon\/faq\/del","groupon\/faq\/destroy","groupon\/faq\/restore","groupon\/faq\/multi","groupon\/user_wallet_apply","groupon\/user_wallet_apply\/index","groupon\/user_wallet_apply\/gettype","groupon\/user_wallet_apply\/handle","groupon\/user_wallet_apply\/log","groupon\/order","groupon\/order\/order","groupon\/order\/order\/index","groupon\/order\/order\/recyclebin","groupon\/order\/order\/add","groupon\/order\/order\/edit","groupon\/order\/order\/del","groupon\/order\/order\/destroy","groupon\/order\/order\/restore","groupon\/order\/order\/multi","groupon\/order\/order\/detail","groupon\/order\/order\/send","groupon\/order\/order\/aftersalefinish","groupon\/order\/order\/refund","groupon\/order\/order\/editmemo","groupon\/order\/order\/gettype","groupon\/order\/order\/refundrefuse","groupon\/order\/order\/export","groupon\/order\/order\/actions","groupon\/order\/order\/editconsignee","groupon\/order\/order\/sendstore","groupon\/order\/order_item","groupon\/order\/order_item\/index","groupon\/order\/order_item\/recyclebin","groupon\/order\/order_item\/add","groupon\/order\/order_item\/edit","groupon\/order\/order_item\/del","groupon\/order\/order_item\/destroy","groupon\/order\/order_item\/restore","groupon\/order\/order_item\/multi","groupon\/store\/dashboard","groupon\/store\/dashboard\/index","groupon\/order\/store_express","groupon\/order\/store_express\/getexpress","groupon\/order\/store_express\/index","groupon\/order\/store_express\/add","groupon\/order\/store_express\/send","groupon\/order\/store_express\/detail","groupon\/order\/store_express\/setexpress","groupon\/order\/store_express\/export","groupon\/feedback","groupon\/feedback\/index","groupon\/feedback\/recyclebin","groupon\/feedback\/add","groupon\/feedback\/edit","groupon\/feedback\/del","groupon\/feedback\/destroy","groupon\/feedback\/restore","groupon\/feedback\/multi","groupon\/feedback\/detail","groupon\/notification","groupon\/notification\/index","groupon\/notification\/recyclebin","groupon\/notification\/add","groupon\/notification\/edit","groupon\/notification\/del","groupon\/notification\/destroy","groupon\/notification\/restore","groupon\/notification\/config","groupon\/notification\/set_template","groupon\/notification\/set_status","groupon\/store","groupon\/store\/store","groupon\/store\/store\/add","groupon\/store\/store\/edit","groupon\/store\/store\/del","groupon\/store\/store\/recyclebin","groupon\/store\/store\/setstatus","groupon\/store\/store\/select","groupon\/store\/store\/index","groupon\/store\/store\/all","groupon\/store\/store\/restore","groupon\/store\/store\/destroy","groupon\/store\/apply","groupon\/store\/apply\/applyoper","groupon\/store\/apply\/index","groupon\/store\/apply\/detail","groupon\/store\/take_log","groupon\/store\/take_log\/index","groupon\/store\/take_log\/run_money","groupon\/store\/take_log\/back_money","groupon\/user\/user","groupon\/user\/user\/index","groupon\/user\/user\/select","groupon\/user\/user\/profile","groupon\/user\/user\/del","groupon\/user\/user\/order_log","groupon\/user\/user\/login_log","groupon\/user\/user\/money_log","groupon\/user\/user\/score_log","groupon\/user\/user\/share_log","groupon\/user\/user\/goods_favorite","groupon\/user\/user\/coupon_log","groupon\/user\/user\/goods_view","groupon\/user\/user\/recyclebin","groupon\/user\/user\/update","groupon\/user\/user\/money_recharge","groupon\/user\/user\/score_recharge","groupon\/activity","groupon\/activity\/activity","groupon\/activity\/activity\/index","groupon\/activity\/activity\/add","groupon\/activity\/activity\/edit","groupon\/activity\/activity\/del","groupon\/activity\/activity\/gettype","groupon\/activity\/activity\/recyclebin"]}
  1 +<?php
  2 +
  3 +namespace addons\groupon;
  4 +
  5 +use think\Addons;
  6 +use app\common\library\Menu;
  7 +use app\admin\model\AuthRule;
  8 +use addons\groupon\library\Hook;
  9 +use think\Exception;
  10 +use think\exception\PDOException;
  11 +use think\Loader;
  12 +
  13 +/**
  14 + * Groupon插件
  15 + */
  16 +class Groupon extends Addons
  17 +{
  18 +
  19 + /**
  20 + * 插件安装方法
  21 + * @return bool
  22 + */
  23 + public function install()
  24 + {
  25 + $menu = self::getMenu();
  26 + Menu::create($menu['new']);
  27 + return true;
  28 + }
  29 +
  30 + /**
  31 + * 插件卸载方法
  32 + * @return bool
  33 + */
  34 + public function uninstall()
  35 + {
  36 + Menu::delete('groupon');
  37 + return true;
  38 + }
  39 +
  40 + /**
  41 + * 插件启用方法
  42 + */
  43 + public function enable()
  44 + {
  45 + Menu::enable('groupon');
  46 + return true;
  47 + }
  48 +
  49 + /**
  50 + * 插件更新方法
  51 + */
  52 + public function upgrade()
  53 + {
  54 + $menu = self::getMenu();
  55 + if (method_exists(Menu::class, 'upgrade')) {
  56 + Menu::upgrade('groupon', $menu['new']);
  57 + } else {
  58 + //使用Groupon自带的更新操作
  59 + self::menuCreateOrUpdate($menu['new'], $menu['old']);
  60 + }
  61 +
  62 + return true;
  63 + }
  64 +
  65 + /**
  66 + * 插件禁用方法
  67 + */
  68 + public function disable()
  69 + {
  70 + Menu::disable('groupon');
  71 + return true;
  72 + }
  73 +
  74 +
  75 + /**
  76 + * 应用初始化
  77 + */
  78 + public function appInit()
  79 + {
  80 + // 公共方法
  81 + require_once __DIR__ . '/helper.php';
  82 + // 补丁
  83 + if (!class_exists('\GuzzleHttp\UriTemplate')) {
  84 + require_once __DIR__ . '/patch/storage.php';
  85 + Loader::addNamespace('GuzzleHttp', $this->addons_path . str_replace('/', DS, 'patch/storage'));
  86 + }
  87 +
  88 + // 全局注册行为事件
  89 + Hook::register();
  90 + }
  91 +
  92 + private static function getMenu()
  93 + {
  94 + $newMenu = [];
  95 + $config_file = ADDON_PATH . "groupon" . DS . 'config' . DS . "menu.php";
  96 + if (is_file($config_file)) {
  97 + $newMenu = include $config_file;
  98 + }
  99 + $oldMenu = AuthRule::where('name', 'like', "groupon%")->select();
  100 + $oldMenu = array_column($oldMenu, null, 'name');
  101 + return ['new' => $newMenu, 'old' => $oldMenu];
  102 + }
  103 +
  104 + private static function menuCreateOrUpdate($newMenu, $oldMenu, $parent = 0)
  105 + {
  106 + if (!is_numeric($parent)) {
  107 + $parentRule = AuthRule::getByName($parent);
  108 + $pid = $parentRule ? $parentRule['id'] : 0;
  109 + } else {
  110 + $pid = $parent;
  111 + }
  112 + $allow = array_flip(['file', 'name', 'title', 'icon', 'condition', 'remark', 'ismenu', 'weigh']);
  113 + foreach ($newMenu as $k => $v) {
  114 + $hasChild = isset($v['sublist']) && $v['sublist'] ? true : false;
  115 + $data = array_intersect_key($v, $allow);
  116 + $data['ismenu'] = isset($data['ismenu']) ? $data['ismenu'] : ($hasChild ? 1 : 0);
  117 + $data['icon'] = isset($data['icon']) ? $data['icon'] : ($hasChild ? 'fa fa-list' : 'fa fa-circle-o');
  118 + $data['pid'] = $pid;
  119 + $data['status'] = 'normal';
  120 + try {
  121 + if (!isset($oldMenu[$data['name']])) {
  122 + $menu = AuthRule::create($data);
  123 + } else {
  124 + $menu = $oldMenu[$data['name']];
  125 + }
  126 + if ($hasChild) {
  127 + self::menuCreateOrUpdate($v['sublist'], $oldMenu, $menu['id']);
  128 + }
  129 + } catch (PDOException $e) {
  130 + throw new Exception($e->getMessage());
  131 + }
  132 + }
  133 + }
  134 +}
  1 +<?php
  2 + return array (
  3 + 'table_name' => '',
  4 + 'self_path' => '
  5 +application/admin/lang/zh-cn/groupon',
  6 + 'update_data' => '',
  7 +);
此 diff 太大无法显示。
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\model\UserAddress;
  7 +use addons\groupon\model\Area;
  8 +
  9 +class Address extends Base
  10 +{
  11 +
  12 + protected $noNeedLogin = ['area'];
  13 + protected $noNeedRight = ['*'];
  14 +
  15 + public function index()
  16 + {
  17 + $this->success('收货地址', UserAddress::getUserAddress());
  18 + }
  19 +
  20 + public function defaults()
  21 + {
  22 + $this->success('默认收货地址', UserAddress::getUserDefaultAddress());
  23 + }
  24 +
  25 + public function area()
  26 + {
  27 + $data['provinceData'] = Area::where('level', 1)->order('id asc')->field('id as value, name as label, pid, level')->select();
  28 + foreach ($data['provinceData'] as $k => $p) {
  29 + $data['cityData'][$k] = Area::where(['level' => 2, 'pid' => $p->value])->order('id asc')->field('id as value, name as label, pid, level')->select();
  30 + foreach ($data['cityData'][$k] as $i => $c) {
  31 + $data['areaData'][$k][$i] = Area::where(['level' => 3, 'pid' => $c->value])->order('id asc')->field('id as value, name as label, pid, level')->select();
  32 + }
  33 + }
  34 +
  35 + $this->success('省市区', $data);
  36 +
  37 + }
  38 +
  39 + public function edit()
  40 + {
  41 + $params = $this->request->post();
  42 +
  43 + // 表单验证
  44 + $this->grouponValidate($params, get_class(), 'edit');
  45 +
  46 + $this->success('编辑地址', UserAddress::edit($params));
  47 + }
  48 +
  49 + public function info()
  50 + {
  51 + $params = $this->request->get();
  52 + $this->success('地址详情', UserAddress::info($params));
  53 + }
  54 +
  55 + public function del()
  56 + {
  57 + $params = $this->request->post();
  58 + $this->success('地址详情', UserAddress::del($params));
  59 +
  60 + }
  61 +
  62 +
  63 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use app\common\controller\Api;
  7 +use think\Lang;
  8 +
  9 +class Base extends Api
  10 +{
  11 + public function _initialize()
  12 + {
  13 +
  14 + parent::_initialize();
  15 + $controllername = strtolower($this->request->controller());
  16 + $this->loadlang($controllername);
  17 +
  18 + }
  19 +
  20 + protected function loadlang($name)
  21 + {
  22 + Lang::load(ADDON_PATH . 'groupon/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php');
  23 + }
  24 +
  25 +
  26 + protected function grouponValidate($params, $class, $scene, $rules = []) {
  27 + $validate = validate(str_replace('controller', 'validate', $class));
  28 + if (!$validate->check($params, $rules, $scene)) {
  29 + $this->error($validate->getError());
  30 + }
  31 + }
  32 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\model\Cart as CartModel;
  6 +
  7 +class Cart extends Base
  8 +{
  9 +
  10 + protected $noNeedLogin = [];
  11 + protected $noNeedRight = ['*'];
  12 +
  13 + public function index()
  14 + {
  15 + $data = CartModel::info();
  16 + $this->success('我的购物车', $data);
  17 + }
  18 +
  19 + public function add()
  20 + {
  21 + $params = $this->request->post();
  22 +
  23 + // 表单验证
  24 + $this->grouponValidate($params, get_class(), 'add');
  25 +
  26 + $goodsList = $params['goods_list'];
  27 + $this->success('已添加', CartModel::add($goodsList));
  28 + }
  29 +
  30 + public function edit()
  31 + {
  32 + $params = $this->request->post();
  33 +
  34 + // 表单验证
  35 + $this->grouponValidate($params, get_class(), 'edit');
  36 +
  37 + $this->success('', CartModel::edit($params));
  38 + }
  39 +
  40 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\model\Category as CategoryModel;
  6 +use addons\groupon\model\Goods;
  7 +
  8 +/**
  9 + * 分类管理
  10 + *
  11 + * @icon fa fa-list
  12 + * @remark 用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加
  13 + */
  14 +
  15 +class Category extends Base
  16 +{
  17 + protected $noNeedLogin = ['*'];
  18 + protected $noNeedRight = ['*'];
  19 +
  20 +
  21 +
  22 + public function detail () {
  23 + $id = $this->request->get('id');
  24 +
  25 + $data = CategoryModel::getCategoryDetail($id);
  26 + $this->success('商城分类', $data);
  27 + }
  28 +
  29 + public function index()
  30 + {
  31 + $id = $this->request->get('id');
  32 + $data = CategoryModel::getCategoryList($id);
  33 + $this->success('商城分类', $data);
  34 +
  35 + }
  36 +
  37 +
  38 + // 获取指定 ids 分类列表
  39 + public function list()
  40 + {
  41 + $ids = $this->request->get('ids');
  42 + $data = CategoryModel::getCategoryListByIds($ids);
  43 + $this->success('商城分类', $data);
  44 + }
  45 +
  46 +
  47 + public function goods() {
  48 + $params = $this->request->get();
  49 + $category_id = $params['category_id'];
  50 +
  51 + $categories = CategoryModel::where('pid', $category_id)->where('status', 'normal')->select();
  52 +
  53 + // 获取这个分类下面的所有商品
  54 + $goodsList = Goods::getGoodsList($params, false);
  55 +
  56 + foreach($categories as $key => $category) {
  57 + $categoryIds = ',' . $category['id'] . ',';
  58 +
  59 + $currentCategoryGoods = [];
  60 + foreach ($goodsList as $k => $goods) {
  61 + $goodsCategoryIds = ',' . $goods['category_ids'] . ',';
  62 +
  63 + if (strpos($goodsCategoryIds, $categoryIds) !== false) {
  64 + $currentCategoryGoods[] = $goods;
  65 + }
  66 + }
  67 +
  68 + $categories[$key]['goods'] = $currentCategoryGoods;
  69 + }
  70 +
  71 + $this->success('商城分类商品', $categories);
  72 + }
  73 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class Coupons extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = ['lists', 'detail', 'goods'];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 + // 领券中心,自己的优惠券
  13 + public function index()
  14 + {
  15 + $type = $this->request->get('type');
  16 + $this->success('优惠券中心', \addons\groupon\model\Coupons::getCouponsList($type));
  17 + }
  18 +
  19 + public function lists()
  20 + {
  21 + $ids = $this->request->get('ids');
  22 + $this->success('优惠券列表', \addons\groupon\model\Coupons::getCouponsListByIds($ids));
  23 +
  24 + }
  25 +
  26 + public function get()
  27 + {
  28 + $id = $this->request->get('id');
  29 + $this->success('领取成功', \addons\groupon\model\Coupons::getCoupon($id));
  30 + }
  31 +
  32 + public function detail()
  33 + {
  34 + $id = $this->request->get('id');
  35 + $user_coupons_id = $this->request->get('user_coupons_id', 0);
  36 + $detail = \addons\groupon\model\Coupons::getCouponsDetail($id, $user_coupons_id);
  37 +
  38 + $this->success('优惠券详情', $detail);
  39 + }
  40 +
  41 + public function goods()
  42 + {
  43 + $id = $this->request->get('id');
  44 + $detail = \addons\groupon\model\Coupons::getGoodsByCoupons($id);
  45 +
  46 + $this->success('优惠券详情', $detail);
  47 + }
  48 +
  49 +
  50 +
  51 +
  52 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\model\Order;
  7 +use addons\groupon\model\User;
  8 +use think\Db;
  9 +use think\Log;
  10 +
  11 +class Express extends Base
  12 +{
  13 +
  14 + protected $noNeedLogin = ['callback'];
  15 + protected $noNeedRight = ['*'];
  16 +
  17 +
  18 + /**
  19 + * 物流信息订阅回调接口
  20 + */
  21 + public function callback()
  22 + {
  23 + $requestData = $this->request->post();
  24 +
  25 + $expressLib = new \addons\groupon\library\Express();
  26 +
  27 + // 信息记录日志
  28 + // \think\Log::write('expresscallback:'. json_encode($requestData));
  29 +
  30 + $data = json_decode(html_entity_decode($requestData['RequestData']), true);
  31 + $expressData = $data['Data'];
  32 +
  33 + foreach ($expressData as $key => $express) {
  34 + $order_code = $express['OrderCode'] ?? '';
  35 +
  36 + $orderExpress = null;
  37 + if (substr($order_code, 0, 1) == 'S') {
  38 + // 门店包裹
  39 + $orderExpress = \addons\groupon\model\OrderStoreExpress::where('code', $order_code)->find();
  40 + }
  41 +
  42 + if (!$orderExpress) {
  43 + // 包裹不存在,记录日志信息,然后继续下一个
  44 + \think\Log::write('orderExpressNotFound:' . json_encode($express));
  45 + continue;
  46 + }
  47 +
  48 + if (!$express['Success']) {
  49 + // 失败了
  50 + if (isset($express['Reason']) && ($express['Reason'] == '三天无轨迹' || $express['Reason'] == '七天无轨迹')) {
  51 + // 需要重新订阅
  52 + $expressLib->subscribe([
  53 + 'express_code' => $express['ShipperCode'],
  54 + 'express_no' => $express['LogisticCode'],
  55 + 'order_code' => $orderExpress->code ? $orderExpress->code : ''
  56 + ], $orderExpress, $orderExpress->order ?? null);
  57 + }
  58 +
  59 + \think\Log::write('orderExpressReason:' . json_encode($express));
  60 + continue;
  61 + }
  62 +
  63 + $expressLib->checkAndAddTraces($orderExpress, $express);
  64 + }
  65 +
  66 + $result = $expressLib->setPushResult(true);
  67 +
  68 + return json($result);
  69 + }
  70 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class Faq extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = ['*'];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 +
  13 + // faq 列表
  14 + public function index()
  15 + {
  16 + $this->success('获取成功', \addons\groupon\model\Faq::order('id', 'DESC')->paginate(10));
  17 + }
  18 +
  19 +
  20 + public function detail () {
  21 + $id = $this->request->get('id');
  22 +
  23 + $this->success('签到成功', \addons\groupon\model\Faq::where('id', $id)->find());
  24 + }
  25 +
  26 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class Feedback extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = ['type'];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 +
  13 + public function type()
  14 + {
  15 + $this->success('反馈类型', array_values(\addons\groupon\model\Feedback::$typeAll));
  16 + }
  17 +
  18 +
  19 + public function add() {
  20 + $params = $this->request->post();
  21 +
  22 + // 表单验证
  23 + $this->grouponValidate($params, get_class(), 'add');
  24 +
  25 + $this->success('反馈成功', \addons\groupon\model\Feedback::add($params));
  26 + }
  27 +
  28 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use PhpOffice\PhpSpreadsheet\Spreadsheet;
  7 +use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  8 +use PhpOffice\PhpSpreadsheet\Style\Alignment;
  9 +use PhpOffice\PhpSpreadsheet\Style\Fill;
  10 +use PhpOffice\PhpSpreadsheet\Style\Border;
  11 +
  12 +
  13 +class Goods extends Base
  14 +{
  15 +
  16 + protected $noNeedLogin = ['index', 'detail', 'lists', 'activity', 'seckillList', 'grouponList', 'store'];
  17 + protected $noNeedRight = ['*'];
  18 +
  19 + public function index()
  20 + {
  21 +
  22 + }
  23 +
  24 + public function detail()
  25 + {
  26 + $id = $this->request->get('id');
  27 + $detail = \addons\groupon\model\Goods::getGoodsDetail($id);
  28 +
  29 + // 记录足记
  30 + \addons\groupon\model\UserView::addView($detail);
  31 +
  32 + $sku_price = $detail['sku_price']; // 处理过的规格
  33 + // tp bug json_encode 或者 toArray 的时候 sku_price 会重新查询数据库,导致被处理过的规格又还原回去了
  34 + $detail = json_decode(json_encode($detail), true);
  35 + $detail['sku_price'] = $sku_price;
  36 +
  37 + $this->success('商品详情', $detail);
  38 + }
  39 +
  40 + public function lists()
  41 + {
  42 + $params = $this->request->get();
  43 + $data = \addons\groupon\model\Goods::getGoodsList($params);
  44 +
  45 + $this->success('商品列表', $data);
  46 +
  47 + }
  48 +
  49 +
  50 +
  51 + public function favorite()
  52 + {
  53 + $params = $this->request->post();
  54 + $result = \addons\groupon\model\UserFavorite::edit($params);
  55 + $this->success($result ? '收藏成功' : '取消收藏', $result);
  56 + }
  57 +
  58 + public function favoriteList()
  59 + {
  60 + $data = \addons\groupon\model\UserFavorite::getGoodsList();
  61 + $this->success('商品收藏列表', $data);
  62 + }
  63 +
  64 +
  65 + public function viewDelete()
  66 + {
  67 + $params = $this->request->post();
  68 + $result = \addons\groupon\model\UserView::del($params);
  69 + $this->success('删除成功', $result);
  70 + }
  71 +
  72 +
  73 + public function viewList()
  74 + {
  75 + $data = \addons\groupon\model\UserView::getGoodsList();
  76 + $this->success('商品浏览列表', $data);
  77 + }
  78 +
  79 +
  80 +
  81 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class GoodsComment extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = ['index', 'type'];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 +
  13 + public function index()
  14 + {
  15 + $params = $this->request->get();
  16 +
  17 + $goodsComments = \addons\groupon\model\GoodsComment::getList($params);
  18 +
  19 + $this->success('评价详情', $goodsComments);
  20 + }
  21 +
  22 +
  23 + public function type() {
  24 + $goods_id = $this->request->get('goods_id', 0);
  25 +
  26 + $type = array_values(\addons\groupon\model\GoodsComment::$typeAll);
  27 +
  28 + foreach ($type as $key => $val) {
  29 + // 只查询 count 比查出来所有评论,在判断状态要快
  30 + $comment = \addons\groupon\model\GoodsComment::where('goods_id', $goods_id);
  31 + if ($val['code'] != 'all') {
  32 + $comment = $comment->{$val['code']}();
  33 + }
  34 + $comment = $comment->count();
  35 + $type[$key]['num'] = $comment;
  36 + }
  37 +
  38 + $this->success('筛选类型', $type);
  39 + }
  40 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\model\Config;
  6 +use think\Db;
  7 +use think\Config as FaConfig;
  8 +use fast\Random;
  9 +use think\exception\HttpResponseException;
  10 +
  11 +class Index extends Base
  12 +{
  13 +
  14 + protected $noNeedLogin = ['*'];
  15 + protected $noNeedRight = ['*'];
  16 +
  17 + public function index()
  18 + {
  19 + }
  20 +
  21 + //初始化商城数据 服务器压力大可以把最后的data数据存入cache缓存中来调用,防止多次查sql
  22 + public function init()
  23 + {
  24 + $params = $this->request->get();
  25 + $platform = $this->request->header('platform');
  26 +
  27 + $config = new \addons\groupon\model\Config;
  28 +
  29 + $platformConfig = json_decode($config->where(['name' => $platform])->value('value'), true);
  30 + $paymentConfig = $config->where('group', 'payment')->select();
  31 + $payment = [];
  32 + foreach ($paymentConfig as $key => $config) {
  33 + $val = json_decode($config->value, true);
  34 + if ($val && in_array($platform, $val['platform'])) {
  35 + $payment[] = $config->name;
  36 + }
  37 + }
  38 +
  39 + // 处理微信支付 v3
  40 + $pay_version = pay_version();
  41 + if ($pay_version == 'v3' && in_array('wechatv3', $payment) && !in_array('wechat', $payment)) {
  42 + $payment[] = 'wechat';
  43 + };
  44 + $data['payment'] = $payment; // 平台支持的支付方式
  45 +
  46 + $groupon = json_decode(Config::where(['name' => 'groupon'])->value('value'), true);
  47 + $data['info'] = $groupon;
  48 + $data['info']['logo'] = cdnurl($data['info']['logo'], true);
  49 + $autologin = false;
  50 + if (isset($platformConfig['auto_login']) && $platformConfig['auto_login'] == 1) {
  51 + $autologin = true;
  52 + }
  53 + $data['wechat'] = [
  54 + 'appid' => isset($platformConfig['app_id']) ? $platformConfig['app_id'] : '',
  55 + 'autologin' => $autologin,
  56 + ];
  57 + $share = json_decode(Config::where(['name' => 'share'])->value('value'), true);
  58 +
  59 + $data['share'] = [
  60 + 'title' => $share['title'],
  61 + 'image' => isset($share['image']) ? cdnurl($share['image'], true) : '',
  62 + 'goods_poster_bg' => isset($share['goods_poster_bg']) ? cdnurl($share['goods_poster_bg'], true) : '',
  63 + 'user_poster_bg' => isset($share['user_poster_bg']) ? cdnurl($share['user_poster_bg'], true) : '',
  64 + 'groupon_poster_bg' => isset($share['groupon_poster_bg']) ? cdnurl($share['groupon_poster_bg'], true) : '',
  65 + 'store_poster_bg' => isset($share['store_poster_bg']) ? cdnurl($share['store_poster_bg'], true) : '',
  66 + ];
  67 + $data['addons'] = array_keys(get_addon_list());
  68 + $data['chat'] = json_decode(Config::where(['name' => 'chat'])->value('value'), true);
  69 + $this->success('初始化数据', $data);
  70 +
  71 + }
  72 +
  73 + //商城模板数据
  74 + public function template()
  75 + {
  76 + $get = $this->request->get();
  77 + $platform = $this->request->header('platform');
  78 + if (isset($get['shop_id'])) {
  79 + $template = \addons\groupon\model\Decorate::getCurrentPlatformDecorate('preview', $get['shop_id']);
  80 + } else {
  81 + $template = \addons\groupon\model\Decorate::getCurrentPlatformDecorate($platform);
  82 + }
  83 + $this->success('模板数据', $template);
  84 + }
  85 +
  86 + //自定义页面
  87 + public function custom()
  88 + {
  89 + $get = $this->request->get();
  90 + $decorate = \addons\groupon\model\Decorate::get($get['id']);
  91 + if (!$decorate) {
  92 + $this->error('未找到自定义页面');
  93 + }
  94 + $decorate->template = \addons\groupon\model\Decorate::getCustomDecorate($get['id']);
  95 + $this->success('自定义模板数据', $decorate);
  96 + }
  97 +
  98 + //富文本详情
  99 + public function richtext()
  100 + {
  101 + $id = $this->request->get('id');
  102 + $data = \addons\groupon\model\Richtext::get(['id' => $id]);
  103 + $this->success($data->title, $data);
  104 + }
  105 +
  106 + //同步前端所有页面链接
  107 + public function asyncLink()
  108 + {
  109 + $post = $this->request->post();
  110 + $newLink = $post['data'];
  111 + $existLink = Db::name('groupon_link')->select();
  112 + $newLinkPath = array_column($newLink, 'path');
  113 + $existLinkPath = array_flip(array_column($existLink, 'path'));
  114 + $insertData = [];
  115 + $count = 1;
  116 + foreach ($newLinkPath as $key => $item) {
  117 + if (!isset($existLinkPath[$item]) && isset($newLink[$key]['meta']['async']) && $newLink[$key]['meta']['async']) {
  118 + $insertData[] = [
  119 + 'name' => isset($newLink[$key]['meta']['title']) ? $newLink[$key]['meta']['title'] : '新链接' . $count,
  120 + 'path' => $item,
  121 + 'group' => isset($newLink[$key]['meta']['group']) ? $newLink[$key]['meta']['group'] : '其它',
  122 + 'createtime' => time(),
  123 + 'updatetime' => time()
  124 + ];
  125 + $count++;
  126 + }
  127 + }
  128 + if ($insertData !== []) {
  129 + Db::name('groupon_link')->insertAll($insertData);
  130 + }
  131 + }
  132 +
  133 +
  134 + //店铺装修 截图商城首页
  135 + public function asyncDecorateScreenShot()
  136 + {
  137 + $params = $this->request->post();
  138 + \addons\groupon\model\Decorate::asyncDecorateScreenShot($params);
  139 + }
  140 +
  141 + /**
  142 + * 上传文件
  143 + * @ApiMethod (POST)
  144 + * @param File $file 文件流
  145 + */
  146 + public function upload()
  147 + {
  148 + $file = $this->request->file('file');
  149 + if (empty($file)) {
  150 + $this->error(__('No file upload or server upload limit exceeded'));
  151 + }
  152 +
  153 + //判断是否已经存在附件
  154 + $sha1 = $file->hash();
  155 +
  156 + $upload = FaConfig::get('upload');
  157 +
  158 + preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
  159 + $type = strtolower($matches[2]);
  160 + $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
  161 + $size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
  162 + $fileInfo = $file->getInfo();
  163 + $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
  164 + $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
  165 +
  166 + $mimetypeArr = explode(',', strtolower($upload['mimetype']));
  167 + $typeArr = explode('/', $fileInfo['type']);
  168 +
  169 + //禁止上传PHP和HTML文件
  170 + if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) {
  171 + $this->error(__('Uploaded file format is limited'));
  172 + }
  173 + //验证文件后缀
  174 + if ($upload['mimetype'] !== '*' &&
  175 + (
  176 + !in_array($suffix, $mimetypeArr)
  177 + || (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))
  178 + )
  179 + ) {
  180 + $this->error(__('Uploaded file format is limited'));
  181 + }
  182 + //验证是否为图片文件
  183 + $imagewidth = $imageheight = 0;
  184 + if (in_array($fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) {
  185 + $imgInfo = getimagesize($fileInfo['tmp_name']);
  186 + if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
  187 + $this->error(__('Uploaded file is not a valid image'));
  188 + }
  189 + $imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
  190 + $imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
  191 + }
  192 +
  193 + // 文件 md5
  194 + $fileMd5 = md5_file($fileInfo['tmp_name']);
  195 +
  196 + $replaceArr = [
  197 + '{year}' => date("Y"),
  198 + '{mon}' => date("m"),
  199 + '{day}' => date("d"),
  200 + '{hour}' => date("H"),
  201 + '{min}' => date("i"),
  202 + '{sec}' => date("s"),
  203 + '{random}' => Random::alnum(16),
  204 + '{random32}' => Random::alnum(32),
  205 + '{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'],
  206 + '{suffix}' => $suffix,
  207 + '{.suffix}' => $suffix ? '.' . $suffix : '',
  208 + '{filemd5}' => $fileMd5,
  209 + ];
  210 + $savekey = $upload['savekey'];
  211 + $savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
  212 +
  213 + $uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
  214 + $fileName = substr($savekey, strripos($savekey, '/') + 1);
  215 + //
  216 +
  217 + if (in_array($upload['storage'], ['cos', 'alioss', 'qiniu'])) { // upyun:又拍云 ,bos:百度BOS,ucloud: Ucloud, 如果要使用这三种,请自行安装插件配置,并将标示填入前面数组,进行测试
  218 + $token_name = $upload['storage'] . 'token'; // costoken, aliosstoken, qiniutoken
  219 + $controller_name = '\\addons\\' . $upload['storage'] . '\\controller\\Index';
  220 +
  221 + $storageToken[$token_name] = $upload['multipart'] && $upload['multipart'][$token_name] ? $upload['multipart'][$token_name] : '';
  222 + $domain = request()->domain();
  223 +
  224 + try {
  225 + $uploadCreate = \think\Request::create('foo', 'POST', array_merge([
  226 + 'key' => $savekey,
  227 + 'name' => $fileInfo['name'],
  228 + 'md5' => $fileMd5,
  229 + 'chunk' => 0,
  230 + ], $storageToken));
  231 +
  232 + // 重新设置跨域允许域名
  233 + $cors = config('fastadmin.cors_request_domain');
  234 + config('fastadmin.cors_request_domain', $cors . ',' . $domain);
  235 +
  236 + $uploadController = new $controller_name($uploadCreate);
  237 + $uploadController->upload();
  238 + } catch (HttpResponseException $e) {
  239 + $result = $e->getResponse()->getData();
  240 + if (isset($result['code']) && $result['code'] == 0) {
  241 + $this->error($result['msg']);
  242 + }
  243 +
  244 + $resultData = $result['data'];
  245 + }
  246 + } else {
  247 + $splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
  248 +
  249 + if ($splInfo) {
  250 + $resultData = [
  251 + 'url' => $uploadDir . $splInfo->getSaveName(),
  252 + 'fullurl' => request()->domain() . $uploadDir . $splInfo->getSaveName()
  253 + ];
  254 + } else {
  255 + // 上传失败获取错误信息
  256 + $this->error($file->getError());
  257 + }
  258 + }
  259 +
  260 + $params = array(
  261 + 'admin_id' => 0,
  262 + 'user_id' => (int)$this->auth->id,
  263 + 'filename' => substr(htmlspecialchars(strip_tags($fileInfo['name'])), 0, 100),
  264 + 'filesize' => $fileInfo['size'],
  265 + 'imagewidth' => $imagewidth,
  266 + 'imageheight' => $imageheight,
  267 + 'imagetype' => $suffix,
  268 + 'imageframes' => 0,
  269 + 'mimetype' => $fileInfo['type'] == 'application/octet-stream' && in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp']) ? 'image/' . $suffix : $fileInfo['type'],
  270 + 'url' => $resultData['url'],
  271 + 'uploadtime' => time(),
  272 + 'storage' => $upload['storage'],
  273 + 'sha1' => $sha1,
  274 + );
  275 + $attachment = new \app\common\model\Attachment;
  276 + $attachment->data(array_filter($params));
  277 + $attachment->save();
  278 + \think\Hook::listen("upload_after", $attachment);
  279 + $this->success(__('Upload successful'), $resultData);
  280 + }
  281 +
  282 +
  283 +
  284 + /**
  285 + * 上传 base64 图片
  286 + * @ApiMethod (POST)
  287 + * @param base64 内容
  288 + */
  289 + public function uploadBase64()
  290 + {
  291 + $content = $this->request->post('content', '');
  292 +
  293 + if (empty($content) || !preg_match('/^(data:\s*image\/(\w+);base64,)/', $content, $matches)) {
  294 + $this->error(__('No file upload or server upload limit exceeded'));
  295 + }
  296 +
  297 + $upload = FaConfig::get('upload');
  298 +
  299 + $suffix = $matches[2]; //图片后缀
  300 +
  301 + $mimetypeArr = explode(',', strtolower($upload['mimetype']));
  302 +
  303 + // 判断是否允许的后缀
  304 + if ($upload['mimetype'] !== '*' && !in_array($suffix, $mimetypeArr)) {
  305 + $this->error(__('Uploaded file format is limited'));
  306 + }
  307 +
  308 + $replaceArr = [
  309 + '{year}' => date("Y"),
  310 + '{mon}' => date("m"),
  311 + '{day}' => date("d"),
  312 + '{hour}' => date("H"),
  313 + '{min}' => date("i"),
  314 + '{sec}' => date("s"),
  315 + '{random}' => Random::alnum(16),
  316 + '{random32}' => Random::alnum(32),
  317 + '{filename}' => "店铺装修截图",
  318 + '{suffix}' => $suffix,
  319 + '{.suffix}' => $suffix ? '.' . $suffix : '',
  320 + '{filemd5}' => md5($content),
  321 + ];
  322 + $savekey = $upload['savekey'];
  323 + $savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
  324 +
  325 + $uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
  326 + $fileName = substr($savekey, strripos($savekey, '/') + 1);
  327 + $fullDir = ROOT_PATH . '/public' . $uploadDir;
  328 +
  329 + $uploadPath = $uploadDir . $fileName;
  330 + $fullPath = $fullDir . $fileName;
  331 +
  332 + if (!is_dir($fullDir)) {
  333 + mkdir($fullDir, 0777, true);
  334 + }
  335 +
  336 + $imageContent = base64_decode(str_replace($matches[1], '', $content));
  337 + if (file_put_contents($fullPath, $imageContent)) {
  338 + $imgInfo = getimagesize($fullPath);
  339 +
  340 + $imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : 0;
  341 + $imageheight = isset($imgInfo[1]) ? $imgInfo[1] : 0;
  342 +
  343 + $params = array(
  344 + 'admin_id' => 0,
  345 + 'user_id' => (int) $this->auth->id,
  346 + 'filesize' => strlen($content),
  347 + 'imagewidth' => $imagewidth,
  348 + 'imageheight' => $imageheight,
  349 + 'imagetype' => $suffix,
  350 + 'imageframes' => 0,
  351 + 'mimetype' => isset($imgInfo['mime']) ? $imgInfo['mime'] : 'image/' . $suffix,
  352 + 'url' => $uploadPath,
  353 + 'uploadtime' => time(),
  354 + 'storage' => 'local',
  355 + 'sha1' => hash_file('sha1', $fullPath),
  356 + );
  357 + $attachment = new \app\common\model\Attachment;
  358 + $attachment->data(array_filter($params));
  359 + $attachment->save();
  360 +
  361 + \think\Hook::listen("upload_after", $attachment);
  362 + $this->success(__('Upload successful'), [
  363 + 'url' => $uploadPath,
  364 + 'full_url' => request()->domain() . $uploadPath
  365 + ]);
  366 + } else {
  367 + $this->error("文件上传失败");
  368 + }
  369 + }
  370 +
  371 + public function debugLog()
  372 + {
  373 + $params = $this->request->post();
  374 + \think\Log::write($params, 'notice');
  375 + }
  376 +
  377 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\library\Wechat;
  6 +use addons\groupon\exception\Exception;
  7 +use think\Cache;
  8 +
  9 +class Live extends Base
  10 +{
  11 +
  12 + protected $noNeedLogin = ['*'];
  13 + protected $noNeedRight = ['*'];
  14 +
  15 +
  16 + public function index()
  17 + {
  18 + $params = $this->request->get();
  19 +
  20 + $type = $params['type'] ?? 'all';
  21 + $ids = array_filter(isset($params['ids']) ? explode(',', $params['ids']) : []);
  22 +
  23 + if (!in_array($type, ['all', 'notice', 'living', 'lived'])) {
  24 + $this->error('参数错误');
  25 + }
  26 +
  27 + // 同步直播
  28 + \addons\groupon\model\Live::autoSyncLive();
  29 +
  30 + if ($type != 'all') {
  31 + $lives = \addons\groupon\model\Live::{$type}()
  32 + ->with('goods')
  33 + ->order('id', 'desc')
  34 + ->paginate(10);
  35 + } else {
  36 + $lives = \addons\groupon\model\Live::order('live_status', 'asc')
  37 + ->with('goods')
  38 + ->order('id', 'desc');
  39 +
  40 + if (isset($params['ids'])) {
  41 + // 首页根据 id 获取
  42 + $lives = $lives->where('id', 'in', $ids)->select();
  43 + } else {
  44 + // 直播列表
  45 + $lives = $lives->paginate(10);
  46 + }
  47 + }
  48 +
  49 + $this->success('获取成功', $lives);
  50 + }
  51 +
  52 +
  53 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\model\NotificationConfig;
  7 +use think\Cache;
  8 +
  9 +
  10 +class Notification extends Base
  11 +{
  12 +
  13 + protected $noNeedLogin = ['*'];
  14 + protected $noNeedRight = ['*'];
  15 +
  16 +
  17 + public function template()
  18 + {
  19 + // 获取小程序模板消息所有配置
  20 + $notificationConfig = NotificationConfig::cache(300)->where('platform', 'wxMiniProgram')->select();
  21 +
  22 + $templates = [];
  23 + foreach ($notificationConfig as $k => $config) {
  24 + if ($config['status'] && $config['content_arr'] && $config['content_arr']['template_id']) {
  25 + $templates[$config['event']] = $config['content_arr']['template_id'];
  26 + }
  27 + }
  28 +
  29 + $this->success('获取成功', $templates);
  30 + }
  31 +
  32 +
  33 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class Order extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = ['remind'];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 +
  13 + public function index()
  14 + {
  15 + $params = $this->request->get();
  16 +
  17 + $this->success('订单列表', \addons\groupon\model\Order::getList($params));
  18 + }
  19 +
  20 +
  21 +
  22 + public function detail()
  23 + {
  24 + $params = $this->request->get();
  25 + $this->success('订单详情', \addons\groupon\model\Order::detail($params));
  26 + }
  27 +
  28 +
  29 + public function itemDetail()
  30 + {
  31 + $params = $this->request->get();
  32 + $this->success('订单商品', \addons\groupon\model\Order::itemDetail($params));
  33 + }
  34 +
  35 + //
  36 + public function statusNum () {
  37 + $this->success('订单数量', \addons\groupon\model\Order::statusNum());
  38 + }
  39 +
  40 +
  41 + // 取消订单
  42 + public function cancel()
  43 + {
  44 + $params = $this->request->post();
  45 +
  46 + // 表单验证
  47 + $this->grouponValidate($params, get_class(), 'cancel');
  48 +
  49 + $this->success('取消成功', \addons\groupon\model\Order::operCancel($params));
  50 + }
  51 +
  52 + // 删除订单
  53 + public function delete()
  54 + {
  55 + $params = $this->request->post();
  56 +
  57 + // 表单验证
  58 + $this->grouponValidate($params, get_class(), 'delete');
  59 +
  60 + $this->success('删除成功', \addons\groupon\model\Order::operDelete($params));
  61 + }
  62 +
  63 + // 确认收货订单
  64 + public function confirm()
  65 + {
  66 + repeat_filter(); // 防抖
  67 + $params = $this->request->post();
  68 +
  69 + // 表单验证
  70 + $this->grouponValidate($params, get_class(), 'confirm');
  71 +
  72 + $this->success('收货成功', \addons\groupon\model\Order::operConfirm($params));
  73 + }
  74 +
  75 +
  76 + // 确认收货订单商品
  77 + public function confirmItem()
  78 + {
  79 + $params = $this->request->post();
  80 +
  81 + // 表单验证
  82 + $this->grouponValidate($params, get_class(), 'confirm_item');
  83 +
  84 + $this->success('收货成功', \addons\groupon\model\Order::operConfirmItem($params));
  85 + }
  86 +
  87 +
  88 + public function comment ()
  89 + {
  90 + $params = $this->request->post();
  91 +
  92 + // 表单验证
  93 + $this->grouponValidate($params, get_class(), 'comment');
  94 +
  95 + $this->success('评价成功', \addons\groupon\model\Order::operComment($params));
  96 + }
  97 +
  98 +
  99 + public function pre()
  100 + {
  101 + $params = $this->request->post();
  102 +
  103 + // 表单验证
  104 + $this->grouponValidate($params, get_class(), 'pre');
  105 +
  106 + $result = \addons\groupon\model\Order::pre($params);
  107 +
  108 + $this->success('计算成功', $result);
  109 + }
  110 +
  111 +
  112 + public function createOrder()
  113 + {
  114 + repeat_filter(); // 防抖
  115 + $params = $this->request->post();
  116 +
  117 + // 表单验证
  118 + $this->grouponValidate($params, get_class(), 'createOrder');
  119 +
  120 + $order = \addons\groupon\model\Order::createOrder($params);
  121 +
  122 + $this->success('订单添加成功', $order);
  123 + }
  124 +
  125 +
  126 + // 获取可用优惠券列表
  127 + public function coupons () {
  128 + $params = $this->request->post();
  129 +
  130 + // 表单验证
  131 + $this->grouponValidate($params, get_class(), 'coupons');
  132 +
  133 + $coupons = \addons\groupon\model\Order::coupons($params);
  134 +
  135 + $this->success('获取成功', $coupons);
  136 + }
  137 +
  138 +
  139 + public function remind() {
  140 + // 查询最近三天的 20 个订单,的用户头像昵称
  141 + $reminds = \addons\groupon\model\Order::getRemind();
  142 +
  143 + $this->success('获取成功', $reminds);
  144 + }
  145 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\epay\library\Service;
  6 +use fast\Random;
  7 +use think\addons\Controller;
  8 +use addons\groupon\exception\Exception;
  9 +use addons\groupon\model\Order;
  10 +use addons\groupon\model\User;
  11 +use think\Db;
  12 +use think\Log;
  13 +use Yansongda\Pay\Pay as YansongdaPay;
  14 +
  15 +class Pay extends Base
  16 +{
  17 +
  18 + protected $noNeedLogin = ['prepay', 'notifyx', 'notifyr', 'alipay'];
  19 + protected $noNeedRight = ['*'];
  20 +
  21 +
  22 + /**
  23 + * 支付宝网页支付
  24 + */
  25 + public function alipay()
  26 + {
  27 + $pay_version = pay_version(); // 获取微信支付版本
  28 +
  29 + $order_sn = $this->request->get('order_sn');
  30 + $platform = $this->request->get('platform');
  31 +
  32 + $order = Order::where('order_sn', $order_sn)->find();
  33 +
  34 + try {
  35 + if (!$order) {
  36 + throw new \Exception("订单不存在");
  37 + }
  38 + if ($order->status > 0) {
  39 + throw new \Exception("订单已支付");
  40 + }
  41 + if ($order->status < 0) {
  42 + throw new \Exception("订单已失效");
  43 + }
  44 +
  45 + $order_data = [
  46 + 'order_id' => $order->id,
  47 + 'out_trade_no' => $order->order_sn,
  48 + 'total_fee' => $order->total_fee,
  49 + 'subject' => '商城订单支付',
  50 + ];
  51 +
  52 + if ($pay_version == 'v3') {
  53 + $payService = new \addons\groupon\library\payv3\PayServiceV3('alipay', $platform);
  54 + $result = $payService->pay($order_data, [], 'url');
  55 +
  56 + $result = $result->getBody()->getContents();
  57 + } else {
  58 + $notify_url = $this->request->root(true) . '/addons/groupon/pay/notifyx/payment/alipay/platform/H5';
  59 + $pay = new \addons\groupon\library\PayService('alipay', 'url', $notify_url);
  60 + $result = $pay->create($order_data);
  61 +
  62 + $result = $result->getContent();
  63 + }
  64 +
  65 + $result = $pay->create($order_data);
  66 +
  67 + $result = $result->getContent();
  68 +
  69 + echo $result;
  70 + } catch (\Exception $e) {
  71 + echo $e->getMessage();
  72 + }
  73 +
  74 + // $this->assign('result', $result);
  75 +
  76 + // return $this->view->fetch();
  77 + }
  78 +
  79 +
  80 + /**
  81 + * 拉起支付
  82 + */
  83 + public function prepay()
  84 + {
  85 + $user = User::info();
  86 + $pay_version = pay_version(); // 获取微信支付版本
  87 +
  88 + $order_sn = $this->request->post('order_sn');
  89 + $payment = $this->request->post('payment');
  90 + $openid = $this->request->post('openid', '');
  91 + $platform = request()->header('platform');
  92 +
  93 + $order = Order::nopay()->where('user_id', $user->id)->where('order_sn', $order_sn)->find();
  94 +
  95 + if (!$order) {
  96 + $this->error("订单不存在");
  97 + }
  98 +
  99 + if (in_array($order->status, [$order::STATUS_INVALID, $order::STATUS_CANCEL])) {
  100 + $this->error("订单已失效");
  101 + }
  102 +
  103 + if (!$payment || !in_array($payment, ['wechat', 'alipay', 'wallet'])) {
  104 + $this->error("支付类型不能为空");
  105 + }
  106 +
  107 + if ($payment == 'wallet') {
  108 + // 余额支付
  109 + $this->walletPay($order, $payment, $platform);
  110 + }
  111 +
  112 + $order_data = [
  113 + 'out_trade_no' => $order->order_sn,
  114 + 'total_fee' => $order->total_fee,
  115 + ];
  116 +
  117 + // 微信公众号,小程序支付,必须有 openid
  118 + if ($payment == 'wechat') {
  119 + if (in_array($platform, ['wxOfficialAccount', 'wxMiniProgram'])) {
  120 + if (isset($openid) && $openid) {
  121 + // 如果传的有 openid
  122 + $order_data['openid'] = $openid;
  123 + } else {
  124 + // 没有 openid 默认拿下单人的 openid
  125 + $oauth = \addons\groupon\model\UserOauth::where([
  126 + 'user_id' => $order->user_id,
  127 + 'provider' => 'Wechat',
  128 + 'platform' => $platform
  129 + ])->find();
  130 +
  131 + $order_data['openid'] = $oauth ? $oauth->openid : '';
  132 + }
  133 +
  134 + if (empty($order_data['openid'])) {
  135 + // 缺少 openid
  136 + return $this->success('缺少 openid', 'no_openid');
  137 + }
  138 + }
  139 +
  140 + $order_data['body'] = '商城订单支付';
  141 + } else {
  142 + $order_data['subject'] = '商城订单支付';
  143 + }
  144 +
  145 + try {
  146 + if ($pay_version == 'v3') {
  147 + $payService = new \addons\groupon\library\payv3\PayServiceV3($payment); // 回调地址再 payServiceV3 中拼接
  148 + $result = $payService->pay($order_data);
  149 + } else {
  150 + $notify_url = $this->request->root(true) . '/addons/groupon/pay/notifyx/payment/' . $payment . '/platform/' . $platform;
  151 + $pay = new \addons\groupon\library\PayService($payment, $platform, $notify_url);
  152 + $result = $pay->create($order_data);
  153 + }
  154 + } catch (\Exception $e) {
  155 + $this->error("支付配置错误:" . $e->getMessage());
  156 + }
  157 +
  158 + if ($platform == 'App') {
  159 + if ($pay_version == 'v3') {
  160 + if ($payment == 'wechat') {
  161 + // Yansongda\Supports\Collection,可当数组,可当字符串, 因为前端会 JSON.parse 所以这里提前转字符串
  162 + $result = json_encode($result);
  163 + } else {
  164 + $result = $result->getBody()->getContents();
  165 + }
  166 + } else {
  167 + $result = $result->getContent();
  168 + }
  169 + }
  170 + if ($platform == 'H5' && $payment == 'wechat') {
  171 + if ($pay_version == 'v3') {
  172 + $result = [
  173 + 'h5_url' => $result->h5_url
  174 + ];
  175 + } else {
  176 + $result = $result->getContent();
  177 + }
  178 + }
  179 +
  180 + return $this->success('获取预付款成功', [
  181 + 'pay_data' => $result,
  182 + ]);
  183 + }
  184 +
  185 +
  186 +
  187 + // 余额支付
  188 + public function walletPay($order, $type, $method)
  189 + {
  190 + $order = Db::transaction(function () use ($order, $type, $method) {
  191 + // 重新加锁读,防止连点问题
  192 + $order = Order::nopay()->where('order_sn', $order->order_sn)->lock(true)->find();
  193 + if (!$order) {
  194 + $this->error("订单已支付");
  195 + }
  196 + $total_fee = $order->total_fee;
  197 +
  198 + // 扣除余额
  199 + $user = User::info();
  200 +
  201 + if (is_null($user)) {
  202 + // 没有登录,请登录
  203 + $this->error(__('Please login first'), null, 401);
  204 + }
  205 +
  206 + User::money(-$total_fee, $user->id, 'wallet_pay', $order->id, '', [
  207 + 'order_id' => $order->id,
  208 + 'order_sn' => $order->order_sn,
  209 + ]);
  210 +
  211 + // 支付后流程
  212 + $notify = [
  213 + 'order_sn' => $order['order_sn'],
  214 + 'transaction_id' => '',
  215 + 'notify_time' => date('Y-m-d H:i:s'),
  216 + 'buyer_email' => $user->id,
  217 + 'pay_fee' => $order->total_fee,
  218 + 'pay_type' => 'wallet' // 支付方式
  219 + ];
  220 + $notify['payment_json'] = json_encode($notify);
  221 + $order->paymentProcess($order, $notify);
  222 +
  223 + return $order;
  224 + });
  225 +
  226 + $this->success('支付成功', $order);
  227 + }
  228 +
  229 +
  230 + /**
  231 + * 支付成功回调
  232 + */
  233 + public function notifyx()
  234 + {
  235 + Log::write('notifyx-comein:');
  236 +
  237 + $pay_version = pay_version(); // 获取微信支付版本
  238 +
  239 + $payment = $this->request->param('payment');
  240 + $platform = $this->request->param('platform');
  241 +
  242 + if ($pay_version == 'v3') {
  243 + $pay = new \addons\groupon\library\payv3\PayServiceV3($payment, $platform);
  244 + } else {
  245 + $pay = new \addons\groupon\library\PayService($payment, $platform);
  246 + }
  247 +
  248 + $result = $pay->notify(function ($data, $pay = null) use ($payment, $pay_version) {
  249 + Log::write('notifyx-result:' . json_encode($data));
  250 + // { // 微信回调参数
  251 + // "appid":"wx39cd0799d4567dd0",
  252 + // "bank_type":"OTHERS",
  253 + // "cash_fee":"1",
  254 + // "fee_type":"CNY",
  255 + // "is_subscribe":"N",
  256 + // "mch_id":"1481069012",
  257 + // "nonce_str":"dPpcZ6AzCDU8daNC",
  258 + // "openid":"oD9ko4x7QMDQPZEuN8V5jtZjie3g",
  259 + // "out_trade_no":"202010240834057843002700",
  260 + // "result_code":"SUCCESS",
  261 + // "return_code":"SUCCESS",
  262 + // "sign":"3103B6D06F13D2B3959C5B3F7F1FD61C",
  263 + // "time_end":"20200407102424",
  264 + // "total_fee":"1",
  265 + // "trade_type":"JSAPI",
  266 + // "transaction_id":"4200000479202004070804485027"
  267 + // }
  268 +
  269 + // { // 支付宝支付成功回调参数
  270 + // "gmt_create":"2020-04-10 19:15:00",
  271 + // "charset":"utf-8",
  272 + // "seller_email":"xptech@qq.com",
  273 + // "subject":"\u5546\u57ce\u8ba2\u5355\u652f\u4ed8",
  274 + // "sign":"AOiYZ1a2mEMOuIbHFCi6V6A0LJ97UMiHsCWgNdSU9dlzKFl15Ts8b0mL\/tN+Hhskl+94S3OUiNTBD3dD0Kv923SqaTWxNdj533PCdo2GDKsZIZgKbavnOvaccSKUdmQRE9KtmePPq9V9lFzEQvdUkKq1M8KAWO5K9LTy2iT2y5CUynpiu\/04GVzsTL9PqY+LDwqj6K+w7MgceWm1BWaFWg27AXIRw7wvsFckr3k9GGajgE2fufhoCYGYtGFbhGOp6ExtqS5RXBuPODOyRhBLpD8mwpOX38Oy0X+R4YQIrOi02dhqwPpvw79YjnvgXY3qZEQ66EdUsrT7EBdcPHK0Gw==",
  275 + // "buyer_id":"2088902485164146",
  276 + // "invoice_amount":"0.01",
  277 + // "notify_id":"2020041000222191501064141414240102",
  278 + // "fund_bill_list":"[{\"amount\":\"0.01\",\"fundChannel\":\"PCREDIT\"}]",
  279 + // "notify_type":"trade_status_sync",
  280 + // "trade_status":"TRADE_SUCCESS",
  281 + // "receipt_amount":"0.01",
  282 + // "buyer_pay_amount":"0.01",
  283 + // "app_id":"2021001114666742",
  284 + // "sign_type":"RSA2",
  285 + // "seller_id":"2088721922277739",
  286 + // "gmt_payment":"2020-04-10 19:15:00",
  287 + // "notify_time":"2020-04-10 19:15:01",
  288 + // "version":"1.0",
  289 + // "out_trade_no":"202007144778322770017000",
  290 + // "total_amount":"0.01",
  291 + // "trade_no":"2020041022001464141443020240",
  292 + // "auth_app_id":"2021001114666742",
  293 + // "buyer_logon_id":"157***@163.com",
  294 + // "point_amount":"0.00"
  295 + // }
  296 +
  297 + // { // 支付宝退款成功(交易关闭)回调参数
  298 + // "gmt_create": "2020-08-15 14:48:32",
  299 + // "charset": "utf-8",
  300 + // "seller_email": "xptech@qq.com",
  301 + // "gmt_payment": "2020-08-15 14:48:32",
  302 + // "notify_time": "2020-08-15 16:11:45",
  303 + // "subject": "商城订单支付",
  304 + // "gmt_refund": "2020-08-15 16:11:45.140",
  305 + // "sign": "b6ArkgzLIRteRL9FMGC6i\/jf6VwFYQbaGDGRx002W+pdmN5q9+O4edZ3ALF74fYaijSl9ksNr0dKdvanu3uYxBTcd\/GIS4N1CWzmCOv6pzgx5rO\/YvGoHLM3Yop0GKKuMxmnNsZ6jhYKEY7SYD3Y0L6PU9ZMdHV7yIiVj+zZmbKzUgK9MPDCEXs+nzpNAiSM8GTqYRSUvKobAK68hswG2k1QIcqr5z+ZmVYa\/nHHkoC9qXt5zwyGi4P+2eOsr6V2PjA3x8qqe7TN5aI1DeoZD5KqHPYYaYF17J2q6YPlgl3WUl1RhE7H86bivB1fIuYEv\/3+JR74WN\/o7krGw1RPHg==",
  306 + // "out_biz_no": "R202004114414846255015300",
  307 + // "buyer_id": "2088902485164146",
  308 + // "version": "1.0",
  309 + // "notify_id": "2020081500222161145064141453349793",
  310 + // "notify_type": "trade_status_sync",
  311 + // "out_trade_no": "202002460317545607015300",
  312 + // "total_amount": "0.01",
  313 + // "trade_status": "TRADE_CLOSED",
  314 + // "refund_fee": "0.01",
  315 + // "trade_no": "2020081522001464141438570535",
  316 + // "auth_app_id": "2021001114666742",
  317 + // "buyer_logon_id": "157***@163.com",
  318 + // "gmt_close": "2020-08-15 16:11:45",
  319 + // "app_id": "2021001114666742",
  320 + // "sign_type": "RSA2",
  321 + // "seller_id": "2088721922277739"
  322 + // }
  323 +
  324 + try {
  325 + $out_trade_no = $data['out_trade_no'];
  326 + $out_refund_no = $data['out_biz_no'] ?? '';
  327 +
  328 +
  329 +
  330 + // 判断是否是支付宝退款(支付宝退款成功会通知该接口)
  331 + if ($payment == 'alipay' // 支付宝支付
  332 + && $data['notify_type'] == 'trade_status_sync' // 同步交易状态
  333 + && $data['trade_status'] == 'TRADE_CLOSED' // 交易关闭
  334 + && $out_refund_no // 退款单号
  335 + ) {
  336 + // 退款回调
  337 + $this->refundFinish($out_trade_no, $out_refund_no);
  338 +
  339 + return $this->payResponse($pay, $payment);
  340 + }
  341 +
  342 +
  343 + // 判断支付宝微信是否是支付成功状态,如果不是,直接返回响应
  344 + if ($payment == 'alipay' && $data['trade_status'] != 'TRADE_SUCCESS') {
  345 + // 不是交易成功的通知,直接返回成功
  346 + return $this->payResponse($pay, $payment);
  347 + }
  348 +
  349 + if ($pay_version == 'v3') {
  350 + // 支付成功流程
  351 + $pay_fee = $data['pay_fee'];
  352 + } else {
  353 + if ($payment == 'wechat' && ($data['result_code'] != 'SUCCESS' || $data['return_code'] != 'SUCCESS')) {
  354 + // 微信交易未成功,返回 false,让微信再次通知
  355 + return false;
  356 + }
  357 +
  358 + // 支付成功流程
  359 + $pay_fee = $payment == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
  360 + }
  361 +
  362 + //你可以在此编写订单逻辑
  363 + $order = Order::where('order_sn', $out_trade_no)->find();
  364 +
  365 + if (!$order || $order->status > 0) {
  366 + // 订单不存在,或者订单已支付
  367 + return $this->payResponse($pay, $payment);
  368 + }
  369 +
  370 + Db::transaction(function () use ($order, $data, $payment, $pay_fee) {
  371 + $notify = [
  372 + 'order_sn' => $data['out_trade_no'],
  373 + 'transaction_id' => $payment == 'alipay' ? $data['trade_no'] : $data['transaction_id'],
  374 + 'notify_time' => date('Y-m-d H:i:s', (strtotime($data['time_end'] ?? $data['notify_time']))),
  375 + 'buyer_email' => $payment == 'alipay' ? $data['buyer_logon_id'] : $data['openid'],
  376 + 'payment_json' => json_encode($data),
  377 + 'pay_fee' => $pay_fee,
  378 + 'pay_type' => $payment // 支付方式
  379 + ];
  380 + $order->paymentProcess($order, $notify);
  381 + });
  382 +
  383 + return $this->payResponse($pay, $payment);
  384 + } catch (\Exception $e) {
  385 + Log::write('notifyx-error:' . json_encode($e->getMessage()));
  386 + }
  387 + });
  388 +
  389 + return $result;
  390 + }
  391 +
  392 +
  393 + /**
  394 + * 退款成功回调
  395 + */
  396 + public function notifyr()
  397 + {
  398 + Log::write('notifyreturn-comein:');
  399 +
  400 + $pay_version = pay_version(); // 获取微信支付版本
  401 + $payment = $this->request->param('payment');
  402 + $platform = $this->request->param('platform');
  403 +
  404 + if ($pay_version == 'v3') {
  405 + $pay = new \addons\groupon\library\payv3\PayServiceV3($payment, $platform);
  406 + } else {
  407 + $pay = new \addons\groupon\library\PayService($payment, $platform);
  408 + }
  409 +
  410 + $result = $pay->notifyRefund(function ($data, $pay = null) use ($payment, $platform) {
  411 + try {
  412 + $out_refund_no = $data['out_refund_no'];
  413 + $out_trade_no = $data['out_trade_no'];
  414 +
  415 + // 退款
  416 + $this->refundFinish($out_trade_no, $out_refund_no);
  417 +
  418 + return $this->payResponse($pay, $payment);
  419 + } catch (\Exception $e) {
  420 + Log::write('notifyreturn-error:' . json_encode($e->getMessage()));
  421 + return false;
  422 + }
  423 + });
  424 +
  425 + return $result;
  426 + }
  427 +
  428 +
  429 + private function refundFinish($out_trade_no, $out_refund_no)
  430 + {
  431 + $order = Order::where('order_sn', $out_trade_no)->find();
  432 + $refundLog = \app\admin\model\groupon\order\RefundLog::where('refund_sn', $out_refund_no)->find();
  433 +
  434 + if (!$order || !$refundLog || $refundLog->status != 0) {
  435 + // 订单不存在,或者订单已退款
  436 + return true;
  437 + }
  438 +
  439 + $item = \app\admin\model\groupon\order\OrderItem::where('id', $refundLog->order_item_id)->find();
  440 +
  441 + Db::transaction(function () use ($order, $item, $refundLog) {
  442 + \app\admin\model\groupon\order\Order::refundFinish($order, $item, $refundLog);
  443 + });
  444 +
  445 + return true;
  446 + }
  447 +
  448 +
  449 + /**
  450 + * v2, v3 响应转换
  451 + */
  452 + private function payResponse($pay = null, $payment = null)
  453 + {
  454 + $pay_version = pay_version(); // 获取微信支付版本
  455 +
  456 + if ($pay_version == 'v3') {
  457 + $result = YansongdaPay::$payment()->success();
  458 +
  459 + $content = $result->getBody()->getContents();
  460 + $content = $payment == 'wechat' ? json_decode($content, true) : $content;
  461 + return response($content, 200, [], ($payment == 'wechat' ? 'json' : ''));
  462 + } else {
  463 + return $pay->success()->send();
  464 + }
  465 +
  466 + return $pay_version == 'v3' ? YansongdaPay::$payment()->success() : $pay->success()->send();
  467 + }
  468 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\model\Share as ShareModel;
  6 +
  7 +class Share extends Base
  8 +{
  9 +
  10 + protected $noNeedLogin = [];
  11 + protected $noNeedRight = ['*'];
  12 +
  13 +
  14 + /**
  15 + * 获取分享记录
  16 + *
  17 + * @return void
  18 + */
  19 + public function index()
  20 + {
  21 + $params = $this->request->get();
  22 +
  23 + $shares = ShareModel::getList($params);
  24 + return $this->success('获取成功', $shares);
  25 + }
  26 +
  27 +
  28 + public function add()
  29 + {
  30 + $params = $this->request->post();
  31 + $params['platform'] = $this->request->header('platform');
  32 + $shareLog = ShareModel::add($params);
  33 + if ($shareLog) {
  34 + \think\Hook::listen('share_after', $shareLog);
  35 + $this->success('添加分享记录', $shareLog);
  36 + }
  37 +
  38 + $this->success('添加分享记录'); // 使用 success 前端不提示
  39 + }
  40 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use app\common\library\Sms as Smslib;
  6 +use addons\groupon\model\User;
  7 +use think\Hook;
  8 +
  9 +/**
  10 + * 手机短信接口
  11 + */
  12 +class Sms extends Base
  13 +{
  14 + protected $noNeedLogin = '*';
  15 + protected $noNeedRight = '*';
  16 +
  17 + /**
  18 + * 发送验证码
  19 + *
  20 + * @param string $mobile 手机号
  21 + * @param string $event 事件名称
  22 + */
  23 + public function send()
  24 + {
  25 + $mobile = $this->request->post("mobile");
  26 + $event = $this->request->post("event");
  27 + $event = $event ? $event : 'register';
  28 +
  29 + if (!$mobile || !\think\Validate::regex($mobile, "^1\d{10}$")) {
  30 + $this->error(__('手机号不正确'));
  31 + }
  32 + $last = Smslib::get($mobile, $event);
  33 + if ($last && time() - $last['createtime'] < 60) {
  34 + $this->error(__('发送频繁'));
  35 + }
  36 + $ipSendTotal = \app\common\model\Sms::where(['ip' => $this->request->ip()])->whereTime('createtime', '-1 hours')->count();
  37 + if ($ipSendTotal >= 5) {
  38 + $this->error(__('发送频繁'));
  39 + }
  40 + if ($event) {
  41 + $userinfo = User::getByMobile($mobile);
  42 + if ($event == 'register' && $userinfo) {
  43 + //已被注册
  44 + $this->error(__('已被注册'));
  45 + } elseif (in_array($event, ['changemobile']) && $userinfo) {
  46 + //被占用
  47 + $this->error(__('已被占用'));
  48 + } elseif (in_array($event, ['changepwd', 'resetpwd', 'mobilelogin']) && !$userinfo) {
  49 + //未注册
  50 + $this->error(__('未注册'));
  51 + }
  52 + }
  53 + if (!Hook::get('sms_send')) {
  54 + $this->error(__('请在后台插件管理安装短信验证插件'));
  55 + }
  56 + $ret = Smslib::send($mobile, null, $event);
  57 + if ($ret) {
  58 + $this->success(__('发送成功'));
  59 + } else {
  60 + $this->error(__('发送失败,请检查短信配置是否正确'));
  61 + }
  62 + }
  63 +
  64 + /**
  65 + * 检测验证码
  66 + *
  67 + * @param string $mobile 手机号
  68 + * @param string $event 事件名称
  69 + * @param string $captcha 验证码
  70 + */
  71 + public function check()
  72 + {
  73 + $mobile = $this->request->post("mobile");
  74 + $event = $this->request->post("event");
  75 + $event = $event ? $event : 'register';
  76 + $captcha = $this->request->post("captcha");
  77 +
  78 + if (!$mobile || !\think\Validate::regex($mobile, "^1\d{10}$")) {
  79 + $this->error(__('手机号不正确'));
  80 + }
  81 + if ($event) {
  82 + $userinfo = User::getByMobile($mobile);
  83 + if ($event == 'register' && $userinfo) {
  84 + //已被注册
  85 + $this->error(__('已被注册'));
  86 + } elseif (in_array($event, ['changemobile']) && $userinfo) {
  87 + //被占用
  88 + $this->error(__('已被占用'));
  89 + } elseif (in_array($event, ['changepwd', 'resetpwd']) && !$userinfo) {
  90 + //未注册
  91 + $this->error(__('未注册'));
  92 + }
  93 + }
  94 + $ret = Smslib::check($mobile, $captcha, $event);
  95 + if ($ret) {
  96 + $this->success(__('成功'));
  97 + } else {
  98 + $this->error(__('验证码不正确'));
  99 + }
  100 + }
  101 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\model\Store as ModelStore;
  7 +use addons\groupon\model\User;
  8 +use addons\groupon\model\UserStore;
  9 +
  10 +class Store extends Base
  11 +{
  12 +
  13 + protected $noNeedLogin = ['nearby', 'detail'];
  14 + protected $noNeedRight = ['*'];
  15 +
  16 +
  17 + public function index()
  18 + {
  19 + $user = User::info();
  20 + $userStore = UserStore::where('user_id', $user->id)->select();
  21 + $store_id_arr = array_column($userStore, 'store_id');
  22 +
  23 + $stores = [];
  24 + if ($store_id_arr) {
  25 + $stores = ModelStore::show()->where('id', 'in', $store_id_arr)->select();
  26 + }
  27 +
  28 + $this->success('获取自提点列表', $stores);
  29 + }
  30 +
  31 +
  32 +
  33 + public function nearby()
  34 + {
  35 + $params = $this->request->get();
  36 +
  37 + $stores = ModelStore::getStoreList($params);
  38 +
  39 + $this->success('获取自提点列表', $stores);
  40 + }
  41 +
  42 +
  43 + public function detail() {
  44 + $params = $this->request->get();
  45 +
  46 + $store = ModelStore::getStoreDetail($params);
  47 +
  48 + $this->success('获取自提点', $store);
  49 + }
  50 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use app\common\library\Ems;
  6 +use app\common\library\Sms;
  7 +use fast\Random;
  8 +use think\Validate;
  9 +use addons\groupon\library\Wechat;
  10 +use think\Db;
  11 +use addons\groupon\model\UserOauth;
  12 +use addons\groupon\model\User as UserModel;
  13 +use addons\groupon\model\UserStore;
  14 +
  15 +/**
  16 + * 会员接口
  17 + */
  18 +class User extends Base
  19 +{
  20 + protected $noNeedLogin = ['login', 'mobilelogin', 'accountLogin', 'register', 'resetpwd', 'changeemail', 'changemobile', 'third', 'wxMiniProgramOauth', 'getWxMiniProgramSessionKey', 'getUserDefaultFields'];
  21 + protected $noNeedRight = '*';
  22 +
  23 + public function _initialize()
  24 + {
  25 + return parent::_initialize();
  26 + }
  27 +
  28 + /**
  29 + * 会员中心
  30 + */
  31 + public function index()
  32 + {
  33 + $auth = \app\common\library\Auth::instance();
  34 + $auth->setAllowFields(['id', 'username', 'nickname', 'mobile', 'avatar', 'score', 'birthday', 'money', 'group', 'group_id', 'child_user_count', 'child_user_count_1', 'child_user_count_2', 'total_consume']);
  35 + $data = $auth->getUserinfo();
  36 + $data['avatar'] = $data['avatar'] ? cdnurl($data['avatar'], true) : '';
  37 + if (!isset($data['group'])) {
  38 + $data['group'] = \addons\groupon\model\UserGroup::get($data['group_id']);
  39 + }
  40 +
  41 + // 查询用户优惠券数量
  42 + $userCoupons = \addons\groupon\model\Coupons::getCouponsList(1);
  43 + $data['coupons_num'] = count($userCoupons);
  44 +
  45 + // 查询用户是否是门店管理员
  46 + $userStores = UserStore::where('user_id', $data['id'])->select();
  47 + $data['is_store'] = $userStores ? 1 : 0;
  48 + $data['store_id'] = 0;
  49 + if (count($userStores) == 1) {
  50 + // 只有一个店铺 直接进入店铺
  51 + $data['store_id'] = $userStores[0]['store_id'];
  52 + }
  53 +
  54 + $this->success('用户信息', $data);
  55 + }
  56 +
  57 + /**
  58 + * 会员登录
  59 + *
  60 + * @param string $account 账号
  61 + * @param string $password 密码
  62 + */
  63 + public function accountLogin()
  64 + {
  65 + $account = $this->request->post('account');
  66 + $password = $this->request->post('password');
  67 + if (!$account || !$password) {
  68 + $this->error(__('Invalid parameters'));
  69 + }
  70 + $ret = $this->auth->login($account, $password);
  71 + if ($ret) {
  72 + $data = ['userinfo' => $this->auth->getUserinfo()];
  73 + $this->success(__('Logged in successful'), $data);
  74 + } else {
  75 + $this->error($this->auth->getError());
  76 + }
  77 + }
  78 +
  79 + /**
  80 + * 获取微信小程序session_key
  81 + *
  82 + * @param string $code 加密code
  83 + * @param boolean $autoLogin 是否自动登录(需已注册会员)
  84 + */
  85 + public function getWxMiniProgramSessionKey()
  86 + {
  87 + $post = $this->request->post();
  88 + $autoLogin = $post['autoLogin'];
  89 + $wechat = new Wechat('wxMiniProgram');
  90 + $decryptSession = $wechat->code($post['code']);
  91 + if (!isset($decryptSession['session_key'])) {
  92 + $this->error('未获取session_key,请重启应用');
  93 + }
  94 + $uuid = Random::uuid();
  95 + \think\Cache::set($uuid, $decryptSession, 24 * 3600 * 30); // 强制30天过期
  96 + $userOauth = UserOauth::get([
  97 + 'provider' => 'Wechat',
  98 + 'platform' => 'wxMiniProgram',
  99 + 'openid' => $decryptSession['openid'],
  100 + 'user_id' => ['neq', 0]
  101 + ]);
  102 + if ($userOauth) {
  103 + $userOauth->save(['session_key' => $decryptSession['session_key']]);
  104 + }
  105 + if ($autoLogin && $userOauth) {
  106 + $ret = $this->auth->direct($userOauth->user_id);
  107 + if ($ret) {
  108 + $token = $this->auth->getToken();
  109 + $this->success(__('Logged in successful'), ['token' => $token, 'session_id' => $uuid, 'openid' => $decryptSession['openid']]);
  110 + } else {
  111 + $this->error($this->auth->getError());
  112 + }
  113 + }
  114 +
  115 + $this->success('', ['session_id' => $uuid]);
  116 + }
  117 + /**
  118 + * 微信小程序登录
  119 + *
  120 + * @param string $session_key session_key
  121 + * @param string $signature 校验签名
  122 + * @param string $iv 解密向量
  123 + * @param string $encryptedData 需解密完整用户信息
  124 + * @param boolean $refresh 重新获取或刷新最新的用户信息 (用户头像失效或微信客户端修改昵称等情况)
  125 + */
  126 + public function wxMiniProgramOauth()
  127 + {
  128 + $post = $this->request->post();
  129 + $post['encryptedData'] = $this->request->post('encryptedData', null, null);
  130 +
  131 + $token = Db::transaction(function () use ($post) {
  132 + try {
  133 + $wechat = new Wechat('wxMiniProgram');
  134 + $decryptSession = \think\Cache::get($post['session_id']);
  135 + if (!$decryptSession || !isset($decryptSession['openid'])) {
  136 + throw new \Exception('未获取到登录态,请重试');
  137 + }
  138 + $decryptUserInfo = $wechat->decryptData($decryptSession['session_key'], $post['iv'], $post['encryptedData']); // 客户端传值数据都不可信,需服务端解密用户信息
  139 + $decryptUserInfo = array_merge($decryptUserInfo, $decryptSession);
  140 + //组装decryptData
  141 + $decryptData = array_change_key_case($decryptUserInfo, CASE_LOWER);
  142 + if (empty($decryptData['openid'])) {
  143 + throw new \Exception('code错误,请重试');
  144 + }
  145 + $decryptData['headimgurl'] = $decryptData['avatarurl'];
  146 + $decryptData['sex'] = $decryptData['gender'];
  147 + $decryptData['session_key'] = $decryptSession['session_key'];
  148 + return $this->oauthLoginOrRegisterOrBindOrRefresh($post['event'], $decryptData, 'wxMiniProgram', 'Wechat');
  149 + } catch (\Exception $e) {
  150 + $this->error($e->getMessage());
  151 + }
  152 + });
  153 +
  154 + if ($token) {
  155 + $this->success(__('Logged in successful'), ['token' => $token]);
  156 + } else {
  157 + $this->error($this->auth->getError());
  158 + }
  159 + }
  160 +
  161 +
  162 + /**
  163 + * 微信小程序登录
  164 + *
  165 + * @param string $code 加密code
  166 + */
  167 + public function wxMiniProgramPhone()
  168 + {
  169 + $user = $this->auth->getUser();
  170 +
  171 + $post = $this->request->post();
  172 + $decryptSession = \think\Cache::get($post['session_id']);
  173 + $wechat = new Wechat('wxMiniProgram');
  174 + $decryptUserInfo = $wechat->decryptData($decryptSession['session_key'], $post['iv'], $post['encryptedData']);
  175 +
  176 + $phone = $decryptUserInfo['purePhoneNumber'];
  177 +
  178 + if (empty($phone)) {
  179 + $this->error('获取手机号授权失败');
  180 + }
  181 +
  182 + if ($post['type'] == 'bind') {
  183 + // 绑定手机号
  184 + if (\app\common\model\User::where('mobile', $phone)->where('id', '<>', $user->id)->find()) {
  185 + $this->error('手机号已存在');
  186 + }
  187 +
  188 + $verification = $user->verification;
  189 + $verification->mobile = 1;
  190 + $user->verification = $verification;
  191 + $user->mobile = $phone;
  192 + $user->save();
  193 + }
  194 +
  195 + $this->success("绑定成功", [
  196 + 'phone' => $phone
  197 + ]);
  198 + }
  199 +
  200 + /**
  201 + * 第三方登录或自动注册
  202 + *
  203 + * @param array $decryptData 解密参数
  204 + * @param string $platform 平台名称
  205 + * @param string $provider 厂商名称
  206 + * @param int $keeptime 有效时长
  207 + * @param boolean $refresh 刷新头像和昵称
  208 + */
  209 + private function oauthLoginOrRegisterOrBindOrRefresh($event, $decryptData, $platform, $provider, $keeptime = 0)
  210 + {
  211 + $oauthData = $decryptData;
  212 + $oauthData = array_merge($oauthData, [
  213 + 'provider' => $provider,
  214 + 'platform' => $platform,
  215 +
  216 + ]);
  217 + if ($platform === 'wxMiniProgram' || $platform === 'App') {
  218 + $oauthData['expire_in'] = 7200;
  219 + $oauthData['expiretime'] = time() + 7200;
  220 + }
  221 + $userOauth = UserOauth::where(['openid' => $decryptData['openid'], 'user_id' => ['neq', 0]])->where('platform', $platform)->where('provider', $provider)->lock(true)->find();
  222 + switch ($event) {
  223 + case 'login': // 登录(自动注册)
  224 + if (!$userOauth) { // 没有找到第三方登录信息 创建新用户
  225 + //默认创建新用户
  226 + $user_id = 0;
  227 + $createNewUser = true;
  228 + $oauthData['logintime'] = time();
  229 + $oauthData['logincount'] = 1;
  230 + // 判断是否有unionid 并且已存在oauth数据中
  231 + if (isset($oauthData['unionid'])) {
  232 + //存在同厂商信息,添加oauthData数据,合并用户
  233 + $userUnionOauth = UserOauth::get(['unionid' => $oauthData['unionid'], 'provider' => $provider, 'user_id' => ['neq', 0]]);
  234 + if ($userUnionOauth) {
  235 + $existUser = $this->auth->direct($userUnionOauth->user_id);
  236 + if ($existUser) {
  237 + $createNewUser = false;
  238 + }
  239 + }
  240 + }
  241 +
  242 + // 创建空用户
  243 + if ($createNewUser) {
  244 + $username = Random::alnum(20);
  245 + $password = '';
  246 + $domain = request()->host();
  247 + $extend = $this->getUserDefaultFields();
  248 + $extend['nickname'] = $oauthData['nickname'] ? $oauthData['nickname'] : $extend['nickname'];
  249 + $extend['avatar'] = $oauthData['headimgurl'] ? $oauthData['headimgurl'] : $extend['avatar'];
  250 + $this->auth->register($username, $password, $username . '@' . $domain, '', $extend, $keeptime);
  251 + if (empty($oauthData['nickname'])) {
  252 + $this->auth->getUser()->save(['nickname' => $extend['nickname'] . $this->auth->getUser()->id]);
  253 + }
  254 + }
  255 + $oauthData['user_id'] = $this->auth->getUser()->id;
  256 + $oauthData['createtime'] = time();
  257 + UserOauth::strict(false)->insert($oauthData);
  258 + } else {
  259 + // 找到第三方登录信息,直接登录
  260 + $user_id = $userOauth->user_id;
  261 + if ($this->auth->direct($user_id) && $this->auth->getUser()) { // 获取到用户
  262 + $oauthData['logincount'] = $userOauth->logincount + 1;
  263 + $oauthData['logintime'] = time();
  264 + $userOauth->allowField(true)->save($oauthData);
  265 + } else { // 用户已被删除 重新执行登录
  266 + // throw new \Exception('此用户已删除');
  267 + $userOauth->delete();
  268 + $this->oauthLoginOrRegisterOrBindOrRefresh($event, $decryptData, $platform, $provider);
  269 + }
  270 + }
  271 + break;
  272 + case 'refresh':
  273 + if (!$userOauth) {
  274 + throw new \Exception('未找到第三方授权账户');
  275 + }
  276 + if (!empty($oauthData['nickname'])) {
  277 + $refreshFields['nickname'] = $oauthData['nickname'];
  278 + }
  279 + if (!empty($oauthData['headimgurl'])) {
  280 + $refreshFields['avatar'] = $oauthData['headimgurl'];
  281 + }
  282 + $this->auth->getUser()->save($refreshFields);
  283 + $userOauth->allowField(true)->save($oauthData);
  284 + break;
  285 + case 'bind':
  286 + if (!$this->auth->getUser()) {
  287 + throw new \Exception('请先登录');
  288 + }
  289 +
  290 + $oauthData['user_id'] = $this->auth->getUser()->id;
  291 +
  292 + if ($userOauth) {
  293 + if ($userOauth['user_id'] != 0 && $userOauth['user_id'] != $this->auth->getUser()->id && UserModel::get($userOauth['user_id'])) {
  294 + throw new \Exception('该账号已被其他用户绑定');
  295 + }
  296 + $oauthData['id'] = $userOauth->id;
  297 + $userOauth->strict(false)->update($oauthData);
  298 + } else {
  299 + $oauthData['logincount'] = 1;
  300 + $oauthData['logintime'] = time();
  301 + $oauthData['createtime'] = time();
  302 + UserOauth::strict(false)->insert($oauthData);
  303 + }
  304 + break;
  305 + }
  306 + if ($this->auth->getUser()) {
  307 + return $this->auth->getToken();
  308 + }
  309 + return false;
  310 + }
  311 +
  312 +
  313 + /**
  314 + * 手机验证码登录
  315 + *
  316 + * @param string $mobile 手机号
  317 + * @param string $code 验证码
  318 + */
  319 + public function mobileLogin()
  320 + {
  321 + $mobile = $this->request->post('mobile');
  322 + $code = $this->request->post('code');
  323 + if (!$mobile || !$code) {
  324 + $this->error(__('Invalid parameters'));
  325 + }
  326 + if (!Validate::regex($mobile, "^1\d{10}$")) {
  327 + $this->error(__('Mobile is incorrect'));
  328 + }
  329 + if (!Sms::check($mobile, $code, 'mobilelogin')) {
  330 + $this->error(__('Captcha is incorrect'));
  331 + }
  332 + $user = \app\common\model\User::getByMobile($mobile);
  333 + if ($user) {
  334 + if ($user->status != 'normal') {
  335 + $this->error(__('Account is locked'));
  336 + }
  337 + //如果已经有账号则直接登录
  338 + $ret = $this->auth->direct($user->id);
  339 + }
  340 + if ($ret) {
  341 + Sms::flush($mobile, 'mobilelogin');
  342 + $data = ['userinfo' => $this->auth->getUserinfo()];
  343 + $this->success(__('Logged in successful'), $data);
  344 + } else {
  345 + $this->error($this->auth->getError());
  346 + }
  347 + }
  348 +
  349 + /**
  350 + * 注册会员
  351 + *
  352 + * @param string $username 用户名
  353 + * @param string $password 密码
  354 + * @param string $email 邮箱
  355 + * @param string $mobile 手机号
  356 + * @param string $code 验证码
  357 + */
  358 + public function register()
  359 + {
  360 + $username = $this->request->post('mobile');
  361 + $password = $this->request->post('password');
  362 + $email = $this->request->post('mobile') . '@' . request()->host();
  363 +
  364 + $mobile = $this->request->post('mobile');
  365 + $code = $this->request->post('code');
  366 + if (!$password) {
  367 + $this->error(__('请填写密码')); //TODO:密码规则校验
  368 + }
  369 + if (strlen($password) < 6 || strlen($password) > 16) {
  370 + $this->error(__('密码长度 6-16 位')); //TODO:密码规则校验
  371 + }
  372 + if ($email && !Validate::is($email, "email")) {
  373 + $this->error(__('邮箱填写错误'));
  374 + }
  375 + if ($mobile && !Validate::regex($mobile, "^1\d{10}$")) {
  376 + $this->error(__('手机号填写错误'));
  377 + }
  378 + $ret = Sms::check($mobile, $code, 'register');
  379 + if (!$ret) {
  380 + $this->error(__('Captcha is incorrect'));
  381 + }
  382 + $extend = $this->getUserDefaultFields();
  383 + $ret = $this->auth->register($username, $password, $email, $mobile, $extend);
  384 + if ($ret) {
  385 + $user = $this->auth->getUser();
  386 + $user->nickname = $user->nickname . $user->id;
  387 + $user->save();
  388 + $data = ['userinfo' => $this->auth->getUserinfo()];
  389 + $this->success(__('注册成功'), $data);
  390 + } else {
  391 + $this->error($this->auth->getError());
  392 + }
  393 + }
  394 +
  395 + /**
  396 + * 注销登录
  397 + */
  398 + public function logout()
  399 + {
  400 + $this->auth->logout();
  401 + $this->success(__('Logout successful'));
  402 + }
  403 +
  404 + /**
  405 + * 修改会员个人信息
  406 + *
  407 + * @param string $avatar 头像地址
  408 + * @param string $username 用户名
  409 + * @param string $nickname 昵称
  410 + * @param string $birthday 生日
  411 + * @param string $bio 个人简介
  412 + */
  413 + public function profile()
  414 + {
  415 + $user = $this->auth->getUser();
  416 + $username = $this->request->post('username');
  417 + $nickname = $this->request->post('nickname');
  418 + $bio = $this->request->post('bio', '');
  419 + $birthday = $this->request->post('birthday');
  420 + $avatar = $this->request->post('avatar', '', 'trim,strip_tags,htmlspecialchars');
  421 + if ($username) {
  422 + $exists = \app\common\model\User::where('username', $username)->where('id', '<>', $this->auth->id)->find();
  423 + if ($exists) {
  424 + $this->error(__('Username already exists'));
  425 + }
  426 + $user->username = $username;
  427 + }
  428 + $user->nickname = $nickname;
  429 + $user->bio = $bio;
  430 + $user->birthday = $birthday;
  431 + $user->avatar = $avatar;
  432 + $user->save();
  433 + $this->success();
  434 + }
  435 +
  436 + /**
  437 + * 修改邮箱
  438 + *
  439 + * @param string $email 邮箱
  440 + * @param string $captcha 验证码
  441 + */
  442 + public function changeemail()
  443 + {
  444 + $user = $this->auth->getUser();
  445 + $email = $this->request->post('email');
  446 + $captcha = $this->request->request('captcha');
  447 + if (!$email || !$captcha) {
  448 + $this->error(__('Invalid parameters'));
  449 + }
  450 + if (!Validate::is($email, "email")) {
  451 + $this->error(__('Email is incorrect'));
  452 + }
  453 + if (\app\common\model\User::where('email', $email)->where('id', '<>', $user->id)->find()) {
  454 + $this->error(__('Email already exists'));
  455 + }
  456 + $result = Ems::check($email, $captcha, 'changeemail');
  457 + if (!$result) {
  458 + $this->error(__('Captcha is incorrect'));
  459 + }
  460 + $verification = $user->verification;
  461 + $verification->email = 1;
  462 + $user->verification = $verification;
  463 + $user->email = $email;
  464 + $user->save();
  465 +
  466 + Ems::flush($email, 'changeemail');
  467 + $this->success();
  468 + }
  469 +
  470 + /**
  471 + * 修改手机号
  472 + *
  473 + * @param string $mobile 手机号
  474 + * @param string $captcha 验证码
  475 + */
  476 + public function changemobile()
  477 + {
  478 + $user = $this->auth->getUser();
  479 + $mobile = $this->request->post('mobile');
  480 + $captcha = $this->request->post('captcha');
  481 + if (!$mobile || !$captcha) {
  482 + $this->error(__('Invalid parameters'));
  483 + }
  484 + if (!Validate::regex($mobile, "^1\d{10}$")) {
  485 + $this->error(__('Mobile is incorrect'));
  486 + }
  487 + if (\app\common\model\User::where('mobile', $mobile)->where('id', '<>', $user->id)->find()) {
  488 + $this->error(__('Mobile already exists'));
  489 + }
  490 + $result = Sms::check($mobile, $captcha, 'changemobile');
  491 + if (!$result) {
  492 + $this->error(__('Captcha is incorrect'));
  493 + }
  494 + $verification = $user->verification;
  495 + $verification->mobile = 1;
  496 + $user->verification = $verification;
  497 + $user->mobile = $mobile;
  498 + $user->save();
  499 +
  500 + Sms::flush($mobile, 'changemobile');
  501 + $this->success();
  502 + }
  503 +
  504 +
  505 + /**
  506 + * 重置密码
  507 + *
  508 + * @param string $mobile 手机号
  509 + * @param string $newpassword 新密码
  510 + * @param string $captcha 验证码
  511 + */
  512 + public function resetpwd()
  513 + {
  514 + $type = $this->request->post("type", 'mobile');
  515 + $mobile = $this->request->post("mobile");
  516 + $email = $this->request->post("email");
  517 + $newpassword = $this->request->post("newpassword");
  518 + $captcha = $this->request->post("captcha");
  519 + if (!$newpassword || !$captcha) {
  520 + $this->error(__('Invalid parameters'));
  521 + }
  522 + if (strlen($newpassword) < 6 || strlen($newpassword) > 16) {
  523 + $this->error(__('密码长度 6-16 位')); //TODO:密码规则校验
  524 + }
  525 + if ($type == 'mobile') {
  526 + if (!Validate::regex($mobile, "^1\d{10}$")) {
  527 + $this->error(__('Mobile is incorrect'));
  528 + }
  529 + $user = \app\common\model\User::getByMobile($mobile);
  530 + if (!$user) {
  531 + $this->error(__('User not found'));
  532 + }
  533 + $ret = Sms::check($mobile, $captcha, 'resetpwd');
  534 + if (!$ret) {
  535 + $this->error(__('Captcha is incorrect'));
  536 + }
  537 + Sms::flush($mobile, 'resetpwd');
  538 + } else {
  539 + if (!Validate::is($email, "email")) {
  540 + $this->error(__('Email is incorrect'));
  541 + }
  542 + $user = \app\common\model\User::getByEmail($email);
  543 + if (!$user) {
  544 + $this->error(__('User not found'));
  545 + }
  546 + $ret = Ems::check($email, $captcha, 'resetpwd');
  547 + if (!$ret) {
  548 + $this->error(__('Captcha is incorrect'));
  549 + }
  550 + Ems::flush($email, 'resetpwd');
  551 + }
  552 + //模拟一次登录
  553 + $this->auth->direct($user->id);
  554 + $ret = $this->auth->changepwd($newpassword, '', true);
  555 + if ($ret) {
  556 + $this->success(__('Reset password successful'));
  557 + } else {
  558 + $this->error($this->auth->getError());
  559 + }
  560 + }
  561 +
  562 +
  563 + public function changepwd()
  564 + {
  565 + $user = $this->auth->getUser();
  566 +
  567 + $oldpassword = $this->request->post("oldpassword");
  568 + $newpassword = $this->request->post("newpassword");
  569 +
  570 + if (!$newpassword || !$oldpassword) {
  571 + $this->error(__('Invalid parameters'));
  572 + }
  573 + if (strlen($newpassword) < 6 || strlen($newpassword) > 16) {
  574 + $this->error(__('密码长度 6-16 位')); //TODO:密码规则校验
  575 + }
  576 +
  577 + $ret = $this->auth->changepwd($newpassword, $oldpassword);
  578 +
  579 + if ($ret) {
  580 + $this->auth->direct($user->id);
  581 + $data = ['userinfo' => $this->auth->getUserinfo()];
  582 +
  583 + $this->success(__('Change password successful'), $data);
  584 + } else {
  585 + $this->error($this->auth->getError());
  586 + }
  587 + }
  588 +
  589 + private function getUserDefaultFields()
  590 + {
  591 + $userConfig = json_decode(\addons\groupon\model\Config::get(['name' => 'user'])->value, true);
  592 + return $userConfig;
  593 + }
  594 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class UserBank extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = [];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 +
  13 + public function info()
  14 + {
  15 + $type = $this->request->get('type');
  16 +
  17 + $bankInfo = \addons\groupon\model\UserBank::info($type);
  18 +
  19 + $this->success('提现账户', $bankInfo);
  20 + }
  21 +
  22 +
  23 + public function edit () {
  24 + $params = $this->request->post();
  25 + if ($params['type'] === 'alipay') {
  26 + $params['bank_name'] = '支付宝账户';
  27 + }
  28 +
  29 + // 表单验证
  30 + $this->grouponValidate($params, get_class(), 'edit');
  31 +
  32 + $this->success('编辑成功', \addons\groupon\model\UserBank::edit($params));
  33 + }
  34 +
  35 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +
  6 +class UserWalletApply extends Base
  7 +{
  8 +
  9 + protected $noNeedLogin = ['rule'];
  10 + protected $noNeedRight = ['*'];
  11 +
  12 +
  13 + public function index()
  14 + {
  15 + $this->success('提现记录', \addons\groupon\model\UserWalletApply::getList());
  16 + }
  17 +
  18 +
  19 + public function apply () {
  20 + repeat_filter(); // 防抖
  21 + $type = $this->request->post('type');
  22 + $money = $this->request->post('money');
  23 +
  24 + $apply = \addons\groupon\model\UserWalletApply::apply($type, $money);
  25 +
  26 + if ($apply) {
  27 + $this->success('申请成功');
  28 + }
  29 + $this->error('申请失败');
  30 + }
  31 +
  32 +
  33 + public function rule () {
  34 + // 提现规则
  35 + $config = \addons\groupon\model\UserWalletApply::getWithdrawConfig();
  36 + $min = round(floatval($config['min']), 2);
  37 + $max = round(floatval($config['max']), 2);
  38 + $service_fee = floatval($config['service_fee']) * 100;
  39 + $service_fee = round($service_fee, 1); // 1 位小数
  40 +
  41 + $rule = [
  42 + 'min' => $min,
  43 + 'max' => $max,
  44 + 'service_fee' => $service_fee,
  45 + 'methods' => $config['methods'] ?? []
  46 + ];
  47 +
  48 + $this->success('提现规则', $rule);
  49 + }
  50 +
  51 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +
  7 +class UserWalletLog extends Base
  8 +{
  9 +
  10 + protected $noNeedLogin = ['*'];
  11 + protected $noNeedRight = ['*'];
  12 +
  13 +
  14 + public function index()
  15 + {
  16 + $params = $this->request->get();
  17 + $wallet_type = $params['wallet_type'] ?? 'money';
  18 +
  19 + if (!in_array($wallet_type, ['money', 'score'])) {
  20 + $this->error('参数错误');
  21 + }
  22 +
  23 + $this->success(($wallet_type == 'money' ? '钱包记录' : '积分记录'), \addons\groupon\model\UserWalletLog::getList($params));
  24 + }
  25 +
  26 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller;
  4 +
  5 +use addons\groupon\library\Wechat as WechatLibrary;
  6 +use addons\groupon\model\Wechat as WechatModel;
  7 +use addons\groupon\model\Config;
  8 +
  9 +/**
  10 + * 微信接口
  11 + */
  12 +class Wechat extends Base
  13 +{
  14 + protected $noNeedLogin = ['*'];
  15 + protected $noNeedRight = ['*'];
  16 + protected $app = null;
  17 + protected $userOpenId = '';
  18 + /**
  19 + * 微信公众号服务端API对接、处理消息回复
  20 + */
  21 + public function index()
  22 + {
  23 + $wechat = new WechatLibrary('wxOfficialAccount');
  24 + $this->app = $wechat->getApp();
  25 + $this->app->server->push(function ($message) {
  26 + //初始化信息
  27 + $this->userOpenId = $message['FromUserName'];
  28 + // return json_encode($message, JSON_UNESCAPED_UNICODE); //调试使用
  29 +
  30 + switch ($message['MsgType']) {
  31 + case 'event': //收到事件消息
  32 + switch ($message['Event']) {
  33 + case 'subscribe': //订阅(关注)事件
  34 + //获取粉丝信息并保存
  35 + $subscribe = WechatModel::get(['type' => 'subscribe']);
  36 + if ($subscribe) {
  37 + return $this->response($subscribe);
  38 + }
  39 + break;
  40 + case 'unsubscribe': //取消订阅(关注)事件
  41 + //获取粉丝信息并保存
  42 + break;
  43 + case 'CLICK': //自定义菜单事件
  44 + return $this->response($message, 'CLICK');
  45 + break;
  46 + case 'SCAN': //扫码事件
  47 + return '';
  48 + break;
  49 + }
  50 + break;
  51 + case 'text': //收到文本消息
  52 + //检测关键字回复
  53 + $content = $message['Content'];
  54 + $auto_reply = WechatModel::where('type', 'auto_reply')->where('find_in_set(:keywords,rules)', ['keywords' => $content])->find();
  55 + if ($auto_reply) {
  56 + return $this->response($auto_reply);
  57 + }
  58 + case 'image': //收到图片消息
  59 + case 'voice': //收到语音消息
  60 + case 'video': //收到视频消息
  61 + case 'location': //收到坐标消息
  62 + case 'link': //收到链接消息
  63 + case 'file': //收到文件消息
  64 + default: // ... 默认回复消息
  65 + $default_reply = WechatModel::where('type', 'default_reply')->find();
  66 + if ($default_reply) {
  67 + return $this->response($default_reply);
  68 + }
  69 + }
  70 + });
  71 + $response = $this->app->server->serve();
  72 + // 将响应输出
  73 + $response->send();
  74 + }
  75 +
  76 + public function jssdk()
  77 + {
  78 + $params = $this->request->post();
  79 + $apis = [
  80 + 'checkJsApi',
  81 + 'onMenuShareTimeline',
  82 + 'onMenuShareAppMessage',
  83 + 'getLocation', //获取位置
  84 + 'openLocation', //打开位置
  85 + 'scanQRCode', //扫一扫接口
  86 + 'chooseWXPay', //微信支付
  87 + 'chooseImage', //拍照或从手机相册中选图接口
  88 + 'previewImage', //预览图片接口
  89 + 'uploadImage', //上传图片
  90 + 'openAddress', // 获取微信地址
  91 + ];
  92 + $uri = urldecode($params['uri']);
  93 +
  94 + $wechat = new WechatLibrary('wxOfficialAccount');
  95 + $res = $wechat->getApp()->jssdk->setUrl($uri)->buildConfig($apis, $debug = false, $beta = false, $json = false);
  96 + $this->success('sdk', $res);
  97 + }
  98 +
  99 + /**
  100 + * 微信公众号服务端API对接
  101 + */
  102 + public function wxacode()
  103 + {
  104 + $scene = $this->request->get('scene', '');
  105 + $path = $this->request->get('path', '');
  106 +
  107 + if (empty($path)) {
  108 + $path = 'pages/index/index';
  109 + }
  110 +
  111 + $wechat = new WechatLibrary('wxMiniProgram');
  112 + $content = $wechat->getApp()->app_code->getUnlimit($scene, [
  113 + 'page' => $path,
  114 + 'is_hyaline' => true,
  115 + ]);
  116 +
  117 + if ($content instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
  118 + return response($content->getBody(), 200, ['Content-Length' => strlen($content)])->contentType('image/png');
  119 + } else {
  120 + // 小程序码获取失败
  121 + $msg = isset($content['errcode']) ? $content['errcode'] : '-';
  122 + $msg .= isset($content['errmsg']) ? $content['errmsg'] : '';
  123 + \think\Log::write('wxacode-error' . $msg);
  124 +
  125 + $this->error('获取失败', $msg);
  126 + }
  127 + }
  128 +
  129 + /**
  130 + * 回复消息
  131 + */
  132 + private function response($replyInfo, $event = 'text')
  133 + {
  134 + switch ($event) {
  135 + case 'SCAN': //解析扫码事件EventKey
  136 + break;
  137 + case 'CLICK': //解析菜单点击事件EventKey
  138 + $key = explode('|', $replyInfo['EventKey']);
  139 + if ($key) {
  140 + $message['type'] = $key[0];
  141 + if ($key[0] === 'text') {
  142 + $message['content'] = json_decode(WechatModel::get($key[1])->content, true);
  143 + } elseif($key[0] === 'link') {
  144 + $link = WechatModel::get($key[1]);
  145 + $message = array_merge($message, json_decode($link->content, true));
  146 + $message['title'] = $link->name;
  147 + // return json_encode($message);
  148 + }else {
  149 + $message['media_id'] = $key[1];
  150 + }
  151 + }
  152 + break;
  153 + default:
  154 + $message = json_decode($replyInfo['content'], true);
  155 + break;
  156 + }
  157 +
  158 + switch ($message['type']) {
  159 + case 'text': //回复文本
  160 + $content = new \EasyWeChat\Kernel\Messages\Text($message['content']);
  161 + break;
  162 + case 'image': //回复图片
  163 + $content = new \EasyWeChat\Kernel\Messages\Image($message['media_id']);
  164 + break;
  165 + case 'news': //回复图文
  166 + $message = new \EasyWeChat\Kernel\Messages\Media($message['media_id'], 'mpnews');
  167 + $this->app->customer_service->message($message)->to($this->userOpenId)->send(); //素材消息使用客服接口回复
  168 + break;
  169 + case 'voice': //回复语音
  170 + $content = new \EasyWeChat\Kernel\Messages\Voice($message['media_id']);
  171 + break;
  172 + case 'video': //回复视频
  173 + $content = new \EasyWeChat\Kernel\Messages\Video($message['media_id']);
  174 + break;
  175 + case 'link': //回复链接
  176 + $items = new \EasyWeChat\Kernel\Messages\NewsItem([
  177 + 'title' => $message['title'],
  178 + 'description' => $message['description'],
  179 + 'url' => $message['url'],
  180 + 'image' => cdnurl($message['image'], true),
  181 + // ...
  182 + ]);
  183 + $content = new \EasyWeChat\Kernel\Messages\News([$items]);
  184 + break;
  185 + }
  186 + return $content;
  187 + }
  188 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller\store;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\controller\Base as GrouponBase;
  7 +
  8 +/**
  9 + * 不继承门店的 base
  10 + */
  11 +class Apply extends GrouponBase
  12 +{
  13 +
  14 + protected $noNeedLogin = [];
  15 + protected $noNeedRight = ['*'];
  16 +
  17 +
  18 + public function info() {
  19 + $config = new \addons\groupon\model\Config;
  20 +
  21 + $storeConfig = json_decode($config->where(['name' => 'store'])->value('value'), true);
  22 + $storeConfig['intro_image'] = (isset($storeConfig['intro_image']) && $storeConfig['intro_image']) ? cdnurl($storeConfig['intro_image'], true) : '';
  23 +
  24 + $this->success('自提点申请', [
  25 + 'apply' => \addons\groupon\model\store\Apply::info(),
  26 + 'config' => $storeConfig
  27 + ]);
  28 + }
  29 +
  30 +
  31 + public function apply() {
  32 + $params = $this->request->post();
  33 +
  34 + // 表单验证
  35 + $this->grouponValidate($params, get_class(), 'apply');
  36 +
  37 + $order = \addons\groupon\model\store\Apply::apply($params);
  38 +
  39 + $this->success('自提点申请提交成功', $order);
  40 + }
  41 +
  42 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller\store;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\controller\Base as AddonsBase;
  7 +use addons\groupon\model\Store;
  8 +use addons\groupon\model\User;
  9 +use addons\groupon\model\UserStore;
  10 +
  11 +class Base extends AddonsBase
  12 +{
  13 + public function _initialize()
  14 + {
  15 + parent::_initialize();
  16 +
  17 + // 验证登录用户是否可以访问门店接口
  18 + $this->checkUserStore();
  19 + }
  20 +
  21 +
  22 + /**
  23 + * 检测用户管理的是否有门店
  24 + */
  25 + private function checkUserStore() {
  26 + // 获取当前用户的门店
  27 + $user = User::info();
  28 + $store_id = $this->request->param('store_id');
  29 +
  30 + if (!$store_id) {
  31 + $this->error('请选择自提点');
  32 + }
  33 +
  34 + $userStore = UserStore::with('store')->where('user_id', $user->id)->where('store_id', $store_id)->find();
  35 + if (!$userStore || !$userStore->store) {
  36 + $this->error('权限不足');
  37 + }
  38 +
  39 + $store = $userStore->store->toArray();
  40 +
  41 + if (!$store['status']) {
  42 + $this->error('自提点已被禁用');
  43 + }
  44 +
  45 + // 存 session 本次请求有效
  46 + session('current_oper_store', $store);
  47 + }
  48 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller\store;
  4 +
  5 +class Order extends Base
  6 +{
  7 +
  8 + protected $noNeedLogin = [];
  9 + protected $noNeedRight = ['*'];
  10 +
  11 +
  12 + public function index()
  13 + {
  14 + $params = $this->request->get();
  15 +
  16 + $this->success('订单列表', \addons\groupon\model\store\Order::getList($params));
  17 + }
  18 +
  19 +
  20 +
  21 + public function detail()
  22 + {
  23 + $params = $this->request->get();
  24 + $this->success('订单详情', \addons\groupon\model\store\Order::detail($params));
  25 + }
  26 +
  27 +
  28 +
  29 + public function confirm()
  30 + {
  31 + repeat_filter(); // 防抖
  32 + $params = $this->request->post();
  33 + $this->success('核销成功', \addons\groupon\model\store\Order::operConfirm($params));
  34 + }
  35 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller\store;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\model\Store as ModelStore;
  7 +
  8 +class Store extends Base
  9 +{
  10 +
  11 + protected $noNeedLogin = [];
  12 + protected $noNeedRight = ['*'];
  13 +
  14 +
  15 + public function index()
  16 + {
  17 + $params = $this->request->get();
  18 + $store = ModelStore::info();
  19 + if (!$store) {
  20 + $this->error('门店不存在');
  21 + }
  22 +
  23 + $this->success('获取成功', $store);
  24 + }
  25 +
  26 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\controller\store;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use addons\groupon\library\traits\export\ExportOrder;
  7 +/**
  8 + * 门店包裹
  9 + */
  10 +class StoreExpress extends Base
  11 +{
  12 + use ExportOrder;
  13 +
  14 + protected $noNeedLogin = [];
  15 + protected $noNeedRight = ['*'];
  16 +
  17 +
  18 + public function index() {
  19 + $params = $this->request->get();
  20 +
  21 + $this->success('包裹列表', \addons\groupon\model\store\StoreExpress::getList($params));
  22 + }
  23 +
  24 +
  25 + public function detail() {
  26 + $params = $this->request->get();
  27 +
  28 + $this->success('包裹详情', \addons\groupon\model\store\StoreExpress::getDetail($params));
  29 + }
  30 +
  31 +
  32 + /**
  33 + * 包裹到货
  34 + *
  35 + * @return void
  36 + */
  37 + public function arrive()
  38 + {
  39 + $params = $this->request->post();
  40 + $this->success('到货成功', \addons\groupon\model\store\StoreExpress::operArrive($params));
  41 + }
  42 +
  43 +
  44 + public function export() {
  45 + $params = $this->request->get();
  46 + $id = $params['id'] ?? 0;
  47 +
  48 + $orderStoreExpress = \app\admin\model\groupon\order\OrderStoreExpress::with(['store'])->where('id', $id)->find();
  49 +
  50 + if (!$orderStoreExpress) {
  51 + $this->error('包裹未找到');
  52 + }
  53 +
  54 + $this->exportOutput($orderStoreExpress);
  55 + }
  56 +}
  1 +<?php
  2 +namespace addons\groupon\exception;
  3 +
  4 +use think\Response;
  5 +
  6 +class Exception
  7 +{
  8 + protected $msg = '错误消息';
  9 + protected $code = 0; //TOAST自动弹出消息
  10 + const NOT_LOGIN = 401; //未登录自动弹框提醒
  11 + const NOT_AUTHORIZE = 403;//
  12 + const IGNORE = -1;
  13 + public function __construct($msg, $code = 0, $status_code = 200)
  14 + {
  15 + $this->msg = $msg;
  16 + $this->code = $code;
  17 + $this->send($status_code);
  18 + }
  19 + protected function send($code = 200)
  20 + {
  21 + $data = [
  22 + 'code' => $this->code,
  23 + 'msg' => $this->msg,
  24 + 'data' => null,
  25 + 'time' => time()
  26 + ];
  27 + $response = Response::create($data, 'json', $code);
  28 + throw new \think\exception\HttpResponseException($response);
  29 + }
  30 +}
  1 +<?php
  2 +
  3 +if (!function_exists('matchLatLng')) {
  4 + function matchLatLng($latlng) {
  5 + $match = "/^\d{1,3}\.\d{1,30}$/";
  6 + return preg_match($match, $latlng) ? $latlng : 0;
  7 + }
  8 +}
  9 +
  10 +
  11 +if (!function_exists('getDistanceBuilder')) {
  12 + function getDistanceBuilder($lat, $lng) {
  13 + return "ROUND(6378.138 * 2 * ASIN(SQRT(POW(SIN((". matchLatLng($lat) . " * PI() / 180 - latitude * PI() / 180) / 2), 2) + COS(". matchLatLng($lat). " * PI() / 180) * COS(latitude * PI() / 180) * POW(SIN((". matchLatLng($lng). " * PI() / 180 - longitude * PI() / 180) / 2), 2))) * 1000) AS distance";
  14 + }
  15 +}
  16 +
  17 +
  18 +/**
  19 + * 下划线转驼峰
  20 + * step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
  21 + * step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
  22 + */
  23 +if (!function_exists('camelize')) {
  24 + function camelize($uncamelized_words, $separator = '_') {
  25 + $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
  26 + return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator);
  27 + }
  28 +}
  29 +
  30 +/**
  31 + * 驼峰命名转下划线命名
  32 + * 思路:
  33 + * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
  34 + */
  35 +if (!function_exists('uncamelize')) {
  36 + function uncamelize($camelCaps, $separator='_')
  37 + {
  38 + return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
  39 + }
  40 +}
  41 +
  42 +
  43 +/**
  44 + * 检测系统必要环境
  45 + */
  46 +if (!function_exists('checkEnv')) {
  47 + function checkEnv($need = [], $is_throw = true)
  48 + {
  49 + $need = is_string($need) ? [$need] : $need;
  50 +
  51 + // 检测是否安装浮点数运算扩展
  52 + if (in_array('bcmath', $need)) {
  53 + if (!extension_loaded('bcmath')) {
  54 + if ($is_throw) {
  55 + new \addons\groupon\exception\Exception('请安装浮点数扩展 bcmath');
  56 + } else {
  57 + return false;
  58 + }
  59 + }
  60 + }
  61 +
  62 + // 检测是否安装了队列
  63 + if (in_array('queue', $need)) {
  64 + if (!class_exists(\think\Queue::class)) {
  65 + if ($is_throw) {
  66 + new \addons\groupon\exception\Exception('请安装 topthink/think-queue:v1.1.6 队列扩展');
  67 + } else {
  68 + return false;
  69 + }
  70 + }
  71 + }
  72 +
  73 + if (in_array('yansongda', $need)) {
  74 + if (!class_exists(\Yansongda\Pay\Pay::class)) {
  75 + if ($is_throw) {
  76 + new \addons\groupon\exception\Exception('请查看文档,配置支付');
  77 + } else {
  78 + return false;
  79 + }
  80 + }
  81 + }
  82 +
  83 + return true;
  84 + }
  85 +}
  86 +
  87 +
  88 +/**
  89 + * 后端防抖
  90 + */
  91 +if (!function_exists('repeat_filter')) {
  92 + function repeat_filter($key = null, $expire = 5)
  93 + {
  94 + if (!$key) {
  95 + $url = request()->baseUrl();
  96 + $ip = request()->ip();
  97 +
  98 + $key = md5($url . ':' . $ip);
  99 + }
  100 +
  101 + $hasRedis = config('redis');
  102 + if ($hasRedis) {
  103 + $redis = (new \addons\groupon\library\Redis())->getRedis();
  104 + if ($redis->exists($key)) {
  105 + throw new \addons\groupon\exception\Exception('请不要重复提交');
  106 + }
  107 + $redis->setex($key, $expire, time()); // 缓存 五秒
  108 + } else {
  109 + if (cache('?' . $key)) {
  110 + throw new \addons\groupon\exception\Exception('请不要重复提交');
  111 + }
  112 + cache($key, time(), $expire);
  113 + }
  114 + }
  115 +}
  116 +
  117 +/**
  118 + * 返回配置的微信支付版本
  119 + */
  120 +if (!function_exists('pay_version')) {
  121 + function pay_version()
  122 + {
  123 + $version = config('pay_version');
  124 +
  125 + $version = $version ? strtolower($version) : 'v2';
  126 +
  127 + return $version;
  128 + }
  129 +}
  130 +
  131 +
  132 +
  133 +/**
  134 + * 过滤掉字符串中的 sql 关键字
  135 + */
  136 +if (!function_exists('filter_sql')) {
  137 + function filter_sql($str)
  138 + {
  139 + $str = strtolower($str); // 转小写
  140 + $str = str_replace("and", "", $str);
  141 + $str = str_replace("execute", "", $str);
  142 + $str = str_replace("update", "", $str);
  143 + $str = str_replace("count", "", $str);
  144 + $str = str_replace("chr", "", $str);
  145 + $str = str_replace("mid", "", $str);
  146 + $str = str_replace("master", "", $str);
  147 + $str = str_replace("truncate", "", $str);
  148 + $str = str_replace("char", "", $str);
  149 + $str = str_replace("declare", "", $str);
  150 + $str = str_replace("select", "", $str);
  151 + $str = str_replace("create", "", $str);
  152 + $str = str_replace("delete", "", $str);
  153 + $str = str_replace("insert", "", $str);
  154 + $str = str_replace("union", "", $str);
  155 + $str = str_replace("alter", "", $str);
  156 + $str = str_replace("into", "", $str);
  157 + $str = str_replace("'", "", $str);
  158 + $str = str_replace("or", "", $str);
  159 + $str = str_replace("=", "", $str);
  160 +
  161 + return $str;
  162 + }
  163 +}
  1 +<?php
  2 +
  3 +$defaultHooks = [
  4 + // 订单创建
  5 + 'groupon_order_create_before' => [ // 订单创建前
  6 + 'addons\\groupon\\listener\\order\\Create'
  7 + ],
  8 + 'groupon_order_create_after' => [ // 订单创建后
  9 + 'addons\\groupon\\listener\\order\\Create'
  10 + ],
  11 + 'groupon_order_payed_after' => [ // 订单支付成功
  12 + 'addons\\groupon\\listener\\order\\Payed',
  13 + 'addons\\groupon\\listener\\store\\Take',
  14 + 'addons\\groupon\\listener\\order\\Coupon',
  15 + ],
  16 +
  17 + // 订单关闭
  18 + 'groupon_order_close_before' => [ // 订单关闭前
  19 + ],
  20 + 'groupon_order_close_after' => [ // 订单关闭后
  21 + 'addons\\groupon\\listener\\order\\Invalid'
  22 + ],
  23 +
  24 + // 订单取消
  25 + 'groupon_order_cancel_before' => [ // 订单取消前
  26 + ],
  27 + 'groupon_order_cancel_after' => [ // 订单取消后
  28 + 'addons\\groupon\\listener\\order\\Invalid'
  29 + ],
  30 +
  31 + // 订单备货
  32 + 'groupon_order_send_before' => [
  33 + ],
  34 + // 订单备货
  35 + 'groupon_order_send_after' => [
  36 + 'addons\\groupon\\listener\\order\\Send'
  37 + ],
  38 +
  39 + // 订单到货
  40 + 'groupon_order_arrive_before' => [],
  41 + // 订单备货
  42 + 'groupon_order_arrive_after' => [
  43 + 'addons\\groupon\\listener\\order\\Send'
  44 + ],
  45 +
  46 + // 订单确认收货
  47 + 'groupon_order_confirm_before' => [ // 订单确认收货前
  48 + ],
  49 + 'groupon_order_confirm_after' => [ // 订单确认收货后
  50 + 'addons\\groupon\\listener\\order\\Confirm',
  51 + 'addons\\groupon\\listener\\store\\Take'
  52 + ],
  53 + 'groupon_order_confirm_finish' => [ // 订单确认收货完成
  54 + 'addons\\groupon\\listener\\order\\Coupon',
  55 + ],
  56 +
  57 + // 订单完成事件
  58 + 'groupon_order_finish' => [
  59 + 'addons\\groupon\\listener\\store\\Take',
  60 + 'addons\\groupon\\listener\\order\\Coupon',
  61 + ],
  62 +
  63 + // 订单评价
  64 + 'groupon_order_comment_before' => [ // 订单评价前
  65 + ],
  66 + 'groupon_order_comment_after' => [ // 订单评价后
  67 + 'addons\\groupon\\listener\\order\\Comment'
  68 + ],
  69 +
  70 + // 订单退款
  71 + 'groupon_order_refund_before' => [ // 订单退款前
  72 + 'addons\\groupon\\listener\\order\\Refund'
  73 + ],
  74 + 'groupon_order_refund_after' => [ // 订单退款后
  75 + 'addons\\groupon\\listener\\order\\Refund',
  76 + 'addons\\groupon\\listener\\store\\Take'
  77 + ],
  78 +
  79 + // 活动更新
  80 + 'groupon_activity_update_after' => [ // 活动更新后
  81 + 'addons\\groupon\\listener\\activity\\Update'
  82 + ],
  83 + 'groupon_activity_delete_after' => [ // 活动删除之后
  84 + 'addons\\groupon\\listener\\activity\\Update'
  85 + ],
  86 +
  87 +];
  88 +
  89 +return $defaultHooks;
  1 +name = groupon
  2 +title = 社区团购
  3 +intro = 自提点管理、数据统计、店铺装修、自定义限时活动、Canvas分享海报、消息通知等多种功能
  4 +author = 星品科技
  5 +website = https://groupon.7wpp.com
  6 +version = 1.1.1
  7 +state = 1
  8 +url = /addons/groupon
  9 +first_menu = groupon
  10 +license = regular
  11 +licenseto = 44234
此 diff 太大无法显示。
  1 +<?php
  2 +
  3 +namespace addons\groupon\job;
  4 +
  5 +use addons\groupon\library\traits\ActivityCache;
  6 +use addons\groupon\model\Activity;
  7 +use think\queue\Job;
  8 +
  9 +
  10 +/**
  11 + * 订单自动操作
  12 + */
  13 +class ActivityAutoOper extends BaseJob
  14 +{
  15 + use ActivityCache;
  16 +
  17 + /**
  18 + * 活动自动删除
  19 + */
  20 + public function autoClose(Job $job, $data){
  21 + try {
  22 + $activity = $data['activity'];
  23 +
  24 + $activity = Activity::get($activity['id']);
  25 +
  26 + // 一个活动会存在多个队列,要排重
  27 + if ($activity) {
  28 + // 如果活动还没被删除
  29 +
  30 + // 规则
  31 + $rules = $activity['rules'];
  32 +
  33 + // 当前配置应该自动结束的时间
  34 + $laterTime = $activity['endtime'];
  35 + if (isset($rules['activity_auto_close']) && $rules['activity_auto_close'] > 0) {
  36 + $laterTime += ($rules['activity_auto_close'] * 60);
  37 + }
  38 +
  39 + // 如果当前时间大于 laterTime,可以执行删除
  40 + if (time() >= $laterTime) {
  41 + // 删除活动 【软删】
  42 + $activity->delete();
  43 +
  44 + // 删除活动缓存
  45 + $this->delActivity($activity);
  46 + }
  47 + }
  48 +
  49 + // 删除 job
  50 + $job->delete();
  51 + } catch (\Exception $e) {
  52 + // 队列执行失败
  53 + \think\Log::write('queue-' . get_class() . '-autoClose' . ':执行失败,错误信息:' . $e->getMessage());
  54 + }
  55 + }
  56 +
  57 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\job;
  4 +
  5 +use addons\groupon\model\Order;
  6 +use addons\groupon\model\OrderAction;
  7 +use think\queue\Job;
  8 +
  9 +
  10 +/**
  11 + * BaseJob 基类
  12 + */
  13 +class BaseJob
  14 +{
  15 +
  16 + public function failed($data){
  17 + // 记录日志
  18 + \think\Db::name('groupon_failed_job')->insert([
  19 + 'data' => json_encode($data),
  20 + 'createtime' => time(),
  21 + 'updatetime' => time()
  22 + ]);
  23 + }
  24 +
  25 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\job;
  4 +
  5 +use think\queue\Job;
  6 +
  7 +/**
  8 + * 队列消息通知
  9 + */
  10 +class Notification extends BaseJob
  11 +{
  12 + /**
  13 + * 发送通知
  14 + */
  15 + public function send(Job $job, $data){
  16 + try {
  17 + // 这里获取到的 $notifiables 和 notification 两个都是数组,不是类,尴尬, 更可恨的 notification 只是 {"delay":0,"event":"changemobile"}
  18 + $notifiables = $data['notifiables'];
  19 + $notification = $data['notification'];
  20 + // 因为 notification 只有参数,需要把对应的类传过来,在这里重新初始化
  21 + $notification_name = $data['notification_name'];
  22 +
  23 + // 重新实例化 notification 实例
  24 + $notification = new $notification_name($notification['data']);
  25 +
  26 + // 发送消息
  27 + \addons\groupon\library\notify\Notify::sendNow($notifiables, $notification);
  28 +
  29 + // 删除 job
  30 + $job->delete();
  31 + } catch (\Exception $e) {
  32 + // 队列执行失败
  33 + \think\Log::write('queue-' . get_class()
  34 + . (isset($notification->event) ? ('-' . $notification->event) : '')
  35 + . ':执行失败,错误信息:' . $e->getMessage());
  36 + }
  37 + }
  38 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\job;
  4 +
  5 +use addons\groupon\model\GoodsComment;
  6 +use addons\groupon\model\Order;
  7 +use addons\groupon\model\OrderAction;
  8 +use addons\groupon\model\OrderItem;
  9 +use addons\groupon\model\Config;
  10 +use addons\groupon\model\Verify;
  11 +use think\queue\Job;
  12 +
  13 +
  14 +/**
  15 + * 订单自动操作
  16 + */
  17 +class OrderAutoOper extends BaseJob
  18 +{
  19 +
  20 + /**
  21 + * 订单自动关闭
  22 + */
  23 + public function autoClose(Job $job, $data){
  24 + try {
  25 + $order = $data['order'];
  26 +
  27 + $order = Order::get($order['id']);
  28 +
  29 + if ($order && $order['status'] == 0) {
  30 + \think\Db::transaction(function () use ($order, $data) {
  31 + // 订单关闭前
  32 + \think\Hook::listen('groupon_order_close_before', $data);
  33 + // 执行关闭
  34 + $order->status = Order::STATUS_INVALID;
  35 + $order->ext = json_encode($order->setExt($order, ['invalid_time' => time()])); // 取消时间
  36 + $order->save();
  37 +
  38 + OrderAction::operAdd($order, null, null, 'system', '系统自动失效订单');
  39 +
  40 + // 订单自动关闭之后 行为 返还用户优惠券,积分
  41 + $data = ['order' => $order];
  42 + \think\Hook::listen('groupon_order_close_after', $data);
  43 + });
  44 + }
  45 +
  46 + // 删除 job
  47 + $job->delete();
  48 + } catch (\Exception $e) {
  49 + // 队列执行失败
  50 + \think\Log::write('queue-' . get_class() . '-autoClose' . ':执行失败,错误信息:' . $e->getMessage());
  51 + }
  52 + }
  53 +
  54 +
  55 + /**
  56 + * 订单自动确认
  57 + */
  58 + public function autoConfirm(Job $job, $data) {
  59 + try {
  60 + $order = $data['order'];
  61 + $item = $data['item'];
  62 +
  63 + // 只要没有退款成功,所有队列正常执行
  64 + $item = OrderItem::where('id', $item['id'])
  65 + ->where('dispatch_status', 'in', [OrderItem::DISPATCH_STATUS_READY, OrderItem::DISPATCH_STATUS_ARRIVE])
  66 + ->where('refund_status', 'not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH])
  67 + ->find();
  68 +
  69 + if ($item) {
  70 + \think\Db::transaction(function () use ($order, $item, $data) {
  71 + // 查询核销券,并且将核销券设置为已使用
  72 + Verify::canUse()->where('order_item_id', $item['id'])->where('type', 'verify')->update([
  73 + 'usetime' => time(),
  74 + 'oper_type' => 'system',
  75 + 'oper_id' => 0,
  76 + ]);
  77 +
  78 + (new Order())->getedItem($order, $item, ['oper_type' => 'system']);
  79 + });
  80 + }
  81 +
  82 + // 删除 job
  83 + $job->delete();
  84 + } catch (\Exception $e) {
  85 + // 队列执行失败
  86 + \think\Log::write('queue-' . get_class() . '-autoConfirm' . ':执行失败,错误信息:' . $e->getMessage());
  87 + }
  88 + }
  89 +
  90 +
  91 +
  92 + public function autoComment(Job $job, $data) {
  93 + try {
  94 + $order = $data['order'];
  95 + $item = $data['item'];
  96 +
  97 + // 只要没有退款成功,所有队列正常执行
  98 + $item = OrderItem::where('id', $item['id'])
  99 + ->where('dispatch_status', OrderItem::DISPATCH_STATUS_GETED)
  100 + ->where('comment_status', OrderItem::COMMENT_STATUS_NO)
  101 + ->where('refund_status', 'not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH])
  102 + ->find();
  103 +
  104 + if ($item) {
  105 + \think\Db::transaction(function () use ($order, $item, $data) {
  106 + // 订单评价前
  107 + \think\Hook::listen('groupon_order_comment_before', $data);
  108 +
  109 + // 获取自动好评内容
  110 + $config = Config::where('name', 'order')->cache(300)->find(); // 读取配置自动缓存 5 分钟
  111 + $config = isset($config) ? json_decode($config['value'], true) : [];
  112 + $comment_content = (isset($config['order_comment_content']) && $config['order_comment_content'])
  113 + ? $config['order_comment_content'] : '用户默认好评'; // 单位天
  114 +
  115 + GoodsComment::create([
  116 + 'goods_id' => $item['goods_id'],
  117 + 'order_id' => $order['id'],
  118 + 'user_id' => $order['user_id'],
  119 + 'level' => 5, // 自动好评
  120 + 'content' => $comment_content,
  121 + 'images' => '',
  122 + 'status' => 'show'
  123 + ]);
  124 +
  125 + $item->comment_status = OrderItem::COMMENT_STATUS_OK; // 评价成功
  126 + $item->save();
  127 +
  128 + OrderAction::operAdd($order, $item, null, 'system', '系统自动评价成功');
  129 +
  130 + // 订单评价后
  131 + $data = ['order' => $order, 'item' => $item];
  132 + \think\Hook::listen('groupon_order_comment_after', $data);
  133 + });
  134 + }
  135 +
  136 + // 删除 job
  137 + $job->delete();
  138 + } catch (\Exception $e) {
  139 + // 队列执行失败
  140 + \think\Log::write('queue-' . get_class() . '-autoComment' . $e->getMessage());
  141 + }
  142 + }
  143 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\job;
  4 +
  5 +use addons\groupon\library\traits\Groupon;
  6 +use addons\groupon\library\traits\StockSale;
  7 +use addons\groupon\model\GoodsComment;
  8 +use addons\groupon\model\Order;
  9 +use addons\groupon\model\OrderAction;
  10 +use addons\groupon\model\OrderItem;
  11 +use think\queue\Job;
  12 +
  13 +/**
  14 + * 订单支付完成
  15 + */
  16 +class OrderPayed extends BaseJob
  17 +{
  18 + use StockSale;
  19 +
  20 + /**
  21 + * 订单支付完成
  22 + */
  23 + public function payed(Job $job, $data){
  24 + try {
  25 + $order = $data['order'];
  26 + $user = $data['user'];
  27 +
  28 + $order = Order::with('item')->where('id', $order['id'])->find();
  29 +
  30 + // 数据库删订单的问题常见,这里被删的订单直接把队列移除
  31 + if ($order) {
  32 + \think\Db::transaction(function () use ($order, $user, $data) {
  33 + // 触发订单支付完成事件
  34 + $data = ['order' => $order];
  35 + \think\Hook::listen('groupon_order_payed_after', $data);
  36 + });
  37 + }
  38 +
  39 + // 删除 job
  40 + $job->delete();
  41 + } catch (\Exception $e) {
  42 + // 队列执行失败
  43 + $error = json_encode([
  44 + 'a' => $e->getLine(),
  45 + 'b' => $e->getFile(),
  46 + 'c' =>$e->getTrace(),
  47 + 'd' => $e->getMessage()
  48 + ], JSON_UNESCAPED_UNICODE);
  49 +
  50 +
  51 + \think\Log::error('queue-' . get_class() . '-payed' . ':执行失败,错误信息:' . $error);
  52 + }
  53 + }
  54 +}
  1 +<?php
  2 +
  3 +return [
  4 + 'Keep login' => '保持会话',
  5 + 'Username' => '用户名',
  6 + 'User id' => '会员ID',
  7 + 'Nickname' => '昵称',
  8 + 'Password' => '密码',
  9 + 'Sign up' => '注 册',
  10 + 'Sign in' => '登 录',
  11 + 'Sign out' => '注 销',
  12 + 'Guest' => '游客',
  13 + 'Welcome' => '%s,你好!',
  14 + 'Add' => '添加',
  15 + 'Edit' => '编辑',
  16 + 'Delete' => '删除',
  17 + 'Move' => '移动',
  18 + 'Name' => '名称',
  19 + 'Status' => '状态',
  20 + 'Weigh' => '权重',
  21 + 'Operate' => '操作',
  22 + 'Warning' => '温馨提示',
  23 + 'Default' => '默认',
  24 + 'Article' => '文章',
  25 + 'Page' => '单页',
  26 + 'OK' => '确定',
  27 + 'Cancel' => '取消',
  28 + 'Loading' => '加载中',
  29 + 'More' => '更多',
  30 + 'Normal' => '正常',
  31 + 'Hidden' => '隐藏',
  32 + 'Submit' => '提交',
  33 + 'Reset' => '重置',
  34 + 'Execute' => '执行',
  35 + 'Close' => '关闭',
  36 + 'Search' => '搜索',
  37 + 'Refresh' => '刷新',
  38 + 'First' => '首页',
  39 + 'Previous' => '上一页',
  40 + 'Next' => '下一页',
  41 + 'Last' => '末页',
  42 + 'None' => '无',
  43 + 'Home' => '主页',
  44 + 'Online' => '在线',
  45 + 'Logout' => '注销',
  46 + 'Profile' => '个人资料',
  47 + 'Index' => '首页',
  48 + 'Hot' => '热门',
  49 + 'Recommend' => '推荐',
  50 + 'Dashboard' => '控制台',
  51 + 'Code' => '编号',
  52 + 'Message' => '内容',
  53 + 'Line' => '行号',
  54 + 'File' => '文件',
  55 + 'Menu' => '菜单',
  56 + 'Type' => '类型',
  57 + 'Title' => '标题',
  58 + 'Content' => '内容',
  59 + 'Append' => '追加',
  60 + 'Memo' => '备注',
  61 + 'Parent' => '父级',
  62 + 'Params' => '参数',
  63 + 'Permission' => '权限',
  64 + 'Advance search' => '高级搜索',
  65 + 'Check all' => '选中全部',
  66 + 'Expand all' => '展开全部',
  67 + 'Begin time' => '开始时间',
  68 + 'End time' => '结束时间',
  69 + 'Create time' => '创建时间',
  70 + 'Flag' => '标志',
  71 + 'Please login first' => '请登录后操作',
  72 + 'Redirect now' => '立即跳转',
  73 + 'Operation completed' => '操作成功!',
  74 + 'Operation failed' => '操作失败!',
  75 + 'Unknown data format' => '未知的数据格式!',
  76 + 'Network error' => '网络错误!',
  77 + 'Advanced search' => '高级搜索',
  78 + 'Invalid parameters' => '未知参数',
  79 + 'No results were found' => '记录未找到',
  80 + 'Parameter %s can not be empty' => '参数%s不能为空',
  81 + 'You have no permission' => '你没有权限访问',
  82 + 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
  83 + 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
  84 +];
  1 +<?php
  2 +
  3 +return [
  4 + 'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
  5 + 'Uploaded file format is limited' => '上传文件格式受限制',
  6 + 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
  7 + 'Upload successful' => '上传成功',
  8 +];
  1 +<?php
  2 +
  3 +return [
  4 + 'User center' => '会员中心',
  5 + 'Register' => '注册',
  6 + 'Login' => '登录',
  7 + 'Sign up successful' => '注册成功',
  8 + 'Username can not be empty' => '用户名不能为空',
  9 + 'Username must be 6 to 30 characters' => '用户名必须6-30个字符',
  10 + 'Password can not be empty' => '密码不能为空',
  11 + 'Password must be 6 to 30 characters' => '密码必须6-30个字符',
  12 + 'Mobile is incorrect' => '手机格式不正确',
  13 + 'Username already exist' => '用户名已经存在',
  14 + 'Email already exist' => '邮箱已经存在',
  15 + 'Mobile already exist' => '手机号已经存在',
  16 + 'Username is incorrect' => '用户名不正确',
  17 + 'Email is incorrect' => '邮箱不正确',
  18 + 'Account is locked' => '账户已经被锁定',
  19 + 'Password is incorrect' => '密码不正确',
  20 + 'Account is incorrect' => '账户不正确',
  21 + 'Account not exist' => '账户不存在',
  22 + 'Account can not be empty' => '账户不能为空',
  23 + 'Username or password is incorrect' => '用户名或密码不正确',
  24 + 'You are not logged in' => '你当前还未登录',
  25 + 'You\'ve logged in, do not login again' => '你已经存在,请不要重复登录',
  26 + 'Profile' => '个人资料',
  27 + 'Verify email' => '邮箱验证',
  28 + 'Change password' => '修改密码',
  29 + 'Captcha is incorrect' => '验证码不正确',
  30 + 'Logged in successful' => '登录成功',
  31 + 'Logout successful' => '注销成功',
  32 + 'Operation failed' => '操作失败',
  33 + 'Invalid parameters' => '参数不正确',
  34 + 'Change password failure' => '修改密码失败',
  35 + 'Change password successful' => '修改密码成功',
  36 + 'Reset password successful' => '重置密码成功',
  37 +];
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use EasyWeChat\Factory;
  6 +use PhpOffice\PhpSpreadsheet\Spreadsheet;
  7 +use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  8 +use League\Flysystem\Adapter\Local;
  9 +use League\Flysystem\Filesystem;
  10 +use Cache\Adapter\Filesystem\FilesystemCachePool;
  11 +use addons\groupon\library\Redis;
  12 +
  13 +/**
  14 + *
  15 + */
  16 +class Export
  17 +{
  18 +
  19 +
  20 + public function __construct()
  21 + {
  22 + }
  23 +
  24 + public function exportExcel($expTitle, $expCellName, $expTableData, &$spreadsheet = null, &$sheet = null, $pages = [])
  25 + {
  26 + $page = $pages['page'] ?? 1;
  27 + $page_size = $pages['page_size'] ?? 1000;
  28 + $is_last_page = $pages['is_last_page'] ?? 1;
  29 + $current_total = $pages['current_total'] ?? 0;
  30 +
  31 + if ($current_total) {
  32 + // 每次传来的 expTableData 数据条数不等,比如订单导出
  33 + $base_cell = $current_total - count($expTableData) + 2;
  34 + } else {
  35 + $base_cell = ($page - 1) * $page_size + 2;
  36 + }
  37 +
  38 + $fileName = $expTitle;
  39 + $cellNum = count($expCellName);
  40 + $dataNum = count($expTableData);
  41 + $cellName = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ');
  42 +
  43 + if ($page == 1) {
  44 + // 不限时
  45 + set_time_limit(0);
  46 + // 根据需要调大内存限制
  47 + ini_set('memory_limit', '512M');
  48 +
  49 + $cache_type = 'redis';
  50 + // 设置缓存
  51 + if ($cache_type == 'redis' && class_exists(\Cache\Adapter\Redis\RedisCachePool::class)) {
  52 + // 将表格数据暂存 redis,可以降低 php 进程内存占用,需要安装扩展包 composer require cache/simple-cache-bridge cache/redis-adapter
  53 + $options = [
  54 + // 'select' => 0 // 注释解开,并且换成一个空的 select 库,redis 库默认是 0-15 共 16 个库
  55 + ];
  56 +
  57 + $redis = (new Redis($options))->getRedis();
  58 + $pool = new \Cache\Adapter\Redis\RedisCachePool($redis);
  59 + $simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool);
  60 +
  61 + \PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache);
  62 + } else if ($cache_type == 'file' && class_exists(FilesystemCachePool::class)) {
  63 + // 将数据暂存磁盘,可以降低内存,但是导出速度会大幅下降 需要安装扩展包 composer require cache/filesystem-adapter
  64 + $path = ROOT_PATH . 'runtime' . DS . 'export/';
  65 + @mkdir($path);
  66 + $filesystemAdapter = new Local($path);
  67 + $filesystem = new Filesystem($filesystemAdapter);
  68 + $pool = new FilesystemCachePool($filesystem);
  69 +
  70 + \PhpOffice\PhpSpreadsheet\Settings::setCache($pool);
  71 + }
  72 +
  73 + // 实例化excel
  74 + $spreadsheet = new Spreadsheet();
  75 + // 初始化工作簿
  76 + $sheet = $spreadsheet->getActiveSheet(0);
  77 + // 给表头设置边框
  78 + $sheet->getStyle('A1:' . $cellName[$cellNum - 1] . '1')->getFont()->setBold(true);
  79 +
  80 + // 表头
  81 + $i = 0;
  82 + foreach ($expCellName as $key => $cell) {
  83 + $sheet->setCellValue($cellName[$i] . '1', $cell);
  84 + $i++;
  85 + }
  86 + }
  87 +
  88 + // for ($i = 0; $i < $cellNum; $i++) {
  89 + // $sheet->getColumnDimension($cellName[$i])->setWidth(30);
  90 + // }
  91 +
  92 + // 写入数据
  93 + for ($i = 0; $i < $dataNum; $i++) {
  94 + if ($is_last_page && $i == ($dataNum - 1)) {
  95 + $sheet->mergeCells('A' . ($i + $base_cell) . ':' . $cellName[$cellNum - 1] . ($i + $base_cell));
  96 + $sheet->setCellValue('A' . ($i + $base_cell), $expTableData[$i][key($expCellName)]);
  97 + } else {
  98 + $j = 0;
  99 + foreach ($expCellName as $key => $cell) {
  100 + $sheet->setCellValue($cellName[$j] . ($i + $base_cell), $expTableData[$i][$key]);
  101 + $j++;
  102 + }
  103 + }
  104 + }
  105 +
  106 + if ($is_last_page) {
  107 + // ini_set('memory_limit', '256M');
  108 +
  109 + ob_end_clean();
  110 + header('pragma:public');
  111 + header('Content-type:application/vnd.ms-excel;charset=utf-8;name="' . $fileName . '.xlsx"');
  112 + header("Content-Disposition:attachment;filename=$fileName.xlsx"); //attachment新窗口打印inline本窗口打印
  113 + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
  114 + $writer->save('php://output');
  115 + }
  116 + }
  117 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use fast\Http;
  6 +
  7 +class Express
  8 +{
  9 + // 查询接口
  10 + const REQURL = "https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
  11 + // 订阅接口
  12 + const SUBURL = "https://api.kdniao.com/api/dist";
  13 +
  14 + protected $config = [];
  15 +
  16 + /**
  17 + * 构造函数
  18 + */
  19 + public function __construct()
  20 + {
  21 + $config = \addons\groupon\model\Config::get(['name' => 'services']);
  22 + $config = ($config && $config->value) ? json_decode($config->value, true) : [];
  23 +
  24 + $expressConfig = $config['express'] ?? [];
  25 + if (!$expressConfig || !$expressConfig['ebusiness_id'] || !$expressConfig['appkey']) {
  26 + throw new \Exception('请配置快递接口');
  27 + }
  28 +
  29 + $this->config = $expressConfig;
  30 + }
  31 +
  32 +
  33 + /**
  34 + * Json方式 物流信息订阅
  35 + */
  36 + public function subscribe($data = [], $orderExpress = null, $order = null)
  37 + {
  38 + $requestData = $this->getRequestData($data, $orderExpress, $order);
  39 +
  40 + $datas = [
  41 + 'EBusinessID' => $this->config['ebusiness_id'],
  42 + 'RequestType' => $this->config['type'] == 'free' ? '1008' : '8008',
  43 + 'RequestData' => urlencode($requestData),
  44 + 'DataType' => '2',
  45 + ];
  46 + $datas['DataSign'] = $this->encrypt($requestData, $this->config['appkey']);
  47 +
  48 + $result = Http::sendRequest(self::SUBURL, $datas, 'POST', []);
  49 +
  50 + if ($result['ret'] == 1) {
  51 + $exResult = json_decode($result['msg'], true);
  52 +
  53 + if (!$exResult['Success']) {
  54 + throw new \Exception($exResult['Reason']);
  55 + }
  56 +
  57 + return $exResult;
  58 + } else {
  59 + throw new \Exception($result['msg']);
  60 + }
  61 + }
  62 +
  63 +
  64 + // 查询快递信息
  65 + public function search($data = [], $orderExpress = null, $order = null)
  66 + {
  67 + $requestData = $this->getRequestData($data, $orderExpress, $order);
  68 +
  69 + $datas = [
  70 + 'EBusinessID' => $this->config['ebusiness_id'],
  71 + 'RequestType' => $this->config['type'] == 'free' ? '1002' : '8001',
  72 + 'RequestData' => urlencode($requestData),
  73 + 'DataType' => '2',
  74 + ];
  75 + $datas['DataSign'] = $this->encrypt($requestData, $this->config['appkey']);
  76 + $result = Http::sendRequest(self::REQURL, $datas, 'POST', []);
  77 +
  78 + if ($result['ret'] == 1) {
  79 + $exResult = json_decode($result['msg'], true);
  80 +
  81 + if (!$exResult['Success']) {
  82 + throw new \Exception($exResult['Reason']);
  83 + }
  84 +
  85 + return $exResult;
  86 + } else {
  87 + throw new \Exception($result['msg']);
  88 + }
  89 + }
  90 +
  91 +
  92 + // 组装请求数据
  93 + private function getRequestData($data = [], $orderExpress = null, $order = null) {
  94 + $requestData = [
  95 + 'OrderCode' => $order ? $order->order_sn : ($data['order_code'] ?? ''),
  96 + 'ShipperCode' => $data['express_code'],
  97 + 'LogisticCode' => $data['express_no'],
  98 + ];
  99 +
  100 + if ($data['express_code'] == 'JD') {
  101 + // 京东青龙配送单号
  102 + $requestData['CustomerName'] = $this->config['jd_code'];
  103 + } else if ($data['express_code'] == 'SF') {
  104 + // 收件人手机号后四位
  105 + $requestData['CustomerName'] = substr($order->phone, 7);
  106 + }
  107 +
  108 + $requestData = json_encode($requestData);
  109 +
  110 + return $requestData;
  111 + }
  112 +
  113 +
  114 +
  115 + // 差异更新物流信息
  116 + public function checkAndAddTraces ($orderExpress, $express) {
  117 + $traces = $express['Traces'];
  118 +
  119 + // 查询现有轨迹记录
  120 + $orderExpressLog = \addons\groupon\model\OrderExpressLog::where('order_express_id', $orderExpress->id)->where('store_id', $orderExpress['store_id'])->select();
  121 +
  122 + $log_count = count($orderExpressLog);
  123 + if ($log_count > 0) {
  124 + // 移除已经存在的记录
  125 + array_splice($traces, 0, $log_count);
  126 + }
  127 +
  128 + // 增加包裹记录
  129 + foreach ($traces as $k => $trace) {
  130 + $orderExpressLog = new \addons\groupon\model\OrderExpressLog();
  131 +
  132 + $orderExpressLog->user_id = $orderExpress['user_id'] ?? 0;
  133 + $orderExpressLog->order_id = $orderExpress['order_id'] ?? 0;
  134 + $orderExpressLog->store_id = $orderExpress['store_id'] ?? 0;
  135 + $orderExpressLog->order_express_id = $orderExpress['id'];
  136 + $orderExpressLog->status = $trace['Action'] ?? $express['State'];
  137 + $orderExpressLog->content = $trace['AcceptStation'];
  138 + $orderExpressLog->changedate = substr($trace['AcceptTime'], 0, 19); // 快递鸟测试数据 返回的是个 2020-08-03 16:58:272 格式
  139 + $orderExpressLog->location = $trace['Location'] ?? ($express['Location'] ?? null);
  140 + $orderExpressLog->save();
  141 + }
  142 + }
  143 +
  144 +
  145 + // 组装返回结果
  146 + public function setPushResult($success = false, $reason = '') {
  147 + $result = [
  148 + "EBusinessID" => $this->config['ebusiness_id'],
  149 + "UpdateTime" => date('Y-m-d H:i:s'),
  150 + "Success" => $success,
  151 + "Reason" => $reason
  152 + ];
  153 +
  154 + return $result;
  155 + }
  156 +
  157 +
  158 + // 加签
  159 + function encrypt($data, $appkey)
  160 + {
  161 + return urlencode(base64_encode(md5($data . $appkey)));
  162 + }
  163 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use EasyWeChat\Factory;
  6 +use addons\groupon\model\Config;
  7 +use think\Model;
  8 +
  9 +/**
  10 + *
  11 + */
  12 +class Hook
  13 +{
  14 +
  15 + public function __construct()
  16 + {
  17 +
  18 + }
  19 +
  20 + public static function register ($behaviors = []) {
  21 + $default = require ROOT_PATH . 'addons/groupon/hooks.php';
  22 +
  23 + $behaviors = array_merge($default, $behaviors);
  24 + foreach ($behaviors as $tag => $behavior) {
  25 + // 数组反转 保证最上面的行为优先级最高
  26 + $behavior = array_reverse($behavior);
  27 + foreach ($behavior as $be) {
  28 + \think\Hook::add($tag, $be, true); // 所有行为都插入最前面
  29 + }
  30 + }
  31 + }
  32 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use app\admin\library\Auth as AdminAuth;
  6 +use addons\groupon\model\Store;
  7 +use addons\groupon\model\User;
  8 +
  9 +class Oper
  10 +{
  11 + public static function set($operType = '', $operId = 0)
  12 + {
  13 + if ($operType === '') {
  14 + // 自动获取操作人
  15 + $admin = AdminAuth::instance(); // 没有登录返回的还是这个类实例
  16 + if ($admin->isLogin()) {
  17 + // 后台管理员
  18 + $operType = 'admin';
  19 + $operId = $admin->id;
  20 + } else if (strpos(request()->url(), 'store.store') !== false) {
  21 + // 门店
  22 + $store = Store::info();
  23 + if ($store) {
  24 + $operType = 'store';
  25 + $operId = $store['id'];
  26 + }
  27 + } else if (strpos(request()->url(), 'addons/groupon') !== false) {
  28 + // 用户
  29 + $user = User::info();
  30 + if ($user) {
  31 + $operType = 'user';
  32 + $operId = $user->id;
  33 + }
  34 + }
  35 + }
  36 + if ($operType === '') {
  37 + $operType = 'system';
  38 + }
  39 + return [
  40 + 'oper_type' => $operType,
  41 + 'oper_id' => $operId
  42 + ];
  43 + }
  44 +
  45 + public static function get($operType, $operId)
  46 + {
  47 + $operator = null;
  48 + if ($operType === 'admin') {
  49 + $operator = \app\admin\model\Admin::where('id', $operId)->field('nickname as name, avatar')->find();
  50 + $operator['type'] = '管理员';
  51 + } elseif ($operType === 'user') {
  52 + $operator = \addons\groupon\model\User::where('id', $operId)->field('nickname as name, avatar')->find();
  53 + $operator['type'] = '用户';
  54 + } elseif ($operType === 'store') {
  55 + $operator = \addons\groupon\model\Store::where('id', $operId)->field('name')->find();
  56 + $operator['type'] = '用户';
  57 + $operator['avatar'] = '';
  58 + } else {
  59 + $operator = [
  60 + 'name' => '系统',
  61 + 'avatar' => '',
  62 + 'type' => '系统'
  63 + ];
  64 + }
  65 + if(!isset($operator['name'])) {
  66 + $operator['name'] = '已删除';
  67 + $operator['avatar'] = '';
  68 + }
  69 + return $operator;
  70 + }
  71 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use Yansongda\Pay\Pay;
  6 +use Yansongda\Pay\Log;
  7 +use addons\groupon\exception\Exception;
  8 +
  9 +class PayService
  10 +{
  11 + protected $config;
  12 + protected $platform;
  13 + protected $payment;
  14 + protected $notify_url;
  15 + public $method;
  16 +
  17 +
  18 + public function __construct($payment, $platform = '', $notify_url = '', $type = 'pay')
  19 + {
  20 + $this->platform = $platform;
  21 + $this->payment = $payment;
  22 + $this->notify_url = $notify_url;
  23 + $this->type = $type;
  24 +
  25 + $this->setPaymentConfig();
  26 + }
  27 +
  28 + private function setPaymentConfig()
  29 + {
  30 + $paymentConfig = json_decode(\addons\groupon\model\Config::get(['name' => $this->payment])->value, true);
  31 +
  32 + // 如果是支付,并且不是 复制地址的支付宝支付
  33 + if ($this->type == 'pay' && $this->platform != 'url' && !in_array($this->platform, $paymentConfig['platform'])) {
  34 + throw new Exception('暂不支持该方式付款');
  35 + }
  36 +
  37 + $this->config = $paymentConfig;
  38 + $this->config['notify_url'] = $this->notify_url;
  39 +
  40 + $this->config['http'] = [
  41 + 'timeout' => 10,
  42 + 'connect_timeout' => 10,
  43 + ];
  44 +
  45 + if ($this->payment === 'wechat') {
  46 + // 根据不同平台设置相应的 appid
  47 + $this->setWechatAppId();
  48 + }
  49 +
  50 + // 设置支付证书路径
  51 + $this->setCert();
  52 + }
  53 +
  54 + private function setWechatAppId()
  55 + {
  56 + switch ($this->platform) {
  57 + case 'wxOfficialAccount':
  58 + $platformConfig = json_decode(\addons\groupon\model\Config::get(['name' => $this->platform])->value, true);
  59 + if (isset($this->config['mode']) && $this->config['mode'] === 'service') {
  60 + $this->config['sub_app_id'] = $platformConfig['app_id'];
  61 + $this->config['app_id'] = $this->config['app_id']; // 主商户号,关联的 app_id
  62 + } else {
  63 + $this->config['app_id'] = $platformConfig['app_id'];
  64 + }
  65 + break;
  66 + case 'wxMiniProgram':
  67 + $platformConfig = json_decode(\addons\groupon\model\Config::get(['name' => $this->platform])->value, true);
  68 + if (isset($this->config['mode']) && $this->config['mode'] === 'service') {
  69 + $this->config['sub_miniapp_id'] = $platformConfig['app_id'];
  70 + // $this->config['sub_app_id'] = $platformConfig['app_id'];
  71 + $this->config['miniapp_id'] = $this->config['app_id']; // 主商户号,关联的 app_id
  72 + } else {
  73 + $this->config['miniapp_id'] = $platformConfig['app_id'];
  74 + $this->config['app_id'] = $platformConfig['app_id']; // 小程序微信企业付款
  75 + }
  76 + break;
  77 + case 'H5':
  78 + $platformConfig = json_decode(\addons\groupon\model\Config::get(['name' => $this->platform])->value, true);
  79 + if (isset($this->config['mode']) && $this->config['mode'] === 'service') {
  80 + $this->config['sub_app_id'] = $platformConfig['app_id'];
  81 + $this->config['appid'] = $this->config['app_id']; // 主商户号,关联的 app_id
  82 + } else {
  83 + $this->config['app_id'] = $platformConfig['app_id'];
  84 + }
  85 + break;
  86 + case 'App':
  87 + $platformConfig = json_decode(\addons\groupon\model\Config::get(['name' => 'App'])->value, true);
  88 + if (isset($this->config['mode']) && $this->config['mode'] === 'service') {
  89 + $this->config['sub_appid'] = $platformConfig['app_id'];
  90 + $this->config['sub_app_id'] = $platformConfig['app_id'];
  91 + $this->config['appid'] = $this->config['app_id']; // 主商户号,关联的 app_id
  92 + } else {
  93 + $this->config['appid'] = $platformConfig['app_id']; // 微信 App 支付使用这个
  94 + $this->config['app_id'] = $platformConfig['app_id']; // 微信 App 支付退款使用的是这个
  95 + }
  96 +
  97 + break;
  98 + }
  99 + }
  100 +
  101 +
  102 + // 处理证书路径
  103 + private function setCert()
  104 + {
  105 + // 处理证书路径
  106 + if ($this->payment == 'wechat') {
  107 + // 微信支付证书
  108 + $this->config['cert_client'] = ROOT_PATH . 'public' . $this->config['cert_client'];
  109 + $this->config['cert_key'] = ROOT_PATH . 'public' . $this->config['cert_key'];
  110 + } else {
  111 + // 支付宝证书路径
  112 + $end = substr($this->config['ali_public_key'], -4);
  113 + if ($end == '.crt') {
  114 + $this->config['ali_public_key'] = ROOT_PATH . 'public' . $this->config['ali_public_key'];
  115 + }
  116 + $this->config['app_cert_public_key'] = ROOT_PATH . 'public' . $this->config['app_cert_public_key'];
  117 + $this->config['alipay_root_cert'] = ROOT_PATH . 'public' . $this->config['alipay_root_cert'];
  118 + }
  119 + }
  120 +
  121 + private function setPaymentMethod()
  122 + {
  123 + $method = [
  124 + 'wechat' => [
  125 + 'wxOfficialAccount' => 'mp', //公众号支付 Collection
  126 + 'wxMiniProgram' => 'miniapp', //小程序支付
  127 + 'H5' => 'wap', //手机网站支付 Response
  128 + 'App' => 'app' //APP 支付 JsonResponse
  129 + ],
  130 + 'alipay' => [
  131 + 'wxOfficialAccount' => 'wap', //手机网站支付 Response
  132 + 'wxMiniProgram' => 'wap', //小程序支付
  133 + 'H5' => 'wap', //手机网站支付 Response
  134 + 'url' => 'wap', //手机网站支付 Response
  135 + 'App' => 'app' //APP 支付 JsonResponse
  136 + ],
  137 + ];
  138 +
  139 + $this->method = $method[$this->payment][$this->platform];
  140 + }
  141 +
  142 + public function create($order)
  143 + {
  144 + // $order = [
  145 + // 'out_trade_no' => time(),
  146 + // 'total_fee' => '1', // **单位:分**
  147 + // 'body' => 'test body - 测试',
  148 + // 'openid' => 'onkVf1FjWS5SBIixxxxxxx', //微信需要带openid过来
  149 + // ];
  150 + // 设置支付方式
  151 + $this->setPaymentMethod();
  152 +
  153 + $method = $this->method;
  154 + switch ($this->payment) {
  155 + case 'wechat':
  156 + if (isset($this->config['mode']) && $this->config['mode'] === 'service') {
  157 + $order['sub_openid'] = $order['openid'] ?? '';
  158 + unset($order['openid']);
  159 + }
  160 +
  161 + $order['total_fee'] = $order['total_fee'] * 100;
  162 + $pay = Pay::wechat($this->config)->$method($order);
  163 +
  164 + break;
  165 + case 'alipay':
  166 + if (in_array($this->platform, ['wxOfficialAccount', 'wxMiniProgram', 'H5'])) {
  167 + // 返回支付宝支付链接
  168 + $pay = request()->domain() . '/addons/groupon/pay/alipay?order_sn=' . $order['out_trade_no'];
  169 + } else {
  170 + if ($this->method == 'wap') {
  171 + // 支付宝 wap 支付,增加 return_url
  172 + // 获取 h5 域名
  173 + $platformConfig = json_decode(\addons\groupon\model\Config::get(['name' => 'groupon'])->value, true);
  174 + // 如果域名存在,增加 return_url
  175 + if ($platformConfig && isset($platformConfig['domain'])) {
  176 + $start = substr($platformConfig['domain'], -1) == '/' ? "" : "/";
  177 + $this->config['return_url'] = $platformConfig['domain'] . $start . "pages/order/payment/result?orderSn=" . $order['out_trade_no'] . "&type=alipay&pay=1";
  178 + }
  179 + }
  180 +
  181 + $pay = Pay::alipay($this->config)->$method($order);
  182 + }
  183 +
  184 + break;
  185 + }
  186 +
  187 + return $pay;
  188 + }
  189 +
  190 +
  191 + // 企业付款
  192 + public function transfer($payload)
  193 + {
  194 + // 服务商模式企业付款使用子商户证书
  195 + if ($this->payment == 'wechat' && isset($this->config['mode']) && $this->config['mode'] === 'service') {
  196 + $this->config['cert_client'] = ROOT_PATH . 'public' . $this->config['sub_cert_client'];
  197 + $this->config['cert_key'] = ROOT_PATH . 'public' . $this->config['sub_cert_key'];
  198 + $this->config['key'] = $this->config['sub_key'];
  199 + $this->config['mch_id'] = $this->config['sub_mch_id'];
  200 + $this->config['mode'] = 'normal'; // 临时改为普通商户
  201 + }
  202 +
  203 + $code = 0;
  204 + $response = [];
  205 + // 支付宝返回结果
  206 + // [code] => 10000
  207 + // [msg] => Success
  208 + // [order_id] => 20210309110070000006730051669626
  209 + // [out_biz_no] => W202107231625700647008000
  210 + // [pay_fund_order_id] => 20210309110070001506730065959451
  211 + // [status] => SUCCESS
  212 + // [trans_date] => 2021-03-09 10:42:23
  213 +
  214 + // 微信返回结果
  215 + // [return_code] => SUCCESS
  216 + // [return_msg] => Array
  217 + // (
  218 + // )
  219 + // [mch_appid] => wx38939920ace0d244
  220 + // [mchid] => 1481069012
  221 + // [nonce_str] => O9fwzb6HDGO2zU6H
  222 + // [result_code] => SUCCESS
  223 + // [partner_trade_no] => W202106361960501154008000
  224 + // [payment_no] => 10100294905592102257932685920233
  225 + // [payment_time] => 2021-02-25 18:36:30
  226 + switch ($this->payment) {
  227 + case 'wechat':
  228 + $payload['amount'] = $payload['amount'] * 100;
  229 + $response = Pay::wechat($this->config)->transfer($payload);
  230 + if ($response['return_code'] === 'SUCCESS' && $response['result_code'] === 'SUCCESS') {
  231 + $code = 1;
  232 + }
  233 + break;
  234 + case 'alipay':
  235 + $response = Pay::alipay($this->config)->transfer($payload);
  236 + if ($response['code'] === '10000' && $response['status'] === 'SUCCESS') {
  237 + $code = 1;
  238 + }
  239 + break;
  240 + }
  241 +
  242 + return [$code, $response];
  243 + }
  244 +
  245 +
  246 + public function notify($callback)
  247 + {
  248 + $pay = $this->getPay();
  249 +
  250 + try {
  251 + $data = $pay->verify(); // 是的,验签就这么简单!
  252 +
  253 + $result = $callback($data, $pay);
  254 +
  255 + // Log::debug('Wechat notify', $data->all());
  256 + } catch (\Exception $e) {
  257 + \think\Log::error('notify-error:' . $e->getMessage());
  258 + // $e->getMessage();
  259 + }
  260 +
  261 + return $result;
  262 + }
  263 +
  264 +
  265 + public function refund($order_data)
  266 + {
  267 + $pay = $this->getPay();
  268 +
  269 + $order_data['type'] = $this->platform == 'wxMiniProgram' ? 'miniapp' : '';
  270 +
  271 + $result = $pay->refund($order_data);
  272 +
  273 + \think\Log::write('refund-result' . json_encode($result));
  274 +
  275 + if ($this->payment == 'wechat') {
  276 + // 微信通知回调 pay->notifyr
  277 + if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
  278 + return $result;
  279 + } else {
  280 + throw new \Exception($result['return_msg']);
  281 + }
  282 + } else {
  283 + // 支付宝通知回调 pay->notifyx
  284 + if ($result['code'] == "10000") {
  285 + return $result;
  286 + } else {
  287 + throw new \Exception($result['msg']);
  288 + }
  289 + }
  290 + }
  291 +
  292 +
  293 + public function notifyRefund($callback)
  294 + {
  295 + $pay = $this->getPay();
  296 +
  297 + try {
  298 + $data = $pay->verify(null, true); // 是的,验签就这么简单!
  299 + \think\Log::write('notifyr-result:' . json_encode($data));
  300 +
  301 + $result = $callback($data, $pay);
  302 +
  303 + // Log::debug('Wechat notify', $data->all());
  304 + } catch (\Exception $e) {
  305 + \think\Log::write('notifyr-verify-error:' . $e->getMessage()); // $e->getMessage();
  306 +
  307 + return false;
  308 + }
  309 +
  310 + return $result;
  311 + }
  312 +
  313 +
  314 + private function getPay()
  315 + {
  316 + switch ($this->payment) {
  317 + case 'wechat':
  318 + $pay = Pay::wechat($this->config);
  319 + break;
  320 + case 'alipay':
  321 + $pay = Pay::alipay($this->config);
  322 + break;
  323 + default:
  324 + throw new Exception('支付方式不支持');
  325 + }
  326 +
  327 + return $pay;
  328 + }
  329 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +
  7 +class Redis
  8 +{
  9 + protected $handler = null;
  10 +
  11 + protected $options = [
  12 + 'host' => '127.0.0.1',
  13 + 'port' => 6379,
  14 + 'password' => '',
  15 + 'select' => 0,
  16 + 'timeout' => 0,
  17 + 'expire' => 0,
  18 + 'persistent' => false,
  19 + 'prefix' => '',
  20 + ];
  21 +
  22 + /**
  23 + * 构造函数
  24 + * @param array $options 缓存参数
  25 + * @access public
  26 + */
  27 + public function __construct($options = [])
  28 + {
  29 + if (!extension_loaded('redis')) {
  30 + throw new \BadFunctionCallException('not support: redis');
  31 + }
  32 +
  33 + // 获取 redis 配置
  34 + $config = \think\Config::get('redis');
  35 + if (empty($config) && empty($options)) {
  36 + throw new \Exception('redis connection fail: no redis config');
  37 + }
  38 +
  39 + if (!empty($config)) {
  40 + $this->options = array_merge($this->options, $config);
  41 + }
  42 +
  43 + if (!empty($options)) {
  44 + $this->options = array_merge($this->options, $options);
  45 + }
  46 + $this->handler = new \Redis();
  47 + if ($this->options['persistent']) {
  48 + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
  49 + } else {
  50 + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
  51 + }
  52 +
  53 + if ('' != $this->options['password']) {
  54 + $this->handler->auth($this->options['password']);
  55 + }
  56 +
  57 + if (0 != $this->options['select']) {
  58 + $this->handler->select($this->options['select']);
  59 + }
  60 +
  61 + // 赋值全局,避免多次实例化
  62 + $GLOBALS['SPREDIS'] = $this->handler;
  63 + }
  64 +
  65 + public function getRedis() {
  66 + return $this->handler;
  67 + }
  68 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library;
  4 +
  5 +use EasyWeChat\Factory;
  6 +use addons\groupon\model\Config;
  7 +use think\Model;
  8 +use fast\Http;
  9 +
  10 +/**
  11 + *
  12 + */
  13 +class Wechat extends Model
  14 +{
  15 + protected $config;
  16 + protected $app;
  17 +
  18 +
  19 + public function __construct($platform)
  20 + {
  21 + $this->config = Config::getEasyWechatConfig($platform);
  22 + switch ($platform) {
  23 + case 'wxOfficialAccount':
  24 + $this->app = Factory::officialAccount($this->config);
  25 + break;
  26 + case 'wxMiniProgram':
  27 + $this->app = Factory::miniProgram($this->config);
  28 + break;
  29 + }
  30 +
  31 + }
  32 +
  33 + // 返回实例
  34 + public function getApp() {
  35 + return $this->app;
  36 + }
  37 +
  38 + //小程序:获取openid&session_key
  39 + public function code($code)
  40 + {
  41 + return $this->app->auth->session($code);
  42 + }
  43 +
  44 + public function oauth()
  45 + {
  46 + $oauth = $this->app->oauth;
  47 + return $oauth;
  48 + }
  49 +
  50 + //解密信息
  51 + public function decryptData($session, $iv, $encryptData)
  52 + {
  53 + $data = $this->app->encryptor->decryptData($session, $iv, $encryptData);
  54 +
  55 + return $data;
  56 + }
  57 +
  58 + public function unify($orderBody)
  59 + {
  60 + $result = $this->app->order->unify($orderBody);
  61 + return $result;
  62 + }
  63 +
  64 + public function bridgeConfig($prepayId)
  65 + {
  66 + $jssdk = $this->app->jssdk;
  67 + $config = $jssdk->bridgeConfig($prepayId, false);
  68 + return $config;
  69 + }
  70 +
  71 + public function notify()
  72 + {
  73 + $result = $this->app;
  74 + return $result;
  75 + }
  76 +
  77 + //获取accessToken
  78 + public function getAccessToken()
  79 + {
  80 + $accessToken = $this->app->access_token;
  81 + $token = $accessToken->getToken(); // token 数组 token['access_token'] 字符串
  82 + //$token = $accessToken->getToken(true); // 强制重新从微信服务器获取 token.
  83 + return $token;
  84 + }
  85 +
  86 + public function sendTemplateMessage($attributes)
  87 + {
  88 + extract($attributes);
  89 + $this->app->template_message->send([
  90 + 'touser' => $openId,
  91 + 'template_id' => $templateId,
  92 + 'page' => $page,
  93 + 'form_id' => $formId,
  94 + 'data' => $data,
  95 + 'emphasis_keyword' => $emphasis_keyword
  96 + ]);
  97 + }
  98 +
  99 +
  100 + // 同步小程序直播
  101 + public function live(Array $params = [])
  102 + {
  103 + $default = [
  104 + 'start' => 0,
  105 + 'limit' => 10
  106 + ];
  107 + $params = array_merge($default, $params);
  108 + $default = json_encode($params);
  109 +
  110 +
  111 + $access_token = $this->app->access_token->getToken();
  112 + $getRoomsListUrl = "https://api.weixin.qq.com/wxa/business/getliveinfo?access_token={$access_token['access_token']}";
  113 + $headers = ['Content-type: application/json'];
  114 + $options = [
  115 + CURLOPT_HTTPHEADER => $headers
  116 + ];
  117 + $result = Http::sendRequest($getRoomsListUrl, $default, 'POST', $options);
  118 + if (isset($result['ret']) && $result['ret']) {
  119 + $msg = json_decode($result['msg'], true);
  120 + $result = $msg;
  121 + }
  122 +
  123 +// $result = $this->app->live->getRooms(...array_values($params));
  124 +
  125 + $rooms = [];
  126 + if ($result && $result['errcode'] == 0 && $result['errmsg'] === 'ok') {
  127 + $rooms = $result['room_info'];
  128 + }
  129 +
  130 + return $rooms;
  131 + }
  132 +
  133 + // 小程序直播回放
  134 + public function liveReplay(array $params = [])
  135 + {
  136 + $default = [
  137 + 'room_id' => 0,
  138 + 'start' => 0,
  139 + 'limit' => 20
  140 + ];
  141 +
  142 + $params = array_merge($default, $params);
  143 + $default = json_encode($params);
  144 + $access_token = $this->app->access_token->getToken();
  145 + $getPlayBackListUrl = "https://api.weixin.qq.com/wxa/business/getliveinfo?access_token={$access_token['access_token']}";
  146 + $headers = ['Content-type: application/json'];
  147 + $options = [
  148 + CURLOPT_HTTPHEADER => $headers
  149 + ];
  150 + $result = Http::sendRequest($getPlayBackListUrl, $default, 'POST', $options);
  151 + if (isset($result['ret']) && $result['ret']) {
  152 + $msg = json_decode($result['msg'], true);
  153 + $result = $msg;
  154 + }
  155 +// $result = $this->app->live->getPlaybacks(...array_values($params));
  156 +
  157 + $liveReplay = [];
  158 + if ($result && $result['errcode'] == 0 && $result['errmsg'] === 'ok') {
  159 + $liveReplay = $result['live_replay'];
  160 + }
  161 +
  162 + return $liveReplay;
  163 + }
  164 +
  165 + public function menu($act = 'create', $buttons = '')
  166 + {
  167 + $result = $this->app->menu->$act($buttons);
  168 + return $result;
  169 +
  170 + }
  171 +
  172 + // 公众号 获取所有粉丝
  173 + public function asyncFans($nextOpenId = null, $currentPage = 1, $totalPage = 1)
  174 + {
  175 + $fans = $this->app->user->list($nextOpenId);
  176 + $openIdsArray = $fans['data']['openid'];
  177 + //放入最大10000条openid队列去执行
  178 + \think\Queue::push('\addons\groupon\job\Wechat@createQueueByOpenIdsArray', $openIdsArray, 'groupon');
  179 + //第一次计算总页数
  180 + if ($currentPage === 1) {
  181 + $totalPage = intval($fans['total'] % $fans['count'] === 0 ? $fans['total'] / $fans['count'] : ceil($fans['total'] / $fans['count']));
  182 + }
  183 + //有分页 递归下一页
  184 + if ($currentPage < $totalPage) {
  185 + $openIdsArray = array_merge($openIdsArray, $this->asyncFans($fans['next_openid'], $currentPage++, $totalPage));
  186 + }
  187 + if ($currentPage == $totalPage) {
  188 + if ($totalPage == 1) {
  189 + $code = 1;
  190 + $msg = '同步成功';
  191 + }else{
  192 + $code = 1;
  193 + $msg = '数据较大,请稍后再查看...';
  194 + }
  195 + return [
  196 + 'code' => $code,
  197 + 'msg' => $msg
  198 + ];
  199 + }
  200 + return $openIdsArray;
  201 + }
  202 +
  203 + //通过openid获取已经关注的用户信息
  204 + public function getSubscribeUserInfoByOpenId(array $openIdsArray)
  205 + {
  206 + $result = $this->app->user->select($openIdsArray);
  207 + return $result;
  208 + }
  209 +
  210 +
  211 +
  212 +
  213 +}
  1 +<?php
  2 +
  3 +namespace addons\groupon\library\notify;
  4 +
  5 +use addons\groupon\exception\Exception;
  6 +use think\queue\ShouldQueue;
  7 +/**
  8 + * 消息通知 trait
  9 + */
  10 +
  11 +trait Notifiable
  12 +{
  13 + public function notify ($notification) {
  14 + return \addons\groupon\library\notify\Notify::send([$this], $notification);
  15 + }
  16 +
  17 +}