================================================================================
  执笔自助发卡系统 — 更新日志（CHANGELOG）
================================================================================
  说明：每次更新都在本文件顶部追加一节，记录「改了什么 / 涉及哪些文件 / 需要注意什么」。
        升级时如涉及数据库字段，请到「后台 → 系统设置 → 商品扩展功能 / 上游站点」点
        「一键初始化」，或在宝塔数据库里导入 sql/ 目录下对应的 .sql 文件（均为幂等语句）。

--------------------------------------------------------------------------------
【新】简约商城：首页商品表格 → 弹窗下单（不用再跳详情页）
--------------------------------------------------------------------------------
■ 背景
  · 这个模板是"表格一行一个商品"的布局，但本系统的商品是「商品 → 规格」两级、
    一个商品有多个规格（1个月 / 3个月 / 永久…）。原来的做法是表格显示起价、
    点「立即购买」跳到商品详情页再选规格下单，多一次跳转。
  · 现在改成：点「立即购买」直接在首页弹出面板，选规格 → 填信息 → 直接下单付款。

■ 弹窗里有什么（承载完整下单表单）
  · 选择规格（带各自价格；缺货的规格置灰划掉，不可选）
  · 购买数量（受「最小/最大下单数量」和「每人限购」约束，实时提示）
  · 接收邮箱（商品可自定义填写项时，自动变成那个填写项：下单账号 / QQ / 手机号）
  · 上游采集项（对接了上游的商品，自动列出上游要求的字段，按必填校验）
  · 实物收货信息（实物商品自动切换成：收货人 / 手机号 / 省市区 / 详细地址）
  · 支付方式（支付宝 / 微信，只开一个时自动选中）
  · 底部实时显示「应付金额」，提交后直接跳支付页

■ 细节处理
  · 手机端弹窗改成「从底部滑出的全屏面板」，输入框字号 ≥15px
    （避免 iOS 聚焦时自动放大页面）
  · 提交按钮带防连点；失败会恢复可点击状态并把后端报错显示出来
  · 弹窗里保留「查看商品详情 →」链接，想细看的用户仍然能进详情页
  · 商品详情页（goods.php）保持不动 —— 搜索引擎和分享链接会直接指向它

■ 数量与库存的边界处理（独立审核发现，已修）
  · 问题一：如果「最小下单数量」比当前可售数量还大（比如最少买 5 件、库存只剩 2），
    原来会把数量夹到 2 —— 低于最小值，用户点了提交才被后端拒绝，
    完全不知道错在哪。现在这种规格直接禁用下单按钮，并在按钮下方写明原因：
    「当前可售数量（2）少于最少起购数量（5），暂时无法下单」
  · 问题二：所有规格都缺货时，金额显示 ¥0.00 但按钮仍可点。
    现在会把金额清零并禁用按钮，提示「该商品当前所有规格都缺货，暂时无法下单」
  · 数量上限统一为「最大下单数量 / 每人限购 / 规格库存」三者取最小，不再各夹各的
  · 下单失败后不再无条件放开按钮，而是重新按当前数量算一遍能不能下单

■ 商品列表的价格显示
  · 改成显示**价格区间**：多规格显示「¥8.80 ~ ¥25.00」，单规格只显示一个价
  · 这样一眼能看出这个商品从多少钱到多少钱，比只显示最低价清楚

■ 模板设置里多了一个开关
  · 「下单方式 / 顶部导航 / 订单查询」新增一项：
      「点『立即购买』弹出弹窗下单（推荐）」
    - 开着（默认）：表格里点按钮弹窗下单
    - 关掉：恢复成原来「跳商品详情页」的流程
  · 如果你哪天上游商品需要填很多字段、弹窗显得挤，可以一键关掉

■ 涉及文件
  · themes/caidou/order_modal.php   新增（弹窗下单：样式 + 结构 + 下单逻辑）
  · themes/caidou/index.php         商品表格改为弹窗按钮；价格改成区间显示；引入弹窗
  · admin.php                       模板设置新增「弹窗下单」开关
  · 本文件

--------------------------------------------------------------------------------
【新】新增「简约商城」模板 + 独立的模板设置
--------------------------------------------------------------------------------
■ 新模板：简约商城（目录名 themes/caidou）
  · 首页顺序（按需求调整）：
      网站公告（可折叠，1. 2. 3. 4. 自动编号）
      → 联系客服 / 官方Q群 / APP下载
      → 订单查询（下单账号 + 订单号）
      → 商品分类（胶囊按钮，带数量）
      → 商品列表（表格：商品名称 | 单价 | 库存 | 操作）
      顶栏：Logo + 站名 | 返回首页 / 订单查询 / 售后工单
      页脚 + 右下角悬浮 QQ 客服
  · 商品详情页：左图右信息，规格、数量、邮箱、支付方式、立即购买，下方商品详情
  · 订单详情页：支付成功卡片、订单信息、卡密复制、申请售后
  · 订单查询页：与其它 7 套主题共用同一份 front/tpl/query_page.php
  · 蓝白配色（#2f7ff5），整套手机端适配：
      商品表格在手机上自动转成卡片式（不再横向滚动）
      分类、按钮、查询框全部铺满一行，悬浮客服缩到右下角
  · 没有登录/注册（本系统没有会员功能），顶栏放的是「售后工单」入口
  · 规格处理方式：表格显示「起价」，点进详情页再选规格

■ 新增「模板设置」（只有正在使用简约商城时才出现）
  · 入口：后台 → 前台模板 → 简约商城卡片底部「模板设置」按钮
  · 能配这些：
      网站公告        显示开关 / 默认展开 / 标题 / 内容（一行一条，自动编号）
      联系客服        QQ 号（可填完整链接）+ 显示开关
      官方Q群         群号或加群链接 + 显示开关
      APP 下载        链接 + 显示开关
      悬浮客服        右下角 QQ 悬浮按钮开关
      导航与查询      首页查询框开关 / 售后工单入口开关 / 工单页地址
      前台文案        价格单位（元/个）/ 购买按钮文字 / 邮箱栏标题 / 页脚文字
  · 所有设置项存在 zb_settings 表里，键名前缀 caidou_，不新建表
  · 切换到其它模板后：这些设置原封不动保留，但入口和效果都不会出现
  · 页面顶部有一行提示 + 「预览前台」和「返回模板列表」两个按钮

■ 涉及文件
  · themes/caidou/theme.json    新增（模板信息）
  · themes/caidou/config.php    新增（设置读取：caidou_cfg / caidou_on / 公告与联系方式解析）
  · themes/caidou/index.php     新增（首页）
  · themes/caidou/goods.php     新增（商品详情）
  · themes/caidou/result.php    新增（订单详情）
  · themes/caidou/query.php     新增（订单查询，复用公共页）
  · admin.php                   新增 act=theme_caidou 模板设置页；
                                模板卡片在使用中且是简约商城时显示「模板设置」按钮
  · 本文件

■ 其它模板一律没动。

--------------------------------------------------------------------------------
【修】优惠券 / 售后工单的「一键初始化」按钮点了没反应
--------------------------------------------------------------------------------
· 现象：后台进「售后工单」或「优惠券」，看到「初始化数据表」提示，
  点「一键初始化」完全没反应（连"初始化中…"都不出现）。
· 原因：初始化按钮的 JS 函数（tkInit / cpnInit）被写在了 `$installed` 的
  else 分支里 —— 只有「已经初始化过」的页面才会输出这段脚本。
  而未初始化时走的正是 if 分支，函数根本不存在，onclick 找不到函数，
  浏览器静默失败，所以看起来像"点了没反应"。
· 修复：把这两个函数各自挪到独立的 <script> 里，放在 $installed 判断外面，
  只要页面上出现初始化按钮，函数就一定存在。
· 顺手加了 .catch()，请求失败时会把错误显示在按钮下方，不再静默。
· 自查：同样模式的另外两个初始化按钮（上游对接的 initUpstream、
  商品编辑的 geInitFields）本来就在无条件脚本里，不受影响。

--------------------------------------------------------------------------------
【新】商品上架页重做（仿彩虹上架模式）+ 成本价 + 修复 3 个隐患
--------------------------------------------------------------------------------
■ 商品编辑：从弹窗改成整页表单
  · 入口：商品列表的「添加商品 / 编辑」现在跳到 admin.php?act=goods&edit=ID 整页
  · 分 5 段排布（仿彩虹的「商品基本信息设置」）：
      ① 商品基本信息   分类 / 名称 / 标签 / 排序 / 状态 / 商品类型（虚拟或实物）
      ② 规格与价格     一行一个规格的表格，可加行删行
      ③ 买家填写项     第一个输入框标题 + 内容类型 + 是否必填
      ④ 下单限制       最小下单数量 / 最大下单数量 / 每人限购
      ⑤ 商品图片与简介 封面 / 多图 / 富文本简介
    底部是常驻的「确定添加 / 保存修改」按钮，不用滚到最底下找
  · 上游对接相关的「从上游获取商品」也跟着搬到这个页面
  · 手机端适配：两栏三栏自动折成一栏，按钮铺满

■ 价格：只有两栏（按你的要求，没开下级所以砍掉普及版/专业版/批发价/加价模板）
      *零售价（元）              成本价（元）
      前台展示 / 买家支付         仅后台可见，留空 = 不设
  · 成本价挂在「规格」上而不是商品上 —— 因为本系统是商品→规格两级、价格在规格上，
    挂商品会导致多规格之间对不上
  · 成本价规则：只存、只在后台展示，不参与任何金额计算；
    前台 / 订单 / 邮件 / API 一律不输出
  · 唯一约束：零售价不能低于成本价
      前端：输入时红框 + 「零售价不能低于成本价」提示，保存前拦下
      后台：保存接口再校验一次（防绕过），提示到具体哪个规格
  · 顺手在列表页加了「成本价」列，显示最低成本价与毛利率；低于成本会标红提醒

■ 新增「最小下单数量 / 最大下单数量」
  · 后台可设；前台下单时校验（一次订单能买几件）
  · 和原有的「每人限购」区分开：限购管的是同一邮箱累计能买几件

■ 修复 3 个隐患
  ① 支付回调缺少重入保护 / 错误回滚
     · 加了同一订单的重入锁：重复回调（支付平台重试、notify 与 result 同时进来）
       只会真正发放一次
     · 原来 commit 之后如果发邮件或通知管理员抛异常，catch 会对着「已提交的事务」
       再 rollBack 一次 —— 这会抛 PDO 异常，而且整个回调返回 false，
       支付平台就会一直重试。现在事务外的工作单独兜底，失败不影响「已支付已发货」
  ② 卡密发放的并发保护
     · 取未售卡密的 SQL 补上 ORDER BY id，保证并发时取到的是确定的一批
     · 占用卡密改成「WHERE id=? AND status=0」，并检查影响行数；
       万一被别的订单抢先，整笔回滚让回调重试，不会把同一张卡发给两个订单
  ③ 商品表单重复提交
     · 编辑页带一次性 submit_token，只有保存成功后才登记；
       网络慢时连点两次不会建出两个重复商品（失败重试不会被误拦）

■ 涉及文件
  · core/goods_ext.php           新增（成本价 / 数量区间的字段与校验）
  · core/lib.php                 fulfill_paid_order 重入锁 + 并发保护 + 错误回滚修复
  · index.php                    下单校验最小/最大数量
  · admin.php                    商品编辑整页化；列表加成本价列；新增 install_fields 操作
  · sql/install.sql              补 specs.cost_price 与 goods.min_qty / max_qty
  · 本文件

■ 上线步骤
  1. 上传上述文件
  2. 后台 → 商品管理 → 直接点「添加商品」或任意商品的「编辑」，
     页面顶部会出现「一键初始化新字段」，点一下即可
     （幂等、可重复点，不会动任何已有数据；不点的话成本价那两栏不会出现）

■ 补充修复：保存商品报「1366 Incorrect integer value: '' for column 'upstream_tid'」
  · 原因：specs.upstream_tid 在老库里是 int(11)，新版为了存异次元的商品 code
    （字符串）需要它是 varchar(64)。这段迁移原先只写在「上游站点」页的一键初始化里，
    所以只更新了代码、没点过那边初始化的站，保存商品时把空字符串写进 int 字段就被
    MySQL 直接拒绝了。
  · 处理（三重保险）：
      ① 把这段迁移也并入商品页的「一键初始化新字段」，两个入口随便点哪个都行
      ② goods_ext_ready() 现在也会检查 upstream_tid 是不是 varchar，
         没迁过来就算「未就绪」，编辑页顶部会照常弹出一键初始化提示
      ③ 保存逻辑里加了字段类型探测：万一还没初始化就点了保存，
         对 int 字段自动把空值写成 0，不再 500；
         如果是异次元的字符串 code 存不进 int 字段，会给出明确提示
         （告诉你去点一键初始化），而不是抛 SQL 报错

--------------------------------------------------------------------------------
【新】优惠券 + 售后工单
--------------------------------------------------------------------------------
■ ① 优惠券（营销中心 → 优惠券）
  · 两种券：满减（直接减固定金额）、折扣（按比例打折，如 8.5 折）
  · 可设置：最低消费、折扣券最高抵扣、发行总量、每人限用次数、限定商品、
    生效 / 失效时间、启用停用、备注
  · 批量生成：按表单里的参数一次生成多张随机券码（1~200 张，每张限用 1 次）
  · 券码自动转大写，避免买家输入大小写不一致导致「券不存在」
  · 已用次数直接从订单表统计（待支付 + 已支付都算占用，防止一张券被刷单占满）
  · 抵扣规则（这一点很重要）：
      抵扣只作用于「商品金额」，运费不打折；
      折扣券可以设「最高抵扣」上限；
      无论怎么算，订单至少保留 0.01 元应付 —— 否则会生成 0 元订单，
      把易支付这类通道直接搞挂
  · 前台：商品详情页会自动出现「有优惠券？输入券码」的输入框，
    点「使用」即时校验并显示抵扣金额，下单时自动带上券码
  · 开放 API（act=create_order）也支持传 coupon_code 参数
  · 实现方式：和验证码一样走「注入」，7 套主题 + 兜底模板一律不用改

■ ② 售后工单（订单管理 → 售后工单）
  · 买家在订单详情页点「申请售后」，带着订单号跳到工单页提交
  · 四种问题类型：没收到卡密 / 卡密无效 / 与描述不符 / 其他
  · 提交后拿到工单号，凭「工单号 + 下单邮箱」随时回来查看客服回复
  · 后台：按状态筛选（待处理 / 处理中 / 已解决 / 已关闭）、关键词搜索、分页；
    点「查看 / 回复」弹窗里能看到买家描述、关联订单信息（商品 / 金额 /
    支付状态 / 卡密预览），直接填回复并改状态
  · 有新工单时，如果开了「订单通知邮件」，会给管理员发一封邮件
  · 安全：
      提交时要校验订单真实存在，且邮箱必须和订单上留的一致（订单没留邮箱则跳过）；
      同一订单 10 分钟内最多 3 张工单，防刷；
      查看工单必须「工单号 + 邮箱」都对，防止遍历工单号偷看别人的售后；
      工单内容纯文本存储，输出统一转义，不解析 HTML
  · 一个订单可以提多张工单（先反映问题、后来补充情况）
  · 权限：售后工单本来就是客服的活，所以「客服」角色也开放；
    优惠券、公告管理这类只给「运营」和超级管理员

■ 涉及文件
  · core/coupon.php            新增（优惠券：建表 / 校验 / 算价 / 前台注入）
  · core/ticket.php            新增（工单：建表 / 提交 / 查询 / 回复 / 前台注入）
  · ticket.php                 新增（前台工单页：提交 + 查询 + 查看回复）
  · index.php                  下单支持优惠券；新增 act=coupon_check；商品页注入券码输入框
  · core/api.php               开放 API 下单支持 coupon_code
  · api.php                    加载 coupon.php
  · result.php                 订单详情页注入「申请售后」入口
  · admin.php                  新增 act=coupons（优惠券）与 act=tickets（售后工单）；
                               菜单加「优惠券」（营销中心）与「售后工单」（订单管理）
  · sql/install.sql            补上两张新表与订单表的 3 个券字段
  · 本文件

■ 上线步骤
  1. 上传上述文件
  2. 后台 → 营销中心 → 优惠券 → 点「一键初始化」
  3. 后台 → 订单管理 → 售后工单 → 点「一键初始化」
  （两个初始化都是幂等的，可重复点，不会动任何已有数据；）
  （全新安装的站不用手动点，install.sql 里已经带了这两张表）

--------------------------------------------------------------------------------
【新】订单查询页全部重做（7 套主题统一）+ 手机端适配 + 新增「公告管理」
--------------------------------------------------------------------------------
■ 订单查询页重做
  · 前台「订单查询」页（pay.php?act=query）按参考站重新设计：
      顶部条（返回箭头 + 站点名/订单查询标题）
      → 居中大标题 → 说明文字
      → 搜索行（输入框 + 主色查询按钮，带放大镜图标）
      → 免责声明（「免责声明：」开头自动加深）
      → 浅黄色「温馨提示」卡片（自动编号列表 + 底部红字）
      背景是淡淡的马赛克方块底纹（中间浓、四周淡），和参考站一致
  · 7 套主题（default / elegant / gradient / minimal / card / cute / jing）全部换新，
    各自保留自己的主色，布局完全统一
  · 手机端适配：
      ≤640px 时顶部条改为「返回箭头 + 居中标题」，大标题 32px→23px，
      输入框与按钮保持同一行、按钮变胶囊形（跟参考站手机端一致），
      边距与字号整体收紧，适配 iPhone 底部安全区（env(safe-area-inset-bottom)）
  · 「找到 N 条匹配订单」结果页（pay.php 多条匹配分支）一并换成同一套风格
  · 查不到订单时的红框提示重新排版（不再是 13px 居中挤在按钮上方）

■ 代码结构：7 份模板合并成 1 份
  · 新增 front/tpl/query_page.php —— 查询页本体，7 套主题共用
  · themes/<主题>/query.php 现在只有 4 行：设置本主题主色 → include 公共页
      $qAccent = '#ff5722';  $qAccentDark = '#e64a19';
      include __DIR__ . '/../../front/tpl/query_page.php';
  · 好处：以后改查询页样式只改一个文件，不会像以前那样 7 份分头改、改漏

■ 新增后台「公告管理」（系统管理 → 公告管理）
  · ① 站点公告（首页顶部横幅，支持 HTML）
  · ② 公告弹窗（开关 + 弹窗内容，用户首次访问弹出）
  · ③ 订单查询页文案：页面大标题 / 说明文字 / 输入框提示 / 按钮文字
        附「预览这一页」链接、「恢复默认文案」按钮
  · ④ 订单查询页免责声明：开关 + 内容
  · ⑤ 订单查询页温馨提示：开关 + 卡片标题 + 提示内容（一行一条，自动编号）+ 底部红字
  · 原来在「系统设置」里的公告、公告弹窗两项已挪到这里，系统设置里留了一行指路说明
  · 权限：超级管理员与「运营」角色可访问，客服角色不可见

■ 顺带修好的两个问题（独立审核发现）
  · 订单查询页副标题的类名写成了 .sub 但样式定义的是 .q-sub，会导致副标题没有样式
    （黑色、左对齐）→ 改成 class="sub q-sub"，同时满足 pay.php 的注入脚本和页面样式
  · jing 主题原本没有「公告弹窗」：另外 6 套主题都有，只有它缺，导致公告弹窗开关
    在 jing 主题下点了没反应 → 已按 card 主题的实现补上（弹窗样式 + 结构 + JS），
    并顺手让 jing 的顶部公告也支持 HTML（原来是 strip_tags 强制剥掉的）

■ 涉及文件
  · front/tpl/query_page.php        新增（查询页本体）
  · front/tpl/query.php             改为引用公共页
  · themes/*/query.php              7 个主题文件 → 各 4 行的主色配置
  · themes/jing/index.php           补上公告弹窗；顶部公告支持 HTML
  · preview-order-query.html        新增（静态预览，仅用于看效果，系统不加载）
  · pay.php                         查不到订单的提示重新排版；多条匹配结果页换新；注入脚本不再覆盖新副标题
  · admin.php                       新增 act=notice 公告管理页；从系统设置移除公告/弹窗字段；菜单加「公告管理」
  · sql/install.sql                 补上订单查询页文案的默认设置项
  · 本文件

--------------------------------------------------------------------------------
【重写】上游对接模块 2.0：按官方文档重做彩虹代刷 / 异次元发卡 / 独角数卡
--------------------------------------------------------------------------------
■ 背景
  · 之前的「异次元 / 独角数卡」适配是我自己臆想的 /api/v1/goods 之类路径，
    这三家在官方源码里根本不存在，所以怎么点都对接不通。
  · 这次把三家的官方接口源码逐行读完，按真实协议重写，并拆出独立的适配层。

■ 新架构（新增文件 core/upstream_adapters.php）
  · 抽象基类 UpstreamAdapter：统一契约，业务代码不再关心上游是哪家
       siteInfo / goodsList / goodsByCid / goodsDetail / classList / leftCount /
       pay / searchOrder / changeOrder / balance
  · 统一商品结构：tid(字符串) / cid / name / price / stock / isfaka / input / desc
  · 统一订单状态：0 待处理 · 1 已完成 · 2 处理中 · 3 异常 · 4 已退单
  · 统一成功判定 UpstreamResult::isOk()：适配器把各家结果归一化成 code=0 成功
  · 四套适配器：
      CaiHongAdapter    彩虹代刷        按官方 api.php 源码实现
      AcgFakaAdapter    异次元发卡       按官方「店铺共享」协议实现
      DujiaokaAdapter   独角数卡        按 Dujiao-Next 开放 API 实现
      GenericHttpAdapter 通用 HTTP      路径/字段可后台覆盖
  · UpstreamAutoClient：自动识别客户端。按选定的系统请求，打不通就用其它适配器
    挨个探测识别，认出后自动把 sys_type 写回数据库，以后不再重复探测。

■ 彩虹代刷（源码级修正，和网上二手文档不一致的地方一律以源码为准）
  · siteinfo 没有 code 字段，字段是 sitename/kfqq/anounce(官方拼错)/version/build
    → 判定成功的逻辑改成「code >= 0 即成功」，而不是死认 code==0 或 ==1
  · pay 成功返回 {code:0,message:"success",orderid:123}，空卡直发时额外带
    faka=true 与 kmdata[{card,pass}]（pass 是嵌套字段，不是顶层的）
  · pay/goods*/search 的错误文字在 message 字段，change 在 msg 字段 —— 两个都取
  · search 的 status 是数字 0/1/2/3/4（不是中文），订单结果文字在 data['订单结果']
  · getleftcount 必须用 POST（官方文档写 GET 是错的），tid 逗号分隔最多 20 个；
    返回 code=-2 表示「该商品不限库存」——按不限库存处理，不再当成错误
  · change 的参数名是 zt（不是 status），走 GET，鉴权用「API对接密钥」

■ 异次元发卡（acg-faka）—— 官方开放接口是「店铺共享」协议
  · 真实路径（全部 POST + form-urlencoded）：
      /shared/authentication/connect   连通性测试
      /shared/commodity/items          商品列表（分类树 + children）
      /shared/commodity/item           商品详情（参数 code，另兼容 sharedCode）
      /shared/commodity/inventory      库存（⚠ 参数是驼峰 sharedCode）
      /shared/commodity/trade          下单（shared_code / contact / num / card_id ...）
      /shared/commodity/query          查单（参数 tradeNo）
  · 鉴权：app_id（= 商户ID，即 user.id）、app_key（= 商户密钥）、sign
      sign = md5( urldecode( http_build_query(去掉sign后的字段, ksort, 去掉空字符串) . "&key=" . app_key ) )
    商户ID / 商户密钥 在对方网站「用户中心 → 账户安全」里，不是后台设置页
  · ⚠ 成功判定和别家相反：code == 200 才是成功，code == 0 是失败
  · 商品用 code（字符串）标识，不是数字 id → 见下面「数据库调整」
  · 下单是余额扣款供货模式，没有支付链接；卡密在同一个响应的 data.secret 里

■ 独角数卡（dujiaoka）—— 必须说清楚的一件事
  · 老版独角数卡（GitHub assimon/dujiaoka，已归档，最高 2.0.6）**没有任何开放 API**：
    routes/api.php 就是 Laravel 骨架自带的那个，连 Api 控制器目录都没有；
    下单 /create-order 是带 CSRF 的网页表单，查单 /check-order-status/{sn}
    只回 {code:400000/200/400001} 且**不含卡密**。所以它无法被上游对接。
  · 能对接的是官方后继产品 Dujiao-Next（现在 dujiaoka.com 就是它）：
      Base: {域名}/api/v1/upstream
      请求头：Dujiao-Next-Api-Key / Dujiao-Next-Timestamp / Dujiao-Next-Signature
              Signature = HMAC-SHA256(api_secret, "{METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY_MD5}") 小写hex
      接口：POST /ping、GET /categories、GET /products、GET /products/:id、
            POST /orders、GET /orders/:id、POST /orders/:id/cancel
      约定：成功 {"ok":true,...}，失败 {"ok":false,"error_code":...,"error_message":...}
      下单体：sku_id / quantity / manual_form_data / downstream_order_no（幂等键）
      卡密在 fulfillment.payload；状态 pending_payment/paid/fulfilling/delivered/completed/canceled
  · 适配器会自动纠正签名的 PATH 口径（全路径 vs 相对路径），报 invalid_signature 时自动重试

■ 数据库调整（后台「上游站点」页点一次「一键初始化」即可，幂等）
  · specs.upstream_tid 由 int 改成 varchar(64)：
    彩虹/独角数卡是数字ID，异次元是字符串 code，不改类型存不下

■ 后台界面改动
  · 「上游站点」表单按所选系统动态切换字段标签与说明：
      彩虹＝分站用户名 + 分站密码；异次元＝商户ID + 商户密钥；
      独角数卡＝api_key + api_secret；通用 HTTP＝自定义
  · 「测试连接」现在会依次尝试四套适配器，并告诉你「已自动识别为 XX 并更新配置」
  · 「商品对接」页的下单参数字段随上游系统动态变化（彩虹 input1~input5，
    异次元 contact/卡密ID/优惠码…），上游商品标识输入框支持字符串 code

■ 涉及文件
  · core/upstream_adapters.php  新增（协议适配层，四套适配器）
  · core/upstream.php           接入适配层；订单提交/同步/库存改用统一契约
  · admin.php                   上游站点表单动态标签、测试连接、商品对接动态字段
  · 本文件

--------------------------------------------------------------------------------
【修】上游对接自动识别系统类型 + 仪表盘去掉顶部统计卡
--------------------------------------------------------------------------------
■ 修复：用「通用 HTTP」对接彩虹系统时，点订单「同步」报 HTTP 404
  · 现象：弹窗提示「同步失败：请求失败：接口未返回有效 JSON（HTTP 404）：
    404 Not Found / nginx」
  · 原因：「对接系统」选的是「通用 HTTP（自定义接口路径）」，但上游其实是
    彩虹自助下单系统。通用适配器会去请求 /api/goods、/api/order/query 这些
    路径，而彩虹站点上根本没有这些地址，nginx 直接回 404 的 HTML 页面，
    解析 JSON 自然就失败了。
  · 新增 core/upstream.php → HybridApiClient（自动适配客户端）：
      - 先按所选系统的接口路径请求一次；一旦发现对方返回的根本不是 JSON
        （典型就是 nginx 的 404 页），先请求一次彩虹的 /api.php?act=siteinfo
        确认对方确实是彩虹系统（返回里必须带 sitename / version 等站点信息字段，
        防止把「能返回 JSON 但不是彩虹」的站点误判），确认后再用彩虹协议重试原操作
      - 成功后记住这条协议；如果确认是 404 导致的，还会顺手把数据库里的
        sys_type 改成 caihong，之后所有请求直接走彩虹协议，不再多一次探测
      - 安全约束：pay（会真的下单）这类写操作只在 HTTP 404（地址根本不存在）时
        才重试，避免对方其实已经建单、只是返回了非 JSON 时被重复下单；
        查询类接口（站点信息 / 商品 / 查单 / 库存）可以安全重试
      - 两条路都不通时，错误信息会同时列出「通用接口的地址 + 报错」和
        「彩虹接口的地址 + 报错」，并提示检查接口地址与系统类型
      - 通过 didRequest / badEndpoint 两个标记区分「真的发了请求但地址不对」
        和「这个方法本身不发请求」，避免误判（classList / leftCount 等）
  · 后台「上游站点 → 测试连接」现在会明确提示：
    「✅ 连接成功，并已自动把『对接系统』修正为『彩虹代刷』（配置已保存）」
  · 彩虹查单接口补上账号凭据（与下单接口保持一致）
  · 顺便加固彩虹订单状态解析：状态可能在顶层、也可能在 data 里（数字或中文），
    现在都兼容，避免出现「同步成功但状态没变」
  · 涉及文件：core/upstream.php、admin.php（测试连接提示 + 表单文案）

■ 修复：接口地址填成「示例地址」导致怎么连都是 404（本次实测踩到的坑）
  · 现象：测试连接 / 同步一直报
    「彩虹接口 http://cs1.cdcs.cc/api.php?act=siteinfo 没有返回 JSON（HTTP 404）」
  · 真相：cs1.cdcs.cc 是表单里写的示例域名，早就不是彩虹站了
    （实测它现在跳转到「监控助手 · 安装向导」），填这个必然 404
  · 修复：
      - 表单里的示例地址换成明显不是真实域名的提示
        （placeholder 改成「https://你上游站点的真实域名」，并在下方给出填写说明）
      - 保存时直接拦截：填 cs1.cdcs.cc 一律拒绝，并告诉你要填「浏览器能打开对方首页的域名」
      - 前端保存前也会弹确认框提醒
      - 错误信息里也会点破：「cs1.cdcs.cc 是系统表单里的示例地址，不是真实上游站点」
  · 新增：彩虹接口路径自动探测 upstream_probe_caihong_endpoint()
      - 依次尝试 /api.php、/user/api.php、/api/api.php、/index.php、/api/index.php
      - 找到能返回彩虹站点信息（sitename/version/rmb/domain）的那个就用它
      - 如果接口地址原先是「纯域名」，会自动把探测到的完整地址写回配置（并记操作日志）
      - 如果原先是「纯域名 + 真实站点但路径不同」，从此一劳永逸
  · 接口地址现在也支持直接粘贴完整接口地址：
      http://xxx.com/api.php、http://xxx.com/user/api.php 都能识别，
      不会再拼成 http://xxx.com/api.php/api.php
  · 「自动适配」客户端现在对所有系统类型都生效（包括已经保存成「彩虹代刷」的行）：
      - 选彩虹 → 优先走彩虹协议，路径不对会自动探测其它常见路径，再退回通用协议
      - 选通用 HTTP / 异次元 / 独角数卡 → 先走自己的路径，发现对方其实是彩虹就自动换过去
  · 后台「测试连接」连不上时，会把「已尝试过哪些接口地址、各自返回什么 HTTP 状态」
    一并列出来，能一眼看出是域名错了还是路径错了

■ 调整：后台仪表盘去掉顶部 5 个统计卡
  · 去掉「今日订单 / 今日销售额 / 总订单 / 总销售额 / 总浏览量」五张卡片
  · 顺手删掉这 5 个统计对应的数据库查询，打开仪表盘少 5 次查询
  · 页面现在直接从「数据可视化大屏」入口开始，后面依次是库存预警、
    近 30 天销售趋势、商品销量排行与明细

--------------------------------------------------------------------------------
【新】后台登录页改版（左插画 + 右登录卡片，手机端只留登录框）
--------------------------------------------------------------------------------
■ 改版：后台登录页（front/tpl/login.php）换成左右分栏样式
  · 电脑端：左边一块品牌区（左上角「站点名 - 后台管理系统」+ 等距风格插画），
    右边居中一张白色登录卡片（欢迎回来 / 用户名 / 密码 / 记住账号 / 忘记密码 /
    登录按钮 / 页脚版权），右侧背景带柔和的蓝粉光斑
  · 手机端（≤900px）：左侧品牌与插画整块隐藏，只保留右边的登录卡片，
    换成浅色渐变底 + 悬浮卡片；≤400px 再进一步收窄内边距与控件高度
  · 插画是页面内自绘的 SVG（等距平台 + 面板 + 卡片 + 盾牌徽章），
    不引用任何外部图片，也就不存在图片失效 / 加载慢的问题
  · 「记住账号」用 localStorage 只记住用户名（不存密码），下次打开自动回填并聚焦密码框
  · 「忘记密码？」点击给出提示：系统没有自助找回，需超级管理员到
    「系统管理 → 管理员」重置
  · 登录失败 / 未通过安全验证的红框提示、安全验证组件（内置验证码或第三方平台）
    都按新样式重新对齐；组件位置仍在密码框下方，未开启开关时不占位
  · 涉及文件：front/tpl/login.php（整页重写），无需改动其它文件

--------------------------------------------------------------------------------
【新】后台手机端适配 + 安全验证（内置字母数字验证码 + 多平台对接）
--------------------------------------------------------------------------------
■ 改版：后台登录页
  · 见上方独立一节（左插画 + 右登录卡片，手机端只留登录框）

■ 修复：手机端后台「订单列表」等页面右边的信息看不到、划不过去
  · 原因：后台卡片 .card 是 overflow:hidden，宽表格（订单列表 10 列、商品列表 13 列…）
    在手机上撑不开也划不动，右边的列直接被裁掉
  · assets/admin.js：页面加载后自动给每个 <table> 包一层 .table-scroll 横向滚动容器，
    并按列数估算最小宽度（写进 CSS 变量 --zb-minw），表格再宽也能左右滑动查看；
    弹窗里动态生成的表格用 MutationObserver 自动补包一层，页面脚本不受影响
  · assets/admin.css：新增「手机端适配层」，只在窄屏生效，PC 端样式完全不变：
      - ≤1024px：卡片标题里的操作按钮允许换行、面包屑超长省略号、弹窗铺满
      - ≤640px：表格改为横向滑动、筛选条写死的输入宽度收敛到屏幕内、
        所有内联 flex 行自动换行、弹窗/统计卡/分页/上传预览/主题卡片全部重排
      - 表格上方会提示「表格可以左右滑动查看完整内容」
  · 纯前端改动，不改任何业务逻辑，也不影响 PC 端排版

■ 新增：后台一级菜单「安全验证」（admin.php?act=security）
  · 菜单位置：左侧一级菜单，紧跟在「运维监控」下面（独立一项，不放在
    「系统管理」折叠组里，避免被折叠起来找不到；图标为盾牌）
  · 页面结构一共三块：
      1) 总开关      —— 登录后台开关 / 前台下单验证开关 / 免验证时长
      2) 验证方式    —— 卡片式单选，点一下就能在内置验证码和各第三方平台之间自由切换
      3) 当前状态    —— 显示实际生效的方式、GD 状态、以及被锁在门外时的自救办法
  · 总开关（默认全部关闭，装完不影响原来的下单与登录）：
      - 登录后台开关      captcha_login_enabled
      - 前台下单验证开关  captcha_order_enabled
      - 免验证时长（分钟）captcha_pass_ttl，默认 30 分钟，同一访客只验证一次
  · 内置「字母数字验证码」：系统自带、免费、不依赖第三方
      - 4 位（可在 3~6 位之间配置）字母 + 数字，点击图片换一张，5 分钟过期
      - 字符集已去掉 0 / O / 1 / I / L 等容易看错的字符
      - 服务器没装 GD 扩展时自动放行（不会把管理员和买家挡在门外）
  · 可切换到下面这些常用验证平台（密钥保存在 zb_settings，不需要新增数据表）：
      - 极验 Geetest 4.0（行为式验证）
      - 腾讯天御验证码（防水墙）
      - 网易易盾行为式验证码
      - Cloudflare Turnstile
      - hCaptcha
      - Google reCAPTCHA v2
      - 阿里云验证码 2.0
      - 自定义对接（自填前端 JS + 服务端校验接口）
  · 安全兜底：选中的平台密钥没填全时自动退回内置验证码，
    避免出现「密钥填错 → 谁都验证不过 → 谁也下不了单」的故障；后台会给出醒目提示

■ 新增：core/captcha.php（安全验证总控）
  · captcha_providers / captcha_cfg / captcha_save_cfg      平台注册表与配置读写
  · captcha_enabled / captcha_need / captcha_passed          开关与免验证状态
  · captcha_verify_request / captcha_verify_platform         统一校验入口（内置 + 8 个平台）
  · captcha_inject_html('order')                             前台弹窗组件（注入 </body> 之前）
  · captcha_form_widget_html('login')                        后台登录页内嵌组件

■ 改造：captcha.php（根目录）
  · action=code      输出内置「字母数字验证码」图片，支持 ?scene=login|order
  · action=verify    统一校验：内置传 code、第三方传 payload（JSON 或纯令牌）
  · action=status    查询某场景当前是否需要验证（便于排查）
  · 旧写法 action=verify_code（不带 scene）仍然可用，按后台登录场景处理
  · ⚠ 已删除老版本的滑块接口 action=get / action=slider_verify：
    这两个接口能把任意场景直接标记成「已通过」（GET 一张拼图 + 猜 x 即可），
    等于给安全验证开后门；本版本已无任何页面引用它们
  · ⚠ 内置验证码改为「取过码才认」：没请求过验证码就直接提交一律拒绝，
    不再无条件放行（之前那样任何人都能直接 POST verify 拿到通过状态）。
    只有在服务器确实没装 GD 扩展时才整体放行，保证不会把人锁在外面

■ 接入：后台登录
  · admin.php 登录流程：勾了「登录后台开关」后，先校验验证码 / 平台令牌再验账号密码
  · 密码错误会立刻作废本次验证状态（防止一次验证反复试密码），登录成功后也清掉
  · front/tpl/login.php 自动显示验证码输入框或第三方组件，未开启开关时一个字节都不输出

■ 接入：前台下单（零侵入，不动任何主题文件）
  · index.php?act=create 服务端强制校验：没通过验证的请求一律拒绝
    （返回 code=0 + msg「请先完成安全验证」，data.need_captcha=1）
  · index.php 商品详情页自动注入验证弹窗：拦截主题的 fetch(act=create)，
    验证通过后自动重放原请求，取消则告知调用方
  · 7 个前台主题（default / elegant / gradient / minimal / card / cute / jing）全部自动生效
  · 顺带补了一版「前台手机端兜底样式」（core/lib.php → front_mobile_inject_html）：
    窄屏下订单号等长串自动换行、图片不撑破卡片，PC 端不受影响

■ 涉及文件
  · core/captcha.php    新增
  · captcha.php         重写（内置验证码 + 统一校验入口，滑块验证保留）
  · admin.php           新增「安全验证」菜单与页面；登录接入安全验证；系统设置页提示同步更新
  · index.php           引入 core/captcha.php；下单接口校验；商品页注入验证弹窗
  · front/tpl/login.php 后台登录页整页改版（左插画 + 右登录卡片，手机端只留登录框）；显示安全验证组件
  · core/lib.php        新增前台手机端兜底样式 front_mobile_inject_html()
  · result.php / pay.php 前台订单结果 / 查询页注入手机端兜底样式，查询列表页手机端重排
  · assets/admin.css    新增手机端适配层 + 安全验证页样式
  · assets/admin.js     新增表格横向滚动自动包装
  · 本文件
  · 无数据库变更：所有开关与平台密钥都存在 zb_settings 表里，无需导入 SQL

--------------------------------------------------------------------------------
【新】卡密授权门禁：没有卡密授权，整个系统不可用（安装页 / 后台都能填卡密）
--------------------------------------------------------------------------------
■ 新增：全局授权门禁（core/lib.php + core/license.php）
  · 判定方式：本地看 zb_settings 里有没有 token、有没有过期，不联网、不采集机器码，
    走 setting() 的内存缓存，所以前台每个请求的开销可以忽略
  · 未授权时：前台页面 / 接口一律 403 拦截，显示「系统未授权」页；api.php、order_action.php
    这类接口以及 AJAX 请求返回 JSON（code=0 + msg），不会给调用方回一段 HTML
  · 未授权时：后台只能进「系统管理 → 授权与更新」，访问其它页面会被弹回该页并给出提示，
    左侧菜单也只显示这一项
  · 门禁写在 core/lib.php 末尾自动生效（任何入口只要加载了 lib.php 就受保护），
    以后新增入口不会漏
  · 故意豁免的入口：
      install.php  安装向导（还没装好，必须能进）
      notify.php   支付异步回调（已经付过钱的订单不能因为授权掉了就丢单）
      cron.php     计划任务（掉单补发 / 超时关单 / 上游同步继续跑）
      captcha.php  验证码（登录页要用）
      admin.php    后台（按页面粒度拦，见上）
      命令行执行（PHP_SAPI === cli）同样不拦
  · 授权成功后所有功能立即恢复，不需要重启或改任何配置
  · ⚠ 上传时千万别漏 core/license.php：该文件缺失（漏传 / 被删 / 被改名）时前台同样一律 403
    （不会静默放行变成"没授权也能用"），只放行安装向导、支付回调、计划任务、验证码与后台，
    方便管理员进后台把文件补回去。热更新后的自检也会检查这个文件是否存在
  · ⚠ 因为 core/lib.php 现在会自动加载 core/license.php，admin.php / core/cron.php 里对它的
    引用必须用 require_once（不能再用 require），否则会「函数重复声明」直接 500；
    本次已把 admin.php、core/cron.php 的 require 全部改成 require_once + is_file 保护
  · 拦截页与接口都带 `Cache-Control: no-store`，避免被 CDN/反代缓存住导致授权后仍显示拦截页
  · 被拦时按接口类型返回：api.php / order_action.php / upload.php 返回 JSON（code=0 + msg），
    其余页面返回 403 拦截页
  · 建议再叠一层服务器防护：Nginx 里 `deny /core/` 与 `/themes/*.php`（core/ 下都是被 include
    的库文件，本来就不该被直接访问），robots.txt 里已经 Disallow: /core/

■ 新增：安装界面可以填卡密（选填）
  · install.php 新增「卡密 / 授权密钥（选填）」输入框：
      填了 → 建表完成后当场激活，成功 / 失败都会写进安装结果清单
      不填 → 照常安装，但装完系统是未授权状态，必须到后台「系统管理 → 授权与更新」补填
  · 卡密填错不会导致安装失败（照样能装完、能进后台），只是仍然处于未授权状态

■ 调整：后台「授权与更新」页
  · 顶部的「授权」按钮改名「卡密授权」，弹窗内字段改为「卡密（授权密钥）」
  · 删除「授权服务器」整块设置卡（接口地址 / AppKey / 通信密钥 / RSA 公钥 / 更新包地址），
    使用者看不到也改不了；这些值改为 core/license.php 顶部的常量：
      LICENSE_DEFAULT_API / LICENSE_DEFAULT_APP_KEY / LICENSE_DEFAULT_CLIENT_SECRET / LICENSE_DEFAULT_PUBLIC_KEY
    如果需要单独的通信密钥，把 LICENSE_DEFAULT_CLIENT_SECRET 填上即可（留空 = 用 AppKey 参与签名）
  · 授权状态表里也不再显示「授权服务器 / 传输方式 / 授权系统签名」这些服务器细节
  · 未授权时：页面顶部给出醒目提示（整个系统此时只有本页可用），并隐藏「热更新」与
    「更新环境自检 / 更新日志」两张卡；对应接口也一并限制：未授权时 POST
    check_update / do_update / upload_update 一律返回「请先完成卡密授权」

■ 涉及文件
  · core/license.php  新增 license_is_active / license_deny / license_auto_gate /
                      license_admin_guard；license_conf() 改为常量兜底；
                      删除 license_conf_save()（页面不再暴露配置）
  · core/lib.php      末尾加载 core/license.php，让门禁对所有入口自动生效；
                      多一道兜底：core/license.php 缺失时前台一样 403（不静默放行）
  · core/update.php   热更新后的关键文件自检加入 core/license.php
  · admin.php         登录后执行 license_admin_guard()；菜单未授权时只留「授权与更新」；
                      授权按钮 / 弹窗改名；删除授权服务器设置卡与对应 JS；热更新按授权状态显示
  · install.php       新增选填的卡密输入框 + 建表后自动激活；安装完成提示补充授权步骤

--------------------------------------------------------------------------------
【新】后台「系统管理 → 授权与更新」：授权按钮 + 热更新（授权系统对接）
--------------------------------------------------------------------------------
■ 新增：授权对接（鼠大侠 V2 授权验证系统，文档 https://sq.zbiwl.cn/docs/api.php）
  · 后台左侧「系统管理 → 授权与更新」，页面上一个醒目的「授权」按钮：
    点开填授权码 → 自动采集本机机器码（CPU / 硬盘 / 网卡 / 主板）→ 调 activate.php 激活
  · 页面还提供：刷新云端状态（心跳，同时拉云端变量 / 公告 / 最新版本）、申请试用、
    重新采集机器码（换服务器 / 换硬件后用，采集完页面显示的就是激活上报的那一份）、
    清除本机授权，以及授权状态表（授权码打码显示、到期时间、剩余天数、机器码一键复制）
  · 接口地址 / AppKey / 通信密钥（client_secret）/ RSA 公钥 都能在页面上修改，
    默认已经写好本系统的 AppKey 与 RSA 公钥；通信密钥留空时用 AppKey 参与签名
  · 传输方式自动探测：RSA+SHA256 失败会依次自动尝试 RSA+MD5、明文+SHA256、明文+MD5
    （文档说明服务端同时支持 SHA256 / MD5 两种签名，并提供「明文模式」），
    成功一次后记住该方式，页面「传输方式」一行会显示实际生效的是哪一种；
    若报「签名错误」，把授权系统后台的通信密钥填进页面即可，不用改代码
  · 授权信息全部存 zb_settings（k/v），不新增数据表

■ 新增：热更新（同一个页面）
  · 「检查更新」：调 heartbeat.php，读云端 cloud.latest_version（version / version_code /
    download_url / force_update）并与本地版本比较（优先比 version_code，再比版本字符串）
  · 「立即更新」：下载更新包 → 解压 → 先备份被覆盖的文件 → 覆盖程序目录 → 写新版本号
  · 也支持「上传 zip 并更新」：本地上传更新包直接升级（云端还没配更新包时可先自己打包）
  · 更新包约定：包内就是网站根目录的内容；也允许多套一层目录（能识别 core/lib.php 或
    update.json 时自动剥掉那层）；可选清单文件 update.json（也认 manifest.json / version.json）：
      {"version":"V3.1","version_code":301,"notice":"更新说明","delete":["old/a.php"]}
  · 安全护栏：
      - 永不覆盖 config.php / install.php / .user.ini / uploads/ / update_backup/ / *.log
      - 更新前把被覆盖（以及被删除）的文件备份到 update_backup/日期时间/，可人工回滚
      - 解压时挡掉 ../ 与盘符路径（zip slip），单包限制 300MB / 2 万个文件
      - 解压临时目录优先放系统临时目录（不在网站目录里，避免解压出来的文件被网址访问到）；
        覆盖前再用 realpath 复核每个目标路径必须落在网站根目录内
      - 没有 zip 扩展、回退用 unzip 命令时，先用 unzip -l 列一遍条目挡掉越界路径再解压
      - 下载更新包、访问授权服务器都默认校验证书，只有证书类失败才降级重试一次
      - 下载、上传、执行都在后台登录 + CSRF 校验之后才能触发
      - update_backup/ 里自动放 .htaccess（Require all denied）与 index.html；Nginx 不读
        .htaccess，建议在宝塔「网站 → 配置文件」里补一条：
          location ^~ /update_backup/ { deny all; }
  · robots.txt 新增 Disallow: /update_backup/ 与 Disallow: /update.log

■ 关键：更新完成后「当前版本号」自动变成最新版
  · core/lib.php 新增 system_version()：取「代码常量 SYSTEM_VERSION」与「热更新写入
    zb_settings.system_version」里较新的那个；ver_norm() 负责把 V3.0 / 3.0.1 规范化再比较
  · 更新成功时写入 system_version / system_version_code / system_version_time，
    因此仪表盘底部的「系统版本」与「授权与更新」页的「当前版本」立刻显示最新版，
    不用再手改代码里的常量（改了常量也不会显示成旧号）
  · set_setting() 现在会把 setting() 的静态缓存标记为脏，同一次请求里读到的就是新值

■ 其它
  · 后台菜单「系统管理」新增「授权与更新」（super / admin 可见，客服不可见），
    新增 svg 图标 license / update
  · core/cron.php 末尾增加授权保活：每次计划任务顺带发一次心跳（服务端要求 5 秒一次，
    PHP 站点做不到，5 分钟一次足够用于拉取云端变量 / 公告 / 最新版本）
  · assets/admin.css 新增「授权与更新」页样式（授权状态头、版本对比卡）
  · 不涉及数据库改动：全部走 zb_settings，不需要点「一键初始化」

■ 涉及文件
  · core/license.php   新增（授权 SDK + 机器指纹 + 本地状态存取）
  · core/update.php    新增（热更新引擎）
  · core/lib.php       system_version() / ver_norm()；setting() / set_setting() 缓存脏标记
  · core/cron.php      授权心跳保活
  · admin.php          引入两个新文件；菜单与权限；act=license 页面 + 全部 AJAX 动作；
                       仪表盘版本号改读 system_version()；新增两个 svg 图标
  · assets/admin.css   授权与更新页样式
  · robots.txt         Disallow /update_backup/ 与 /update.log

--------------------------------------------------------------------------------
【新】侧边栏「计划任务」独立页面 + 后台整体视觉层次优化
--------------------------------------------------------------------------------
■ 新增：左侧菜单「运维监控 → 计划任务」（admin.php?act=cron）
  · 原来只有仪表盘上的一张定时任务状态卡，现在独立成页，监控信息集中在这里
  · 运行状态：上次执行时间（大字号 + 红绿状态药丸）、是否正常运行、正常周期说明
  · 异常时给出红色提示条，直接说明「掉单自动补发 / 超时关单 / 上游同步都会失效」
  · 「立即执行一次」按钮：不用等计划任务，当场跑一遍（只影响本次，
    故意不更新「上次执行时间」，避免掩盖计划任务本身没在跑的问题），执行日志输出到页面
  · 任务清单表：超时自动关单 / 掉单自动补发 / 上游订单同步 / 上游库存同步 / 库存预警邮件，
    每项写清作用与当前配置（关单分钟数、上游是否启用等）
  · 配置步骤卡：宝塔计划任务 4 步图文说明 + 一行命令 + 一键复制按钮 + PHP 路径排查提示
  · 仪表盘上原来那张大卡片已移除，只在「计划任务没在跑」时保留一条黄色提示条（带跳转链接），
    避免把提醒彻底藏起来导致掉单没人发现
  · 权限：新页面加入 admin（运营）角色可见列表；super 依旧全可见

■ 删除：后台「系统设置 → 页脚版权」区块
  · 交付给他人使用时不再暴露作者信息编辑入口（作者信息改为 core/lib.php 里写死，
    仪表盘底部的版权表格照常展示，见上一节）

■ 优化：后台所有页面的区块层次感（assets/admin.css）
  · 页面底色改为带冷色调过渡的渐变，不再是死白一片
  · 卡片：更明确的描边 + 双层投影（悬浮感）+ 顶部渐变高光条，卡片与底色明显分层
  · 卡片标题：左侧蓝色竖条 + 分隔线，设置页那些带 border-top 的次级小标题同样带竖条
  · 表格：表头底色加深、斑马纹、悬停高亮（修掉了斑马纹抢掉悬停高亮的层级问题）
  · 计划任务页专用样式：状态区、深色执行日志输出框、命令框、步骤列表、提示条

■ 涉及文件
  · admin.php        svg_icon 新增 cron 图标；菜单新增「运维监控」组；权限表加 cron；
                     新增 act=cron 页面；仪表盘大卡改为条件提示条；删除设置页页脚版权区块
  · assets/admin.css 追加「区块层次感增强层」+ 计划任务页样式 + 页脚表格样式补全
  · core/lib.php     无需改动（cron_health / recover_lost_orders 等原有函数直接复用）

--------------------------------------------------------------------------------
【新】后台仪表盘底部「版权与运行状态」表格
--------------------------------------------------------------------------------
■ 新增
  · 「仪表盘」页面底部新增一张卡片（样式与后台列表表格一致）：版权与运行状态
    其他后台页面（商品 / 订单 / 设置…）不显示，只在仪表盘展示，用于对外交付时标明作者
  · 展示内容：作者名称 / 作者网站（可点击）/ 联系方式 / 系统版本 /
    服务器系统 / Web 服务器 / PHP 版本 / MySQL 版本 / Redis 缓存 /
    服务器时间 / 系统负载 / 磁盘剩余 / 内存占用与上限
  · 作者信息可在「后台 → 系统设置 → 页脚版权」里直接填，留空则用 core/lib.php 里的默认值
  · 系统版本号改成常量：core/lib.php 顶部的 SYSTEM_VERSION（当前 V3.0，升级时改这一处）

■ 涉及文件
  · admin.php        新增 admin_footer_info()，admin_footer() 里调用；系统设置页新增「页脚版权」三项
  · core/lib.php     新增 SYSTEM_VERSION 常量、author_info()、format_bytes()
  · assets/admin.css 新增 .card.sysinfo 相关样式（沿用后台表格风格）
  · 无需改数据库：作者信息走 zb_settings 的 k/v，setting() 有默认值，不升级也不会报错

--------------------------------------------------------------------------------
【新】空数据库一键安装：sql/install.sql + install.php
--------------------------------------------------------------------------------
■ 背景
  · 换服务器 / 新建站点后，如果 faka 库里没有 zb_goods 等基础表，首页会直接 500：
    Fatal error: ... Table 'faka.zb_goods' doesn't exist in .../index.php:238
  · 项目里 sql/ 目录原本只有升级脚本（indexes / upstream / physical / security_api），
    没有包含基础表的初始建表脚本，所以空库根本装不起来

■ 新增文件
  · sql/install.sql  空库完整初始化：15 张表一次建全（基础表 + 上游 + 实物 + 安全/API），
                     所有索引一并建好，并写入默认站点配置与 3 个支付通道占位行；
                     全部为 CREATE TABLE IF NOT EXISTS / INSERT IGNORE，可重复执行、不覆盖数据
  · install.php      一次性安装向导：读取 config.php 连库 → 执行 sql/install.sql →
                     按你填写的账号密码创建后台管理员（password_hash 生成，不写死哈希）
  · ⚠ 安装完成后必须删除 install.php，否则任何人都能重置后台密码；已装过会被拒绝，需勾选「强制重装」

■ 用法
  · 上传 install.php 与整个 sql/ 目录到网站根目录 → 访问 https://域名/install.php → 填账号密码 → 安装 → 删掉 install.php
  · 有旧库备份的话优先直接导入备份（商品 / 订单 / 配置都能一起回来），本方案是「没有备份、纯空库」时的兜底

--------------------------------------------------------------------------------
【2026-10-01】安全三件套（取货密码 / 登录加固 / 定时任务体检）+ 开放 API
--------------------------------------------------------------------------------
■ 升级前必做：数据库升级（二选一，都是幂等的，重复执行没关系）
  · 后台「系统设置 → 商品扩展功能 → 一键初始化」按钮（推荐，同时会补订单字段）
  · 或宝塔 → 数据库 → 导入 sql/security_api.sql
  · 说明：即使忘了升级，代码也会自动尝试补列（order_guard_ready），不会白屏；
    但 API 密钥表建议用上面两种方式正规创建。

■ 新增 1：取货密码（堵住卡密泄露）
  · 背景：原来 pay.php 支持「订单号 或 邮箱」直接查单，result.php?order_no=xxx 也能直接看卡密，
    等于知道买家邮箱的人就能拿走卡密
  · 现在每个新订单自动生成 6 位「取货密码」：
      - 下单成功后结果页右下角弹出密码卡（可一键复制）
      - 发货邮件里也会带上密码 + 查单链接（模板变量新增 {fetch_pwd}、{query_url}）
      - 查单 / 看卡密时需要输入密码；同一浏览器发起支付的订单自动放行（不用重复输）
      - 老订单（无密码）保持原来的免密查询，不影响老客户
      - 密码入口有简单限速：同一 IP + 订单 10 分钟内最多试 20 次
  · 涉及文件：core/lib.php、index.php、pay.php、result.php

■ 修复（顺带发现的真实漏洞）：result.php 的「同步回调兜底」原来没有验签
  · 任何人构造 result.php?order_no=xxx&trade_status=TRADE_SUCCESS 就能把未支付订单直接变成已发货
    （自己下单 → 不付钱 → 手工访问这个地址 → 白拿卡密）
  · 现在必须先通过易支付签名校验（与 notify.php 用同一套 epay_sign_check），验签失败不发货
  · 涉及文件：core/lib.php（新增 epay_sign_check）、result.php、notify.php（改用公共函数）

■ 新增 2：后台登录加固（替代原来的滑块 / 图形验证码）
  · 登录失败锁定：同一「IP + 账号」连续失败 N 次（默认 5 次）锁定 M 分钟（默认 10 分钟），
    可在「系统设置 → 安全设置」调整；登录成功自动清零
  · 后台 IP 白名单：留空 = 不限制；支持 1.2.3.4 / 1.2.3.* / 1.2.3.0/24，多个用逗号或换行分隔；
    不在名单内的 IP 访问 admin.php 一律 403（对所有后台请求生效，不只是登录页）
  · 只认 REMOTE_ADDR，不信任 X-Forwarded-For，避免伪造请求头绕过
  · ⚠ 白名单填错会把自己挡在门外，补救办法：
      DELETE FROM `zb_settings` WHERE `k` = 'admin_ip_whitelist';
  · 涉及文件：core/security.php（新增）、admin.php

■ 新增 3：定时任务健康检查
  · core/cron.php 每次执行会记录时间，后台「仪表盘」新增一张卡片显示「上次执行 x 分钟前」
  · 超过 15 分钟未执行会标红，并直接给出宝塔计划任务的配置命令，避免
    「掉单自动补发 / 超时关单 / 上游同步」静默失效却没人知道
  · 涉及文件：core/cron.php、core/security.php、admin.php

■ 新增 4：开放 API（给别人对接你的站）
  · 后台新增菜单「系统管理 → API 对接」，可生成 / 停用 / 重置 / 删除密钥，
    支持每日调用上限、IP 白名单、备注，并显示今日调用量与最近调用时间
  · 接口入口 api.php，act：ping / goods / goods_detail / create_order / query_order
  · 签名：sign = strtoupper(hash_hmac('sha256', 参数按名升序拼成 k=v&k=v, key_secret))
  · 安全设计：API 只能查到「由自己密钥创建」的订单（zb_orders.api_key_id 隔离）
  · 后台页面内置完整接口文档 + PHP 调用示例，可直接复制给对接方
  · 调用日志写 api_debug.log（不含签名与邮箱），超过 2MB 自动截断
  · 涉及文件：api.php（新增）、core/api.php（新增）、core/lib.php（新增 site_base_url）、admin.php

■ 其它
  · robots.txt 增加 Disallow: /*.log 与 /api.php
  · 新增 sql/security_api.sql（zb_login_attempts、zb_api_keys、orders.fetch_pwd / api_key_id）
  · 后台「一键初始化」按钮现在会同时创建上述表与字段


--------------------------------------------------------------------------------
【2026-10-01】后台登录取消安全验证 + 手机端界面优化（已被上一节取代/增强）
--------------------------------------------------------------------------------
■ 改动：后台登录页「安全验证」已整条去掉（不再有滑块，也不再有验证码弹窗）
  · 登录页（front/tpl/login.php）现在只有：管理员账号 + 登录密码 + 登录按钮
  · 服务端（admin.php）登录分支里的验证码校验也一并删除，不再读取
    $_SESSION['captcha_passed']、不再读写 login_captcha 设置
  · 后台「系统设置 → 安全设置」里的开关已移除，改为一条说明文字
  · 涉及文件：front/tpl/login.php、admin.php
  · ⚠ 安全提示：登录页不再有任何验证，请务必使用「强密码 + 不要用 admin 这种
    常见账号名」，并建议给 admin.php 加一层服务器防护（如宝塔「网站 → 访问限制」
    / IP 白名单 / 宝塔防火墙的登录保护），例如只允许你自己的 IP 访问后台。

■ 说明：如何把安全验证恢复回来（如果以后需要）
  · captcha.php 没有删除，滑动拼图（?action=get / verify）和图形验证码
    （?action=code / verify_code）两套接口都还在，可以随时接回登录页
  · 恢复步骤：在 admin.php 登录分支里把「校验 $_SESSION['captcha_passed']」的判断加回去，
    并在 front/tpl/login.php 里引用 captcha.php 生成验证码；具体可参考本文件下方
    【2026-10-01】后台登录改为「弹窗输入验证码」那一节。

■ 紧急通道：如果服务器上还是旧版登录页导致进不去后台
  · 方式一（推荐）：把本目录下的 front/tpl/login.php 上传覆盖，登录页立刻变干净
  · 方式二（不想动代码）：宝塔 → 数据库 → 执行
        REPLACE INTO `zb_settings` (`k`,`v`) VALUES ('login_captcha','0');
    再把根目录的 captcha.php 临时改名为 captcha.php.bak
    （旧登录页加载验证码失败会自动隐藏滑块并放行登录），登录成功后改回来即可
  · 注意：设置项有 5 分钟 Redis 缓存（config.php 的 redis.ttl），改完稍等一下或重启 Redis

■ 优化：前台「鲸发卡风格」手机端界面
  · 顶部导航：手机端导航条改为 56px，Logo 旁店名过长自动省略号（避免和右侧按钮挤在一起）；
    「订单查询」药丸按钮缩小到 34px 高（原来是 40px），间距收紧
  · 商家信息区：联系客服 / 分享 / 「···」三个按钮统一为 34px 高的圆角按钮，
    「···」不再是文字省略号，改为真正的图标按钮 + 下拉菜单（订单查询 / 常见问题 / 分享本页），
    手机上点击不再歪歪扭扭
  · 商品列表：手机端由「一排一个」改为「一排两个」（去掉 ≤400px 时降为单列的规则），
    卡片内边距 / 封面色块 / 价格与标签排布同步调整（价格与标签上下排列，避免窄卡片换行错位）
  · 商品详情页顶部导航同步做手机端紧凑处理
  · 涉及文件：themes/jing/index.php、themes/jing/goods.php

--------------------------------------------------------------------------------
【2026-10-01】后台登录改为「弹窗输入验证码」（已被上一节取代）
--------------------------------------------------------------------------------
■ 改动：后台登录安全验证由「内嵌滑动拼图」改为「点登录后弹出验证码弹窗」
  · 登录页不再显示内嵌的滑块验证码，界面更干净
  · 填完账号密码点「登 录」→ 弹出验证码弹窗 → 输入图中 4 位字符 → 确定并登录
  · 不区分大小写、可点图片或「换一张」刷新、支持回车提交、Esc / 点遮罩关闭
  · 验证码 5 分钟有效、一次性使用；验证通过后本页表单直接提交，无需二次点击
  · 安全性：验证结果仍由服务端 session 判定（captcha.php 生成 → verify_code 校验 →
    写入 $_SESSION['captcha_passed']，admin.php 登录时一次性消费），前端无法绕过
  · 服务器未启用 GD 扩展或后台关闭该开关时，自动跳过验证码，不会把登录卡死
  · 顺带说明：原来「拖不动」的原因是样式里 .captcha-canvas img{pointer-events:none}
    把滑块图片也一起禁用了事件（现已不再使用滑动交互）
  · 涉及文件：front/tpl/login.php、captcha.php、admin.php
  · 说明：滑动拼图接口（captcha.php?action=get / verify）仍然保留，需要时可继续使用

■ 优化：前台「鲸发卡风格」手机端界面
  · 顶部导航：手机端导航条改为 56px，Logo 旁店名过长自动省略号（避免和右侧按钮挤在一起）；
    「订单查询」药丸按钮缩小到 34px 高（原来是 40px），间距收紧
  · 商家信息区：联系客服 / 分享 / 「···」三个按钮统一为 34px 高的圆角按钮，
    「···」不再是文字省略号，改为真正的图标按钮 + 下拉菜单（订单查询 / 常见问题 / 分享本页），
    手机上点击不再歪歪扭扭
  · 商品列表：手机端由「一排一个」改为「一排两个」（去掉 ≤400px 时降为单列的规则），
    卡片内边距 / 封面色块 / 价格与标签排布同步调整（价格与标签上下排列，避免窄卡片换行错位）
  · 商品详情页顶部导航同步做手机端紧凑处理
  · 涉及文件：themes/jing/index.php、themes/jing/goods.php

--------------------------------------------------------------------------------
【2026-10-01】新增前台主题「鲸发卡风格」
--------------------------------------------------------------------------------
■ 新增：themes/jing/（第 7 套前台主题，不影响现有 6 套）
  · 按鲸发卡首页布局复刻：顶部导航（Logo + 菜单 + 蓝色药丸「订单查询」按钮）
  · 商家信息区：圆形头像 + 店名 + 金色认证徽章 + 商品/成交数 + 联系客服/分享按钮 + 店铺公告
  · 主面板：分类 Tab（带图标与数量）+ 右侧搜索框 + 分类卡片网格
    （当前分类为蓝色渐变卡并带勾选水印，其余为浅灰卡）
  · 商品卡片网格：桌面 4 列 / 平板 3 列 / 手机 2 列；卡片含封面、名称、价格、
    标签（新品热销等 + 库存/缺货状态药丸）
  · 「加载更多」分页显示（默认每页 8 个），分类切换与搜索为前端即时筛选，无刷新
  · 配色：主色 #2563eb 蓝、页面底色 #f5f7fa、卡片白底细边框 + 轻阴影
  · 涉及文件：themes/jing/theme.json、index.php、goods.php、result.php、query.php（全部新增）
  · 使用方法：后台 → 前台模板 → 找到「鲸发卡风格」→ 使用此模板

■ 说明：商品详情 / 支付结果 / 订单查询三页沿用本系统的功能逻辑，
  配色与首页统一。同时保留了上游对接、实物商品、自定义填写项等注入所需的
  页面钩子（selectSpec / submitOrder / doSubmit / #email / #payChannels /
  #cardContent / .copy-btn / .section），因此这些功能在该主题下同样生效。

--------------------------------------------------------------------------------
【2026-10-01】多上游系统对接 + 自定义下单填写项 + 更新日志
--------------------------------------------------------------------------------
■ 新增：上游对接支持多系统
  · 「上游站点」新增「对接系统」选择器：彩虹代刷 / 异次元发卡 / 独角数卡 / 通用 HTTP
  · 选择系统后填写：站点名称、接口地址、账号、密码（或 AppID / AppKey）
  · 异次元 / 独角数卡 / 通用 支持「接口路径覆盖」——如对接不通，可在页面上手动改
    商品列表 / 下单 / 订单查询 三个接口路径，无需改代码
  · 站点列表新增「系统」列，一眼看出每个上游是什么系统
  · 涉及文件：core/upstream.php、admin.php
  · 数据库：zb_upstream 新增 sys_type / api_goods / api_order / api_query
  · ⚠ 说明：彩虹代刷接口已用真实站点验证；异次元 / 独角数卡 是按公开接口形式
    实现的，务必先用测试站实测，路径不对就在页面上覆盖。

■ 新增：自己添加的商品可自定义「买家下单填写项」
  · 商品编辑里新增配置：填写项标签（如「下单账号 / QQ号」）+ 内容类型
    （邮箱 / 任意文本 / 手机号）+ 是否必填
  · 不再强制买家填邮箱；对接上游的商品仍跟随上游配置
  · 涉及文件：core/physical.php、index.php、admin.php
  · 数据库：zb_goods 新增 input_label / input_type / input_required

■ 新增：CHANGELOG.txt（本文件）

--------------------------------------------------------------------------------
【2026-10-01】掉单自动补发 + 多字段订单查询 + 后台 UI 美化
--------------------------------------------------------------------------------
■ 新增：掉单自动补发（重要）
  · 主动调用易支付查单接口，扫描「下单超过 3 分钟仍未支付」的订单
  · 若平台显示已支付 → 自动补发货 + 发邮件（解决回调丢包导致的「付了钱没收到货」）
  · 已接入 core/cron.php（每 5 分钟）；后台订单列表新增「检测掉单」按钮可手动触发
  · 涉及文件：core/lib.php、core/cron.php、admin.php

■ 新增：订单查询支持 QQ号 / 手机号
  · 一个输入框支持：订单号 / 邮箱 / 手机号 / QQ号（纯数字按邮箱前缀匹配）
  · 命中多条时显示订单列表供选择；没命中给出明确提示
  · 涉及文件：pay.php

■ 优化：后台界面全面美化（assets/admin.css）
  · 设置项复选框改为 iOS 风格滑动开关（纯 CSS，无需改 PHP）
  · 按钮渐变 + 悬停上浮 + 按下回弹；输入框圆角与聚焦光环
  · 表格圆角容器 + 隔行底色 + 悬停高亮；卡片多层阴影
  · 弹窗毛玻璃背景 + 弹入动画；分页器改为 class 样式
  · 静态资源版本号改为按文件修改时间自动刷新（改完不用手动改 v=）

--------------------------------------------------------------------------------
【2026-10-01】性能优化 + 安全加固 + 代码整理
--------------------------------------------------------------------------------
■ 优化：首页 N+1 查询
  · 原本每件商品查一次规格（10 件商品 = 11 次查询），改为 1 次 IN 查询 + PHP 分组
  · 涉及文件：index.php

■ 优化：后台 CSS / JS 抽离为独立静态文件（可被浏览器缓存）
  · 新增 assets/admin.css（约 150 行样式）、assets/admin.js（公共脚本）
  · admin.php 体积减少约 180 行
  · ⚠ 上传时必须带上整个 assets/ 目录，否则后台会变成无样式页面

■ 修复：日志文件无限增长
  · notify_debug.log / epay_submit.log 原本每次回调都追加且永不清理，会撑爆磁盘
  · 新增 safe_log_append()，单个日志超过 2MB 自动只保留最后 512KB
  · 涉及文件：core/lib.php、notify.php、pay.php

■ 修复：check.php 可被公开访问，会输出全部商品与分类数据
  · 已加后台登录校验，未登录返回 403

--------------------------------------------------------------------------------
【2026-09-30】实物商品功能（单一规格 + 固定运费/满额包邮）
--------------------------------------------------------------------------------
■ 新增：实物商品完整闭环
  · 商品类型可选「虚拟卡密 / 实物商品」；实物商品支持固定运费 + 满额包邮
  · 实物商品的规格库存改为手动维护（虚拟商品仍由卡密数量自动决定）
  · 前台采集收货信息：收货人 / 手机号 / 省市区 / 详细地址（自动隐藏邮箱框）
  · 支付后进入「待发货」并扣减库存，并给买家发下单确认邮件
  · 后台订单支持「发货」（填快递公司 + 单号）、「发货状态」筛选、售后处理
  · 订单页显示收货信息、物流单号（可复制）、确认收货、评价晒单、申请售后
  · 新增 zb_comments 评价表
  · 涉及文件：core/physical.php（新增）、order_action.php（新增）、
    sql/physical.sql（新增）、index.php、result.php、notify.php、core/lib.php、admin.php

--------------------------------------------------------------------------------
【2026-09-30】彩虹代刷上游对接
--------------------------------------------------------------------------------
■ 新增：上游对接模块（彩虹自助下单系统）
  · 支持自定义接口地址 + 分站账号密码，可添加多个上游站点
  · 支持从上游拉取商品、绑定到本地规格、自动读取上游下单参数
  · 「添加商品」页可直接「从上游获取商品」，自动填名称、价格、采集项
  · 支付成功后自动向上游下单：卡密类直接返回卡密发货，代刷类存单号等同步
  · 定时任务同步上游订单状态；后台订单列表显示上游状态并可手动同步/补发
  · 上游库存定时同步到本地
  · 对接了上游的商品，前台自动隐藏邮箱框（跟随上游采集配置）
  · 涉及文件：core/upstream.php（新增）、sql/upstream.sql（新增）、
    admin.php、index.php、result.php、notify.php、core/lib.php、core/cron.php

--------------------------------------------------------------------------------
【2026-09-30】基础功能增强（第一批）
--------------------------------------------------------------------------------
■ 图片自动压缩 + 转 WebP（upload.php）
■ 后台主题大图实时预览（采用 iframe 渲染，index.php 支持 ?preview_theme=xxx）
■ 多场景邮件 / 短信模板（邮件 3 场景、短信 2 场景，均支持变量占位符）
■ 批量操作：商品（批量上架/下架/删除）、订单（批量删除）、卡密（批量删除）
■ 自建滑动验证码（captcha.php，无需第三方），接入后台登录
■ 订单高级筛选：关键词 / 支付通道 / 日期范围
■ 拖拽排序：商品 / 分类 / 轮播图 / 广告位（HTML5 原生拖拽）
■ 真分页：商品 / 订单 / 卡密 / 操作日志（替换原来的 LIMIT 硬截断）
■ 卡密导入去重（批次内 + 库内双重去重，返回跳过条数）
■ CSRF 防护：后台全部 POST + 上传接口，统一 token 校验
■ 数据库索引优化（后台一键建索引 + sql/indexes.sql）
■ Redis 缓存 settings（config.php 可开关，连接失败自动降级）

================================================================================
  文件结构说明
================================================================================
  /                     前台入口与接口（必须保持在根目录）
    index.php           前台入口（首页 / 商品详情 / 创建订单）
    admin.php           后台管理（单文件，含全部页面）
    pay.php             发起支付 + 订单查询
    notify.php          支付异步回调
    result.php          订单结果 / 详情页
    order_action.php    前台订单操作（确认收货 / 评价 / 售后）
    upload.php          图片上传接口
    captcha.php         滑动验证码
    check.php           调试脚本（已加登录校验）
    dashboard.php       数据大屏（需登录）
    config.php          数据库 / Redis 配置
  /core                 核心库
    lib.php             公共函数（数据库 / 设置 / 邮件 / 短信 / 支付 / 缓存 / CSRF）
    upstream.php        上游对接（彩虹 / 异次元 / 独角数卡 / 通用）
    physical.php        实物商品 + 自定义下单填写项
    cron.php            定时任务（关超时单 / 掉单补发 / 同步上游）
  /assets               后台静态资源（CSS / JS）
  /sql                  数据库升级脚本
  /themes               前台主题（6 套）
  /uploads              上传的图片
  /front/tpl            后台登录页等模板

================================================================================
