- 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 生产就绪dev
| @ -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 | |||||
| @ -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 | |||||
| @ -0,0 +1,6 @@ | |||||
| vendor/ | |||||
| runtime/ | |||||
| .env | |||||
| .DS_Store | |||||
| *.log | |||||
| composer.lock | |||||
| @ -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 | |||||
| @ -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. | |||||
| @ -0,0 +1,77 @@ | |||||
|  | |||||
| 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助理服务! | |||||
|  | |||||
| ThinkPHP生态服务由[顶想云](https://www.topthink.com)(TOPThink Cloud)提供,为生态提供专业的开发者服务和价值之选。 | |||||
| ## 文档 | |||||
| [完全开发手册](https://doc.thinkphp.cn) | |||||
| ## 赞助 | |||||
| 全新的[赞助计划](https://www.thinkphp.cn/sponsor)可以让你通过我们的网站、手册、欢迎页及GIT仓库获得巨大曝光,同时提升企业的品牌声誉,也更好保障ThinkPHP的可持续发展。 | |||||
| [](https://www.thinkphp.cn/sponsor/special) | |||||
| [](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) | |||||
| @ -0,0 +1 @@ | |||||
| deny from all | |||||
| @ -0,0 +1,22 @@ | |||||
| <?php | |||||
| declare (strict_types = 1); | |||||
| namespace app; | |||||
| use think\Service; | |||||
| /** | |||||
| * 应用服务类 | |||||
| */ | |||||
| class AppService extends Service | |||||
| { | |||||
| public function register() | |||||
| { | |||||
| // 服务注册 | |||||
| } | |||||
| public function boot() | |||||
| { | |||||
| // 服务启动 | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,94 @@ | |||||
| <?php | |||||
| declare (strict_types = 1); | |||||
| namespace app; | |||||
| use think\App; | |||||
| use think\exception\ValidateException; | |||||
| use think\Validate; | |||||
| /** | |||||
| * 控制器基础类 | |||||
| */ | |||||
| abstract class BaseController | |||||
| { | |||||
| /** | |||||
| * Request实例 | |||||
| * @var \think\Request | |||||
| */ | |||||
| protected $request; | |||||
| /** | |||||
| * 应用实例 | |||||
| * @var \think\App | |||||
| */ | |||||
| protected $app; | |||||
| /** | |||||
| * 是否批量验证 | |||||
| * @var bool | |||||
| */ | |||||
| protected $batchValidate = false; | |||||
| /** | |||||
| * 控制器中间件 | |||||
| * @var array | |||||
| */ | |||||
| protected $middleware = []; | |||||
| /** | |||||
| * 构造方法 | |||||
| * @access public | |||||
| * @param App $app 应用对象 | |||||
| */ | |||||
| public function __construct(App $app) | |||||
| { | |||||
| $this->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); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,58 @@ | |||||
| <?php | |||||
| namespace app; | |||||
| use think\db\exception\DataNotFoundException; | |||||
| use think\db\exception\ModelNotFoundException; | |||||
| use think\exception\Handle; | |||||
| use think\exception\HttpException; | |||||
| use think\exception\HttpResponseException; | |||||
| use think\exception\ValidateException; | |||||
| use think\Response; | |||||
| use Throwable; | |||||
| /** | |||||
| * 应用异常处理类 | |||||
| */ | |||||
| class ExceptionHandle extends Handle | |||||
| { | |||||
| /** | |||||
| * 不需要记录信息(日志)的异常类列表 | |||||
| * @var array | |||||
| */ | |||||
| protected $ignoreReport = [ | |||||
| HttpException::class, | |||||
| HttpResponseException::class, | |||||
| ModelNotFoundException::class, | |||||
| DataNotFoundException::class, | |||||
| ValidateException::class, | |||||
| ]; | |||||
| /** | |||||
| * 记录异常信息(包括日志或者其它方式记录) | |||||
| * | |||||
| * @access public | |||||
| * @param Throwable $exception | |||||
| * @return void | |||||
| */ | |||||
| public function report(Throwable $exception): void | |||||
| { | |||||
| // 使用内置的方式记录异常日志 | |||||
| parent::report($exception); | |||||
| } | |||||
| /** | |||||
| * Render an exception into an HTTP response. | |||||
| * | |||||
| * @access public | |||||
| * @param \think\Request $request | |||||
| * @param Throwable $e | |||||
| * @return Response | |||||
| */ | |||||
| public function render($request, Throwable $e): Response | |||||
| { | |||||
| // 添加自定义异常处理机制 | |||||
| // 其他错误交给系统处理 | |||||
| return parent::render($request, $e); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,8 @@ | |||||
| <?php | |||||
| namespace app; | |||||
| // 应用请求对象类 | |||||
| class Request extends \think\Request | |||||
| { | |||||
| } | |||||
| @ -0,0 +1,2 @@ | |||||
| <?php | |||||
| // 应用公共文件 | |||||
| @ -0,0 +1,35 @@ | |||||
| <?php | |||||
| namespace app\controller; | |||||
| use app\BaseController; | |||||
| use app\service\CardService; | |||||
| use app\service\OrderService; | |||||
| class Card extends BaseController | |||||
| { | |||||
| // POST api/card/generate — 生成新号码牌 | |||||
| public function generate(CardService $cardService) | |||||
| { | |||||
| $cardNo = $cardService->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', | |||||
| ]); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,18 @@ | |||||
| <?php | |||||
| namespace app\controller; | |||||
| use app\BaseController; | |||||
| class Index extends BaseController | |||||
| { | |||||
| public function index() | |||||
| { | |||||
| return '<style>*{ padding: 0; margin: 0; }</style><iframe src="https://www.thinkphp.cn/welcome?version=' . \think\facade\App::version() . '" width="100%" height="100%" frameborder="0" scrolling="auto"></iframe>'; | |||||
| } | |||||
| public function hello($name = 'ThinkPHP8') | |||||
| { | |||||
| return 'hello,' . $name; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,52 @@ | |||||
| <?php | |||||
| namespace app\controller; | |||||
| use app\BaseController; | |||||
| use app\model\Product; | |||||
| class Menu extends BaseController | |||||
| { | |||||
| // GET api/menu/categories — 获取分类列表 | |||||
| public function categories() | |||||
| { | |||||
| $categories = Product::where('status', 1) | |||||
| ->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']); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,76 @@ | |||||
| <?php | |||||
| namespace app\controller; | |||||
| use app\BaseController; | |||||
| use app\model\Message as MessageModel; | |||||
| class Message extends BaseController | |||||
| { | |||||
| // BUG-03: 聊天记录读取保持原有逻辑 | |||||
| public function list() | |||||
| { | |||||
| $cardNo = $this->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']); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,58 @@ | |||||
| <?php | |||||
| namespace app\controller; | |||||
| use app\BaseController; | |||||
| use app\service\OrderService; | |||||
| class Order extends BaseController | |||||
| { | |||||
| public function submit(OrderService $orderService) | |||||
| { | |||||
| $cardNo = $this->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()]); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,180 @@ | |||||
| <?php | |||||
| namespace app\controller; | |||||
| use app\BaseController; | |||||
| use app\model\Staff as StaffModel; | |||||
| use app\model\Order as OrderModel; | |||||
| use app\model\Product; | |||||
| use app\service\CardService; | |||||
| class Staff extends BaseController | |||||
| { | |||||
| protected $middleware = [ | |||||
| 'StaffAuth' => ['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' => '已取消']); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,17 @@ | |||||
| <?php | |||||
| // 事件定义文件 | |||||
| return [ | |||||
| 'bind' => [ | |||||
| ], | |||||
| 'listen' => [ | |||||
| 'AppInit' => [], | |||||
| 'HttpRun' => [], | |||||
| 'HttpEnd' => [], | |||||
| 'LogLevel' => [], | |||||
| 'LogWrite' => [], | |||||
| ], | |||||
| 'subscribe' => [ | |||||
| ], | |||||
| ]; | |||||
| @ -0,0 +1,10 @@ | |||||
| <?php | |||||
| // 全局中间件定义文件 | |||||
| return [ | |||||
| // 全局请求缓存 | |||||
| // \think\middleware\CheckRequestCache::class, | |||||
| // 多语言加载 | |||||
| // \think\middleware\LoadLangPack::class, | |||||
| // Session初始化 | |||||
| // \think\middleware\SessionInit::class | |||||
| ]; | |||||
| @ -0,0 +1,47 @@ | |||||
| <?php | |||||
| namespace app\middleware; | |||||
| use think\facade\Config; | |||||
| class StaffAuth | |||||
| { | |||||
| public function handle($request, \Closure $next) | |||||
| { | |||||
| $token = $request->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); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,13 @@ | |||||
| <?php | |||||
| namespace app\model; | |||||
| use think\Model; | |||||
| class Message extends Model | |||||
| { | |||||
| protected $table = 'messages'; | |||||
| protected $pk = 'id'; | |||||
| protected $autoWriteTimestamp = true; | |||||
| protected $createTime = 'created_at'; | |||||
| protected $updateTime = false; | |||||
| } | |||||
| @ -0,0 +1,15 @@ | |||||
| <?php | |||||
| namespace app\model; | |||||
| use think\Model; | |||||
| class Order extends Model | |||||
| { | |||||
| protected $table = 'orders'; | |||||
| protected $pk = 'id'; | |||||
| public function items() | |||||
| { | |||||
| return $this->hasMany(OrderItem::class, 'order_id', 'id'); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,15 @@ | |||||
| <?php | |||||
| namespace app\model; | |||||
| use think\Model; | |||||
| class OrderItem extends Model | |||||
| { | |||||
| protected $table = 'order_items'; | |||||
| protected $pk = 'id'; | |||||
| public function order() | |||||
| { | |||||
| return $this->belongsTo(Order::class, 'order_id', 'id'); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,20 @@ | |||||
| <?php | |||||
| namespace app\model; | |||||
| use think\Model; | |||||
| class Product extends Model | |||||
| { | |||||
| protected $table = 'products'; | |||||
| protected $pk = 'id'; | |||||
| public function scopeStatus($query) | |||||
| { | |||||
| return $query->where('status', 1); | |||||
| } | |||||
| public function scopeByCategory($query, $cate) | |||||
| { | |||||
| return $query->where('category', $cate)->order('sort_order', 'asc'); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,10 @@ | |||||
| <?php | |||||
| namespace app\model; | |||||
| use think\Model; | |||||
| class Staff extends Model | |||||
| { | |||||
| protected $table = 'staff'; | |||||
| protected $pk = 'id'; | |||||
| } | |||||
| @ -0,0 +1,9 @@ | |||||
| <?php | |||||
| use app\ExceptionHandle; | |||||
| use app\Request; | |||||
| // 容器Provider定义文件 | |||||
| return [ | |||||
| 'think\Request' => Request::class, | |||||
| 'think\exception\Handle' => ExceptionHandle::class, | |||||
| ]; | |||||
| @ -0,0 +1,9 @@ | |||||
| <?php | |||||
| use app\AppService; | |||||
| // 系统服务定义文件 | |||||
| // 服务在完成全局初始化之后执行 | |||||
| return [ | |||||
| AppService::class, | |||||
| ]; | |||||
| @ -0,0 +1,41 @@ | |||||
| <?php | |||||
| namespace app\service; | |||||
| use app\model\Order; | |||||
| class CardService | |||||
| { | |||||
| // 生成 [A-Z][0-9]{3} 格式号码牌,最多重试10次避免冲突 | |||||
| public function generate(): string | |||||
| { | |||||
| $maxRetry = 10; | |||||
| for ($i = 0; $i < $maxRetry; $i++) { | |||||
| $letter = chr(rand(65, 90)); // A-Z | |||||
| $num = str_pad(rand(0, 999), 3, '0', STR_PAD_LEFT); | |||||
| $cardNo = $letter . $num; | |||||
| if ($this->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=仍占用 | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,122 @@ | |||||
| <?php | |||||
| namespace app\service; | |||||
| use app\model\Order; | |||||
| use app\model\OrderItem; | |||||
| use app\model\Product; | |||||
| use think\facade\Db; | |||||
| class OrderService | |||||
| { | |||||
| // 下单事务 (BUG-04: 增加数量校验) | |||||
| public function submit(string $cardNo, array $items, string $note = ''): array | |||||
| { | |||||
| return Db::transaction(function () use ($cardNo, $items, $note) { | |||||
| $totalAmount = 0; | |||||
| $orderItems = []; | |||||
| foreach ($items as $item) { | |||||
| // BUG-04: 校验 qty 为正整数 | |||||
| $qty = intval($item['qty'] ?? 1); | |||||
| if ($qty < 1 || $qty > 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(); | |||||
| } | |||||
| } | |||||
| @ -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" | |||||
| ] | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,14 @@ | |||||
| <?php | |||||
| return [ | |||||
| 'app_namespace' => '', | |||||
| '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, | |||||
| ]; | |||||
| @ -0,0 +1,29 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 缓存设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // 默认缓存驱动 | |||||
| 'default' => 'file', | |||||
| // 缓存连接方式配置 | |||||
| 'stores' => [ | |||||
| 'file' => [ | |||||
| // 驱动方式 | |||||
| 'type' => 'File', | |||||
| // 缓存保存目录 | |||||
| 'path' => '', | |||||
| // 缓存前缀 | |||||
| 'prefix' => '', | |||||
| // 缓存有效期 0表示永久缓存 | |||||
| 'expire' => 0, | |||||
| // 缓存标签前缀 | |||||
| 'tag_prefix' => 'tag:', | |||||
| // 序列化机制 例如 ['serialize', 'unserialize'] | |||||
| 'serialize' => [], | |||||
| ], | |||||
| // 更多的缓存连接 | |||||
| ], | |||||
| ]; | |||||
| @ -0,0 +1,9 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 控制台配置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // 指令定义 | |||||
| 'commands' => [ | |||||
| ], | |||||
| ]; | |||||
| @ -0,0 +1,20 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Cookie设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // cookie 保存时间 | |||||
| 'expire' => 0, | |||||
| // cookie 保存路径 | |||||
| 'path' => '/', | |||||
| // cookie 有效域名 | |||||
| 'domain' => '', | |||||
| // cookie 启用安全传输 | |||||
| 'secure' => false, | |||||
| // httponly设置 | |||||
| 'httponly' => false, | |||||
| // 是否使用 setcookie | |||||
| 'setcookie' => true, | |||||
| // samesite 设置,支持 'strict' 'lax' | |||||
| 'samesite' => '', | |||||
| ]; | |||||
| @ -0,0 +1,31 @@ | |||||
| <?php | |||||
| return [ | |||||
| 'default' => 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' => '', | |||||
| ], | |||||
| ], | |||||
| ]; | |||||
| @ -0,0 +1,24 @@ | |||||
| <?php | |||||
| return [ | |||||
| // 默认磁盘 | |||||
| 'default' => 'local', | |||||
| // 磁盘列表 | |||||
| 'disks' => [ | |||||
| 'local' => [ | |||||
| 'type' => 'local', | |||||
| 'root' => app()->getRuntimePath() . 'storage', | |||||
| ], | |||||
| 'public' => [ | |||||
| // 磁盘类型 | |||||
| 'type' => 'local', | |||||
| // 磁盘路径 | |||||
| 'root' => app()->getRootPath() . 'public/storage', | |||||
| // 磁盘路径对应的外部URL路径 | |||||
| 'url' => '/storage', | |||||
| // 可见性 | |||||
| 'visibility' => 'public', | |||||
| ], | |||||
| // 更多的磁盘配置信息 | |||||
| ], | |||||
| ]; | |||||
| @ -0,0 +1,29 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 多语言设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // 默认语言 | |||||
| 'default_lang' => 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, | |||||
| ]; | |||||
| @ -0,0 +1,45 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 日志设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // 默认日志记录通道 | |||||
| 'default' => '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, | |||||
| ], | |||||
| // 其它日志通道配置 | |||||
| ], | |||||
| ]; | |||||
| @ -0,0 +1,7 @@ | |||||
| <?php | |||||
| return [ | |||||
| 'alias' => [ | |||||
| 'StaffAuth' => app\middleware\StaffAuth::class, | |||||
| ], | |||||
| 'priority' => [], | |||||
| ]; | |||||
| @ -0,0 +1,55 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 路由设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // pathinfo分隔符 | |||||
| 'pathinfo_depr' => '/', | |||||
| // 是否开启路由延迟解析 | |||||
| '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', | |||||
| ]; | |||||
| @ -0,0 +1,19 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 会话设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // session name | |||||
| 'name' => 'PHPSESSID', | |||||
| // SESSION_ID的提交变量,解决flash上传跨域 | |||||
| 'var_session_id' => '', | |||||
| // 驱动方式 支持file cache | |||||
| 'type' => 'file', | |||||
| // 存储连接标识 当type使用cache的时候有效 | |||||
| 'store' => null, | |||||
| // 过期时间 | |||||
| 'expire' => 1440, | |||||
| // 前缀 | |||||
| 'prefix' => '', | |||||
| ]; | |||||
| @ -0,0 +1,10 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Trace设置 开启调试模式后有效 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // 内置Html和Console两种方式 支持扩展 | |||||
| 'type' => 'Html', | |||||
| // 读取的日志通道名 | |||||
| 'channel' => '', | |||||
| ]; | |||||
| @ -0,0 +1,25 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | 模板设置 | |||||
| // +---------------------------------------------------------------------- | |||||
| return [ | |||||
| // 模板引擎类型使用Think | |||||
| 'type' => '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' => '}', | |||||
| ]; | |||||
| @ -0,0 +1,2 @@ | |||||
| * | |||||
| !.gitignore | |||||
| @ -0,0 +1,8 @@ | |||||
| <IfModule mod_rewrite.c> | |||||
| Options +FollowSymlinks -Multiviews | |||||
| RewriteEngine On | |||||
| RewriteCond %{REQUEST_FILENAME} !-d | |||||
| RewriteCond %{REQUEST_FILENAME} !-f | |||||
| RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] | |||||
| </IfModule> | |||||
| @ -0,0 +1,25 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | ThinkPHP [ WE CAN DO IT JUST THINK ] | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved. | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Author: liu21st <liu21st@gmail.com> | |||||
| // +---------------------------------------------------------------------- | |||||
| use think\App; | |||||
| // [ 应用入口文件 ] | |||||
| require __DIR__ . '/../vendor/autoload.php'; | |||||
| // 执行HTTP应用并响应 | |||||
| $http = (new App())->http; | |||||
| $response = $http->run(); | |||||
| $response->send(); | |||||
| $http->end($response); | |||||
| @ -0,0 +1,2 @@ | |||||
| User-agent: * | |||||
| Disallow: | |||||
| @ -0,0 +1,19 @@ | |||||
| <?php | |||||
| // +---------------------------------------------------------------------- | |||||
| // | ThinkPHP [ WE CAN DO IT JUST THINK ] | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved. | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) | |||||
| // +---------------------------------------------------------------------- | |||||
| // | Author: liu21st <liu21st@gmail.com> | |||||
| // +---------------------------------------------------------------------- | |||||
| // $Id$ | |||||
| if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { | |||||
| return false; | |||||
| } else { | |||||
| $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; | |||||
| require __DIR__ . "/index.php"; | |||||
| } | |||||
| @ -0,0 +1,2 @@ | |||||
| * | |||||
| !.gitignore | |||||
| @ -0,0 +1,24 @@ | |||||
| <?php | |||||
| use think\facade\Route; | |||||
| // ── 顾客端(无需认证) ── | |||||
| Route::post('api/card/generate', 'Card/generate'); | |||||
| Route::get('api/card/check', 'Card/check'); | |||||
| Route::get('api/menu/categories', 'Menu/categories'); | |||||
| Route::get('api/menu/products', 'Menu/products'); | |||||
| Route::get('api/menu/product', 'Menu/detail'); | |||||
| Route::post('api/order/submit', 'Order/submit'); | |||||
| Route::get('api/order/list', 'Order/list'); | |||||
| Route::post('api/order/remind', 'Order/remind'); | |||||
| Route::get('api/message/list', 'Message/list'); | |||||
| Route::post('api/message/send', 'Message/send'); | |||||
| // ── 员工端(需Token中间件) ── | |||||
| Route::post('api/staff/login', 'Staff/login'); | |||||
| Route::group('api/staff', function () { | |||||
| Route::get('orders', 'Staff/orders'); | |||||
| Route::get('order', 'Staff/detail'); | |||||
| Route::post('order/confirm', 'Staff/confirm'); | |||||
| Route::post('order/done', 'Staff/done'); | |||||
| Route::post('order/cancel', 'Staff/cancel'); | |||||
| })->middleware('StaffAuth'); | |||||
| @ -0,0 +1,11 @@ | |||||
| #!/usr/bin/env php | |||||
| <?php | |||||
| use think\App; | |||||
| // 命令行入口文件 | |||||
| // 加载基础文件 | |||||
| require __DIR__ . '/vendor/autoload.php'; | |||||
| // 应用初始化 | |||||
| (new App())->console->run(); | |||||
| @ -0,0 +1 @@ | |||||
| 如果不使用模板,可以删除该目录 | |||||