Commit 8e72bc4d by renyizhao

条件乘数

parent f3ae131f
......@@ -235,6 +235,9 @@ public class AppAiModelController {
if ("tiered_expr".equals(billingMode) && billingExpr != null) {
List<AppModelPricingVO.TierVO> tiers = parseTieredExpr(billingExpr, groupRatio);
vo.setTiers(tiers);
// 解析条件乘数
List<AppModelPricingVO.ConditionMultiplierVO> conditionMultipliers = parseConditionMultipliers(billingExpr);
vo.setConditionMultipliers(conditionMultipliers);
}
// 计算价格
......@@ -470,7 +473,7 @@ public class AppAiModelController {
/**
* 解析分档计费表达式
* 格式: len <= 200000 && p > 200000 ? tier("base", p * 1 + c * 1.2) : c > 200000 && p > 200000 ? tier("第2档", p * 0.8 + c * 0.9) : tier("第3档", p * 0.5 + c * 0.6)
* 格式: (条件1 ? tier("档位1", ...) : 条件2 ? tier("档位2", ...) : tier("档位3", ...)) * (条件 ? 倍率 : 1) * ...
*/
private List<AppModelPricingVO.TierVO> parseTieredExpr(String billingExpr, BigDecimal groupRatio) {
List<AppModelPricingVO.TierVO> tiers = new java.util.ArrayList<>();
......@@ -479,59 +482,254 @@ public class AppAiModelController {
}
try {
// 正则匹配 tier("名称", p * 输入倍率 + c * 输出倍率)
// 找到最后一个 tier(...) 结束的位置(用于截取分档表达式部分)
int lastTierEnd = -1;
int searchStart = 0;
while (true) {
int tierStart = billingExpr.indexOf("tier(", searchStart);
if (tierStart == -1) break;
int depth = 0;
int i = tierStart;
for (; i < billingExpr.length(); i++) {
char ch = billingExpr.charAt(i);
if (ch == '(') depth++;
else if (ch == ')') {
depth--;
if (depth == 0) {
lastTierEnd = i + 1;
break;
}
}
}
searchStart = i + 1;
}
if (lastTierEnd == -1) {
return tiers;
}
// 提取分档表达式部分(从开头到最后一个 tier(...) 结束)
String tierExpr = billingExpr.substring(0, lastTierEnd);
// 找到所有 tier("名称", p * x + c * y) 的位置
java.util.regex.Pattern tierPattern = java.util.regex.Pattern.compile(
"tier\\s*\\(\\s*\"([^\"]+)\"\\s*,\\s*p\\s*\\*\\s*([\\d.]+)\\s*\\+\\s*c\\s*\\*\\s*([\\d.]+)\\s*\\)"
);
java.util.regex.Matcher tierMatcher = tierPattern.matcher(billingExpr);
java.util.regex.Matcher tierMatcher = tierPattern.matcher(tierExpr);
// 提取档位信息
// 收集所有 tier 的位置和内容
java.util.List<java.util.regex.MatchResult> tierMatches = new java.util.ArrayList<>();
while (tierMatcher.find()) {
AppModelPricingVO.TierVO tier = new AppModelPricingVO.TierVO();
tier.setName(tierMatcher.group(1));
// 动态计费的价格倍率已经是最终值,不需要乘以 groupRatio
BigDecimal inputRatio = new BigDecimal(tierMatcher.group(2)).setScale(4, RoundingMode.HALF_UP);
BigDecimal outputRatio = new BigDecimal(tierMatcher.group(3)).setScale(4, RoundingMode.HALF_UP);
tier.setInputRatio(inputRatio);
tier.setOutputRatio(outputRatio);
tiers.add(tier);
tierMatches.add(tierMatcher.toMatchResult());
}
// 解析条件描述
if (tiers.isEmpty()) {
if (tierMatches.isEmpty()) {
return tiers;
}
// 按 : 分割,每一段是 条件? tier(...) 的格式
String[] segments = billingExpr.split(":");
int tierIndex = 0;
for (String segment : segments) {
// 找 tier(...) 前的条件(?前面的部分)
// 遍历每个 tier,提取其对应的条件
for (int i = 0; i < tierMatches.size(); i++) {
AppModelPricingVO.TierVO tier = new AppModelPricingVO.TierVO();
java.util.regex.MatchResult mr = tierMatches.get(i);
tier.setName(mr.group(1));
tier.setInputRatio(new BigDecimal(mr.group(2)).setScale(4, RoundingMode.HALF_UP));
tier.setOutputRatio(new BigDecimal(mr.group(3)).setScale(4, RoundingMode.HALF_UP));
// 提取条件:从上一个 tier 结束(或表达式开头)到当前 tier 开始的 ? 之前
int tierStart = mr.start();
String segment;
if (i == 0) {
// 第一个 tier:条件在表达式开头到 tier 之前
segment = tierExpr.substring(0, tierStart);
} else {
// 后续 tier:条件在上一个 tier 结束后到当前 tier 之前
int prevTierEnd = tierMatches.get(i - 1).end();
segment = tierExpr.substring(prevTierEnd, tierStart);
}
// 找到 ? 的位置,? 之前是条件
int questionMarkIndex = segment.lastIndexOf('?');
if (questionMarkIndex > 0 && tierIndex < tiers.size()) {
String conditionDesc = segment.substring(0, questionMarkIndex).trim();
// 简化条件描述
conditionDesc = conditionDesc
.replaceAll("\\s+&&\\s+", " 且 ")
.replaceAll("\\s+\\|\\|\\s+", " 或 ")
.replace("len", "长度")
.replace(" p ", " 输入 ")
.replace(" c ", " 输出 ")
.replace("p >", "输入 >")
.replace("p <", "输入 <")
.replace("p >=", "输入 >=")
.replace("p <=", "输入 <=")
.replace("c >", "输出 >")
.replace("c <", "输出 <")
.replace("c >=", "输出 >=")
.replace("c <=", "输出 <=");
tiers.get(tierIndex).setConditionDesc(conditionDesc);
tierIndex++;
if (questionMarkIndex > 0) {
String condition = segment.substring(0, questionMarkIndex).trim();
// 清理:去掉开头的 ( : 和结尾的空格
condition = condition.replaceAll("^[\\s(:)]+", "");
// 去掉首尾括号对
while (condition.startsWith("(") && condition.endsWith(")")) {
condition = condition.substring(1, condition.length() - 1).trim();
}
tier.setConditionDesc(simplifyCondition(condition));
} else if (i == tierMatches.size() - 1) {
// 最后一个 tier 没有 ?,是默认档位
tier.setConditionDesc(null);
}
tiers.add(tier);
}
} catch (Exception e) {
log.warn("[parseTieredExpr] 解析分档表达式失败:{}", billingExpr, e);
}
return tiers;
}
/**
* 简化条件描述
*/
private String simplifyCondition(String condition) {
if (condition == null || condition.isEmpty()) {
return "";
}
return condition
.replaceAll("\\s+&&\\s+", " 且 ")
.replaceAll("\\s+\\|\\|\\s+", " 或 ")
.replace("len", "长度")
.replace(" p ", " 输入 ")
.replace(" c ", " 输出 ")
.replace("p >", "输入 >")
.replace("p <", "输入 <")
.replace("p >=", "输入 >=")
.replace("p <=", "输入 <=")
.replace("p==", "输入 ==")
.replace("c >", "输出 >")
.replace("c <", "输出 <")
.replace("c >=", "输出 >=")
.replace("c <=", "输出 <=")
.replace("c==", "输出 ==")
.trim();
}
/**
* 解析条件乘数表达式
* 格式: ((条件) ? 倍率 : 1) * ((条件) ? 倍率 : 1) * ...
*/
private List<AppModelPricingVO.ConditionMultiplierVO> parseConditionMultipliers(String billingExpr) {
List<AppModelPricingVO.ConditionMultiplierVO> multipliers = new java.util.ArrayList<>();
if (billingExpr == null || billingExpr.isEmpty()) {
return multipliers;
}
try {
// 找到最后一个 tier(...) 结束的位置
int lastTierEnd = -1;
int searchStart = 0;
while (true) {
int tierStart = billingExpr.indexOf("tier(", searchStart);
if (tierStart == -1) break;
int depth = 0;
int i = tierStart;
for (; i < billingExpr.length(); i++) {
char ch = billingExpr.charAt(i);
if (ch == '(') depth++;
else if (ch == ')') {
depth--;
if (depth == 0) {
lastTierEnd = i + 1;
break;
}
}
}
searchStart = i + 1;
}
if (lastTierEnd == -1 || lastTierEnd >= billingExpr.length()) {
return multipliers;
}
// 提取条件乘数部分(最后一个 tier(...) 之后的内容)
String multiplierExpr = billingExpr.substring(lastTierEnd).replaceAll("^\\s*\\)\\s*\\*\\s*", "");
// 用栈匹配方式解析每个条件组: ((... ) ? 倍率 : 1)
int pos = 0;
while (pos < multiplierExpr.length()) {
// 找到下一个 ( 的位置
int startParen = multiplierExpr.indexOf('(', pos);
if (startParen == -1) break;
// 从这个 ( 开始,找到对应的 )
int depth = 0;
int i = startParen;
for (; i < multiplierExpr.length(); i++) {
char ch = multiplierExpr.charAt(i);
if (ch == '(') depth++;
else if (ch == ')') {
depth--;
if (depth == 0) {
// 找到了对应的 )
// 提取从 startParen 到 i+1 的内容(完整条件组)
String group = multiplierExpr.substring(startParen, i + 1);
// 检查是否是条件组格式: (... ) ? 倍率 : 1
int questionMark = group.lastIndexOf('?');
int colonIndex = group.lastIndexOf(':');
if (questionMark > 0 && colonIndex > questionMark) {
String condition = group.substring(1, questionMark).trim(); // 去掉首尾括号
String afterQuestion = group.substring(questionMark + 1, colonIndex).trim();
try {
BigDecimal multiplier = new BigDecimal(afterQuestion).setScale(1, RoundingMode.HALF_UP);
AppModelPricingVO.ConditionMultiplierVO cm = new AppModelPricingVO.ConditionMultiplierVO();
cm.setMultiplier(multiplier);
cm.setDescription(parseConditionDesc(condition));
multipliers.add(cm);
} catch (NumberFormatException ignored) {}
}
pos = i + 1;
break;
}
}
}
if (i >= multiplierExpr.length()) break;
}
} catch (Exception e) {
log.warn("[parseConditionMultipliers] 解析条件乘数失败:{}", billingExpr, e);
}
return multipliers;
}
/**
* 解析条件描述为用户可读文字
*/
private String parseConditionDesc(String condition) {
if (condition == null || condition.isEmpty()) {
return "";
}
String result = condition;
// 先处理 has(header(...), "xxx") 这种模式
result = result.replaceAll(
"has\\s*\\(\\s*header\\s*\\(\\s*\"([^\"]+)\"\\s*\\)\\s*,\\s*\"([^\"]+)\"\\s*\\)",
"请求头 $1 包含 \"$2\""
);
// 替换逻辑运算符
result = result.replaceAll("\\s+&&\\s+", " 且 ");
result = result.replaceAll("\\s+\\|\\|\\s+", " 或 ");
// 时间条件 - 完整函数调用替换
result = result.replaceAll("hour\\s*\\(\\s*\"([^\"]+)\"\\s*\\)", "小时");
result = result.replaceAll("minute\\s*\\(\\s*\"([^\"]+)\"\\s*\\)", "分钟");
result = result.replaceAll("weekday\\s*\\(\\s*\"([^\"]+)\"\\s*\\)", "星期");
result = result.replaceAll("month\\s*\\(\\s*\"([^\"]+)\"\\s*\\)", "月份");
result = result.replaceAll("day\\s*\\(\\s*\"([^\"]+)\"\\s*\\)", "日期");
// 请求参数条件
result = result.replaceAll("param\\s*\\(\\s*\"([^\"]+)\"\\s*\\)\\s*", "请求参数 $1 ");
result = result.replaceAll("param\\s*\\(\\s*\"([^\"]+)\"\\s*\\)\\s*!=\\s*nil", "请求参数 $1 存在");
// 请求头条件 - 处理剩余的 header()
result = result.replaceAll("header\\s*\\(\\s*\"([^\"]+)\"\\s*\\)\\s*!=\\s*\"\"", "请求头 $1 存在");
result = result.replaceAll("header\\s*\\(\\s*\"([^\"]+)\"\\s*\\)\\s*", "请求头 $1 ");
// 比较运算符
result = result.replace(">=", " ≥ ");
result = result.replace("<=", " ≤ ");
result = result.replace("==", " = ");
result = result.replace("!=", " ≠ ");
result = result.replace(">", " > ");
result = result.replace("<", " < ");
// 清理多余空格
result = result.replaceAll("\\s+", " ");
result = result.trim();
return result;
}
}
......@@ -80,6 +80,9 @@ public class AppModelPricingVO {
@Schema(description = "分档列表")
private List<TierVO> tiers;
@Schema(description = "条件乘数列表")
private List<ConditionMultiplierVO> conditionMultipliers;
/**
* 分档信息 VO
*/
......@@ -96,4 +99,16 @@ public class AppModelPricingVO {
private String conditionDesc;
}
/**
* 条件乘数 VO
*/
@Data
@Schema(description = "条件乘数")
public static class ConditionMultiplierVO {
@Schema(description = "条件描述(用户可读的文字)")
private String description;
@Schema(description = "倍率")
private BigDecimal multiplier;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment