- 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 @@ | |||
| 如果不使用模板,可以删除该目录 | |||