新增MES模块,包含供应商、客户、车辆和地磅数据记录管理功能,支持免密接口和数据同步。更新相关控制器、实体、服务和数据库配置,优化权限管理和数据字典支持,确保系统的灵活性和可扩展性。
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
-- 客户端连接列表权限初始化(可直接执行,带防重复)
|
||||
-- 权限标识:xslmes:mes_xsl_client_connection:list
|
||||
|
||||
-- 1) 请按你们实际菜单树确认 parent_id
|
||||
-- 优先使用你指定的父菜单 ID;若不指定,脚本会优先挂到“MES基础资料”菜单下
|
||||
SET @input_parent_id = NULL;
|
||||
SET @parent_id = COALESCE(
|
||||
@input_parent_id,
|
||||
(SELECT id FROM sys_permission WHERE name = 'MES基础资料' LIMIT 1),
|
||||
(SELECT id FROM sys_permission WHERE perms = 'xslmes' LIMIT 1)
|
||||
);
|
||||
|
||||
-- 2) 新增菜单(目录/菜单),避免重复插入
|
||||
SET @menu_url = '/xslmes/mesXslClientConnection/list';
|
||||
SET @menu_name = '客户端连接列表';
|
||||
SET @menu_id = (
|
||||
SELECT id FROM sys_permission
|
||||
WHERE url = @menu_url AND menu_type = '1'
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_permission (
|
||||
id, parent_id, name, perms, perms_type, menu_type, url, component, sort_no, status, del_flag, create_by, create_time
|
||||
)
|
||||
SELECT
|
||||
REPLACE(UUID(), '-', ''), @parent_id, @menu_name, NULL, '1', '1',
|
||||
@menu_url, 'xslmes/mesXslClientConnection/MesXslClientConnectionList', 100, '1', 0, 'admin', NOW()
|
||||
FROM dual
|
||||
WHERE @menu_id IS NULL AND @parent_id IS NOT NULL;
|
||||
|
||||
SET @menu_id = (
|
||||
SELECT id FROM sys_permission
|
||||
WHERE url = @menu_url AND menu_type = '1'
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- 2.1) 若菜单已存在,则强制迁移到“MES基础资料”父菜单,并修正组件路径
|
||||
UPDATE sys_permission
|
||||
SET parent_id = @parent_id,
|
||||
component = 'xslmes/mesXslClientConnection/MesXslClientConnectionList',
|
||||
update_by = 'admin',
|
||||
update_time = NOW()
|
||||
WHERE id = @menu_id
|
||||
AND @menu_id IS NOT NULL
|
||||
AND @parent_id IS NOT NULL;
|
||||
|
||||
-- 3) 新增按钮权限(接口权限点),避免重复插入
|
||||
INSERT INTO sys_permission (
|
||||
id, parent_id, name, perms, perms_type, menu_type, url, component, sort_no, status, del_flag, create_by, create_time
|
||||
)
|
||||
SELECT
|
||||
REPLACE(UUID(), '-', ''), @menu_id, '查询', 'xslmes:mes_xsl_client_connection:list', '1', '2',
|
||||
NULL, NULL, 1, '1', 0, 'admin', NOW()
|
||||
FROM dual
|
||||
WHERE @menu_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_permission WHERE perms = 'xslmes:mes_xsl_client_connection:list' LIMIT 1
|
||||
);
|
||||
|
||||
-- 4) 如果 @parent_id 为空,表示没找到 xslmes 菜单,需要你手工设置 @input_parent_id 再执行
|
||||
SELECT @parent_id AS resolved_parent_id, @menu_id AS resolved_menu_id;
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.xslmes.model.MesXslClientConnectionDTO;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslClientConnectionStore;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MES 客户端连接列表
|
||||
*/
|
||||
@Tag(name = "MES客户端连接管理")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslClientConnection")
|
||||
public class MesXslClientConnectionController {
|
||||
|
||||
private final IMesXslClientConnectionStore connectionStore;
|
||||
|
||||
public MesXslClientConnectionController(IMesXslClientConnectionStore connectionStore) {
|
||||
this.connectionStore = connectionStore;
|
||||
}
|
||||
|
||||
@Operation(summary = "客户端连接列表查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_client_connection:list")
|
||||
@GetMapping("/list")
|
||||
public Result<Map<String, Object>> list(
|
||||
@RequestParam(name = "type", required = false, defaultValue = "all") String type,
|
||||
@RequestParam(name = "pageNo", required = false, defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", required = false, defaultValue = "20") Integer pageSize) {
|
||||
List<MesXslClientConnectionDTO> all = connectionStore.listByType(type);
|
||||
int safePageNo = pageNo == null || pageNo < 1 ? 1 : pageNo;
|
||||
int safePageSize = pageSize == null || pageSize < 1 ? 20 : pageSize;
|
||||
int fromIndex = (safePageNo - 1) * safePageSize;
|
||||
int total = all.size();
|
||||
List<MesXslClientConnectionDTO> records;
|
||||
if (fromIndex >= total) {
|
||||
records = List.of();
|
||||
} else {
|
||||
int toIndex = Math.min(fromIndex + safePageSize, total);
|
||||
records = all.subList(fromIndex, toIndex);
|
||||
}
|
||||
Map<String, Object> page = new HashMap<>(8);
|
||||
page.put("records", records);
|
||||
page.put("total", total);
|
||||
page.put("current", safePageNo);
|
||||
page.put("size", safePageSize);
|
||||
page.put("pages", safePageSize == 0 ? 0 : (total + safePageSize - 1) / safePageSize);
|
||||
return Result.OK(page);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslCustomerService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -45,6 +46,9 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
@Autowired
|
||||
private IMesXslCustomerService mesXslCustomerService;
|
||||
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Operation(summary = "MES客户管理-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslCustomer>> queryPageList(
|
||||
@@ -82,6 +86,7 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
}
|
||||
mesXslCustomerService.syncIzEnableWithStatus(mesXslCustomer);
|
||||
mesXslCustomerService.save(mesXslCustomer);
|
||||
stompNotify.publishCustomerChanged("add", mesXslCustomer.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@@ -107,6 +112,7 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
}
|
||||
mesXslCustomerService.syncIzEnableWithStatus(mesXslCustomer);
|
||||
mesXslCustomerService.updateById(mesXslCustomer);
|
||||
stompNotify.publishCustomerChanged("edit", mesXslCustomer.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@@ -131,11 +137,12 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
.set(MesXslCustomer::getIzEnable, izEnable)
|
||||
.update();
|
||||
if (updated) {
|
||||
stompNotify.publishCustomerChanged("status", id);
|
||||
return Result.OK("操作成功");
|
||||
}
|
||||
// MySQL 在 SET 值与库中完全一致时可能返回 0 行;或需二次确认是否已是目标状态
|
||||
MesXslCustomer cur = mesXslCustomerService.getById(id);
|
||||
if (cur != null && Objects.equals(status, cur.getStatus())) {
|
||||
stompNotify.publishCustomerChanged("status", id);
|
||||
return Result.OK("操作成功");
|
||||
}
|
||||
return Result.error("操作失败,请确认记录存在且租户与当前登录一致(tenant_id 勿为空)");
|
||||
@@ -147,6 +154,7 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslCustomerService.removeById(id);
|
||||
stompNotify.publishCustomerChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@@ -156,6 +164,7 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
mesXslCustomerService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishCustomerChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@@ -263,4 +272,5 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
|
||||
}
|
||||
return Result.error("文件导入失败!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslSupplier;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslVehicle;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslCustomerService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslSupplierService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslVehicleService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 桌面端免密接口 — 统一入口
|
||||
*
|
||||
* <p>所有供 WPF 桌面端(匿名模式)调用的接口集中于此,便于统一维护权限白名单和版本演进。
|
||||
* ShiroConfig 白名单:
|
||||
* /xslmes/mesXslVehicle/anon/**
|
||||
* /xslmes/mesXslCustomer/anon/**
|
||||
*/
|
||||
@Tag(name = "桌面端免密接口")
|
||||
@RestController
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MesXslDesktopAnonController {
|
||||
|
||||
private final IMesXslVehicleService vehicleService;
|
||||
private final IMesXslCustomerService customerService;
|
||||
private final IMesXslSupplierService supplierService;
|
||||
private final MesXslStompNotifyService stompNotify;
|
||||
|
||||
// ═══════════════════════════ 车辆管理 ═══════════════════════════
|
||||
|
||||
@Operation(summary = "车辆-免密分页列表查询")
|
||||
@GetMapping("/xslmes/mesXslVehicle/anon/list")
|
||||
public Result<IPage<MesXslVehicle>> vehicleAnonList(
|
||||
MesXslVehicle mesXslVehicle,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslVehicle> qw = QueryGenerator.initQueryWrapper(mesXslVehicle, req.getParameterMap());
|
||||
IPage<MesXslVehicle> page = vehicleService.page(new Page<>(pageNo, pageSize), qw);
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "车辆-免密通过id查询")
|
||||
@GetMapping("/xslmes/mesXslVehicle/anon/queryById")
|
||||
public Result<MesXslVehicle> vehicleAnonQueryById(@RequestParam(name = "id") String id) {
|
||||
MesXslVehicle entity = vehicleService.getById(id);
|
||||
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
|
||||
}
|
||||
|
||||
@Operation(summary = "车辆-免密添加")
|
||||
@PostMapping("/xslmes/mesXslVehicle/anon/add")
|
||||
public Result<String> vehicleAnonAdd(@RequestBody MesXslVehicle mesXslVehicle) {
|
||||
if (oConvertUtils.isEmpty(mesXslVehicle.getVehicleBelong())) {
|
||||
return Result.error("车辆归属不能为空");
|
||||
}
|
||||
if (mesXslVehicle.getStatus() == null || mesXslVehicle.getStatus().isEmpty()) {
|
||||
mesXslVehicle.setStatus("0");
|
||||
}
|
||||
applyVehicleBelong(mesXslVehicle);
|
||||
vehicleService.save(mesXslVehicle);
|
||||
stompNotify.publishVehicleChanged("add", mesXslVehicle.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "车辆-免密编辑")
|
||||
@RequestMapping(value = "/xslmes/mesXslVehicle/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> vehicleAnonEdit(@RequestBody MesXslVehicle mesXslVehicle) {
|
||||
if (oConvertUtils.isEmpty(mesXslVehicle.getVehicleBelong())) {
|
||||
return Result.error("车辆归属不能为空");
|
||||
}
|
||||
applyVehicleBelong(mesXslVehicle);
|
||||
boolean ok = vehicleService.updateById(mesXslVehicle);
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
}
|
||||
forceNullOppositeVehicleFieldsInDb(mesXslVehicle.getId(), mesXslVehicle.getVehicleBelong());
|
||||
stompNotify.publishVehicleChanged("edit", mesXslVehicle.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "车辆-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslVehicle/anon/delete")
|
||||
public Result<String> vehicleAnonDelete(@RequestParam(name = "id") String id) {
|
||||
vehicleService.removeById(id);
|
||||
stompNotify.publishVehicleChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "车辆-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslVehicle/anon/deleteBatch")
|
||||
public Result<String> vehicleAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
vehicleService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishVehicleChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "车辆-免密停用/启用")
|
||||
@PostMapping("/xslmes/mesXslVehicle/anon/updateStatus")
|
||||
public Result<String> vehicleAnonUpdateStatus(
|
||||
@RequestParam(name = "id") String id,
|
||||
@RequestParam(name = "status") String status) {
|
||||
if (!"0".equals(status) && !"1".equals(status)) {
|
||||
return Result.error("状态参数非法");
|
||||
}
|
||||
boolean ok = vehicleService.lambdaUpdate()
|
||||
.eq(MesXslVehicle::getId, id)
|
||||
.set(MesXslVehicle::getStatus, status)
|
||||
.update();
|
||||
if (ok) {
|
||||
stompNotify.publishVehicleChanged("status", id);
|
||||
}
|
||||
return ok ? Result.OK("操作成功") : Result.error("操作失败");
|
||||
}
|
||||
|
||||
// ═══════════════════════════ 客户管理 ═══════════════════════════
|
||||
|
||||
@Operation(summary = "客户-免密分页列表查询")
|
||||
@GetMapping("/xslmes/mesXslCustomer/anon/list")
|
||||
public Result<IPage<MesXslCustomer>> customerAnonList(
|
||||
MesXslCustomer mesXslCustomer,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslCustomer> qw = QueryGenerator.initQueryWrapper(mesXslCustomer, req.getParameterMap());
|
||||
IPage<MesXslCustomer> page = customerService.page(new Page<>(pageNo, pageSize), qw);
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密通过id查询")
|
||||
@GetMapping("/xslmes/mesXslCustomer/anon/queryById")
|
||||
public Result<MesXslCustomer> customerAnonQueryById(@RequestParam(name = "id") String id) {
|
||||
MesXslCustomer entity = customerService.getById(id);
|
||||
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密添加")
|
||||
@PostMapping("/xslmes/mesXslCustomer/anon/add")
|
||||
public Result<String> customerAnonAdd(@RequestBody MesXslCustomer mesXslCustomer) {
|
||||
if (oConvertUtils.isEmpty(mesXslCustomer.getCustomerCode())) {
|
||||
return Result.error("客户编码不能为空");
|
||||
}
|
||||
String code = mesXslCustomer.getCustomerCode().trim();
|
||||
mesXslCustomer.setCustomerCode(code);
|
||||
if (customerService.existsSameCustomerCode(code, null)) {
|
||||
return Result.error("客户编码已存在,不允许重复");
|
||||
}
|
||||
String st = mesXslCustomer.getStatus();
|
||||
if (st != null) {
|
||||
st = st.trim();
|
||||
mesXslCustomer.setStatus(st.isEmpty() ? null : st);
|
||||
}
|
||||
if (mesXslCustomer.getStatus() == null || mesXslCustomer.getStatus().isEmpty()) {
|
||||
mesXslCustomer.setStatus(MesXslCustomerBizStatus.ENABLED);
|
||||
}
|
||||
customerService.syncIzEnableWithStatus(mesXslCustomer);
|
||||
customerService.save(mesXslCustomer);
|
||||
stompNotify.publishCustomerChanged("add", mesXslCustomer.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密编辑")
|
||||
@RequestMapping(value = "/xslmes/mesXslCustomer/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> customerAnonEdit(@RequestBody MesXslCustomer mesXslCustomer) {
|
||||
if (oConvertUtils.isEmpty(mesXslCustomer.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
if (oConvertUtils.isEmpty(mesXslCustomer.getCustomerCode())) {
|
||||
return Result.error("客户编码不能为空");
|
||||
}
|
||||
String code = mesXslCustomer.getCustomerCode().trim();
|
||||
mesXslCustomer.setCustomerCode(code);
|
||||
if (customerService.existsSameCustomerCode(code, mesXslCustomer.getId())) {
|
||||
return Result.error("客户编码已存在,不允许重复");
|
||||
}
|
||||
if (mesXslCustomer.getStatus() != null) {
|
||||
String s = mesXslCustomer.getStatus().trim();
|
||||
mesXslCustomer.setStatus(s.isEmpty() ? null : s);
|
||||
}
|
||||
customerService.syncIzEnableWithStatus(mesXslCustomer);
|
||||
boolean ok = customerService.updateById(mesXslCustomer);
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishCustomerChanged("edit", mesXslCustomer.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslCustomer/anon/delete")
|
||||
public Result<String> customerAnonDelete(@RequestParam(name = "id") String id) {
|
||||
customerService.removeById(id);
|
||||
stompNotify.publishCustomerChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslCustomer/anon/deleteBatch")
|
||||
public Result<String> customerAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
customerService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishCustomerChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密启用/停用")
|
||||
@PostMapping("/xslmes/mesXslCustomer/anon/updateStatus")
|
||||
public Result<String> customerAnonUpdateStatus(
|
||||
@RequestParam(name = "id") String id,
|
||||
@RequestParam(name = "status") String status) {
|
||||
if (status != null) {
|
||||
status = status.trim();
|
||||
}
|
||||
if (!MesXslCustomerBizStatus.ENABLED.equals(status) && !MesXslCustomerBizStatus.DISABLED.equals(status)) {
|
||||
return Result.error("状态参数非法");
|
||||
}
|
||||
int izEnable = MesXslCustomerBizStatus.DISABLED.equals(status) ? 0 : 1;
|
||||
boolean updated = customerService.lambdaUpdate()
|
||||
.eq(MesXslCustomer::getId, id)
|
||||
.set(MesXslCustomer::getStatus, status)
|
||||
.set(MesXslCustomer::getIzEnable, izEnable)
|
||||
.update();
|
||||
if (updated) {
|
||||
stompNotify.publishCustomerChanged("status", id);
|
||||
return Result.OK("操作成功");
|
||||
}
|
||||
MesXslCustomer cur = customerService.getById(id);
|
||||
if (cur != null && Objects.equals(status, cur.getStatus())) {
|
||||
stompNotify.publishCustomerChanged("status", id);
|
||||
return Result.OK("操作成功");
|
||||
}
|
||||
return Result.error("操作失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "客户-免密校验客户编码是否重复")
|
||||
@GetMapping("/xslmes/mesXslCustomer/anon/checkCustomerCode")
|
||||
public Result<String> customerAnonCheckCustomerCode(
|
||||
@RequestParam(name = "customerCode") String customerCode,
|
||||
@RequestParam(name = "dataId", required = false) String dataId) {
|
||||
if (oConvertUtils.isEmpty(customerCode) || customerCode.trim().isEmpty()) {
|
||||
return Result.OK("该值可用!");
|
||||
}
|
||||
if (customerService.existsSameCustomerCode(customerCode.trim(), dataId)) {
|
||||
return Result.error("该客户编码已存在");
|
||||
}
|
||||
return Result.OK("该值可用!");
|
||||
}
|
||||
|
||||
// ═══════════════════════════ 供应商管理 ═══════════════════════════
|
||||
|
||||
@Operation(summary = "供应商-免密分页列表查询")
|
||||
@GetMapping("/xslmes/mesXslSupplier/anon/list")
|
||||
public Result<IPage<MesXslSupplier>> supplierAnonList(
|
||||
MesXslSupplier mesXslSupplier,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslSupplier> qw = QueryGenerator.initQueryWrapper(mesXslSupplier, req.getParameterMap());
|
||||
IPage<MesXslSupplier> page = supplierService.page(new Page<>(pageNo, pageSize), qw);
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "供应商-免密通过id查询")
|
||||
@GetMapping("/xslmes/mesXslSupplier/anon/queryById")
|
||||
public Result<MesXslSupplier> supplierAnonQueryById(@RequestParam(name = "id") String id) {
|
||||
MesXslSupplier entity = supplierService.getById(id);
|
||||
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
|
||||
}
|
||||
|
||||
@Operation(summary = "供应商-免密添加")
|
||||
@PostMapping("/xslmes/mesXslSupplier/anon/add")
|
||||
public Result<String> supplierAnonAdd(@RequestBody MesXslSupplier mesXslSupplier) {
|
||||
if (oConvertUtils.isEmpty(mesXslSupplier.getSupplierCode())) {
|
||||
return Result.error("供应商编码不能为空");
|
||||
}
|
||||
if (mesXslSupplier.getStatus() == null || mesXslSupplier.getStatus().isEmpty()) {
|
||||
mesXslSupplier.setStatus("0");
|
||||
}
|
||||
supplierService.save(mesXslSupplier);
|
||||
stompNotify.publishSupplierChanged("add", mesXslSupplier.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "供应商-免密编辑")
|
||||
@RequestMapping(value = "/xslmes/mesXslSupplier/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> supplierAnonEdit(@RequestBody MesXslSupplier mesXslSupplier) {
|
||||
boolean ok = supplierService.updateById(mesXslSupplier);
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishSupplierChanged("edit", mesXslSupplier.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "供应商-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslSupplier/anon/delete")
|
||||
public Result<String> supplierAnonDelete(@RequestParam(name = "id") String id) {
|
||||
supplierService.removeById(id);
|
||||
stompNotify.publishSupplierChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "供应商-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslSupplier/anon/deleteBatch")
|
||||
public Result<String> supplierAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
supplierService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishSupplierChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "供应商-免密启用/停用")
|
||||
@PostMapping("/xslmes/mesXslSupplier/anon/updateStatus")
|
||||
public Result<String> supplierAnonUpdateStatus(
|
||||
@RequestParam(name = "id") String id,
|
||||
@RequestParam(name = "status") String status) {
|
||||
if (!"0".equals(status) && !"1".equals(status)) {
|
||||
return Result.error("状态参数非法");
|
||||
}
|
||||
boolean ok = supplierService.lambdaUpdate()
|
||||
.eq(MesXslSupplier::getId, id)
|
||||
.set(MesXslSupplier::getStatus, status)
|
||||
.update();
|
||||
if (ok) {
|
||||
stompNotify.publishSupplierChanged("status", id);
|
||||
}
|
||||
return ok ? Result.OK("操作成功") : Result.error("操作失败");
|
||||
}
|
||||
|
||||
// ─────────────────────────── 车辆私有辅助 ────────────────────────────
|
||||
|
||||
private void applyVehicleBelong(MesXslVehicle v) {
|
||||
if (oConvertUtils.isEmpty(v.getVehicleBelong())) {
|
||||
return;
|
||||
}
|
||||
String b = v.getVehicleBelong();
|
||||
if ("1".equals(b)) {
|
||||
v.setSupplierId(null);
|
||||
v.setSupplierName(null);
|
||||
v.setSupplierShortName(null);
|
||||
} else if ("2".equals(b)) {
|
||||
v.setCustomerIds(null);
|
||||
v.setCustomerShortName(null);
|
||||
} else if ("3".equals(b)) {
|
||||
v.setCustomerIds(null);
|
||||
v.setCustomerShortName(null);
|
||||
v.setSupplierId(null);
|
||||
v.setSupplierName(null);
|
||||
v.setSupplierShortName(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void forceNullOppositeVehicleFieldsInDb(String id, String vehicleBelong) {
|
||||
if (oConvertUtils.isEmpty(id) || oConvertUtils.isEmpty(vehicleBelong)) {
|
||||
return;
|
||||
}
|
||||
LambdaUpdateWrapper<MesXslVehicle> uw = new LambdaUpdateWrapper<MesXslVehicle>().eq(MesXslVehicle::getId, id);
|
||||
if ("1".equals(vehicleBelong)) {
|
||||
uw.set(MesXslVehicle::getSupplierId, null)
|
||||
.set(MesXslVehicle::getSupplierName, null)
|
||||
.set(MesXslVehicle::getSupplierShortName, null);
|
||||
} else if ("2".equals(vehicleBelong)) {
|
||||
uw.set(MesXslVehicle::getCustomerIds, null).set(MesXslVehicle::getCustomerShortName, null);
|
||||
} else if ("3".equals(vehicleBelong)) {
|
||||
uw.set(MesXslVehicle::getCustomerIds, null)
|
||||
.set(MesXslVehicle::getCustomerShortName, null)
|
||||
.set(MesXslVehicle::getSupplierId, null)
|
||||
.set(MesXslVehicle::getSupplierName, null)
|
||||
.set(MesXslVehicle::getSupplierShortName, null);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
vehicleService.update(null, uw);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslSupplier;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslSupplierService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@@ -32,6 +33,8 @@ public class MesXslSupplierController extends JeecgController<MesXslSupplier, IM
|
||||
|
||||
@Autowired
|
||||
private IMesXslSupplierService mesXslSupplierService;
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Operation(summary = "MES供应商管理-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
@@ -55,6 +58,7 @@ public class MesXslSupplierController extends JeecgController<MesXslSupplier, IM
|
||||
mesXslSupplier.setStatus("0");
|
||||
}
|
||||
mesXslSupplierService.save(mesXslSupplier);
|
||||
stompNotify.publishSupplierChanged("add", mesXslSupplier.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@@ -64,6 +68,7 @@ public class MesXslSupplierController extends JeecgController<MesXslSupplier, IM
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslSupplier mesXslSupplier) {
|
||||
mesXslSupplierService.updateById(mesXslSupplier);
|
||||
stompNotify.publishSupplierChanged("edit", mesXslSupplier.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@@ -81,6 +86,9 @@ public class MesXslSupplierController extends JeecgController<MesXslSupplier, IM
|
||||
.eq(MesXslSupplier::getId, id)
|
||||
.set(MesXslSupplier::getStatus, status)
|
||||
.update();
|
||||
if (ok) {
|
||||
stompNotify.publishSupplierChanged("status", id);
|
||||
}
|
||||
return ok ? Result.OK("操作成功") : Result.error("操作失败");
|
||||
}
|
||||
|
||||
@@ -90,6 +98,7 @@ public class MesXslSupplierController extends JeecgController<MesXslSupplier, IM
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslSupplierService.removeById(id);
|
||||
stompNotify.publishSupplierChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@@ -99,6 +108,7 @@ public class MesXslSupplierController extends JeecgController<MesXslSupplier, IM
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
mesXslSupplierService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishSupplierChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslVehicle;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslVehicleService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@@ -35,6 +36,9 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
@Autowired
|
||||
private IMesXslVehicleService mesXslVehicleService;
|
||||
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Operation(summary = "MES车辆管理-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslVehicle>> queryPageList(
|
||||
@@ -61,6 +65,7 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
}
|
||||
applyVehicleBelong(mesXslVehicle);
|
||||
mesXslVehicleService.save(mesXslVehicle);
|
||||
stompNotify.publishVehicleChanged("add", mesXslVehicle.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@@ -76,6 +81,7 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
mesXslVehicleService.updateById(mesXslVehicle);
|
||||
// updateById 默认不更新 null 字段,互斥侧需在库中显式置 NULL
|
||||
forceNullOppositeFieldsInDb(mesXslVehicle.getId(), mesXslVehicle.getVehicleBelong());
|
||||
stompNotify.publishVehicleChanged("edit", mesXslVehicle.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@@ -143,6 +149,9 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
.eq(MesXslVehicle::getId, id)
|
||||
.set(MesXslVehicle::getStatus, status)
|
||||
.update();
|
||||
if (ok) {
|
||||
stompNotify.publishVehicleChanged("status", id);
|
||||
}
|
||||
return ok ? Result.OK("操作成功") : Result.error("操作失败");
|
||||
}
|
||||
|
||||
@@ -152,6 +161,7 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslVehicleService.removeById(id);
|
||||
stompNotify.publishVehicleChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@@ -161,6 +171,7 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
mesXslVehicleService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishVehicleChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@@ -185,4 +196,5 @@ public class MesXslVehicleController extends JeecgController<MesXslVehicle, IMes
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, MesXslVehicle.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* 地磅数据记录
|
||||
*/
|
||||
@Tag(name = "地磅数据记录")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslWeightRecord")
|
||||
@Slf4j
|
||||
public class MesXslWeightRecordController extends JeecgController<MesXslWeightRecord, IMesXslWeightRecordService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslWeightRecordService mesXslWeightRecordService;
|
||||
|
||||
@Operation(summary = "地磅数据记录-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslWeightRecord>> queryPageList(MesXslWeightRecord mesXslWeightRecord,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslWeightRecord> queryWrapper = QueryGenerator.initQueryWrapper(mesXslWeightRecord, req.getParameterMap());
|
||||
queryWrapper.orderByDesc("create_time");
|
||||
Page<MesXslWeightRecord> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslWeightRecord> pageList = mesXslWeightRecordService.page(page, queryWrapper);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@AutoLog(value = "地磅数据记录-添加")
|
||||
@Operation(summary = "地磅数据记录-添加")
|
||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:add")
|
||||
@PostMapping(value = "/add")
|
||||
public Result<String> add(@RequestBody MesXslWeightRecord mesXslWeightRecord) {
|
||||
// 自动生成榜单号:BDH-yyyyMMddHHmmss + 3位随机数
|
||||
if (mesXslWeightRecord.getBillNo() == null || mesXslWeightRecord.getBillNo().isBlank()) {
|
||||
String dateStr = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
String seq = String.format("%03d", new Random().nextInt(1000));
|
||||
mesXslWeightRecord.setBillNo("BDH-" + dateStr + seq);
|
||||
}
|
||||
computeNetWeight(mesXslWeightRecord);
|
||||
mesXslWeightRecordService.save(mesXslWeightRecord);
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "地磅数据记录-编辑")
|
||||
@Operation(summary = "地磅数据记录-编辑")
|
||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslWeightRecord mesXslWeightRecord) {
|
||||
computeNetWeight(mesXslWeightRecord);
|
||||
mesXslWeightRecordService.updateById(mesXslWeightRecord);
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "地磅数据记录-通过id删除")
|
||||
@Operation(summary = "地磅数据记录-通过id删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslWeightRecordService.removeById(id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "地磅数据记录-批量删除")
|
||||
@Operation(summary = "地磅数据记录-批量删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:deleteBatch")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
this.mesXslWeightRecordService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "地磅数据记录-通过id查询")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslWeightRecord> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslWeightRecord record = mesXslWeightRecordService.getById(id);
|
||||
if (record == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(record);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslWeightRecord mesXslWeightRecord) {
|
||||
return super.exportXls(request, mesXslWeightRecord, MesXslWeightRecord.class, "地磅数据记录");
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:importExcel")
|
||||
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, MesXslWeightRecord.class);
|
||||
}
|
||||
|
||||
private void computeNetWeight(MesXslWeightRecord record) {
|
||||
BigDecimal gross = record.getGrossWeight();
|
||||
BigDecimal tare = record.getTareWeight();
|
||||
if (gross != null && tare != null) {
|
||||
BigDecimal net = gross.subtract(tare);
|
||||
record.setNetWeight(net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -52,7 +53,10 @@ public class MesXslCustomer extends JeecgEntity implements Serializable {
|
||||
@Schema(description = "业务状态(字典 xslmes_customer_status:0启用1停用2删除);逻辑删除见 del_flag")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "删除状态(0正常 1已删除)")
|
||||
@Schema(description = "乐观锁版本(MyBatis-Plus @Version,更新时自增)")
|
||||
@Version
|
||||
private Integer version;
|
||||
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -49,7 +50,10 @@ public class MesXslSupplier extends JeecgEntity implements Serializable {
|
||||
@Schema(description = "状态:0启用 1停用")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "删除状态(0正常 1已删除)")
|
||||
@Schema(description = "乐观锁版本(MyBatis-Plus @Version,更新时自增)")
|
||||
@Version
|
||||
private Integer version;
|
||||
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -95,7 +96,10 @@ public class MesXslVehicle extends JeecgEntity implements Serializable {
|
||||
@Schema(description = "状态:0启用 1停用")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "删除状态(0正常 1已删除)")
|
||||
@Schema(description = "乐观锁版本(MyBatis-Plus @Version,更新时自增)")
|
||||
@Version
|
||||
private Integer version;
|
||||
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 地磅数据记录
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("mes_xsl_weight_record")
|
||||
@Schema(description = "地磅数据记录")
|
||||
public class MesXslWeightRecord extends JeecgEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Excel(name = "榜单号", width = 18)
|
||||
@Schema(description = "榜单号")
|
||||
private String billNo;
|
||||
|
||||
@Excel(name = "称重日期", width = 15, format = "yyyy-MM-dd")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@Schema(description = "称重日期")
|
||||
private Date weighDate;
|
||||
|
||||
@Excel(name = "进出方向", width = 10, dicCode = "xslmes_inout_direction")
|
||||
@Dict(dicCode = "xslmes_inout_direction")
|
||||
@Schema(description = "进出方向:1进厂 2出厂")
|
||||
private String inoutDirection;
|
||||
|
||||
@Schema(description = "车辆ID")
|
||||
private String vehicleId;
|
||||
|
||||
@Excel(name = "车号", width = 15)
|
||||
@Schema(description = "车号(车牌号)")
|
||||
private String plateNumber;
|
||||
|
||||
@Excel(name = "发货单位", width = 25)
|
||||
@Schema(description = "发货单位(进厂时为供应商名称)")
|
||||
private String senderUnit;
|
||||
|
||||
@Excel(name = "收货单位", width = 25)
|
||||
@Schema(description = "收货单位(出厂时为客户简称)")
|
||||
private String receiverUnit;
|
||||
|
||||
@Excel(name = "货物名称", width = 20)
|
||||
@Schema(description = "货物名称")
|
||||
private String goodsName;
|
||||
|
||||
@Excel(name = "毛重(KG)", width = 12)
|
||||
@Schema(description = "毛重(KG),实际称量")
|
||||
private BigDecimal grossWeight;
|
||||
|
||||
@Excel(name = "皮重(KG)", width = 12)
|
||||
@Schema(description = "皮重(KG),从车辆档案带出")
|
||||
private BigDecimal tareWeight;
|
||||
|
||||
@Excel(name = "净重(KG)", width = 12)
|
||||
@Schema(description = "净重(KG)=毛重-皮重,自动计算")
|
||||
private BigDecimal netWeight;
|
||||
|
||||
@Excel(name = "司机", width = 12)
|
||||
@Schema(description = "司机")
|
||||
private String driverName;
|
||||
|
||||
@Excel(name = "手机号", width = 15)
|
||||
@Schema(description = "手机号")
|
||||
private String driverPhone;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.jeecg.modules.xslmes.http;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.model.MesXslClientConnectionDTO;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslClientConnectionStore;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* xslmes 免密接口 lastSeen 采集
|
||||
*/
|
||||
@Component
|
||||
public class MesXslHttpLastSeenFilter extends OncePerRequestFilter {
|
||||
|
||||
private final IMesXslClientConnectionStore connectionStore;
|
||||
|
||||
public MesXslHttpLastSeenFilter(IMesXslClientConnectionStore connectionStore) {
|
||||
this.connectionStore = connectionStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
if (oConvertUtils.isEmpty(uri)) {
|
||||
return true;
|
||||
}
|
||||
String lower = uri.toLowerCase();
|
||||
return !(lower.contains("/xslmes/") && lower.contains("/anon/"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
String ip = resolveIp(request);
|
||||
String hostName = resolveHostName(request);
|
||||
// 优先用客户端显式传来的设备ID,否则回退到 http:ip
|
||||
String explicitDeviceId = request.getHeader("X-Device-Id");
|
||||
String deviceId = oConvertUtils.isNotEmpty(explicitDeviceId)
|
||||
? explicitDeviceId.trim()
|
||||
: "http:" + ip;
|
||||
Date now = new Date();
|
||||
|
||||
String userName = request.getHeader("X-User-Name");
|
||||
String realName = request.getHeader("X-Real-Name");
|
||||
|
||||
MesXslClientConnectionDTO dto = new MesXslClientConnectionDTO();
|
||||
dto.setDeviceId(deviceId);
|
||||
dto.setSessionId(null);
|
||||
dto.setUserName(oConvertUtils.isNotEmpty(userName) ? userName.trim() : "unknown");
|
||||
dto.setRealName(oConvertUtils.isNotEmpty(realName) ? realName.trim() : "");
|
||||
dto.setPlatform(resolvePlatform(request));
|
||||
dto.setIp(ip);
|
||||
dto.setHostName(hostName);
|
||||
dto.setConnectTime(now);
|
||||
dto.setLastSeen(now);
|
||||
connectionStore.upsertHttp(dto);
|
||||
}
|
||||
|
||||
private String resolveIp(HttpServletRequest request) {
|
||||
String xff = request.getHeader("X-Forwarded-For");
|
||||
if (oConvertUtils.isNotEmpty(xff)) {
|
||||
int comma = xff.indexOf(',');
|
||||
return comma > 0 ? xff.substring(0, comma).trim() : xff.trim();
|
||||
}
|
||||
String realIp = request.getHeader("X-Real-IP");
|
||||
if (oConvertUtils.isNotEmpty(realIp)) {
|
||||
return realIp.trim();
|
||||
}
|
||||
String remote = request.getRemoteAddr();
|
||||
return oConvertUtils.isNotEmpty(remote) ? remote : "unknown";
|
||||
}
|
||||
|
||||
private String resolveHostName(HttpServletRequest request) {
|
||||
String value = request.getHeader("X-Host-Name");
|
||||
if (oConvertUtils.isNotEmpty(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private String resolvePlatform(HttpServletRequest request) {
|
||||
// 优先读客户端显式传来的平台标识
|
||||
String explicit = request.getHeader("X-Platform");
|
||||
if (oConvertUtils.isNotEmpty(explicit)) {
|
||||
String v = explicit.trim().toLowerCase();
|
||||
if ("desktop".equals(v) || "pc".equals(v) || "windows".equals(v) || "win".equals(v)) return "desktop";
|
||||
if ("pda".equals(v) || "mobile".equals(v) || "android".equals(v) || "ios".equals(v)) return "pda";
|
||||
return v;
|
||||
}
|
||||
// 回退到 User-Agent 分析
|
||||
String ua = request.getHeader("User-Agent");
|
||||
if (oConvertUtils.isEmpty(ua)) {
|
||||
return "unknown";
|
||||
}
|
||||
String lower = ua.toLowerCase();
|
||||
if (lower.contains("android") || lower.contains("iphone") || lower.contains("ipad") || lower.contains("mobile")) {
|
||||
return "pda";
|
||||
}
|
||||
if (lower.contains("windows") || lower.contains("macintosh") || lower.contains("linux")) {
|
||||
return "desktop";
|
||||
}
|
||||
// WPF HttpClient 默认 UA 为 "dotnet-httpclient",归为 desktop
|
||||
if (lower.contains("dotnet")) {
|
||||
return "desktop";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* 地磅数据记录 Mapper
|
||||
*/
|
||||
public interface MesXslWeightRecordMapper extends BaseMapper<MesXslWeightRecord> {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.xslmes.mapper.MesXslWeightRecordMapper">
|
||||
</mapper>
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.jeecg.modules.xslmes.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 客户端连接信息
|
||||
*/
|
||||
public class MesXslClientConnectionDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 来源:stomp/http */
|
||||
private String source;
|
||||
/** STOMP 会话 ID(HTTP 场景为空) */
|
||||
private String sessionId;
|
||||
/** 设备标识:当前默认使用 sessionId 或 http:ip */
|
||||
private String deviceId;
|
||||
/** 用户账号/登录名,未知时为 unknown */
|
||||
private String userName;
|
||||
/** 用户姓名(真实姓名) */
|
||||
private String realName;
|
||||
/** 平台:desktop/pda/unknown */
|
||||
private String platform;
|
||||
/** 客户端 IP */
|
||||
private String ip;
|
||||
/** 客户端主机名(机器名),WPF 传 Environment.MachineName */
|
||||
private String hostName;
|
||||
/** 首次连接时间 */
|
||||
private Date connectTime;
|
||||
/** 最近活跃时间 */
|
||||
private Date lastSeen;
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getRealName() {
|
||||
return realName;
|
||||
}
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
public void setHostName(String hostName) {
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
public Date getConnectTime() {
|
||||
return connectTime;
|
||||
}
|
||||
|
||||
public void setConnectTime(Date connectTime) {
|
||||
this.connectTime = connectTime;
|
||||
}
|
||||
|
||||
public Date getLastSeen() {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
public void setLastSeen(Date lastSeen) {
|
||||
this.lastSeen = lastSeen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import org.jeecg.modules.xslmes.model.MesXslClientConnectionDTO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客户端连接状态存储
|
||||
*/
|
||||
public interface IMesXslClientConnectionStore {
|
||||
|
||||
void upsertStomp(MesXslClientConnectionDTO dto);
|
||||
|
||||
void touchStomp(String sessionId, Date lastSeen);
|
||||
|
||||
void removeStomp(String sessionId);
|
||||
|
||||
void upsertHttp(MesXslClientConnectionDTO dto);
|
||||
|
||||
List<MesXslClientConnectionDTO> listByType(String type);
|
||||
|
||||
/** 清空所有连接记录(后端重启时调用,清除进程失联后的残留记录) */
|
||||
void clearAll();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 地磅数据记录 Service
|
||||
*/
|
||||
public interface IMesXslWeightRecordService extends IService<MesXslWeightRecord> {
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 桌面端 STOMP 实时通知服务
|
||||
* 统一管理各业务实体的变更事件广播,供各 Controller 注入使用。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MesXslStompNotifyService {
|
||||
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
/** 广播车辆数据变更事件到 /topic/sync/mes-vehicles */
|
||||
public void publishVehicleChanged(String action, String vehicleId) {
|
||||
publish("/topic/sync/mes-vehicles", "MES_VEHICLE_CHANGED", "vehicleId", vehicleId, action);
|
||||
}
|
||||
|
||||
/** 广播客户数据变更事件到 /topic/sync/mes-customers */
|
||||
public void publishCustomerChanged(String action, String customerId) {
|
||||
publish("/topic/sync/mes-customers", "MES_CUSTOMER_CHANGED", "customerId", customerId, action);
|
||||
}
|
||||
|
||||
/** 广播供应商数据变更事件到 /topic/sync/mes-suppliers */
|
||||
public void publishSupplierChanged(String action, String supplierId) {
|
||||
publish("/topic/sync/mes-suppliers", "MES_SUPPLIER_CHANGED", "supplierId", supplierId, action);
|
||||
}
|
||||
|
||||
// ─────────────────────────── 私有辅助 ────────────────────────────
|
||||
|
||||
private void publish(String topic, String cmd, String idKey, String idValue, String action) {
|
||||
try {
|
||||
Map<String, Object> event = new HashMap<>();
|
||||
event.put("cmd", cmd);
|
||||
event.put("action", action);
|
||||
event.put(idKey, idValue);
|
||||
event.put("timestamp", System.currentTimeMillis());
|
||||
messagingTemplate.convertAndSend(topic, JSON.toJSONString(event));
|
||||
} catch (Exception e) {
|
||||
log.debug("广播 STOMP 事件失败 [{}]: {}", cmd, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.model.MesXslClientConnectionDTO;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslClientConnectionStore;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 客户端连接状态 Redis 存储
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MesXslClientConnectionStore implements IMesXslClientConnectionStore {
|
||||
|
||||
private static final String STOMP_MAP_KEY = "mes:xsl:client-connections:stomp:map";
|
||||
private static final String HTTP_MAP_KEY = "mes:xsl:client-connections:http:map";
|
||||
private static final long TTL_SECONDS = 180L;
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
public MesXslClientConnectionStore(RedisUtil redisUtil) {
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void upsertStomp(MesXslClientConnectionDTO dto) {
|
||||
if (dto == null || oConvertUtils.isEmpty(dto.getSessionId())) {
|
||||
return;
|
||||
}
|
||||
dto.setSource("stomp");
|
||||
Map<String, MesXslClientConnectionDTO> map = loadMap(STOMP_MAP_KEY);
|
||||
MesXslClientConnectionDTO old = map.get(dto.getSessionId());
|
||||
if (old != null && dto.getConnectTime() == null) {
|
||||
dto.setConnectTime(old.getConnectTime());
|
||||
}
|
||||
if (dto.getConnectTime() == null) {
|
||||
dto.setConnectTime(new Date());
|
||||
}
|
||||
if (dto.getLastSeen() == null) {
|
||||
dto.setLastSeen(new Date());
|
||||
}
|
||||
map.put(dto.getSessionId(), dto);
|
||||
saveMap(STOMP_MAP_KEY, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void touchStomp(String sessionId, Date lastSeen) {
|
||||
if (oConvertUtils.isEmpty(sessionId)) {
|
||||
return;
|
||||
}
|
||||
Map<String, MesXslClientConnectionDTO> map = loadMap(STOMP_MAP_KEY);
|
||||
MesXslClientConnectionDTO dto = map.get(sessionId);
|
||||
if (dto == null) {
|
||||
dto = new MesXslClientConnectionDTO();
|
||||
dto.setSource("stomp");
|
||||
dto.setSessionId(sessionId);
|
||||
dto.setDeviceId(sessionId);
|
||||
dto.setUserName("unknown");
|
||||
dto.setPlatform("unknown");
|
||||
dto.setIp("unknown");
|
||||
dto.setConnectTime(lastSeen == null ? new Date() : lastSeen);
|
||||
}
|
||||
dto.setLastSeen(lastSeen == null ? new Date() : lastSeen);
|
||||
map.put(sessionId, dto);
|
||||
saveMap(STOMP_MAP_KEY, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeStomp(String sessionId) {
|
||||
if (oConvertUtils.isEmpty(sessionId)) {
|
||||
return;
|
||||
}
|
||||
Map<String, MesXslClientConnectionDTO> map = loadMap(STOMP_MAP_KEY);
|
||||
if (map.remove(sessionId) != null) {
|
||||
saveMap(STOMP_MAP_KEY, map);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void upsertHttp(MesXslClientConnectionDTO dto) {
|
||||
if (dto == null || oConvertUtils.isEmpty(dto.getDeviceId())) {
|
||||
return;
|
||||
}
|
||||
dto.setSource("http");
|
||||
Map<String, MesXslClientConnectionDTO> map = loadMap(HTTP_MAP_KEY);
|
||||
MesXslClientConnectionDTO old = map.get(dto.getDeviceId());
|
||||
if (old != null && dto.getConnectTime() == null) {
|
||||
dto.setConnectTime(old.getConnectTime());
|
||||
}
|
||||
if (dto.getConnectTime() == null) {
|
||||
dto.setConnectTime(new Date());
|
||||
}
|
||||
if (dto.getLastSeen() == null) {
|
||||
dto.setLastSeen(new Date());
|
||||
}
|
||||
map.put(dto.getDeviceId(), dto);
|
||||
saveMap(HTTP_MAP_KEY, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<MesXslClientConnectionDTO> listByType(String type) {
|
||||
String t = oConvertUtils.getString(type);
|
||||
List<MesXslClientConnectionDTO> result = new ArrayList<>();
|
||||
if ("stomp".equalsIgnoreCase(t)) {
|
||||
result.addAll(loadMap(STOMP_MAP_KEY).values());
|
||||
} else if ("http".equalsIgnoreCase(t)) {
|
||||
result.addAll(loadMap(HTTP_MAP_KEY).values());
|
||||
} else {
|
||||
result.addAll(loadMap(STOMP_MAP_KEY).values());
|
||||
result.addAll(loadMap(HTTP_MAP_KEY).values());
|
||||
}
|
||||
result.sort(Comparator.comparing(MesXslClientConnectionDTO::getLastSeen,
|
||||
Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clearAll() {
|
||||
try {
|
||||
redisUtil.del(STOMP_MAP_KEY);
|
||||
redisUtil.del(HTTP_MAP_KEY);
|
||||
log.info("[连接管理] 已清空所有客户端连接记录(后端重启)");
|
||||
} catch (Exception e) {
|
||||
log.warn("[连接管理] 清空连接记录失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, MesXslClientConnectionDTO> loadMap(String key) {
|
||||
Object obj = redisUtil.get(key);
|
||||
if (obj instanceof Map) {
|
||||
return (Map<String, MesXslClientConnectionDTO>) obj;
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
private void saveMap(String key, Map<String, MesXslClientConnectionDTO> map) {
|
||||
try {
|
||||
redisUtil.set(key, map);
|
||||
redisUtil.expire(key, TTL_SECONDS);
|
||||
} catch (Exception e) {
|
||||
log.warn("保存客户端连接状态失败, key={}, error={}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslWeightRecordMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
/**
|
||||
* 地磅数据记录 ServiceImpl
|
||||
*/
|
||||
@Service
|
||||
public class MesXslWeightRecordServiceImpl extends ServiceImpl<MesXslWeightRecordMapper, MesXslWeightRecord>
|
||||
implements IMesXslWeightRecordService {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.jeecg.modules.xslmes.websocket;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslClientConnectionStore;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 后端启动/关闭时维护客户端连接状态一致性。
|
||||
*
|
||||
* <p>后端进程重启时,所有 WebSocket 连接已断开,但 Redis 里的 STOMP/HTTP 记录
|
||||
* 可能仍在 TTL 内残留,导致旧 sessionId 与新连接共存。
|
||||
* 在 ApplicationReadyEvent(容器全部就绪后)清空全部记录,保证重启后列表干净。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MesXslConnectionLifecycleListener {
|
||||
|
||||
private final IMesXslClientConnectionStore connectionStore;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onApplicationReady() {
|
||||
connectionStore.clearAll();
|
||||
log.info("[连接管理] 后端启动完成,已清空历史连接记录");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.jeecg.modules.xslmes.websocket;
|
||||
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.model.MesXslClientConnectionDTO;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslClientConnectionStore;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.simp.stomp.StompCommand;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* STOMP 连接状态采集拦截器
|
||||
*/
|
||||
@Component
|
||||
public class MesXslStompConnectionChannelInterceptor implements ChannelInterceptor {
|
||||
|
||||
private final IMesXslClientConnectionStore connectionStore;
|
||||
|
||||
public MesXslStompConnectionChannelInterceptor(IMesXslClientConnectionStore connectionStore) {
|
||||
this.connectionStore = connectionStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
|
||||
if (accessor == null) {
|
||||
return message;
|
||||
}
|
||||
|
||||
String sessionId = accessor.getSessionId();
|
||||
if (oConvertUtils.isEmpty(sessionId)) {
|
||||
return message;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
StompCommand command = accessor.getCommand();
|
||||
if (StompCommand.DISCONNECT.equals(command)) {
|
||||
connectionStore.removeStomp(sessionId);
|
||||
return message;
|
||||
}
|
||||
if (StompCommand.CONNECT.equals(command)) {
|
||||
MesXslClientConnectionDTO dto = new MesXslClientConnectionDTO();
|
||||
dto.setSessionId(sessionId);
|
||||
dto.setDeviceId(resolveDeviceId(accessor, sessionId));
|
||||
dto.setUserName(resolveUserName(accessor));
|
||||
dto.setRealName(resolveRealName(accessor));
|
||||
dto.setPlatform(resolvePlatform(accessor));
|
||||
dto.setIp(resolveIp(accessor));
|
||||
dto.setHostName(resolveHostName(accessor));
|
||||
dto.setConnectTime(now);
|
||||
dto.setLastSeen(now);
|
||||
connectionStore.upsertStomp(dto);
|
||||
return message;
|
||||
}
|
||||
|
||||
// 对 SEND/SUBSCRIBE/HEARTBEAT 等其他活动统一刷新活跃时间
|
||||
connectionStore.touchStomp(sessionId, now);
|
||||
return message;
|
||||
}
|
||||
|
||||
private String resolveUserName(StompHeaderAccessor accessor) {
|
||||
Principal principal = accessor.getUser();
|
||||
if (principal != null && oConvertUtils.isNotEmpty(principal.getName())) {
|
||||
return principal.getName();
|
||||
}
|
||||
String byHeader = firstNotEmptyHeader(accessor, "userName", "username", "userId", "x-user");
|
||||
return oConvertUtils.isNotEmpty(byHeader) ? byHeader : "unknown";
|
||||
}
|
||||
|
||||
private String resolvePlatform(StompHeaderAccessor accessor) {
|
||||
String value = firstNotEmptyHeader(accessor, "platform", "x-platform", "deviceType");
|
||||
if (oConvertUtils.isEmpty(value)) {
|
||||
return "unknown";
|
||||
}
|
||||
String v = value.trim().toLowerCase();
|
||||
if ("desktop".equals(v) || "pc".equals(v) || "windows".equals(v) || "win".equals(v)) {
|
||||
return "desktop";
|
||||
}
|
||||
if ("pda".equals(v) || "mobile".equals(v) || "android".equals(v) || "ios".equals(v) || "iphone".equals(v)) {
|
||||
return "pda";
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private String resolveIp(StompHeaderAccessor accessor) {
|
||||
String value = firstNotEmptyHeader(accessor, "ip", "clientIp", "X-Forwarded-For", "x-forwarded-for");
|
||||
if (oConvertUtils.isEmpty(value)) {
|
||||
return "unknown";
|
||||
}
|
||||
int comma = value.indexOf(',');
|
||||
return comma > 0 ? value.substring(0, comma).trim() : value.trim();
|
||||
}
|
||||
|
||||
private String firstNotEmptyHeader(StompHeaderAccessor accessor, String... keys) {
|
||||
for (String key : keys) {
|
||||
List<String> values = accessor.getNativeHeader(key);
|
||||
if (values != null) {
|
||||
for (String v : values) {
|
||||
if (oConvertUtils.isNotEmpty(v)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveRealName(StompHeaderAccessor accessor) {
|
||||
String value = firstNotEmptyHeader(accessor, "realName", "real-name", "x-real-name", "displayName");
|
||||
return oConvertUtils.isNotEmpty(value) ? value.trim() : "";
|
||||
}
|
||||
|
||||
private String resolveHostName(StompHeaderAccessor accessor) {
|
||||
String value = firstNotEmptyHeader(accessor, "hostName", "hostname", "x-host-name", "machineName");
|
||||
return oConvertUtils.isNotEmpty(value) ? value.trim() : "unknown";
|
||||
}
|
||||
|
||||
private String resolveDeviceId(StompHeaderAccessor accessor, String sessionId) {
|
||||
String value = firstNotEmptyHeader(accessor, "deviceId", "terminalNo", "terminalId", "clientId");
|
||||
return oConvertUtils.isNotEmpty(value) ? value : sessionId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.jeecg.modules.xslmes.websocket;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.ChannelRegistration;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
/**
|
||||
* xslmes WebSocket 入站通道配置
|
||||
*/
|
||||
@Configuration
|
||||
public class MesXslWebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
private final MesXslStompConnectionChannelInterceptor connectionInterceptor;
|
||||
|
||||
public MesXslWebSocketBrokerConfig(MesXslStompConnectionChannelInterceptor connectionInterceptor) {
|
||||
this.connectionInterceptor = connectionInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||
registration.interceptors(connectionInterceptor);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user