20260118:bug公式计算负数+值计算异常

This commit is contained in:
George 2026-01-18 21:14:47 +08:00
parent 0a309d5fec
commit e7e1a9d577
7 changed files with 170 additions and 87 deletions

View File

@ -23,7 +23,6 @@ public class CaculateUtils {
*/
private static final String symbol = "+-,*/,(),%^";
/**
* 公式计算 字符串
*
@ -49,7 +48,7 @@ public class CaculateUtils {
BigDecimal a = BigDecimal.ZERO;
BigDecimal b = BigDecimal.ZERO;
// 构建一个操作数栈 每当获得操作符号时取出最上面两个数进行计算。
Stack<BigDecimal> caculateStack = new Stack<BigDecimal>();
Deque<BigDecimal> caculateStack = new ArrayDeque<>();
if (suffixList.size() > 1) {
for (int i = 0; i < suffixList.size(); i++) {
@ -78,7 +77,6 @@ public class CaculateUtils {
return a;
}
/**
* 计算 使用double 进行计算 如果需要可以在这里使用bigdecimal 进行计算
*
@ -124,7 +122,14 @@ public class CaculateUtils {
Deque<Character> stack = new ArrayDeque<>();
char[] exeChars = exeStr.toCharArray();
List<String> res = new ArrayList<>();
for (char x : exeChars) {
for (int i = 0; i < exeChars.length; i++) {
char x = exeChars[i];
char prev = i > 0 ? exeChars[i - 1] : '\0';
// 处理一元正负号:位于开头或运算符/左括号之后
if ((x == '-' || x == '+') && (i == 0 || (symbol.indexOf(prev) > -1 && prev != ')'))) {
buf.append(x);
continue;
}
// 判断是不是操作符号
if (symbol.indexOf(x) > -1) {
// 不管怎样先将数据添加进列表
@ -132,14 +137,15 @@ public class CaculateUtils {
// 添加数据到res
String temp = buf.toString();
// 验证是否为数
if (!isNumber(temp)) throw new RuntimeException(buf.append(" 格式不对").toString());
if (!isNumber(temp))
throw new RuntimeException(buf.append(" 格式不对").toString());
// 添加到结果列表中
res.add(temp);
// 清空临时buf
buf.delete(0, buf.length());
}
if (stack.size() > 0) {
if (!stack.isEmpty()) {
// 2.判断是不是开是括号
if (x == '(') {
@ -148,11 +154,10 @@ public class CaculateUtils {
}
// 3.判断是不是闭合括号
if (x == ')') {
while (stack.size() > 0) {
char con = (char) stack.peek();
while (!stack.isEmpty()) {
char con = stack.peek();
if (con == '(') {
stack.pop();
continue;
} else {
res.add(String.valueOf(stack.pop()));
}
@ -160,7 +165,7 @@ public class CaculateUtils {
continue;
}
// 取出最后最近的一个操作符
char last = (char) stack.peek();
char last = stack.peek();
if (compare(x, last) > 0) {
stack.push(x);
} else if (compare(x, last) <= 0) {
@ -176,15 +181,15 @@ public class CaculateUtils {
buf.append(x);
}
}
if (buf.length() > 0) res.add(buf.toString());
while (stack.size() > 0) {
if (buf.length() > 0)
res.add(buf.toString());
while (!stack.isEmpty()) {
res.add(String.valueOf(stack.pop()));
}
return res;
}
/**
* 比较两个操作符号的优先级
*
@ -202,7 +207,6 @@ public class CaculateUtils {
}
}
/**
* 判断是否为数 字符串
*
@ -210,7 +214,7 @@ public class CaculateUtils {
* @return
*/
public static boolean isNumber(String str) {
Pattern pattern = Pattern.compile("[0-9]+\\.{0,1}[0-9]*");
Pattern pattern = Pattern.compile("[-+]?[0-9]+\\.{0,1}[0-9]*");
Matcher isNum = pattern.matcher(str);
return isNum.matches();
}
@ -242,8 +246,7 @@ public class CaculateUtils {
return String.format("%.6f", v);
} catch (Exception e) {
throw new ServiceException("modbus16转浮点数错误");
}
finally {
} finally {
mDataInputStream.close();
mByteArrayInputStream.close();
}
@ -260,6 +263,7 @@ public class CaculateUtils {
/**
* 转16位无符号整形
*
* @param value
* @return
*/
@ -270,6 +274,7 @@ public class CaculateUtils {
/**
* 32位有符号CDAB数据类型
*
* @param value
* @return
*/
@ -280,6 +285,7 @@ public class CaculateUtils {
/**
* 32位无符号ABCD数据类型
*
* @param value
* @return
*/
@ -289,6 +295,7 @@ public class CaculateUtils {
/**
* 32位无符号CDAB数据类型
*
* @param value
* @return
*/
@ -300,6 +307,7 @@ public class CaculateUtils {
/**
* 转32位浮点数 ABCD
*
* @param bytes
* @return
*/
@ -310,11 +318,13 @@ public class CaculateUtils {
/**
* 转32位浮点数 CDAB
*
* @param bytes
* @return
*/
public static Float toFloat32_DCBA(byte[] bytes) {
int intValue = ((bytes[3] & 0xFF) << 24) | ((bytes[2] & 0xFF) << 16) | ((bytes[1] & 0xFF) << 8) | ((bytes[0] & 0xFF)) ;
int intValue = ((bytes[3] & 0xFF) << 24) | ((bytes[2] & 0xFF) << 16) | ((bytes[1] & 0xFF) << 8)
| ((bytes[0] & 0xFF));
return Float.intBitsToFloat(intValue);
}
@ -359,7 +369,6 @@ public class CaculateUtils {
return value;
}
/**
* byte数组中取int数值,本方法适用于(低位在后,高位在前)的顺序。和intToBytes2()配套使用
*/
@ -386,20 +395,18 @@ public class CaculateUtils {
return hexString.substring(6, 6 + index * 2);
}
public static void main(String[] args) throws IOException {
Map<String, String> replaceMap = new HashMap<>();
replaceMap.put("A", "1.5");
replaceMap.put("B", "2.5");
replaceMap.put("C", "3.5");
replaceMap.put("D", "4.5");
replaceMap.put("E", "10");
// replaceMap.put("A", "1.5");
// replaceMap.put("B", "2.5");
// replaceMap.put("C", "3.5");
// replaceMap.put("D", "4.5");
replaceMap.put("AngZ", "-10");
// replaceMap.put("%s", "1");
// replaceMap.put("%s1", "5");
BigDecimal execute = execute("A + B * (C - D) % E", replaceMap);
BigDecimal execute = execute("AngZ+88.59", replaceMap);
System.out.println(execute);
Map<String, String> map = new HashMap<>();
map.put("%s", "10");
String caculate = caculateReplace("%s*2", map);

View File

@ -60,12 +60,18 @@ public class TCPServer extends Server {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(config.readerIdleTime, config.writerIdleTime, config.allIdleTime)) //设置心跳时间
.addLast(FastBeeConstant.SERVER.FRAMEDECODER, frameDecoder())//粘包处理器
.addLast(FastBeeConstant.SERVER.ADAPTER, adapter)//消息适配器
.addLast(FastBeeConstant.SERVER.DECODER, decoder) //报文解码器
.addLast(FastBeeConstant.SERVER.ENCODER, encoder) //报文编码器
.addLast(FastBeeConstant.SERVER.DISPATCHER, dispatcher); //消息分发
// 设置心跳时间
.addLast(new IdleStateHandler(config.readerIdleTime, config.writerIdleTime, config.allIdleTime))
// 第一步、添加粘包处理器
.addLast(FastBeeConstant.SERVER.FRAMEDECODER, frameDecoder())
// 第二步、添加消息适配器
.addLast(FastBeeConstant.SERVER.ADAPTER, adapter)
// 第三步、添加报文解码器
.addLast(FastBeeConstant.SERVER.DECODER, decoder)
// 第四步、添加报文编码器
.addLast(FastBeeConstant.SERVER.ENCODER, encoder)
// 第五步、添加消息分发器
.addLast(FastBeeConstant.SERVER.DISPATCHER, dispatcher);
}
});
}

View File

@ -600,8 +600,12 @@ public class DeviceServiceImpl implements IDeviceService {
}
// 执行计算
try {
value = String.valueOf(CaculateUtils.execute(formula, params));
log.info("=>纯计算物模型结果[{}],标识符:[{}],结果:{}", input.getProductId(), identity, value);
} catch (Exception e) {
log.error("=>计算公式执行失败[{}],标识符:[{}],公式:[{}],错误:{}", input.getProductId(), identity, formula, e.getMessage());
}
//log.info("=>纯计算物模型结果[{}],标识符:[{}],结果:{}", input.getProductId(), identity, value);
item.setValue(value);
}
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★ */

View File

@ -264,6 +264,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
}
ThingsModel input = new ThingsModel();
input.setProductId(thingsModel.getProductId());
input.setIsCalculated(0); // 只需要查产品的物模型即可
input.setLanguage(SecurityUtils.getLanguage());
List<ThingsModel> list = thingsModelMapper.selectThingsModelList(input);
Boolean isRepeat = list.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier()) && x.getModelId().longValue() != thingsModel.getModelId());

View File

@ -177,8 +177,8 @@
</div>
<!-- 输入框和清空按钮 -->
<div class="formula-input-section">
<el-input v-model="form.formula" type="textarea" :rows="2" placeholder="输入或点击上方运算符、物模型变量构建公式" class="formula-input" />
<el-button type="danger" plain size="small" title="清空公式" @click="form.formula = ''" class="clear-btn">清空</el-button>
<el-input ref="formulaInput" v-model="form.formula" type="textarea" :rows="2" placeholder="输入或点击上方运算符、物模型变量构建公式" class="formula-input" />
<el-button type="danger" plain size="small" title="清空公式" @click="clearFormula" class="clear-btn">清空</el-button>
</div>
</div>
</el-form-item>
@ -646,11 +646,53 @@ export default {
},
/** 插入运算符 */
insertOperator(op) {
this.form.formula = (this.form.formula || '') + op;
this.insertAtCursor(op);
},
/** 插入变量(标识符使用大括号包裹) */
insertVariable(identifier) {
this.form.formula = (this.form.formula || '') + `{${identifier}}`;
this.insertAtCursor(`{${identifier}}`);
},
/** 在光标位置插入内容(支持替换选中文本) */
insertAtCursor(text) {
const input = this.$refs.formulaInput;
if (!input) {
this.form.formula = (this.form.formula || '') + text;
return;
}
// 获取 textarea 的 DOM 元素
const textarea = input.$refs.textarea || input.$el.querySelector('textarea');
if (!textarea) {
this.form.formula = (this.form.formula || '') + text;
return;
}
const formula = this.form.formula || '';
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
// 在光标位置插入或替换选中的文本
const newFormula = formula.substring(0, start) + text + formula.substring(end);
this.form.formula = newFormula;
// 设置光标位置到插入内容之后
this.$nextTick(() => {
const newCursorPos = start + text.length;
textarea.focus();
textarea.setSelectionRange(newCursorPos, newCursorPos);
});
},
/** 清空公式 */
clearFormula() {
this.form.formula = '';
this.focusFormulaInput();
},
/** 聚焦到公式输入框 */
focusFormulaInput() {
this.$nextTick(() => {
if (this.$refs.formulaInput) {
this.$refs.formulaInput.focus();
}
});
},
/** 格式化物模型specs */
formatThingsSpecs() {

View File

@ -389,33 +389,6 @@ export default {
};
},
created() {
// 产品筛选
let productId = this.$route.query.productId;
if (productId != null) {
this.queryParams.productId = Number(productId);
this.queryParams.groupId = null;
this.queryParams.serialNumber = null;
}
// 分组筛选
let groupId = this.$route.query.groupId;
if (groupId != null) {
this.queryParams.groupId = Number(groupId);
this.queryParams.productId = null;
this.queryParams.serialNumber = null;
}
// 设备编号筛选
let sn = this.$route.query.sn;
if (sn != null) {
this.queryParams.serialNumber = sn;
this.queryParams.productId = null;
this.queryParams.groupId = null;
}
this.connectMqtt();
},
activated() {
const time = this.$route.query.t;
if (time != null && time != this.uniqueId) {
this.uniqueId = time;
// 页码筛选
let pageNum = this.$route.query.pageNum;
if (pageNum != null) {
@ -442,6 +415,42 @@ export default {
this.queryParams.productId = null;
this.queryParams.groupId = null;
}
this.connectMqtt();
},
activated() {
// 页码筛选(优先恢复页码,无论是否有其他参数变化)
let pageNum = this.$route.query.pageNum;
if (pageNum != null) {
this.queryParams.pageNum = Number(pageNum);
}
const time = this.$route.query.t;
if (time != null && time != this.uniqueId) {
this.uniqueId = time;
// 产品筛选
let productId = this.$route.query.productId;
if (productId != null) {
this.queryParams.productId = Number(productId);
this.queryParams.groupId = null;
this.queryParams.serialNumber = null;
}
// 分组筛选
let groupId = this.$route.query.groupId;
if (groupId != null) {
this.queryParams.groupId = Number(groupId);
this.queryParams.productId = null;
this.queryParams.serialNumber = null;
}
// 设备编号筛选
let sn = this.$route.query.sn;
if (sn != null) {
this.queryParams.serialNumber = sn;
this.queryParams.productId = null;
this.queryParams.groupId = null;
}
this.getList();
} else if (pageNum != null) {
// 浏览器回退时,即使没有 t 参数也要刷新列表
this.getList();
}
},
@ -625,6 +634,8 @@ export default {
this.loading = true;
this.queryParams.params = {};
this.getGroupList();
// 同步更新 URL 中的页码参数(用于浏览器回退时恢复页码)
this.updateUrlPageNum();
listDeviceShort(this.queryParams).then((response) => {
this.deviceList = response.rows;
this.total = response.total;
@ -635,6 +646,18 @@ export default {
this.loading = false;
});
},
/** 更新 URL 中的页码参数 */
updateUrlPageNum() {
const currentPageNum = this.$route.query.pageNum;
if (Number(currentPageNum) !== this.queryParams.pageNum) {
this.$router.replace({
query: {
...this.$route.query,
pageNum: this.queryParams.pageNum,
},
}).catch(() => {});
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;

View File

@ -31,7 +31,7 @@
<el-form-item prop="username">
<div class="username">
<i class="el-icon-user icon"></i>
<input class="form__input" v-model="loginForm.username" auto-complete="off" type="text" :placeholder="$t('login.989807-4')" />
<input class="form__input" v-model="loginForm.username" auto-complete="off" type="text" :placeholder="$t('login.989807-4')" @keyup.enter="handleLogin" />
</div>
</el-form-item>
@ -46,7 +46,7 @@
<el-form-item v-if="captchaOnOff" prop="code" :class="{ 'is-null': ispwd }">
<div style="width: 350px" class="check">
<svg-icon icon-class="auth-code" class="icon" />
<input v-model="loginForm.code" auto-complete="off" class="form__input__code" type="text" :placeholder="$t('login.989807-6')" />
<input v-model="loginForm.code" auto-complete="off" class="form__input__code" type="text" :placeholder="$t('login.989807-6')" @keyup.enter="handleLogin" />
<div class="login-code">
<img :src="codeUrl" @click="getCode" style="float: right" />
</div>
@ -114,7 +114,7 @@
<el-form-item prop="smsCode">
<div class="smscode" :class="{ 'is-null': isphone }">
<svg-icon icon-class="password" class="icon" />
<input class="form__input" type="password" v-model="smsLoginForm.smsCode" auto-complete="off" :placeholder="$t('login.989807-9')" />
<input class="form__input" type="password" v-model="smsLoginForm.smsCode" auto-complete="off" :placeholder="$t('login.989807-9')" @keyup.enter="handleSmsLogin" />
</div>
</el-form-item>