From 2f7039526ac2e922850703b425fa8febfa4c6b6e Mon Sep 17 00:00:00 2001 From: mac Date: Mon, 8 Jun 2026 14:58:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BA=AF=E7=98=BE=E5=A4=A7=20ThinkPHP?= =?UTF-8?q?=208=20=E5=90=8E=E7=AB=AF=20v2.1=20R3=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 5 Controller: Card/Menu/Order/Message/Staff (16 API) - 5 Model: Product/Order/OrderItem/Message/Staff - 2 Service: CardService(号码生成+冲突检测)/OrderService(下单事务+催单) - 1 Middleware: StaffAuth(HMAC-SHA256无状态Token) - BUG-01修复: 催单归属+30s冷却+5次上限 - BUG-02修复: 订单状态机校验(confirm/done/cancel) - BUG-03修复: 消息输入校验(长度/枚举/XSS) - BUG-04修复: 下单qty 1-99校验 - APP_DEBUG=false 生产就绪 --- .env.example | 11 +++ .example.env | 11 +++ .gitignore | 6 ++ .travis.yml | 42 ++++++++++ LICENSE.txt | 32 ++++++++ README.md | 77 ++++++++++++++++++ app/.htaccess | 1 + app/AppService.php | 22 ++++++ app/BaseController.php | 94 ++++++++++++++++++++++ app/ExceptionHandle.php | 58 ++++++++++++++ app/Request.php | 8 ++ app/common.php | 2 + app/controller/Card.php | 35 +++++++++ app/controller/Index.php | 18 +++++ app/controller/Menu.php | 52 +++++++++++++ app/controller/Message.php | 76 ++++++++++++++++++ app/controller/Order.php | 58 ++++++++++++++ app/controller/Staff.php | 180 +++++++++++++++++++++++++++++++++++++++++++ app/event.php | 17 ++++ app/middleware.php | 10 +++ app/middleware/StaffAuth.php | 47 +++++++++++ app/model/Message.php | 13 ++++ app/model/Order.php | 15 ++++ app/model/OrderItem.php | 15 ++++ app/model/Product.php | 20 +++++ app/model/Staff.php | 10 +++ app/provider.php | 9 +++ app/service.php | 9 +++ app/service/CardService.php | 41 ++++++++++ app/service/OrderService.php | 122 +++++++++++++++++++++++++++++ composer.json | 49 ++++++++++++ config/app.php | 14 ++++ config/cache.php | 29 +++++++ config/console.php | 9 +++ config/cookie.php | 20 +++++ config/database.php | 31 ++++++++ config/filesystem.php | 24 ++++++ config/lang.php | 29 +++++++ config/log.php | 45 +++++++++++ config/middleware.php | 7 ++ config/route.php | 55 +++++++++++++ config/session.php | 19 +++++ config/trace.php | 10 +++ config/view.php | 25 ++++++ extend/.gitignore | 2 + public/.htaccess | 8 ++ public/drink | 0 public/favicon.ico | Bin 0 -> 5434 bytes public/index.php | 25 ++++++ public/robots.txt | 2 + public/router.php | 19 +++++ public/static/.gitignore | 2 + route/app.php | 24 ++++++ think | 11 +++ view/README.md | 1 + 55 files changed, 1571 insertions(+) create mode 100644 .env.example create mode 100644 .example.env create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 app/.htaccess create mode 100644 app/AppService.php create mode 100644 app/BaseController.php create mode 100644 app/ExceptionHandle.php create mode 100644 app/Request.php create mode 100644 app/common.php create mode 100644 app/controller/Card.php create mode 100644 app/controller/Index.php create mode 100644 app/controller/Menu.php create mode 100644 app/controller/Message.php create mode 100644 app/controller/Order.php create mode 100644 app/controller/Staff.php create mode 100644 app/event.php create mode 100644 app/middleware.php create mode 100644 app/middleware/StaffAuth.php create mode 100644 app/model/Message.php create mode 100644 app/model/Order.php create mode 100644 app/model/OrderItem.php create mode 100644 app/model/Product.php create mode 100644 app/model/Staff.php create mode 100644 app/provider.php create mode 100644 app/service.php create mode 100644 app/service/CardService.php create mode 100644 app/service/OrderService.php create mode 100644 composer.json create mode 100644 config/app.php create mode 100644 config/cache.php create mode 100644 config/console.php create mode 100644 config/cookie.php create mode 100644 config/database.php create mode 100644 config/filesystem.php create mode 100644 config/lang.php create mode 100644 config/log.php create mode 100644 config/middleware.php create mode 100644 config/route.php create mode 100644 config/session.php create mode 100644 config/trace.php create mode 100644 config/view.php create mode 100644 extend/.gitignore create mode 100644 public/.htaccess create mode 100644 public/drink create mode 100644 public/favicon.ico create mode 100644 public/index.php create mode 100644 public/robots.txt create mode 100644 public/router.php create mode 100644 public/static/.gitignore create mode 100644 route/app.php create mode 100644 think create mode 100644 view/README.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..36ee9d2 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +APP_DEBUG = false + +DB_TYPE = mysql +DB_HOST = 127.0.0.1 +DB_NAME = drink +DB_USER = drink_app +DB_PASS = your_password_here +DB_PORT = 3306 +DB_CHARSET = utf8mb4 + +DEFAULT_LANG = zh-cn diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..c457fe5 --- /dev/null +++ b/.example.env @@ -0,0 +1,11 @@ +APP_DEBUG = true + +DB_TYPE = mysql +DB_HOST = 127.0.0.1 +DB_NAME = test +DB_USER = username +DB_PASS = password +DB_PORT = 3306 +DB_CHARSET = utf8 + +DEFAULT_LANG = zh-cn diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc735a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +vendor/ +runtime/ +.env +.DS_Store +*.log +composer.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8d94897 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2025 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..05c7b83 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +![](https://www.thinkphp.cn/uploads/images/20230630/300c856765af4d8ae758c503185f8739.png) + +ThinkPHP 8 +=============== + +## 特性 + +* 基于PHP`8.0+`重构 +* 升级`PSR`依赖 +* 依赖`think-orm`3.0+版本 +* 全新的`think-dumper`服务,支持远程调试 +* 支持`6.0`/`6.1`无缝升级 + +> ThinkPHP8的运行环境要求PHP8.0+ + +现在开始,你可以使用官方提供的[ThinkChat](https://chat.topthink.com/),让你在学习ThinkPHP的旅途中享受私人AI助理服务! + +![](https://www.topthink.com/uploads/assistant/20230630/4d1a3f0ad2958b49bb8189b7ef824cb0.png) + +ThinkPHP生态服务由[顶想云](https://www.topthink.com)(TOPThink Cloud)提供,为生态提供专业的开发者服务和价值之选。 + +## 文档 + +[完全开发手册](https://doc.thinkphp.cn) + + +## 赞助 + +全新的[赞助计划](https://www.thinkphp.cn/sponsor)可以让你通过我们的网站、手册、欢迎页及GIT仓库获得巨大曝光,同时提升企业的品牌声誉,也更好保障ThinkPHP的可持续发展。 + +[![](https://www.thinkphp.cn/sponsor/special.svg)](https://www.thinkphp.cn/sponsor/special) + +[![](https://www.thinkphp.cn/sponsor.svg)](https://www.thinkphp.cn/sponsor) + +## 安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 命名规范 + +`ThinkPHP`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +直接提交PR或者Issue即可 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2024 by ThinkPHP (http://thinkphp.cn) All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/AppService.php b/app/AppService.php new file mode 100644 index 0000000..96556e8 --- /dev/null +++ b/app/AppService.php @@ -0,0 +1,22 @@ +app = $app; + $this->request = $this->app->request; + + // 控制器初始化 + $this->initialize(); + } + + // 初始化 + protected function initialize() + {} + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + +} diff --git a/app/ExceptionHandle.php b/app/ExceptionHandle.php new file mode 100644 index 0000000..453d126 --- /dev/null +++ b/app/ExceptionHandle.php @@ -0,0 +1,58 @@ +generate(); + return json(['code' => 0, 'data' => ['cardNo' => $cardNo], 'msg' => 'ok']); + } + + // GET api/card/check?no=K182 — 校验号码牌 + public function check(CardService $cardService, OrderService $orderService) + { + $no = $this->request->get('no', ''); + if (!preg_match('/^[A-Z]\d{3}$/', $no)) { + return json(['code' => -1, 'data' => null, 'msg' => '号码牌格式错误']); + } + + $orders = $orderService->listByCard($no); + return json([ + 'code' => 0, + 'data' => [ + 'valid' => true, + 'orders' => $orders, + ], + 'msg' => 'ok', + ]); + } +} diff --git a/app/controller/Index.php b/app/controller/Index.php new file mode 100644 index 0000000..27c1c62 --- /dev/null +++ b/app/controller/Index.php @@ -0,0 +1,18 @@ +*{ padding: 0; margin: 0; }'; + } + + public function hello($name = 'ThinkPHP8') + { + return 'hello,' . $name; + } +} diff --git a/app/controller/Menu.php b/app/controller/Menu.php new file mode 100644 index 0000000..5a0ec28 --- /dev/null +++ b/app/controller/Menu.php @@ -0,0 +1,52 @@ +field('category') + ->group('category') + ->order('sort_order', 'asc') + ->column('category'); + + return json(['code' => 0, 'data' => $categories, 'msg' => 'ok']); + } + + // GET api/menu/products?cate=经典鸡尾酒 — 按分类获取商品(不含recipe) + public function products() + { + $cate = $this->request->get('cate', ''); + $query = Product::where('status', 1)->order('sort_order', 'asc'); + + if (!empty($cate) && $cate !== 'all') { + $query->where('category', $cate); + } + + $products = $query->field('id,category,name,en,emoji,image_url,price,original_price,alc,desc') + ->select() + ->toArray(); + + return json(['code' => 0, 'data' => $products, 'msg' => 'ok']); + } + + // GET api/menu/product?id=5 — 商品详情(不含recipe) + public function detail() + { + $id = $this->request->get('id', 0); + $product = Product::where('status', 1) + ->field('id,category,name,en,emoji,image_url,price,original_price,alc,desc') + ->find($id); + + if (!$product) { + return json(['code' => -1, 'data' => null, 'msg' => '商品不存在']); + } + + return json(['code' => 0, 'data' => $product->toArray(), 'msg' => 'ok']); + } +} diff --git a/app/controller/Message.php b/app/controller/Message.php new file mode 100644 index 0000000..10d3d52 --- /dev/null +++ b/app/controller/Message.php @@ -0,0 +1,76 @@ +request->get('card_no', ''); + $since = $this->request->get('since', 0); + + if (empty($cardNo)) { + return json(['code' => -1, 'data' => null, 'msg' => '缺少号码牌']); + } + + $query = MessageModel::where('card_no', $cardNo) + ->order('created_at', 'asc'); + + if ($since > 0) { + $query->where('id', '>', intval($since)); + } + + $messages = $query->select()->toArray(); + $list = array_map(function ($m) { + return [ + 'id' => $m['id'], + 'cardNo' => $m['card_no'], + 'senderType' => $m['sender_type'], + 'content' => $m['content'], + 'time' => date('H:i', strtotime($m['created_at'])), + 'staffId' => $m['staff_id'] ?? null, + ]; + }, $messages); + + return json(['code' => 0, 'data' => $list, 'msg' => 'ok']); + } + + // BUG-03: 增加输入校验 + XSS防护 + public function send() + { + $cardNo = $this->request->post('cardNo', ''); + $senderType = $this->request->post('senderType', 'customer'); + $content = $this->request->post('content', ''); + $staffId = $this->request->post('staffId', null); + + // 必填校验 + if (empty($cardNo) || empty($content)) { + return json(['code' => -1, 'data' => null, 'msg' => '参数不完整']); + } + + // senderType 枚举校验 + if (!in_array($senderType, ['customer', 'staff', 'system'])) { + return json(['code' => -1, 'data' => null, 'msg' => '发送者类型无效']); + } + + // 内容长度校验 (DB VARCHAR 500) + if (mb_strlen($content) > 500) { + return json(['code' => -1, 'data' => null, 'msg' => '消息过长,最多500字']); + } + + // XSS 防护 — HTML 转义 + $content = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); + + $msg = MessageModel::create([ + 'card_no' => $cardNo, + 'sender_type' => $senderType, + 'staff_id' => $staffId, + 'content' => $content, + ]); + + return json(['code' => 0, 'data' => ['id' => $msg->id], 'msg' => 'ok']); + } +} diff --git a/app/controller/Order.php b/app/controller/Order.php new file mode 100644 index 0000000..4c5a7a0 --- /dev/null +++ b/app/controller/Order.php @@ -0,0 +1,58 @@ +request->post('cardNo', ''); + $items = $this->request->post('items', []); + $note = $this->request->post('note', ''); + + if (empty($cardNo) || !preg_match('/^[A-Z]\d{3}$/', $cardNo)) { + return json(['code' => -1, 'data' => null, 'msg' => '号码牌无效']); + } + if (empty($items)) { + return json(['code' => -1, 'data' => null, 'msg' => '购物车为空']); + } + + try { + $result = $orderService->submit($cardNo, $items, $note); + return json(['code' => 0, 'data' => $result, 'msg' => '下单成功']); + } catch (\Exception $e) { + return json(['code' => -1, 'data' => null, 'msg' => $e->getMessage()]); + } + } + + public function list(OrderService $orderService) + { + $cardNo = $this->request->get('card_no', ''); + if (empty($cardNo)) { + return json(['code' => -1, 'data' => null, 'msg' => '缺少号码牌']); + } + + $orders = $orderService->listByCard($cardNo); + return json(['code' => 0, 'data' => $orders, 'msg' => 'ok']); + } + + // BUG-01: 增加 cardNo 归属校验 + public function remind(OrderService $orderService) + { + $id = $this->request->param('id', 0); + $cardNo = $this->request->param('card_no', ''); + + if (empty($id) || empty($cardNo)) { + return json(['code' => -1, 'data' => null, 'msg' => '参数不完整']); + } + + try { + $orderService->remind(intval($id), $cardNo); + return json(['code' => 0, 'data' => null, 'msg' => '已催单']); + } catch (\Exception $e) { + return json(['code' => -1, 'data' => null, 'msg' => $e->getMessage()]); + } + } +} diff --git a/app/controller/Staff.php b/app/controller/Staff.php new file mode 100644 index 0000000..68037ad --- /dev/null +++ b/app/controller/Staff.php @@ -0,0 +1,180 @@ + ['except' => ['login']], + ]; + + public function login() + { + $username = $this->request->post('username', ''); + $password = $this->request->post('password', ''); + + $staff = StaffModel::where('username', $username) + ->where('status', 1) + ->find(); + + if (!$staff || !password_verify($password, $staff->password)) { + return json(['code' => -1, 'data' => null, 'msg' => '账号或密码错误']); + } + + $secret = config('app.app_secret', 'bar_order_secret_key_2026'); + $expire = time() + 86400; + $sign = hash_hmac('sha256', $staff->id . '|' . $expire, $secret); + $token = base64_encode($staff->id . '|' . $expire . '|' . $sign); + + $staff->last_login = date('Y-m-d H:i:s'); + $staff->save(); + + return json([ + 'code' => 0, + 'data' => [ + 'token' => $token, + 'nickname' => $staff->nickname, + 'staffId' => $staff->id, + ], + 'msg' => '登录成功', + ]); + } + + public function orders() + { + $status = $this->request->get('status', 0); + $orders = OrderModel::with('items') + ->where('status', intval($status)) + ->order('remind_count', 'desc') + ->order('submitted_at', 'asc') + ->select() + ->toArray(); + + $list = array_map(function ($o) { + return [ + 'id' => $o['id'], + 'orderNo' => $o['order_no'], + 'cardNo' => $o['card_no'], + 'status' => $o['status'], + 'note' => $o['note'] ?? '', + 'remindCount' => $o['remind_count'] ?? 0, + 'submittedAt' => date('H:i', strtotime($o['submitted_at'])), + 'items' => array_map(function ($i) { + return [ + 'name' => $i['product_name'], + 'emoji' => $i['emoji'] ?? '', + 'alc' => 0.0, + 'qty' => $i['quantity'], + ]; + }, $o['items'] ?? []), + ]; + }, $orders); + + return json(['code' => 0, 'data' => $list, 'msg' => 'ok']); + } + + public function detail() + { + $id = $this->request->get('id', 0); + $order = OrderModel::with('items')->find(intval($id)); + + if (!$order) { + return json(['code' => -1, 'data' => null, 'msg' => '订单不存在']); + } + + $items = []; + foreach ($order->items as $item) { + $product = Product::find($item->product_id); + $items[] = [ + 'name' => $item->product_name, + 'emoji' => $item->emoji ?? '', + 'alc' => $product ? floatval($product->alc) : 0.0, + 'qty' => $item->quantity, + 'recipe' => $product ? $product->recipe : '', + ]; + } + + return json([ + 'code' => 0, + 'data' => [ + 'id' => $order->id, + 'orderNo' => $order->order_no, + 'cardNo' => $order->card_no, + 'status' => $order->status, + 'note' => $order->note ?? '', + 'remindCount' => $order->remind_count ?? 0, + 'submittedAt' => date('H:i', strtotime($order->submitted_at)), + 'items' => $items, + ], + 'msg' => 'ok', + ]); + } + + // BUG-02: 增加状态机校验 — confirm仅允许 status=0 + public function confirm() + { + $id = $this->request->param('id', 0); + $order = OrderModel::find(intval($id)); + if (!$order) { + return json(['code' => -1, 'data' => null, 'msg' => '订单不存在']); + } + if ($order->status !== 0) { + return json(['code' => -1, 'data' => null, 'msg' => '仅新订单可接单']); + } + + $order->status = 1; + $order->remind_count = 0; + $order->staff_id = $this->request->staffId ?? null; + $order->handled_at = date('Y-m-d H:i:s'); + $order->save(); + + return json(['code' => 0, 'data' => null, 'msg' => '已接单']); + } + + // BUG-02: 增加状态机校验 — done仅允许 status=1 + public function done(CardService $cardService) + { + $id = $this->request->param('id', 0); + $order = OrderModel::find(intval($id)); + if (!$order) { + return json(['code' => -1, 'data' => null, 'msg' => '订单不存在']); + } + if ($order->status !== 1) { + return json(['code' => -1, 'data' => null, 'msg' => '仅进行中订单可结单']); + } + + $order->status = 2; + $order->save(); + + $released = $cardService->release($order->card_no); + + return json([ + 'code' => 0, + 'data' => ['released' => $released], + 'msg' => '已结单', + ]); + } + + // BUG-02: 增加状态机校验 — cancel仅允许 status=0 + public function cancel() + { + $id = $this->request->param('id', 0); + $order = OrderModel::find(intval($id)); + if (!$order) { + return json(['code' => -1, 'data' => null, 'msg' => '订单不存在']); + } + if ($order->status !== 0) { + return json(['code' => -1, 'data' => null, 'msg' => '仅新订单可取消']); + } + + $order->status = 3; + $order->save(); + + return json(['code' => 0, 'data' => null, 'msg' => '已取消']); + } +} diff --git a/app/event.php b/app/event.php new file mode 100644 index 0000000..e9851bb --- /dev/null +++ b/app/event.php @@ -0,0 +1,17 @@ + [ + ], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [ + ], +]; diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..d2c3fda --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,10 @@ +header('Authorization', ''); + if (strpos($token, 'Bearer ') === 0) { + $token = substr($token, 7); + } + + if (empty($token)) { + return json(['code' => -1, 'msg' => '请登录', 'data' => null])->code(401); + } + + // 解析Token: base64(staff_id|expire|hmac) + $plain = base64_decode($token); + if (!$plain) { + return json(['code' => -1, 'msg' => 'Token无效', 'data' => null])->code(401); + } + + $parts = explode('|', $plain); + if (count($parts) !== 3) { + return json(['code' => -1, 'msg' => 'Token格式错误', 'data' => null])->code(401); + } + + [$staffId, $expire, $sign] = $parts; + + // 校验过期 + if (time() > intval($expire)) { + return json(['code' => -1, 'msg' => '登录已过期', 'data' => null])->code(401); + } + + // 校验签名 + $secret = Config::get('app.app_secret', 'bar_order_secret_key_2026'); + $expected = hash_hmac('sha256', $staffId . '|' . $expire, $secret); + if (!hash_equals($expected, $sign)) { + return json(['code' => -1, 'msg' => 'Token签名错误', 'data' => null])->code(401); + } + + $request->staffId = intval($staffId); + return $next($request); + } +} diff --git a/app/model/Message.php b/app/model/Message.php new file mode 100644 index 0000000..a703501 --- /dev/null +++ b/app/model/Message.php @@ -0,0 +1,13 @@ +hasMany(OrderItem::class, 'order_id', 'id'); + } +} diff --git a/app/model/OrderItem.php b/app/model/OrderItem.php new file mode 100644 index 0000000..d07704a --- /dev/null +++ b/app/model/OrderItem.php @@ -0,0 +1,15 @@ +belongsTo(Order::class, 'order_id', 'id'); + } +} diff --git a/app/model/Product.php b/app/model/Product.php new file mode 100644 index 0000000..8ad0ae9 --- /dev/null +++ b/app/model/Product.php @@ -0,0 +1,20 @@ +where('status', 1); + } + + public function scopeByCategory($query, $cate) + { + return $query->where('category', $cate)->order('sort_order', 'asc'); + } +} diff --git a/app/model/Staff.php b/app/model/Staff.php new file mode 100644 index 0000000..cb3a166 --- /dev/null +++ b/app/model/Staff.php @@ -0,0 +1,10 @@ + Request::class, + 'think\exception\Handle' => ExceptionHandle::class, +]; diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..db1ee6a --- /dev/null +++ b/app/service.php @@ -0,0 +1,9 @@ +isAvailable($cardNo)) { + return $cardNo; + } + } + // 冲突过多时追加随机码 + return chr(rand(65, 90)) . str_pad(rand(0, 999), 3, '0', STR_PAD_LEFT) . rand(0, 9); + } + + // 检查号码牌是否可用(不存在进行中/新单订单) + public function isAvailable(string $cardNo): bool + { + $count = Order::where('card_no', $cardNo) + ->whereIn('status', [0, 1]) + ->count(); + return $count === 0; + } + + // 释放号码牌:检查该cardNo是否还有未完成订单 + public function release(string $cardNo): bool + { + $count = Order::where('card_no', $cardNo) + ->whereIn('status', [0, 1]) + ->count(); + return $count === 0; // true=已释放,false=仍占用 + } +} diff --git a/app/service/OrderService.php b/app/service/OrderService.php new file mode 100644 index 0000000..62663c3 --- /dev/null +++ b/app/service/OrderService.php @@ -0,0 +1,122 @@ + 99) { + throw new \Exception('商品数量不合法'); + } + + $product = Product::find($item['productId']); + if (!$product || $product->status != 1) { + throw new \Exception('商品不存在或已下架'); + } + $subtotal = $product->price * $qty; + $totalAmount += $subtotal; + $orderItems[] = [ + 'product_id' => $product->id, + 'product_name' => $product->name, + 'emoji' => $product->emoji ?? '', + 'price' => $product->price, + 'quantity' => $qty, + ]; + } + + if ($totalAmount <= 0) { + throw new \Exception('订单金额无效'); + } + + // BUG-03 (旧): 订单号增加秒级精度避免并发冲突 + $orderNo = 'OD' . date('His') . substr($cardNo, -3) . rand(10, 99); + + $order = Order::create([ + 'order_no' => $orderNo, + 'card_no' => $cardNo, + 'total_amount' => $totalAmount, + 'status' => 0, + 'note' => $note, + 'submitted_at' => date('Y-m-d H:i:s'), + ]); + + foreach ($orderItems as &$oi) { + $oi['order_id'] = $order->id; + } + (new OrderItem())->saveAll($orderItems); + + return ['orderNo' => $order->order_no]; + }); + } + + // 顾客订单列表 + public function listByCard(string $cardNo): array + { + $orders = Order::with('items') + ->where('card_no', $cardNo) + ->order('submitted_at', 'desc') + ->select() + ->toArray(); + + return array_map(function ($o) { + return [ + 'id' => $o['id'], + 'orderNo' => $o['order_no'], + 'cardNo' => $o['card_no'], + 'status' => $o['status'], + 'note' => $o['note'] ?? '', + 'remindCount' => $o['remind_count'] ?? 0, + 'submittedAt' => date('H:i', strtotime($o['submitted_at'])), + 'items' => array_map(function ($i) { + return [ + 'name' => $i['product_name'], + 'emoji' => $i['emoji'] ?? '', + 'alc' => 0.0, + 'qty' => $i['quantity'], + ]; + }, $o['items'] ?? []), + ]; + }, $orders); + } + + // BUG-01: 催单增加归属校验 + 状态校验 + 频率限制 + public function remind(int $orderId, string $cardNo): void + { + $order = Order::find($orderId); + if (!$order) { + throw new \Exception('订单不存在'); + } + // 归属校验 + if ($order->card_no !== $cardNo) { + throw new \Exception('无权操作此订单'); + } + // 状态校验:仅新单可催 + if ($order->status !== 0) { + throw new \Exception('当前订单状态不可催单'); + } + // 频率限制:30秒冷却 + $lastRemind = strtotime($order->updated_at ?? $order->submitted_at); + if (time() - $lastRemind < 30) { + throw new \Exception('催单太频繁,请30秒后再试'); + } + // 上限:最多5次 + if ($order->remind_count >= 5) { + throw new \Exception('催单次数已达上限'); + } + + Order::where('id', $orderId)->inc('remind_count', 1)->update(); + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ffb0d25 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "https://www.thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=8.0.0", + "topthink/framework": "^8.0", + "topthink/think-orm": "^3.0|^4.0", + "topthink/think-filesystem": "^2.0|^3.0" + }, + "require-dev": { + "topthink/think-dumper": "^1.0", + "topthink/think-trace": "^2.0" + }, + "autoload": { + "psr-4": { + "app\\": "app" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist" + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..05d46da --- /dev/null +++ b/config/app.php @@ -0,0 +1,14 @@ + '', + 'with_route' => true, + 'default_app' => 'index', + 'default_timezone' => 'Asia/Shanghai', + 'app_map' => [], + 'domain_bind' => [], + 'deny_app_list' => [], + 'app_secret' => 'bar_order_secret_key_2026', + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + 'error_message' => '页面错误!请稍后再试~', + 'show_error_msg' => false, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..6b72dc8 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,29 @@ + 'file', + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // 更多的缓存连接 + ], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..a818a98 --- /dev/null +++ b/config/console.php @@ -0,0 +1,9 @@ + [ + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..d3b3aab --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,20 @@ + 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..51f728f --- /dev/null +++ b/config/database.php @@ -0,0 +1,31 @@ + env('DB_TYPE', 'mysql'), + 'time_query_rule' => [], + 'auto_timestamp' => true, + 'datetime_format' => 'Y-m-d H:i:s', + 'datetime_field' => '', + 'connections' => [ + 'mysql' => [ + 'type' => 'mysql', + 'hostname' => env('DB_HOST', '127.0.0.1'), + 'database' => env('DB_NAME', 'bar_order'), + 'username' => env('DB_USER', 'root'), + 'password' => env('DB_PASS', ''), + 'hostport' => env('DB_PORT', '3306'), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'prefix' => env('DB_PREFIX', ''), + 'deploy' => 0, + 'rw_separate' => false, + 'master_num' => 1, + 'fields_strict' => true, + 'trigger_sql' => env('APP_DEBUG', true), + 'fields_cache' => false, + ], + 'sqlite' => [ + 'type' => 'sqlite', + 'database' => env('DB_NAME', 'runtime/bar_order.db'), + 'prefix' => '', + ], + ], +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..582a8f8 --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,24 @@ + 'local', + // 磁盘列表 + 'disks' => [ + 'local' => [ + 'type' => 'local', + 'root' => app()->getRuntimePath() . 'storage', + ], + 'public' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..ccad14a --- /dev/null +++ b/config/lang.php @@ -0,0 +1,29 @@ + env('DEFAULT_LANG', 'zh-cn'), + // 自动侦测浏览器语言 + 'auto_detect_browser' => true, + // 允许的语言列表 + 'allow_lang_list' => [], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..0d406f8 --- /dev/null +++ b/config/log.php @@ -0,0 +1,45 @@ + 'file', + // 日志记录级别 + 'level' => [], + // 日志类型记录的通道 ['error'=>'email',...] + 'type_channel' => [], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => '', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + // 其它日志通道配置 + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..a0dd81b --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,7 @@ + [ + 'StaffAuth' => app\middleware\StaffAuth::class, + ], + 'priority' => [], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..729d7be --- /dev/null +++ b/config/route.php @@ -0,0 +1,55 @@ + '/', + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 是否区分大小写 + 'url_case_sensitive' => false, + // 自动扫描子目录分组 + 'route_auto_group' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 去除斜杠 + 'remove_slash' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认模块名(开启自动多模块有效) + 'default_module' => 'index', + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 非路由变量是否使用普通参数方式(用于URL生成) + 'url_common_param' => true, + // 操作方法的参数绑定方式 route get param + 'action_bind_param' => 'get', + // 请求缓存规则 true为自动规则 + 'request_cache_key' => true, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 请求缓存的Tag + 'request_cache_tag' => '', + // API版本header变量 + 'api_version' => 'Api-Version', +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..c1ef6e1 --- /dev/null +++ b/config/session.php @@ -0,0 +1,19 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间 + 'expire' => 1440, + // 前缀 + 'prefix' => '', +]; diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/config/trace.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..01259a0 --- /dev/null +++ b/config/view.php @@ -0,0 +1,25 @@ + 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板目录名 + 'view_dir_name' => 'view', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', +]; diff --git a/extend/.gitignore b/extend/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/extend/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..cbc7868 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + diff --git a/public/drink b/public/drink new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3f2817f0665b40973f7004ea31b223e3a685c668 GIT binary patch literal 5434 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 kXA?jE*Ad76z9MS>4;eE +// +---------------------------------------------------------------------- + +use think\App; + +// [ 应用入口文件 ] + +require __DIR__ . '/../vendor/autoload.php'; + +// 执行HTTP应用并响应 +$http = (new App())->http; + +$response = $http->run(); + +$response->send(); + +$http->end($response); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/router.php b/public/router.php new file mode 100644 index 0000000..9b39a62 --- /dev/null +++ b/public/router.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + + require __DIR__ . "/index.php"; +} diff --git a/public/static/.gitignore b/public/static/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/public/static/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/route/app.php b/route/app.php new file mode 100644 index 0000000..c179c1e --- /dev/null +++ b/route/app.php @@ -0,0 +1,24 @@ +middleware('StaffAuth'); diff --git a/think b/think new file mode 100644 index 0000000..979ac35 --- /dev/null +++ b/think @@ -0,0 +1,11 @@ +#!/usr/bin/env php +console->run(); diff --git a/view/README.md b/view/README.md new file mode 100644 index 0000000..360eb24 --- /dev/null +++ b/view/README.md @@ -0,0 +1 @@ +如果不使用模板,可以删除该目录 \ No newline at end of file