# BUFF 选品第二步:利润判断与选中饰品落库 ## 1. 目标与前置 - **前置**:第一步已得到「初始化要筛选的饰品 ID 集合」,并已做应用层乱序(见 [buff_selection_filter_logic.md](./buff_selection_filter_logic.md))。 - **第二步**:按乱序后的 ID 顺序,**逐个**判断每个饰品是否满足利润要求;满足则写入 `buff_selection_request_item`,并参与「每日选品条数」与「本次选品数量」限制,直到达到用户配置的选品数量或所有 ID 处理完毕。 --- ## 2. 单饰品判断流程(概要) 对每个饰品 ID 依次执行: 1. 取**当前在售最低价格**(视图一条即可)。 2. 取该饰品的**磨损区间列表**(最小磨损值、最大磨损值、区间底价),按最小磨损值升序。 3. 从前往后筛出**正常磨损区间**:随最小磨损值递增,区间底价必须**单调不升**;一旦某区间底价高于前一个,则该区间及之后全部舍弃。 4. 用**比较价** = 当前在售最低价格 × (1 + 用户利润率) 与上述正常区间的区间底价比较,只保留**区间底价 > 比较价**的区间;若存在至少一个满足的区间,则该饰品**被选中**。 5. 被选中时:从视图取展示与链接字段,按规则算**价格(人民币)**、**价格(美元)**和**区间**,写入 `buff_selection_request_item`。 6. **选品日志**:每判断完一个饰品,向 `buff_selection_request_log` 写入一条记录,记录该饰品的判断结果、判断时间等,供前端展示「后台在逐个判断」的过程感(详见第 9 节)。 --- ## 3. 单饰品判断(详细) ### 3.1 获取饰品最低价 ```sql SELECT DISTINCT `当前在售最低价格` FROM buff_goods_filtered_view WHERE `BUFF饰品ID` = :goods_id; ``` 得到该饰品的**当前在售最低价格**(下称「最低价」)。 ### 3.2 获取磨损区间并排序 ```sql SELECT `最小磨损值`, `最大磨损值`, `区间底价` FROM buff_goods_filtered_view WHERE `BUFF饰品ID` = :goods_id ORDER BY `最小磨损值` ASC; ``` 得到多行:每行一个磨损区间(最小磨损值、最大磨损值、区间底价),按最小磨损值升序。 ### 3.3 正常磨损区间(区间底价单调不升) - 按**最小磨损值升序**依次看**区间底价**。 - 要求:随最小磨损值增大,区间底价**单调不升**(可等于或下降)。 - 从第一个区间开始,若某区间的区间底价**高于**前一个区间的区间底价,则该区间及其后所有区间视为「异常」,全部舍弃;只保留前面连续满足单调不升的那一段,称为**正常磨损区间**。 **示例**(最小磨损值升序): | 区间 | 区间底价 | |------------|----------| | 0.00-0.01 | 215 | | 0.01-0.02 | 210 | | 0.02-0.03 | 178 | | 0.03-0.04 | 200 | | 0.04-0.05 | 130 | - 215 → 210 → 178 为降序,保留。 - 0.03-0.04 的 200 > 178,违反单调不升,故 0.03-0.04 及 0.04-0.05 均舍弃。 - **正常磨损区间**仅为:0.00-0.01(215)、0.01-0.02(210)、0.02-0.03(178)。 ### 3.4 利润比较与「满足要求的区间」 - **比较价** = 最低价 × (1 + 用户设置的利润率)。 例:最低价 175,利润率 10% → 比较价 = 175 × 1.1 = 192.5。 - 用比较价与**正常磨损区间**的区间底价比较:只保留**区间底价 > 比较价**的区间;一旦某区间底价 ≤ 比较价,该区间及后续正常区间均不再保留(因为后面价格更低,也不满足「有利润」)。 - 若最终至少剩一个区间,则该饰品**被选中**;否则不入选。 **接上例**(正常区间 215、210、178,比较价 192.5): - 0.00-0.01:215 > 192.5,保留。 - 0.01-0.02:210 > 192.5,保留。 - 0.02-0.03:178 < 192.5,不保留,且其后不再看。 - **满足要求的区间**:0.00-0.01、0.01-0.02。 --- ## 4. 选中饰品的落库数据(buff_selection_request_item) 当某饰品被选中时,写入明细表的数据来源与计算方式如下。 | 字段说明 | 来源/计算方式 | |------------------|----------------| | 饰品 ID | `buff_goods_filtered_view.BUFF饰品ID` | | 饰品名称 | `buff_goods_filtered_view.BUFF饰品名称` | | 饰品图片 URL | `buff_goods_filtered_view.饰品图片URL地址` | | 饰品 Steam 链接 | `buff_goods_filtered_view.饰品STEAM链接` | | 价格(人民币) | 比较价 × (1 + 扫货价格系数)。系数由**智能扫货助手**等级决定:低→`buff_sweep_price_factor_min`,标准→`buff_sweep_price_factor_mid`,高→`buff_sweep_price_factor_max`(来自 `system_config`)。 | | 价格(美元) | 先算 价格(人民币)/ Steam 卡价汇率,**保留 2 位小数**,再 +0.01。 | | 区间 | 满足要求的区间:取这些区间的**最小磨损值的最小值** 到 **最大磨损值的最大值**。例:仅 0.00-0.01、0.01-0.02 满足 → 区间为「0.00-0.02」。 | | 选品时间 | 写入时的服务器时间(即明细行的 `created_at`)。 | - 上述「比较价」即 最低价 × (1 + 用户利润率)。 - Steam 卡价汇率来自用户选品配置(如 `steamCardRatio`)。 --- ## 5. 智能扫货助手与扫货价格系数 - 在「BUFF选品」界面增加选项**智能扫货助手**:**低 / 标准 / 高**,**默认「低」**;该选项需写入用户选品配置(`buff_selection_config.config_json`),例如字段名:`assistantLevel` 或 `sweepAssistantLevel`,值与第一步「智能选品助手」一致时可复用同一字段(low/standard/high)。 - 扫货价格系数从 `system_config` 读取(`app_name = 'backend-selection'`): | 选项 | config_key | 用途 | |------|------------|------| | 低 | buff_sweep_price_factor_min | 价格(人民币)= 比较价 × (1 + 该值) | | 标准 | buff_sweep_price_factor_mid | 同上 | | 高 | buff_sweep_price_factor_max | 同上 | --- ## 6. 选品结束条件与系统限制 ### 6.1 本次选品何时结束 满足其一即结束本次选品请求: - **达到用户配置的选品数量**:已入选的饰品条数 = 用户在界面上配置的「选品数量」; - **第一步的饰品 ID 全部处理完**:尚未达到选品数量,但乱序后的 ID 列表已遍历完毕。 ### 6.2 系统限制(system_config) - **每日选品次数**:`buff_selection_daily_times_limit_per_user`(如 3),即每个用户每天最多发起 N 次选品(按请求单条数计)。 - **每日选品条数**:`buff_selection_daily_items_limit_per_user`(如 900),即每个用户每天累计入选的饰品条数上限(跨多次选品累计);超过后当日不再接受新选品或不再写入新明细,以产品规则为准。 选品逻辑需在创建/执行选品前校验上述两项,不满足则直接拒绝并提示。 --- ## 7. 表结构说明 - **buff_selection_request**:已具备 `user_id`、`config_id`、`status`、`created_at`/`updated_at` 等,**满足需求,无需改动**。`config_id` 关联本次使用的 `buff_selection_config`,可拿到选品数量、利润率、Steam 卡价、智能扫货助手等级等。 - **buff_selection_request_item**:在原有 `id`、`request_id`、`user_id`、`goods_id`、`created_at` 基础上,通过迁移 `20260228_buff_selection_request_item_columns.sql` 增加:`goods_name`、`icon_url`、`steam_link`、`price_cny`、`price_usd`、`wear_range`,用于列表展示与扫货。 - **buff_selection_request_log**:选品日志表,见第 9 节。 --- ## 8. 第二步流程小结 1. 输入:第一步得到的饰品 ID 列表(已乱序)、用户选品配置(含利润率、选品数量、Steam 卡价、智能扫货助手等级)、本请求单与用户当日已选次数/条数(用于校验限制)。 2. 校验:当日选品次数、当日选品条数是否已达上限;若达上限则拒绝并提示。 3. 按 ID 顺序逐个处理: - 取最低价、磨损区间(按最小磨损值升序); - 求正常磨损区间(区间底价单调不升); - 比较价 = 最低价 × (1 + 利润率);在正常区间中保留区间底价 > 比较价的区间; - 若存在满足要求的区间,则从视图取名称、图片、Steam 链接,算价格(人民币)、价格(美元)、区间,写入 `buff_selection_request_item`; - **无论是否入选**,均向 `buff_selection_request_log` 写入一条日志(见第 9 节)。 4. 当已入选数量 = 用户配置的选品数量,或所有 ID 已处理完,则本单选品结束,更新请求单状态为 completed(或 failed,若中途异常)。 --- ## 9. 选品日志(buff_selection_request_log) ### 9.1 用途 - 记录第二步中**对每个饰品的判断结果与判断时间**,不追求精细,主要用于前端展示,让用户感知「后台在逐个判断饰品、满足则入选」。 - 前端可按 `request_id` 分页或滚动拉取日志,按 `sequence_no` 或 `created_at` 排序展示。 ### 9.2 表结构(迁移 20260228_buff_selection_request_log.sql) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint, PK | 主键 | | request_id | bigint, NOT NULL | 选品请求单 ID | | user_id | int, NOT NULL | 用户 ID,按用户隔离 | | goods_id | varchar(64), NOT NULL | BUFF 饰品 ID | | goods_name | varchar(256), NULL | 饰品名称,便于日志展示 | | sequence_no | int, NOT NULL | 判断序号:本次选品中第几个被判断(从 1 递增) | | result | varchar(20), NOT NULL | 判断结果:`selected`-已入选,`not_selected`-未入选 | | message | varchar(256), NULL | 简短说明,如「无满足利润的磨损区间」「已入选」等,可为空 | | created_at | datetime | 判断时间 | 索引:`request_id`、`idx_request_sequence`、`idx_request_created`,便于按请求单查日志并排序。 ### 9.3 写入时机与内容 - **时机**:每判断完一个饰品(无论是否入选)后,立即插入一条日志。 - **内容建议**: - `result = 'selected'`:该饰品已入选并写入 `buff_selection_request_item`;`message` 可填「已入选」或为空。 - `result = 'not_selected'`:未入选;`message` 可填简短原因,如「无满足利润的磨损区间」「无正常磨损区间」等,可为空。 - `goods_name` 可从 `buff_goods_filtered_view` 取当前名称,便于前端展示;可为空。 - `sequence_no` 为本次选品中该饰品的判断顺序号(从 1 递增),便于前端展示「第 N 个判断」。