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,14 +23,13 @@ public class CaculateUtils {
*/
private static final String symbol = "+-,*/,(),%^";
/**
* 公式计算 字符串
*
* @param exeStr
*/
public static BigDecimal execute(String exeStr, Map<String, String> replaceMap) {
//替换掉占位符
// 替换掉占位符
exeStr = caculateReplace(exeStr, replaceMap);
exeStr = exeStr.replaceAll("\\s*", "");
List<String> suffixList = suffixHandle(exeStr);
@ -48,8 +47,8 @@ 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,9 +77,8 @@ public class CaculateUtils {
return a;
}
/**
* 计算 使用double 进行计算 如果需要可以在这里使用bigdecimal 进行计算
* 计算 使用double 进行计算 如果需要可以在这里使用bigdecimal 进行计算
*
* @param a
* @param b
@ -104,7 +102,7 @@ public class CaculateUtils {
b = new BigDecimal(b + ".0");
}
int length = b.toString().split("\\.")[1].length();
return a.divide(b, length, BigDecimal.ROUND_HALF_UP);
return a.divide(b, length, BigDecimal.ROUND_HALF_UP);
case '^':
// 幂运算:a的b次方
double result = Math.pow(a.doubleValue(), b.doubleValue());
@ -120,11 +118,18 @@ public class CaculateUtils {
*/
public static List<String> suffixHandle(String exeStr) {
StringBuilder buf = new StringBuilder();
//Stack stack = new Stack();
// Stack stack = new Stack();
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,27 +137,27 @@ 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.判断是不是开是括号
// 2.判断是不是开是括号
if (x == '(') {
stack.push(x);
continue;
}
//3.判断是不是闭合括号
// 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();
}
@ -221,9 +225,9 @@ public class CaculateUtils {
String key = entry.getKey();
String value = entry.getValue() == null ? "1" : entry.getValue();
// 如果占位符包含正则表达式特殊字符,使用 Pattern.quote() 转义
if (key.contains("{") || key.contains("}") || key.contains("(") || key.contains(")")
|| key.contains("[") || key.contains("]") || key.contains("*") || key.contains("+")
|| key.contains("?") || key.contains("^") || key.contains("$") || key.contains("|")) {
if (key.contains("{") || key.contains("}") || key.contains("(") || key.contains(")")
|| key.contains("[") || key.contains("]") || key.contains("*") || key.contains("+")
|| key.contains("?") || key.contains("^") || key.contains("$") || key.contains("|")) {
// 转义正则表达式特殊字符
String escapedKey = Pattern.quote(key);
str = str.replaceAll(escapedKey, value);
@ -239,47 +243,49 @@ public class CaculateUtils {
DataInputStream mDataInputStream = new DataInputStream(mByteArrayInputStream);
try {
float v = mDataInputStream.readFloat();
return String.format("%.6f",v);
}catch (Exception e){
return String.format("%.6f", v);
} catch (Exception e) {
throw new ServiceException("modbus16转浮点数错误");
}
finally {
} finally {
mDataInputStream.close();
mByteArrayInputStream.close();
}
}
public static String handleToUnSign16(String value,String dataType){
public static String handleToUnSign16(String value, String dataType) {
long l = Long.parseLong(value);
if (dataType.equals(ModbusDataType.U_SHORT.getType())){
return toUnSign16(l);
}else {
if (dataType.equals(ModbusDataType.U_SHORT.getType())) {
return toUnSign16(l);
} else {
return value;
}
}
/**
* 转16位无符号整形
*
* @param value
* @return
*/
public static String toUnSign16(long value) {
long unSigned = value & 0xFFFF;
return unSigned +""; // 将字节数组转换为十六进制字符串
return unSigned + ""; // 将字节数组转换为十六进制字符串
}
/**
* 32位有符号CDAB数据类型
*
* @param value
* @return
*/
public static String toSign32_CDAB(long value) {
byte[] bytes = intToBytes2((int) value);
return bytesToInt2(bytes)+"";
return bytesToInt2(bytes) + "";
}
/**
* 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()配套使用
*/
@ -368,7 +377,7 @@ public class CaculateUtils {
}
/**
* 将int数值转换为占四个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。 和bytesToInt2()配套使用
* 将int数值转换为占四个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。 和bytesToInt2()配套使用
*/
public static byte[] intToBytes2(int value) {
byte[] src = new byte[4];
@ -379,58 +388,56 @@ public class CaculateUtils {
return src;
}
public static String subHexValue(String hexString){
//截取报文中的值
public static String subHexValue(String hexString) {
// 截取报文中的值
String substring = hexString.substring(4, 6);
int index = Integer.parseInt(substring);
return hexString.substring(6, 6 + index*2);
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("%s", "1");
// replaceMap.put("%s1", "5");
BigDecimal execute = execute("A + B * (C - D) % E", replaceMap);
// 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("AngZ+88.59", replaceMap);
System.out.println(execute);
Map<String, String> map = new HashMap<>();
map.put("%s", "10");
String caculate = caculateReplace("%s*2", map);
System.out.println(caculate);
System.out.println(execute("%s%3.00",map));
Map<String, String> map = new HashMap<>();
map.put("%s", "10");
String caculate = caculateReplace("%s*2", map);
System.out.println(caculate);
System.out.println(execute("%s%3.00", map));
String s4 = toUnSign16(-1);
System.out.println("转16位无符号:"+s4);
System.out.println("转16位无符号:" + s4);
String s1 = toSign32_CDAB(40100);
System.out.println("转32位有符号-CDAB序"+s1);
System.out.println("转32位有符号-CDAB序" + s1);
String s2 = toUnSign32_ABCD(-10);
System.out.println("转32位无符号-ABCD序:"+s2);
System.out.println("转32位无符号-ABCD序:" + s2);
String s3 = toUnSign32_CDAB(123456789);
System.out.println("转32位无符号-CDAB序:"+s3);
System.out.println("转32位无符号-CDAB序:" + s3);
String hexToBytes = "3fea3d71";
byte[] bytes = ByteBufUtil.decodeHexDump(hexToBytes);
float v1 = toFloat32_ABCD(bytes);
System.out.println("转32位浮点型-ABCD序:"+v1);
System.out.println("转32位浮点型-ABCD序:" + v1);
String hexToBytes1= "800041EE";
String hexToBytes1 = "800041EE";
long i = Long.parseLong(hexToBytes1, 16);
System.out.println(i);
byte[] bytes1 = ByteBufUtil.decodeHexDump(hexToBytes1);
float v2 = toFloat32_DCBA(bytes1);
System.out.println("转32位浮点型-CDAB序:"+v2);
System.out.println("转32位浮点型-CDAB序:" + v2);
int signedShort = -32627; // 16位有符号整形
// 将有符号短整型转换为无符号短整型
@ -441,10 +448,10 @@ public class CaculateUtils {
long l = Long.parseLong("00501F40", 16);
System.out.println(l);
int val1 = -6553510;
byte[] bytes2 = intToBytes2(val1);
int i1 = bytesToInt2(bytes2);
System.out.println(i1);
int val1 = -6553510;
byte[] bytes2 = intToBytes2(val1);
int i1 = bytesToInt2(bytes2);
System.out.println(i1);
}
}

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 {
}
// 执行计算
value = String.valueOf(CaculateUtils.execute(formula, params));
log.info("=>纯计算物模型结果[{}],标识符:[{}],结果:{}", input.getProductId(), identity, value);
try {
value = String.valueOf(CaculateUtils.execute(formula, params));
} 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,6 +389,11 @@ export default {
};
},
created() {
// 页码筛选
let pageNum = this.$route.query.pageNum;
if (pageNum != null) {
this.queryParams.pageNum = Number(pageNum);
}
// 产品筛选
let productId = this.$route.query.productId;
if (productId != null) {
@ -413,14 +418,15 @@ export default {
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 pageNum = this.$route.query.pageNum;
if (pageNum != null) {
this.queryParams.pageNum = Number(pageNum);
}
// 产品筛选
let productId = this.$route.query.productId;
if (productId != null) {
@ -443,6 +449,9 @@ export default {
this.queryParams.groupId = null;
}
this.getList();
} else if (pageNum != null) {
// 浏览器回退时,即使没有 t 参数也要刷新列表
this.getList();
}
},
methods: {
@ -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>