Browse Source

fix: 向量数据库数据同步接口优化

zhongwei
haungxing 2 months ago
parent
commit
b333808138
  1. 3
      hzims-service-api/big-model-api/src/main/java/com/hnac/gglm/bigmodel/BigModelConstants.java
  2. 2
      hzims-service-api/equipment-api/src/main/java/com/hnac/hzims/fdp/vo/FdpFaultMatchVO.java
  3. 3
      hzims-service/equipment/src/main/java/com/hnac/hzims/equipment/scheduled/DeviceLedgerScheduledTask.java
  4. 3
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/business/controller/VectorDataController.java
  5. 3
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/business/service/VectorDataService.java
  6. 33
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/business/service/impl/VectorDataServiceImpl.java
  7. 25
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/database/service/WeaviateService.java
  8. 48
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/interactive/service/impl/CanvasResolveServiceImpl.java
  9. 42
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/maintenance/service/DataRecordService.java
  10. 5
      hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/utils/RequestClientUtil.java
  11. 2
      hzims-service/operational/src/main/java/com/hnac/hzims/operational/station/mapper/StationVideoTypeMapper.xml

3
hzims-service-api/big-model-api/src/main/java/com/hnac/gglm/bigmodel/BigModelConstants.java

@ -10,5 +10,6 @@ public interface BigModelConstants {
String APP_NAME = "gglm-big-model";
/**前端展示服务名**/
String MODULE_NAME = "大模型管理";
/**项目前缀**/
String PREFIX = "Hzims";
}

2
hzims-service-api/equipment-api/src/main/java/com/hnac/hzims/fdp/vo/FdpFaultMatchVO.java

@ -45,7 +45,7 @@ public class FdpFaultMatchVO implements Serializable {
private String deviceFaultName;
public void setItemName() {
this.itemId = String.format("%s %s", this.deviceName, this.faultName);
this.itemName = String.format("%s %s", this.deviceName, this.faultName);
}
public void setDeviceFaultName() {

3
hzims-service/equipment/src/main/java/com/hnac/hzims/equipment/scheduled/DeviceLedgerScheduledTask.java

@ -3,6 +3,7 @@ package com.hnac.hzims.equipment.scheduled;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hnac.gglm.bigmodel.BigModelConstants;
import com.hnac.gglm.bigmodel.api.dto.WeaviateInsertDTO;
import com.hnac.gglm.bigmodel.api.dto.WeaviateQueryDTO;
import com.hnac.gglm.bigmodel.api.feign.IWeaviatesClient;
@ -76,7 +77,7 @@ public class DeviceLedgerScheduledTask {
// 批量插入数据
WeaviateInsertDTO weaviateInsertDTO = new WeaviateInsertDTO();
weaviateInsertDTO.setEntities(deviceLedgerList);
weaviateInsertDTO.setClassName(ScheduledConstant.DEVICE_LEDGER_CLASS_NAME.replace("Hzn_lm_",""));
weaviateInsertDTO.setClassName(ScheduledConstant.DEVICE_LEDGER_CLASS_NAME.replace(BigModelConstants.PREFIX + "_",""));
Map<String,String> attrMap = new HashMap<>();
attrMap.put("dataSummary","dataSummary");
weaviateInsertDTO.setAttrsMap(attrMap);

3
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/business/controller/VectorDataController.java

@ -4,6 +4,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.hnac.gglm.bigmodel.business.service.KnowledgeDataService;
import com.hnac.gglm.bigmodel.business.service.VectorDataService;
import com.hnac.gglm.bigmodel.business.vo.DeviceModelData;
import com.hnac.gglm.bigmodel.maintenance.dto.DeviceAttrDTO;
import com.hnac.gglm.bigmodel.maintenance.dto.KnowledgeData;
import com.hnac.hzinfo.api.annotation.ApiInterface;
import com.hnac.hzinfo.sdk.v5.device.dto.DeviceAttrInfoDTO;
@ -74,7 +75,7 @@ public class VectorDataController {
@ApiOperation(value = "获取设备属性数据")
@ApiOperationSupport(order = 5)
@ApiInterface
public R<List<DeviceAttrInfoDTO>> getDeviceAttr() {
public R<List<DeviceAttrDTO>> getDeviceAttr() {
return R.data(vectorDataService.getDeviceAttr());
}

3
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/business/service/VectorDataService.java

@ -1,6 +1,7 @@
package com.hnac.gglm.bigmodel.business.service;
import com.hnac.gglm.bigmodel.business.vo.DeviceModelData;
import com.hnac.gglm.bigmodel.maintenance.dto.DeviceAttrDTO;
import com.hnac.hzinfo.sdk.v5.device.dto.DeviceAttrInfoDTO;
import com.hnac.hzinfo.sdk.v5.device.dto.DeviceFuncInfoDTO;
import com.hnac.hzinfo.sdk.v5.model.vo.ModelAttrVO;
@ -24,7 +25,7 @@ public interface VectorDataService {
List<DeviceModelData> getModelData();
List<DeviceAttrInfoDTO> getDeviceAttr();
List<DeviceAttrDTO> getDeviceAttr();
List<DeviceFuncInfoDTO> getDeviceFunc();

33
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/business/service/impl/VectorDataServiceImpl.java

@ -3,6 +3,7 @@ package com.hnac.gglm.bigmodel.business.service.impl;
import com.hnac.gglm.bigmodel.business.constants.AccessRules;
import com.hnac.gglm.bigmodel.business.service.VectorDataService;
import com.hnac.gglm.bigmodel.business.vo.DeviceModelData;
import com.hnac.gglm.bigmodel.maintenance.dto.DeviceAttrDTO;
import com.hnac.hzinfo.sdk.v5.device.client.DeviceClient;
import com.hnac.hzinfo.sdk.v5.device.dto.DeviceAttrInfoDTO;
import com.hnac.hzinfo.sdk.v5.device.dto.DeviceFuncInfoDTO;
@ -17,11 +18,10 @@ import com.hnac.hzinfo.sdk.v5.scada.vo.CanvasInfo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.BeanUtil;
import org.springframework.stereotype.Service;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -75,17 +75,33 @@ public class VectorDataServiceImpl implements VectorDataService {
}
@Override
public List<DeviceAttrInfoDTO> getDeviceAttr() {
public List<DeviceAttrDTO> getDeviceAttr() {
List<DeviceAttrInfoDTO> list = deviceClient.listDeviceAttr().getData();
List<DeviceAttrDTO> deviceAttrDTOS = new ArrayList<>();
if (null != list && !list.isEmpty()) {
list.forEach(item -> {
String rule = AccessRules.getDescByCode(item.getRule());
if (null != rule) {
item.setRule(rule);
}
DeviceAttrDTO deviceAttrDTO = BeanUtil.copy(item, DeviceAttrDTO.class);
deviceAttrDTO.setDeviceId(item.getDeviceId());
deviceAttrDTO.setDeviceName(item.getDeviceName());
deviceAttrDTO.setStationName(item.getProjectName());
deviceAttrDTO.setStationId(item.getProjectId());
deviceAttrDTO.setRecordName(item.getName());
deviceAttrDTO.setItemId(item.getSignage());
String stationName = deviceAttrDTO.getStationName() == null ? "" : deviceAttrDTO.getStationName();
String deviceName = deviceAttrDTO.getDeviceName() == null ? "" : deviceAttrDTO.getDeviceName();
String recordName = deviceAttrDTO.getRecordName() == null ? "" : deviceAttrDTO.getRecordName();
deviceAttrDTO.setItemName(new StringBuffer(stationName)
.append(" ").append(deviceName)
.append(" ").append(recordName).toString());
deviceAttrDTO.setRule(item.getRule());
deviceAttrDTOS.add(deviceAttrDTO);
});
}
return list;
return deviceAttrDTOS;
}
@Override
@ -104,12 +120,9 @@ public class VectorDataServiceImpl implements VectorDataService {
if (null == nameList || nameList.isEmpty()) {
return list;
}
Map<String, String> map = nameList.stream().collect(Collectors.toMap(ProjectNameVo::getId, ProjectNameVo::getName));
list.forEach(item -> {
String projectName = map.get(item.getProjectId());
if (null != projectName) {
item.setProjectName(projectName);
}
String projectName = Optional.ofNullable(nameList.stream().filter(name -> name.getId().equals(item.getProjectId())).findFirst().orElse(null)).map(ProjectNameVo::getName).orElse("");
item.setProjectName(projectName);
item.setItemName(item.getProjectName() + " " + item.getName());
});
return list;

25
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/database/service/WeaviateService.java

@ -5,7 +5,9 @@ import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.hnac.gglm.bigmodel.BigModelConstants;
import com.hnac.gglm.bigmodel.configuration.BigModelInvokeApi;
import com.hnac.gglm.bigmodel.utils.RequestClientUtil;
import com.hnac.hzinfo.exception.HzServiceException;
@ -30,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.user.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@ -118,8 +121,12 @@ public class WeaviateService {
// }
public Boolean saveBatch(List entities,String className, Map<String,String> attrsMap) {
Optional modelId = entities.stream().filter(entity -> {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(entity));
return "1442295246932828161".equals(jsonObject.getString("modelId"));
}).findFirst();
// 查询Weaviate 4.7.0表是否存在 若不存则新建表
Result<Boolean> existResult = weaviateClient.schema().exists().withClassName("Hzn_lm_" + className).run();
Result<Boolean> existResult = weaviateClient.schema().exists().withClassName(BigModelConstants.PREFIX + "_" + className).run();
if(existResult.hasErrors() || !existResult.getResult()) {
Map<java.lang.String,Object> createTableParams = new HashMap<>(2);
Map<String,String> deleteTableParams = new HashMap<>(1);
@ -134,7 +141,13 @@ public class WeaviateService {
Map<String,Object> params = new HashMap<>(2);
params.put("table_name", className);
List<Map<String, Object>> data = new ArrayList<>();
entities.forEach(entity -> data.add(this.getVectorData(entity,attrsMap)));
entities.forEach(entity -> {
// 将entity转换为Map<String,String>
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(entity, SerializerFeature.WriteMapNullValue));
Map<String,String> map = new HashMap<>();
jsonObject.forEach((k,v) -> map.put(k,Optional.ofNullable(jsonObject.getString(k)).orElse("")));
data.add(this.getVectorData(map,attrsMap));
});
log.info("data:{}",JSON.toJSONString(data));
params.put("data",data);
String url = gglmUrl + invokeApi.getInsertVectors();
@ -142,14 +155,16 @@ public class WeaviateService {
return true;
}
private Map<String,Object> getVectorData(Object entity,Map<String,String> attrsMap) {
private Map<String,Object> getVectorData(Map<String,String> entity,Map<String,String> attrsMap) {
Map<String,Object> result = new HashMap<>(2);
result.put("object", entity);
List<Map<String,String>> vectors = new ArrayList<>();
attrsMap.forEach((k,fields) -> {
Map<String,String> vector = new HashMap<>();
vector.put("key",k);
vector.put("content", this.getFieldValue(fields, entity));
String value = Func.toStrList(",", fields).stream().map(field -> entity.get(field)).collect(Collectors.joining(" "));
vector.put("content", value);
vectors.add(vector);
});
result.put("vector", vectors);
@ -178,7 +193,7 @@ public class WeaviateService {
if(Func.isEmpty(ids) && Func.isNotEmpty(className)) {
// 删除className
Map<String,String> deleteTableParams = new HashMap<>(1);
deleteTableParams.put("table_name",className.replace("Hzn_lm_",""));
deleteTableParams.put("table_name",className.replace(BigModelConstants.PREFIX + "_",""));
RequestClientUtil.postCall(gglmUrl + invokeApi.getDeleteTable(),deleteTableParams);
} else {
// 删除记录

48
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/interactive/service/impl/CanvasResolveServiceImpl.java

@ -10,6 +10,9 @@ import com.hnac.gglm.bigmodel.interactive.vo.ExtraVO;
import com.hnac.hzims.operational.station.entity.StationEntity;
import com.hnac.hzims.operational.station.feign.IStationClient;
import com.hnac.hzinfo.exception.HzServiceException;
import com.hnac.hzinfo.sdk.core.response.Result;
import com.hnac.hzinfo.sdk.v5.scada.ScadaClient;
import com.hnac.hzinfo.sdk.v5.scada.vo.CanvasVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
@ -34,6 +37,7 @@ import java.util.stream.IntStream;
public class CanvasResolveServiceImpl implements IResolveService {
private final IStationClient stationClient;
private final ScadaClient scadaClient;
@Override
public ExtraVO resolve(ModelFunctionReq req) {
@ -49,41 +53,57 @@ public class CanvasResolveServiceImpl implements IResolveService {
@Override
public ExtraVO resolve(String id) {
List<String> params = Func.toStrList("\\^", id);
Long scadaId;
if(params.size() == 1) {
scadaId = Long.valueOf(id);
} else if(params.size() == 7) {
scadaId = Long.valueOf(params.get(6));
} else {
throw new HzServiceException("画面id格式错误,解析失败");
}
Result<CanvasVO> canvasById = scadaClient.getCanvasById(null, scadaId);
if(!canvasById.isSuccess()) {
throw new HzServiceException("获取画面信息失败");
}
CanvasVO canvasVO = canvasById.getData();
if(canvasVO == null) {
throw new HzServiceException("获取画面信息失败");
}
ExtraVO extraVO = new ExtraVO();
extraVO.setImmediatelyJump(true);
extraVO.setFunc(FuncRouteEnum.OPEN_CANVAS.getFunc());
final String[] SCADA_PARAMS_SOLVE = new String[]{"picResource","context","stationNum","projectId","taskId","name","id"};
String canvasHost = ParamCache.getValue(ParamKeyConstants.CANVAS_HOST);
// 将ID解析为
Map<String, String> resolveMap = this.resolve(id, SCADA_PARAMS_SOLVE);
R<StationEntity> stationR = stationClient.getStationByCode(resolveMap.get("projectId"));
extraVO.setLabel(Optional.ofNullable(stationR).filter(r -> r.isSuccess()).map(R::getData).map(StationEntity::getName).orElse("") + "_" + resolveMap.get("name"));
int picResource = Integer.parseInt(resolveMap.get("picResource"));
Map<String,Object> extraParams = new HashMap<>(1);
extraParams.put("picResource",picResource);
extraVO.setParams(extraParams);
int picSource = canvasVO.getPicSource();
// 将 canvasVO 转换为 Map
Map<String, String> resolveMap = JSON.parseObject(JSON.toJSONString(canvasVO), Map.class);
// 云组态
if(picResource == 0) {
if(picSource == 0) {
String path = this.replacePath(ParamCache.getValue(ParamKeyConstants.CANVAS_YZT_PATH), resolveMap);
extraVO.setRoute(canvasHost + path);
}
// v3.0
else if(picResource == 1) {
else if(picSource == 1) {
String path = this.replacePath(ParamCache.getValue(ParamKeyConstants.CANVAS_V3_PATH), resolveMap);
extraVO.setRoute(canvasHost + path);
}
// v4.0
else if (picResource == 2) {
else if (picSource == 2) {
// pic_name 去掉头部的/ 以及尾部的.js
String context = resolveMap.get("context");
context = this.removeHeadChars(context,"/");
context = this.removeTailChars(context,".js");
resolveMap.put("context",context);
extraVO.setRoute(this.replacePath("other/v4/canvas/index.html?hzinfowebkit=true&taskId={taskId}&stationNum={stationNum}&projectId={projectId}&source=app&picName={context}&itemName={itemName}", resolveMap));
extraVO.setRoute(canvasHost + this.replacePath("other/v4/canvas/index.html?hzinfowebkit=true&taskId={taskId}&stationNum={stationNum}&projectId={projectId}&source=app&picName={context}&name={name}", resolveMap));
}
else {
throw new HzServiceException(ResultCode.FAILURE,"解析出来的画面类型在云组态、v3、v4类型之外,无法解析路由");
}
R<StationEntity> stationR = stationClient.getStationByCode(canvasVO.getProjectId());
extraVO.setLabel(Optional.ofNullable(stationR).filter(r -> r.isSuccess()).map(R::getData).map(StationEntity::getName).orElse("") + "_" + resolveMap.get("name"));
Map<String,Object> extraParams = new HashMap<>(1);
extraParams.put("picResource", picSource);
extraVO.setParams(extraParams);
return extraVO;
}
@ -102,7 +122,7 @@ public class CanvasResolveServiceImpl implements IResolveService {
// 替换path中变量
for (Map.Entry<String, String> entry : entries) {
String replaceVariables = "{" + entry.getKey() + "}";
path = StringUtil.replace(path,replaceVariables,entry.getValue());
path = StringUtil.replace(path,replaceVariables,String.valueOf(entry.getValue()));
}
return path;
}

42
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/maintenance/service/DataRecordService.java

@ -17,6 +17,7 @@ import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springframework.stereotype.Service;
import java.util.List;
@ -81,26 +82,29 @@ public class DataRecordService extends BaseServiceImpl<DataRecordMapper, DataRec
public List<DeviceAttrDTO> getDataRecordList() {
List<DataRecordEntity> list = baseMapper.selectList(null);
List<DeviceAttrDTO> deviceAttrDTOS = BeanUtil.copyProperties(list, DeviceAttrDTO.class);
List<DeviceAttrInfoDTO> deviceAttrList = vectorDataService.getDeviceAttr();
if (deviceAttrList != null) {
for (DeviceAttrInfoDTO deviceAttr : deviceAttrList) {
DeviceAttrDTO deviceAttrDTO = new DeviceAttrDTO();
deviceAttrDTO.setDeviceId(deviceAttr.getDeviceId());
deviceAttrDTO.setDeviceName(deviceAttr.getDeviceName());
deviceAttrDTO.setStationName(deviceAttr.getProjectName());
deviceAttrDTO.setStationId(deviceAttr.getProjectId());
deviceAttrDTO.setRecordName(deviceAttr.getName());
deviceAttrDTO.setItemId(deviceAttr.getSignage());
String stationName = deviceAttrDTO.getStationName() == null ? "" : deviceAttrDTO.getStationName();
String deviceName = deviceAttrDTO.getDeviceName() == null ? "" : deviceAttrDTO.getDeviceName();
String recordName = deviceAttrDTO.getRecordName() == null ? "" : deviceAttrDTO.getRecordName();
deviceAttrDTO.setItemName(new StringBuffer(stationName)
.append(" ").append(deviceName)
.append(" ").append(recordName).toString());
deviceAttrDTO.setRule(deviceAttr.getRule());
deviceAttrDTOS.add(deviceAttrDTO);
}
List<DeviceAttrDTO> deviceAttrList = vectorDataService.getDeviceAttr();
if(Func.isNotEmpty(deviceAttrList)) {
deviceAttrDTOS.addAll(deviceAttrList);
}
// if (deviceAttrList != null) {
// for (DeviceAttrInfoDTO deviceAttr : deviceAttrList) {
// DeviceAttrDTO deviceAttrDTO = new DeviceAttrDTO();
// deviceAttrDTO.setDeviceId(deviceAttr.getDeviceId());
// deviceAttrDTO.setDeviceName(deviceAttr.getDeviceName());
// deviceAttrDTO.setStationName(deviceAttr.getProjectName());
// deviceAttrDTO.setStationId(deviceAttr.getProjectId());
// deviceAttrDTO.setRecordName(deviceAttr.getName());
// deviceAttrDTO.setItemId(deviceAttr.getSignage());
// String stationName = deviceAttrDTO.getStationName() == null ? "" : deviceAttrDTO.getStationName();
// String deviceName = deviceAttrDTO.getDeviceName() == null ? "" : deviceAttrDTO.getDeviceName();
// String recordName = deviceAttrDTO.getRecordName() == null ? "" : deviceAttrDTO.getRecordName();
// deviceAttrDTO.setItemName(new StringBuffer(stationName)
// .append(" ").append(deviceName)
// .append(" ").append(recordName).toString());
// deviceAttrDTO.setRule(deviceAttr.getRule());
// deviceAttrDTOS.add(deviceAttrDTO);
// }
// }
return deviceAttrDTOS;
}
}

5
hzims-service/gglm-big-model/src/main/java/com/hnac/gglm/bigmodel/utils/RequestClientUtil.java

@ -5,6 +5,7 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hnac.hzinfo.exception.HzServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.ResultCode;
@ -43,7 +44,7 @@ public class RequestClientUtil {
* @param body 传参body
*/
public static void postCall(String url, Map body) {
HttpResponse response = HttpRequest.post(url).body(JSON.toJSONString(body)).execute();
HttpResponse response = HttpRequest.post(url).body(JSON.toJSONString(body, SerializerFeature.WriteMapNullValue)).execute();
if(Func.isNotEmpty(response.body()) && !"[]".equals(response.body())) {
log.info("接口调用结果为:{}",response.body());
}
@ -61,7 +62,7 @@ public class RequestClientUtil {
* @param <T> 结果解析对象类型
*/
public static <T> T postCall(String url, Map body, TypeReference<T> typeRef) {
HttpResponse response = HttpRequest.post(url).body(JSON.toJSONString(body)).execute();
HttpResponse response = HttpRequest.post(url).body(JSON.toJSONString(body, SerializerFeature.WriteMapNullValue)).execute();
Assert.isTrue(response.getStatus() == HttpServletResponse.SC_OK, () -> {
throw new HzServiceException(ResultCode.FAILURE, "远程调用大模型接口" + url + "失败!");
});

2
hzims-service/operational/src/main/java/com/hnac/hzims/operational/station/mapper/StationVideoTypeMapper.xml

@ -78,7 +78,7 @@
hsvt.`name` AS video_name,
hsvt.station_id AS station_id,
hs.`name` AS station_name,
concat(hs.`code`, ' ', hs.`name`) AS item_name
concat(hs.`name`, ' ', hsvt.`name`) AS item_name
FROM `hzims_station_video_type` hsvt
LEFT JOIN hzims_station hs ON hsvt.station_id = hs.`code`
WHERE

Loading…
Cancel
Save