84 KiB
修改记录
只记大改动:一个需求 / 一个模块算一条;同一件事的多轮微调合并成一条,不逐轮记。 排序:最新在最上面。 展开细节看
docs/updata_sql.md(数据库改动,含完整可执行 SQL)与.workbuddy/memory/YYYY-MM-DD.md(过程与踩坑)。 维护:完成一个大改动后,在顶部按日期加一条;同一天追加在该日期块末尾。
2026-09-27
-
管理后台按钮级权限补全:
sys_menu补 24 行F+ 防漂移校验脚本起因:用户问「管理后台会不会有只有超管能看到、非超管看不见的页面内按钮」。核查结论:会,而且比按钮更严重。
机制:按钮显隐走前端
v-hasPermi、接口放行走后端@PreAuthorize("@ss.hasPermi(...)"), 两边比对的是同一份sys_menu.perms;超管(user_id==1)拿*:*:*所以永远看得见。 ⇒ 某权限串只要在sys_menu里没有对应行,就永远授权不到任何非超管角色:按钮看不见、部分列表/详情接口直接 403。盘出的问题(代码在用、库里未登记,并集 43 串):
- 4 个孤儿页面(开箱概率 / 首充奖励 / 月度充值 / 换装记录)占 23 串 —— 已核实无菜单、无组件引用、无路由、无接口复用方,
不可达,本期不动(手输 URL 也 404,因 RuoYi 路由由
/getRouters动态注册)。 - 其余 20 串 +
admin:box:*复用组补 4 行 = 24 行,本次补齐。 - 结构性漂移:从
menu_id31xx 段起,新增页面只建C行、不建F行(F 行分布:1000 段 61、2000 段 18、3100 段 9、3200+ 段 0)。
改动清单
sys_menu新增 24 行F(menu_id 3209~3232,挂 2035/2038/2086/2092/2123/3118 六个 C 行下)。sys_role_menu动态补齐:把新 F 行补给「已授权其父 C 行」的角色(本次实写 24 行,全给role_id=104)。sys_menu修正 3 行(3160~3162)parent_id:目录3156→ 页面3157(F 行挂错层级,父子联动带不到)。- 代码侧配套:新增
.workbuddy/verify-perms.cjs—— 扫前端v-hasPermi+ 后端@PreAuthorize, 与sys_menu.perms做双向差集 + 校验全库 F 行 parent 必须指向 C 行。非 0 退出码,建议挂publish.sh前防复发。
验证:库层 5 项全对(24 行 / 0 重复 / 0 挂错父节点 / 0 未授权 /
role_menu222→246);node .workbuddy/verify-perms.cjs⇒ ALL_OK 6 passed / 0 failed。注意:① 权限生效需用户重新登录(
permissions登录时从/getInfo拉取,不热更新)。 ② 执行时库内授权已被重排 —— 只有role_id=104(测试人员)持有这些页面,三恒等角色0 菜单, 且多个启用用户(三恒/国荣/s1/zzyzzy123/Fdily)未分配任何角色,这是业务配置问题,本次未动。细节 SQL 见
docs/updata_sql.md「管理后台按钮级权限补全」,过程与踩坑见.workbuddy/memory/2026-09-27.md。 - 4 个孤儿页面(开箱概率 / 首充奖励 / 月度充值 / 换装记录)占 23 串 —— 已核实无菜单、无组件引用、无路由、无接口复用方,
不可达,本期不动(手输 URL 也 404,因 RuoYi 路由由
-
推广后台「佣金明细记录」等 3 张报表右侧留白 → 全列
width改min-width起因:用户截图
promo-ui2/src/views/CommissionReport/index.vue的「佣金明细记录」表,说「这里表格填充不满」。根因(读 Element Plus 源码定论,不是猜):
element-plus/es/components/table/src/table-layout.mjs:100-171的updateColumnsWidth()只在 存在弹性列 时才把表格撑满容器 (flexColumns = flattenColumns.filter(c => !isNumber(c.width))); 全列都是数字width时走 else 分支 ⇒bodyWidth = Σ realWidth⇒ 表格宽恒等于列宽之和,容器再宽右侧也留白。 ⚠️ 关键陷阱:width="170"这种静态字符串也会被table-column/render-helper.mjs:39的parseWidth()(=Number.parseInt)转成数字 170 ⇒ 那是固定列; 反过来只写min-width时setColumnWidth()会执行column.width = void 0⇒ 那才是弹性列。改动清单(
promo-ui2/src/views/CommissionReport/index.vue,3 张表共 16 列;另有 6 张表见本节末):- 「佣金明细记录」6 列(本地时间170 / 手机号130 / 该次消费120 / 佣金比例90 / 佣金金额100 / 来源120)
- 「佣金日报」5 列(手机号130 / 今日消费120 / 来源120 / 佣金金额120 / 日期130)
- 「佣金汇总」5 列(手机号130 / 来源120 / 消费金额120 / 本次佣金120 / 参加游戏次数140)
—— 一律
width→min-width,数值不变。选「全列都变弹性」而不是「只挑一列变弹性」: 弹性的那一列会独吞全部余量(1280 视口下 170px 的时间列会涨到 ≈590px),全列按 min-width 等比摊才均匀。 模板顶部加了注释说明为什么不能改回width。脚本/接口/分页/过滤逻辑一行未动。
验证(3 件,全部 ALL_OK):
- SFC 编译:
node .workbuddy/verify-promo-vue-compile.cjs src/views/CommissionReport/index.vue⇒ ALL_OK 4/0 (连带跑 Home ⇒ 11/0,未受影响)。 - 算法级多视口回归:
node .workbuddy/verify-ep-table-stretch.cjs src/views/CommissionReport/index.vue⇒ ALL_OK 15/0。1280/1440/1600/1920/2560 五个视口下 3 张表留白全为 0px、scrollX全 false。 - 真产物:
node .workbuddy/check-promo-modules.cjs src/views/CommissionReport/index.vue --has=min-width --prop=min-width:16⇒ ALL_OK(用户常驻的:4000dev server 上主模块 200、"min-width"prop 恰好 16 次、0 处编译错误串)。 - 全站审计:
node .workbuddy/audit-table-fill-ep.cjs⇒ CommissionReport 3 张表从「✗ 不能撑满」转为有弹性列。
新增脚本(3 个,都是 promo-ui2 / Element Plus 侧原先缺的):
.workbuddy/audit-table-fill-ep.cjs(全站扫「全固定列 ⇒ 留白」)、.workbuddy/verify-ep-table-stretch.cjs(复现updateColumnsWidth做多视口定量回归)、.workbuddy/check-promo-modules.cjs(从:4000拉真实编译产物做断言)。 原有的sim-table-width.cjs/verify-table-fill.cjs只适用 ruoyi-ui(Element UI 2.x),不要混用。注意 / 一并修的另外 6 张:审计出全站共 9 张同样会留白的表,用户拍板「6 张全改」,已一并完成:
BusinessManage/index.vue3 张(招商 Σ1128 / 主播 Σ828 / 普通用户 Σ920,后两张在展开行内)、CardMerchant/index.vue:44(Σ1210)、subFlow/index.vue:20(Σ1570)、SubordinateRecharge/index.vue:23(Σ760)。- 改法一致:数据列
width→min-width;「操作」列保持width(CardMerchant 100、subFlow 120)—— 操作列固定宽是设计意图,不该被拉宽。BusinessManage 的type="expand"列也不动(cellStarts强制 48px 固定)。 - 嵌套子表(BusinessManage 二/三级):容器宽度由父级展开行决定,不能用「视口 − 288」推算。
verify-ep-table-stretch.cjs已加depth判定,嵌套表只做「有没有弹性列」的定性断言,不套用视口断言。 - 两张宽表仍会横向滚动(改 min-width 治不了,属列宽总量问题):
CardMerchantΣ1210 ⇒ 视口 ≤1440 横滚(差 58px);subFlowΣ1570 ⇒ 视口 ≤1600 横滚。这两个视口下改前改后都是横滚,未新增;≥1600 / ≥1920 起改为撑满(改前是留白)。 若要 1440 也不横滚,得再压列宽(使用时间/创建时间180→165、卡密200→190 之类),本次未做,等确认。 - 复验:全站审计 不能撑满 0 / 34;SFC 编译 19/0 ALL_OK;列宽回归 22/0 ALL_OK(6 张表 × 视口);
真产物(
:4000)4 个文件主模块全 200、"min-width"prop 计数 22 / 8 / 6 / 13 逐一吻合、0 错误串。
⚠️ 未经浏览器实测(本机 Chrome 起不来,见
.workbuddy/memory/theme-skin.md)⇒ 效果请自己刷:4000确认。 -
修复:幸运升级「水位驱动」因 mapper 里
SUM(x, 0)语法错而整条不可用现象:启动日志
TtUpgradeRewardLogMapper.xml→sumRecordTotals报java.sql.SQLSyntaxErrorException: ... right syntax to use near '' at line 4, 外层 WARN「幸运升级水位驱动初始化失败(不阻断启动,首次拉取时会自动重试)」。根因:
TtUpgradeRewardLogMapper.xml的blankBet/blankWin两行把外层COALESCE(..., 0)的写法抄进了SUM()——SUM(CASE WHEN is_blank THEN r.amount_consumed END, 0)。SUM()只接受一个参数,第二个, 0直接语法错。 (同文件totalBet/totalWin两行写的是CASE WHEN is_blank THEN 0 ELSE ... END,是对的,可作对照。)改动:删掉
SUM(...)里多余的, 0(外层COALESCE(SUM(...), 0)保留,仍是 null-safe)。全仓库仅此一处。影响面(不是"只是个 WARN"):
ensureInit(false)(@PostConstruct预热)异常 → Redis 累计量KEY_BET/KEY_WIN/KEY_BLANK_*从未写入,KEY_INIT_AT也没落 ⇒ 每次调用都重新全表扫并再次失败,水位恒为 0,水位驱动实际等于停摆。- 后台「奖池水位 → 按历史重算」
/recalc调ensureInit(true)且没有 try/catch ⇒ 该按钮必 500。 calibrationFactor()有 try/catch → 失败返回 0 → 回落兜底系数 1.19(这条不受影响)。
验证:库里实测
SELECT SUM(CASE WHEN 1 THEN 2 END, 0)复现同一ERROR 1064; 改成SUM(CASE WHEN ... END)后整条sumRecordTotals跑通出数。XML 重新解析通过。上线动作:
./ry.sh stop→mvn clean install(见USER.md的「必须 clean install,否则嵌套旧 jar」)→./ry.sh start。 -
推广后台登录页新增「开发环境账号密码登录」
起因:推广后台唯一登录入口是
/promo/smsLogin(手机号 + 4 位短信验证码 + 滑块),本地联调要连短信通道、拖滑块; 后端此前已明确下线账号密码登录(PromoLoginServiceImpl注释:「账号密码登录已下线……避免用密码绕过短信校验被撞库」)。改动清单(前后端配合,默认关闭):
- 后端新增
POST /promo/devLogin(@Anonymous):账号解析直接复用createUserToken的promo_user_入口 ⇒ 推广账号(tt_user的主播/招商/卡商,登录名=手机号)与超管(promo_admin的username或phone_number) 都能登录,区别只是「短信验证码」换成了SecurityUtils.matchesPassword的 BCrypt 密码校验。 账号不存在 / 停用 / 密码错统一返回「账号或密码错误」(防账号枚举,真实原因走log.warn)。 - 开关走配置文件而非数据库:新增
promo.dev-login.enabled——application.yml默认false,application-dev.yml置true, 生产 profile 不覆盖 ⇒ 接口恒返回「账号密码登录未开启」,且后台管理员无法误开。 PromoWebConfig放行/promo/devLogin(维护拦截器不拦登录类接口,与 smsLogin/register 同口径)。- 前端
LoginForm.vue只在import.meta.env.DEV(即pnpm run dev)下渲染「短信验证码 / 账号密码」切换与密码表单; 登录成功后复用原有的completeLogin+enterSystem落地流程。api/login增加devLoginApi与DevLoginType。
验证:
vue-tsc --noEmit改动文件 0 报错(仓库其余 30+ 条为既存错误);mvn -B -DskipTests compile⇒BUILD SUCCESS。无需数据库变更:开关是配置文件项,不是
sys_config(相对promoMaintenance的取舍见.workbuddy/memory/2026-09-27.md)。使用注意:密码按 BCrypt 校验,测试账号若不知明文,查
tt_user.remark(格式明文密码:xxx)或promo_admin表。 - 后端新增
-
管理后台首页改排版:11 张等宽统计卡 → 「左窄轨 + 右主区」两栏
起因:用户反馈首页「排版还是和原来一样,只是改了 UI 的风格」——换肤把配色圆角阴影改了,但骨架仍是 「页头 → 卡片网格 → 区块 → 区块」的垂直堆叠模板,要求排版差异化、风格照旧。
改动清单(
ruoyi-ui/src/views/index.vue,单文件)- 骨架拆两栏:
grid-template-columns: 300px minmax(0,1fr)+align-items:start。- 左窄轨(
position:sticky; top:16px,跟随滚动):3 个核心大数字(在线用户 / 总充值 / 总流水,字号 28px)- 待办区(待处理提货申请 / 提货余额)。待处理 >0 时雏菊金描边 + 数值变色。
- 右侧主区:实时 RTP → 「经营数据」紧凑指标带(6 项
mini-stat,小图标 + 标签 + 数值)→ 流水统计图 (顺序按用户看完后的要求定:图表 400px 最高,放最底部滚到底正好)。
- 左窄轨(
- 指标一项不丢:原
statCards11 项拆成railMetrics(3) +miniStats(6) + 待办区硬编码 2 项 = 11。 原「图标 + 大数值 + 标签」的等权卡片形态取消,改为「大数字 / 紧凑条」两级,主次拉开。 - RTP 网格
repeat(5,1fr)→repeat(auto-fit, minmax(160px,1fr)):右区宽度随屏幕浮动(左轨占了 300px), 写死 5 列会在 1440 以下挤出 4+1。 - 旧骨架 CSS(
.stat-grid/.stat-card/.stat-icon/.stat-info/.stat-arrow)与statCardscomputed 一并删除。
风格:色板 / 圆角 / 阴影 / 图标浅底规则全部沿用,未新增任何色值 —— cottagecore 配色纪律不动。
验证:
node .workbuddy/verify-home-layout.cjs(新增,cwd=ruoyi-ui)⇒ ALL_OK 28 条 0 失败 (含 vue-template-compiler 编译模板、dart-sass 编译样式、新旧骨架核对、区块顺序、指标总数 11、交互绑定未丢);node .workbuddy/audit-palette.cjs⇒ 离版文件 0/185、离版色值 0 次; dev server(:80)/static/js/app.js⇒ 200 / 7.1MB / 0 处Module build failed。 ⚠️ 本机助手拉不起 Chrome(安全策略)⇒ 渲染后实际观感未经实测,需用户刷新页面肉眼确认。后续补丁(用户看完截图提的):左轨「待处理提货申请 / 提货余额」那格里,提货余额写的是
$9259.64(美元符号),而页面其余金额(总充值¥1800、总流水¥0.00、RTP 投注/奖励¥0.00、经营数据 6 项¥…) 一律¥⇒ 统一为¥{{ balance }}(ruoyi-ui/src/views/index.vue:55,全站唯一一处$货币写法,已 grep 确认)。verify-home-layout.cjs补 2 条断言(模板内不得出现$紧跟插值;提货余额那行前缀必须是¥)⇒ 现 30 条 ALL_OK。 真产物复核(新增.workbuddy/check-ruoyi-bundle.cjs,从:80拉app.js):编译后为_vm._v("¥" + _vm._s(_vm.balance)),$版本 0 处。 - 骨架拆两栏:
-
推广后台首页改排版:「基本信息 / 5 张返利卡 / 汇总表」三段堆叠 → 「推广工具置顶」
起因:与上一轮管理端首页同类问题(换肤只改配色,骨架没动)。用户在 3 轮高保真稿里选定 版本 3 · 推广工具置顶。
改动清单(
promo-ui2/src/views/Home/index.vue,单文件)- ① 我的推广(置顶通栏):
hero行放推广链接(15px 加粗、超长省略、整条可复制)+ 主色「复制链接」按钮(无链接时disabled); 下面一条虚线分隔的三格:我的邀请码(复制 / 修改 / 已修改)、手机号码(绑phoneNumber)、上级邀请码。 - ② 返利 KPI 横带(4 格,19px 数字):累计返利 / 今日返利 / 本周返利 / 累计下级 ——
全部从已有的
getGameCommissionData()的periods里取(总/今日/本周行),不新增任何接口。 - ③ 底部两栏
260px minmax(0,1fr):左 = 返利比例(5 行「玩法名 ⋯⋯ 百分比」点线清单,替掉原来 5 张等宽卡); 右 = 周期返利汇总表格(列一项没删,仍是 10 列)。@media(max-width:1200px)收成一栏让表格拿满宽度。 - ④ 账号信息降级到最底部:用户账号 / 用户类型 / 金币余额 / 积分余额 / 注册时间 / Steam交易链接(6 项)。
样式上做成非卡片(
background/border透明、去阴影),避免和上面的数据抢注意力。 - 旧骨架(
.section/.section-title/.game-rate-cards/.rate-card*)与那两条从未命中过的.el-table__row tr:nth-child(odd)伪条纹规则一并删除(Element 的stripe已负责隔行)。 - 颜色从残留的旧 Element 灰(
#f5f7fa/#e4e7ed/#303133/#67c23a/#606266/#909399/#fafafa/#e6a23c) 收敛到田园核白名单 —— 该页 scoped 样式是换肤时漏掉的一份。
一字未动的:
username: userInfo.userName(改邀请码的入参,后端拿它做「只能改自己」校验,与展示用的phoneNumber语义不同); 返利比例出处仍是getInfo()的三个*CommissionRate;表格 10 列 / 弹窗 / 复制 /onMounted双并发加载全部保留。验证(新增 2 个脚本)
node .workbuddy/verify-promo-home.cjs⇒ ALL_OK 54 条 0 失败:新骨架 12 项、旧骨架 4 项须绝迹、区块顺序、 表格 10 列 / 账号 6 项 / KPI 4 项 / 玩法 5 项齐全、交互绑定 7 项、旧 Element 色 0 处、色值全在白名单、8 组对比度 ≥4.5:1。node .workbuddy/verify-promo-vue-compile.cjs(新增,用 promo 自带的vue/compiler-sfc走 parse → compileScript → compileTemplate → compileStyle)⇒ ALL_OK 7 条 0 失败。node .workbuddy/verify-home-page.cjs⇒ ALL_OK 5 条 0 失败(该脚本①的结构性正则随「手机号码」移位已改写为与结构无关的定位)。- 真机 dev 产物(用户的 vite dev server 正在
:4000跑,热更新):/src/views/Home/index.vue的 style / template / script-setup / main 四个模块全 200,0 处Transform failed/Internal server error; 编译后 15/15 个新骨架选择器都带上了[data-v-a763a6ae]前缀,旧类名 0 残留。
⚠️ 两点未做实测:渲染后观感(本机 Chrome 拉不起来);窄屏横向滚动 —— 底部两栏时表格可用宽度 ≈ 屏宽-200(侧栏)-80, 10 列
min-width合计 1090px,视口 < 约 1600px 时右侧表格会出现横向滚动(≥1600 正常),已在<1200px断点收成一栏规避。 数据全是 0 是账号本身无下级/无消费所致,不是渲染问题(periodData的 4 行都在,只是值都为 0.00)。过程与踩坑见
.workbuddy/memory/2026-09-27.md。 - ① 我的推广(置顶通栏):
-
管理后台站点名「河南项目」→「凯科佰娱」(ruoyi-ui)
起因:用户截图(
localhost/#/index)圈出左下角 Logo 旁标题与浏览器 tab 标题,要求改成「凯科佰娱」。定位:截图端口 80 + hash 路由 ⇒ 是
ruoyi-ui(Vue2 管理后台,vue.config.js默认 port 80),不是 promo-ui2(:4000)。 站点名只有一个源:VUE_APP_TITLE(.env.*)。消费方两处 ——src/layout/components/Sidebar/Logo.vue:23(侧栏标题)、src/App.vue:20-29的metaInfo.titleTemplate(${路由名} - ${VUE_APP_TITLE});public/index.html的<title>由vue.config.js:10的name注入。 ⇒ 改一处 env 即可同时改掉侧栏 + tab。另有 login/register 两页是硬编码同名,一并改。改动清单(6 处,全是字符串替换)
.env.development/.env.production/.env.staging:VUE_APP_TITLE = 河南项目→凯科佰娱。vue.config.js:10兜底值|| '河南项目'→|| '凯科佰娱'。src/views/login.vue:5、src/views/register.vue:4的<h3 class="title">。
注意:①
.env在进程启动时注入process.env,改完必须重启 dev server(webpack 不会热更 env)。 ②ruoyi-ui/dist/是构建产物,仍含旧串,需npm run build:prod重新生成才会更新(未手动改 dist)。 ③ 后端application-*.yml里的「河南」是注释(控制台账号备注),与站点名无关,未动;docs/修改记录.md:910等处的「河南项目」是历史叙述,保留。过程见
.workbuddy/memory/2026-09-27.md。 -
推广后台顶栏「分享链接」删除(与首页「我的推广链接」重复)
起因:用户截图
promo-ui2首页(localhost:4000/#/home/index),圈出顶栏的分享链接:https://…?code=…与页面里的「我的推广链接」hero 卡是同一份内容,要求「把上面的去掉」。定位:两处不同来源 —— ① 顶栏:
src/layout/components/ToolHeader.vue:45-47的<span>分享链接:{userStore.getUserInfo?.shareLink}</span>(布局层组件,所有页面都有,role !== '99' && role !== '05'才显示); ② 页面:src/views/Home/index.vue:4-21的 hero 卡 —— 本次改版(版本 3「推广工具置顶」)新加的。 ⇒ hero 一上线,顶栏那份就成了重复。按用户口径删顶栏(上面的)。改动清单
ToolHeader.vue:删掉该<span>(原地留注释说明为何删)。- 同文件连带清理:
useUserStore的 import 与const userStore = useUserStore()—— 全文件只有这一处在用, 不删会变成未使用变量(vue-tsc/ eslint 会报)。 Home/index.vue:删掉「改邀请码成功后同步顶部 header 分享链接」的userStore.setUserInfo({...})块 + 其 import/实例—— 该块存在的唯一目的就是喂被删掉的顶栏,属同一改动的死代码,不是重构。
验证:给
.workbuddy/check-promo-modules.cjs新增--absent=<子串>(原先只有--has用于"必须有", 删元素没法验),从用户常驻的:4000拉 vite 真产物断言:layout/components/ToolHeader.vue→ 主模块 200、style 子模块 200、useUserStore/shareLink/getUserInfo全部已消失,ALL_OK 6/0。views/Home/index.vue→ 主模块 200、useUserStore/setUserInfo已消失,ALL_OK 4/0。 ⚠️--absent的坑:dev 构建保留模板注释 ⇒ 注释里别写被删的那个词,否则_createCommentVNode("…")会命中假 FAIL。
影响面:改的是布局层 ⇒ 所有页面顶栏都不再显示分享链接(不只首页)。
userStore.userInfo.shareLink仍由Login/completeLogin.ts与views/User/index.vue写入,只是不再有展示方(无其他读取处,已 grep 确认); 改邀请码后 store 里那份不再自动刷新,因无消费方,无影响。过程见
.workbuddy/memory/2026-09-27.md。
2026-09-24
-
头像管理:「是否默认」可同时多条为「是」的缺陷修复(唯一性约束 + 存量数据清洗 + 开关交互)
起因:用户在「网站设置 → 头像管理」截图反馈,「是否默认」那一列有 7 条都显示绿色「是」, 而按语义全站默认头像只应该有一条。
排查结论(逐层验证,不是猜的):
层 实测 结论 数据库 SELECT is_default, COUNT(*) ... GROUP BY is_default→0→1、1→7(id 63~69)数据本身就错,界面是忠实反映 前端 v-model="scope.row.isDefault"直读接口字段无显示层问题,不需要改渲染 后端 TtUserAvatarServiceImpl.updateUserAvatarById只做updateById根因:设为默认时不互斥清空其他记录 ⇒ 定位为后端写接口缺约束 + 存量脏数据,不是 UI bug。
二、连带发现(比表面问题更严重):C 端有 4 处消费「默认头像」,其中
ApiRegisterServiceImpl、PromoLoginServiceImpl用的是list(is_default='1')+RandomUtil.choice(...)随机挑一条 —— 这是在「可能有多条默认」的 既成事实上打的补丁。后果是每次注册拿到的初始头像都不一样,默认头像形同虚设。 (TtUserServiceImpl白号那边已有defaultAvatarList.isEmpty()的降级,逻辑本身没问题。)三、改动清单
- 后端互斥(核心)
skins-service/service-admin:TtUserAvatarService新增saveAvatar(TtUserAvatar)、getDefaultAvatar();TtUserAvatarServiceImpl新增私有方法clearOtherDefaultsIfNeeded(...): 凡是把某条写成is_default='1'的写入,先UPDATE ... SET is_default='0' WHERE is_default='1' AND id<>当前id, 再落当前这条,两步同一事务(@Transactional)。只在确实要置默认时才执行 UPDATE,普通编辑不白扫全表。updateUserAvatarById/saveAvatar均接入;顺手删掉原方法里无意义的setAvatar(getAvatar())。
- 控制器
TtUserAvatarController.add由userAvatarService.save(...)改走saveAvatar(...), 使新增也受同一约束(原来新增一条默认就会叠加出第二个)。 - 存量数据清洗:
UPDATE tt_user_avatar SET is_default='0' WHERE is_default='1' AND id <> (最小 id 的默认), 实测影响 6 行,清洗后0→7 / 1→1(保留 id=63)。SQL 全文见docs/updata_sql.md。 - 前端交互
ruoyi-ui/src/views/skins/ttSetting/avatar.vue:「是否默认」由updateUserStatus改走新的updateDefault(...)—— 开启时先$confirm告知「原默认头像「xx」将自动取消默认」, 取消则回滚开关视觉并重新拉列表。关闭时直接保存。
四、验证:后端
mvn -q -pl skins-service/service-admin -am install -DskipTests通过, 字节搜clearOtherDefaultsIfNeeded/saveAvatar/getDefaultAvatar均在.class里; 前端vue-cli-service build通过,新文案与新方法名均落在dist/static/js/chunk-cf2f9538.*.js。五、踩坑:MySQL 不允许
UPDATE的子查询直接查同一张表(报 1093), 解法是包一层派生表(SELECT MIN(id) ... ) AS t绕过 —— 后续同类 SQL 照此办理。⚠️ 待办:后端 jar 未重打包、服务未重启,互斥逻辑尚未在运行中的服务里生效; 前端
dist已就绪但线上资源需按部署流程发布。存量数据已清洗,界面当下已正确。 - 后端互斥(核心)
-
幸运升级:水位配置从「4 档波段表」简化为「一个目标水位百分比」+ 饰品随机数区间恢复可编辑
起因:上一版后台要维护一张 4 档波段表(水位下界/上界 × 随机数下界/上界 = 16 个数), 还要保证档间单调且留有重叠 —— 运营看不懂,也容易配出互相矛盾的表。 用户要的模型其实简单得多:「每个饰品设一个区间,水位高的时候随机数靠近区间下限, 但水位要维持在我设定的那个值」 ⇒ 后台只该填一个水位百分比。
⚠️ 本条取代
2026-09-23条里的「三、波段表」与「十、上线前清空旧区间」两处, 其余口径(三身份判定隔离、幸运值共享展示、标定系数、注水抽水、水位统计口径)全部仍然有效。一、新口径(一句话)
目标水位 → 自动反推随机数中心 → 在该饰品自己的区间内围绕中心抖动。 后台不配随机数,随机数由目标水位算出来:
V* = 100 / ((1 − 目标水位/100) × k) k = 实测标定系数(见第四节,样本不足按 1.19) 中心 = V* × (1 − (当前水位 − 目标水位)/100 × gain) gain 默认 2 t = (中心 − 下限) / (上限 − 下限) 中心在区间内的相对位置,夹到 [0.10, 0.90] V = 下限 + (上限 − 下限) × u^p, p = 1/t − 1 u ~ U(0,1)- 偏斜随机:支撑集始终是整个区间(任何值都可能出),水位只改变概率密度。
水位 = 目标 ⇒
t = 0.5、p = 1⇒ 均匀分布(区间内等概率); 水位高于目标(放水)⇒ 中心下移、p > 1⇒ 概率向下限堆积;低于目标则向上限堆积。 E[u^p] = 1/(p+1) = t⇒E[V] = 中心⇒ 水位精确收敛回目标值(这就是「保证维持在我设定的值」)。- ⚠️ 曾先写过「中心平移 + 固定宽度抖动,再夹取到区间边界」,已废弃:中心贴边时落点会被压成贴着边界的一条窄带, 且水位偏离越大窄带越窄,玩家一眼看出是"卡边界"。改成偏斜随机后,任何水位下支撑集都是整个区间。
- ⚠️
t夹到[0.10, 0.90]决定了中心的可表达范围 = 区间下界 + [10%, 90%] × 区间宽度。 中心超出这个范围(水位远离目标,或区间配得太窄)⇒ 水位只能停在附近。 例:区间[100,160]的中心可表达范围是[106, 154],对应水位约[20.7%, 45.4%]; 想把水位开到 60%,区间上限得提到 210 以上。
二、目标水位 ↔ 随机数中心换算表(
k = 1.19,即实测系数兜底值)目标水位 目标 RTP 随机数中心 V*0% 100% 84 10% 90% 93 20% 80% 105 30% 70% 120 40% 60% 140 45% 55% 153 50% 50% 168 60% 40% 210 ⚠️
V*必须落在饰品的随机数区间内(且离边界 > 10),否则中心被夹住、水位停在别处。 例:目标 60% 需要中心 210 ⇒ 饰品区间上界必须 ≥ 210。三、饰品随机数区间的作用(恢复了编辑入口)
tt_upgrade_ornaments的三个区间列(luck_section/anchor_luck_section/blank_luck_section) 重新成为必填项,语义从「固定随机数区间」变成「随机数的取值范围」: 整个区间始终都可能出,中心由水位决定、只控制概率往哪一端堆。区间越宽,单轮波动越大、越难被玩家归纳出规律。 ⇒2026-09-23条里「上线前清空三个区间列」的做法已作废,不要再执行。 ⇒ 区间还限定了这台机器能维持的水位范围(见第一节末条):区间太窄或下限太高,高水位目标就实现不了。四、实测标定系数 k:改为 5 分钟内存缓存
k = 「改造后累计下注(排除白号) / Σ(
round_target/100× 目标饰品价)」,含义与算法不变。 但它是一次全表聚合 +tt_userJOIN,而抽随机数的热路径现在也要用它 ⇒ 加 5 分钟内存缓存(启动预热 + 后台「按历史重算」主动刷新),不能每次拉取都查一次 DB。五、仿真验证(脚本
.workbuddy/upgrade-target-watermark-sim.mjs,每种配置 60 万次拉取,可复跑)配置 目标水位 实测稳态水位 偏差 实测 RTP p10~p90 随机数 随机数实际范围 低于 100 占比 A 区间 [100,160]30% 29.97% −0.03 70.03% 100~148 [100,160]全覆盖0.0% B 区间 [75,175](宽)30% 29.98% −0.02 69.99% 81~163 [75,175]全覆盖32.8% C 区间 [100,160]45% 43.80% −1.20 56.21% 146~159 [111,160]0.0% D 区间 [100,160]60% 43.79% −16.21 56.21% 146~159 [117,160]0.0% E 区间 [80,90](窄)30% 9.77% −20.23 90.25% 88~90 [83,90]100.0% - A/B:区间包含
V*⇒ 稳态水位与目标值差 < 0.05 个点,「维持在我设定的值」成立; 且随机数的实际出现范围就是整个区间(不是贴着中心的一条窄带)。 - C:
V* = 157.4距区间上限 160 只差 2.6,小于 10% 区间宽度(6)⇒ 被t上限夹住,偏 1.2 个点。 - D/E:区间不包含
V*⇒ 中心被夹死在边界,水位到不了目标值(偏 16~20 个点)—— 这是唯一的坑。 - B 的 32.8% 说明:想把「每次都要 100 以上才出」的观感彻底打掉,区间下限要低于 100(宽区间
[75,175])。
六、改动清单
UpgradeWatermarkSettingBody:删bands/Band类,改为targetWatermark+gain(后台只填目标水位)。UpgradeWatermarkService:删波段表查表逻辑,新增targetCenter()/centerAt()/drawTarget(lo, hi);drawTarget用偏斜随机(V = 下限 + 宽度 × u^p,p = 1/t − 1,t由中心在区间内的相对位置给出); 标定系数加 5 分钟缓存;info()返回targetWatermark/targetGap/targetCenter/centerNow/effectiveCalibration(原bands/bandRows/bandMin/bandMax全部移除)。ApiLuckyUpgradeServiceImpl:新增resolveRange()(读该饰品位 × 本身份的区间,直接读实体字段, 不再走 Redis 区间缓存 ⇒ 后台改完下一轮立即生效,不用清缓存);drawRoundTarget把区间交给水位服务。 未配区间时兜底[80,160]。回退分支legacyFixedTarget()保持旧的 Redis 缓存写法不动。- 前端
views/skins/luckyUpgrade/rewardPool/index.vue:删掉整张波段表,改为一个「目标水位」输入框- 策略开关,改完立即生效(页面没有保存按钮);实时条显示「当前水位 / 目标水位 / 离目标多少 /
下一轮随机数 / 人工调整 / 实测标定系数 / 白号 / 策略开关」。
策略卡里另有一个**「随机数区间 → 可维持水位范围」换算器**:填两个数即时反推
(
中心可表达范围 = 下限 + [10%, 90%] × 宽度,再代回水位 = 1 − 100/(k × 中心)), 目标水位不在算出的范围内会标红提示;右侧?悬浮说明「区间是什么 / 为什么它限定了水位范围 / 怎么用」。 ⇒ 正文只讲区间硬约束,不展示「随机数中心」这类内部推算量。
- 策略开关,改完立即生效(页面没有保存按钮);实时条显示「当前水位 / 目标水位 / 离目标多少 /
下一轮随机数 / 人工调整 / 实测标定系数 / 白号 / 策略开关」。
策略卡里另有一个**「随机数区间 → 可维持水位范围」换算器**:填两个数即时反推
(
- 前端
views/skins/ttUpgrade/index.vue:恢复普通/主播/白号三列「随机数区间」编辑(列名由「幸运区间」改为 「随机数区间」);顶部水位条改为显示「目标水位 / 推算随机数中心」(原「本轮随机数区间」两栏随波段表一起删)。
七、验证
mvn -B clean install -DskipTests通过;ruoyi-ui生产构建通过。- 仿真 E 组反证有效:区间
[80,90]+ 目标 30% ⇒ 稳态 9.84%、RTP 90.2%,说明夹取逻辑确实按预期生效(不是"看起来对")。 - 全仓扫
bands/bandRows/bandMin/bandMax无残留引用。
八、上线前必做
- 给每个在售饰品配随机数区间,且区间要包含「按目标水位推算的中心值」,中心离两端各留约 10% 区间宽度
(后台水位页会显示该中心)。区间缺失时后端兜底
[80,160],能用但不受控。 区间同时限定了水位能调到的范围(例:[100,160]只能维持约[20.7%, 45.4%]的水位)。 - 目标水位默认 30%(≈ 长期 RTP 70%);上线初期水位从 0 起步,会有一段收紧期,想立刻拉到目标值就在水位页注水。
- 后端需重启才生效(策略与抽数逻辑都在 Java 侧)。
九、虚拟水位:一批饰品同时开轮时,不会都按同一个水位放水(2026-09-24 补充)
问题:真实水位只反映已经结算的下注/返奖。一批饰品同时开新轮时(同时上架、或几个玩家同时首次拉取), 它们会全部用同一个真实水位去算中心 —— 水位高时连着出好几个低随机数,而这批货真出完后水位其实已经变了。
做法:每抽一个随机数就把这一轮的预估交易预留进
upgrade_pool:pending:bet/:pending:win:预估下注 = 饰品价 × k × 随机数 / 100(乘 k 是因为判定溢出)、预估返奖 = 饰品价。 抽下一个时用的是虚拟水位:虚拟水位 = (真实bet + pending_bet − 真实win − pending_win + 调整) / (真实bet + pending_bet)这一轮出货过程中产生的实际流水会在
recordPull里逐步把预留扣回去 —— 两边口径一致 (k就是「实际 / 名义」的实测系数),所以真正出货时预留基本归零、不会长期挂账。 白号轮次不进水位口径,不参与预留;enabled=false或没抽过数时 pending = 0,退化为真实水位。 后台「按历史重算」会把 pending 一起归零,兜住「某个轮次永远没出货、pending 长期挂账」的漂移。效果(同一仿真脚本的「预留机制」段;8 轮连开后的
E[V]):累计下注体量 不用预留 用预留(第 8 轮) 收紧 2 万 88.0 108.6 +23.4% 10 万 88.0 90.8 +3.2% 100 万 88.0 88.0 0% ⚠️ 效果与水位体量成反比,这是数学必然、不是实现问题 —— 水位是「累计比例」, 一件货(玩家成本约 1.2 倍饰品价)只占累计下注的极小一份。刚上线体量小时很灵敏,体量大后几乎可忽略。 ⇒ 想让大水位下收敛更灵敏,要调的是
gain(水位每偏离 1 个点、中心移动的幅度,默认 2),不是靠预留。 ⇒ 另外:中心一旦撞到区间边界(t被夹到[0.10, 0.90]),E[V]就不再随水位变化 —— 上面 100 万那组水位明明降了 0.32 个点,E[V]仍是 88,就是这个原因。★ 生效边界:能稳定处理的量级(判据
|Δ虚拟水位| ≥ 1 个点⇒E[V]变化 ≥ 2.4 点、约 2.7%,线上看得出来):累计下注 \ 连开个数 3 个 5 个 10 个 20 个 50 个 1 万 −10.0 * −10.8 * −16.1 * −12.6 * −14.0 * 3 万 −4.1 * −6.6 * −6.4 * −13.1 * −13.2 * 10 万 −1.4 * −2.2 * −4.0 * −6.3 * −10.6 * 30 万 −0.5 −0.7 −1.4 * −2.7 * −6.0 * 100 万 −0.1 −0.2 −0.3 −0.9 −2.0 * 500 万 −0.03 −0.04 −0.1 −0.2 −0.4 (* = 有效;饰品价按 1000、水位 45%、目标 30% 算。因撞区间边界后随机数饱和,1 万那行不再单调。)
门槛公式:
Δ水位 ≈ x·[q(1−r) − 1] / (1 + q·x),其中x = N·p/B、q = k·V/100 ≈ 1.2~1.43、r= 当前水位。 ⇒r = 目标水位时分子为 0 —— 稳态下预留不起作用(本该如此),水位偏离目标越多才越明显。 ⇒ 判据的本质是「连开饰品的总面值Np占累计下注B的比例」:Np/B ≳ 3%~5%才看得出来。为什么和累计下注相关(而不是只看水位) —— 水位是比值、流水是绝对额:
水位 = 利润 / 累计下注,一件货(饰品价 1000、随机数 120)带来「下注 +1200、返奖 +1000、净赚 +200」, 但这 200 元对水位的推动完全取决于分母:累计下注 B 加一件货后 水位变化 10 万 45.00% → 44.80% −0.20 点 100 万 45.00% → 44.98% −0.02 点 ⇒ 同一笔钱落进 10 倍大的池子,效果就是 1/10。 ⚠️ 预留不会凭空制造推动 —— 它预估的就是这批货真实产生的交易,所以效果上限 = 这批货真出完后本来就会推动的量, 机制只是把这份推动提前给第 2、第 3 个随机数看到。⇒ 「连开 N 个才有效」的门槛,本质不是预留不灵, 而是这 N 件货本来也推不动水位。 ⇒ 所以「按水位判断」和「体量影响效果」是两件事:水位→随机数(判断,只看水位与
gain)与 流水→水位(记账,比例反馈天然有惯性,强依赖体量)。累计下注(p = 1000) 需要连开多少个才有效 1 万 ~ 3 万 2 ~ 3 个 10 万 3 个(面值占 3%) 30 万 10 个 100 万 50 个(现实中不存在) 500 万以上 不现实 ⇒ 一句话结论:刚上线、累计下注在十万量级以内能稳定处理;滚到百万以上基本失效。 这是「水位是累计比例」的必然结果 —— 想在大水位下也能快速纠偏,要调
gain(水位每偏离 1 个点、 中心移动的幅度;现在默认 2,暂未在后台暴露),或者直接人工注水/抽水。 - 偏斜随机:支撑集始终是整个区间(任何值都可能出),水位只改变概率密度。
水位 = 目标 ⇒
-
后台开关(el-switch)统一为「文字 = 当前值」口径,修掉反向文字露底色的误导
起因:用户反馈「头像管理」的状态与是否默认两列看得费解。查下来是两个叠加问题:
- 两边文字显示规则相反。element-ui 的
el-switch里active-text渲染在右侧、 且只在开启时显示;inactive-text渲染在左侧、只在关闭时显示。 本页「状态」用的是inactive-text="启用"(把当前值放在左侧), 「是否默认」用的是active-text="是"(把当前值放在右侧)—— 同一张表里两套相反的读法。 - 更糟的是反向文字会露出底色。「状态」列开启时左侧露出绿色底 + 白字「启用」,
点一下滑块滑到左边、绿底被盖住,可读文字变成「停用」。两列的滑块方向可以完全相反,
同一行的绿白视觉还不一样(
is-checked与背景色是两套独立配色)。
⇒ 定的新口径(全后台统一):
inactive-text= 关闭时的值,active-text= 开启时的值, 也就是「文字显示的就是当前值」,滑块停在哪边、哪边的文字就是现在的结果。 颜色同步收敛为与active-value/inactive-value同侧:绿 = 该项生效、红 = 该项未生效。改动清单(7 个文件,15 处开关)
文件 列 / 字段 调整 skins/ttSetting/avatar.vue状态 status文字翻正(启用/停用),配色与 active-value同侧skins/ttSetting/avatar.vue是否默认 isDefaultactive/inactive-value翻正为1/0(= 与initData、弹窗单选按钮一致),文字归右侧skins/ttuser/index.vue帐号状态 statusactive/inactive-value翻正为0/1(原为1/0,是全局唯一的反向异类)skins/ttuser/index.vue提货状态 deliveryStatus同上翻正( 1= 已禁用提货)skins/ttuser/index.vue赠送状态 giftStatus同上翻正( 1= 已禁用赠送)skins/ttBonus/index.vue启用状态 status文字从「是/否」改为「启用/禁用」并翻正 skins/ttBox/boxType.vue是否对战类型 isFightType、是否显示isShowlabel 归位; isShow的active/inactive-value翻正为1/0skins/ttredPacket/index.vue是否启用 useStatusactive/inactive-value翻正为1/0,配色同侧skins/ttUpgrade/index.vue状态 statusactive/inactive-value翻正为0/1(原为1/0)+ 文字翻正skins/ttBox/index.vue弹窗内 是否对战宝箱 isFight、宝箱状态status、是否首页推荐isHome文字归位、 isFight/status的active-value翻正关键约束:以上全部只是「值 ↔ 显示」的映射调整,
active-value与inactive-value成对同翻, 提交给后端的字段值逐字节不变,数据库语义与接口契约零改动(各页@change都是把整个 row/form 原样PUT出去,不做值转换)。语义校对依据(改前逐个确认过,避免把含义改反):
tt_user_avatar.status与tt_user.status:UserStatus枚举 =OK("0","正常")/DISABLE("1","停用"),ApiUserDetailsServiceImpl对DISABLE直接拒登并踢线 ⇒0= 启用。tt_user_avatar.is_default:TtUserAvatar的@Excel(readConverterExp = "0=否,1=是"), 且注册 / 白号 / 登录三处均查is_default = '1'取默认头像 ⇒1= 是。tt_user.delivery_status/gift_status:实体注释 + 业务代码均为1= 已禁用("1".equals(...)返回"已被禁用"提示)。- 可对照的既有正确写法:
ttSetting/banner.vue、ttRechargeList/index.vue、ttuser/userPack.vue。
⚠️ 踩坑:用户截图里「是否默认」列显示为红底白字「否」容易被误读成"开着"。 那是
active-color/inactive-color与active-text/inactive-text配成反的典型症状 —— 改完须确认颜色所在的半边和文字所在的半边是同一半。⚠️ 未改(故意留着):
ttRoll/officialRollConfig.vue的状态开关用的是:value="scope.row.status === '0'"+ 无文字标注的正向布尔(绿=开),本身自洽; 只建议后续补一个文字标注。 - 两边文字显示规则相反。element-ui 的
-
管理后台:3 个页面表格右侧留白(「表格没有平铺完整」)
起因:用户反馈「管理后台有些页面的表格没有平铺完整」。追问确认现象是 表格右侧留一条空白(不是列错位、也不是横向滚动条)。
根因(读 Element 源码定论):
element-ui/packages/table/src/table-layout.js:132-195的updateColumnsWidth()——flexColumns = 所有「width 不是数字」的列(含显式 min-width 列、以及两者都没写的列) if (flexColumns.length > 0 && fit) → 自动分配,表格撑满 bodyWidth (第 145 行) else → bodyWidth = Σ(width || minWidth || 80)(第 182-194 行)⇒ 只要有一列写了
min-width(或宽度两者都不写),表格就会自动撑满容器; 但当所有列都是固定width时,表格宽度恒等于列宽之和 —— 容器再宽也不拉伸,右侧就空一条。同一份源码里还有个易漏的点:
type="selection" / "expand" / "index"在未写 width 时, 由config.js:3-26的cellStarts给死 48px 固定宽,不参与弹性分配 —— 所以「展开列 + 其余全固定宽」的表格同样会留白。扫描结论(115 个顶层表格,1920 视口下容器可用 1584px):
- 仅 6 个是全固定列(会留白);109 个含弹性列、会自行撑满。
- 0 个会出横向滚动条(判据:
Σwidth + Σmin-width > 容器)。 - 留白量随屏幕变宽而增大(2560 视口下最大 1854px)—— 这正是"没平铺完整"的典型特征。
改动清单(3 个页面主表格,各把「内容最长的固定列」改为
min-width)文件 列 调整 表格宽 → 结果 skins/blankCardKey/index.vue卡密编码 cardCodewidth="200"→min-width="200"1000 → 撑满(原留白 584px) system/role/index.vue权限字符 roleKeywidth="150"→min-width="150"1075 → 撑满(原留白 509px) game/wheel/records/index.vue投注符号 betSymbolswidth="140"→min-width="140"1368 → 撑满(原留白 216px) 选列理由:卡密编码是 32 位等宽字符 + 复制按钮、权限字符内容最长且已带
show-overflow-tooltip(加宽无副作用)、投注符号是拼接字符串、长度波动最大 —— 都是"多给宽度只有好处"的列。 三处均加注释说明「为什么必须是 min-width」,防止后来人按"列宽统一用 width"的惯例改回去。未改(用户明确不动):3 个展开行内嵌明细表(
wheel/records下注明细、delivery/transferApply与transferApplyProcessed的饰品明细)与 1 个弹窗内倍率表 (wheel/config,固定width:640px)—— 它们宽度受容器约束是设计意图。 其中transferApply/transferApplyProcessed声明了style="width: auto"却只有 2 列固定宽 (300+120=420),在展开行里只占 420px;若后续要铺满,改其中一列为min-width即可。验证:
- 编译产物断言:
vue-template-compiler编译 3 个页面,✓ 全部通过,min-width各 1 个(200 / 150 / 140),其余列仍为固定 width。 - 算法级回归:复现
updateColumnsWidth后, 1280 / 1440 / 1600 / 1920 / 2560 五个视口下 3 个主表格留白全部归零。 - dev server 起来后
curl返回 200,确认改动未破坏编译。 - ⚠️ 本机无法真机截图核对:助手拉起 Chrome 被安全策略拦(连带报
wsl.exe在程序黑名单), 故本次验证口径是「读源码 + 复现算法 + 编译产物断言」,未经浏览器实测。
纯前端改动,不涉及数据库与接口;
dist未发布。
2026-09-23
- 幸运升级:随机数改由「全局水位」动态决定 + 判定层三身份隔离 / 展示层共享幸运值 + 后台注水抽水页
⚠️ 后续修订(2026-09-24):本条的「三、水位 → 随机数的映射(波段表)」与「十、上线前必做」第 1 条 (清空三个区间列)已被 2026-09-24 条取代 —— 水位从「4 档波段表」简化为「单一目标水位」, 饰品区间从"建议清空"变为"必须配置且要包含推算中心"。其余内容(水位口径、三身份判定隔离、 幸运值共享展示、标定系数、注水抽水、换算关系)全部仍然有效。
起因:原来每个饰品位配一套固定幸运区间(如 [100,160)),每轮从区间里均匀抽一个随机数当作出货阈值,
于是随机数永远 ≥ 区间下限 —— 玩家能看出「每次都要投到 1 倍原价以上才出」这条硬底。
同时三个身份(普通/主播/白号)各有各的区间与进度,前端展示的「幸运值」各不相同,
看主播或白号表演的观众会发现数值对不上账。
一、换算关系(改这个玩法的参数前必看)
幸运区间的单位是**「饰品价的百分比」**:
| 量 | 关系 |
|---|---|
| 每拉一次消耗 | 饰品价 × probability / 100 |
| 每拉一次推进 | eff = min(probability, 75) 点进度 |
| 每 1 点进度的成本 | 饰品价 / 100(probability ≤ 75 时严格成立) |
| 出一件货需要 | 约 fullV 点进度 |
| 理论 RTP | 100 / fullV |
| 水位 | 1 − RTP = 1 − 100/fullV |
| 目标水位 | 目标 RTP | 名义中心随机数 |
|---|---|---|
| −41% | 141% | 71 |
| −20% | 120% | 83 |
| 0% | 100% | 100 |
| 10% | 90% | 111 |
| 20% | 80% | 125 |
| 30% | 70% | 143 |
| 40% | 60% | 167 |
| 47% | 53% | 189 |
⚠️ 水位能到多少由随机数区间的上下界决定:想让水位能长期停在 40% 以上,某一档的随机数上界必须 > 160。
二、最终口径(三条,都是拍过板的)
- 随机数 + 判定进度:按身份完全隔离。每个身份有自己的一份「本轮进度 + 本轮随机数」, 白号中奖只刷新白号自己的随机数,普通玩家的进度与随机数原样不动。
- 水位:全局(整个幸运升级一个水位,不按饰品位拆),且排除白号。
- 幸运值:判定层隔离、展示层共享 —— 见下面第 4 条改动。
三、水位 → 随机数的映射(波段表)
sys_config: luckyUpgradeWatermark 存一张波段表,命中档后在该档区间内均匀随机(有 10s 内存缓存):
| 水位区间 | 可出的随机数区间 |
|---|---|
| < 10% | [95, 130] |
| 10% ~ 26% | [115, 150] |
| 26% ~ 36% | [75, 175] ← 稳态档 |
| ≥ 36% | [85, 128] |
保存时后端强校验:水位档升序不重叠、0 < vMin < vMax、水位越高随机数中心值必须越小(配反直接拒绝)。
四、名义 ≠ 实测:标定系数 k
判定是「跨过阈值即出货、最后一拉整段 eff 计入并丢弃溢出」,所以实测等效需求是名义 fullV 的 118%~120%,
波段表的随机数值不能按 100/fullV 公式拍。后台自动算标定系数:
k = 改造后累计下注(排除白号) / Σ(round_target/100 × 目标饰品价,仅中奖记录)
预计落点水位 = (1 − 100/(k × 中心随机数)) × 100
数据不足时 k = null,后台显示「样本不足」而不是用公式瞎估;「预计落点水位」落在该档自己的水位区间内 ⇒ 该档是稳态档(后台标绿)。
五、改动清单
(SQL 见 docs/updata_sql.md,接口口径见 docs/幸运升级接口文档.md)
- 水位:
水位% = (Σ下注 − Σ返奖 + 人工调整) / Σ下注 × 100,统计范围整个幸运升级且排除白号。 累计量走 Redis 增量计数(upgrade_pool:*),DB 只用于启动初始化与后台「按历史重算」—— 不在拉取热路径上对tt_upgrade_record做全表聚合。 - 随机数:按当前水位查波段表,命中档后在档内均匀随机;水位越高随机数越低(放水)。
enabled=false⇒ 逐字节回退到「每个饰品位固定luck_section」,不发版可回滚。 新增UpgradeWatermarkService(水位/波段表/标定/注水抽水)+TtUpgradeWatermarkController。 - 判定层三身份完全隔离:进度与随机数各存 Redis
upgrade_range_fixed:{饰品位}:{身份}(结构与改造前一致,老缓存不用迁移),白号中奖不影响普通玩家的进度与随机数。 - 幸运值(展示):累加口径保持不变 —— 按「玩家在前端拉的那个值」累加原始
probability; 中奖判定恒用min(概率,75),两者刻意分开(75% 封顶是风控手段,不改展示数字)。 展示值是三个身份共享的一个累计器:任一身份(含白号)中奖即整体归零,且中奖那一拉的增量仍计入 (出货那一刻显示的是含本次的值,下一拉才从 0 重新累计);表里那三列只用来分别记录各身份的贡献(后台排查),展示时相加。getOrnamentsList.luckyValue返回这个共享值(所以所有人看到的数一致),另给myLuckyValue。 另留luckyValueMode开关(默认raw,可切eff),只影响展示值与落库快照。 - 后台新增「幸运升级 → 奖池水位」页(
menu_id=3208):水位/实测RTP 统计卡、波段表编辑器(含名义RTP 与 按实测标定的「预计落点水位」)、注水抽水(原因必填)、调整历史、「按历史重算」。 - 拉取记录新增
watermark/round_target快照列,便于事后复盘「这批货是在什么水位下出的」。
六、仿真结论(脚本 .workbuddy/upgrade-watermark-sim.mjs,40 万次拉取,可复跑)
| 方案 | 名义 target | 实测 RTP | 实测水位 | 等效需求/名义 | 随机数<100 占比 |
|---|---|---|---|---|---|
A 现状:固定 [100,160) |
129 | 65.1% | 34.9% | 119% | 0.0% |
| C 曾设想的「共享池 + 只清中奖方」(已否决) | 108 | 131.8% | −31.9% | 71% | 19.2% |
| F 水位波段表(已实现) | 125 | 66.9% | 33.1% | 120% | 24.6% |
三个关键结论:
- ⚠️ 「三身份共享一个池、中奖只清中奖方」会亏钱:残留进度会垫薄下一轮阈值,非主力身份中奖时垫得最狠还会连锁 ⇒ 仿真实测 RTP 从 62% 飙到 131.8%、水位掉到 −32%。所以判定层必须按身份隔离, 「一个奖池」只体现在幸运值的展示上。
- ⚠️ 名义 ≠ 实测(见第四节):波段表的随机数必须按实测标定,按
100/fullV公式拍一定偏。 - 稳态随机数的均值由目标水位唯一决定(水位 30% ⟺ 平均随机数 ≈ 121),这是数学、改不掉; 能做的只是把档内区间做宽(推荐跨度 ≥ 80)。改造后随机数覆盖 75~175、24.6% 的轮次低于 100, 而 RTP 基本不变(65.1% → 66.9%)—— 玩家不再看到「必须 100 以上才出」的硬底。
七、一个完整算例(对着数字走一遍)
前提:饰品价 1000 元;波段表如上(当前落在 26~36% 档 ⇒ 随机数取自 [75,175]);
起始累计(排除白号)下注 100,000 / 返奖 65,000 / 调整 0 ⇒ 水位 = 35.00%。
| 步 | 谁 | 拉取 | 扣款 | 该池随机数 | 判定(进度 + min(概率,75)) | 结果 | 该池进度 |
|---|---|---|---|---|---|---|---|
| 1 | 普通 A | 10% | 100 | 120(本池新抽) | 0 + 10 = 10 < 120 | 失败 | 10 |
| 2 | 普通 B | 50% | 500 | 120(沿用) | 10 + 50 = 60 < 120 | 失败 | 60 |
| 3 | 主播 C | 20% | 200 | 95(主播池自己抽) | 0 + 20 = 20 < 95 | 失败 | 20 |
| 4 | 白号 D | 90% | 900 | 88(白号池自己抽) | 0 + 75 = 75 < 88 | 失败 | 75 |
| 5 | 白号 D | 20% | 200 | 88(沿用) | 75 + 20 = 95 ≥ 88 | 中奖 | 0 → 重抽 130 |
| 6 | 普通 A | 75% | 750 | 120(沿用) | 60 + 75 = 135 ≥ 120 | 中奖 | 0 → 重抽 105 |
第 5 步后三个池的状态(判定层隔离:白号中奖,普通/主播一点没动)
| 池 | 本轮随机数 | 进度 |
|---|---|---|
| 普通 | 120 | 60 |
| 主播 | 95 | 20 |
| 白号 | 130(重抽) | 0 |
第 4 步为什么要用 75 而不是 90:白号拉 90% 时扣款按 90% 收(900 元)、判定只按 min(90,75)=75 加进度,
而界面幸运值按原始概率加(白号那份 +90)。⇒ 那一刻白号显示「幸运值 90」、本池随机数 88,看着早该中了,
但判定进度只有 75(还差 13)—— 实际没中;下一步再拉 20%(75+20=95 ≥ 88)才中。这就是那个分叉的实际后果。
幸运值展示 = 三个身份共享的一个累计值(所有人看到同一个数;任一身份中奖即整体归零)
| 时点 | 普通 | 主播 | 白号 | 展示 luckyValue | 说明 |
|---|---|---|---|---|---|
| 起始 | 0 | 0 | 0 | 0 | |
| 步 1 后 | 10 | 0 | 0 | 10 | 普通 +10 |
| 步 2 后 | 60 | 0 | 0 | 60 | 普通 +50 |
| 步 3 后 | 60 | 20 | 0 | 80 | 主播也加进同一个数 |
| 步 4 后 | 60 | 20 | 90 | 170 | 白号 +90 |
| 步 5 后 | 0 | 0 | 0 | 190 | 白号 +20 → 中奖 → 整体归零;显示的是中奖那一刻的 190 |
| 步 6 后 | 0 | 0 | 0 | 75 | 0 + 75 → 中奖 → 整体归零 |
水位的变化(白号的量不算)
| 步 | 累计下注 | 累计返奖 | 水位 |
|---|---|---|---|
| 起始 | 100,000 | 65,000 | 35.00% |
| 1(普通 +100) | 100,100 | 65,000 | 35.06% |
| 2(普通 +500) | 100,600 | 65,000 | 35.39% |
| 3(主播 +200) | 100,800 | 65,000 | 35.52% |
| 4(白号 +900) | 100,800 | 65,000 | 35.52%(不变) |
| 5(白号 +200 且中奖 +1000) | 100,800 | 65,000 | 35.52%(不变) |
| 6(普通 +750 且中奖 +1000) | 101,550 | 66,000 | 35.01% |
白号的下注/返奖进另一组计数(后台「白号」那一栏),只展示、不影响水位。
水位什么时候生效:只在「新的一轮开始」读一次(该饰品位该身份首次拉取 / 刚中奖)。
本轮中途水位变化不改手里的随机数;若水位涨到 ≥ 36%,下一次新开的一轮才会从 [85,128] 里抽。
落库记录(tt_upgrade_record,节选)
| 步 | user_type | amount_consumed | amount_required | round_target | watermark | is_victory | lucky_value |
|---|---|---|---|---|---|---|---|
| 1 | 02 | 100 | 1200 | 120 | 35.00 | 0 | 10 |
| 4 | 06 | 900 | 880 | 88 | 35.52 | 0 | 170 |
| 5 | 06 | 200 | 880 | 88 | 35.52 | 1 | 190 |
| 6 | 02 | 750 | 1200 | 120 | 35.52 | 1 | 75 |
amount_required = 饰品价 × round_target / 100。- 中奖记录的
lucky_value记的是「中奖那一刻的累计值」(含本次),不是 0 —— 与玩家当下看到的一致。 watermark记的是「本次拉取之前」的水位 —— 代码先读水位、判定出货成功后才做增量记账,顺序不能反。
八、改动文件
后端 ApiLuckyUpgradeServiceImpl、UpgradeWatermarkService(新)、TtUpgradeWatermarkController(新)、
TtUpgradeRewardLogMapper(新) + XML、RedisConstants、ApiLuckyUpgradeMapper.xml、TtUpgradeOrnamentsMapper.xml、
实体 TtUpgradeRecord / TtUpgradeRewardLog(新) / UpgradeWatermarkSettingBody(新) / 两个 VO;
前端 views/skins/luckyUpgrade/rewardPool/index.vue(新) + api、views/skins/ttUpgrade/index.vue。
九、验证
mvn -B clean install -DskipTests 通过,且 javap 产物确认 doUpgrade 只剩新签名、无旧 (Integer[], UpgradeBodyA) 残留;
ruoyi-ui 构建 DONE Build complete;audit-palette 离版文件/色值 0 / 0;verify-dist ALL_OK(34 项);
audit-ops-fit 需加宽 0 条。新页面确认已进 dist chunk。
算例里的 6 个幸运值(10/60/80/170/190/75)用脚本复刻改后逻辑跑过,全部一致。
踩坑:① 本项目是 Spring Boot 4(jakarta),@PostConstruct 写成 javax. 会编译失败;
② 并行对同一文件发多个 Edit 会静默丢改动(回执 success 但没落盘,仿真脚本主循环那次白排查一轮)⇒ 改完必须 grep 复验;
③ 15 万元素数组用 Math.min(...arr) 直接 RangeError: Maximum call stack size exceeded,大数组统计先 sort 再取分位;
④ 口径类需求别用文字对齐:「幸运值」到底是「三身份各自累计、各清各的」还是「一个共享累计器、谁中奖都整体归零」,
文字上两句话都解释得通 —— 最后靠「第 5 步应该显示 190、第 6 步应该显示 75」这组数字才把模型逼出来
(若是各清各的,第 5 步应为 80;若不清零,第 6 步应为 265)。
十、上线前必做
- 清空两个已存在饰品位的旧固定区间
(
UPDATE tt_upgrade_ornaments SET luck_section = NULL, anchor_luck_section = NULL, blank_luck_section = NULL;) —— 不清的话它们仍会被旧区间夹住,仍看得见「100 以上才出」的硬底; - 水位从 0 起步,
enabled=true时前几件货会落在收紧档,属预期,需要立刻放水就在新页面注水。
2026-09-22
-
docker 配置命名统一:
changzhou→henan(全套henan_前缀)起因:
docker/下的编排文件里到处是changzhou,是从同源项目拷贝模板时的命名残留。查实本机docker volume ls/docker ps -a:从未跑过 changzhou 这套(只存在 hunan 项目的容器与卷hunan_mysql_db/hunan_redis_cache/hunan_rabbitmq_server,3 个月前创建、均已 Exited)⇒ 改名无数据迁移包袱。统一为henan全套前缀,与实跑的 hunan 命名风格对齐。改动 7 个文件:
docker/compose/docker-compose.yml——MYSQL_DATABASE/MYSQL_USER→henan;三个container_name→henan_mysql_db/henan_redis_cache/henan_rabbitmq_server;网络 →henan_app_network;external 卷 →henan_mysql_datadocker/compose/deploy.sh—— 自动建卷的卷名docker/compose/migrate-mysql-volume.sh——NEW_VOL与旧卷候选列表;docker stop henan_mysql_dbdocker/WINDOWS_QUICKSTART.md—— 卷名 / 库名 / 容器名;顺带修正 3 处本身就是错的路径d:\changzhou_app\changzhou_backend\...→D:\henan_background\henan-backend\docker\compose;第 4 节「和 chengdu 项目切换」→ hunanruoyi-admin/src/main/resources/application-dev.yml—— jdbc url 库名 +usernameruoyi-admin/src/main/resources/application-pro.yml—— 同上bin/logtool.js——-uhenan、-D henan、容器名henan_mysql_db
验证:
docker compose -f docker-compose.yml config解析通过、零报错,name段全部已生效(库henan、三个henan_*容器名、网络henan_app_network、external 卷henan_mysql_data)。零 SQL 变更、零后端代码改动。刻意没改:①
chengduhipay—— 第三方支付通道名不是地名,application-dev/pro.yml与updata_sql.md里几十处,动了支付会挂;②docs/updata_sql.md里历史条目的USE changzhou;—— 台账回改会失真,需要留痕就新加一条;③ 各target/dist*/.lststats.html—— 里面是D:/changzhou_app/...的编译期绝对路径,属产物,重新构建自然消失;④docs/DEPLOYMENT.md、docs/README.md、docs/麻将胡了设计方案.md(D:\changzhou-cocos\...是客户端仓库路径)—— 旧项目文档。上线前必做两件事:① 卷是
external: true,必须先docker volume create henan_mysql_data,否则报external volume not found直接起不来(deploy.sh up -d会自动建,手敲docker-compose up不会);② 新卷首次初始化会建出空的henan库 ——MYSQL_DATABASE/MYSQL_USER/MYSQL_PASSWORD只在数据卷首次初始化时生效,原有数据需另行导入,否则应用连上的是空库。遗留待定:网络没固定
name:,实际名会随执行目录变(在docker/compose下执行时是compose_henan_app_network)。卷已用external+ 固定name规避「换目录丢数据」,网络是否同样钉死成henan_app_network待定。 -
下架三个玩法:删除「奥林匹斯 / 荒野争锋 / 极速糖果」的后台菜单与页面
起因:这三个玩法不再需要,要求从后台左侧菜单移除。
关键前提:RuoYi 的左侧菜单是数据库驱动的(
sys_menu→/getRouters→ 前端sidebarRouters),代码里没有菜单清单,所以必须"先删数据、再删页面文件",只删前端文件的话菜单依然在。改动清单(完整 SQL 见
docs/updata_sql.md):- 数据库(库
henan,已执行):sys_menu删 22 行(3 个顶级 M + 19 个子页面 C);sys_role_menu删 22 行授权(全属role_id=103)。无'F'按钮权限行。- 3130 奥林匹斯
olympus→ 子菜单 3131~3137 - 2105 荒野争锋
bullet→ 子菜单 2106~2110、2112 - 2125 极速糖果
sweet→ 子菜单 2126~2131
- 3130 奥林匹斯
- 前端文件(38 个,删前已确认全部被 git 跟踪):
ruoyi-ui/src/views/game/{olympus,bullet,sweet}/(7+6+6)与ruoyi-ui/src/api/game/{olympus,bullet,sweet}/(7+6+6)。 - 文档:本条 +
docs/updata_sql.md新条目。
验证:
- 删除后
sys_menu/sys_role_menu命中行数均为 0(printf实测);SELECT ... WHERE parent_id = 0复查剩余 20 个一级菜单,三个玩法已消失。 - 全量搜索
game/olympus|game/bullet|game/sweet|api/game/...残留引用 = 0。 ruoyi-ui生产构建exitcode=0/DONE Build complete(删掉页面后仍能编译,说明没有别处静态引用它们)。
刻意没动:
- 后端 Java 代码。这些玩法名在后端出现在跨模块共享的文件里 ——
TtAccountRecordSource(流水来源码 60=奥林匹斯)、TtboxRecordSource、RedisConstants、MessageCategoryEnum、PromoTurnover*、ApiUserPackSackServiceImpl、WebsiteSetupServiceImpl。删它们会波及其他玩法与推广/发货链路,不在「删页面」范围内。 - 数据库业务表:
game_olympus_*/game_sweet_*等原样保留,未 TRUNCATE、未 DROP。数据还在,随时可回。 ruoyi-ui/src/views/game/sugar/(5 api + 5 页面)—— 没有任何菜单引用。菜单「极速糖果」指向的是game/sweet/*,而docs/cleanup_records.sql又把game_sugar_*标注为「极速糖果」,两套并存;后端还存在GameSugarWin/User/StepInfo/Spin实体。疑似遗留或未接入的开发分支,未确认前不删。
遗留待定:① 仪表盘「实时 RTP」卡片与「流水统计」折线里仍有 玫瑰女郎(=olympus) 一条 —— 已于下一轮处理;②
game/sugar/*是否清理待定。③ 是否需要把game_olympus_*/game_sweet_*业务表也清掉(现在只是入口没了,数据仍在库里占空间)——实查确认不存在%olympus%业务表,无需处理。 - 数据库(库
-
彻底清理「玫瑰女郎」(olympus)的后端:枚举、返利统计链路与前端列
起因:上一轮只下架了菜单与页面,后端仍保留整套 olympus 常量、统计列与返利计算。本轮要求「和后端相关的都删掉,包括返利的统计」。
顺手修掉一个构建阻塞:
skins-service/pom.xml仍声明<module>game-olympus-service</module>、ruoyi-admin/pom.xml仍依赖该 artifact,而该模块目录早已不存在 ⇒ 这是当时 Maven 构建必挂的隐藏故障(本次删掉引用后构建才通过)。全仓已无任何文件名含 olympus。改动面:2 个 pom + 21 个后端文件 + 13 个前端文件
层 文件 改动 pom skins-service/pom.xml、ruoyi-admin/pom.xml删 game-olympus-service的 module / dependency常量 TtAccountRecordSource.java删 OLYMPUS_SPIN(60)/OLYMPUS_REWARD(61)枚举项,并从 6 处集合与判断中移除(推广福利流水、返利游戏类型、今日投注、今日赢奖、流水抽奖条件);删isSpecialGameConsume/isNewGameConsumeForCommission,isGameConsumeForCommission简化为只判经典常量 TtboxRecordSource.java删 OLYMPUS_BOX(13)常量 MessageCategoryEnum.java删 GAME_OLYMPUS常量 RedisConstants.java删 3 个 OLYMPUS_*缓存键配置 ParameterSettingBody.java、WebsiteSetupServiceImpl.java删 olympusMaintenance字段与读写(数据库侧见updata_sql.md)仪表盘 TtRechargeRecordController.java折线 chartData整条roseladyList链删除;实时 RTP 的gameConfig/gameNames去掉第 8 项(保留索引 5 = Roll 房,if (i == 5)依赖它)用户报表 TtUserBlendErcashMapper.xml、TtUserSourceReportMapper.xml、TtUserAmountRecordsController.java删 olympusGame列 ×2 + 排序分支;总流水 source 列表去 60开箱统计 TtBoxRecordsMapper.xml返奖总额 source IN (...)去 13返利 PromoTurnoverMapper.xml删 todayOlympus/yesterdayOlympus/weekOlympus/totalOlympus四列、todayNewGame/yesterdayNewGame、newGameConsume×2,source 列表去 60返利 PromoTurnoverMapper.java删 getSubNewGameConsumeByDate/getSpecialGameConsumeByUserId两个专属查询返利 PromoTurnoverServiceImpl.java看板 buildPeriod去掉 olympus 周期项与 4 个调用点参数;下级佣金去掉新游戏分项返利 PromoTurnoverController.java三处 olympusConsume统计链删除;businessGameRate统一取经典费率返利 PromoAdminManageController.java删 specialGameTurnover输出与sumNewGameConsume返利 CommissionSettlementTask.java周结佣金去掉新游戏分项 其他 TtUserServiceImpl.java实时返佣分支去掉「新游戏」,只判经典 其他 ApiUserPackSackServiceImpl.java、ApiProfileRecordsServiceImpl.java、TaskUpdate.java、TtBoxServiceImpl.java映射 / 筛选集合 / 发奖公式 / 注释 前端 ruoyi-ui utils/accountSource.js、ttSetting/index.vue、ttuser/{userTurnover,userPack,creditsRecords,amountRecords,blendErcashArchive}.vue删流水来源码 60/61 与 13 的字典与筛选项、删「玫瑰女郎维护」开关、删用户流水表的 olympusGame列与排序标签前端 promo-ui2 Home、FlowRecord、BusinessManage、AnchorManage、subFlow/transaction、CommissionReport删费率卡 / 周期列 / 玫瑰女郎列 ×3 / 特殊游戏流水列 ×2 / 日特殊游戏消费列 / 来源码字典 验证(产物级断言,不是看代码):
- 后端
mvn -B clean install -DskipTests→BUILD SUCCESS,Total time: 04:13 min,无BUILD FAILURE/COMPILATION ERROR;ruoyi-admin.jar正常产出(6 个模块全链打通)。 target/classes里 5 个关键 class(TtAccountRecordSource/TtboxRecordSource/MessageCategoryEnum/RedisConstants/PromoTurnoverServiceImpl)搜OLYMPUS命中 = 0。target中 3 个 mapper XML 搜olympus/newGame/source 60命中全 0。- 数据库:
sys_config里%olympus%残留 = 0;一级菜单 20 个(与上轮一致)。 ruoyi-ui生产构建通过;promo-ui2因node_modules缺esbuild无法构建(环境问题,非本次改动),改做静态校验。
刻意保留:
- 历史数据(
tt_user_blend_ercash47 行、tt_box_records293 行、tt_user_source_report14 行)—— 属账目,未删。副作用:流水来源码字典删了,这些历史流水在后台显示不出中文名。 ruoyi-ui/src/views/index.vue的hidden数组仍留「玫瑰女郎」、.rtp-theme-4样式保留 —— 后端已不再返回,留作二层防护。newGameCommissionRate(新游戏返佣比例)字段保留 —— 它挂在tt_user表上,属用户配置字段而非统计,删除涉及 DDL 与用户管理页,且语义上不点名玫瑰女郎。现状:无任何消费来源指向它,相关分项恒为 0。
- 后端
-
新增「麻将胡了」玩法:以「新游戏」类型接入,复用原玫瑰女郎的整套统计口径
起因:玫瑰女郎下架后,用户引入新游戏「麻将胡了」(后端
mahjong玩法),要求「以新游戏的类型把相应的统计都加上」。 本轮是上一轮删除动作的完整镜像 —— 把上一轮删掉的 olympus 分支,按 mahjong 重新接回全部统计链路。口径映射(沿用原语义,未改结构):
概念 olympus 旧值 mahjong 新值 消费来源码 OLYMPUS_SPIN = 60MAHJONG_SPIN = 68奖励来源码 OLYMPUS_REWARD = 61MAHJONG_REWARD = 69开箱产出码 TtboxRecordSource13MAHJONG_BOX = 15新游戏返佣费率 tt_user.newGameCommissionRate同名沿用(未动 DDL) 返佣流水来源 GAME_CONSUME_COMMISSION(47)「特殊游戏返利」同 改动清单(后端 21 个文件 + 前端 7 个 + DB 3 张表):
层 文件 改了什么 枚举 TtAccountRecordSource/TtboxRecordSource/MessageCategoryEnum/RedisConstants新增 68 / 69 / 15 及消息分类、缓存键 仪表盘 TtRechargeRecordControllerchartData加mahjongList;realTimeRtp加第 8 项「麻将胡了」(追加在末尾,不动i == 5的 Roll 房下标)开箱统计 TtBoxRecordsMapper.xml3 处返奖口径 source IN加15用户报表 TtUserBlendErcashMapper.xml/TtUserSourceReportMapper.xml/TtUserAmountRecordsController加 mahjongGame列 ×2 + 排序分支 +AGG_SORT_FIELDS返利 PromoTurnoverMapper.xmlgetBatchedCommissionData/getGameCommissionData/getWeeklyCommissionDataWithSnapshot/getWeeklyDailyCommissionData/businessGameConsumeRows全部加 68 与 newGame/mahjong 列返利 PromoTurnoverServiceImpl/PromoTurnoverController/PromoAdminManageController/CommissionSettlementTask看板费率卡与周期项、三处 *Consume统计链、businessGameRate新游戏分支、specialGameTurnover、sumNewGameConsume、周结分项其他 TtUserServiceImpl(实时返佣新游戏分支)、ApiProfileRecordsServiceImpl(69 / 68)、ApiUserPackSackServiceImpl(15 → 69 映射)、TaskUpdate(特殊游戏消费 + 0.01 系数)、TtBoxServiceImpl(注释)配置 ParameterSettingBody+WebsiteSetupServiceImplmahjongMaintenance读写(键mahjongMaintenance,兜底"0")前端 ruoyi-ui views/index.vue(折线加「麻将胡了流水」)、utils/accountSource.js(68 / 69)、skins/ttSetting/index.vue(麻将胡了维护)、skins/ttuser/{amountRecords,creditsRecords,blendErcashArchive,userPack,userTurnover}来源码字典与筛选 68 / 69、开箱码 15、 mahjongGame列与排序标签前端 promo-ui2 Home(费率卡 + 周期列)、FlowRecord(麻将胡了列 ×2)、BusinessManage/AnchorManage(特殊游戏流水列)、subFlow/transaction(日特殊游戏消费)、CommissionReport(68 → 麻将胡了)数据库:
sys_menu+8(麻将胡了目录3200+ 7 子页)、sys_role_menu+8(role 103)、sys_config+1(mahjongMaintenance)。完整 SQL 见docs/updata_sql.md。验证(产物级断言):
- 后端
mvn -B clean install -DskipTests→BUILD SUCCESS,19 个模块全过,Total time: 03:55 min。 ruoyi-ui生产构建 →DONE Build complete。promo-ui2生产构建 → 本轮把环境修好了,构建通过(修法见「踩坑」)。- 数据库实测:
sys_menu3200~3207 = 8 行、sys_role_menu= 8 行、sys_config.mahjongMaintenance= 1 行。
踩坑(记一次,下次别重踩):
promo-ui2/node_modules是残缺的扁平布局 —— 绝大多数包只在node_modules/.pnpm/node_modules/里有,顶层没有软链 ⇒ vite 一启动就ERR_MODULE_NOT_FOUND(先esbuild、再@rollup/rollup-win32-x64-msvc、再@babel/core…… 逐个补是打地鼠)。 一次性修法:把.pnpm/node_modules下所有包(含@scope子目录)在顶层批量建 junction (本次补了 800+ 个链接)后构建即通过。根因是当初pnpm install没跑完/被裁过,属环境问题、非代码问题。刻意未做:麻将胡了的 11 张
game_mahjong_*游戏表尚未落库(DDL + 种子在mahjong-py/schema.sql)。 这 7 个新菜单页面依赖它们,未建表前打开会报「表不存在」。属「游戏本体」前置依赖、且 generator 还没写完 (见mahjong-py/README.md),待用户确认后再执行。 - 后端
-
后台管理端(
ruoyi-ui)整站换肤:Cottagecore 田园核风 + 逐页 UI 细节收敛起因:管理后台长期沿用它默认的企业蓝(Element 默认色板 + vue-element-admin / AdminLTE 遗留色),与河南项目的 田园调性不符;用户要求按 Cottagecore 规范整站重做,并逐页检查小问题、把 UI 细节调到位。
做法:不引 Tailwind,走「Element 变量 + 皮肤覆盖层」 (
ruoyi-ui是 Vue2 + Element UI 2.15 + vue-cli4/webpack4/postcss7,Tailwind 的 preflight 会打乱 Element 基础样式)。 新建src/assets/styles/cottagecore.scss作为全站唯一色源,其余文件一律引用变量、不写死十六进制; 再通过重编译element-ui的 theme-chalk 源码(element-variables.scss)换掉 Element 自己的底色。文件 角色 assets/styles/cottagecore.scss唯一色源:品牌色 + 组件槽位色 + 图标色相 + 对比度实测注释 assets/styles/element-variables.scss覆盖 $--color-*/ 圆角 / 阴影 /$--color-white→ 暖白#fffdfa,再@importtheme-chalk 源码整包编译assets/styles/skin.scss皮肤覆盖层(按钮 / 表单 / 表格 / 分页 / 弹层 / Element 写死的冷色) assets/styles/index.scss总入口,最后一行 @import './skin.scss'(整体回滚 = 注释这一行)assets/styles/sidebar.scss/variables.scss侧栏(4 色图标轮换)/ 菜单深浅两套色 + :export给 js六轮递进,要点:
- 建色源 + 换 Element 底色:primary 定陶土
#9a5c35(不用草绿,否则「新增 / 修改」同色系分不清; 想切回只改色源一处),success 草绿#457450,warning 深雏菊金#8f6418,danger 莓红#a4485a,info 暖褐#6b6152。 正文#5f4f3d(#8b7355只有 4.17:1,禁止做正文);草绿#5a8f5a白字仅 3.81:1 ⇒ 只做装饰与大色块, 绿色文字一律#3f6b45。 - 摘掉隐藏的 ThemePicker:
App.vue曾无条件挂<theme-picker>(只用 CSSdisplay:none藏,组件照样 mount), 它在created()里会去unpkg.com拉整包 Element 默认主题并注入<style>把配色改回企业蓝。已移除,别再挂回去。 - 全局色板机械清扫(45 个文件):把散在业务页里的旧 Element 色(
#409eff/#67c23a/#e6a23c/#f56c6c/#909399…) 按语义映射到色源;同时改掉Logo.vue/Navbar.vue的白色毛玻璃 + 蓝阴影、全局滚动条的冷蓝。 - 顶栏与标签页合并成一条,去掉重复显示的面包屑。
- Element 内部写死的冷色(hex 搜索抓不到、只能用更高权重规则盖):白遮罩、蓝色 info toast、
冷绿上传成功勾
#13ce66(它的选择器是带父级的三段式 0,2,0,单类选择器会被压住)、冷灰滚动条 / 标签 / 日期面板。 根因是$--color-white→ 把暖白#fffdfa提为「白」,一次性统一所有白色面。 - 操作列按钮折行(用户截图里那类问题)——根因不是列宽不够,而是
element-ui.scss里一条全局规则.fixed-width .el-button--mini { width: 60px }把按钮钉死 60px,图标 + 2~4 个中文直接被截断换行。 删掉该规则 + 给 19 个操作列补显式宽度(≥3 个按钮的收进「更多」下拉)+ 补下拉与前钮间距。
验证(三层,脚本留在
.workbuddy/,可重跑):node .workbuddy/audit-palette.cjs—— 源码级色板审计(.vue+.scss+.css,先剥注释再扫): 当前 0 / 184 文件离版。色板白名单里另有一类刻意保留的域内语义色: CS2 稀有度、麻将/饰品品质色谱(金/红/紫)、仪表盘折线色 —— 不是主题色,不许改。node .workbuddy/verify-dist.cjs—— 构建产物断言,34 项 ALL_OK:① 本项目源码的冷色必须为 0; ② 新增规则必须存在;③ 中性色(rgba(0,0,0,*))设「冻结基线」上限防复发。 扫描范围是 app.css + 全部chunk-*.css(views/* 是懒加载,只看 app.css 会整片漏掉)。node .workbuddy/verify-computed.cjs—— 真机 Chrome 计算样式断言,20 项 ALL_OK。 专治 element-ui 内部字面量色:这些色值在样式表文本里去不掉,grep = 0不成立,必须看计算值。- 构建:
DONE Build complete,零 Sass 错误;仅剩既有的delLevel缺失导出 + 体积警告,与本改动无关。
踩坑:并行对同一文件发多个 Edit 会撞
EBUSY;更阴的是偶发不报错、但两次写入只剩一次(本次sidebar.scss丢过两个替换) ⇒ 机械替换一律串行,改完用 grep 逐条复验,别信 Edit 的 success 回执。刻意未做:①
variables.scss里sideTheme: theme-dark的深色侧栏回退色(冷蓝灰)保留 —— 设置抽屉里的「深色」按钮现在点下去视觉上是空操作(皮肤层已把侧栏壳设成透明 + 文字!important), 没有删按钮也没改回退色,要不要彻底下掉这个开关待定;②btn.scss的.blue-btn等 7 个类全项目 0 引用, 但其颜色已随variables.scss指向新色板,不构成视觉问题,未清理。 - 建色源 + 换 Element 底色:primary 定陶土