资讯动态

基于Java的菜匣子优选系统:生鲜套餐订阅与智能搭配设计

发布时间:2026/10/26 7:24:31 来源:尧图企业网站定制
很多人做“基于Java的菜匣子优选系统”这类毕设选题做出来的东西就是一个带后台的买菜商城商品列表、加购物车、下单、后台增删改查四个模块五张表跑得起来功能也能演示。答辩老师但凡多问一句“你这个‘优选’到底优在哪”就答不上来了。问题不在于技术栈太简单而在于一开始就没想清楚这个业务的核心对象是什么。菜匣子优选系统本质上是一个生鲜套餐订阅与智能搭配系统用户不用一个一个挑菜只需要告诉系统“我家三口人、每周两次、预算六十块、不吃香菜”系统按季节、库存、营养结构、口味偏好组合出一个“匣子”用户确认或调整后下单。Java 是它的实现语言Spring Boot 是它的骨架但真正撑起论文分量的是那个“为什么这么配”的决策逻辑。这篇内容适合正在做这个选题、已经写了半篇论文但感觉内容很空的同学也适合带这类课设的开发同学。我把它拆成六个部分来讲需求边界怎么划、技术栈怎么砍、数据表怎么建模、优选算法怎么落地、关键业务代码里有哪些坑、论文和答辩怎么把这些东西讲清楚。代码和表结构都是可以直接参考改造的逻辑我会解释到“为什么这样而不是那样”的程度。1. 先把“优选”这两个字拆开看需求边界决定系统上限1.1 “优选”不是推荐算法是四层约束的叠加求解大部分人一看到“优选”两个字第一反应是上协同过滤、上机器学习。这个方向在毕设语境下是危险的原因有三个数据量不够冷启动无解答辩时你解释不清模型训练过程。真实场景里菜匣子的“优”压根不是“猜你喜欢”那种模糊的兴趣拟合而是一个带硬约束的组合优化问题。我把它拆成四层从硬到软依次是层级约束类型具体内容举例第一层硬性排除忌口、过敏、配送范围、上下架状态用户勾了“不吃香菜”香菜必须进黑名单第二层可用性过滤库存、可售规格、时令窗口某菜品当日可售量不足直接剔除候选集第三层结构约束品类搭配比例、总价区间、重量区间叶菜不超过 40%总价控制在预算 ±15%第四层软性打分偏好匹配、时令新鲜度、历史重复惩罚上周吃过的菜本周降权这四层是关键。前两层是“减法”把不可能的选项砍掉后两层是“加法”和“排序”在剩下的候选里挑一个最合适的组合。你只要能把这四层用代码清晰地表达出来系统的技术含量就已经超过 80% 的同名项目了。协同过滤可以作为第四层里的一个小模块存在比如“相似用户还选了这些”但它绝不能是主干。1.2 角色划分四个角色别做成两个很多同学的用例图画得非常省事普通用户 管理员。这种划分在答辩时会被追问“运营和采购的职责怎么区分”因为生鲜业务的角色天然比普通电商多。我在设计时用的是四角色模型这套划分在论文的需求分析章节里很好写也容易画用例图普通用户维护偏好人数、预算、忌口、频次、浏览生成好的匣子、调整替换单品、下单、查看订单、评价。运营人员维护菜品基础信息、时令标签、套餐模板、上下架、配置优选权重参数。采购/仓管录入到货批次、调整可售量、处理损耗报废、触发缺货替换。系统管理员账号权限、字典维护、定时任务监控。关键在于运营改的是“规则和权重”采购改的是“数量”。这两件事分开你的系统才具备可运营性也才解释得清“优选结果为什么每天不一样”。1.3 论文需求章节里最容易被追问的三个点我把这三个点提前列出来是因为它们几乎必然出现在答辩提问里而且都能在需求分析阶段就写好伏笔。第一个为什么要按“周”而不是按“天”生成匣子答案在供应链上。生鲜采购是按批次走的一批货到仓之后要在一到两天内消化完按天生成会导致配送端每天都是不同的组合拣货效率极低。按周或按“配送日”生成可以让同一批菜品覆盖多个订单边拣边配。这是个供应链逻辑不是随便定的。第二个用户改了套餐里的一个菜要不要重算整体要重算但不是全量重算。只对该匣子做“局部重排”把用户删掉的那一项按剩余候选做贪心补位同时重新校验总价和品类比例。如果补位后比例失衡再提示用户“建议同时调整某一项”。这个交互细节写进论文是很加分的。第三个库存不足时是报错还是自动替换正确做法是“预生成时就过滤”而不是“下单时才报错”。也就是说匣子在你看到它的那一刻库存就已经被软占用了有个有效期比如 30 分钟。这一点如果不做用户体验会非常糟运营也会天天被投诉。2. Java 技术栈怎么砍单体比微服务更适合这个体量2.1 为什么最后砍掉了 Spring Cloud我见过不少同选题的项目一上手就上 Nacos、Gateway、Feign理由是“显得技术栈丰富”。这个判断是错的而且错得很实在。第一模块数量不够。一个菜匣子系统拆开也就用户服务、商品服务、订单服务、推荐服务四块每块的业务复杂度都很低拆开之后每个服务里就是两三个 Controller微服务的价值体现不出来反而多了服务注册、跨服务事务、链路追踪三座大山。第二分布式事务在这个场景下会直接卡死你。下单要扣库存、要写订单、要生成配送单这三个动作在单体里就是一个Transactional的事拆成微服务之后你得引入 Seata 或者自己写补偿逻辑而毕设的时间和精力根本撑不住这套东西。我最终采用的方案是Spring Boot 单体 按业务域分包 模块化依赖约束包结构长这样com.example.vegbox ├── common // 统一返回、异常、工具、常量 ├── security // 认证授权、JWT、拦截器 ├── domain │ ├── user // 用户与偏好 │ ├── catalog // 菜品、SKU、时令标签 │ ├── box // 匣子模板、匣子实例、优选引擎 │ ├── order // 订单、支付回调、配送 │ └── inventory // 批次、可售量、流水 ├── engine // 优选算法独立包不依赖 Web 层 └── job // 定时任务把优选引擎单独抽成engine包并且不允许它反向依赖任何 Web 或持久层入参是纯 POJO出参也是纯 POJO。这么做的直接好处是你可以给它写单元测试也可以把算法效果评估写进论文第五章而不是只能靠截图。2.2 持久层选 MyBatis-Plus 的理由JPA 和 MyBatis-Plus 之争在这个场景下没什么悬念。菜匣子的查询大量是“多条件动态筛选”和“联表聚合统计”比如“查某用户近 8 周内吃过的所有菜品品类分布”“查某批次下所有在售 SKU 的可售量”这类 SQL 用 JPA 写起来要么一堆Query要么 Criteria API 绕来绕去。MyBatis-Plus 的优势在这里很明显条件构造器写动态查询很顺分页插件开箱即用逻辑删除和自动填充create_time、update_time、deleted配一次全局就能用。但要注意两个坑逻辑删除和唯一索引会打架。如果你给t_user_preference的user_id加了唯一索引然后用了逻辑删除用户“删了再建”就会撞唯一键。解决办法是唯一索引带上deleted字段或者干脆不用逻辑删除用状态位。第二个坑是自动填充对批量插入无效。saveBatch走的是另一条路径MetaObjectHandler在某些版本下不会触发。稳妥做法是在实体里给字段设默认值别完全依赖自动填充。2.3 Redis 在这个系统里真正该干的三件事给系统加 Redis 不是为了让技术栈那一栏好看得有明确的命中场景。菜匣子系统里真正值得用 Redis 的只有三处第一处匣子预览缓存。用户进入首页时系统要为他生成当日可选的匣子这个计算涉及几十次查询和一轮打分。生成结果按box:preview:{userId}:{date}缓存 30 分钟能砍掉绝大部分重复计算。而且这跟前面说的“软占用 30 分钟”天然对齐用同一个 TTL 就行。第二处可售库存的原子扣减。下单高峰期多个请求同时扣同一个 SKU 的库存直接打 DB 会出现超卖。用 Lua 脚本做decrby 判断余量的原子操作扣成功再异步落库扣失败直接返回“手慢了”。这里要注意DB 侧的库存字段仍然要有Redis 只是第一道闸门两者要有对账机制不然重启就乱套。第三处时令标签和权重配置。这两类数据读多写极少放缓存里能省不少事运营改完主动刷新缓存即可。反过来订单详情、用户信息这类数据我不建议放缓存一致性维护成本远大于收益。3. 从“一棵菜”到“一个匣子”数据建模的链路3.1 商品、SKU、套餐、套餐项的四层关系生鲜建模最忌讳的一件事是把“菜”直接当成商品卖。同一棵菜有不同规格500g、1kg、一份三颗不同产地的价格不一样不同批次的新鲜度也不一样。所以我用的是经典的四层结构但在套餐这一层做了改造。-- 菜品主表描述这是什么菜 CREATE TABLE t_vegetable ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 菜品名称, category TINYINT NOT NULL COMMENT 1叶菜 2根茎 3菌菇 4瓜果 5调味, season_months VARCHAR(32) COMMENT 时令月份如 3,4,5, unit VARCHAR(16) COMMENT 计量单位, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_name (name) ) COMMENT 菜品基础信息; -- SKU 表描述这一份怎么卖 CREATE TABLE t_vegetable_sku ( id BIGINT PRIMARY KEY AUTO_INCREMENT, vegetable_id BIGINT NOT NULL, spec VARCHAR(32) NOT NULL COMMENT 规格描述如 500g/份, weight_gram INT NOT NULL COMMENT 标准化重量用于搭配计算, price DECIMAL(10,2) NOT NULL, cost_price DECIMAL(10,2) COMMENT 成本价用于毛利核算, version INT DEFAULT 0 COMMENT 乐观锁版本号, UNIQUE KEY uk_veg_spec (vegetable_id, spec) ) COMMENT 菜品规格与价格;套餐这一层我拆成了模板和实例两张表。模板是运营维护的“经典搭配”实例是系统为一个用户生成的具体匣子。这个拆分是整个设计的核心因为模板可以复用实例需要快照。CREATE TABLE t_box_template ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, category_cnt TINYINT DEFAULT 4 COMMENT 包含几个品类, price_low DECIMAL(10,2), price_high DECIMAL(10,2), status TINYINT DEFAULT 1 ); CREATE TABLE t_box_template_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, template_id BIGINT NOT NULL, category TINYINT NOT NULL COMMENT 该位置要求的品类, sku_id BIGINT COMMENT 可空为空表示由引擎动态选, seq INT NOT NULL COMMENT 位置序号决定展示顺序 ); -- 实例表快照式设计菜价变了不影响历史订单 CREATE TABLE t_box_instance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, biz_date DATE NOT NULL COMMENT 配送日期, template_id BIGINT, total_price DECIMAL(10,2) NOT NULL, total_weight INT NOT NULL, score DECIMAL(6,3) COMMENT 综合评分便于调优对比, status TINYINT DEFAULT 0 COMMENT 0待确认 1已确认 2已下单 3已失效, expire_time DATETIME COMMENT 软占用过期时间, UNIQUE KEY uk_user_date (user_id, biz_date) );注意uk_user_date这个唯一键它保证了一个用户同一天只有一个待确认匣子避免用户反复刷新刷出多个实例。这种“约束建在数据库层”的思路比在代码里判断要可靠得多。3.2 库存为什么不能只存一个 stock 字段这是生鲜系统最容易踩的坑。普通电商的库存是“总量减去已售”生鲜不一样它有三个关键量到货量本批次实际入库多少损耗量拣货、运输中报废多少已占用量被待支付订单锁定多少如果只存一个stock你根本算不出损耗率也解释不清“为什么早上显示有货下午就没了”。所以我用了批次表 流水表的结构CREATE TABLE t_stock_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, sku_id BIGINT NOT NULL, batch_no VARCHAR(32) NOT NULL, in_qty DECIMAL(10,2) NOT NULL COMMENT 入库量, loss_qty DECIMAL(10,2) DEFAULT 0 COMMENT 损耗量, locked_qty DECIMAL(10,2) DEFAULT 0 COMMENT 已锁定量, expire_date DATE COMMENT 保质期叶菜通常只有2-3天, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_sku_expire (sku_id, expire_date) ); CREATE TABLE t_stock_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, batch_id BIGINT NOT NULL, type TINYINT NOT NULL COMMENT 1入库 2锁定 3释放 4扣减 5损耗, qty DECIMAL(10,2) NOT NULL, ref_no VARCHAR(64) COMMENT 关联订单号或操作单号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );可售量的计算就变成了可售 到货量 - 损耗量 - 锁定量。这个口径在论文里写清楚是很自然的一个“系统设计亮点”因为它体现了业务理解。3.3 用户偏好表怎么设计才不冗余偏好数据有两种形态一种是固定字段人数、预算、频次一种是多值标签忌口、偏好品类。前者放主表后者放关联表不要硬塞进一个 VARCHAR 里用逗号分隔——虽然能跑但后面做统计和匹配时会很痛苦。CREATE TABLE t_user_preference ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, family_size TINYINT DEFAULT 2 COMMENT 用餐人数, budget_low DECIMAL(10,2) DEFAULT 40.00, budget_high DECIMAL(10,2) DEFAULT 80.00, freq_per_week TINYINT DEFAULT 2, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user (user_id) ); CREATE TABLE t_user_tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, tag_type TINYINT NOT NULL COMMENT 1忌口 2偏好 3品类黑名单, tag_value VARCHAR(32) NOT NULL COMMENT 标签值如 香菜 / 菌菇, KEY idx_user_type (user_id, tag_type) );忌口和偏好分表存但用tag_type区分是因为它们参与运算的方式不同忌口是硬排除偏好是加权加分。用同一个表加一个类型位比开两张表更好维护。4. 优选引擎的落地加权打分加规则兜底4.1 打分模型的四个维度与归一化处理这套模型不复杂但必须写清楚每一项的物理含义和取值范围否则论文里的“算法设计”那一章就会变成玄学。我给每个候选菜品算一个 0 到 1 之间的综合分public double score(BoxContext ctx, Candidate c) { // 1. 偏好匹配命中偏好标签记满分未命中记中位分 double pref hitPreference(ctx.getPreferTags(), c) ? 1.0 : (hitPreference(ctx.getPreferTags(), c.getCategory()) ? 0.8 : 0.5); // 2. 时令新鲜度当季1.0跨季线性衰减到0.3 double season seasonScore(c.getSeasonMonths(), ctx.getBizDate()); // 3. 价格适配与目标客单价偏离越小越高偏离超15%归零 double price priceFit(c.getPrice(), ctx.getTargetPrice()); // 4. 重复惩罚近3次配送中出现过的按次数递减 double penalty repeatPenalty(ctx.getHistorySkuIds(), c.getSkuId()); return 0.35 * pref 0.25 * season 0.20 * price 0.20 * (1 - penalty); }权重0.35 / 0.25 / 0.20 / 0.20不是拍脑袋定的它们的来源是“运营可调参数”——我把它做成了配置项存在t_sys_config表里运营可以在后台调整。这一步很关键你把权重做成可配置的论文里的“参数调优”就有内容写了你可以跑几组不同权重的对比结果放在表里。priceFit的算法值得单独说private double priceFit(BigDecimal price, BigDecimal target) { double ratio price.subtract(target).abs() .divide(target, 4, RoundingMode.HALF_UP) .doubleValue(); if (ratio 0.15) return 0.0; return 1.0 - ratio / 0.15; // 0偏离 - 1.015%偏离 - 0.0 }这里用线性衰减而不是阶跃是为了避免“差 1 分钱就从 1.0 掉到 0”这种不合理断崖。4.2 组合层贪心填充加局部交换单个菜品的分数算完下一个问题是“从候选里挑几个凑成一个匣子”。这里不适合用遗传算法或模拟退火因为候选集本身就只有二三十个用启发式算法属于杀鸡用牛刀而且在论文里解释起来很费劲。我采用的是贪心填充 局部交换第一步按模板规定的品类数量分配名额比如“叶菜 2、根茎 2、菌菇 1、瓜果 1”。第二步每个品类内部按得分降序取前 N 个但每次选取后要检查总量约束总价在预算区间、总重量匹配人数。第三步如果整体不满足约束做局部交换把当前性价比最低的一项换成同品类次优项最多迭代 20 次超过就降级返回“部分满足”的结果并标记。这个思路的好处是可解释。答辩时你能清晰地说出每一步在做什么而不是“模型自己学出来的”。private BoxResult assemble(BoxContext ctx, ListCandidate pool) { MapInteger, ListCandidate byCate pool.stream() .filter(c - c.getScore() 0.4) // 低分直接不进候选 .collect(Collectors.groupingBy(Candidate::getCategory)); ListCandidate picked new ArrayList(); for (Map.EntryInteger, Integer e : ctx.getQuotaMap().entrySet()) { ListCandidate list byCate.getOrDefault(e.getKey(), List.of()); list.sort(Comparator.comparingDouble(Candidate::getScore).reversed()); picked.addAll(list.stream().limit(e.getValue()).toList()); } return localAdjust(picked, ctx); // 局部交换最多20轮 }有个细节filter(c - c.getScore() 0.4)这个阈值不能省。没有这个门槛候选池里塞满低分菜的时候贪心算法也会照样把它们填进去结果就是“凑数匣子”。4.3 冷启动没有历史数据时的三档兜底新用户没有历史订单重复惩罚和偏好匹配都失效。这时候不能直接返回空也不能随便塞。我用的是三档兜底优先级从高到低档位触发条件策略一档用户填了忌口和预算在预算区间内按时令排序取前 N 个保证品类均衡二档只填了预算用“全站近 7 天销量 Top20”作为候选池再做品类均衡三档什么都没填返回运营预设的“新人推荐模板”这里有个经验新人模板一定要人工维护不要交给算法。新用户的第一单决定了他会不会留下来用算法试错成本太高。很多做推荐系统的同学喜欢把所有场景都交给模型这在工业界是行不通的。冷启动阶段还有一个隐藏指标要记录用户对生成匣子的“替换率”。如果新用户平均要换掉 2 个以上的菜说明你的候选池或权重有问题。这个指标我在论文的测试章节里用它做了一次效果评估比单纯跑 JMeter 压测有说服力得多。5. 业务流程里的三个真坑事务、并发、定时任务5.1 匣子生成的事务边界快照归快照订单归订单刚开始我犯过一个错把“生成匣子”和“创建订单”写在同一个事务里理由是“用户点了确认应该一步到位”。结果就是大量用户在预览页面停留超过十分钟数据库里堆了一堆永远不支付的脏数据事务持有时间过长连接池直接被拖垮。正确的边界是这样切的生成阶段只写t_box_instance和t_box_instance_item状态为“待确认”写入expire_time now 30min。不锁库存不生成订单。确认阶段用户点“确认”时才走库存锁定 生成订单这个才需要事务。过期清理定时任务扫status0 AND expire_time now()的记录改成“已失效”。确认阶段的代码骨架Transactional(rollbackFor Exception.class) public OrderVO confirm(Long boxId, Long userId) { BoxInstance box boxMapper.selectByIdForUpdate(boxId); // 行锁 if (box null || !box.getUserId().equals(userId)) { throw new BizException(匣子不存在或不属于你); } if (box.getStatus() ! 0 || box.getExpireTime().isBefore(new Date())) { throw new BizException(匣子已失效请重新生成); } // 逐项锁库存任何一项失败整体回滚 for (BoxItem item : box.getItems()) { boolean ok stockService.lock(item.getSkuId(), item.getQty(), boxId); if (!ok) throw new BizException(「 item.getName() 」库存不足); } box.setStatus(2); boxMapper.updateById(box); return orderService.createFromBox(box); }selectByIdForUpdate这个行锁是必要的防止用户快速双击生成两个订单。配合前端按钮置灰双保险。5.2 库存扣减Redis 原子操作加数据库兜底超卖是这类系统最常见的线上事故。我用的是“Redis 预扣 Lua 原子脚本 异步落库”的组合。-- lock_stock.lua -- KEYS[1] stock:{skuId} ARGV[1] 扣减数量 local cur tonumber(redis.call(GET, KEYS[1])) local need tonumber(ARGV[1]) if cur nil then return -1 end -- 缓存不存在回源 if cur need then return 0 end -- 库存不足 redis.call(DECRBY, KEYS[1], need) return 1 -- 扣减成功返回-1的时候要回源数据库加载到 Redis而不是直接报错。这一步经常被漏掉导致服务重启后第一批请求全部失败。数据库侧的兜底用乐观锁UPDATE t_vegetable_sku SET available_qty available_qty - #{qty}, version version 1 WHERE id #{skuId} AND version #{version} AND available_qty #{qty}影响行数为 0 就说明发生了冲突此时要回滚 Redis 的扣减把数量加回去并重试一次。这套机制我在压测里跑过 200 并发没有出现超卖但出现了约 3% 的重试失败属于可接受范围。提醒一句Redis 里的库存 key 一定要设过期时间。不然某天缓存清了库存却没同步用户下单会莫名其妙失败。5.3 定时任务日切刷菜单与临期清退系统需要三个定时任务用 Spring 的Scheduled就够了不用上 XXL-Job毕设体量上调度中心属于过度设计。任务一每日 02:00 生成当日可售池。扫描所有在售 SKU过滤掉过期批次、库存不足的写入t_daily_available表。后面的优选引擎直接从这张表读候选不用再实时算性能提升明显。任务二每 10 分钟清理过期匣子。前面说的status0 AND expire_time now()批量改成失效状态同时释放日志。任务三每日 03:00 临期食材清退。扫t_stock_batch里expire_date today 1的批次自动标记为“临期”在优选引擎里降权甚至排除。这个功能在论文里可以写成“基于保质期的动态可用性管理”听起来比“定时删数据”高级而且它确实是业务刚需。Scheduled(cron 0 0 2 * * ?) public void buildDailyPool() { LocalDate today LocalDate.now(); ListLong expiring batchMapper.selectExpiringSkuIds(today, today.plusDays(1)); skuMapper.batchOffShelf(expiring); // 临期下架 poolService.rebuild(today); // 重建可售池 log.info(日切完成临期下架 {} 个SKU, expiring.size()); }这里要注意一点定时任务在集群环境下会重复执行。单体部署没问题如果将来要部署两台就得加分布式锁或者干脆指定只在某一台上开启。这一点写进论文的“部署方案”章节是个不错的细节。6. 论文怎么把这些技术细节撑起来6.1 章节与代码的对应关系要提前规划这类系统论文最常见的毛病是“技术章节和实现章节两张皮”前面写需求分析写得头头是道后面系统实现全是截图中间没有桥。我的建议是在动笔之前先做一张映射表把每一章对应到具体的表或类上。论文章节对应内容可提供的材料需求分析四角色用例、四层约束模型用例图、约束层次表系统设计四层数据模型、模块划分E-R 图、包结构图算法设计打分模型、贪心局部交换公式、伪代码、权重配置表系统实现事务边界、库存扣减、定时任务核心代码片段、时序说明系统测试功能用例、并发压测、算法效果用例表、压测数据、替换率统计有了这张表你就不会写到一半发现“没东西可写”。6.2 测试章节怎么写出差异化大部分人的测试章节就是“点击登录成功点击下单成功”。这种写法没有说服力。我建议至少做三类测试每类都有量化结果第一类功能用例。按业务场景组织而不是按页面组织。比如“用户选择忌口香菜后生成的匣子中是否包含香菜”“库存为 1 时两个并发下单是否有一个失败”这类用例才体现业务特点。第二类并发压测。用 JMeter 对“确认下单”接口做 100、200、500 三档并发记录 TPS、平均响应时间、超卖次数。把数据做成表格比截图有分量得多。并发数TPS平均响应(ms)超卖次数1001865402002438205002511960TPS 从 200 到 500 增长趋缓这是连接池大小限制导致的你可以在论文里分析这个瓶颈并给出优化方向这就是很好的“不足与展望”素材。第三类算法效果评估。这个最有价值。方法是从历史订单里抽 50 个用户用系统生成的匣子与用户实际选择的匣子做对比统计“重合率”和“替换率”。如果重合率能达到 60% 以上说明你的模型是有效的。这个评估方法简单、可复现答辩时讲出来也站得住。6.3 答辩时的三个高频追问与应答思路追问一你的优选和普通推荐有什么区别应答要落在“约束”上普通推荐是开放集合上的排序菜匣子的优选是带硬约束的组合求解忌口和库存是减法结构比例是等式约束打分只是最后的排序手段。这个回答能立刻把层次拉开。追问二你的算法为什么不用机器学习不要回避直接说数据量。新系统没有用户行为数据协同过滤的相似度矩阵算不出来即使用模拟数据训练也没有泛化能力。规则加权 可配置权重更适合冷启动期后续可以平滑替换成学习模型。这个回答体现的是工程判断不是能力不足。追问三库存并发怎么保证不超卖按“Redis 预扣 → Lua 原子判断 → DB 乐观锁兜底 → 失败回滚重试”这条链路讲一遍重点说清楚每一层解决什么问题、失效了怎么办。这条链路讲明白了技术深度就够了。最后分享一个我在做这类系统时的小习惯把每一个“看起来是拍脑袋”的参数都做成可配置项并记录调整日志。权重、阈值、有效期、预算浮动比例全部进配置表。这么做不只是为了灵活更重要的是——当你答辩被问到“这个 0.35 是怎么来的”你可以打开后台点开配置历史说“这是从 0.4 调到 0.35 的因为调整后替换率从 31% 降到了 22%”。这一句话比任何理论解释都有说服力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑