fix (farm): 生产计划
- 养殖类租户,打印网箱能力.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.farm.controller.admin.managementcenter.baseinfo;
|
||||
|
||||
import cn.iocoder.yudao.module.farm.controller.admin.managementcenter.baseinfo.vo.*;
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.JbyPrintManager;
|
||||
import cn.iocoder.yudao.module.farm.service.plotinfo.PlotLesseeService;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -46,6 +47,8 @@ public class PlotInfoController {
|
||||
private PlotInfoService plotInfoService;
|
||||
@Resource
|
||||
private PlotLesseeService plotLesseeService;
|
||||
@Resource
|
||||
private JbyPrintManager printManager;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建地块")
|
||||
@@ -129,9 +132,9 @@ public class PlotInfoController {
|
||||
@GetMapping("/grid-code/print")
|
||||
@Operation(summary = "网箱二维码打印")
|
||||
@Parameter(name = "planNumber", description = "生产计划编号", required = true)
|
||||
public CommonResult<Boolean> gridPrint(@RequestParam("planNumber") String planNumber) {
|
||||
|
||||
return success(true);
|
||||
public CommonResult<String> gridPrint(@RequestParam("planNumber") String planNumber) {
|
||||
String msg = printManager.printGridCode(planNumber);
|
||||
return success(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer;
|
||||
|
||||
import cn.iocoder.yudao.module.farm.controller.admin.cropfarmingcenter.vo.grid.ProductionPlanGridRespVO;
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.dto.PrintReqDTO;
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.dto.PrintTask;
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.utils.JbyPrintUtil;
|
||||
import cn.iocoder.yudao.module.farm.service.productionplan.ProductionPlanService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 13:50
|
||||
*/
|
||||
@Component
|
||||
public class JbyPrintManager {
|
||||
|
||||
private final JbyPrintUtil jbyPrintUtil;
|
||||
private final ProductionPlanService productionPlanService;
|
||||
|
||||
public JbyPrintManager(JbyPrintUtil jbyPrintUtil, ProductionPlanService productionPlanService) {
|
||||
this.jbyPrintUtil = jbyPrintUtil;
|
||||
this.productionPlanService = productionPlanService;
|
||||
}
|
||||
|
||||
public String printGridCode(String planNumber) {
|
||||
ProductionPlanGridRespVO gridForPlan = productionPlanService.getGridForPlan(planNumber);
|
||||
List<ProductionPlanGridRespVO.Row> rows = gridForPlan.getRows();
|
||||
List<PrintTask> taskList = rows.stream()
|
||||
.map(e -> new PrintTask(e.getGridCode(), new PrintReqDTO.PlanData(planNumber, e.getGridCode())))
|
||||
.toList();
|
||||
|
||||
return jbyPrintUtil.batchPrint(taskList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.conf;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @description: TODO
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 14:21
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "jby")
|
||||
public class PrintConf {
|
||||
|
||||
private String hosts;
|
||||
private String apiKey;
|
||||
private String memberCode;
|
||||
private String templateId;
|
||||
private String deviceId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.conf;
|
||||
|
||||
/**
|
||||
* @description: 佳博云打印机
|
||||
* @URL https://dev.poscom.cn/openapi?sendMsg
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 14:30
|
||||
*/
|
||||
public class PrintConstants {
|
||||
|
||||
/**
|
||||
* 打印信息的格式类型。取值范围[2-3] (无空格,如十六进制 0x12 0xAB 0xCD 0xEF,发送内容 12ABCDEF)
|
||||
* mode=2,自由格式打印(推荐使用)
|
||||
* mode=3,十六进制命令集或十六进制字符串打印
|
||||
*/
|
||||
private final String MODE_2 = "2";
|
||||
private final String MODE_3 = "3";
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.dto;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @description: 打印机请求参数
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 14:05
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PrintReqDTO {
|
||||
|
||||
/**
|
||||
* 当前UNIX时间戳。13位,精确到毫秒。
|
||||
*/
|
||||
private String reqTime;
|
||||
private String securityCode;
|
||||
private String memberCode;
|
||||
private String deviceID;
|
||||
private String templetID;
|
||||
private String tData;
|
||||
|
||||
|
||||
/**
|
||||
* 编码格式,取值范围[1-10] 默认1。
|
||||
* 1: GB18030
|
||||
* 2: GB2312
|
||||
* 3: GBK
|
||||
* 4: UTF-8
|
||||
* 5: Unicode
|
||||
* 6: ISO8859-1
|
||||
* 7: BIG5
|
||||
* 8: EUC-KR
|
||||
* 9: TIS-620
|
||||
* 10: VISCII
|
||||
*/
|
||||
private String charset;
|
||||
/**
|
||||
*业务ID 一般由用户生成,用于跟踪订单状态
|
||||
*/
|
||||
private String msgNo;
|
||||
|
||||
/**
|
||||
* 是否重新打印。取值范围[0-1] 0:否,1:是。默认值0。
|
||||
* 若是重新打印则系统允许 msgNo 字段重复,但不记录订单
|
||||
*/
|
||||
private String reprint;
|
||||
|
||||
private String times;
|
||||
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class PlanData {
|
||||
private String planNumber;
|
||||
private String gridCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @description: TODO
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 15:16
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PrintRespDTO {
|
||||
private Integer code;
|
||||
private String msg;
|
||||
private Object data;
|
||||
private String serverExecTime;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 15:31
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PrintTask {
|
||||
private String msgNo;
|
||||
private PrintReqDTO.PlanData data;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.utils;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.conf.PrintConf;
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.dto.PrintReqDTO;
|
||||
import cn.iocoder.yudao.module.farm.manager.printer.dto.PrintTask;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author
|
||||
* @description
|
||||
* @date 2025/7/18
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JbyPrintUtil {
|
||||
|
||||
private final PrintConf printConf;
|
||||
private final PrintServerHttpClientUtil httpClientUtil;
|
||||
|
||||
public JbyPrintUtil(PrintConf printConf, PrintServerHttpClientUtil httpClientUtil) {
|
||||
this.printConf = printConf;
|
||||
this.httpClientUtil = httpClientUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量打印方法
|
||||
*/
|
||||
public String batchPrint(List<PrintTask> tasks) {
|
||||
if (tasks == null || tasks.isEmpty()) {
|
||||
return buildSuccessResult("无打印任务");
|
||||
}
|
||||
|
||||
log.info("开始批量打印,任务数量: {}", tasks.size());
|
||||
int successCount = 0;
|
||||
|
||||
for (PrintTask task : tasks) {
|
||||
try {
|
||||
String result = print(
|
||||
task.getMsgNo(),
|
||||
printConf.getDeviceId(),
|
||||
printConf.getTemplateId(),
|
||||
1L,
|
||||
task.getData()
|
||||
);
|
||||
|
||||
// 检查API返回结果
|
||||
if (!isSuccessResult(result)) {
|
||||
log.error("打印API返回错误,停止批量打印: msgNo={}, result={}", task.getMsgNo(), result);
|
||||
return buildErrorResultFromResponse(result);
|
||||
}
|
||||
|
||||
successCount++;
|
||||
log.info("打印任务成功: msgNo={}", task.getMsgNo());
|
||||
|
||||
} catch (Exception e) {
|
||||
// print方法异常,立即返回
|
||||
log.error("打印任务异常,停止批量打印: msgNo={}", task.getMsgNo(), e);
|
||||
return buildErrorResult("打印系统异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("批量打印全部成功,共{}条任务", successCount);
|
||||
return buildSuccessResult("成功打印" + successCount + "个任务");
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条打印
|
||||
*
|
||||
* @param msgNo
|
||||
* @param deviceID
|
||||
* @param templateId
|
||||
* @param times
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public String print(String msgNo,
|
||||
String deviceID,
|
||||
String templateId,
|
||||
Long times,
|
||||
PrintReqDTO.PlanData data) {
|
||||
try {
|
||||
String reqTime = String.valueOf(System.currentTimeMillis());
|
||||
|
||||
String securityCode = generateSecurityCode(deviceID, msgNo, reqTime);
|
||||
|
||||
// 构建符合服务商要求的参数Map
|
||||
Map<String, String> params = buildPrintParams(msgNo, deviceID, templateId, times, data, reqTime, securityCode);
|
||||
|
||||
log.info("发送打印请求: msgNo={}, deviceID={}, template={}", msgNo, deviceID, templateId);
|
||||
log.info("请求参数: {}", params);
|
||||
|
||||
// 使用表单格式发送请求
|
||||
String result = httpClientUtil.postForm(printConf.getHosts(), params);
|
||||
|
||||
log.info("打印响应: {}", result);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("打印请求失败: msgNo={}, deviceID={}, error={}", msgNo, deviceID, e.getMessage(), e);
|
||||
throw new RuntimeException("打印服务调用失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建符合服务商要求的参数Map
|
||||
*/
|
||||
private Map<String, String> buildPrintParams(String msgNo, String deviceID, String template,
|
||||
Long printTime, PrintReqDTO.PlanData data,
|
||||
String reqTime, String securityCode) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
|
||||
// 严格按照服务商示例的参数名和顺序
|
||||
params.put("reqTime", reqTime);
|
||||
params.put("securityCode", securityCode);
|
||||
params.put("memberCode", printConf.getMemberCode());
|
||||
params.put("templetID", template);
|
||||
params.put("deviceID", deviceID != null ? deviceID : "undefined");
|
||||
params.put("token", "");
|
||||
params.put("tData", data != null ? JSON.toJSONString(data) : "");
|
||||
params.put("reprint", "0");
|
||||
params.put("multi", "0"); // 服务商示例中有这个参数
|
||||
params.put("msgNo", msgNo != null ? msgNo : "");
|
||||
params.put("charset", "1");
|
||||
params.put("times", printTime != null ? printTime.toString() : "1");
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成安全码
|
||||
*/
|
||||
private String generateSecurityCode(String deviceID, String msgNo, String reqTime) {
|
||||
String source = printConf.getMemberCode() + deviceID + msgNo + reqTime + printConf.getApiKey();
|
||||
return DigestUtils.md5Hex(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建成功结果
|
||||
*/
|
||||
private String buildSuccessResult(String message) {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("code", 0);
|
||||
result.put("msg", message);
|
||||
result.put("success", true);
|
||||
return result.toJSONString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建错误结果(自定义错误信息)
|
||||
*/
|
||||
private String buildErrorResult(String errorMessage) {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("code", -1);
|
||||
result.put("msg", errorMessage);
|
||||
result.put("success", false);
|
||||
return result.toJSONString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从API响应中构建错误结果
|
||||
*/
|
||||
private String buildErrorResultFromResponse(String apiResponse) {
|
||||
try {
|
||||
JSONObject responseJson = JSON.parseObject(apiResponse);
|
||||
Integer code = responseJson.getInteger("code");
|
||||
String msg = responseJson.getString("msg");
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("code", code != null ? code : -1);
|
||||
result.put("msg", msg != null ? msg : "打印服务返回错误");
|
||||
result.put("success", false);
|
||||
result.put("originalResponse", apiResponse); // 保留原始响应
|
||||
|
||||
return result.toJSONString();
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,使用自定义错误信息
|
||||
return buildErrorResult("打印服务返回异常结果: " + apiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断API响应是否成功
|
||||
*/
|
||||
private boolean isSuccessResult(String result) {
|
||||
try {
|
||||
JSONObject json = JSON.parseObject(result);
|
||||
Integer code = json.getInteger("code");
|
||||
return code != null && code == 0;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package cn.iocoder.yudao.module.farm.manager.printer.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description: 打印机 httpClient 氢气工具
|
||||
* @author: binda
|
||||
* @date: 2025/11/14 15:22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PrintServerHttpClientUtil {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public PrintServerHttpClientUtil(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
private RestTemplate createRestTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// 设置消息转换器
|
||||
restTemplate.setMessageConverters(Arrays.asList(
|
||||
new StringHttpMessageConverter(StandardCharsets.UTF_8),
|
||||
new MappingJackson2HttpMessageConverter()
|
||||
));
|
||||
|
||||
// 设置请求工厂
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
||||
factory.setConnectionRequestTimeout(5000);
|
||||
factory.setConnectTimeout(5000);
|
||||
factory.setReadTimeout(10000);
|
||||
restTemplate.setRequestFactory(factory);
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求 - 发送JSON数据
|
||||
*/
|
||||
public <T> T postForObject(String url, Object request, Class<T> responseType) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
|
||||
HttpEntity<Object> entity = new HttpEntity<>(request, headers);
|
||||
|
||||
return restTemplate.postForObject(url, entity, responseType);
|
||||
} catch (RestClientException e) {
|
||||
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求 - 发送表单数据(佳博云API需要表单格式)
|
||||
*/
|
||||
public String postForm(String url, Map<String, String> params) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
params.forEach(formData::add);
|
||||
|
||||
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(formData, headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
|
||||
return response.getBody();
|
||||
|
||||
} catch (RestClientException e) {
|
||||
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
*/
|
||||
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) {
|
||||
try {
|
||||
return restTemplate.getForObject(url, responseType, uriVariables);
|
||||
} catch (RestClientException e) {
|
||||
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求 - 带查询参数
|
||||
*/
|
||||
public <T> T getForObject(String url, Class<T> responseType, Map<String, Object> params) {
|
||||
try {
|
||||
if (params != null && !params.isEmpty()) {
|
||||
StringBuilder urlBuilder = new StringBuilder(url);
|
||||
urlBuilder.append("?");
|
||||
params.forEach((key, value) ->
|
||||
urlBuilder.append(key).append("=").append(value).append("&"));
|
||||
url = urlBuilder.substring(0, urlBuilder.length() - 1);
|
||||
}
|
||||
return restTemplate.getForObject(url, responseType);
|
||||
} catch (RestClientException e) {
|
||||
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带异常处理的POST请求
|
||||
*/
|
||||
public <T> T postWithExceptionHandler(String url, Object request, Class<T> responseType) {
|
||||
try {
|
||||
return postForObject(url, request, responseType);
|
||||
} catch (Exception e) {
|
||||
// 这里可以记录日志或进行其他异常处理
|
||||
throw new RuntimeException("调用外部服务失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user