setName('fc:install') ->addOption('reset', null, Option::VALUE_NONE, '强制重置种子数据') ->setDescription('同城分类信息业务表安装 + 种子数据'); } protected function execute(Input $input, Output $output) { $prefix = \think\Config::get('database.prefix'); $reset = $input->hasOption('reset') && $input->getOption('reset'); $output->writeln('==> 建表(前缀 ' . $prefix . ')'); $tables = $this->tables(); foreach ($tables as $name => $sql) { // 占位符 #P# 位于反引号内(形如 `#P#config`),故按裸串替换 Db::execute(str_replace('#P#', $prefix . 'fc_', $sql)); $output->writeln(' - ' . $prefix . 'fc_' . $name); } $seeded = Db::name('fc_config')->count(); if ($reset) { $this->truncateAll(); $seeded = 0; } if (!$seeded) { $output->writeln('==> 注入种子数据'); $this->seed($output); } else { $output->writeln('==> 已有数据,跳过种子(如需重置请加 --reset)'); } $output->writeln('==> 轮播底色与春绿主题对齐'); $this->upgradeBanners($output); $output->writeln('==> 发布模式与发布套餐升级'); $this->upgradePublish($output); $output->writeln('==> 系统配置分组整理'); $this->upgradeConfigGroups($output); $output->writeln('==> LBS / 店铺装修 / 支付 / 优惠券 / 举报升级(v1.7.0)'); $this->upgradeV170($output); $output->writeln('==> 提现商家转账字段升级(v1.2.1)'); $this->upgradeV121($output); $output->writeln('==> 优惠券抵扣 / 佣金结算字段升级(v1.2.2)'); $this->upgradeV122($output); $output->writeln('==> 服务区域平铺升级(省 / 市 / 区县)'); $this->upgradeArea($output); $output->writeln('==> 初始化后台菜单'); $this->menu($output); $output->writeln('==> 完成'); } /** * 轮播底色升级(幂等,可重复执行) * * 主题迭代史:v1.0.x 暖橙纯色(#FF8A5B 一族)→ v1.2 生态绿(#0e7c66 一族)→ v1.3 春绿(本次)。 * 线上库若还是历史值,这里按「标题」精确匹配把内置 3 条轮播的底色覆盖为春绿渐变; * 只动 color 字段,不新增/删除记录,也不影响发布、订单等业务数据。 * 判定规则:color 为空/非渐变(旧纯色),或命中 LEGACY_COLOR_KEYS(上一版主题色)→ 覆盖; * 自定义过标题、或已是春绿渐变(#00e070 一族)的轮播不会被改动(幂等)。 */ private function upgradeBanners(Output $output) { // ① 先确保 color 列容得下 CSS 渐变字符串:旧库是 varchar(16),只够存 #RRGGBB, // 直接写 linear-gradient(...) 会被截断(严格模式还会报 1406 Data too long)。 $table = \think\Config::get('database.prefix') . 'fc_banner'; $cols = Db::query("SHOW COLUMNS FROM `{$table}` LIKE 'color'"); $row = isset($cols[0]) ? $cols[0] : []; $type = isset($row['Type']) ? (string)$row['Type'] : (isset($row['type']) ? (string)$row['type'] : ''); if (preg_match('/^varchar\((\d+)\)$/i', $type, $m) && (int)$m[1] < 255) { Db::execute("ALTER TABLE `{$table}` MODIFY COLUMN `color` varchar(255) NOT NULL DEFAULT ''" . " COMMENT '底色:CSS 渐变 linear-gradient(...),留空则用内置春绿渐变'"); $output->writeln(' - 轮播底色列 color 已由 ' . $type . ' 扩容为 varchar(255)(存 CSS 渐变)'); } // ② 历史底色(旧纯色 / 上一版生态绿)→ 春绿渐变 $upgraded = 0; foreach (self::BANNERS as $b) { $row = Db::name('fc_banner')->where('title', $b[0])->find(); if (!$row) { continue; } $color = strtolower((string)$row['color']); $isGradient = strpos($color, 'gradient(') !== false; $isLegacy = false; foreach (self::LEGACY_COLOR_KEYS as $k) { if (strpos($color, $k) !== false) { $isLegacy = true; break; } } if (!$isGradient || $isLegacy) { Db::name('fc_banner')->where('id', $row['id']) ->update(['color' => $b[2], 'updatetime' => time()]); $upgraded++; } } $output->writeln($upgraded ? ' - 已把 ' . $upgraded . ' 条内置轮播底色升级为春绿渐变(105deg 亮绿→深绿)' : ' - 轮播底色已是春绿渐变或非内置轮播,无需处理'); } /** * 发布模式与发布套餐升级(幂等,可重复执行) * * 背景:v1.0.7 新增「免费发布 / 付费发布」开关与付费发布定价 * · 单条价格 post.singlePrice * · 免费发布条数 post.freeQuota / post.freeQuotaCycle(v1.0.10:达到条数后按单条价格计费) * 套餐价格与含发布条数唯一真源 = fc_plan(后台「运营推广 → 会员套餐」维护), * 不再使用 post.monthPrice / post.yearPrice 等配置键(历史键在本步骤清理)。 * 老库缺列/缺配置时补齐,**不覆盖运营已改过的值**: * ① fc_user_ext 补 post_quota_left / post_quota_expire(剩余发布条数 + 有效期) * ② fc_plan 补 post_quota(套餐含发布条数,月/年卡赠送额度) * ③ fc_config 补 post.* 四项(已存在则跳过) * ④ 清理历史遗留的套餐定价配置键(post.month* / post.year* / fee.*) */ private function upgradePublish(Output $output) { $prefix = \think\Config::get('database.prefix'); $changed = 0; // ① 用户扩展表:发布额度 $extTable = $prefix . 'fc_user_ext'; if (!$this->hasColumn($extTable, 'post_quota_left')) { Db::execute("ALTER TABLE `{$extTable}` ADD COLUMN `post_quota_left` int(10) NOT NULL DEFAULT 0" . " COMMENT '剩余发布条数(月/年套餐赠送)' AFTER `free_view_used`"); $output->writeln(' - ' . $extTable . ' 新增列 post_quota_left'); $changed++; } if (!$this->hasColumn($extTable, 'post_quota_expire')) { Db::execute("ALTER TABLE `{$extTable}` ADD COLUMN `post_quota_expire` int(10) NULL" . " COMMENT '发布额度有效期(=会员到期时间)' AFTER `post_quota_left`"); $output->writeln(' - ' . $extTable . ' 新增列 post_quota_expire'); $changed++; } // ② 会员套餐:含发布条数、每日免费看条数、超额发布折扣(直接挂在每个套餐上) $planTable = $prefix . 'fc_plan'; if (!$this->hasColumn($planTable, 'post_quota')) { Db::execute("ALTER TABLE `{$planTable}` ADD COLUMN `post_quota` int(10) NOT NULL DEFAULT 0" . " COMMENT '含发布条数(套餐赠送额度)' AFTER `days`"); $output->writeln(' - ' . $planTable . ' 新增列 post_quota'); $changed++; } if (!$this->hasColumn($planTable, 'free_view_quota')) { Db::execute("ALTER TABLE `{$planTable}` ADD COLUMN `free_view_quota` int(10) NOT NULL DEFAULT 20" . " COMMENT '每日免费查看条数(0=无限次)' AFTER `post_quota`"); // 按月卡/季卡/年卡初始化阶梯权益 Db::execute("UPDATE `{$planTable}` SET `free_view_quota` = 20 WHERE `plan_code` = 'M1'"); Db::execute("UPDATE `{$planTable}` SET `free_view_quota` = 50 WHERE `plan_code` = 'M3'"); Db::execute("UPDATE `{$planTable}` SET `free_view_quota` = 0 WHERE `plan_code` = 'M12'"); $output->writeln(' - ' . $planTable . ' 新增列 free_view_quota(0=无限次免费看)'); $changed++; } if (!$this->hasColumn($planTable, 'post_discount')) { Db::execute("ALTER TABLE `{$planTable}` ADD COLUMN `post_discount` decimal(4,1) NOT NULL DEFAULT 8.0" . " COMMENT '超额单条发布折扣(0.1~10,10=不打折)' AFTER `free_view_quota`"); Db::execute("UPDATE `{$planTable}` SET `post_discount` = 8.5 WHERE `plan_code` = 'M1'"); Db::execute("UPDATE `{$planTable}` SET `post_discount` = 8.0 WHERE `plan_code` = 'M3'"); Db::execute("UPDATE `{$planTable}` SET `post_discount` = 7.0 WHERE `plan_code` = 'M12'"); $output->writeln(' - ' . $planTable . ' 新增列 post_discount'); $changed++; } if (!$this->hasColumn($extTable, 'free_view_quota')) { Db::execute("ALTER TABLE `{$extTable}` ADD COLUMN `free_view_quota` int(10) NOT NULL DEFAULT 20" . " COMMENT '当前会员每日免费查看条数(0=无限)' AFTER `free_view_used`"); $output->writeln(' - ' . $extTable . ' 新增列 free_view_quota'); $changed++; } if (!$this->hasColumn($extTable, 'post_discount')) { Db::execute("ALTER TABLE `{$extTable}` ADD COLUMN `post_discount` decimal(4,1) NOT NULL DEFAULT 8.0" . " COMMENT '当前会员超额发布折扣(0.1~10,10=不打折)' AFTER `post_quota_expire`"); $output->writeln(' - ' . $extTable . ' 新增列 post_discount'); $changed++; } // ③ 配置项(只补缺失的,不覆盖运营已改的值) $defaults = [ ['post.mode', 'free', 'post', '发布模式:free 免费发布 / paid 付费发布'], ['post.singlePrice', '5', 'post', '单条发布价格(元)=付费发布标准价'], ['post.freeQuota', '0', 'post', '免费发布条数(0 = 不限制/免费到底;达到条数后按单条发布价格计费)'], ['post.freeQuotaCycle', 'day', 'post', '免费条数统计周期:day 每日 / month 每月 / total 累计'], ]; $now = time(); $added = []; foreach ($defaults as $d) { if (Db::name('fc_config')->where('key', $d[0])->find()) { continue; } Db::name('fc_config')->insert([ 'key' => $d[0], 'value' => $d[1], 'group_key' => $d[2], 'remark' => $d[3], 'createtime' => $now, 'updatetime' => $now, ]); $added[] = $d[0]; } if ($added) { $output->writeln(' - 新增配置 ' . count($added) . ' 项:' . implode(' / ', $added)); $changed++; } // ④ 清理历史遗留配置键:套餐定价已统一到 fc_plan,发布免费条数只认 post.freeQuota $deprecated = [ 'post.monthPrice', 'post.monthQuota', 'post.yearPrice', 'post.yearQuota', 'fee.singlePost', 'fee.freePostPerDay', 'fee.memberMonth', 'fee.memberQuarter', 'fee.memberYear', ]; $removedKeys = Db::name('fc_config')->where('key', 'in', $deprecated)->delete(); if ($removedKeys) { $output->writeln(' - 清理废弃配置 ' . $removedKeys . ' 项(套餐定价真源已统一为 fc_plan)'); $changed++; } $output->writeln($changed ? ' - 完成(发布模式 ' . $this->cfgValue('post.mode', 'free') . ')' : ' - 发布模式与套餐配置已是最新,无需处理'); } /** * 系统配置分组整理(幂等) * * 背景:fa_config 的 configgroup 字典保留了 FastAdmin 演示分组 user(会员配置)与 * example(示例分组),两者没有任何配置项,却会在「系统配置」页渲染出空白 tab, * 且 user 与业务分组「会员配置」重名易混淆;这里从字典中移除。 */ private function upgradeConfigGroups(Output $output) { $row = Db::name('config')->where('name', 'configgroup')->find(); if (!$row) { $output->writeln(' - 未找到 configgroup 字典,跳过'); return; } $decoded = json_decode((string)$row['value'], true); if (!is_array($decoded)) { $output->writeln(' - configgroup 字典格式异常,跳过'); return; } $target = ['basic', 'email', 'dictionary']; $kept = []; foreach ($decoded as $k => $v) { if (in_array($k, $target, true) || strpos((string)$k, 'fc_') === 0) { $kept[$k] = $v; } } if ($kept === $decoded) { $output->writeln(' - 系统配置分组已是最新,无需处理'); return; } Db::name('config')->where('name', 'configgroup')->update([ 'value' => json_encode($kept, JSON_UNESCAPED_UNICODE), ]); $removed = array_diff(array_keys($decoded), array_keys($kept)); $output->writeln(' - 已移除空分组:' . implode(' / ', $removed)); } /** * v1.7.0 升级:LBS 经纬度 / 店铺装修字段 / 收藏关注 / 支付与举报配置(幂等,可重复执行) * * 背景:本轮新增 ① 首页「按距离排序」与详情页真实地图 → fc_post 存经纬度; * ② 店铺装修与推荐位 → fc_merchant 增 banner / notice / recommend; * ③ 微信支付 JSAPI v2 与优惠券、举报原因字典 → 新增 fc_config 配置项。 * 规则:列「不存在才 ADD COLUMN」、配置「已存在则跳过」,**绝不覆盖运营已改过的值**。 * 新增业务表(fc_favorite / fc_follow / fc_coupon / fc_user_coupon)在上方的 tables() 中 * 以 CREATE TABLE IF NOT EXISTS 建立。 */ private function upgradeV170(Output $output) { $prefix = \think\Config::get('database.prefix'); $changed = 0; // ① fc_post:发布坐标(gcj02),供首页按距离排序与详情页地图使用 $postTable = $prefix . 'fc_post'; $postCols = [ ['lat', 'decimal(10,6) NULL COMMENT \'纬度 gcj02(-90~90),未定位为空\'', '`address`'], ['lng', 'decimal(10,6) NULL COMMENT \'经度 gcj02(-180~180),未定位为空\'', '`lat`'], ]; foreach ($postCols as $col) { if ($this->hasColumn($postTable, $col[0])) { continue; } Db::execute("ALTER TABLE `{$postTable}` ADD COLUMN `{$col[0]}` {$col[1]} AFTER {$col[2]}"); $output->writeln(' - ' . $postTable . ' 新增列 ' . $col[0]); $changed++; } // ② fc_merchant:店铺头图 / 店铺公告 / 推荐位 / 实体店地址与经纬度 $merchantTable = $prefix . 'fc_merchant'; $merchantCols = [ ['address', "varchar(255) NOT NULL DEFAULT '' COMMENT '实体店详细地址'", '`contact`'], ['lat', 'decimal(10,6) NULL COMMENT \'纬度 gcj02\'', '`address`'], ['lng', 'decimal(10,6) NULL COMMENT \'经度 gcj02\'', '`lat`'], ['banner', "varchar(255) NOT NULL DEFAULT '' COMMENT '店铺头图'", '`logo`'], ['notice', "varchar(255) NOT NULL DEFAULT '' COMMENT '店铺公告'", '`banner`'], ['recommend', "tinyint(1) NOT NULL DEFAULT 0 COMMENT '推荐位:1 推荐 0 普通'", '`notice`'], ]; foreach ($merchantCols as $col) { if ($this->hasColumn($merchantTable, $col[0])) { continue; } Db::execute("ALTER TABLE `{$merchantTable}` ADD COLUMN `{$col[0]}` {$col[1]} AFTER {$col[2]}"); $output->writeln(' - ' . $merchantTable . ' 新增列 ' . $col[0]); $changed++; } // ③ 配置项(只补缺失的,不覆盖运营已改的值) // 注意:**不再写入支付配置**(历史键 wx.mchId / wx.apiKey / wx.payNotifyUrl 已废弃)。 // 微信支付参数由 epay 插件维护:后台「插件管理 → 微信支付宝整合插件 → 配置」, // 持久化在 addons/epay/config.php(wechat.miniapp_id / wechat.mch_id / wechat.key)。 $defaults = [ ['coupon.enabled', '1', 'coupon', '优惠券功能开关:1 开启 / 0 关闭'], ['report.reasons', json_encode(['虚假信息', '违法违禁', '垃圾广告', '重复发布', '其他'], JSON_UNESCAPED_UNICODE), 'report', '举报原因字典(JSON 数组)'], ]; $now = time(); $added = []; foreach ($defaults as $d) { if (Db::name('fc_config')->where('key', $d[0])->find()) { continue; } Db::name('fc_config')->insert([ 'key' => $d[0], 'value' => $d[1], 'group_key' => $d[2], 'remark' => $d[3], 'createtime' => $now, 'updatetime' => $now, ]); $added[] = $d[0]; $changed++; } if ($added) { $output->writeln(' - 新增配置 ' . count($added) . ' 项:' . implode(' / ', $added)); } // ④ 插件钩子缓存:`application/extra/addons.php` 的 hooks 会被 File 缓存(Cache::get('hooks')), // 若该缓存是在「插件目录还不存在」时生成的,插件启用后也不会被加载 —— // 典型症状:微信支付报 `Class 'Yansongda\Pay\Pay' not found`。 // 这里主动清一次,保证「插件装好/更新后即刻可用」。 try { \think\Cache::rm('hooks'); $output->writeln(' - 已清理插件钩子缓存 hooks(epay 插件即时生效)'); } catch (\Exception $e) { // 清理失败不影响主流程:下一次请求会按 extra/addons.php 重建 } $output->writeln($changed ? ' - 完成(新增结构 ' . $changed . ' 处)' : ' - LBS / 店铺装修 / 支付 / 优惠券 / 举报配置已是最新,无需处理'); } /** * v1.2.1:提现「商家转账」字段 * - fa_fc_withdraw:out_bill_no / transfer_status / transfer_id / package_info / fail_reason / transfer_time * - fa_fc_user_ext:realname(收款人真实姓名) * 幂等:已存在则跳过 */ private function upgradeV121(Output $output) { $prefix = \think\Config::get('database.prefix'); $w = $prefix . 'fc_withdraw'; $e = $prefix . 'fc_user_ext'; $cols = [ 'out_bill_no' => "ALTER TABLE `{$w}` ADD COLUMN `out_bill_no` varchar(32) NOT NULL DEFAULT '' COMMENT '微信商家转账商户单号'", 'transfer_status' => "ALTER TABLE `{$w}` ADD COLUMN `transfer_status` varchar(16) NOT NULL DEFAULT '' COMMENT 'WAIT_USER_CONFIRM/PROCESSING/SUCCESS/FAIL/CANCELLED'", 'transfer_id' => "ALTER TABLE `{$w}` ADD COLUMN `transfer_id` varchar(64) NOT NULL DEFAULT '' COMMENT '微信转账单号'", 'package_info' => "ALTER TABLE `{$w}` ADD COLUMN `package_info` varchar(512) NOT NULL DEFAULT '' COMMENT '拉起确认收款页的package'", 'fail_reason' => "ALTER TABLE `{$w}` ADD COLUMN `fail_reason` varchar(255) NOT NULL DEFAULT ''", 'transfer_time' => "ALTER TABLE `{$w}` ADD COLUMN `transfer_time` int(10) DEFAULT NULL", ]; $changed = 0; foreach ($cols as $col => $sql) { if ($this->hasColumn($w, $col)) { continue; } Db::execute($sql); $changed++; } try { Db::execute("ALTER TABLE `{$w}` ADD KEY `out_bill_no` (`out_bill_no`)"); $changed++; } catch (\Exception $e2) { // 索引已存在则忽略 } if (!$this->hasColumn($e, 'realname')) { Db::execute("ALTER TABLE `{$e}` ADD COLUMN `realname` varchar(32) NOT NULL DEFAULT '' COMMENT '收款人真实姓名(提现用)'"); $changed++; } // 退款单号(微信退款结果回调按此匹配订单) $o = $prefix . 'fc_order'; if (!$this->hasColumn($o, 'out_refund_no')) { Db::execute("ALTER TABLE `{$o}` ADD COLUMN `out_refund_no` varchar(64) NOT NULL DEFAULT '' COMMENT '微信退款单号(商户侧)'"); $changed++; } $output->writeln($changed ? ' - 完成(新增结构 ' . $changed . ' 处)' : ' - 提现转账字段已是最新,无需处理'); } /** * 服务区域平铺升级(省 / 市 / 区县三列,不再使用层级) * * 目标结构:fc_area(id, province, city, district, lat, lng, sort, enabled) * 每条记录 = 一个服务区域(区县);省 / 市只是该行的归属字段,供前端级联。 * 历史层级数据会自动压平:每个区县保留原 ID,省 / 市节点删除。 * 幂等:可重复执行。 */ private function upgradeArea(Output $output) { $prefix = \think\Config::get('database.prefix'); $areaTable = $prefix . 'fc_area'; $postTable = $prefix . 'fc_post'; $draftTable = $prefix . 'fc_draft'; $now = time(); $changed = 0; // ① fc_area 平铺字段 $areaCols = [ 'province' => "ALTER TABLE `{$areaTable}` ADD COLUMN `province` varchar(32) NOT NULL DEFAULT '' COMMENT '省份' AFTER `id`", 'city' => "ALTER TABLE `{$areaTable}` ADD COLUMN `city` varchar(32) NOT NULL DEFAULT '' COMMENT '城市' AFTER `province`", 'district' => "ALTER TABLE `{$areaTable}` ADD COLUMN `district` varchar(32) NOT NULL DEFAULT '' COMMENT '区县(最终服务区域)' AFTER `city`", 'lat' => "ALTER TABLE `{$areaTable}` ADD COLUMN `lat` decimal(10,6) DEFAULT NULL COMMENT '中心纬度(gcj02)' AFTER `district`", 'lng' => "ALTER TABLE `{$areaTable}` ADD COLUMN `lng` decimal(10,6) DEFAULT NULL COMMENT '中心经度(gcj02)' AFTER `lat`", ]; foreach ($areaCols as $col => $sql) { if ($this->hasColumn($areaTable, $col)) { continue; } Db::execute($sql); $changed++; } if (!$this->hasIndex($areaTable, 'province_city')) { Db::execute("ALTER TABLE `{$areaTable}` ADD KEY `province_city` (`province`,`city`)"); } // ② 旧层级数据压平:区县保留原 ID,省 / 市节点删除 $hasLevel = $this->hasColumn($areaTable, 'level'); $hasParent = $this->hasColumn($areaTable, 'parent_id'); $hasName = $this->hasColumn($areaTable, 'name'); if ($hasLevel || $hasParent || $hasName) { $rows = Db::name('fc_area')->select(); $byId = []; foreach ($rows as $row) { $byId[(int)$row['id']] = $row; } $flat = 0; foreach ($rows as $row) { $level = $hasLevel ? (int)$row['level'] : 0; // 只保留区县(level=3);真实省份/城市节点稍后删除 if ($hasLevel && $level > 0 && $level < 3) { continue; } $provinceName = ''; $cityName = ''; $districtName = $hasName ? (string)$row['name'] : (string)$row['district']; if ($hasParent && (int)$row['parent_id'] > 0 && isset($byId[(int)$row['parent_id']])) { $cityRow = $byId[(int)$row['parent_id']]; $cityName = $hasName ? (string)$cityRow['name'] : (string)$cityRow['city']; if ((int)$cityRow['parent_id'] > 0 && isset($byId[(int)$cityRow['parent_id']])) { $provinceRow = $byId[(int)$cityRow['parent_id']]; $provinceName = $hasName ? (string)$provinceRow['name'] : (string)$provinceRow['province']; } } Db::name('fc_area')->where('id', $row['id'])->update([ 'province' => $provinceName, 'city' => $cityName, 'district' => $districtName, 'updatetime' => $now, ]); $flat++; } // 删除省 / 市节点(它们不是服务区域) if ($hasLevel) { $deleted = Db::name('fc_area')->where('level', 'in', [1, 2])->delete(); } // 删除旧字段,彻底去掉层级概念 foreach (['parent_id', 'level', 'name'] as $col) { if ($this->hasColumn($areaTable, $col)) { Db::execute("ALTER TABLE `{$areaTable}` DROP COLUMN `{$col}`"); } } $output->writeln(' - 已将 ' . $flat . ' 条区域压平为「省 / 市 / 区县」三列'); } // ③ 已知区县中心坐标(仅补空值) $coords = [ '云岩区' => [26.605, 106.710], '南明区' => [26.568, 106.716], '观山湖区' => [26.625, 106.625], '花溪区' => [26.430, 106.670], '白云区' => [26.678, 106.623], ]; foreach ($coords as $name => $ll) { Db::name('fc_area')->where('district', $name)->whereNull('lat')->update([ 'lat' => $ll[0], 'lng' => $ll[1], 'updatetime' => $now, ]); } // ④ fc_post:保留 district_id 作为服务区域ID,移除省/市 ID 列 if (!$this->hasColumn($postTable, 'district_id')) { Db::execute("ALTER TABLE `{$postTable}` ADD COLUMN `district_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '服务区域ID(fc_area.id)' AFTER `area`"); $changed++; } foreach (['province_id', 'city_id'] as $col) { if ($this->hasColumn($postTable, $col)) { Db::execute("ALTER TABLE `{$postTable}` DROP COLUMN `{$col}`"); } } if (!$this->hasIndex($postTable, 'district_id')) { Db::execute("ALTER TABLE `{$postTable}` ADD KEY `district_id` (`district_id`)"); } // ⑤ fc_draft:同样只保留 area + district_id foreach (['area', 'district_id'] as $col) { if ($this->hasColumn($draftTable, $col)) { continue; } $sql = $col === 'area' ? "ALTER TABLE `{$draftTable}` ADD COLUMN `area` varchar(32) NOT NULL DEFAULT '' COMMENT '区县名称' AFTER `address`" : "ALTER TABLE `{$draftTable}` ADD COLUMN `district_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '服务区域ID(fc_area.id)' AFTER `area`"; Db::execute($sql); $changed++; } foreach (['province_id', 'city_id'] as $col) { if ($this->hasColumn($draftTable, $col)) { Db::execute("ALTER TABLE `{$draftTable}` DROP COLUMN `{$col}`"); } } // ⑥ 按区县名称回填 district_id(信息 / 草稿) $byName = []; foreach (Db::name('fc_area')->select() as $area) { $byName[(string)$area['district']] = (int)$area['id']; } $backfilled = 0; foreach ([['fc_post', 'updatetime'], ['fc_draft', null]] as $t) { $rows = Db::name($t[0])->where('district_id', 0)->where('area', '<>', '')->field('id,area')->select(); foreach ($rows as $row) { if (!isset($byName[$row['area']])) { continue; } $data = ['district_id' => $byName[$row['area']]]; if ($t[1]) { $data[$t[1]] = $now; } Db::name($t[0])->where('id', $row['id'])->update($data); $backfilled++; } } if ($backfilled) { $output->writeln(' - 已回填 ' . $backfilled . ' 条信息/草稿的服务区域ID'); } $output->writeln($changed || $backfilled ? ' - 完成(结构变更 ' . $changed . ' 处,回填 ' . $backfilled . ' 条)' : ' - 服务区域已是平铺结构,无需处理'); } /** 表是否存在某列 */ private function hasColumn($table, $column) { $cols = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '" . addslashes($column) . "'"); return !empty($cols); } /** * v1.2.2:优惠券抵扣 + 佣金结算周期 * - fa_fc_order:origin_amount / coupon_amount / coupon_id * - fa_fc_commission:settle_due(预计结算时间) */ private function upgradeV122(Output $output) { $prefix = \think\Config::get('database.prefix'); $o = $prefix . 'fc_order'; $c = $prefix . 'fc_commission'; $changed = 0; $orderCols = [ 'origin_amount' => "ALTER TABLE `{$o}` ADD COLUMN `origin_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠前金额'", 'coupon_amount' => "ALTER TABLE `{$o}` ADD COLUMN `coupon_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠券抵扣金额'", 'coupon_id' => "ALTER TABLE `{$o}` ADD COLUMN `coupon_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用的用户优惠券ID'", ]; foreach ($orderCols as $col => $sql) { if (!$this->hasColumn($o, $col)) { Db::execute($sql); $changed++; } } if (!$this->hasColumn($c, 'settle_due')) { Db::execute("ALTER TABLE `{$c}` ADD COLUMN `settle_due` int(10) DEFAULT NULL COMMENT '预计结算时间'"); $changed++; } $output->writeln($changed ? ' - 完成(新增结构 ' . $changed . ' 处)' : ' - 优惠券/结算字段已是最新,无需处理'); } /** 表是否存在某个索引 */ private function hasIndex($table, $index) { $rows = Db::query("SHOW INDEX FROM `{$table}` WHERE Key_name = '" . addslashes($index) . "'"); return !empty($rows); } /** 读配置默认值(不依赖 api 基类的 cfg 缓存) */ private function cfgValue($key, $default = null) { $row = Db::name('fc_config')->where('key', $key)->find(); if (!$row || $row['value'] === '' || $row['value'] === null) { return $default; } return $row['value']; } /** * 初始化 FastAdmin 后台菜单(幂等) * * 菜单结构统一定义在 FcMenu::GROUPS / FcMenu::ITEMS, * 保证 fc:install 与 fc:menu 产出同一套菜单。 */ protected function menu(Output $output) { FcMenu::apply($output); } /** 清空业务表(保留表结构) */ protected function truncateAll() { foreach (array_keys($this->tables()) as $name) { Db::execute('TRUNCATE TABLE `' . \think\Config::get('database.prefix') . 'fc_' . $name . '`'); } } /** 表结构定义:key 为表名后缀,SQL 中 `#P#` 会被替换为 prefix . 'fc_' */ protected function tables() { return [ 'wallet_flow' => "CREATE TABLE IF NOT EXISTS `#P#wallet_flow` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `flow_no` varchar(32) NOT NULL DEFAULT '' COMMENT '流水号', `type` varchar(10) NOT NULL DEFAULT '' COMMENT 'income=入账 expense=支出(用户钱包口径)', `scene` varchar(32) NOT NULL DEFAULT '' COMMENT '场景:余额充值/后台充值/会员开通/发布付费/付费查看/订单退款(余额)/佣金提现', `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变动金额', `balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变动后钱包余额(fa_user.money)', `related_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '关联单据ID(订单/信息/提现单)', `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `flow_no` (`flow_no`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户钱包流水';", 'config' => "CREATE TABLE IF NOT EXISTS `#P#config` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `key` varchar(64) NOT NULL DEFAULT '' COMMENT '配置键', `value` text COMMENT '配置值', `group_key` varchar(32) NOT NULL DEFAULT '' COMMENT '分组', `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '说明', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key` (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-业务配置';", 'category' => "CREATE TABLE IF NOT EXISTS `#P#category` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL DEFAULT '' COMMENT '分类名', `icon_key` varchar(32) NOT NULL DEFAULT '' COMMENT '图标key', `color` varchar(16) NOT NULL DEFAULT '' COMMENT '主题色', `sort` int(10) NOT NULL DEFAULT '0' COMMENT '排序', `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', `post_count` int(10) NOT NULL DEFAULT '0' COMMENT '信息量', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-分类';", 'banner' => "CREATE TABLE IF NOT EXISTS `#P#banner` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(64) NOT NULL DEFAULT '', `image` varchar(255) NOT NULL DEFAULT '' COMMENT '图片,空则用渐变占位', `link` varchar(255) NOT NULL DEFAULT '' COMMENT '跳转路径', `color` varchar(255) NOT NULL DEFAULT '' COMMENT '底色:CSS 渐变 linear-gradient(...),留空则用内置春绿渐变', `sort` int(10) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '1', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-轮播广告位';", 'area' => "CREATE TABLE IF NOT EXISTS `#P#area` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `province` varchar(32) NOT NULL DEFAULT '' COMMENT '省份', `city` varchar(32) NOT NULL DEFAULT '' COMMENT '城市', `district` varchar(32) NOT NULL DEFAULT '' COMMENT '区县(最终服务区域)', `lat` decimal(10,6) DEFAULT NULL COMMENT '中心纬度(gcj02)', `lng` decimal(10,6) DEFAULT NULL COMMENT '中心经度(gcj02)', `sort` int(10) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '1', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `province_city` (`province`,`city`), KEY `enabled` (`enabled`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-服务区域(省/市/区县平铺,区县为服务区域)';", 'post' => "CREATE TABLE IF NOT EXISTS `#P#post` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '发布者', `category_id` int(10) NOT NULL DEFAULT '0', `title` varchar(80) NOT NULL DEFAULT '', `content` text, `cover` varchar(255) NOT NULL DEFAULT '', `media` text COMMENT '媒体JSON数组', `price` decimal(10,2) NOT NULL DEFAULT '0.00', `price_unit` varchar(16) NOT NULL DEFAULT '', `area` varchar(32) NOT NULL DEFAULT '' COMMENT '区县名称(展示用)', `district_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '服务区域ID(fc_area.id)', `address` varchar(128) NOT NULL DEFAULT '', `contact` varchar(32) NOT NULL DEFAULT '' COMMENT '联系人', `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话(完整)', `wechat` varchar(64) NOT NULL DEFAULT '', `tags` varchar(128) NOT NULL DEFAULT '' COMMENT '标签,逗号分隔', `is_paid` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否付费发布', `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '支付金额', `pay_type` varchar(16) NOT NULL DEFAULT '', `audit_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待审1通过2驳回', `audit_reason` varchar(255) NOT NULL DEFAULT '', `views` int(10) NOT NULL DEFAULT '0', `likes` int(10) NOT NULL DEFAULT '0', `comment_count` int(10) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常0下架', `is_top` tinyint(1) NOT NULL DEFAULT '0', `distance` int(10) NOT NULL DEFAULT '0' COMMENT '距离(米)', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, `deletetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `category_id` (`category_id`), KEY `user_id` (`user_id`), KEY `audit_status` (`audit_status`), KEY `district_id` (`district_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-信息';", 'draft' => "CREATE TABLE IF NOT EXISTS `#P#draft` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0', `category_id` int(10) NOT NULL DEFAULT '0', `title` varchar(80) NOT NULL DEFAULT '', `content` text, `media` text, `contact` varchar(32) NOT NULL DEFAULT '', `phone` varchar(20) NOT NULL DEFAULT '', `address` varchar(128) NOT NULL DEFAULT '', `area` varchar(32) NOT NULL DEFAULT '' COMMENT '区县名称', `district_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '服务区域ID(fc_area.id)', `savedat` int(10) DEFAULT NULL, `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-草稿箱';", 'merchant' => "CREATE TABLE IF NOT EXISTS `#P#merchant` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0', `name` varchar(64) NOT NULL DEFAULT '', `logo` varchar(255) NOT NULL DEFAULT '', `contact` varchar(32) NOT NULL DEFAULT '', `category` varchar(32) NOT NULL DEFAULT '' COMMENT '经营类目', `license` varchar(255) NOT NULL DEFAULT '' COMMENT '营业执照', `audit_status` tinyint(1) NOT NULL DEFAULT '0', `audit_reason` varchar(255) NOT NULL DEFAULT '', `shop_status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT 'normal/pending/banned', `violations` int(10) NOT NULL DEFAULT '0', `post_count` int(10) NOT NULL DEFAULT '0', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-商家资质';", 'comment' => "CREATE TABLE IF NOT EXISTS `#P#comment` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `post_id` int(10) unsigned NOT NULL DEFAULT '0', `user_id` int(10) unsigned NOT NULL DEFAULT '0', `score` tinyint(1) NOT NULL DEFAULT '5', `content` varchar(500) NOT NULL DEFAULT '', `images` text, `reply` varchar(500) NOT NULL DEFAULT '', `replytime` int(10) DEFAULT NULL, `top` tinyint(1) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT '1', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `post_id` (`post_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-评价';", 'report' => "CREATE TABLE IF NOT EXISTS `#P#report` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `target_type` varchar(16) NOT NULL DEFAULT 'post' COMMENT 'post/comment', `target_id` int(10) NOT NULL DEFAULT '0', `reason` varchar(255) NOT NULL DEFAULT '', `reporter_id` int(10) unsigned NOT NULL DEFAULT '0', `status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/handled', `remark` varchar(255) NOT NULL DEFAULT '', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-举报';", 'order' => "CREATE TABLE IF NOT EXISTS `#P#order` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL DEFAULT '', `user_id` int(10) unsigned NOT NULL DEFAULT '0', `type` varchar(16) NOT NULL DEFAULT 'post' COMMENT 'post/member/recharge', `title` varchar(64) NOT NULL DEFAULT '', `amount` decimal(10,2) NOT NULL DEFAULT '0.00', `pay_type` varchar(16) NOT NULL DEFAULT '', `status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/refunding/refunded/closed', `paytime` int(10) DEFAULT NULL, `origin_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠前金额', `coupon_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠券抵扣金额', `coupon_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用的用户优惠券ID(fc_user_coupon.id)', `refund_amount` decimal(10,2) NOT NULL DEFAULT '0.00', `refund_reason` varchar(255) NOT NULL DEFAULT '', `out_refund_no` varchar(64) NOT NULL DEFAULT '' COMMENT '微信退款单号(商户侧)', `related_id` int(10) NOT NULL DEFAULT '0' COMMENT '关联业务ID', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `order_no` (`order_no`), KEY `user_id` (`user_id`), KEY `type` (`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-订单';", 'plan' => "CREATE TABLE IF NOT EXISTS `#P#plan` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `plan_code` varchar(16) NOT NULL DEFAULT '' COMMENT 'M1/M3/M12', `name` varchar(32) NOT NULL DEFAULT '', `price` decimal(10,2) NOT NULL DEFAULT '0.00', `origin_price` decimal(10,2) NOT NULL DEFAULT '0.00', `days` int(10) NOT NULL DEFAULT '30', `post_quota` int(10) NOT NULL DEFAULT '0' COMMENT '含发布条数(月/年套餐赠送额度)', `tag` varchar(16) NOT NULL DEFAULT '', `sort` int(10) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '1', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `plan_code` (`plan_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-会员套餐';", 'user_ext' => "CREATE TABLE IF NOT EXISTS `#P#user_ext` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0', `openid` varchar(64) NOT NULL DEFAULT '' COMMENT '微信openid', `user_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1个人2商家', `realname_status` tinyint(1) NOT NULL DEFAULT '0', `realname` varchar(32) NOT NULL DEFAULT '' COMMENT '收款人真实姓名(提现用)', `is_member` tinyint(1) NOT NULL DEFAULT '0', `member_level` varchar(32) NOT NULL DEFAULT '', `member_expiretime` int(10) DEFAULT NULL, `invite_code` varchar(16) NOT NULL DEFAULT '', `inviter_id` int(10) unsigned NOT NULL DEFAULT '0', `free_view_date` varchar(10) NOT NULL DEFAULT '' COMMENT '免费查看额度基准日', `free_view_used` int(10) NOT NULL DEFAULT '0', `post_quota_left` int(10) NOT NULL DEFAULT '0' COMMENT '剩余发布条数(月/年套餐赠送)', `post_quota_expire` int(10) DEFAULT NULL COMMENT '发布额度有效期(=会员到期时间)', `pay_password` varchar(255) NOT NULL DEFAULT '' COMMENT '支付密码(password_hash,空=未设置)', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `user_id` (`user_id`), KEY `invite_code` (`invite_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户扩展';", 'dist_relation' => "CREATE TABLE IF NOT EXISTS `#P#dist_relation` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '下线用户', `parent_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上级用户', `level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1一级2二级', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `user_id_level` (`user_id`,`level`), KEY `parent_id` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-分销关系';", 'commission' => "CREATE TABLE IF NOT EXISTS `#P#commission` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '获得佣金的人', `from_user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产生佣金的下线', `order_id` int(10) unsigned NOT NULL DEFAULT '0', `order_no` varchar(32) NOT NULL DEFAULT '', `order_type` varchar(16) NOT NULL DEFAULT '', `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '佣金金额', `rate` decimal(5,2) NOT NULL DEFAULT '0.00' COMMENT '分佣比例%', `level` tinyint(1) NOT NULL DEFAULT '1', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0未结算1已结算', `settle_due` int(10) DEFAULT NULL, `settletime` int(10) DEFAULT NULL, `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-分销佣金';", 'withdraw' => "CREATE TABLE IF NOT EXISTS `#P#withdraw` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `withdraw_no` varchar(32) NOT NULL DEFAULT '', `user_id` int(10) unsigned NOT NULL DEFAULT '0', `amount` decimal(10,2) NOT NULL DEFAULT '0.00', `channel` varchar(16) NOT NULL DEFAULT 'wxpay', `status` varchar(16) NOT NULL DEFAULT 'auditing' COMMENT 'auditing/paying/paid/failed/rejected', `reason` varchar(255) NOT NULL DEFAULT '' COMMENT '驳回原因', `paytime` int(10) DEFAULT NULL, `out_bill_no` varchar(32) NOT NULL DEFAULT '' COMMENT '微信商家转账商户单号', `transfer_status` varchar(16) NOT NULL DEFAULT '' COMMENT 'WAIT_USER_CONFIRM/PROCESSING/SUCCESS/FAIL/CANCELLED', `transfer_id` varchar(64) NOT NULL DEFAULT '' COMMENT '微信转账单号', `package_info` varchar(512) NOT NULL DEFAULT '' COMMENT '拉起确认收款页的package', `fail_reason` varchar(255) NOT NULL DEFAULT '', `transfer_time` int(10) DEFAULT NULL, `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `withdraw_no` (`withdraw_no`), KEY `user_id` (`user_id`), KEY `out_bill_no` (`out_bill_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-提现';", 'finance_flow' => "CREATE TABLE IF NOT EXISTS `#P#finance_flow` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '归属用户,0=平台/历史数据', `flow_no` varchar(32) NOT NULL DEFAULT '', `type` varchar(16) NOT NULL DEFAULT 'income' COMMENT 'income/expense', `scene` varchar(32) NOT NULL DEFAULT '' COMMENT '业务场景', `amount` decimal(10,2) NOT NULL DEFAULT '0.00', `balance` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '账后余额', `related_id` int(10) NOT NULL DEFAULT '0', `remark` varchar(255) NOT NULL DEFAULT '', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `flow_no` (`flow_no`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-资金流水';", 'message' => "CREATE TABLE IF NOT EXISTS `#P#message` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0=全体', `type` varchar(16) NOT NULL DEFAULT 'system' COMMENT 'system/interact', `title` varchar(64) NOT NULL DEFAULT '', `content` varchar(500) NOT NULL DEFAULT '', `isread` tinyint(1) NOT NULL DEFAULT '0', `readtime` int(10) DEFAULT NULL, `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-消息';", 'pay_view' => "CREATE TABLE IF NOT EXISTS `#P#pay_view` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0', `post_id` int(10) unsigned NOT NULL DEFAULT '0', `pay_type` varchar(16) NOT NULL DEFAULT '', `amount` decimal(10,2) NOT NULL DEFAULT '0.00', `order_id` int(10) unsigned NOT NULL DEFAULT '0', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `user_post` (`user_id`,`post_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-付费查看记录';", 'favorite' => "CREATE TABLE IF NOT EXISTS `#P#favorite` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '收藏人', `post_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '信息ID', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `user_post` (`user_id`,`post_id`), KEY `post_id` (`post_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-信息收藏';", 'follow' => "CREATE TABLE IF NOT EXISTS `#P#follow` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '关注发起人', `target_user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '被关注人', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `user_target` (`user_id`,`target_user_id`), KEY `target_user_id` (`target_user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户关注';", 'coupon' => "CREATE TABLE IF NOT EXISTS `#P#coupon` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL DEFAULT '' COMMENT '优惠券名称', `type` varchar(16) NOT NULL DEFAULT 'fixed' COMMENT 'fixed 满减 / discount 折扣', `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '面额(元);discount 时为折扣数如 8.50', `min_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '使用门槛(元),0 为无门槛', `total` int(10) NOT NULL DEFAULT '0' COMMENT '发行总量,0 为不限量', `received` int(10) NOT NULL DEFAULT '0' COMMENT '已领取数量', `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用:1 启用 0 停用', `sort` int(10) NOT NULL DEFAULT '0' COMMENT '排序,越大越靠前', `days` int(10) NOT NULL DEFAULT '0' COMMENT '领取后有效天数,0 为永久', `createtime` int(10) DEFAULT NULL, `updatetime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `enabled` (`enabled`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-优惠券';", 'user_coupon' => "CREATE TABLE IF NOT EXISTS `#P#user_coupon` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '领取人', `coupon_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '优惠券ID', `status` varchar(16) NOT NULL DEFAULT 'unused' COMMENT 'unused 未使用 / used 已使用 / expired 已过期', `order_no` varchar(32) NOT NULL DEFAULT '' COMMENT '使用时的订单号', `usetime` int(10) DEFAULT NULL COMMENT '使用时间', `expiretime` int(10) DEFAULT NULL COMMENT '过期时间(领取时间 + days)', `createtime` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `coupon_id` (`coupon_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户优惠券';", ]; } /** * 注入种子数据 * 说明:与小程序端 common/mock/data.js 的数据集逐字段对齐, * 保证「前端离线 Mock」与「线上真实接口」返回同一套数据,便于两端对拍。 */ protected function seed(Output $output) { $now = time(); $day = 86400; $t = function ($offsetDays) use ($now, $day) { return (int)($now - $offsetDays * $day); }; /* ---------- 业务配置 ---------- */ $configs = [ ['base.appName', '同城分类信息', 'base', '小程序名称'], ['base.logo', '', 'base', 'LOGO地址'], ['base.servicePhone', '400-000-0000', 'base', '客服电话'], ['base.aboutUs', '本地同城分类信息服务平台,招聘/房产/二手/服务一站直达。', 'base', '关于我们'], ['base.shareTitle', '同城分类信息 · 身边的好帮手', 'base', '分享标题'], ['wx.appid', 'wxbfcb778f3019cff8', 'wx', '小程序AppID(jscode2session 用)'], ['wx.secret', 'b023726713eebe3dad3876f5cea8ee6a', 'wx', '小程序AppSecret(仅服务端持有,不下发前端)'], ['pay.mchId', '', 'pay', '微信支付商户号'], ['pay.apiKey', '', 'pay', '微信支付密钥'], // 发布模式与发布价格(后台「系统配置 → 发布配置」维护) // 套餐价格与含发布条数在 fc_plan(后台「运营推广 → 会员套餐」维护),不走配置键 ['post.mode', 'free', 'post', '发布模式:free 免费发布 / paid 付费发布(默认免费,避免上线即撞付费墙)'], ['post.singlePrice', '5', 'post', '单条发布价格(元)=付费发布标准价'], ['post.freeQuota', '0', 'post', '免费发布条数(0 = 不限制/免费到底;达到条数后按单条发布价格计费)'], ['post.freeQuotaCycle', 'day', 'post', '免费条数统计周期:day 每日 / month 每月 / total 累计'], ['dist.enabled', '1', 'dist', '是否开启分销'], ['dist.level', '2', 'dist', '分佣层级'], ['dist.level1Rate', '20', 'dist', '一级分佣比例%'], ['dist.level2Rate', '10', 'dist', '二级分佣比例%'], ['dist.settleCycle', 'T+7', 'dist', '结算周期'], ['dist.minWithdraw', '50', 'dist', '最低提现金额'], ['member.benefit', json_encode([ 'freeView' => 20, 'postDiscount' => '8 折', 'distRate' => '一级 20% / 二级 10%', 'benefits' => ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '赠送大量发布额度'], ], JSON_UNESCAPED_UNICODE), 'member', '会员权益配置'], ['ui.siteName', '同城分类信息', 'ui', '站点名称'], ['ui.logo', '', 'ui', '自定义LOGO'], ['ui.icons', '{}', 'ui', '图标覆盖 {key:url}'], ['ui.updatedAt', date('c'), 'ui', '最后更新时间'], ]; foreach ($configs as $c) { Db::name('fc_config')->insert([ 'key' => $c[0], 'value' => $c[1], 'group_key' => $c[2], 'remark' => $c[3], 'createtime' => $now, 'updatetime' => $now, ]); } $output->writeln(' - 配置 ' . count($configs) . ' 项'); /* ---------- 分类 ---------- */ $categories = [ [1, '招聘求职', 'cat-job', '#FF7A59', 1280], [2, '房产租售', 'cat-house', '#FF9F45', 860], [3, '二手闲置', 'cat-goods', '#FFB570', 2340], [4, '车辆买卖', 'cat-car', '#F2994A', 430], [5, '本地服务', 'cat-service', '#E8845F', 760], [6, '教育培训', 'cat-education', '#F4A261', 320], [7, '家政保洁', 'cat-clean', '#FFA07A', 210], [8, '同城交友', 'cat-friend', '#FF8C69', 190], ]; foreach ($categories as $i => $c) { Db::name('fc_category')->insert([ 'id' => $c[0], 'name' => $c[1], 'icon_key' => $c[2], 'color' => $c[3], 'sort' => $i + 1, 'enabled' => 1, 'post_count' => $c[4], 'createtime' => $now, 'updatetime' => $now, ]); } /* ---------- 轮播 ---------- */ $banners = self::BANNERS; foreach ($banners as $i => $b) { Db::name('fc_banner')->insert([ 'title' => $b[0], 'image' => '/uploads/banners/banner-' . ($i + 1) . '.png', 'link' => $b[1], 'color' => $b[2], 'sort' => $i + 1, 'enabled' => 1, 'createtime' => $now, 'updatetime' => $now, ]); } /* ---------- 服务区域(省 / 市 / 区县平铺,区县为服务区域) ---------- */ $areas = [ ['贵州省', '贵阳市', '云岩区', 1, 1, 26.605, 106.710], ['贵州省', '贵阳市', '南明区', 2, 1, 26.568, 106.716], ['贵州省', '贵阳市', '观山湖区', 3, 1, 26.625, 106.625], ['贵州省', '贵阳市', '花溪区', 4, 1, 26.430, 106.670], ['贵州省', '贵阳市', '白云区', 5, 0, 26.678, 106.623], ]; foreach ($areas as $a) { Db::name('fc_area')->insert([ 'province' => $a[0], 'city' => $a[1], 'district' => $a[2], 'lat' => $a[5], 'lng' => $a[6], 'sort' => $a[3], 'enabled' => $a[4], 'createtime' => $now, 'updatetime' => $now, ]); } /* ---------- 会员套餐 ---------- */ $plans = [ ['M1', '月卡', 19.9, 29.9, 30, '尝鲜', 30], ['M3', '季卡', 49.9, 89.7, 90, '最划算', 90], ['M12', '年卡', 168, 358.8, 365, '省心', 500], ]; foreach ($plans as $i => $p) { Db::name('fc_plan')->insert([ 'plan_code' => $p[0], 'name' => $p[1], 'price' => $p[2], 'origin_price' => $p[3], 'days' => $p[4], 'tag' => $p[5], 'sort' => $i + 1, 'enabled' => 1, 'post_quota' => $p[6], 'createtime' => $now, 'updatetime' => $now, ]); } $output->writeln(' - 分类8 / 轮播3 / 服务区域5 / 套餐3'); /* ---------- 演示用户(显式ID,与前端 mock 数据集一致) ---------- */ $salt = 'fcseed'; $pwd = md5(md5('123456') . $salt); $users = [ [1001, '同城小助手', '13800001111', 128.50, 320], [1002, '小王同学', '15033443344', 12.00, 40], [1003, '洁净到家', '13156785678', 60.00, 120], [1004, '违规用户A', '13000000000', 0.00, 0], [2001, '顺捷物流', '13988888888', 300.00, 210], [2002, '安家房产', '13566666666', 520.00, 380], [2003, '骑行爱好者', '13712341234', 8.00, 15], [2004, '车行天下', '13800000000', 90.00, 66], [2005, '洁净到家', '13156785678', 45.00, 88], [2006, '小王同学', '15033443344', 6.00, 10], [2007, '优选二手车', '13700000000', 0.00, 0], ]; // 幂等:先清理同 ID 的演示用户(fa_user 属核心表,不在 truncateAll 范围内) $demoUserIds = array_column($users, 0); Db::name('user')->where('id', 'in', $demoUserIds)->delete(); Db::name('fc_user_ext')->where('user_id', 'in', $demoUserIds)->delete(); foreach ($users as $u) { Db::name('user')->insert([ 'id' => $u[0], 'group_id' => 1, 'username' => 'fc' . $u[0], 'nickname' => $u[1], 'password' => $pwd, 'salt' => $salt, 'email' => $u[0] . '@fenleixinxi.local', 'mobile' => $u[2], 'avatar' => '', 'level' => 1, 'gender' => 0, 'money' => $u[3], 'score' => $u[4], 'status' => ($u[0] === 1004 ? 'hidden' : 'normal'), 'jointime' => $t(180), 'joinip' => '127.0.0.1', 'logintime' => $t(0.1), 'loginip' => '127.0.0.1', 'createtime' => $t(180), 'updatetime' => $t(0.1), ]); } /* 用户扩展(会员状态 / 身份 / 邀请码) */ $exts = [ [1001, 1, 1, '年卡会员', 300, 'TC8A9K', 0, 1], [1002, 1, 0, '', 0, 'TC2B7M', 1001, 1], [1003, 2, 1, '月卡会员', 25, 'TC3C1P', 1001, 2], [1004, 1, 0, '', 0, 'TC4D5Q', 0, 0], [2001, 2, 1, '季卡会员', 60, 'TC5E8R', 0, 2], [2002, 2, 1, '年卡会员', 200, 'TC6F2S', 0, 2], [2003, 1, 0, '', 0, 'TC7G9T', 1001, 1], [2004, 2, 0, '', 0, 'TC8H3U', 1002, 2], [2005, 2, 1, '月卡会员', 20, 'TC9J6V', 1001, 2], [2006, 1, 0, '', 0, 'TC2K4W', 1002, 1], [2007, 2, 0, '', 0, 'TC3L7X', 0, 2], ]; foreach ($exts as $e) { Db::name('fc_user_ext')->insert([ 'user_id' => $e[0], 'openid' => 'openid_' . $e[0], 'user_type' => $e[1], 'realname_status' => 1, 'is_member' => $e[2], 'member_level' => $e[3], 'member_expiretime' => $e[4] > 0 ? $t(-$e[4]) : null, 'invite_code' => $e[5], 'inviter_id' => $e[6], 'free_view_date' => date('Y-m-d'), 'free_view_used' => 0, 'createtime' => $t(180), 'updatetime' => $t(0.1), ]); } $output->writeln(' - 用户 ' . count($users) . ' 个 + 扩展'); /* ---------- 信息(覆盖待审/已通过/已驳回三种状态) ---------- */ $posts = [ [101, 2001, 1, '急招快递分拣员(日结)', '站点直招,日结 180-220 元,包工作餐,当日结算。有意者电话联系。', 0, '面议', '云岩区', '贵阳北站物流园', 1892, 32, 0, 1, 12, 1200, '日结,包吃', 0], [102, 2002, 2, '整租·未来方舟精装一室一厅', '家电齐全,拎包入住,近地铁 2 号线,押一付一,可短租。', 1800, '元/月', '南明区', '未来方舟 B 区', 3204, 88, 1, 1, 26, 3400, '精装,近地铁', 0], [103, 2003, 3, '九成新山地自行车 转让', '骑行不到 300 公里,27 速,原价 1599,现 680 出,可小刀,同城自提。', 680, '元', '观山湖区', '世纪城', 640, 21, 0, 1, 4, 820, '自提,9成新', 0], [104, 2004, 4, '2019 款家用轿车 一手车', '车况精品,无事故无泡水,4S 店保养记录齐全,支持第三方检测。', 68000, '元', '云岩区', '贵阳市云岩区', 1120, 40, 1, 0, 0, 5100, '无事故,可检测', 0], [105, 2005, 5, '专业空调清洗 / 加氟上门', '10 年老师傅,上门服务,清洗 60 元/台,加氟 80 元起,不满意不收费。', 60, '元/台', '南明区', '花果园', 2310, 66, 0, 0, 18, 2600, '上门,不满意不收费', 0], [106, 2006, 3, 'iPad 2021 64G 深空灰', '学生自用,无磕碰,配件齐全,支持当面验机。', 1450, '元', '观山湖区', '贵阳奥体中心', 480, 15, 0, 2, 1, 1500, '验机', 0], ]; $offsets = [0.2, 0.5, 1, 0.1, 0.05, 2]; $phones = [101 => '13988888888', 102 => '13566666666', 103 => '13712341234', 104 => '13800000000', 105 => '13156785678', 106 => '15033443344']; $paids = [101 => 0, 102 => 1, 103 => 0, 104 => 1, 105 => 0, 106 => 0]; foreach ($posts as $i => $p) { $id = $p[0]; Db::name('fc_post')->insert([ 'id' => $id, 'user_id' => $p[1], 'category_id' => $p[2], 'title' => $p[3], 'content' => $p[4], 'cover' => '', 'media' => json_encode([], JSON_UNESCAPED_UNICODE), 'price' => $p[5], 'price_unit' => $p[6], 'area' => $p[7], 'address' => $p[8], 'contact' => '联系人', 'phone' => $phones[$id], 'wechat' => 'tongcheng_' . $id, 'tags' => $p[15], 'is_paid' => $paids[$id], 'amount' => $paids[$id] ? 5 : 0, 'pay_type' => $paids[$id] ? 'wxpay' : '', 'audit_status' => $p[12], 'audit_reason' => $p[12] == 2 ? '信息涉嫌重复发布' : '', 'views' => $p[9], 'likes' => $p[10], 'comment_count' => $p[13], 'status' => 1, 'is_top' => 0, 'distance' => $p[14], 'createtime' => $t($offsets[$i]), 'updatetime' => $t($offsets[$i]), ]); } $output->writeln(' - 信息 ' . count($posts) . ' 条'); /* ---------- 草稿 ---------- */ Db::name('fc_draft')->insert([ 'user_id' => 1001, 'category_id' => 2, 'title' => '转租单间(草稿)', 'content' => '还未填完……', 'media' => json_encode([], JSON_UNESCAPED_UNICODE), 'savedat' => $t(0.1), 'createtime' => $t(0.1), 'updatetime' => $t(0.1), ]); /* ---------- 商家资质 ---------- */ $merchants = [ [2001, '顺捷物流', '13988888888', '物流服务', 1, 'normal', 0, 32, 120], [2002, '安家房产', '13566666666', '房产中介', 1, 'normal', 1, 68, 200], [2005, '洁净到家', '13156785678', '家政服务', 0, 'pending', 0, 12, 3], [2007, '优选二手车', '13700000000', '汽车服务', 2, 'banned', 3, 0, 60], ]; foreach ($merchants as $m) { Db::name('fc_merchant')->insert([ 'user_id' => $m[0], 'name' => $m[1], 'logo' => '', 'contact' => $m[2], 'category' => $m[3], 'license' => $m[4] == 2 ? '' : '营业执照.jpg', 'audit_status' => $m[4], 'audit_reason' => $m[4] == 2 ? '营业执照不清晰' : '', 'shop_status' => $m[5], 'violations' => $m[6], 'post_count' => $m[7], 'createtime' => $t($m[8]), 'updatetime' => $t($m[8]), ]); } /* ---------- 评价 ---------- */ $comments = [ [5001, 101, 1002, 5, '联系很及时,当天就上岗了。', '', 0, 0.5], [5002, 102, 1003, 4, '房子不错,房东好说话。', '感谢支持,欢迎续租~', 1, 2], [5003, 103, 1004, 1, '广告信息,疑似虚假。', '', 0, 0.2], ]; foreach ($comments as $c) { Db::name('fc_comment')->insert([ 'id' => $c[0], 'post_id' => $c[1], 'user_id' => $c[2], 'score' => $c[3], 'content' => $c[4], 'images' => json_encode([], JSON_UNESCAPED_UNICODE), 'reply' => $c[5], 'replytime' => $c[5] ? $t($c[7]) : null, 'top' => $c[6], 'status' => 1, 'createtime' => $t($c[7]), 'updatetime' => $t($c[7]), ]); } /* ---------- 举报 ---------- */ Db::name('fc_report')->insert([ 'target_type' => 'comment', 'target_id' => 5003, 'reason' => '疑似虚假信息', 'reporter_id' => 1002, 'status' => 'pending', 'remark' => '', 'createtime' => $t(0.2), 'updatetime' => $t(0.2), ]); Db::name('fc_report')->insert([ 'target_type' => 'post', 'target_id' => 106, 'reason' => '重复发布', 'reporter_id' => 1004, 'status' => 'handled', 'remark' => '已下架处理', 'createtime' => $t(4), 'updatetime' => $t(4), ]); $output->writeln(' - 商家4 / 评价3 / 举报2'); /* ---------- 订单(发布 / 会员 / 充值 三类) ---------- */ $orders = [ [3001, 'PO20240101001', 1001, 'post', '单条付费发布', 5, 'wxpay', 'paid', 1], [3002, 'MO20240102002', 1001, 'member', '年卡会员', 168, 'wxpay', 'paid', 2], [3003, 'RO20240103003', 1001, 'recharge', '余额充值', 100, 'wxpay', 'paid', 3], [3004, 'PO20240104004', 1001, 'post', '单条付费发布', 5, 'balance', 'pending', 0.5], ]; foreach ($orders as $o) { Db::name('fc_order')->insert([ 'id' => $o[0], 'order_no' => $o[1], 'user_id' => $o[2], 'type' => $o[3], 'title' => $o[4], 'amount' => $o[5], 'pay_type' => $o[6], 'status' => $o[7], 'paytime' => $o[7] === 'paid' ? $t($o[8]) : null, 'related_id' => 0, 'createtime' => $t($o[8]), 'updatetime' => $t($o[8]), ]); } /* ---------- 消息中心 ---------- */ $messages = [ [1001, 'system', '审核通过', '您发布的信息「急招快递分拣员(日结)」已审核通过。', 0, 0.2], [1001, 'system', '会员到期提醒', '您的年卡会员将于 30 天后到期,续费可享 9 折。', 0, 1], [1001, 'interact', '收到新评价', '用户「小王同学」评价了您的信息:服务很到位,五星好评!', 1, 2], [1001, 'interact', '点赞提醒', '您发布的信息获得 5 个赞。', 1, 3], ]; foreach ($messages as $m) { Db::name('fc_message')->insert([ 'user_id' => $m[0], 'type' => $m[1], 'title' => $m[2], 'content' => $m[3], 'isread' => $m[4], 'readtime' => $m[4] ? $t($m[5]) : null, 'createtime' => $t($m[5]), ]); } /* ---------- 分销关系(1001 的一/二级下线) ---------- */ $rels = [[1001, 1], [1002, 1], [2003, 1], [1003, 2], [2004, 2], [2005, 2], [2006, 2]]; foreach ($rels as $r) { Db::name('fc_dist_relation')->insert([ 'user_id' => $r[0], 'parent_id' => 1001, 'level' => $r[1], 'createtime' => $t(10), ]); } /* ---------- 佣金(1001 的收益明细) ---------- * 数值与小程序 mock 的 distOverview 对齐: * totalCommission 1286.4 = settled 336.4 + unsettled 950 * 已申请提现 150(auditing 50 + paid 100) * → withdrawable = 336.4 - 150 = 186.4 */ $commissions = [ [1002, 3002, 'MO20240102002', 'member', 900.0, 20, 1, 0, 2], [2003, 3001, 'PO20240101001', 'post', 50.0, 20, 1, 0, 0], [2004, 3003, 'RO20240103003', 'recharge', 1.0, 20, 1, 1, 3], [1003, 3002, 'MO20240102002', 'member', 300.0, 10, 2, 1, 5], [2005, 3003, 'RO20240103003', 'recharge', 35.4, 10, 2, 1, 6], ]; foreach ($commissions as $c) { Db::name('fc_commission')->insert([ 'user_id' => 1001, 'from_user_id' => $c[0], 'order_id' => $c[1], 'order_no' => $c[2], 'order_type' => $c[3], 'amount' => $c[4], 'rate' => $c[5], 'level' => $c[6], 'status' => $c[7], 'settletime' => $c[7] ? $t($c[8]) : null, 'createtime' => $t($c[8]), ]); } /* ---------- 提现(含待审/已打款) ---------- */ $withdraws = [ [1, 'WD20231220001', 1001, 100, 'paid', '', 25], [2, 'WD20240105002', 1001, 50, 'auditing', '', 1], [3, 'WD20240106003', 1002, 200, 'auditing', '', 0.4], ]; foreach ($withdraws as $w) { Db::name('fc_withdraw')->insert([ 'id' => $w[0], 'withdraw_no' => $w[1], 'user_id' => $w[2], 'amount' => $w[3], 'channel' => 'wxpay', 'status' => $w[4], 'reason' => $w[5], 'paytime' => $w[4] === 'paid' ? $t($w[6]) : null, 'createtime' => $t($w[6]), 'updatetime' => $t($w[6]), ]); } /* ---------- 资金流水 ---------- */ $flows = [ ['F20240107001', 'income', '发布付费', 5, 2864.5, 0.2], ['F20240107002', 'income', '会员开通', 168, 2859.5, 0.5], ['F20240106003', 'expense', '佣金提现', 100, 2691.5, 1], ['F20240105004', 'income', '余额充值', 100, 2791.5, 2], ]; foreach ($flows as $f) { Db::name('fc_finance_flow')->insert([ 'flow_no' => $f[0], 'type' => $f[1], 'scene' => $f[2], 'amount' => $f[3], 'balance' => $f[4], 'related_id' => 0, 'remark' => '', 'createtime' => $t($f[5]), ]); } $output->writeln(' - 订单4 / 消息4 / 分销7 / 佣金2 / 提现3 / 流水4'); } }