first
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+1359
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
+4
File diff suppressed because one or more lines are too long
Vendored
+622
@@ -0,0 +1,622 @@
|
||||
(() => {
|
||||
// src/ui/panel.test.js
|
||||
var root = window;
|
||||
var panelNamespace = root.Panel;
|
||||
if (!panelNamespace || typeof panelNamespace !== "object") {
|
||||
throw new Error("加载 panel.test.js 前必须先准备 window.Panel 命名空间");
|
||||
}
|
||||
var page = panelNamespace.ui;
|
||||
if (!page) throw new Error("加载 panel.test.js 前必须先加载 Panel.ui");
|
||||
var noop = () => {
|
||||
};
|
||||
var BatchAppendOptions = Object.freeze({ insertMode: "append" });
|
||||
var createCarCode = (index) => (index + 1).toString().padStart(4, "0");
|
||||
var createCarName = (code) => `第[${code}]号车`;
|
||||
var createCarShape = ({ type, code, name = createCarName(code), point = { x: 0, y: 0 }, rotate = 0, lock = false }) => {
|
||||
const car = {
|
||||
type,
|
||||
code: { text: code },
|
||||
name: { text: name },
|
||||
base: { point: { x: point.x, y: point.y }, rotate }
|
||||
};
|
||||
if (lock) car.lock = { enable: true };
|
||||
return car;
|
||||
};
|
||||
var setCarTransform = (car, x, y, rotate) => {
|
||||
car.base.point.x = x;
|
||||
car.base.point.y = y;
|
||||
car.base.rotate = rotate;
|
||||
return car;
|
||||
};
|
||||
var StaticCarDefs = Object.freeze([
|
||||
{ type: "CarForklift", code: "2000", point: { x: 300, y: 100 }, rotate: 0, lock: true },
|
||||
{ type: "CarForklift", code: "2001", point: { x: 400, y: 100 }, rotate: 45 },
|
||||
{ type: "CarForklift", code: "2002", point: { x: 500, y: 100 }, rotate: 90 },
|
||||
{ type: "CarForklift", code: "2003", point: { x: 600, y: 100 }, rotate: 135 },
|
||||
{ type: "CarConveyor", code: "2010", point: { x: 700, y: 100 }, rotate: 180 },
|
||||
{ type: "CarConveyor", code: "2011", point: { x: 800, y: 100 }, rotate: 225, lock: true },
|
||||
{ type: "CarConveyor", code: "2012", point: { x: 900, y: 100 }, rotate: 270 },
|
||||
{ type: "CarConveyor", code: "2013", point: { x: 1e3, y: 100 }, rotate: 315 },
|
||||
{ type: "CarTugger", code: "2020", point: { x: 300, y: 300 }, rotate: 360 },
|
||||
{ type: "CarTugger", code: "2021", point: { x: 400, y: 300 }, rotate: 405 },
|
||||
{ type: "CarTugger", code: "2022", point: { x: 500, y: 300 }, rotate: 450, lock: true },
|
||||
{ type: "CarTugger", code: "2023", point: { x: 600, y: 300 }, rotate: 495 },
|
||||
{ type: "CarCarrier", code: "2030", point: { x: 700, y: 300 }, rotate: 540 },
|
||||
{ type: "CarCarrier", code: "2031", point: { x: 800, y: 300 }, rotate: 585 },
|
||||
{ type: "CarCarrier", code: "2032", point: { x: 900, y: 300 }, rotate: 630 },
|
||||
{ type: "CarCarrier", code: "2033", point: { x: 1e3, y: 300 }, rotate: 675, lock: true }
|
||||
]);
|
||||
var createStaticCars = () => StaticCarDefs.map((def) => createCarShape(def));
|
||||
var testConfig = {
|
||||
seed: 20240214,
|
||||
debug: false,
|
||||
shapeCount: 4e3,
|
||||
grid: { rows: 4, cols: 4, gap: 100 },
|
||||
padding: 60,
|
||||
car: {
|
||||
types: ["CarForklift", "CarConveyor", "CarTugger", "CarCarrier"],
|
||||
pointX: 0,
|
||||
pointY: 0,
|
||||
rotate: 0,
|
||||
count: 900,
|
||||
row: 30,
|
||||
space: 50
|
||||
}
|
||||
};
|
||||
var testState = {
|
||||
carFrame: [],
|
||||
staticCars: null,
|
||||
carLoop: { frameId: 0, interval: 20, lastTick: 0, callback: noop }
|
||||
};
|
||||
var testUtil = {
|
||||
log: (...args) => {
|
||||
if (page.testConfig.debug) {
|
||||
console.log(...args);
|
||||
}
|
||||
},
|
||||
rand: () => {
|
||||
page.testConfig.seed = page.testConfig.seed * 1664525 + 1013904223 >>> 0;
|
||||
return page.testConfig.seed / 4294967296;
|
||||
},
|
||||
randInt: (min, max) => {
|
||||
if (max <= min) return min;
|
||||
return Math.floor(page.testUtil.rand() * (max - min + 1)) + min;
|
||||
},
|
||||
measure: (label, fn = noop) => {
|
||||
const start = performance.now();
|
||||
const result = fn();
|
||||
const cost = performance.now() - start;
|
||||
page.testUtil.log(`[测试] ${label}: ${cost.toFixed(2)}毫秒`);
|
||||
return result;
|
||||
},
|
||||
scatterPoints: (count, options = {}) => {
|
||||
const size = page.panel.map.base.size;
|
||||
const safeCount = Math.max(1, Number.isFinite(count) ? Math.floor(count) : 1);
|
||||
const padding = Number.isFinite(options.padding) ? options.padding : page.testConfig.padding;
|
||||
const usableW = Math.max(1, size.w - padding * 2);
|
||||
const usableH = Math.max(1, size.h - padding * 2);
|
||||
const cols = Math.max(1, Math.ceil(Math.sqrt(safeCount * usableW / usableH)));
|
||||
const rows = Math.max(1, Math.ceil(safeCount / cols));
|
||||
const gapX = usableW / cols;
|
||||
const gapY = usableH / rows;
|
||||
const jitterRatio = Number.isFinite(options.jitter) ? options.jitter : 0.35;
|
||||
const jitterX = Math.max(2, Math.floor(gapX * jitterRatio));
|
||||
const jitterY = Math.max(2, Math.floor(gapY * jitterRatio));
|
||||
const cells = [];
|
||||
for (let r = 0; r < rows; r++) {
|
||||
for (let c = 0; c < cols; c++) {
|
||||
cells.push({ r, c });
|
||||
}
|
||||
}
|
||||
for (let i = cells.length - 1; i > 0; i--) {
|
||||
const j = page.testUtil.randInt(0, i);
|
||||
const tmp = cells[i];
|
||||
cells[i] = cells[j];
|
||||
cells[j] = tmp;
|
||||
}
|
||||
const points = [];
|
||||
for (let i = 0; i < safeCount; i++) {
|
||||
const cell = cells[i];
|
||||
const x = padding + (cell.c + 0.5) * gapX + page.testUtil.randInt(-jitterX, jitterX);
|
||||
const y = padding + (cell.r + 0.5) * gapY + page.testUtil.randInt(-jitterY, jitterY);
|
||||
points.push({
|
||||
x: Math.min(size.w - padding, Math.max(padding, Math.round(x))),
|
||||
y: Math.min(size.h - padding, Math.max(padding, Math.round(y)))
|
||||
});
|
||||
}
|
||||
return { points, gapX, gapY };
|
||||
},
|
||||
shuffleInPlace: (list = []) => {
|
||||
for (let i = list.length - 1; i > 0; i--) {
|
||||
const j = page.testUtil.randInt(0, i);
|
||||
const tmp = list[i];
|
||||
list[i] = list[j];
|
||||
list[j] = tmp;
|
||||
}
|
||||
return list;
|
||||
},
|
||||
polyPoints: (x, y, size = 80) => [
|
||||
{ x, y },
|
||||
{ x: x + size, y },
|
||||
{ x: x + size * 1.5, y: y + size * 0.7 },
|
||||
{ x: x + size, y: y + size * 1.4 },
|
||||
{ x, y: y + size * 1.4 }
|
||||
],
|
||||
pathPoints: (x, y, size = 80) => [
|
||||
{ x, y },
|
||||
{ x: x + size, y },
|
||||
{ x: x + size * 1.5, y: y + size * 0.7 },
|
||||
{ x: x + size, y: y + size * 1.4 },
|
||||
{ x, y: y + size * 0.7 }
|
||||
],
|
||||
createRotateAdder: (panel, method, prefix, options = {}) => {
|
||||
const formatCode = typeof options.formatCode === "function" ? options.formatCode : (i) => `${prefix}-${i}`;
|
||||
return (point, i) => {
|
||||
panel[method]({
|
||||
code: { text: formatCode(i) },
|
||||
base: { point, rotate: page.testUtil.randInt(0, 359) }
|
||||
}, BatchAppendOptions);
|
||||
};
|
||||
},
|
||||
addEdgeByIndex: (panel, codePrefix, i, startNode, endNode, options = BatchAppendOptions) => {
|
||||
if (i % 4 === 0) {
|
||||
panel.addEdgeBeeline({
|
||||
code: { text: `${codePrefix}-EL-${i}` },
|
||||
base: { startNode, endNode }
|
||||
}, options);
|
||||
return;
|
||||
}
|
||||
if (i % 4 === 1) {
|
||||
panel.addEdgeArc({
|
||||
code: { text: `${codePrefix}-EA-${i}` },
|
||||
base: {
|
||||
startNode,
|
||||
endNode,
|
||||
p1: { x: startNode.base.point.x, y: endNode.base.point.y }
|
||||
}
|
||||
}, options);
|
||||
return;
|
||||
}
|
||||
if (i % 4 === 2) {
|
||||
panel.addEdgeQuadratic({
|
||||
code: { text: `${codePrefix}-EQ-${i}` },
|
||||
base: {
|
||||
startNode,
|
||||
endNode,
|
||||
p1: { x: endNode.base.point.x, y: startNode.base.point.y }
|
||||
}
|
||||
}, options);
|
||||
return;
|
||||
}
|
||||
panel.addEdgeBezier({
|
||||
code: { text: `${codePrefix}-EB-${i}` },
|
||||
base: {
|
||||
startNode,
|
||||
endNode,
|
||||
p1: { x: startNode.base.point.x + 40, y: startNode.base.point.y - 40 },
|
||||
p2: { x: endNode.base.point.x - 40, y: endNode.base.point.y + 40 }
|
||||
}
|
||||
}, options);
|
||||
},
|
||||
addRandomEdges: (panel, nodes, edgeCount, codePrefix, options = BatchAppendOptions) => {
|
||||
if (!Array.isArray(nodes) || nodes.length < 2) return;
|
||||
const max = nodes.length - 1;
|
||||
for (let i = 0; i < edgeCount; i++) {
|
||||
const startIndex = page.testUtil.randInt(0, max);
|
||||
let endIndex = page.testUtil.randInt(0, max);
|
||||
if (endIndex === startIndex) {
|
||||
endIndex = (startIndex + 1) % nodes.length;
|
||||
}
|
||||
const startNode = nodes[startIndex];
|
||||
const endNode = nodes[endIndex];
|
||||
page.testUtil.addEdgeByIndex(panel, codePrefix, i, startNode, endNode, options);
|
||||
}
|
||||
},
|
||||
withPerfBatch: (panel, action) => panel.withBatch(action),
|
||||
measureBatchAdd: (panel, points, adders, label) => {
|
||||
page.testUtil.measure(label, () => {
|
||||
page.testUtil.withPerfBatch(panel, () => {
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
adders[i % adders.length](points[i], i);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
measureBatchEdges: (panel, edgeCount, codePrefix, label) => {
|
||||
const nodes = panel.nodeShapes;
|
||||
const safeEdgeCount = Math.min(nodes.length * 2, edgeCount);
|
||||
if (nodes.length < 2 || safeEdgeCount <= 0) return;
|
||||
page.testUtil.measure(label, () => {
|
||||
page.testUtil.withPerfBatch(panel, () => {
|
||||
page.testUtil.addRandomEdges(panel, nodes, safeEdgeCount, codePrefix);
|
||||
});
|
||||
});
|
||||
},
|
||||
createSizedAdder: (panel, method, prefix, sizeFactory, rotateFactory, options = BatchAppendOptions) => (point, i) => {
|
||||
panel[method]({
|
||||
code: { text: `${prefix}-${i}` },
|
||||
base: { point, size: sizeFactory(), rotate: rotateFactory() }
|
||||
}, options);
|
||||
},
|
||||
createPolyAdder: (panel, method, prefix, pointsBuilder, sizeFactory, rotateFactory, options = BatchAppendOptions) => (point, i) => {
|
||||
const size = sizeFactory();
|
||||
panel[method]({
|
||||
code: { text: `${prefix}-${i}` },
|
||||
base: { point, points: pointsBuilder(point.x, point.y, size), closed: true, rotate: rotateFactory() }
|
||||
}, options);
|
||||
},
|
||||
ensureStaticCars: () => page.testState.staticCars ?? (page.testState.staticCars = createStaticCars()),
|
||||
ensureCarFrame: () => {
|
||||
const carConfig = page.testConfig.car;
|
||||
const staticCars = page.testUtil.ensureStaticCars();
|
||||
const frame = page.testState.carFrame;
|
||||
frame.length = carConfig.count + staticCars.length;
|
||||
for (let i = 0; i < carConfig.count; i++) {
|
||||
const code = createCarCode(i);
|
||||
const type = carConfig.types[i % carConfig.types.length];
|
||||
let car = frame[i];
|
||||
if (!car || car.code?.text !== code) {
|
||||
car = createCarShape({ type, code });
|
||||
frame[i] = car;
|
||||
} else {
|
||||
car.type = type;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < staticCars.length; i++) {
|
||||
frame[carConfig.count + i] = staticCars[i];
|
||||
}
|
||||
return frame;
|
||||
},
|
||||
buildCarFrame: (panel) => {
|
||||
const size = panel.map.base.size;
|
||||
const carConfig = page.testConfig.car;
|
||||
const cars = page.testUtil.ensureCarFrame();
|
||||
const { row, space } = carConfig;
|
||||
carConfig.pointX = carConfig.pointX + 1 > size.w ? 0 : carConfig.pointX + 1;
|
||||
carConfig.pointY = carConfig.pointY + 1 > size.h ? 0 : carConfig.pointY + 1;
|
||||
carConfig.rotate = carConfig.rotate + 1 > 359 ? 0 : carConfig.rotate + 1;
|
||||
for (let i = 0; i < carConfig.count; i++) {
|
||||
const col = i % row;
|
||||
const rowIndex = Math.floor(i / row);
|
||||
const type = carConfig.types[i % carConfig.types.length];
|
||||
const car = cars[i];
|
||||
car.type = type;
|
||||
setCarTransform(car, carConfig.pointX - col * space, carConfig.pointY - rowIndex * space, carConfig.rotate);
|
||||
}
|
||||
return cars;
|
||||
},
|
||||
resetSeed: (seed) => {
|
||||
if (Number.isFinite(seed)) {
|
||||
page.testConfig.seed = seed;
|
||||
}
|
||||
},
|
||||
resetPanel: (options = {}) => {
|
||||
page.testUtil.carStop();
|
||||
if (options.clear !== false) {
|
||||
page.panel.clearShapes();
|
||||
}
|
||||
page.panel.clearActiveShape();
|
||||
page.panel.clearHoverShape();
|
||||
page.panel.clearMarkerShape();
|
||||
},
|
||||
carStart: (callback, interval = 20) => {
|
||||
page.testUtil.carStop();
|
||||
const loop = page.testState.carLoop;
|
||||
loop.callback = typeof callback === "function" ? callback : noop;
|
||||
loop.interval = Number.isFinite(interval) && interval > 0 ? interval : 20;
|
||||
loop.lastTick = 0;
|
||||
const tick = (time) => {
|
||||
if (!loop.frameId) return;
|
||||
if (loop.lastTick === 0 || time - loop.lastTick >= loop.interval) {
|
||||
loop.lastTick = time;
|
||||
loop.callback();
|
||||
if (!loop.frameId) return;
|
||||
}
|
||||
loop.frameId = window.requestAnimationFrame(tick);
|
||||
};
|
||||
loop.frameId = window.requestAnimationFrame(tick);
|
||||
},
|
||||
carStop: () => {
|
||||
const loop = page.testState.carLoop;
|
||||
if (loop.frameId) window.cancelAnimationFrame(loop.frameId);
|
||||
loop.frameId = 0;
|
||||
loop.lastTick = 0;
|
||||
loop.callback = noop;
|
||||
}
|
||||
};
|
||||
Object.assign(page, { testConfig, testState, testUtil });
|
||||
var testActions = {
|
||||
test1: () => {
|
||||
page.testUtil.log("测试1:图形");
|
||||
page.testUtil.resetPanel();
|
||||
const panel = page.panel;
|
||||
const startX = 200;
|
||||
const startY = 200;
|
||||
const gapX = 220;
|
||||
const gapY = 180;
|
||||
const polyPoints = (x, y) => [{ x, y }, { x: x + 120, y }, { x: x + 180, y: y + 90 }, { x: x + 120, y: y + 180 }, { x, y: y + 180 }];
|
||||
const pathPoints = (x, y) => [{ x, y }, { x: x + 120, y }, { x: x + 180, y: y + 90 }, { x: x + 120, y: y + 180 }, { x, y: y + 90 }];
|
||||
panel.addNodeRect({ code: { text: "T10-N-Rect" }, base: { point: { x: startX, y: startY }, size: { w: 120, h: 80 }, rotate: 5 } });
|
||||
panel.addNodeEllipse({ code: { text: "T10-N-Ellipse" }, base: { point: { x: startX + gapX, y: startY }, size: { w: 120, h: 80 }, rotate: 10 } });
|
||||
panel.addNodePoly({ code: { text: "T10-N-Poly" }, base: { point: { x: startX + gapX * 2, y: startY }, points: polyPoints(startX + gapX * 2, startY), closed: true, rotate: 15 } });
|
||||
panel.addNodePath({ code: { text: "T10-N-Path" }, base: { point: { x: startX + gapX * 3, y: startY }, points: pathPoints(startX + gapX * 3, startY), closed: true, rotate: 20 } });
|
||||
panel.addZoneRect({ code: { text: "T10-Z-Rect" }, base: { point: { x: startX, y: startY + gapY }, size: { w: 140, h: 90 }, rotate: 5 } });
|
||||
panel.addZoneEllipse({ code: { text: "T10-Z-Ellipse" }, base: { point: { x: startX + gapX, y: startY + gapY }, size: { w: 140, h: 90 }, rotate: 10 } });
|
||||
panel.addZonePoly({ code: { text: "T10-Z-Poly" }, base: { point: { x: startX + gapX * 2, y: startY + gapY }, points: polyPoints(startX + gapX * 2, startY + gapY), closed: true, rotate: 15 } });
|
||||
panel.addZonePath({ code: { text: "T10-Z-Path" }, base: { point: { x: startX + gapX * 3, y: startY + gapY }, points: pathPoints(startX + gapX * 3, startY + gapY), closed: true, rotate: 20 } });
|
||||
panel.addTagRect({ code: { text: "T10-T-Rect" }, base: { point: { x: startX, y: startY + gapY * 2 }, size: { w: 140, h: 90 }, rotate: 5 } });
|
||||
panel.addTagEllipse({ code: { text: "T10-T-Ellipse" }, base: { point: { x: startX + gapX, y: startY + gapY * 2 }, size: { w: 140, h: 90 }, rotate: 10 } });
|
||||
panel.addTagPoly({ code: { text: "T10-T-Poly" }, base: { point: { x: startX + gapX * 2, y: startY + gapY * 2 }, points: polyPoints(startX + gapX * 2, startY + gapY * 2), closed: true, rotate: 15 } });
|
||||
panel.addTagPath({ code: { text: "T10-T-Path" }, base: { point: { x: startX + gapX * 3, y: startY + gapY * 2 }, points: pathPoints(startX + gapX * 3, startY + gapY * 2), closed: true, rotate: 20 } });
|
||||
const nodeRect = panel.getNode({ code: { text: "T10-N-Rect" } });
|
||||
const nodeEllipse = panel.getNode({ code: { text: "T10-N-Ellipse" } });
|
||||
const nodePoly = panel.getNode({ code: { text: "T10-N-Poly" } });
|
||||
const nodePath = panel.getNode({ code: { text: "T10-N-Path" } });
|
||||
panel.addEdgeBeeline({ code: { text: "T10-E-Line" }, base: { lineWidth: 3, startNode: nodeRect, endNode: nodeEllipse } });
|
||||
panel.addEdgeArc({ code: { text: "T10-E-Arc" }, base: { lineWidth: 3, startNode: nodeEllipse, endNode: nodePoly, p1: { x: startX + gapX * 1.5, y: startY - 100 } } });
|
||||
panel.addEdgeQuadratic({ code: { text: "T10-E-Quadratic" }, base: { lineWidth: 3, startNode: nodePoly, endNode: nodePath, p1: { x: startX + gapX * 2.5, y: startY + 120 } } });
|
||||
panel.addEdgeBezier({ code: { text: "T10-E-Bezier" }, base: { lineWidth: 3, startNode: nodePath, endNode: nodeRect, p1: { x: startX + gapX * 2, y: startY + 260 }, p2: { x: startX + gapX * 0.5, y: startY + 260 } } });
|
||||
panel.addCarForklift({ code: { text: "T10-C-Forklift" }, name: { text: "Forklift" }, base: { point: { x: startX, y: startY + gapY * 3 }, rotate: 45 } });
|
||||
panel.addCarConveyor({ code: { text: "T10-C-Conveyor" }, name: { text: "Conveyor" }, base: { point: { x: startX + gapX, y: startY + gapY * 3 }, rotate: 90 } });
|
||||
panel.addCarTugger({ code: { text: "T10-C-Tugger" }, name: { text: "Tugger" }, base: { point: { x: startX + gapX * 2, y: startY + gapY * 3 }, rotate: 135 } });
|
||||
panel.addCarCarrier({ code: { text: "T10-C-Carrier" }, name: { text: "Carrier" }, base: { point: { x: startX + gapX * 3, y: startY + gapY * 3 }, rotate: 180 } });
|
||||
},
|
||||
test2: () => {
|
||||
page.testUtil.log("测试2:车辆");
|
||||
const panel = page.panel;
|
||||
const cars = page.testUtil.buildCarFrame(panel);
|
||||
panel.addOrUpdateOrDeleteCars(cars);
|
||||
},
|
||||
test3: () => {
|
||||
page.testUtil.log("测试3:运动");
|
||||
page.testUtil.carStart(testActions.test2, 20);
|
||||
},
|
||||
test4: () => {
|
||||
page.testUtil.log("测试4:停止");
|
||||
page.testUtil.carStop();
|
||||
page.testConfig.car.pointX = 0;
|
||||
page.testConfig.car.pointY = 0;
|
||||
page.testConfig.car.rotate = 0;
|
||||
},
|
||||
test5: () => {
|
||||
page.testUtil.log("测试5:轨迹");
|
||||
page.testUtil.resetPanel();
|
||||
const panel = page.panel;
|
||||
panel.addNodeRect({ code: { text: "1" }, base: { point: { x: 300, y: 200 } } });
|
||||
panel.addNodeRect({ code: { text: "2" }, base: { point: { x: 400, y: 200 } } });
|
||||
panel.addNodeRect({ code: { text: "3" }, base: { point: { x: 500, y: 200 } } });
|
||||
panel.addNodeRect({ code: { text: "4" }, base: { point: { x: 600, y: 200 } } });
|
||||
panel.addNodeEllipse({ code: { text: "5" }, base: { point: { x: 300, y: 300 } } });
|
||||
panel.addNodeEllipse({ code: { text: "6" }, base: { point: { x: 400, y: 300 } } });
|
||||
panel.addNodeEllipse({ code: { text: "7" }, base: { point: { x: 500, y: 300 } } });
|
||||
panel.addNodeEllipse({ code: { text: "8" }, base: { point: { x: 600, y: 300 } } });
|
||||
panel.addNodeRect({ code: { text: "9" }, base: { point: { x: 300, y: 400 } } });
|
||||
panel.addNodeRect({ code: { text: "10" }, base: { point: { x: 400, y: 400 } } });
|
||||
panel.addNodeRect({ code: { text: "11" }, base: { point: { x: 500, y: 400 } } });
|
||||
panel.addNodeRect({ code: { text: "12" }, base: { point: { x: 600, y: 400 } } });
|
||||
panel.addNodeEllipse({ code: { text: "13" }, base: { point: { x: 300, y: 500 } } });
|
||||
panel.addNodeEllipse({ code: { text: "14" }, base: { point: { x: 400, y: 500 } } });
|
||||
panel.addNodeEllipse({ code: { text: "15" }, base: { point: { x: 500, y: 500 } } });
|
||||
panel.addNodeEllipse({ code: { text: "16" }, base: { point: { x: 600, y: 500 } } });
|
||||
panel.addEdgeBeeline({ code: { text: "1-2" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "1" } }), endNode: panel.getNode({ code: { text: "2" } }) } });
|
||||
panel.addEdgeBeeline({ code: { text: "3-4" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "3" } }), endNode: panel.getNode({ code: { text: "4" } }) } });
|
||||
panel.addEdgeArc({ code: { text: "5-6" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "5" } }), endNode: panel.getNode({ code: { text: "6" } }) } });
|
||||
panel.addEdgeArc({ code: { text: "7-8" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "7" } }), endNode: panel.getNode({ code: { text: "8" } }) } });
|
||||
panel.addEdgeQuadratic({ code: { text: "9-10" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "9" } }), endNode: panel.getNode({ code: { text: "10" } }) } });
|
||||
panel.addEdgeQuadratic({ code: { text: "11-12" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "11" } }), endNode: panel.getNode({ code: { text: "12" } }) } });
|
||||
panel.addEdgeBezier({ code: { text: "13-14" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "13" } }), endNode: panel.getNode({ code: { text: "14" } }) } });
|
||||
panel.addEdgeBezier({ code: { text: "15-16" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "15" } }), endNode: panel.getNode({ code: { text: "16" } }) } });
|
||||
panel.addEdgeBeeline({ code: { text: "1-5" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "1" } }), endNode: panel.getNode({ code: { text: "5" } }) } });
|
||||
panel.addEdgeBeeline({ code: { text: "5-9" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "5" } }), endNode: panel.getNode({ code: { text: "9" } }) } });
|
||||
panel.addEdgeBeeline({ code: { text: "9-13" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "9" } }), endNode: panel.getNode({ code: { text: "13" } }) } });
|
||||
panel.addEdgeArc({ code: { text: "2-6" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "2" } }), endNode: panel.getNode({ code: { text: "6" } }) } });
|
||||
panel.addEdgeArc({ code: { text: "6-10" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "6" } }), endNode: panel.getNode({ code: { text: "10" } }) } });
|
||||
panel.addEdgeArc({ code: { text: "10-14" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "10" } }), endNode: panel.getNode({ code: { text: "14" } }) } });
|
||||
panel.addEdgeQuadratic({ code: { text: "3-7" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "3" } }), endNode: panel.getNode({ code: { text: "7" } }) } });
|
||||
panel.addEdgeQuadratic({ code: { text: "7-11" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "7" } }), endNode: panel.getNode({ code: { text: "11" } }) } });
|
||||
panel.addEdgeQuadratic({ code: { text: "11-15" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "11" } }), endNode: panel.getNode({ code: { text: "15" } }) } });
|
||||
panel.addEdgeBezier({ code: { text: "4-8" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "4" } }), endNode: panel.getNode({ code: { text: "8" } }) } });
|
||||
panel.addEdgeBezier({ code: { text: "8-12" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "8" } }), endNode: panel.getNode({ code: { text: "12" } }) } });
|
||||
panel.addEdgeBezier({ code: { text: "12-16" }, base: { lineWidth: 4, strokeStyle: "#FFFFFF", startNode: panel.getNode({ code: { text: "12" } }), endNode: panel.getNode({ code: { text: "16" } }) } });
|
||||
panel.addOrUpdateCar({
|
||||
type: "CarForklift",
|
||||
code: { text: "9000", enable: true },
|
||||
name: { text: "第[9000]号车", enable: true },
|
||||
lock: { enable: true },
|
||||
base: {
|
||||
point: panel.getNode({ code: { text: "5" } }).base.point,
|
||||
rotate: 45,
|
||||
carEdges: [
|
||||
{ code: { text: "1-2" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "2-6" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "5-6" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "5-9" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "9-10" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "10-14" }, type: "Next", process: 0 },
|
||||
{ code: { text: "13-14" }, type: "Next", process: 0 }
|
||||
],
|
||||
carNodes: [
|
||||
{ code: { text: "1" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "2" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "6" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "5" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "9" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "10" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "14" }, type: "Next", process: 0 },
|
||||
{ code: { text: "13" }, type: "Next", process: 0 }
|
||||
]
|
||||
}
|
||||
});
|
||||
panel.addOrUpdateCar({
|
||||
type: "CarConveyor",
|
||||
code: { text: "9001", enable: true },
|
||||
name: { text: "第[9001]号车", enable: true },
|
||||
base: {
|
||||
point: panel.getNode({ code: { text: "7" } }).base.point,
|
||||
rotate: 135,
|
||||
carEdges: [
|
||||
{ code: { text: "3-4" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "3-7" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "7-8" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "8-12" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "11-12" }, type: "Next", process: 0 },
|
||||
{ code: { text: "11-15" }, type: "Next", process: 0 },
|
||||
{ code: { text: "15-16" }, type: "Next", process: 0 }
|
||||
],
|
||||
carNodes: [
|
||||
{ code: { text: "4" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "3" }, type: "Prev", process: 1 },
|
||||
{ code: { text: "7" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "8" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "12" }, type: "Curr", process: 0.5 },
|
||||
{ code: { text: "11" }, type: "Next", process: 0 },
|
||||
{ code: { text: "15" }, type: "Next", process: 0 },
|
||||
{ code: { text: "16" }, type: "Next", process: 0 }
|
||||
]
|
||||
}
|
||||
});
|
||||
},
|
||||
test6: () => {
|
||||
page.testUtil.log("测试6:处理");
|
||||
page.testUtil.resetPanel();
|
||||
const panel = page.panel;
|
||||
panel.addNodeRect({ code: { text: "T11-N1" }, base: { point: { x: 200, y: 200 }, size: { w: 120, h: 80 } } });
|
||||
panel.addNodeEllipse({ code: { text: "T11-N2" }, base: { point: { x: 420, y: 200 }, size: { w: 120, h: 80 } } });
|
||||
panel.updateNode({ code: { text: "T11-N1", font: "16px Arial" }, name: { text: "Node-1", font: "14px Arial" }, base: { point: { x: 240, y: 220 }, size: { w: 140, h: 90 }, rotate: 12 } });
|
||||
panel.updateNode({ code: { text: "T11-N2" }, base: { point: { x: 460, y: 220 }, rotate: 18 } });
|
||||
panel.addEdgeBeeline({ code: { text: "T11-E1" }, base: { lineWidth: 4, startNode: panel.getNode({ code: { text: "T11-N1" } }), endNode: panel.getNode({ code: { text: "T11-N2" } }) } });
|
||||
panel.updateEdge({ code: { text: "T11-E1" }, base: { lineWidth: 6 } });
|
||||
panel.addZoneRect({ code: { text: "T11-Z1" }, base: { point: { x: 200, y: 380 }, size: { w: 160, h: 90 } } });
|
||||
panel.updateZone({ code: { text: "T11-Z1" }, base: { point: { x: 220, y: 400 }, rotate: 15 } });
|
||||
panel.addTagEllipse({ code: { text: "T11-T1" }, base: { point: { x: 420, y: 380 }, size: { w: 140, h: 90 } } });
|
||||
panel.updateTag({ code: { text: "T11-T1" }, name: { text: "Tag-1" }, base: { rotate: 25 } });
|
||||
panel.addCarForklift({ code: { text: "T11-C1" }, base: { point: { x: 640, y: 380 }, rotate: 0 } });
|
||||
panel.updateCar({ code: { text: "T11-C1" }, base: { rotate: 45 } });
|
||||
panel.deleteTag({ code: { text: "T11-T1" } });
|
||||
panel.deleteZone({ code: { text: "T11-Z1" } });
|
||||
panel.deleteEdge({ code: { text: "T11-E1" } });
|
||||
panel.deleteCar({ code: { text: "T11-C1" } });
|
||||
panel.deleteNode({ code: { text: "T11-N2" } });
|
||||
panel.deleteNode({ code: { text: "T11-N1" } });
|
||||
},
|
||||
test7: () => {
|
||||
page.testUtil.log("测试7:流程");
|
||||
page.testUtil.resetPanel();
|
||||
const panel = page.panel;
|
||||
const baseX = 200;
|
||||
const baseY = 200;
|
||||
panel.addNodeRect({ code: { text: "T14-N1" }, base: { point: { x: baseX, y: baseY }, size: { w: 100, h: 70 } } });
|
||||
panel.addNodeEllipse({ code: { text: "T14-N2" }, base: { point: { x: baseX + 200, y: baseY }, size: { w: 100, h: 70 } } });
|
||||
panel.addEdgeBeeline({ code: { text: "T14-E1" }, base: { lineWidth: 4, startNode: panel.getNode({ code: { text: "T14-N1" } }), endNode: panel.getNode({ code: { text: "T14-N2" } }) } });
|
||||
panel.addZoneRect({ code: { text: "T14-Z1" }, base: { point: { x: baseX, y: baseY + 200 }, size: { w: 140, h: 80 } } });
|
||||
panel.addTagRect({ code: { text: "T14-T1" }, base: { point: { x: baseX + 200, y: baseY + 200 }, size: { w: 140, h: 80 } } });
|
||||
panel.addCarForklift({ code: { text: "T14-C1" }, base: { point: { x: baseX + 400, y: baseY + 200 }, rotate: 30 } });
|
||||
panel.addNodeRect({ code: { text: "T15-N1", pointRatio: { w: -0.5, h: -0.5 } }, name: { text: "T15-N2", pointRatio: { w: 0.5, h: 0.5 } }, base: { point: { x: baseX + 500, y: baseY - 100 }, size: { w: 100, h: 100 } } });
|
||||
panel.addZoneRect({ code: { text: "T15-Z1", pointRatio: { w: 0, h: -0.5 } }, name: { text: "T15-Z2", pointRatio: { w: 0, h: 0.5 } }, base: { point: { x: baseX + 500, y: baseY }, size: { w: 150, h: 150 } } });
|
||||
panel.addTagRect({ code: { text: "T15-T1", pointRatio: { w: -0.5, h: 0 } }, name: { text: "T15-T2", pointRatio: { w: 0.5, h: 0 } }, base: { point: { x: baseX + 500, y: baseY + 200 }, size: { w: 200, h: 200 } } });
|
||||
panel.addNodeEllipse({ code: { text: "T16-N1", pointRatio: { w: 0, h: 0 } }, name: { text: "T16-N2", pointRatio: { w: 0, h: 0 } }, base: { point: { x: baseX, y: baseY + 400 }, size: { w: 100, h: 100 } } });
|
||||
panel.addZoneEllipse({ code: { text: "T16-Z1", pointRatio: { w: 0.5, h: 0 } }, name: { text: "T16-Z2", pointRatio: { w: -0.5, h: 0 } }, base: { point: { x: baseX + 100, y: baseY + 400 }, size: { w: 150, h: 150 } } });
|
||||
panel.addTagEllipse({ code: { text: "T16-T1", pointRatio: { w: 0.5, h: 0.5 } }, name: { text: "T16-T2", pointRatio: { w: -0.5, h: -0.5 } }, base: { point: { x: baseX + 300, y: baseY + 400 }, size: { w: 200, h: 200 } } });
|
||||
panel.setModeEdit();
|
||||
panel.setSwitchGrid();
|
||||
panel.setSwitchInfo();
|
||||
panel.setSwitchArea();
|
||||
panel.setSwitchZoom();
|
||||
panel.setSwitchCarRoute();
|
||||
panel.setSwitchCarEdges();
|
||||
panel.setSwitchCarNodes();
|
||||
panel.setSwitchFixed();
|
||||
panel.setSwitchFixedCode();
|
||||
panel.setSwitchFixedName();
|
||||
panel.setScalingAdapt();
|
||||
panel.setScalingNormal();
|
||||
panel.setSwitchInfo();
|
||||
panel.setSwitchArea();
|
||||
panel.setSwitchZoom();
|
||||
panel.addActiveAction((e) => e.code && e.code.text === "T14-N1");
|
||||
panel.switchActiveAction((e) => e.code && e.code.text === "T14-E1");
|
||||
panel.addActiveAction((e) => e.code && e.code.text === "T14-Z1");
|
||||
panel.addActiveAction((e) => e.code && e.code.text === "T14-T1");
|
||||
panel.addActiveAction((e) => e.code && e.code.text === "T14-C1");
|
||||
page.updateStatusBar();
|
||||
},
|
||||
test8: (count) => {
|
||||
page.testUtil.log("测试8:性能");
|
||||
page.testUtil.resetSeed(Date.now());
|
||||
const panel = page.panel;
|
||||
const shapeCount = Math.max(0, Math.floor(Number.isFinite(count) ? count : page.testConfig.shapeCount));
|
||||
const scatter = page.testUtil.scatterPoints(shapeCount, { jitter: 0.25 });
|
||||
const adders = page.testUtil.shuffleInPlace([
|
||||
page.testUtil.createRotateAdder(panel, "addNodeRect", "P-NR", { formatCode: (i) => `P-NR-${i.toString().padStart(4, "0")}` }),
|
||||
page.testUtil.createRotateAdder(panel, "addNodeEllipse", "P-NE", { formatCode: (i) => `P-NE-${i.toString().padStart(4, "0")}` }),
|
||||
page.testUtil.createRotateAdder(panel, "addZoneRect", "P-ZR"),
|
||||
page.testUtil.createRotateAdder(panel, "addZoneEllipse", "P-ZE"),
|
||||
page.testUtil.createRotateAdder(panel, "addTagRect", "P-TR"),
|
||||
page.testUtil.createRotateAdder(panel, "addTagEllipse", "P-TE"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarForklift", "P-CF"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarConveyor", "P-CC"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarTugger", "P-CT"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarCarrier", "P-CR")
|
||||
]);
|
||||
page.testUtil.measureBatchAdd(panel, scatter.points, adders, "perf:addShapes");
|
||||
page.testUtil.measureBatchEdges(panel, Math.floor(shapeCount * 1.2), "P", "perf:addEdges");
|
||||
},
|
||||
test9: (count) => {
|
||||
page.testUtil.log("测试9:压力");
|
||||
page.testUtil.resetSeed(Date.now());
|
||||
const panel = page.panel;
|
||||
const shapeCount = Math.max(0, Math.floor(Number.isFinite(count) ? count : page.testConfig.shapeCount));
|
||||
const scatter = page.testUtil.scatterPoints(shapeCount, { jitter: 0.25 });
|
||||
const minGap = Math.min(scatter.gapX, scatter.gapY);
|
||||
const baseSize = Math.max(50, Math.floor(minGap * 0.5));
|
||||
const sizeJitter = Math.max(6, Math.floor(baseSize * 0.25));
|
||||
const rectSize = () => {
|
||||
const w = Math.max(36, baseSize + page.testUtil.randInt(-sizeJitter, sizeJitter));
|
||||
const h = Math.max(28, Math.floor(w * 0.7));
|
||||
return { w, h };
|
||||
};
|
||||
const polySize = () => Math.max(40, baseSize + page.testUtil.randInt(-sizeJitter, sizeJitter));
|
||||
const rotate = () => page.testUtil.randInt(0, 359);
|
||||
const adders = page.testUtil.shuffleInPlace([
|
||||
page.testUtil.createSizedAdder(panel, "addNodeRect", "S-NR", rectSize, rotate),
|
||||
page.testUtil.createSizedAdder(panel, "addNodeEllipse", "S-NE", rectSize, rotate),
|
||||
page.testUtil.createPolyAdder(panel, "addNodePoly", "S-NP", page.testUtil.polyPoints, polySize, rotate),
|
||||
page.testUtil.createPolyAdder(panel, "addNodePath", "S-NH", page.testUtil.pathPoints, polySize, rotate),
|
||||
page.testUtil.createSizedAdder(panel, "addZoneRect", "S-ZR", rectSize, rotate),
|
||||
page.testUtil.createSizedAdder(panel, "addZoneEllipse", "S-ZE", rectSize, rotate),
|
||||
page.testUtil.createPolyAdder(panel, "addZonePoly", "S-ZP", page.testUtil.polyPoints, polySize, rotate),
|
||||
page.testUtil.createPolyAdder(panel, "addZonePath", "S-ZH", page.testUtil.pathPoints, polySize, rotate),
|
||||
page.testUtil.createSizedAdder(panel, "addTagRect", "S-TR", rectSize, rotate),
|
||||
page.testUtil.createSizedAdder(panel, "addTagEllipse", "S-TE", rectSize, rotate),
|
||||
page.testUtil.createPolyAdder(panel, "addTagPoly", "S-TP", page.testUtil.polyPoints, polySize, rotate),
|
||||
page.testUtil.createPolyAdder(panel, "addTagPath", "S-TH", page.testUtil.pathPoints, polySize, rotate),
|
||||
page.testUtil.createRotateAdder(panel, "addCarForklift", "S-CF"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarConveyor", "S-CC"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarTugger", "S-CT"),
|
||||
page.testUtil.createRotateAdder(panel, "addCarCarrier", "S-CR")
|
||||
]);
|
||||
page.testUtil.measureBatchAdd(panel, scatter.points, adders, "stress:addShapes");
|
||||
page.testUtil.measureBatchEdges(panel, 2e3, "S", "stress:addEdges");
|
||||
}
|
||||
};
|
||||
panelNamespace.test = Object.freeze({
|
||||
page,
|
||||
config: page.testConfig,
|
||||
util: page.testUtil,
|
||||
names: Object.freeze(Object.keys(testActions)),
|
||||
has(name) {
|
||||
return typeof testActions[name] === "function";
|
||||
},
|
||||
stop() {
|
||||
page.testUtil.carStop();
|
||||
},
|
||||
run(name, ...args) {
|
||||
const handler = testActions[name];
|
||||
if (typeof handler !== "function") {
|
||||
throw new Error(`未找到测试方法:${name}`);
|
||||
}
|
||||
return handler(...args);
|
||||
}
|
||||
});
|
||||
page.syncTestButtons?.();
|
||||
})();
|
||||
Vendored
+2652
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
using System.Net;
|
||||
using UdpServer = Common.Net.Udp.UdpServer;
|
||||
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class Command
|
||||
{
|
||||
public UdpServer Server { get; private set; }
|
||||
|
||||
public EndPoint Remote { get => Server.RemoteEndPoint; set => Server.RemoteEndPoint = value; }
|
||||
|
||||
public Command(IPEndPoint local)
|
||||
{
|
||||
Server = new UdpServer()
|
||||
{
|
||||
LocalEndPoint = local
|
||||
};
|
||||
}
|
||||
|
||||
public void SendState(byte command, byte state, ulong timeSpan, IPEndPoint remote)
|
||||
{
|
||||
var sendMessage = new SendQueryMessage().SetMessage(command, state, timeSpan);
|
||||
var sendByteArray = sendMessage.GetByteArray();
|
||||
Server.Send(sendByteArray, remote);
|
||||
}
|
||||
public static ReceiveStateMessage GetReceiveStateMessage(byte[] byteArray) => new ReceiveStateMessage().GetMessage(byteArray);
|
||||
public static byte[] GetReceiveStateByteArray(ReceiveStateMessage message) => message.GetByteArray();
|
||||
|
||||
public void SendLightControls(byte taskNo, byte sectionCount, LightControlMessage[] lightControlMessages, ulong timeStamp, IPEndPoint remote)
|
||||
{
|
||||
var sendMessage = new SendControlMessage().SetMessage(taskNo, sectionCount, lightControlMessages, timeStamp);
|
||||
var sendByteArray = sendMessage.GetByteArray();
|
||||
Server.Send(sendByteArray, remote);
|
||||
}
|
||||
public static ReceiveControlRespMessage GetReceiveControlRespMessage(byte[] byteArray) => new ReceiveControlRespMessage().GetMessage(byteArray);
|
||||
public static byte[] GetSendControlByteArray(SendControlMessage message) => message.GetByteArray();
|
||||
|
||||
|
||||
public void SendPressedStateResponse(byte command, byte taskNo, byte lightNo, byte state, uint led, ulong timeSpan, IPEndPoint remote)
|
||||
{
|
||||
var sendMessage = new SendPressedStateRespMessage().SetMessage(command, taskNo, lightNo, state, led, timeSpan);
|
||||
var sendByteArray = sendMessage.GetByteArray();
|
||||
Server.Send(sendByteArray, remote);
|
||||
}
|
||||
|
||||
public static SendPressedStateRespMessage GetPressedStateResponseMessage(byte[] byteArray) => new SendPressedStateRespMessage().GetMessage(byteArray);
|
||||
|
||||
public static byte[] GetPressedStateResponseByteArray(SendPressedStateRespMessage message) => message.GetByteArray();
|
||||
|
||||
public static ReceivePressedStateMessage GetReceivePressedStateMessage(byte[] byteArray) => new ReceivePressedStateMessage().GetMessage(byteArray);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public static class Crc32
|
||||
{
|
||||
private static readonly uint[] Table;
|
||||
|
||||
static Crc32()
|
||||
{
|
||||
// 初始化CRC32查找表
|
||||
Table = new uint[256];
|
||||
const uint poly = 0xEDB88320; // 标准CRC32多项式
|
||||
|
||||
for (uint i = 0; i < 256; i++)
|
||||
{
|
||||
var crc = i;
|
||||
for (var j = 0; j < 8; j++)
|
||||
{
|
||||
crc = (crc & 1) == 1
|
||||
? (crc >> 1) ^ poly
|
||||
: crc >> 1;
|
||||
}
|
||||
Table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint Compute(byte[] data)
|
||||
{
|
||||
uint crc = 0xFFFFFFFF; // 初始值
|
||||
foreach (byte b in data)
|
||||
{
|
||||
// 查表更新CRC值
|
||||
crc = (crc >> 8) ^ Table[(crc ^ b) & 0xFF];
|
||||
}
|
||||
return crc ^ 0xFFFFFFFF; // 最终异或处理
|
||||
}
|
||||
|
||||
public static byte[] LittleEndianComputeBytes(byte[] data)
|
||||
{
|
||||
uint crc = Compute(data);
|
||||
return BitConverter.GetBytes(crc);
|
||||
}
|
||||
|
||||
public static byte[] BigEndianComputeBytes(byte[] data)
|
||||
{
|
||||
uint crc = Compute(data);
|
||||
var result = BitConverter.GetBytes(crc);
|
||||
Array.Reverse(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>2.4.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Common.Net" Version="2.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class LightControlMessage
|
||||
{
|
||||
public byte StartLightNo { get; set; }
|
||||
public byte EndLightNo { get; set; }
|
||||
public byte State { get; set; } = 0;
|
||||
public uint Led { get; set; }
|
||||
public byte Reserve { get; set; }
|
||||
|
||||
|
||||
public LightControlMessage SetMessage(
|
||||
byte startLightNo,
|
||||
byte endLightNo,
|
||||
byte state,
|
||||
uint led)
|
||||
{
|
||||
StartLightNo = startLightNo;
|
||||
EndLightNo = endLightNo;
|
||||
State = state;
|
||||
Led = led;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LightControlMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null || byteArray.Length != 8) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
StartLightNo = byteArray[0];
|
||||
EndLightNo = byteArray[1];
|
||||
State = byteArray[2];
|
||||
Led = BitConverter.ToUInt32(byteArray[3..7]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[8];
|
||||
byteArray[0] = StartLightNo;
|
||||
byteArray[1] = EndLightNo;
|
||||
byteArray[2] = State;
|
||||
var ledArr = BitConverter.GetBytes(Led);
|
||||
byteArray[3] = ledArr[0];
|
||||
byteArray[4] = ledArr[1];
|
||||
byteArray[5] = ledArr[2];
|
||||
byteArray[6] = ledArr[3];
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class LightStateMessage
|
||||
{
|
||||
public byte Alarm { get; set; }
|
||||
public byte State { get; set; }
|
||||
|
||||
public LightStateMessage SetMessage(
|
||||
byte alarm,
|
||||
byte state)
|
||||
{
|
||||
Alarm = alarm;
|
||||
State = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LightStateMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null || byteArray.Length < 2) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Alarm = byteArray[0];
|
||||
State = byteArray[1];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[2];
|
||||
byteArray[0] = Alarm;
|
||||
byteArray[1] = State;
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class ReceiveControlRespMessage
|
||||
{
|
||||
public byte Begin { get; set; } = 0xBB;
|
||||
public byte Command { get; set; }
|
||||
public string? Uuid { get; set; }
|
||||
public byte TaskNo { get; set; }
|
||||
public byte[] Reserve { get; set; } = new byte[6];
|
||||
public byte[] Check { get; set; } = new byte[4];
|
||||
public byte[] End { get; set; } = { 0xEE, 0xEE, 0xEE };
|
||||
|
||||
public ReceiveControlRespMessage SetMessage(
|
||||
byte command,
|
||||
string uuid,
|
||||
byte taskNo)
|
||||
{
|
||||
Command = command;
|
||||
Uuid = uuid;
|
||||
TaskNo = taskNo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReceiveControlRespMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null || byteArray.Length < 24) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[98] != Utility.XOR(byteArray[1..^2])) throw new Exception($"数据校验错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[0] != 0xBB || byteArray[99] != 0xEE) throw new Exception($"数据帧头尾错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Begin = byteArray[0];
|
||||
Command = byteArray[1];
|
||||
Uuid = Utility.ByteArrayToHexString(byteArray[2..10]).ToLower();
|
||||
TaskNo = byteArray[10];
|
||||
Reserve = byteArray[11..17];
|
||||
Check = byteArray[17..21];
|
||||
End = byteArray[21..24];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[24];
|
||||
byteArray[0] = Begin;
|
||||
byteArray[1] = Command;
|
||||
var uuid = Utility.HexStringToBytes(Uuid!);
|
||||
Array.Copy(uuid, 0, byteArray, 2, uuid.Length);
|
||||
byteArray[10] = TaskNo;
|
||||
Array.Copy(Reserve, 0, byteArray, 11, Reserve.Length);
|
||||
var crc32Check = Crc32.LittleEndianComputeBytes(byteArray[1..^7]);
|
||||
Array.Copy(crc32Check, 0, byteArray, 17, crc32Check.Length);
|
||||
Array.Copy(End, 0, byteArray, 21, End.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class ReceivePressedStateMessage
|
||||
{
|
||||
public byte Begin { get; set; } = 0xBB;
|
||||
public byte Command { get; set; }
|
||||
public string? Uuid { get; set; }
|
||||
public byte TaskNo { get; set; }
|
||||
public byte LightNo { get; set; }
|
||||
public byte[] Reserve { get; set; } = new byte[6];
|
||||
public byte[] Check { get; set; } = new byte[4];
|
||||
public byte[] End { get; set; } = { 0xEE, 0xEE, 0xEE };
|
||||
|
||||
public ReceivePressedStateMessage SetMessage(
|
||||
byte command,
|
||||
string uuid,
|
||||
byte taskNo,
|
||||
byte lightNo)
|
||||
{
|
||||
Command = command;
|
||||
Uuid = uuid;
|
||||
TaskNo = taskNo;
|
||||
LightNo = lightNo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReceivePressedStateMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null || byteArray.Length < 24) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[98] != Utility.XOR(byteArray[1..^2])) throw new Exception($"数据校验错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[0] != 0xBB || byteArray[99] != 0xEE) throw new Exception($"数据帧头尾错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Begin = byteArray[0];
|
||||
Command = byteArray[1];
|
||||
Uuid = Utility.ByteArrayToHexString(byteArray[2..10]).ToLower();
|
||||
TaskNo = byteArray[10];
|
||||
LightNo = byteArray[11];
|
||||
Reserve = byteArray[12..17];
|
||||
Check = byteArray[17..21];
|
||||
End = byteArray[21..24];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[24];
|
||||
byteArray[0] = Begin;
|
||||
byteArray[1] = Command;
|
||||
var uuid = Utility.HexStringToBytes(Uuid!);
|
||||
Array.Copy(uuid, 0, byteArray, 2, uuid.Length);
|
||||
byteArray[10] = TaskNo;
|
||||
byteArray[11] = LightNo;
|
||||
Array.Copy(Reserve, 0, byteArray, 12, Reserve.Length);
|
||||
var crc32Check = Crc32.LittleEndianComputeBytes(byteArray[1..^7]);
|
||||
Array.Copy(crc32Check, 0, byteArray, 17, crc32Check.Length);
|
||||
Array.Copy(End, 0, byteArray, 21, End.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class ReceiveStateMessage
|
||||
{
|
||||
public byte Begin { get; set; } = 0xBB;
|
||||
public byte Command { get; set; }
|
||||
public string? Uuid { get; set; }
|
||||
public string? SoftwareVersion { get; set; }
|
||||
public string? HardwareVersion { get; set; }
|
||||
public uint Alarm { get; set; }
|
||||
public byte Count { get; set; }
|
||||
public LightStateMessage[] LightMessages { get; set; } = new LightStateMessage[128];
|
||||
public byte[] Reserve { get; set; } = new byte[14];
|
||||
public byte[] Check { get; set; } = new byte[4];
|
||||
public byte[] End { get; set; } = { 0xEE, 0xEE, 0xEE };
|
||||
|
||||
public ReceiveStateMessage SetMessage(
|
||||
byte command,
|
||||
string uuid,
|
||||
string softwareVersion,
|
||||
string hardwareVersion,
|
||||
uint alarm,
|
||||
byte count,
|
||||
LightStateMessage[] lightMessages)
|
||||
{
|
||||
Command = command;
|
||||
Uuid = uuid;
|
||||
SoftwareVersion = softwareVersion;
|
||||
HardwareVersion = hardwareVersion;
|
||||
Alarm = alarm;
|
||||
Count = count;
|
||||
Array.Copy(lightMessages, 0, LightMessages, 0, lightMessages.Length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReceiveStateMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null || byteArray.Length < 300) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[98] != Utility.XOR(byteArray[1..^2])) throw new Exception($"数据校验错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[0] != 0xBB || byteArray[99] != 0xEE) throw new Exception($"数据帧头尾错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Begin = byteArray[0];
|
||||
Command = byteArray[1];
|
||||
Uuid = Utility.ByteArrayToHexString(byteArray[2..10]).ToLower();
|
||||
HardwareVersion = $"{byteArray[11]}.{byteArray[12]}.{byteArray[13]}";
|
||||
SoftwareVersion = $"{byteArray[15]}.{byteArray[16]}.{byteArray[17]}";
|
||||
Alarm = BitConverter.ToUInt32(byteArray[18..22]);
|
||||
Count = byteArray[22];
|
||||
for (var i = 0; i < 128; i++)
|
||||
{
|
||||
var start = i * 2 + 23;
|
||||
var end = start + 2;
|
||||
LightMessages[i] = new LightStateMessage().GetMessage(byteArray[start..end]);
|
||||
}
|
||||
Reserve = byteArray[279..293];
|
||||
Check = byteArray[293..297];
|
||||
End = byteArray[297..300];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[300];
|
||||
byteArray[0] = Begin;
|
||||
byteArray[1] = Command;
|
||||
var uuid = Utility.HexStringToBytes(Uuid!);
|
||||
Array.Copy(uuid, 0, byteArray, 2, uuid.Length);
|
||||
//硬件版本
|
||||
var hardVersion = HardwareVersion!.Split('.');
|
||||
byteArray[11] = byte.Parse(hardVersion[0]);
|
||||
byteArray[12] = byte.Parse(hardVersion[1]);
|
||||
byteArray[13] = byte.Parse(hardVersion[2]);
|
||||
//软件版本
|
||||
var softVersion = SoftwareVersion!.Split('.');
|
||||
byteArray[15] = byte.Parse(softVersion[0]);
|
||||
byteArray[16] = byte.Parse(softVersion[1]);
|
||||
byteArray[17] = byte.Parse(softVersion[2]);
|
||||
var alarm = BitConverter.GetBytes(Alarm);
|
||||
byteArray[18] = alarm[0];
|
||||
byteArray[19] = alarm[1];
|
||||
byteArray[20] = alarm[2];
|
||||
byteArray[21] = alarm[3];
|
||||
var count = Count;
|
||||
var ligthMessages = LightMessages.SelectMany(e => e.GetByteArray()).ToArray();
|
||||
Array.Copy(ligthMessages, 0, byteArray, 23, ligthMessages.Length);
|
||||
Array.Copy(Reserve, 0, byteArray, 279, Reserve.Length);
|
||||
var crc32Check = Crc32.LittleEndianComputeBytes(byteArray[1..^7]);
|
||||
Array.Copy(crc32Check, 0, byteArray, 293, crc32Check.Length);
|
||||
Array.Copy(End, 0, byteArray, 297, End.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class SendControlMessage
|
||||
{
|
||||
public byte Begin { get; set; } = 0xBB;
|
||||
public byte Command { get; set; } = 0x01;
|
||||
public byte TaskNo { get; set; }
|
||||
public byte SectionCount { get; set; }
|
||||
|
||||
public List<LightControlMessage> LightControlMessages { get; set; } = new List<LightControlMessage>();
|
||||
public ulong TimeStamp { get; set; }
|
||||
public byte[] Reserve { get; set; } = new byte[5];
|
||||
public byte[] Check { get; set; } = new byte[4];
|
||||
public byte[] End { get; set; } = { 0xEE, 0xEE, 0xEE };
|
||||
|
||||
public SendControlMessage SetMessage(
|
||||
byte taskNo,
|
||||
byte sectionCount,
|
||||
LightControlMessage[] lightControlMessages,
|
||||
ulong timeStamp)
|
||||
{
|
||||
TaskNo = taskNo;
|
||||
SectionCount = sectionCount;
|
||||
TimeStamp = timeStamp;
|
||||
LightControlMessages.AddRange(lightControlMessages);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SendControlMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
//最少一个灯范围控制
|
||||
if (byteArray == null || byteArray.Length < 32) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[298] != Utility.XOR(byteArray[1..^2])) throw new Exception($"数据校验错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Begin = byteArray[0];
|
||||
Command = byteArray[1];
|
||||
TaskNo = byteArray[2];
|
||||
SectionCount = byteArray[3];
|
||||
if (SectionCount == 0)
|
||||
{
|
||||
throw new Exception($"数据长度错误,控制段个数错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
}
|
||||
if (SectionCount > 60)
|
||||
{
|
||||
throw new Exception($"数据长度错误,控制段个数超出限制:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
}
|
||||
var length = SectionCount * 8 + 24;
|
||||
if (byteArray.Length != length) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
for (var i = 0; i < SectionCount; i++)
|
||||
{
|
||||
var start = i * 8 + 4;
|
||||
var end = start + 8;
|
||||
LightControlMessages.Add(new LightControlMessage().GetMessage(byteArray[start..end]));
|
||||
}
|
||||
Reserve = byteArray[(4 + 8 * SectionCount)..(9 + 8 * SectionCount)];
|
||||
TimeStamp = BitConverter.ToUInt64(byteArray[(9 + 8 * SectionCount)..(17 + 8 * SectionCount)]);
|
||||
Check = byteArray[(17 + 8 * SectionCount)..(21 + 8 * SectionCount)];
|
||||
End = byteArray[(21 + 8 * SectionCount)..(24 + 8 * SectionCount)];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var length = 24 + 8 * SectionCount;
|
||||
var byteArray = new byte[length];
|
||||
byteArray[0] = Begin;
|
||||
byteArray[1] = Command;
|
||||
byteArray[2] = TaskNo;
|
||||
byteArray[3] = SectionCount;
|
||||
var lightControlMessages = LightControlMessages.SelectMany(e => e.GetByteArray()).ToArray();
|
||||
Array.Copy(lightControlMessages, 0, byteArray, 4, lightControlMessages.Length);
|
||||
Array.Copy(Reserve, 0, byteArray, 4 + 8 * SectionCount, Reserve.Length);
|
||||
var date = new DateTime(1970, 1, 1, 8, 0, 0).AddMilliseconds(TimeStamp);
|
||||
byteArray[9 + 8 * SectionCount] = (byte)(date.Year - 1970);
|
||||
byteArray[10 + 8 * SectionCount] = (byte)(date.Month);
|
||||
byteArray[11 + 8 * SectionCount] = (byte)(date.Day);
|
||||
byteArray[12 + 8 * SectionCount] = (byte)(date.Hour);
|
||||
byteArray[13 + 8 * SectionCount] = (byte)(date.Minute);
|
||||
byteArray[14 + 8 * SectionCount] = (byte)(date.Second);
|
||||
var millisecond = BitConverter.GetBytes((ushort)(date.Millisecond));
|
||||
byteArray[15 + 8 * SectionCount] = millisecond[0];
|
||||
byteArray[16 + 8 * SectionCount] = millisecond[1];
|
||||
var crc32Check = Crc32.LittleEndianComputeBytes(byteArray[1..^7]);
|
||||
Array.Copy(crc32Check, 0, byteArray, 17 + 8 * SectionCount, crc32Check.Length);
|
||||
Array.Copy(End, 0, byteArray, 21 + 8 * SectionCount, End.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class SendPressedStateRespMessage
|
||||
{
|
||||
public byte Begin { get; set; } = 0xBB;
|
||||
public byte Command { get; set; } = 0x82;
|
||||
public byte TaskNo { get; set; }
|
||||
public byte LightNo { get; set; }
|
||||
public byte State { get; set; }
|
||||
public uint Led { get; set; }
|
||||
public ulong TimeStamp { get; set; }
|
||||
public byte[] Check { get; set; } = new byte[4];
|
||||
public byte[] End { get; set; } = { 0xEE, 0xEE, 0xEE };
|
||||
|
||||
public SendPressedStateRespMessage SetMessage(
|
||||
byte command,
|
||||
byte taskNo,
|
||||
byte lightNo,
|
||||
byte state,
|
||||
uint led,
|
||||
ulong param)
|
||||
{
|
||||
Command = command;
|
||||
TaskNo = taskNo;
|
||||
LightNo = lightNo;
|
||||
State = state;
|
||||
Led = led;
|
||||
TimeStamp = param;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SendPressedStateRespMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null || byteArray.Length != 24) throw new Exception($"数据长度错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[98] != Utility.XOR(byteArray[1..^2])) throw new Exception($"数据校验错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[0] != 0xBB || byteArray[99] != 0xEE) throw new Exception($"数据帧头尾错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Begin = byteArray[0];
|
||||
Command = byteArray[1];
|
||||
TaskNo = byteArray[2];
|
||||
LightNo = byteArray[3];
|
||||
State = byteArray[4];
|
||||
Led = BitConverter.ToUInt32(byteArray[5..9]);
|
||||
TimeStamp = BitConverter.ToUInt64(byteArray[9..17]);
|
||||
Check = byteArray[17..21];
|
||||
End = byteArray[21..24];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[24];
|
||||
byteArray[0] = Begin;
|
||||
byteArray[1] = Command;
|
||||
byteArray[2] = TaskNo;
|
||||
byteArray[3] = LightNo;
|
||||
byteArray[4] = State;
|
||||
var ledArr = BitConverter.GetBytes(Led);
|
||||
byteArray[5] = ledArr[0];
|
||||
byteArray[6] = ledArr[1];
|
||||
byteArray[7] = ledArr[2];
|
||||
byteArray[8] = ledArr[3];
|
||||
var date = new DateTime(1970, 1, 1, 8, 0, 0).AddMilliseconds(TimeStamp);
|
||||
byteArray[9] = (byte)(date.Year - 1970);
|
||||
byteArray[10] = (byte)(date.Month);
|
||||
byteArray[11] = (byte)(date.Day);
|
||||
byteArray[12] = (byte)(date.Hour);
|
||||
byteArray[13] = (byte)(date.Minute);
|
||||
byteArray[14] = (byte)(date.Second);
|
||||
var millisecond = BitConverter.GetBytes((ushort)(date.Millisecond));
|
||||
byteArray[15] = millisecond[0];
|
||||
byteArray[16] = millisecond[1];
|
||||
var crc32Check = Crc32.LittleEndianComputeBytes(byteArray[1..^7]);
|
||||
Array.Copy(crc32Check, 0, byteArray, 17, crc32Check.Length);
|
||||
Array.Copy(End, 0, byteArray, 21, End.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public class SendQueryMessage
|
||||
{
|
||||
public byte Begin { get; set; } = 0xBB;
|
||||
public byte Command { get; set; } = 0x00;
|
||||
public byte State { get; set; } = 0x00;
|
||||
public byte[] Reserve { get; set; } = new byte[6];
|
||||
public ulong TimeStamp { get; set; }
|
||||
public byte[] Check { get; set; } = new byte[4];
|
||||
public byte[] End { get; set; } = { 0xEE, 0xEE, 0xEE };
|
||||
|
||||
public SendQueryMessage SetMessage(
|
||||
byte command,
|
||||
byte state,
|
||||
ulong param)
|
||||
{
|
||||
Command = command;
|
||||
TimeStamp = param;
|
||||
State = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SendQueryMessage GetMessage(byte[] byteArray)
|
||||
{
|
||||
if (byteArray is null || byteArray.Length < 24) throw new Exception($"数据长度错误: {Utility.ByteArrayToHexString(byteArray)}");
|
||||
//if (byteArray[48] != Utility.XOR(byteArray[1..^2])) throw new Exception($"数据校验错误:{Utility.ByteArrayToHexString(byteArray)}");
|
||||
Begin = byteArray[0];
|
||||
Command = byteArray[1];
|
||||
State = byteArray[2];
|
||||
Reserve = byteArray[3..9];
|
||||
TimeStamp = BitConverter.ToUInt64(byteArray[9..17]);
|
||||
Check = byteArray[17..21];
|
||||
End = byteArray[21..24];
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
var byteArray = new byte[24];
|
||||
byteArray[0] = Begin;
|
||||
byteArray[1] = Command;
|
||||
byteArray[2] = State;
|
||||
Array.Copy(Reserve, 0, byteArray, 3, Reserve.Length);
|
||||
var date = new DateTime(1970, 1, 1, 8, 0, 0).AddMilliseconds(TimeStamp);
|
||||
byteArray[9] = (byte)(date.Year - 1970);
|
||||
byteArray[10] = (byte)(date.Month);
|
||||
byteArray[11] = (byte)(date.Day);
|
||||
byteArray[12] = (byte)(date.Hour);
|
||||
byteArray[13] = (byte)(date.Minute);
|
||||
byteArray[14] = (byte)(date.Second);
|
||||
var millisecond = BitConverter.GetBytes((ushort)(date.Millisecond));
|
||||
byteArray[15] = millisecond[0];
|
||||
byteArray[16] = millisecond[1];
|
||||
var crc32Check = Crc32.LittleEndianComputeBytes(byteArray[1..^7]);
|
||||
Array.Copy(crc32Check, 0, byteArray, 17, crc32Check.Length);
|
||||
Array.Copy(End, 0, byteArray, 21, End.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FASS.Extend.Master
|
||||
{
|
||||
public static class Utility
|
||||
{
|
||||
public static string ByteArrayToHexString(byte[]? byteArray, string separator = "")
|
||||
{
|
||||
if (byteArray is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return string.Join(separator, byteArray.Select(t => t.ToString("X2")));
|
||||
}
|
||||
|
||||
public static byte XOR(byte[] byteArray)
|
||||
{
|
||||
byte xor = 0;
|
||||
for (int i = 0; i < byteArray.Length; i++)
|
||||
{
|
||||
xor ^= byteArray[i];
|
||||
}
|
||||
return xor;
|
||||
}
|
||||
|
||||
public static byte[] GetCRC16(byte[] data)
|
||||
{
|
||||
byte b = byte.MaxValue;
|
||||
byte b2 = byte.MaxValue;
|
||||
byte b3 = 1;
|
||||
byte b4 = 160;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
b = (byte)(b ^ data[i]);
|
||||
for (int j = 0; j <= 7; j++)
|
||||
{
|
||||
byte b5 = b2;
|
||||
byte b6 = b;
|
||||
b2 = (byte)(b2 >> 1);
|
||||
b = (byte)(b >> 1);
|
||||
if ((b5 & 1) == 1)
|
||||
{
|
||||
b = (byte)(b | 0x80u);
|
||||
}
|
||||
if ((b6 & 1) == 1)
|
||||
{
|
||||
b2 = (byte)(b2 ^ b4);
|
||||
b = (byte)(b ^ b3);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new byte[2]
|
||||
{
|
||||
b,b2
|
||||
};
|
||||
}
|
||||
|
||||
public static byte[] HexStringToBytes(string hex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hex))
|
||||
throw new ArgumentException("输入字符串不能为空");
|
||||
|
||||
// 清理字符串中的非十六进制字符
|
||||
string cleanHex = Regex.Replace(hex, "[^0-9A-Fa-f]", "");
|
||||
|
||||
if (cleanHex.Length % 2 != 0)
|
||||
throw new FormatException("十六进制字符串长度必须为偶数");
|
||||
|
||||
return Convert.FromHexString(cleanHex);
|
||||
}
|
||||
|
||||
public static List<byte[]> SplitByMarkers(byte[] source, byte head, byte[] tail)
|
||||
{
|
||||
List<byte[]> result = new List<byte[]>();
|
||||
int startIndex = 0;
|
||||
int tailLength = tail.Length;
|
||||
|
||||
if (tailLength != 3)
|
||||
throw new ArgumentException("Tail marker must be 3 bytes");
|
||||
|
||||
while (startIndex < source.Length)
|
||||
{
|
||||
// 查找头字节
|
||||
int headPos = Array.IndexOf(source, head, startIndex);
|
||||
if (headPos == -1) break;
|
||||
|
||||
// 检查尾部空间是否足够
|
||||
int minTailStart = headPos + 1;
|
||||
int maxPossibleTailStart = source.Length - tailLength;
|
||||
|
||||
bool tailFound = false;
|
||||
int tailStart = minTailStart;
|
||||
|
||||
// 从最小可能位置开始查找尾部
|
||||
while (tailStart <= maxPossibleTailStart)
|
||||
{
|
||||
// 检查连续三个字节是否匹配尾部
|
||||
if (source[tailStart] == tail[0] &&
|
||||
source[tailStart + 1] == tail[1] &&
|
||||
source[tailStart + 2] == tail[2] && ((tailStart + 2 == source.Length-1) ||(source[tailStart + 3] == head)))//找到尾部是整条报文的末尾、或者是下一帧报文的包头
|
||||
{
|
||||
tailFound = true;
|
||||
break;
|
||||
}
|
||||
tailStart++;
|
||||
}
|
||||
|
||||
if (!tailFound) break;
|
||||
|
||||
// 计算完整数据块(包含头尾)
|
||||
int chunkEnd = tailStart + tailLength - 1;
|
||||
int chunkSize = chunkEnd - headPos + 1;
|
||||
|
||||
byte[] chunk = new byte[chunkSize];
|
||||
Array.Copy(source, headPos, chunk, 0, chunkSize);
|
||||
result.Add(chunk);
|
||||
|
||||
// 更新起始点为尾部之后
|
||||
startIndex = chunkEnd + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool[] ByteToBoolArray(byte b)
|
||||
{
|
||||
// 创建长度为8的布尔数组(一个字节=8位)
|
||||
bool[] result = new bool[8];
|
||||
|
||||
// 从高位到低位遍历(索引0对应最高位,索引7对应最低位)
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
// 通过位掩码检查每一位的值
|
||||
result[i] = (b & (1 << (7 - i))) != 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte BoolArrayToByte(bool[] bools)
|
||||
{
|
||||
if (bools.Length != 8)
|
||||
throw new ArgumentException("数组长度必须为 8");
|
||||
|
||||
byte result = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (bools[i])
|
||||
{
|
||||
// 选择位序:
|
||||
// 低位优先(第一个元素对应 byte 的最低位,即 bit0)
|
||||
//result |= (byte)(1 << i);
|
||||
|
||||
// 高位优先(第一个元素对应 byte 的最高位,即 bit7)
|
||||
result |= (byte)(1 << (7 - i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static byte SetLightColour(string value, bool ledOpen)
|
||||
{
|
||||
var lightValue = byte.Parse(value);
|
||||
var boolArr = ByteToBoolArray(lightValue);
|
||||
boolArr[1] = ledOpen;
|
||||
return BoolArrayToByte(boolArr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置红灯
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightRed()
|
||||
{
|
||||
return LightStateControl(false, false, false, true, false, false, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置红灯闪烁
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightRedWithFlash()
|
||||
{
|
||||
return LightStateControl(false, false, true, true, false, false, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置绿灯+数显
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightGreenWithLed()
|
||||
{
|
||||
return LightStateControl(false, true, false, false, false, true, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置绿灯
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightGreen()
|
||||
{
|
||||
return LightStateControl(false, false, false, false, false, true, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置黄灯
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightYellow()
|
||||
{
|
||||
return LightStateControl(false, false, false, true, false, true, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置洋红色灯
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightMagenta()
|
||||
{
|
||||
return LightStateControl(false, false, false, true, false, false, false, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置洋红灯+数显
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightMagentaWithLed()
|
||||
{
|
||||
return LightStateControl(false, true, false, true, false, false, false, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灭灯
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetLightOff()
|
||||
{
|
||||
return LightStateControl(false, false, false, false, false, false, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置显示灯地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte SetDisplayLightAddress()
|
||||
{
|
||||
return LightStateControl(true, false, false, false, false, false, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置灯状态
|
||||
/// </summary>
|
||||
/// <param name="fd">数码管闪烁</param>
|
||||
/// <param name="sd">数码管开关</param>
|
||||
/// <param name="fr">红色闪烁</param>
|
||||
/// <param name="sr">红</param>
|
||||
/// <param name="fg">绿色闪烁</param>
|
||||
/// <param name="sg">绿</param>
|
||||
/// <param name="fb">蓝色闪烁</param>
|
||||
/// <param name="sb">蓝</param>
|
||||
/// <returns></returns>
|
||||
public static byte LightStateControl(bool fd = false, bool sd = false, bool fr = false, bool sr = false, bool fg = false, bool sg = false, bool fb = false, bool sb = false)
|
||||
{
|
||||
bool[] bools = new bool[8] { fd, sd, fr, sr, fg, sg, fb, sb };
|
||||
return BoolArrayToByte(bools);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制转bcb
|
||||
/// </summary>
|
||||
/// <param name="decimalNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static byte DecimalToBcd(byte decimalNumber)
|
||||
{
|
||||
int tens = decimalNumber / 10; // 获取十位数字
|
||||
int ones = decimalNumber % 10; // 获取个位数字
|
||||
return (byte)((tens << 4) | ones); // 合并为BCD码
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数码管显示值
|
||||
/// </summary>
|
||||
/// <param name="maxTaskNum">最大合单数</param>
|
||||
/// <param name="list">各包车的数值</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public static byte[] GetLedBytes(int maxTaskNum, List<int> list)
|
||||
{
|
||||
var byteArr = new byte[4];
|
||||
if (list.Count < maxTaskNum)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
if (maxTaskNum == 1)
|
||||
{
|
||||
byteArr[1] = DecimalToBcd((byte)list[0]);
|
||||
}
|
||||
else if (maxTaskNum == 2)
|
||||
{
|
||||
byteArr[1] = DecimalToBcd((byte)list[0]);
|
||||
byteArr[0] = DecimalToBcd((byte)list[1]);
|
||||
}
|
||||
else if (maxTaskNum == 3)
|
||||
{
|
||||
string str1 = DecimalToBcd((byte)list[0]).ToString("X2");
|
||||
string str2 = DecimalToBcd((byte)list[1]).ToString("X2");
|
||||
string str3 = DecimalToBcd((byte)list[2]).ToString("X2");
|
||||
var appedStr = str1.Substring(str1.Length - 1) + str2.Substring(str2.Length - 1) + str3.Substring(str3.Length - 1) + "0";
|
||||
byte[] hexArr = HexStringToBytes(appedStr);
|
||||
byteArr[1] = hexArr[0];
|
||||
byteArr[0] = hexArr[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
string str1 = DecimalToBcd((byte)list[0]).ToString("X2");
|
||||
string str2 = DecimalToBcd((byte)list[1]).ToString("X2");
|
||||
string str3 = DecimalToBcd((byte)list[2]).ToString("X2");
|
||||
string str4 = DecimalToBcd((byte)list[3]).ToString("X2");
|
||||
var appedStr = str1.Substring(str1.Length - 1) + str2.Substring(str2.Length - 1) + str3.Substring(str3.Length - 1) + str4.Substring(str4.Length - 1);
|
||||
byte[] hexArr = HexStringToBytes(appedStr);
|
||||
byteArr[1] = hexArr[0];
|
||||
byteArr[0] = hexArr[1];
|
||||
}
|
||||
return byteArr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.0",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Common.AspNetCore.Extensions;
|
||||
using Common.AspNetCore.Helpers;
|
||||
using Common.Frame.Dtos.Trace;
|
||||
using Common.Frame.Services.Trace.Interfaces;
|
||||
using Common.NETCore.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FASS.Scheduler.Attributes
|
||||
{
|
||||
public class ActionLogIgnoreAttribute : ActionFilterAttribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class ActionLogAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly IUserActionService _userLogService;
|
||||
private readonly ILogger<ActionLogAttribute> _logger;
|
||||
|
||||
public ActionLogAttribute(
|
||||
IUserActionService userLogService,
|
||||
ILogger<ActionLogAttribute> logger)
|
||||
{
|
||||
_userLogService = userLogService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
if (IsIgnore(context))
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
var watch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
finally
|
||||
{
|
||||
watch.Stop();
|
||||
var userActionDto = new UserActionDto
|
||||
{
|
||||
UserId = IdentityHelper.ToUserIdentity(context.HttpContext.User).Id,
|
||||
Controller = context.RouteData.DataTokens["area"] is null ? $"{context.RouteData.Values["controller"]}" : $"{context.RouteData.DataTokens["area"]}/{context.RouteData.Values["controller"]}",
|
||||
Action = $"{context.RouteData.Values["action"]}",
|
||||
Watch = watch.Elapsed.ToString(),
|
||||
RequestUrl = context.HttpContext.Request.GetAbsoluteUri(),
|
||||
RequestToken = context.HttpContext.Request.Cookies["Authorization"],
|
||||
ResponseCode = context.HttpContext.Response.StatusCode.ToString(),
|
||||
UserAgent = context.HttpContext.Request.Headers["User-Agent"],
|
||||
IpAddress = context.HttpContext.GetUserIp()
|
||||
};
|
||||
try
|
||||
{
|
||||
await _userLogService.AddAsync(userActionDto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录用户操作日志失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsIgnore(FilterContext context)
|
||||
{
|
||||
if (context.Filters.OfType<ActionLogIgnoreAttribute>().Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return context.ActionDescriptor.FilterDescriptors.Select(f => f.Filter).OfType<TypeFilterAttribute>().Any(f => f.ImplementationType == typeof(ActionLogIgnoreAttribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Common.AspNetCore.Helpers;
|
||||
using Common.Frame.Services.Account.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace FASS.Scheduler.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class AuthorizeActionIgnoreAttribute : Attribute, IAuthorizationFilter
|
||||
{
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class AuthorizeActionAttribute : Attribute, IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly IPermissionService _permissionService;
|
||||
|
||||
public AuthorizeActionAttribute(
|
||||
IPermissionService permissionService)
|
||||
{
|
||||
_permissionService = permissionService;
|
||||
}
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (IsIgnore(context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var userIdentity = IdentityHelper.ToUserIdentity(context.HttpContext.User);
|
||||
if (userIdentity.IsSystem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var target = context.HttpContext.Request.Path.ToString();
|
||||
var isOk = await _permissionService.CheckTargetAsync(userIdentity.Id, target);
|
||||
if (isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
|
||||
private static bool IsIgnore(AuthorizationFilterContext context)
|
||||
{
|
||||
if (context.Filters.OfType<AuthorizeActionIgnoreAttribute>().Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return context.ActionDescriptor.FilterDescriptors.Select(f => f.Filter).OfType<TypeFilterAttribute>().Any(f => f.ImplementationType == typeof(AuthorizeActionIgnoreAttribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using Common.NETCore.Extensions;
|
||||
using Common.NETCore.Models;
|
||||
using FASS.Service.Dtos.Record;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Attributes
|
||||
{
|
||||
public class InterfaceLogAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly IDiaryService _diaryService;
|
||||
private readonly ILogger<InterfaceLogAttribute> _logger;
|
||||
public InterfaceLogAttribute(
|
||||
IDiaryService diaryService,
|
||||
ILogger<InterfaceLogAttribute> logger)
|
||||
{
|
||||
_diaryService = diaryService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
// 保存 Action 参数
|
||||
context.HttpContext.Items["ActionParameters"] = context.ActionArguments;
|
||||
}
|
||||
public override void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
var logDto = new DiaryDto()
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.InterfaceLog,
|
||||
Code = $"{context.RouteData.Values["action"]}"
|
||||
};
|
||||
if (context.RouteData?.Values["controller"]?.ToString() == "ProductionBiz")
|
||||
{
|
||||
logDto.Message = "MOM => PTL";
|
||||
}
|
||||
else if(context.RouteData?.Values["controller"]?.ToString() == "EsbBiz")
|
||||
{
|
||||
logDto.Message = "Esb => Ptl";
|
||||
}
|
||||
else if (context.RouteData?.Values["controller"]?.ToString() == "LogisticsExecutionBiz")
|
||||
{
|
||||
logDto.Message = "LES => PTL";
|
||||
}
|
||||
else { }
|
||||
// 读取保存的参数
|
||||
if (context.HttpContext.Items.TryGetValue("ActionParameters", out var value))
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
var parameters = value as IDictionary<string, object>;
|
||||
logDto.Data = parameters?.ToJson();
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Result is StatusCodeResult statusCodeResult)
|
||||
{
|
||||
var responseResult = new ResponseResult();
|
||||
responseResult.Code = statusCodeResult.StatusCode.ToString();
|
||||
if (statusCodeResult is OkResult)
|
||||
{
|
||||
responseResult.Success = true;
|
||||
responseResult.Data = Enum.GetName(typeof(HttpStatusCode), (HttpStatusCode)statusCodeResult.StatusCode);
|
||||
logDto.Extend = responseResult.ToJson();
|
||||
}
|
||||
else
|
||||
{
|
||||
responseResult.Success = false;
|
||||
responseResult.Message = Enum.GetName(typeof(HttpStatusCode), (HttpStatusCode)statusCodeResult.StatusCode);
|
||||
logDto.Remark = responseResult.ToJson();
|
||||
}
|
||||
}
|
||||
else if (context.Result is ObjectResult objectResult)
|
||||
{
|
||||
var responseResult = new ResponseResult();
|
||||
responseResult.Code = (objectResult?.StatusCode ?? 0).ToString();
|
||||
if (objectResult is OkObjectResult)
|
||||
{
|
||||
responseResult.Success = true;
|
||||
responseResult.Data = objectResult.Value;
|
||||
logDto.Extend = responseResult.ToJson();
|
||||
}
|
||||
else
|
||||
{
|
||||
responseResult.Success = false;
|
||||
responseResult.Message = objectResult?.Value?.ToString();
|
||||
logDto.Remark = responseResult.ToJson();
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
_diaryService.AddAsync(logDto).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Common.NETCore.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Attributes
|
||||
{
|
||||
public class ResultAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
if (context.Result is StatusCodeResult statusCodeResult)
|
||||
{
|
||||
var responseResult = new ResponseResult();
|
||||
responseResult.Code = statusCodeResult.StatusCode.ToString();
|
||||
if (statusCodeResult is OkResult)
|
||||
{
|
||||
responseResult.Success = true;
|
||||
responseResult.Data = Enum.GetName(typeof(HttpStatusCode), (HttpStatusCode)statusCodeResult.StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
responseResult.Success = false;
|
||||
responseResult.Message = Enum.GetName(typeof(HttpStatusCode), (HttpStatusCode)statusCodeResult.StatusCode);
|
||||
}
|
||||
context.Result = new OkObjectResult(responseResult);
|
||||
}
|
||||
else if (context.Result is ObjectResult objectResult)
|
||||
{
|
||||
var responseResult = new ResponseResult();
|
||||
responseResult.Code = (objectResult?.StatusCode ?? 0).ToString();
|
||||
if (objectResult is OkObjectResult)
|
||||
{
|
||||
responseResult.Success = true;
|
||||
responseResult.Data = objectResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
responseResult.Success = false;
|
||||
responseResult.Message = objectResult?.Value;
|
||||
}
|
||||
context.Result = new OkObjectResult(responseResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using FASS.Scheduler.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FASS.Scheduler.Controllers.Base
|
||||
{
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/v1/[controller]/[action]")]
|
||||
[TypeFilter(typeof(AuthorizeActionAttribute))]
|
||||
[TypeFilter(typeof(ActionLogAttribute))]
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using Common.NETCore.Extensions;
|
||||
using FASS.Scheduler.Attributes;
|
||||
using FASS.Scheduler.Controllers.Base;
|
||||
using FASS.Scheduler.Lite.Controllers.Models.Response;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Consts.Core;
|
||||
using FASS.Service.Dtos.Record;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using FASS.Service.Lite.Services.Interface.Interfaces;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[TypeFilter(typeof(AuthorizeActionIgnoreAttribute))]
|
||||
[TypeFilter(typeof(ActionLogIgnoreAttribute))]
|
||||
[Tags("AGV接口")]
|
||||
[EnableRateLimiting(AppConst.Rate.Name)]
|
||||
public class CarController : BaseController
|
||||
{
|
||||
private readonly ILogger<CarController> _logger;
|
||||
private readonly IAgvControlService _agvControlService;
|
||||
private readonly IShelfService _shelfService;
|
||||
private readonly ITaskService _taskService;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IDiaryService _diaryService;
|
||||
|
||||
public CarController(
|
||||
ILogger<CarController> logger,
|
||||
IAgvControlService agvControlService,
|
||||
IShelfService shelfService,
|
||||
ITaskService taskService,
|
||||
IDataService dataService,
|
||||
IDiaryService diaryService)
|
||||
{
|
||||
_logger = logger;
|
||||
_agvControlService = agvControlService;
|
||||
_shelfService = shelfService;
|
||||
_taskService = taskService;
|
||||
_dataService = dataService;
|
||||
_diaryService = diaryService;
|
||||
}
|
||||
|
||||
[Tags("AGV控制配置")]
|
||||
[HttpPost]
|
||||
[DisableRateLimiting]
|
||||
public async Task<IActionResult> AgvConfig(Models.Request.AgvConfigParam request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug($"AGV控制配置接口传入参数[{request.ToJson()}]");
|
||||
if (request.ConfigKey.Equals("AgvControl"))
|
||||
{
|
||||
bool isAgvControl = false;
|
||||
var configServiceDto = await _dataService.GetConfigToDtoAsync<ConfigServiceDto>(CacheKey.Setting.ConfigService);
|
||||
if (!string.IsNullOrEmpty(configServiceDto?.AgvControl))
|
||||
{
|
||||
var configArr = configServiceDto.AgvControl.Split(',');
|
||||
if (configArr.Contains(request.AreaCode)) { isAgvControl = true; }
|
||||
}
|
||||
var resonse = new AgvConfigInfo { ConfigKey = "AgvControl", ConfigValue = isAgvControl, AreaCode = request.AreaCode };
|
||||
_logger.LogDebug($"AGV控制配置接口返回结果:{resonse.ToJson()}");
|
||||
return Ok(resonse);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"传入参数[{request.ConfigKey}] 不正确!");
|
||||
return BadRequest($"传入参数[{request.ConfigKey}] 不正确!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"AGV控制配置接口异常,传入参数request:{request.ToJson()} ex=>{ex}");
|
||||
return BadRequest($"AGV控制配置接口异常,传入参数request:{request.ToJson()} ex=>{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
[Tags("AGV放行")]
|
||||
[HttpPost]
|
||||
[DisableRateLimiting]
|
||||
public async Task<IActionResult> AgvRelease(Models.Request.AgvReleaseParam request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug($"AGV放行接口传入参数[{request.ToJson()}]");
|
||||
//todo 考虑加入agvCode是否存在的判定
|
||||
var shelfDtos = await _shelfService.ToListAsync(e => request.ShelfCodes.Contains(e.Code));
|
||||
if (shelfDtos is null || shelfDtos.Count == 0)
|
||||
{
|
||||
_logger.LogError($"AGV放行失败,货架编码[{string.Join(",", request.ShelfCodes)}] 不存在");
|
||||
return BadRequest($"AGV放行失败,货架编码[{string.Join(",", request.ShelfCodes)}] 不存在");
|
||||
}
|
||||
if (shelfDtos.Select(e => e.SegmentCode).Distinct().Count() > 1)
|
||||
{
|
||||
_logger.LogError($"AGV放行失败,货架编码[{string.Join(",", request.ShelfCodes)}] 不在一个分段");
|
||||
return BadRequest($"AGV放行失败,货架编码[{string.Join(",", request.ShelfCodes)}] 不在一个分段");
|
||||
}
|
||||
//判断区域联动开关是否关闭
|
||||
bool isAgvControl = false;
|
||||
var configServiceDto = await _dataService.GetConfigToDtoAsync<ConfigServiceDto>(CacheKey.Setting.ConfigService);
|
||||
if (!string.IsNullOrEmpty(configServiceDto?.AgvControl))
|
||||
{
|
||||
var configArr = configServiceDto.AgvControl.Split(',');
|
||||
if (configArr.Contains(shelfDtos[0].AreaCode)) { isAgvControl = true; }
|
||||
}
|
||||
if (!isAgvControl)
|
||||
{
|
||||
var stateResponse = new AgvReleaseResult
|
||||
{
|
||||
TaskNo = request.TaskNo,
|
||||
ShelfCodes = request.ShelfCodes,
|
||||
State = AgvReleaseConst.State.Released,
|
||||
Message = "AGV区域控制开关关闭,放行AGV"
|
||||
};
|
||||
_logger.LogDebug($"AGV区域控制开关关闭,AGV放行。返回状态:{stateResponse.ToJson()}");
|
||||
return Ok(stateResponse);
|
||||
}
|
||||
if (string.IsNullOrEmpty(request.TaskNo))
|
||||
{
|
||||
#region 根据货架号获取任务号以及任务状态
|
||||
//根据上传的货架编号对应的区域,获取区域内最早未绑定agv的任务编号(agv编号填充到扩展字段)
|
||||
var taskDto = await _taskService.Set().AsNoTracking().Where(e => e.AreaCode == shelfDtos[0].AreaCode && e.State == OrderConst.State.Picking && string.IsNullOrWhiteSpace(e.Extend)).OrderBy(e => e.CreateAt).FirstOrDefaultAsync();
|
||||
if (taskDto is null)
|
||||
{
|
||||
//不存在当前正在分拣且没有绑定AGV的合单任务,不放行
|
||||
var stateResponse = new AgvReleaseResult
|
||||
{
|
||||
TaskNo = "",
|
||||
ShelfCodes = request.ShelfCodes,
|
||||
State = AgvReleaseConst.State.Unreleased,
|
||||
Message = "不存在任务,获取任务号失败!"
|
||||
};
|
||||
_logger.LogDebug($"不存在任务,获取任务号失败! 返回状态:{stateResponse.ToJson()}");
|
||||
return Ok(stateResponse);
|
||||
}
|
||||
//获取新任务编号、更新AgvCode到分拣任务
|
||||
await _taskService.Repository.ExecuteUpdateAsync(e => e.Id == taskDto.Id, s => s.SetProperty(b => b.Extend, request.AgvCode));//存在任务号
|
||||
var releaseDtos = await _agvControlService.ToListAsync(e => e.BatchNo == taskDto.BatchNo && request.ShelfCodes.Contains(e.ShelfCode));
|
||||
if (releaseDtos is not null && releaseDtos.Count > 0)
|
||||
{
|
||||
var stateResponse = new AgvReleaseResult
|
||||
{
|
||||
ShelfCodes = request.ShelfCodes,
|
||||
TaskNo = releaseDtos[0].BatchNo,
|
||||
State = releaseDtos.Any(e => e.State == AgvReleaseConst.State.Unreleased) ? AgvReleaseConst.State.Unreleased : AgvReleaseConst.State.Released,
|
||||
Message = "AGV获取任务号成功"
|
||||
};
|
||||
_logger.LogDebug($"AGV获取任务号成功,任务号[{stateResponse.TaskNo}] 货架编码[{string.Join(",", request.ShelfCodes)}] 状态[{stateResponse.State}]");
|
||||
if (stateResponse.State == AgvReleaseConst.State.Released)
|
||||
{
|
||||
//更新获取任务号关联的放行记录agv编码和放行时间
|
||||
await _agvControlService.Repository.ExecuteUpdateAsync(e => e.TaskId == taskDto.Id && request.ShelfCodes.Contains(e.ShelfCode), s => s.SetProperty(b => b.AgvCode, request.AgvCode).SetProperty(c => c.ReleaseTime, DateTime.Now));
|
||||
await _diaryService.AddAsync(new DiaryDto
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.AgvReleaseLog,
|
||||
Code = $"{releaseDtos[0].BatchNo}",
|
||||
Data = $"小车[{request.AgvCode}]获取任务号[{releaseDtos[0].BatchNo}],货架编号:[{string.Join(",", request.ShelfCodes)}],放行状态:[{stateResponse.State}]",
|
||||
Message = $"{request.AgvCode}",
|
||||
Remark = stateResponse.Message
|
||||
});
|
||||
}
|
||||
else {
|
||||
//更新获取任务号关联的放行记录agv编码
|
||||
await _agvControlService.Repository.ExecuteUpdateAsync(e => e.TaskId == taskDto.Id && request.ShelfCodes.Contains(e.ShelfCode), s => s.SetProperty(b => b.AgvCode, request.AgvCode));
|
||||
}
|
||||
return Ok(stateResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
//没有关联的货架任务,直接放行
|
||||
var stateResponse = new AgvReleaseResult
|
||||
{
|
||||
TaskNo = taskDto.BatchNo,
|
||||
ShelfCodes = request.ShelfCodes,
|
||||
State = AgvReleaseConst.State.Released,
|
||||
Message = "货架无关联的任务,放行成功"
|
||||
};
|
||||
_logger.LogDebug($"货架无关联的任务,放行成功! 返回状态:{stateResponse.ToJson()}");
|
||||
await _diaryService.AddAsync(new DiaryDto
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.AgvReleaseLog,
|
||||
Code = $"{stateResponse.TaskNo}",
|
||||
Message = $"{request.AgvCode}",
|
||||
Data = $"小车[{request.AgvCode}]获取任务号[{stateResponse.TaskNo}],货架编号:[{string.Join(",", request.ShelfCodes)}],放行状态:[{stateResponse.State}]",
|
||||
Remark = stateResponse.Message
|
||||
});
|
||||
return Ok(stateResponse);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
else
|
||||
{
|
||||
#region 根据货架号/任务号获取任务状态
|
||||
var releaseDtos = await _agvControlService.ToListAsync(e => e.BatchNo == request.TaskNo && request.ShelfCodes.Contains(e.ShelfCode));
|
||||
if (releaseDtos is not null && releaseDtos.Count > 0)
|
||||
{
|
||||
var stateResponse = new AgvReleaseResult
|
||||
{
|
||||
TaskNo = request.TaskNo,
|
||||
ShelfCodes = request.ShelfCodes
|
||||
};
|
||||
if (releaseDtos.Any(e => e.State == AgvReleaseConst.State.Unreleased))
|
||||
{
|
||||
stateResponse.State = AgvReleaseConst.State.Unreleased;
|
||||
stateResponse.Message = "当前任务分拣中,放行失败";
|
||||
_logger.LogDebug($"车辆放行失败,任务号[{request.TaskNo}] 货架编码[{string.Join(",", request.ShelfCodes)}] 状态[{stateResponse.State}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
stateResponse.State = AgvReleaseConst.State.Released;
|
||||
stateResponse.Message = "当前任务分拣完成,放行成功";
|
||||
_logger.LogDebug($"车辆放行成功,任务号[{request.TaskNo}] 货架编码[{string.Join(",", request.ShelfCodes)}] 状态[{stateResponse.State}]");
|
||||
//更新获取任务号关联的放行记录agv编码
|
||||
await _agvControlService.Repository.ExecuteUpdateAsync(e => releaseDtos.Select(e => e.Id).Contains(e.Id), s => s.SetProperty(b => b.AgvCode, request.AgvCode).SetProperty(c => c.ReleaseTime, DateTime.Now));
|
||||
await _diaryService.AddAsync(new DiaryDto
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.AgvReleaseLog,
|
||||
Code = $"{stateResponse.TaskNo}",
|
||||
Message = $"{request.AgvCode}",
|
||||
Data = $"小车[{request.AgvCode}],任务号[{stateResponse.TaskNo}],货架编号:[{string.Join(",", request.ShelfCodes)}],放行状态:[{stateResponse.State}]",
|
||||
Remark = stateResponse.Message
|
||||
});
|
||||
}
|
||||
return Ok(stateResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateResponse = new AgvReleaseResult
|
||||
{
|
||||
TaskNo = request.TaskNo,
|
||||
ShelfCodes = request.ShelfCodes,
|
||||
State = AgvReleaseConst.State.Released,
|
||||
Message = "不存在货架号对应的任务记录,放行AGV"
|
||||
};
|
||||
_logger.LogDebug($"不存在货架号对应的任务记录,AGV放行。返回状态:{stateResponse.ToJson()}");
|
||||
await _diaryService.AddAsync(new DiaryDto
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.AgvReleaseLog,
|
||||
Code = $"{stateResponse.TaskNo}",
|
||||
Data = $"小车[{request.AgvCode}],任务号[{stateResponse.TaskNo}],货架编号:[{string.Join(",", request.ShelfCodes)}],放行状态:[{stateResponse.State}]",
|
||||
Message = stateResponse.Message
|
||||
});
|
||||
return Ok(stateResponse);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"AGV放行接口异常,传入参数request:{request.ToJson()} ex=>{ex}");
|
||||
return BadRequest($"AGV放行异常 ex=>{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
[Tags("AGV状态回传")]
|
||||
[HttpPost]
|
||||
[DisableRateLimiting]
|
||||
public async Task<IActionResult> AgvState(Models.Request.AgvStateParam request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug($"AGV状态回传接口传入参数[{request.ToJson()}]");
|
||||
if (await _agvControlService.AnyAsync(e => e.BatchNo == request.TaskNo && request.ShelfCodes.Contains(e.ShelfCode)))
|
||||
{
|
||||
await _agvControlService.Repository.ExecuteUpdateAsync(e => e.BatchNo == request.TaskNo && request.ShelfCodes.Contains(e.ShelfCode), s => s.SetProperty(b => b.IsCallBack, true).SetProperty(b => b.CallBackTime, request.CallBackTime));
|
||||
_logger.LogDebug($"AGV状态回传接口:任务编号[{request.TaskNo}],货架编号[{string.Join(",", request.ShelfCodes)}] 接收处理成功!");
|
||||
await _diaryService.AddAsync(new DiaryDto
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.AgvReleaseLog,
|
||||
Code = $"{request.TaskNo}",
|
||||
Data = $"任务号[{request.TaskNo}],货架编号:[{string.Join(",", request.ShelfCodes)}],回传时间:[{request.CallBackTime}]"
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"AGV状态回传接口入参:任务编号[{request.TaskNo}],货架编号[{string.Join(",", request.ShelfCodes)}] 没有匹配到任务");
|
||||
return BadRequest($"任务编号[{request.TaskNo}],货架编号[{string.Join(",", request.ShelfCodes)}] 没有匹配到任务");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"AGV状态回传接口异常,传入参数request:{request.ToJson()} ex=>{ex}");
|
||||
return BadRequest($"AGV状态回传接口异常 ex=>{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using Common.Frame.Services.Frame.Interfaces;
|
||||
using Common.NETCore.Extensions;
|
||||
using FASS.Scheduler.Attributes;
|
||||
using FASS.Scheduler.Controllers.Base;
|
||||
using FASS.Scheduler.Lite.Attributes;
|
||||
using FASS.Service.Dtos.Record;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Dtos.Interface;
|
||||
using FASS.Service.Lite.Services.Interface.Interfaces;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using IInterfacePassPointService = FASS.Service.Lite.Services.Interface.Interfaces.IPassPointService;
|
||||
using InterFacePassPointDto = FASS.Service.Lite.Dtos.Interface.PassPointDto;
|
||||
using IPassPointService = FASS.Service.Lite.Services.Data.Interfaces.IPassPointService;
|
||||
using PassPointDto = FASS.Service.Lite.Dtos.Data.PassPointDto;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[TypeFilter(typeof(AuthorizeActionIgnoreAttribute))]
|
||||
[TypeFilter(typeof(ActionLogIgnoreAttribute))]
|
||||
[TypeFilter(typeof(InterfaceLogAttribute))]
|
||||
[Tags("Esb接口(赛力斯二厂)")]
|
||||
public class EsbBizController : BaseController
|
||||
{
|
||||
private readonly ILogger<EsbBizController> _logger;
|
||||
private readonly IPassPointService _passPointService;
|
||||
private readonly IInterfacePassPointService _interfacePassPointService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IMaterialStorageService _materialStorageService;
|
||||
private readonly IDictItemService _dictItemService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
|
||||
|
||||
public EsbBizController(
|
||||
ILogger<EsbBizController> logger,
|
||||
IPassPointService passPointService,
|
||||
IInterfacePassPointService interfacePassPointService,
|
||||
IOrderService orderService,
|
||||
IMaterialStorageService materialStorageService,
|
||||
IDictItemService dictValueService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_logger = logger;
|
||||
_passPointService = passPointService;
|
||||
_interfacePassPointService = interfacePassPointService;
|
||||
_orderService = orderService;
|
||||
_materialStorageService = materialStorageService;
|
||||
_dictItemService = dictValueService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
[Tags("赛力斯二厂-过点数据")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> TrackPoint(Models.Request.Seres2.OverPoint request)
|
||||
{
|
||||
var interfaceDto = new InterFacePassPointDto
|
||||
{
|
||||
SequenceNo = request.SEQUENCE_NO,
|
||||
Vin = request.VIN,
|
||||
PrOrderNo = request.ORDER_NUM.TrimStart('0'),//处理掉车型-订单号前面的0
|
||||
PassPointTime = request.CREATION_TIME,
|
||||
StationCode = request.STATION,
|
||||
LineCode = request.Assembly_LINE,
|
||||
FactoryCode = request.FACTORY
|
||||
};
|
||||
|
||||
//过点站点的过滤
|
||||
if (request.STATION?.ToUpper() != "PBS02")
|
||||
{
|
||||
interfaceDto.Remark = $"过点数据错误,非PBS02站点数据,采集点 [{request.STATION}]";
|
||||
_logger.LogError($"过点数据错误,采集点[{request.STATION}]错误, 数据:{request.ToJson()}");
|
||||
await _interfacePassPointService.AddAsync(interfaceDto);
|
||||
return BadRequest($"过点数据错误,车辆Vin[{request.VIN}],采集点[{request.STATION}]");
|
||||
}
|
||||
|
||||
//判定车辆订单&车辆VIN是否重复
|
||||
if (await _passPointService.AnyAsync(e => e.PrOrderNo == request.ORDER_NUM.TrimStart('0') && e.Vin == request.VIN))
|
||||
{
|
||||
interfaceDto.Remark = $"过点数据错误,车型订单号[{request.ORDER_NUM}] 车辆VIN[{request.VIN}]重复";
|
||||
_logger.LogError($"过点数据错误,车型订单号[{request.ORDER_NUM}] 车辆VIN[{request.VIN}]重复, 数据:{request.ToJson()}");
|
||||
await _interfacePassPointService.AddAsync(interfaceDto);
|
||||
return BadRequest($"过点数据错误,车型订单号 [{request.ORDER_NUM}] 车辆VIN[{request.VIN}]重复");
|
||||
}
|
||||
|
||||
var dto = new PassPointDto
|
||||
{
|
||||
SequenceNo = request.SEQUENCE_NO,
|
||||
Vin = request.VIN,
|
||||
PrOrderNo = request.ORDER_NUM.TrimStart('0'),//处理掉车型-订单号前面的0
|
||||
PassPointTime = request.CREATION_TIME,
|
||||
PostTime = DateTime.Now,
|
||||
State = PassPointConst.State.Pending,
|
||||
StationCode = request.STATION,
|
||||
LineCode = request.Assembly_LINE,
|
||||
FactoryCode = request.FACTORY
|
||||
};
|
||||
await _passPointService.AddAsync(dto);//写入过点业务数据表
|
||||
await _interfacePassPointService.AddAsync(interfaceDto);//写入接口表
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Tags("赛力斯二厂-SPS配载单")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> BomInfos(List<Models.Request.Seres2.BomInfo> request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
_logger.LogError($"SPS配载单参数错误! 传入参数为空.");
|
||||
return BadRequest($"SPS配载单参数错误! 传入参数为空.");
|
||||
}
|
||||
if (request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS配载单传入参数错误, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS配载单传入参数错误, 数据:{request.ToJson()}]");
|
||||
}
|
||||
var dtos = new List<OrderDto>();
|
||||
List<AlarmDto> alarms = new List<AlarmDto>();
|
||||
var dictItemDtos = await _dictItemService.ToListAsync(e => e.IsEnable && e.Dict.Code == "PickingLineArea");
|
||||
foreach (var item in request)
|
||||
{
|
||||
var dictItemDto = dictItemDtos.Where(e => e.Code == item.PICKING_LINE_CODE).FirstOrDefault();
|
||||
if (dictItemDto is null)
|
||||
{
|
||||
_logger.LogError($"SPS配载单参数 [配载区代码]错误, 数据:{item.ToJson()}");
|
||||
var model = new AlarmDto
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.BomInfosError,
|
||||
Code = item.VIN,
|
||||
Message = $"车辆[{item.VIN}] 区域编码[{item.PICKING_LINE_CODE}]不存在"
|
||||
};
|
||||
if (!alarms.Any(e => e.Code == model.Code && e.Message == model.Message))
|
||||
{
|
||||
alarms.Add(model);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var dto = new OrderDto
|
||||
{
|
||||
PullNo = item.RUNSHEET_NO,
|
||||
PrOrderNo = item.ORDER_NO,
|
||||
Vin = item.VIN,
|
||||
SequenceNo = item.RUNNING_NUMBER!,
|
||||
CarDetail = item.CAR_DETAIL,
|
||||
MaterialCode = item.PART_NO,
|
||||
MaterialName = item.PART_NAME,
|
||||
StorageCode = item.ADDRESS_DESCRIBE!,
|
||||
Quantity = item.REQUIRED_PACKAGE_QTY,
|
||||
StationCode = item.LOCATION,
|
||||
FactoryCode = item.FACTORY,
|
||||
AreaCode = dictItemDto.Value.ToString(),
|
||||
AreaName = dictItemDto.Name!.ToString()
|
||||
};
|
||||
dtos.Add(dto);
|
||||
}
|
||||
|
||||
if (alarms.Count > 0)
|
||||
{
|
||||
await _alarmService.AddAsync(alarms);
|
||||
}
|
||||
if (dtos.Count > 0)
|
||||
{
|
||||
await _orderService.AddAsync(dtos);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Tags("赛力斯二厂-零件基础信息")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PartInfos(List<Models.Request.Seres2.PartInfo> request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
_logger.LogError($"SPS零件基础信息参数错误! 传入参数为空.");
|
||||
return BadRequest($"SPS零件基础信息参数错误! 传入参数为空.");
|
||||
}
|
||||
if (request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS零件基础信息参数错误, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS零件基础信息参数错误, 数据:{request.ToJson()}]");
|
||||
}
|
||||
var dtos = new List<MaterialStorageDto>();
|
||||
foreach (var item in request)
|
||||
{
|
||||
var dto = new MaterialStorageDto
|
||||
{
|
||||
FactoryCode = item.FACTORY,
|
||||
MaterialCode = item.PART_NO,
|
||||
MaterialName = item.PART_NAME,
|
||||
StorageCode = item.ADDRESS_DESCRIBE,
|
||||
StationCode = item.LOCATION,
|
||||
LmsUpdateAt = DateTime.Now
|
||||
};
|
||||
if (dtos.Any(e => e.MaterialCode == dto.MaterialCode && e.StationCode == dto.StationCode))
|
||||
{
|
||||
_logger.LogError($"SPS零件信息重复, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS零件信息重复, 数据:{request.ToJson()}]");
|
||||
}
|
||||
dtos.Add(dto);
|
||||
}
|
||||
await _materialStorageService.AddAsync(dtos);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
using Common.Frame.Services.Frame.Interfaces;
|
||||
using Common.NETCore.Extensions;
|
||||
using FASS.Scheduler.Attributes;
|
||||
using FASS.Scheduler.Controllers.Base;
|
||||
using FASS.Scheduler.Lite.Attributes;
|
||||
using FASS.Service.Dtos.Record;
|
||||
using FASS.Service.Lite.Consts.Base;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Dtos.Interface;
|
||||
using FASS.Service.Lite.Services.Interface.Interfaces;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[TypeFilter(typeof(AuthorizeActionIgnoreAttribute))]
|
||||
[TypeFilter(typeof(ActionLogIgnoreAttribute))]
|
||||
[TypeFilter(typeof(InterfaceLogAttribute))]
|
||||
[Tags("物流执行接口")]
|
||||
public class LogisticsExecutionBizController : BaseController
|
||||
{
|
||||
private readonly ILogger<LogisticsExecutionBizController> _logger;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IMaterialStorageService _materialStorageService;
|
||||
private readonly IMaterialService _materialService;
|
||||
private readonly IMaterialInventoryService _materialInventoryService;
|
||||
private readonly IDictItemService _dictItemService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
|
||||
|
||||
public LogisticsExecutionBizController(
|
||||
ILogger<LogisticsExecutionBizController> logger,
|
||||
IOrderService orderService,
|
||||
IMaterialStorageService materialStorageService,
|
||||
IMaterialService materialService,
|
||||
IMaterialInventoryService materialInventoryService,
|
||||
IDictItemService dictValueService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_logger = logger;
|
||||
_orderService = orderService;
|
||||
_materialStorageService = materialStorageService;
|
||||
_materialService = materialService;
|
||||
_materialInventoryService = materialInventoryService;
|
||||
_dictItemService = dictValueService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
[Tags("SPS配载单")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> BomInfos(List<Models.Request.BomItem> request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
_logger.LogError($"SPS配载单参数错误! 传入参数为空.");
|
||||
return BadRequest($"SPS配载单参数错误! 传入参数为空.");
|
||||
}
|
||||
if (request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS配载单传入参数错误, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS配载单传入参数错误, 数据:{request.ToJson()}]");
|
||||
}
|
||||
var dtos = new List<OrderDto>();
|
||||
List<AlarmDto> alarms = new List<AlarmDto>();
|
||||
var dictItemDtos = await _dictItemService.ToListAsync(e => e.IsEnable && e.Dict.Code == "PickingLineArea");
|
||||
foreach (var item in request)
|
||||
{
|
||||
var dictItemDto = dictItemDtos.Where(e => e.Code == item.AreaCode).FirstOrDefault();
|
||||
if (dictItemDto is null)
|
||||
{
|
||||
_logger.LogError($"SPS配载单参数 [配载区代码]错误, 数据:{item.ToJson()}");
|
||||
var model = new AlarmDto
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.BomInfosError,
|
||||
Code = item.Vin,
|
||||
Message = $"车辆[{item.Vin}] 区域编码[{item.AreaCode}]不存在"
|
||||
};
|
||||
if (!alarms.Any(e => e.Code == model.Code && e.Message == model.Message))
|
||||
{
|
||||
alarms.Add(model);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var dto = new OrderDto
|
||||
{
|
||||
PullNo = item.PullNo,
|
||||
PrOrderNo = item.PrOrderNo,
|
||||
Vin = item.Vin,
|
||||
SequenceNo = item.SequenceNo,
|
||||
PassPointTime = item.PassPointTime,
|
||||
CarDetail = item.CarDetail,
|
||||
MaterialCode = item.MaterialCode,
|
||||
MaterialName = item.MaterialName,
|
||||
SmplifiedCode = item.SmplifiedCode,
|
||||
StorageCode = item.StorageCode,
|
||||
Unit = item.Unit,
|
||||
Quantity = item.Quantity,
|
||||
StationCode = item.StationCode,
|
||||
FactoryCode = item.FactoryCode,
|
||||
FactoryName = item.FactoryName,
|
||||
WorkshopCode = item.WorkshopCode,
|
||||
WorkshopName = item.WorkshopName,
|
||||
AreaCode = dictItemDto.Value.ToString(),
|
||||
AreaName = dictItemDto.Name!.ToString()
|
||||
};
|
||||
dtos.Add(dto);
|
||||
}
|
||||
if (alarms.Count > 0)
|
||||
{
|
||||
await _alarmService.AddAsync(alarms);
|
||||
}
|
||||
if (dtos.Count > 0)
|
||||
{
|
||||
await _orderService.AddAsync(dtos);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Tags("SPS物料库位关系")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> MaterialStorageRels(List<Models.Request.MaterialStorageRel> request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
_logger.LogError($"SPS物料库位关系参数错误! 传入参数为空.");
|
||||
return BadRequest($"SPS物料库位关系参数错误! 传入参数为空.");
|
||||
}
|
||||
if (request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS物料库位关系参数错误, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS物料库位关系参数错误, 数据:{request.ToJson()}]");
|
||||
}
|
||||
var dtos = new List<MaterialStorageDto>();
|
||||
foreach (var item in request)
|
||||
{
|
||||
var dto = new MaterialStorageDto
|
||||
{
|
||||
MaterialCode = item.MaterialCode,
|
||||
MaterialName = item.MaterialName,
|
||||
StorageCode = item.StorageCode,
|
||||
FactoryCode = item.FactoryCode,
|
||||
FactoryName = item.FactoryName,
|
||||
WorkshopCode = item.WorkshopCode,
|
||||
WorkshopName = item.WorkshopName,
|
||||
AreaCode = item.AreaCode,
|
||||
LmsUpdateAt = item.LmsUpdateAt
|
||||
};
|
||||
if (dtos.Any(e => e.MaterialCode == dto.MaterialCode && e.StationCode == dto.StationCode))
|
||||
{
|
||||
_logger.LogError($"SPS零件库位关系重复, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS零件库位关系重复, 数据:{request.ToJson()}]");
|
||||
}
|
||||
dtos.Add(dto);
|
||||
}
|
||||
await _materialStorageService.AddAsync(dtos);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Tags("SPS物料基础信息")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> MaterialInfos(List<Models.Request.MaterialInfo> request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
_logger.LogError($"SPS零件基础信息参数错误! 传入参数为空.");
|
||||
return BadRequest($"SPS零件基础信息参数错误! 传入参数为空.");
|
||||
}
|
||||
if (request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS零件基础信息参数错误, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS零件基础信息参数错误, 数据:{request.ToJson()}]");
|
||||
}
|
||||
var dtos = new List<MaterialDto>();
|
||||
foreach (var item in request)
|
||||
{
|
||||
var dto = new MaterialDto
|
||||
{
|
||||
Code = item.Code,
|
||||
Name = item.Name,
|
||||
EnName = item.EnName,
|
||||
Type = item.Type ?? MaterialConst.Type.Default,
|
||||
FactoryCode = item.FactoryCode,
|
||||
Spec = item.Spec,
|
||||
Unit = item.Unit,
|
||||
IsDelete = item.IsDelete//标记接口物料数据删除状态
|
||||
};
|
||||
if (dtos.Any(e => e.Code == dto.Code))
|
||||
{
|
||||
_logger.LogError($"SPS零件信息重复, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS零件信息重复, 数据:{request.ToJson()}]");
|
||||
}
|
||||
dtos.Add(dto);
|
||||
}
|
||||
await _materialService.AddAsync(dtos);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Tags("SPS库位物料库存信息")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> MaterialInventorys(List<Models.Request.MaterialInventoryInfo> request)
|
||||
{
|
||||
if (request is null || request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS零件库存参数错误! 传入参数为空.");
|
||||
return BadRequest($"SPS零件库存参数错误! 传入参数为空.");
|
||||
}
|
||||
if (request.Count == 0)
|
||||
{
|
||||
_logger.LogError($"SPS零件库存参数错误, 数据:{request.ToJson()}");
|
||||
return BadRequest($"SPS零件库存参数错误, 数据:{request.ToJson()}]");
|
||||
}
|
||||
var dtos = new List<MaterialInventoryDto>();
|
||||
foreach (var item in request)
|
||||
{
|
||||
var dto = new MaterialInventoryDto
|
||||
{
|
||||
FactoryCode = item.FactoryCode,
|
||||
MaterialCode = item.MaterialCode,
|
||||
MaterialName = item.MaterialName,
|
||||
StorageCode = item.StorageCode,
|
||||
Type = item.Type,
|
||||
Quantity = item.Quantity,
|
||||
AreaCode = item.AreaCode
|
||||
};
|
||||
dtos.Add(dto);
|
||||
}
|
||||
await _materialInventoryService.AddAsync(dtos);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class AgvConfigParam
|
||||
{
|
||||
public required string ConfigKey { get; set; }//固定参数"AgvControl"
|
||||
public required string AreaCode { get; set; }//区域编码
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class AgvReleaseParam
|
||||
{
|
||||
public required string AgvCode { get; set; }//agv编号
|
||||
public string? TaskNo { get; set; }//任务号
|
||||
public required string[] ShelfCodes { get; set; }//货架编码数组
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class AgvStateParam
|
||||
{
|
||||
public required string TaskNo { get; set; }//任务号(对应合单批次号)
|
||||
public required string[] ShelfCodes { get; set; }//货架编码数组
|
||||
public DateTime CallBackTime { get; set; } //回调时间
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class BomItem
|
||||
{
|
||||
public string? FactoryCode { get; set; }//工厂编码
|
||||
public string? FactoryName { get; set; }//工厂名称
|
||||
public string? WorkshopCode { get; set; }//车间编码
|
||||
public string? WorkshopName { get; set; }//车间名称
|
||||
public required string AreaCode { get; set; }//配载区编码
|
||||
public string? AreaName { get; set; }//配载区名称
|
||||
public required string PullNo { get; set; }//拉动单号
|
||||
public DateTime? PassPointTime { get; set; }//过点时间
|
||||
public required string SequenceNo { get; set; }//过点流水号
|
||||
public required string PrOrderNo { get; set; }//生产订单号
|
||||
public required string Vin { get; set; }//车辆VIN
|
||||
public required string CarDetail { get; set; } //车型配置
|
||||
public required string MaterialCode { get; set; }//物料编码
|
||||
public string? MaterialName { get; set; }//物料名称
|
||||
public string? SmplifiedCode { get; set; }//物料简码
|
||||
public required string StorageCode { get; set; }//库位编码
|
||||
public string? Unit { get; set; }//基本单位
|
||||
public int Quantity { get; set; }//需求数量
|
||||
public string? StationCode { get; set; }//线边工位
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class MaterialInfo
|
||||
{
|
||||
public required string Code { get; set; }//物料编码
|
||||
public string? Name { get; set; }//物料名称
|
||||
public string? EnName { get; set; }//物料英文名
|
||||
public string? Type { get; set; }//物料类型
|
||||
public string? FactoryCode { get; set; }//工厂
|
||||
public string? Spec { get; set; }//规格
|
||||
public string? Unit { get; set; }//单位
|
||||
public bool IsDelete { get; set; } = false;//删除标记
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using FASS.Service.Lite.Consts.Interface;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class MaterialInventoryInfo
|
||||
{
|
||||
public string? FactoryCode { get; set; }
|
||||
public required string MaterialCode { get; set; }
|
||||
public string? MaterialName { get; set; }
|
||||
public required string StorageCode { get; set; }
|
||||
public required string Type { get; set; } = MaterialInventoryConst.Type.Increment;// 默认增量
|
||||
public int Quantity { get; set; }
|
||||
public string? AreaCode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class MaterialStorageRel
|
||||
{
|
||||
public required string MaterialCode { get; set; }
|
||||
public required string MaterialName { get; set; }
|
||||
public required string StorageCode { get; set; }
|
||||
public string? FactoryCode { get; set; }
|
||||
public string? FactoryName { get; set; }
|
||||
public string? WorkshopCode { get; set; }
|
||||
public string? WorkshopName { get; set; }
|
||||
public string? AreaCode { get; set; }
|
||||
public DateTime? LmsUpdateAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class PassPoint
|
||||
{
|
||||
public required string SequenceNo { get; set; }//过点流水号
|
||||
public required string Vin { get; set; }//车辆VIN
|
||||
public required string PrOrderNo { get; set; }//生产订单号
|
||||
public required DateTime PassPointTime { get; set; }//过点时间
|
||||
public DateTime? PostTime { get; set; }//发送时间
|
||||
public string? StationCode { get; set; }//站点代码
|
||||
public string? StationDescription { get; set; }//站点描述
|
||||
public string? Platform { get; set; }//车型
|
||||
public string? LineCode { get; set; }//产线代码
|
||||
public string? LineDescription { get; set; }//产线描述
|
||||
public string? FactoryCode { get; set; }//工厂编码
|
||||
public string? WorkshopCode { get; set; }//车间编码
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request
|
||||
{
|
||||
public class RecvData
|
||||
{
|
||||
public byte[] bytes = [];
|
||||
public EndPoint remote = null!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request.Seres2
|
||||
{
|
||||
public class BomInfo
|
||||
{
|
||||
public string? FACTORY { get; set; }//工厂
|
||||
public string? ASSEMBLY_LINE { get; set; } //流水线
|
||||
public string? PICKING_LINE_CODE { get; set; } //配载区代码
|
||||
public required string RUNSHEET_NO { get; set; }//拉动单号
|
||||
public int RUNSHEET_SN { get; set; }//拉动单号流水号
|
||||
public int RUNSHEET_DETAIL_ID { get; set; }//拉动单明细序号
|
||||
public string? RUNNING_NUMBER { get; set; } //过点流水号
|
||||
public required string VIN { get; set; }//车辆VIN
|
||||
public required string ORDER_NO { get; set; }//车辆订单号
|
||||
public required string CAR_DETAIL { get; set; }//车型配置
|
||||
public required string PART_NO { get; set; }//物料编号
|
||||
public required string PART_NAME { get; set; }//物料名称
|
||||
public string? ADDRESS_DESCRIBE { get; set; } //库位编号
|
||||
public int REQUIRED_PACKAGE_QTY { get; set; } //物料数量
|
||||
public string? LOCATION { get; set; }//线边工位
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request.Seres2
|
||||
{
|
||||
public class OverPoint
|
||||
{
|
||||
public string? FACTORY { get; set; }//工厂代码
|
||||
public required string STATION { get; set; }//采集点
|
||||
public required string VIN { get; set; }//车辆VIN
|
||||
public required string ORDER_NUM { get; set; }//工单号
|
||||
public string? Assembly_LINE { get; set; } //流水线
|
||||
public required string SEQUENCE_NO { get; set; }//顺序号
|
||||
public DateTime CREATION_TIME { get; set; } //过点时间
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Request.Seres2
|
||||
{
|
||||
public class PartInfo
|
||||
{
|
||||
public string? FACTORY { get; set; }//工厂
|
||||
public required string PART_NO { get; set; }//零件号
|
||||
public required string PART_NAME { get; set; }//零件名称(中文)
|
||||
public required string ADDRESS_DESCRIBE { get; set; } //库位编号
|
||||
public required string LOCATION { get; set; }//线边工位
|
||||
public int PICKING_LINE_ID { get; set; }//捡料线序号
|
||||
public int PICKING_SUBLINE_ID { get; set; }//子捡料线编号
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Response
|
||||
{
|
||||
public class AgvConfigInfo
|
||||
{
|
||||
public required string ConfigKey { get; set; }
|
||||
public bool ConfigValue { get; set; }
|
||||
public required string AreaCode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Response
|
||||
{
|
||||
public class AgvReleaseResult
|
||||
{
|
||||
public string? TaskNo { get; set; }//任务号
|
||||
public required string[] ShelfCodes { get; set; }//货架编码数组
|
||||
public string? State { get; set; }//放行状态
|
||||
public string? Message { get; set; }//描述
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace FASS.Scheduler.Lite.Controllers.Models.Response
|
||||
{
|
||||
public class OrderReportRecord
|
||||
{
|
||||
public string? LOCATION { get; set; }//工厂
|
||||
public required string ORDERNO { get; set; }//拉动单号
|
||||
public required string VIN { get; set; }//车辆Vin
|
||||
public required string ReceiveTime { get; set; }//完成时间
|
||||
public int PICKING_LINE_ID { get; set; }//捡料线序号
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Common.NETCore.Extensions;
|
||||
using FASS.Scheduler.Attributes;
|
||||
using FASS.Scheduler.Controllers.Base;
|
||||
using FASS.Scheduler.Lite.Attributes;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using IInterfacePassPointService = FASS.Service.Lite.Services.Interface.Interfaces.IPassPointService;
|
||||
using InterFacePassPointDto = FASS.Service.Lite.Dtos.Interface.PassPointDto;
|
||||
using IPassPointService = FASS.Service.Lite.Services.Data.Interfaces.IPassPointService;
|
||||
using PassPointDto = FASS.Service.Lite.Dtos.Data.PassPointDto;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[TypeFilter(typeof(AuthorizeActionIgnoreAttribute))]
|
||||
[TypeFilter(typeof(ActionLogIgnoreAttribute))]
|
||||
[TypeFilter(typeof(InterfaceLogAttribute))]
|
||||
[Tags("生产数据接口")]
|
||||
public class ProductionBizController : BaseController
|
||||
{
|
||||
private readonly ILogger<ProductionBizController> _logger;
|
||||
private readonly IPassPointService _passPointService;
|
||||
private readonly IInterfacePassPointService _interfacePassPointService;
|
||||
|
||||
|
||||
public ProductionBizController(
|
||||
ILogger<ProductionBizController> logger,
|
||||
IPassPointService passPointService,
|
||||
IInterfacePassPointService interfacePassPointService)
|
||||
{
|
||||
_logger = logger;
|
||||
_passPointService = passPointService;
|
||||
_interfacePassPointService = interfacePassPointService;
|
||||
}
|
||||
|
||||
[Tags("过点数据")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PassPoint(Models.Request.PassPoint request)
|
||||
{
|
||||
var interfaceDto = new InterFacePassPointDto
|
||||
{
|
||||
SequenceNo = request.SequenceNo,
|
||||
Vin = request.Vin,
|
||||
PrOrderNo = request.PrOrderNo,
|
||||
PassPointTime = request.PassPointTime,
|
||||
PostTime = request.PostTime,
|
||||
StationCode = request.StationCode,
|
||||
StationDescription = request.StationDescription,
|
||||
Platform = request.Platform,
|
||||
LineCode = request.LineCode,
|
||||
LineDescription = request.LineDescription,
|
||||
FactoryCode = request.FactoryCode,
|
||||
WorkshopCode = request.WorkshopCode,
|
||||
};
|
||||
|
||||
//过点站点的过滤
|
||||
if (request.StationCode?.ToUpper() != "PBS02")
|
||||
{
|
||||
interfaceDto.Remark = $"过点数据错误,非PBS02站点数据,采集点 [{request.StationCode}]";
|
||||
_logger.LogError($"过点数据错误,采集点[{request.StationCode}]错误, 数据:{request.ToJson()}");
|
||||
await _interfacePassPointService.AddAsync(interfaceDto);
|
||||
return BadRequest($"过点数据错误,车辆Vin[{request.Vin}],采集点[{request.StationCode}]");
|
||||
}
|
||||
|
||||
//判定车辆订单&车辆VIN是否重复
|
||||
if (await _passPointService.AnyAsync(e => e.PrOrderNo == request.PrOrderNo && e.Vin == request.Vin))
|
||||
{
|
||||
interfaceDto.Remark = $"过点数据错误,车型订单号[{request.PrOrderNo}] 车辆VIN[{request.Vin}]重复";
|
||||
_logger.LogError($"过点数据错误,车型订单号[{request.PrOrderNo}] 车辆VIN[{request.Vin}]重复, 数据:{request.ToJson()}");
|
||||
await _interfacePassPointService.AddAsync(interfaceDto);
|
||||
return BadRequest($"过点数据错误,车型订单号 [{request.PrOrderNo}] 车辆VIN[{request.Vin}]重复");
|
||||
}
|
||||
await _interfacePassPointService.AddAsync(interfaceDto);//写入接口表
|
||||
var dto = new PassPointDto
|
||||
{
|
||||
SequenceNo = request.SequenceNo,
|
||||
Vin = request.Vin,
|
||||
PrOrderNo = request.PrOrderNo,
|
||||
PassPointTime = request.PassPointTime,
|
||||
PostTime = request.PostTime,
|
||||
State = PassPointConst.State.Pending,
|
||||
StationCode = request.StationCode,
|
||||
CarModel = request.Platform,
|
||||
LineCode = request.LineCode,
|
||||
FactoryCode = request.FactoryCode,
|
||||
WorkshopCode = request.WorkshopCode
|
||||
};
|
||||
await _passPointService.AddAsync(dto);//写入过点业务数据表
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using FASS.Scheduler.Models;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
namespace FASS.Scheduler.Extensions.Configure
|
||||
{
|
||||
public static class AuthExtension
|
||||
{
|
||||
public static IServiceCollection AddAuth(this IServiceCollection services, AppSettings appSettings)
|
||||
{
|
||||
services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters()
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = appSettings.Auth.Issuer,
|
||||
|
||||
ValidateAudience = true,
|
||||
ValidAudience = appSettings.Auth.Audience,
|
||||
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings.Auth.SigningKey)),
|
||||
|
||||
ValidateLifetime = true,
|
||||
RequireExpirationTime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
};
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
if (context.Request.Headers.ContainsKey("Authorization"))
|
||||
{
|
||||
context.Token = context.Request.Headers["Authorization"].FirstOrDefault()?.Substring("Bearer ".Length);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
if (context.Exception is SecurityTokenExpiredException)
|
||||
{
|
||||
context.Response.Headers.Append("Token-Expired", "true");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseAuth(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Services.CronTasks;
|
||||
using FASS.Scheduler.Services.EventBus;
|
||||
using FASS.Scheduler.Services.Extends;
|
||||
using FASS.Service.Extensions;
|
||||
|
||||
namespace FASS.Scheduler.Extensions.Configure
|
||||
{
|
||||
public static class BootExtension
|
||||
{
|
||||
public static IServiceCollection AddBoot(this IServiceCollection services, IConfiguration configuration, AppSettings appSettings)
|
||||
{
|
||||
services.AddSingleton<ExtendService>();
|
||||
|
||||
services.AddSingleton<CronTaskService>();
|
||||
services.AddSingleton<EventBusService>();
|
||||
|
||||
services.AddService(configuration, appSettings.App.ActivationCode, () => appSettings.Frame);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceProvider UseBoot(this IServiceProvider provider)
|
||||
{
|
||||
provider.UseService();
|
||||
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Common.NETCore.Extensions;
|
||||
using Common.NETCore.Models;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FASS.Scheduler.Extensions.Configure
|
||||
{
|
||||
public static class ExceptionExtension
|
||||
{
|
||||
public static IApplicationBuilder UseException(this IApplicationBuilder app)
|
||||
{
|
||||
var jsonSerializerOptions = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
app.UseExceptionHandler(builder =>
|
||||
{
|
||||
builder.Run(async context =>
|
||||
{
|
||||
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error.GetBaseException();
|
||||
var responseResult = new ResponseResult()
|
||||
{
|
||||
Success = false,
|
||||
Code = context.Response.StatusCode.ToString()
|
||||
};
|
||||
if (ex != null)
|
||||
{
|
||||
responseResult.Message = ex.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
responseResult.Message = "未知错误";
|
||||
}
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.Body.WriteAsync(responseResult.ToJson(jsonSerializerOptions).ToBytes());
|
||||
});
|
||||
});
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Common.AspNetCore.Extensions;
|
||||
namespace FASS.Scheduler.Extensions.Configure
|
||||
{
|
||||
public static class SessionExtension
|
||||
{
|
||||
public static IApplicationBuilder UseCurrent(this IApplicationBuilder app)
|
||||
{
|
||||
return app.UseCurrentUserContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Common.NETCore.Utility;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace FASS.Scheduler.Extensions.Configure
|
||||
{
|
||||
public static class SwaggerExtension
|
||||
{
|
||||
public static IServiceCollection AddSwashbuckle(this IServiceCollection services)
|
||||
{
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = Session.AssemblyName.Name,
|
||||
Version = Session.AssemblyName.Version?.ToString()
|
||||
});
|
||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
Name = "Authorization",
|
||||
BearerFormat = "JWT",
|
||||
Description = "Value {Bearer Token}"
|
||||
});
|
||||
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
|
||||
{
|
||||
[new OpenApiSecuritySchemeReference("Bearer", document)] = []
|
||||
});
|
||||
options.OrderActionsBy(api => api.RelativePath);
|
||||
//options.TagActionsBy(api => [api.HttpMethod]);
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseSwashbuckle(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
|
||||
});
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Common.AspNetCore.Helpers;
|
||||
using FASS.Scheduler.Models;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FASS.Scheduler.Extensions
|
||||
{
|
||||
public static class TokenExtension
|
||||
{
|
||||
public static string GetToken(this AppSettings appSettings, IEnumerable<Claim> claims)
|
||||
{
|
||||
var signingKey = appSettings.Auth.SigningKey;
|
||||
var algorithm = SecurityAlgorithms.HmacSha256;
|
||||
var issuer = appSettings.Auth.Issuer;
|
||||
var audience = appSettings.Auth.Audience;
|
||||
var notBefore = DateTime.Now;
|
||||
var expires = notBefore.AddSeconds(appSettings.Auth.ExpireSeconds);
|
||||
var token = JwtHelper.CreateToken(signingKey, algorithm, issuer, audience, claims, notBefore, expires);
|
||||
return token;
|
||||
}
|
||||
|
||||
public static string RefreshToken(this AppSettings appSettings, string token)
|
||||
{
|
||||
var signingKey = appSettings.Auth.SigningKey;
|
||||
var refreshToken = JwtHelper.RefreshToken(token, signingKey);
|
||||
return refreshToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<CETCompat>false</CETCompat>
|
||||
<Version>2.4.2</Version>
|
||||
<AssemblyName>FASS.Scheduler</AssemblyName>
|
||||
<ApplicationIcon>Resources\App.ico</ApplicationIcon>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Common.Net" Version="2.4.2" />
|
||||
<PackageReference Include="DocX" Version="5.0.0" />
|
||||
<PackageReference Include="FreeSpire.Barcode" Version="6.6.0" />
|
||||
<PackageReference Include="FreeSpire.Doc" Version="12.2.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.80.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="System.Management" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FASS.Extend.Master\FASS.Extend.Master.csproj" />
|
||||
<ProjectReference Include="..\FASS.Service.Lite\FASS.Service.Lite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Protos\remote.proto" GrpcServices="Server" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="PrintTemplate\模板.docx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
@FASS.Scheduler.Lite_HostAddress = http://localhost:5276
|
||||
|
||||
GET {{FASS.Scheduler.Lite_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -0,0 +1,121 @@
|
||||
using Common.AspNetCore.Extensions;
|
||||
using Common.Frame.Dtos.Frame;
|
||||
using Common.NETCore;
|
||||
using Common.NETCore.Extensions;
|
||||
using FASS.Extend.Master;
|
||||
using FASS.Scheduler.Lite.Services.EventBuses.Model;
|
||||
using FASS.Scheduler.Services.Extends.Demo;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Dtos.Base;
|
||||
using FASS.Service.Lite.Dtos.Setting;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Grpcs
|
||||
{
|
||||
public sealed class RemoteService : Remote.RemoteBase
|
||||
{
|
||||
private readonly ILogger<RemoteService> _logger;
|
||||
private readonly IDistributedCache _distributedCache;
|
||||
public RemoteService(
|
||||
ILogger<RemoteService> logger,
|
||||
IDistributedCache distributedCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_distributedCache = distributedCache;
|
||||
}
|
||||
|
||||
private static T ParseDto<T>(string json) => Guard.NotNull(json.JsonTo<T>());
|
||||
|
||||
private static List<T> ParseList<T>(string json) => Guard.NotNull(json.JsonTo<List<T>>());
|
||||
|
||||
public override Task<ResponseReply> ControlCommandSending(ControlCommandSendingRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"收到消息:{request.Dto.ToJson()}");
|
||||
var message = ParseDto<RemoteControlMsg>(request.Dto);
|
||||
foreach (var item in message.MasterMsgList)
|
||||
{
|
||||
ControlMessage controlMessage = new ControlMessage
|
||||
{
|
||||
Command = 0x01,
|
||||
SectionCount = (byte)item.LightControlList.Count,
|
||||
LightControlMessages = item.LightControlList.DeepClone<List<LightControlMessage>>(),
|
||||
State = item.Operate == "on" ? Extend.Master.Utility.SetLightGreen() : (item.Operate == "display" ? Extend.Master.Utility.SetDisplayLightAddress() : Extend.Master.Utility.SetLightOff()),
|
||||
TaskNo = Lite.Utility.Common.GetTaskSn(IPEndPoint.Parse(item.Remote)),
|
||||
PublishTime = DateTime.Now
|
||||
};
|
||||
if (ExtendUdpServerService.WaitSendData.ContainsKey(IPEndPoint.Parse(item.Remote)))
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData[IPEndPoint.Parse(item.Remote)].Add(controlMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData.TryAdd(IPEndPoint.Parse(item.Remote), new List<ControlMessage>
|
||||
{
|
||||
controlMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
return Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingConfig(SettingConfigRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dtos.ToJson()}");
|
||||
var dtos = ParseList<ConfigDto>(request.Dtos);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.Config, dtos);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingDictItem(SettingDictItemRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dtos.ToJson()}");
|
||||
var dtos = ParseList<DictItemDto>(request.Dtos);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.DictItem, dtos);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingConfigService(SettingConfigServiceRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dto.ToJson()}");
|
||||
var dto = ParseDto<ConfigServiceDto>(request.Dto);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.ConfigService, dto);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingConfigData(SettingConfigDataRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dto.ToJson()}");
|
||||
var dto = ParseDto<ConfigDataDto>(request.Dto);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.ConfigData, dto);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingAreaData(SettingAreaDataRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dtos.ToJson()}");
|
||||
var dtos = ParseList<AreaDto>(request.Dtos);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.AreaData, dtos);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingSegmentData(SettingSegmentDataRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dtos.ToJson()}");
|
||||
var dtos = ParseList<SegmentDto>(request.Dtos);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.SegmentData, dtos);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
public override async Task<ResponseReply> SettingColorRulesData(SettingColorRulesDataRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogDebug($"操作成功:{request.Dtos.ToJson()}");
|
||||
var dtos = ParseList<ColorRulesDto>(request.Dtos);
|
||||
await _distributedCache.SetAsync(CacheKey.Setting.ColorRulesData, dtos);
|
||||
return await Task.FromResult(new ResponseReply { Success = true });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using Common.Frame.Options;
|
||||
|
||||
namespace FASS.Scheduler.Models
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public Rate Rate { get; set; } = null!;
|
||||
public Auth Auth { get; set; } = null!;
|
||||
public App App { get; set; } = null!;
|
||||
public FrameOption Frame { get; set; } = null!;
|
||||
public Extend Extend { get; set; } = null!;
|
||||
public Scheduler Scheduler { get; set; } = null!;
|
||||
public PrintPath PrintPath { get; set; } = null!;
|
||||
public MasterConfig MasterConfig { get; set; } = null!;
|
||||
public InterfaceConfig InterfaceConfig { get; set; } = null!;
|
||||
}
|
||||
public class Rate
|
||||
{
|
||||
public int PermitLimit { get; set; }
|
||||
public int QueueLimit { get; set; }
|
||||
public int WindowMilliseconds { get; set; }
|
||||
}
|
||||
public class Auth
|
||||
{
|
||||
public required string SigningKey { get; set; }
|
||||
public required string Issuer { get; set; }
|
||||
public required string Audience { get; set; }
|
||||
public int ExpireSeconds { get; set; }
|
||||
}
|
||||
public class App
|
||||
{
|
||||
public required string ActivationCode { get; set; }
|
||||
}
|
||||
public class Service
|
||||
{
|
||||
public string? TcpServerLocalIP { get; set; }
|
||||
public string? UdpServerLocalIP { get; set; }
|
||||
}
|
||||
public class Extend
|
||||
{
|
||||
public bool EnableComClient { get; set; }
|
||||
public string? ComClientPortName { get; set; }
|
||||
public bool EnableTcpClient { get; set; }
|
||||
public string? TcpClientRemoteIP { get; set; }
|
||||
public bool EnableTcpServer { get; set; }
|
||||
public string? TcpServerLocalIP { get; set; }
|
||||
public bool EnableUdpServer { get; set; }
|
||||
public string? UdpServerRemoteIP { get; set; }
|
||||
public string? UdpServerLocalIP { get; set; }
|
||||
public bool EnableHttpClient { get; set; }
|
||||
public string? HttpClientBaseAddress { get; set; }
|
||||
public bool EnableHttpServer { get; set; }
|
||||
public List<string> HttpServerPrefixes { get; set; } = [];
|
||||
}
|
||||
|
||||
public class Scheduler
|
||||
{
|
||||
public int StartupDueTime { get; set; }
|
||||
public int DataDueTime { get; set; }
|
||||
public int FirstLightDueTime { get; set; }
|
||||
public int MaterialLightDueTime { get; set; }
|
||||
public int EndLightDueTime { get; set; }
|
||||
public int SwitchSegmentDueTime { get; set; }
|
||||
public int SegmentStartLightDueTime { get; set; }
|
||||
public int OrderPackagingDueTime { get; set; }
|
||||
public int PrintedDueTime { get; set; }
|
||||
public int MaterialUpdateDueTime { get; set; }
|
||||
public int MaterialRelationDueTime { get; set; }
|
||||
public bool EnableOrderReport { get; set; }
|
||||
public int OrderReportDueTime { get; set; }
|
||||
public bool EnableBomInfoWarning { get; set; }
|
||||
public int BomInfoWarningDueTime { get; set; }
|
||||
public bool EnableAlarmPush { get; set; }
|
||||
public int AlarmPushDueTime { get; set; }
|
||||
public bool EnableLightOfflineWarning { get; set; }
|
||||
public int LightOfflineDueTime { get; set; }
|
||||
public bool EnablePrinterOfflineWarning { get; set; }
|
||||
public int PrinterOfflineDueTime { get; set; }
|
||||
public int DataArchivingDueTime { get; set; }
|
||||
public int TaskArchivingDueTime { get; set; }
|
||||
public int PrintedFileClearDueTime { get; set; }
|
||||
public bool EnableMaterialResolver { get; set; }
|
||||
public int MaterialResolverDueTime { get; set; }
|
||||
}
|
||||
|
||||
public class PrintPath
|
||||
{
|
||||
public required string Template { get; set; }
|
||||
public required string PrintFilePath { get; set; }
|
||||
public required string PicPath { get; set; }
|
||||
}
|
||||
public class MasterConfig
|
||||
{
|
||||
public int CommandSendInterval { get; set; }
|
||||
public int HeartbeatInterval { get; set; }
|
||||
public int CommTimeOut { get; set; }
|
||||
public int OfflineThreshold { get; set; }
|
||||
public int ButtonPressedThreshold { get; set; }
|
||||
}
|
||||
public class InterfaceConfig
|
||||
{
|
||||
public required string EsbUrl { get; set; }
|
||||
public required string Source { get; set; }
|
||||
public required string BasicSecretKey { get; set; }
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,68 @@
|
||||
using Common.NETCore;
|
||||
using Common.NETCore.Extensions;
|
||||
using Common.NETCore.Helpers;
|
||||
using FASS.Scheduler.Attributes;
|
||||
using FASS.Scheduler.Extensions.Configure;
|
||||
using FASS.Scheduler.Grpcs;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Services;
|
||||
using FASS.Service.Consts.Core;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Serilog;
|
||||
using System.Threading.RateLimiting;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var appSettings = builder.Configuration.Get<AppSettings>();
|
||||
builder.Services.AddSingleton(Guard.NotNull(appSettings));
|
||||
builder.Services.AddSerilog((services, logger) => logger.ReadFrom.Configuration(builder.Configuration));
|
||||
builder.Services
|
||||
.AddControllers(options =>
|
||||
{
|
||||
options.Filters.Add(typeof(ResultAttribute));
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.AddDefaultOptions();
|
||||
});
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwashbuckle();
|
||||
builder.Services.AddAuth(appSettings);
|
||||
builder.Services.AddBoot(builder.Configuration, appSettings);
|
||||
builder.Services.AddHostedService<AppHostService>();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddGrpc();
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy(AppConst.Cors.Name, policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().WithExposedHeaders("X-Pagination");
|
||||
});
|
||||
});
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.AddFixedWindowLimiter(AppConst.Rate.Name, opt =>
|
||||
{
|
||||
opt.Window = TimeSpan.FromMilliseconds(appSettings.Rate.WindowMilliseconds);
|
||||
opt.PermitLimit = appSettings.Rate.PermitLimit;
|
||||
opt.QueueLimit = appSettings.Rate.QueueLimit;
|
||||
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
||||
});
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
});
|
||||
var app = builder.Build();
|
||||
app.UseException();
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseRouting();
|
||||
app.UseSwashbuckle();
|
||||
app.UseAuth();
|
||||
app.UseCurrent();
|
||||
app.Services.UseBoot();
|
||||
app.MapGrpcService<RemoteService>();
|
||||
app.UseCors(AppConst.Cors.Name);
|
||||
app.UseRateLimiter();
|
||||
app.MapControllers();
|
||||
app.Lifetime.ApplicationStarted.Register(() => BrowserHelper.OpenBrowser($"{app.Urls.First()}/swagger"));
|
||||
app.Run();
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:20101",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "FASS.Scheduler";
|
||||
|
||||
package remote;
|
||||
|
||||
service Remote {
|
||||
rpc ControlCommandSending (ControlCommandSendingRequest) returns (ResponseReply);
|
||||
rpc SettingConfig (SettingConfigRequest) returns (ResponseReply);
|
||||
rpc SettingDictItem (SettingDictItemRequest) returns (ResponseReply);
|
||||
rpc SettingConfigService (SettingConfigServiceRequest) returns (ResponseReply);
|
||||
rpc SettingConfigData (SettingConfigDataRequest) returns (ResponseReply);
|
||||
rpc SettingAreaData (SettingAreaDataRequest) returns (ResponseReply);
|
||||
rpc SettingSegmentData (SettingSegmentDataRequest) returns (ResponseReply);
|
||||
rpc SettingColorRulesData (SettingColorRulesDataRequest) returns (ResponseReply);
|
||||
}
|
||||
|
||||
message ResponseReply {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
string data = 3;
|
||||
}
|
||||
|
||||
message ControlCommandSendingRequest {
|
||||
string dto = 1;
|
||||
}
|
||||
|
||||
message SettingConfigRequest {
|
||||
string dtos = 1;
|
||||
}
|
||||
|
||||
message SettingDictItemRequest {
|
||||
string dtos = 1;
|
||||
}
|
||||
|
||||
message SettingConfigServiceRequest {
|
||||
string dto = 1;
|
||||
}
|
||||
|
||||
message SettingConfigDataRequest {
|
||||
string dto = 1;
|
||||
}
|
||||
|
||||
message SettingAreaDataRequest {
|
||||
string dtos = 1;
|
||||
}
|
||||
|
||||
message SettingSegmentDataRequest {
|
||||
string dtos = 1;
|
||||
}
|
||||
|
||||
message SettingColorRulesDataRequest {
|
||||
string dtos = 1;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,149 @@
|
||||
using Common.AspNetCore.Extensions;
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using Common.NETCore.Utility;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Services.CronTasks;
|
||||
using FASS.Scheduler.Services.EventBus;
|
||||
using FASS.Scheduler.Services.Extends;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
|
||||
namespace FASS.Scheduler.Services
|
||||
{
|
||||
public class AppHostService : IHostedService, IAsyncDisposable
|
||||
{
|
||||
private IHostApplicationLifetime Lifetime { get; }
|
||||
private CancellationTokenSource? _startupTokenSource;
|
||||
private Task? _startupTask;
|
||||
|
||||
public ILogger<AppHostService> Logger { get; }
|
||||
public AppSettings AppSettings { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public ExtendService ExtendService { get; private set; } = null!;
|
||||
public EventBusService EventBusService { get; private set; } = null!;
|
||||
public CronTaskService CronTaskService { get; private set; } = null!;
|
||||
|
||||
public AppHostService(
|
||||
IHostApplicationLifetime lifetime,
|
||||
ILogger<AppHostService> logger,
|
||||
AppSettings appSettings,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
Lifetime = lifetime;
|
||||
Logger = logger;
|
||||
AppSettings = appSettings;
|
||||
ServiceProvider = serviceProvider;
|
||||
|
||||
Lifetime.ApplicationStarted.Register(OnApplicationStarted);
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogInformation("服务启动中");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogInformation("服务停止中");
|
||||
|
||||
var startupTokenSource = Interlocked.Exchange(ref _startupTokenSource, null);
|
||||
startupTokenSource?.Cancel();
|
||||
var startupTask = Interlocked.Exchange(ref _startupTask, null);
|
||||
if (startupTask is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await startupTask.WaitAsync(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (startupTokenSource?.IsCancellationRequested == true || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogInformation("服务启动流程已取消");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "服务等待启动流程结束失败");
|
||||
}
|
||||
}
|
||||
startupTokenSource?.Dispose();
|
||||
|
||||
var stopTasks = new List<Task>();
|
||||
if (CronTaskService is not null)
|
||||
{
|
||||
stopTasks.Add(CronTaskService.StopAsync(cancellationToken));
|
||||
}
|
||||
if (EventBusService is not null)
|
||||
{
|
||||
stopTasks.Add(EventBusService.StopAsync(cancellationToken));
|
||||
}
|
||||
if (ExtendService is not null)
|
||||
{
|
||||
stopTasks.Add(ExtendService.StopAsync(cancellationToken));
|
||||
}
|
||||
await Task.WhenAll(stopTasks);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
Logger.LogInformation("服务释放资源");
|
||||
}
|
||||
|
||||
private void OnApplicationStarted()
|
||||
{
|
||||
if (_startupTask is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_startupTokenSource = new CancellationTokenSource();
|
||||
_startupTask = RunStartupAsync(_startupTokenSource.Token);
|
||||
}
|
||||
|
||||
private async Task RunStartupAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("[{Name} V {Version}]", Session.AssemblyName.Name, Session.AssemblyName.Version);
|
||||
|
||||
if (AppSettings.Scheduler.StartupDueTime > 0)
|
||||
{
|
||||
Logger.LogInformation("启动延迟:{Delay} 毫秒", AppSettings.Scheduler.StartupDueTime);
|
||||
await Task.Delay(AppSettings.Scheduler.StartupDueTime, cancellationToken);
|
||||
}
|
||||
|
||||
Logger.LogInformation("--------初始化--------");
|
||||
|
||||
InitializeCache();
|
||||
InitializeService();
|
||||
|
||||
Logger.LogInformation("--------启动--------");
|
||||
|
||||
await Task.WhenAll(
|
||||
ExtendService.StartAsync(cancellationToken),
|
||||
EventBusService.StartAsync(cancellationToken),
|
||||
CronTaskService.StartAsync(cancellationToken));
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogInformation("--------取消--------");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogCritical(ex, "错误");
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeCache()
|
||||
{
|
||||
ServiceProvider.GetScopeService<IDataService>().GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);
|
||||
}
|
||||
|
||||
public void InitializeService()
|
||||
{
|
||||
ExtendService = ServiceProvider.GetRequiredService<ExtendService>();
|
||||
EventBusService = ServiceProvider.GetRequiredService<EventBusService>();
|
||||
CronTaskService = ServiceProvider.GetRequiredService<CronTaskService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
using FASS.Scheduler.Lite.Services.CronTasks.Jobs;
|
||||
using FASS.Scheduler.Models;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Services.CronTasks
|
||||
{
|
||||
public class CronTaskService
|
||||
{
|
||||
public ILogger<CronTaskService> Logger { get; }
|
||||
public AppSettings AppSettings { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public CronTaskService(
|
||||
ILogger<CronTaskService> logger,
|
||||
AppSettings appSettings,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
Logger = logger;
|
||||
AppSettings = appSettings;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (AppSettings.Frame.CronTask.IsEnable)
|
||||
{
|
||||
var factory = ServiceProvider.GetRequiredService<ISchedulerFactory>();
|
||||
|
||||
var scheduler = await factory.GetScheduler();
|
||||
#region [5]基础数据同步以及订单组单Job
|
||||
//零件主数据同步Job
|
||||
var job1 = JobBuilder.Create<MaterialUpdataJob>()
|
||||
.WithIdentity("materialUpdateJob", "dataSyncGroup")
|
||||
.Build();
|
||||
|
||||
var trigger1 = TriggerBuilder.Create()
|
||||
.WithIdentity("materialUpdataTrigger", "dataSyncGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.MaterialUpdateDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job1, trigger1);
|
||||
|
||||
//零件库位关系数据同步Job
|
||||
var job2 = JobBuilder.Create<MaterialStorageUpdataJob>()
|
||||
.WithIdentity("materialStorageUpdataJob", "dataSyncGroup")
|
||||
.Build();
|
||||
|
||||
var trigger2 = TriggerBuilder.Create()
|
||||
.WithIdentity("materialStorageUpdataTrigger", "dataSyncGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.MaterialRelationDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job2, trigger2);
|
||||
|
||||
//订单组单Job
|
||||
var job3 = JobBuilder.Create<OrderPackagingJob>()
|
||||
.WithIdentity("orderPackagingJob", "dataSyncGroup")
|
||||
.Build();
|
||||
|
||||
var trigger3 = TriggerBuilder.Create()
|
||||
.WithIdentity("orderPackagingTrigger", "dataSyncGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.OrderPackagingDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job3, trigger3);
|
||||
|
||||
//拣配单打印服务
|
||||
var job4 = JobBuilder.Create<OrderPrintJob>()
|
||||
.WithIdentity("orderPrintJob", "dataSyncGroup")
|
||||
.Build();
|
||||
|
||||
var trigger4 = TriggerBuilder.Create()
|
||||
.WithIdentity("orderPrintJobTrigger", "dataSyncGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.PrintedDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job4, trigger4);
|
||||
|
||||
//回传拣配完成状态到LES
|
||||
if (AppSettings.Scheduler.EnableOrderReport)
|
||||
{
|
||||
var job5 = JobBuilder.Create<OrderReportJob>()
|
||||
.WithIdentity("orderReportJob", "dataSyncGroup")
|
||||
.Build();
|
||||
|
||||
var trigger5 = TriggerBuilder.Create()
|
||||
.WithIdentity("orderReportJobTrigger", "dataSyncGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.OrderReportDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job5, trigger5);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [5]开始灯/结束灯/物料灯亮灯job
|
||||
//区域开始灯job
|
||||
var job10 = JobBuilder.Create<CheckAreaFirstLightJob>()
|
||||
.WithIdentity("checkAreaFirstLightJob", "lightGroup")
|
||||
.Build();
|
||||
|
||||
var trigger10 = TriggerBuilder.Create()
|
||||
.WithIdentity("checkAreaFirstLightJobTrigger", "lightGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.FirstLightDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job10, trigger10);
|
||||
|
||||
//物料灯点亮job
|
||||
var job11 = JobBuilder.Create<CheckMaterialLightJob>()
|
||||
.WithIdentity("checkMaterialLightJob", "lightGroup")
|
||||
.Build();
|
||||
|
||||
var trigger11 = TriggerBuilder.Create()
|
||||
.WithIdentity("checkMaterialLightJobTrigger", "lightGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.MaterialLightDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job11, trigger11);
|
||||
|
||||
//结束灯点亮job
|
||||
var job12 = JobBuilder.Create<CheckEndLightJob>()
|
||||
.WithIdentity("checkEndLightJob", "lightGroup")
|
||||
.Build();
|
||||
|
||||
var trigger12 = TriggerBuilder.Create()
|
||||
.WithIdentity("checkEndLightJobTrigger", "lightGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.EndLightDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job12, trigger12);
|
||||
|
||||
//切换区域job[包括结束任务]
|
||||
var job13 = JobBuilder.Create<CheckSwitchSegmentJob>()
|
||||
.WithIdentity("checkSwitchSegmentJob", "lightGroup")
|
||||
.Build();
|
||||
|
||||
var trigger13 = TriggerBuilder.Create()
|
||||
.WithIdentity("checkSwitchSegmentJobTrigger", "lightGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.SwitchSegmentDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job13, trigger13);
|
||||
|
||||
//分段开始灯点亮job
|
||||
var job14 = JobBuilder.Create<CheckStartLightJob>()
|
||||
.WithIdentity("checkStartLightJob", "lightGroup")
|
||||
.Build();
|
||||
|
||||
var trigger14 = TriggerBuilder.Create()
|
||||
.WithIdentity("checkStartLightJobTrigger", "lightGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.SegmentStartLightDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job14, trigger14);
|
||||
|
||||
#endregion
|
||||
|
||||
#region [4]异常判定/消息推送job
|
||||
//配载单超时未接收判定服务
|
||||
if (AppSettings.Scheduler.EnableBomInfoWarning)
|
||||
{
|
||||
var job20 = JobBuilder.Create<BomInfoWarningJob>()
|
||||
.WithIdentity("bomInfoWarningJob", "warningGroup")
|
||||
.Build();
|
||||
|
||||
var trigger20 = TriggerBuilder.Create()
|
||||
.WithIdentity("bomInfoWarningJobTrigger", "warningGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInSeconds(AppSettings.Scheduler.BomInfoWarningDueTime)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job20, trigger20);
|
||||
}
|
||||
|
||||
//控制器/灯离线判定服务
|
||||
if (AppSettings.Scheduler.EnableLightOfflineWarning)
|
||||
{
|
||||
var job21 = JobBuilder.Create<LightOfflineWarningJob>()
|
||||
.WithIdentity("lightOfflineJob", "warningGroup")
|
||||
.Build();
|
||||
|
||||
var trigger21 = TriggerBuilder.Create()
|
||||
.WithIdentity("lightOfflineJobTrigger", "warningGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInMinutes(AppSettings.Scheduler.LightOfflineDueTime)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job21, trigger21);
|
||||
}
|
||||
|
||||
//打印机离线判定服务
|
||||
if (AppSettings.Scheduler.EnablePrinterOfflineWarning)
|
||||
{
|
||||
var job22 = JobBuilder.Create<PrinterOfflineWarningJob>()
|
||||
.WithIdentity("printerOfflineJob", "warningGroup")
|
||||
.Build();
|
||||
|
||||
var trigger22 = TriggerBuilder.Create()
|
||||
.WithIdentity("printerOfflineJobTrigger", "warningGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInMinutes(AppSettings.Scheduler.PrinterOfflineDueTime)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job22, trigger22);
|
||||
}
|
||||
|
||||
//钉钉推送服务(默认启动,在job中判定推送)
|
||||
var job23 = JobBuilder.Create<AlarmPushJob>()
|
||||
.WithIdentity("alarmPushJob", "warningGroup")
|
||||
.Build();
|
||||
|
||||
var trigger23 = TriggerBuilder.Create()
|
||||
.WithIdentity("alarmPushJobTrigger", "warningGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.AlarmPushDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job23, trigger23);
|
||||
|
||||
#endregion
|
||||
|
||||
#region [3]数据/文件归档job
|
||||
//任务完成后从实时表归档到历史表
|
||||
var job30 = JobBuilder.Create<TaskArchivingJob>()
|
||||
.WithIdentity("taskArchivingJob", "archivingGroup")
|
||||
.Build();
|
||||
|
||||
var trigger30 = TriggerBuilder.Create()
|
||||
.WithIdentity("taskArchivingJob", "archivingGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInMinutes(AppSettings.Scheduler.TaskArchivingDueTime)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job30, trigger30);
|
||||
|
||||
//其他数据表归档服务
|
||||
var job31 = JobBuilder.Create<DataArchivingJob>()
|
||||
.WithIdentity("dataArchivingJob", "archivingGroup")
|
||||
.Build();
|
||||
|
||||
var trigger31 = TriggerBuilder.Create()
|
||||
.WithIdentity("dataArchivingJobTrigger", "archivingGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInMinutes(AppSettings.Scheduler.DataArchivingDueTime)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job31, trigger31);
|
||||
|
||||
//文件清理服务(12h清理一次)
|
||||
var job32 = JobBuilder.Create<PrintedFilesArchivingJob>()
|
||||
.WithIdentity("printedFilesArchivingJob", "archivingGroup")
|
||||
.Build();
|
||||
|
||||
var trigger32 = TriggerBuilder.Create()
|
||||
.WithIdentity("printedFilesArchivingJobTrigger", "archivingGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInHours(AppSettings.Scheduler.PrintedFileClearDueTime)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job32, trigger32);
|
||||
|
||||
#endregion
|
||||
|
||||
#region [1]BOM解析生成车辆物料清单job
|
||||
//BOM解析服务
|
||||
if (AppSettings.Scheduler.EnableMaterialResolver)
|
||||
{
|
||||
var job40 = JobBuilder.Create<ProductionMaterialResolverJob>()
|
||||
.WithIdentity("productionMaterialResolverJob", "bomResolverGroup")
|
||||
.Build();
|
||||
|
||||
var trigger40 = TriggerBuilder.Create()
|
||||
.WithIdentity("ProductionMaterialResolverJobTrigger", "bomResolverGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(AppSettings.Scheduler.MaterialResolverDueTime))
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
await scheduler.ScheduleJob(job40, trigger40);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (AppSettings.Frame.CronTask.IsEnable)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex.ToString());
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
using Common.Frame.Dtos.Frame;
|
||||
using Common.Frame.Entities.Frame;
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Scheduler.Lite.Utility;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
using System.Text;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 推送钉钉机器人消息Job
|
||||
* 频次:3s同步一次
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class AlarmPushJob : IJob
|
||||
{
|
||||
private readonly ILogger<AlarmPushJob> _logger;
|
||||
private readonly AppSettings _appSettings;
|
||||
private readonly IAlarmService _alarmService;
|
||||
private readonly IPushService _pushService;
|
||||
private readonly IPushAlarmService _pushAlarmService;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public AlarmPushJob(
|
||||
ILogger<AlarmPushJob> logger,
|
||||
AppSettings appSettings,
|
||||
IAlarmService alarmService,
|
||||
IPushService pushService,
|
||||
IPushAlarmService pushAlarmService,
|
||||
IDataService dataService)
|
||||
{
|
||||
_logger = logger;
|
||||
_appSettings = appSettings;
|
||||
_alarmService = alarmService;
|
||||
_pushService = pushService;
|
||||
_pushAlarmService = pushAlarmService;
|
||||
_dataService = dataService;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogDebug($"ServiceName:[AlarmPushJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var configDto = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);//获取配置信息
|
||||
if (configDto.EnablePush is not null && configDto.EnablePush.ToLower() == "true" && _appSettings.Scheduler.EnableAlarmPush)
|
||||
{
|
||||
var dtos = await _alarmService.ToListAsync(e => !e.IsPushed && e.IsEnable);//获取所有未推送的报警
|
||||
if (dtos is null || dtos.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var DictItemDtos = _dataService.GetToList<DictItemDto, DictItemEntity>(CacheKey.Setting.DictItem, e => e.IsEnable).ToList();
|
||||
var dingTalkAlarmDtos = await _pushAlarmService.ToListAsync(e => e.IsEnable);//获取钉钉群组与报警映射关系
|
||||
var dingTalkGroupDtos = await _pushService.ToListAsync(e => e.IsEnable);//获取钉钉群组
|
||||
List<string> alarmTypes = new List<string>();
|
||||
//需要推送的报警
|
||||
var pushAlarmTypes = DictItemDtos.Where(x => x.Dict.Code == "AlarmType" && dingTalkAlarmDtos.Select(e => e.AlarmId).Contains(x.Id)).ToList();
|
||||
alarmTypes.AddRange(pushAlarmTypes.Select(e => e.Code));
|
||||
|
||||
//1、直接更新不推送的报警
|
||||
var unPushedAlarmIds = dtos.Where(e => !alarmTypes.Contains(e.Type!)).Select(e => e.Id).ToList();
|
||||
if (unPushedAlarmIds.Count > 0)
|
||||
{
|
||||
await _alarmService.Repository.ExecuteUpdateAsync(e => unPushedAlarmIds.Contains(e.Id), s => s.SetProperty(b => b.IsPushed, true).SetProperty(b => b.Remark, "报警类型不推送钉钉,默认推送完成"));
|
||||
}
|
||||
|
||||
//2、按类型分组拼接报警的推送信
|
||||
var pushedAlarmDtos = dtos.Where(e => alarmTypes.Contains(e.Type!)).ToList();
|
||||
var groups = pushedAlarmDtos.GroupBy(e => e.Type);
|
||||
const int MaxMsgLength = 6000;
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var type = group.Key;
|
||||
var alarmTypeDto = DictItemDtos.Where(x => x.Dict.Code == "AlarmType").FirstOrDefault(e => e.Code == type);
|
||||
if (alarmTypeDto is null)
|
||||
{
|
||||
await _alarmService.Repository.ExecuteUpdateAsync(e => e.Type == type && e.IsPushed == false, s => s.SetProperty(b => b.IsPushed, true).SetProperty(b => b.Remark, "报警类型未匹配到报警名称,默认推送完成"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var alarmName = alarmTypeDto.Name;
|
||||
var msgs = new List<string>();
|
||||
var groupIds = new List<List<string>>();
|
||||
|
||||
// 使用 StringBuilder,预分配一点容量减少扩容
|
||||
var sb = new StringBuilder(1024);
|
||||
var currentIds = new List<string>();
|
||||
|
||||
foreach (var alarm in group)
|
||||
{
|
||||
var message = alarm.Message ?? string.Empty;
|
||||
// 预估追加长度:消息内容 + 1 个逗号
|
||||
var estimatedNewLen = sb.Length + message.Length + 1;
|
||||
if (estimatedNewLen <= MaxMsgLength)
|
||||
{
|
||||
sb.Append(message).Append(',');
|
||||
currentIds.Add(alarm.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果当前 sb 为空且单条消息超过限制,则需要强制加入(避免无限循环)
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
// 截断单条消息以适配限制(保留尽量多的字符)
|
||||
var available = Math.Max(0, MaxMsgLength - 1); // 预留1位给逗号(虽然后面会去掉)
|
||||
var toAppend = message.Length > available ? message.Substring(0, available) : message;
|
||||
sb.Append(toAppend).Append(',');
|
||||
currentIds.Add(alarm.Id);
|
||||
}
|
||||
|
||||
// 将当前缓冲区入列(去掉末尾逗号)
|
||||
if (sb.Length > 0 && sb[^1] == ',') sb.Length--;
|
||||
msgs.Add(sb.ToString());
|
||||
groupIds.Add(new List<string>(currentIds));
|
||||
|
||||
// 重置 StringBuilder 和 ids(保留分配容量以降低 GC 频率)
|
||||
sb.Clear();
|
||||
currentIds.Clear();
|
||||
|
||||
// 将当前报警作为新的开始项
|
||||
if (message.Length + 1 <= MaxMsgLength)
|
||||
{
|
||||
sb.Append(message).Append(',');
|
||||
currentIds.Add(alarm.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 单条消息仍然超长,截断后直接作为一条消息/分组
|
||||
var available = Math.Max(0, MaxMsgLength - 1);
|
||||
var toAppend = message.Length > available ? message.Substring(0, available) : message;
|
||||
sb.Append(toAppend).Append(',');
|
||||
currentIds.Add(alarm.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余缓冲区
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
if (sb[^1] == ',') sb.Length--;
|
||||
msgs.Add(sb.ToString());
|
||||
groupIds.Add(new List<string>(currentIds));
|
||||
sb.Clear();
|
||||
currentIds.Clear();
|
||||
}
|
||||
|
||||
// 推送每条消息给对应的钉钉群组并更新数据库
|
||||
for (int i = 0; i < msgs.Count; i++)
|
||||
{
|
||||
var relations = dingTalkAlarmDtos.Where(e => e.AlarmId == alarmTypeDto.Id).ToList();
|
||||
string resultMgs = string.Empty;
|
||||
foreach (var relation in relations)
|
||||
{
|
||||
var pushAddress = relation.PushAddress!;
|
||||
var result = await DingTalkHelper.SendTextWithMarkDown(pushAddress, "PTL预警", alarmName!, msgs[i]);
|
||||
resultMgs += $" {result}";
|
||||
}
|
||||
|
||||
var idsToUpdate = groupIds[i];
|
||||
if (idsToUpdate.Count > 0)
|
||||
{
|
||||
await _alarmService.Repository.ExecuteUpdateAsync(e => idsToUpdate.Contains(e.Id), s => s.SetProperty(b => b.IsPushed, true).SetProperty(b => b.Data, resultMgs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"AlarmPushJob ex=>{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using Common.Frame.Services.Frame.Interfaces;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using FASS.Service.Models.Record;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
using ILesOrderService = FASS.Service.Lite.Services.Interface.Interfaces.IOrderService;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 配载单接口预警Job
|
||||
* 频次:10min轮询一次
|
||||
* 描述:PTL收到车辆过点信息,但是在设置时间阈值(默认30min)内没有收到配载单信息时触发报警
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class BomInfoWarningJob : IJob
|
||||
{
|
||||
private readonly ILogger<BomInfoWarningJob> _logger;
|
||||
private readonly IPassPointService _passPointService;
|
||||
private readonly ILesOrderService _lesOrderService;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
|
||||
public BomInfoWarningJob(
|
||||
ILogger<BomInfoWarningJob> logger,
|
||||
ILesOrderService lesOrderService,
|
||||
IPassPointService passPointService,
|
||||
IDataService dataService,
|
||||
IConfigService configService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_logger = logger;
|
||||
_passPointService = passPointService;
|
||||
_lesOrderService = lesOrderService;
|
||||
_dataService = dataService;
|
||||
_configService = configService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[BomInfoWarningJob],CurrentTime: {DateTime.Now}");
|
||||
var configDtos = _configService.ToList(e => e.IsEnable);
|
||||
var seqConfigDto = configDtos.FirstOrDefault(e => e.Key == "LastMomSequenceNo");
|
||||
if (seqConfigDto is null)
|
||||
{
|
||||
_logger.LogError($"ServiceName:[BomInfoWarningJob],未设置过点顺序号初始值");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var ruleConfigDto = configDtos.FirstOrDefault(e => e.Key == "SequenceNoCaptureIndex");//流水号截取索引
|
||||
if (ruleConfigDto is null || string.IsNullOrEmpty(ruleConfigDto.Value))
|
||||
{
|
||||
_logger.LogError($"ServiceName:[BomInfoWarningJob],未设置过点截取规则的起始值");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!int.TryParse(ruleConfigDto.Value, out var indexValue))
|
||||
{
|
||||
_logger.LogError($"ServiceName:[BomInfoWarningJob],系统设置的过点规则不正确");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (seqConfigDto.Value!.Length <= indexValue)
|
||||
{
|
||||
//至少是大于规则值的长度
|
||||
_logger.LogError($"ServiceName:[BomInfoWarningJob],系统配置的初始过点顺序号不正确");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//1、获取未打包完成的过点信息
|
||||
var seqNo = int.Parse(seqConfigDto.Value!.Substring(indexValue).TrimStart('0').Length == 0 ? "0" : seqConfigDto.Value!.Substring(indexValue).TrimStart('0'));
|
||||
var passPointDto = _passPointService.ToList(e => e.State == PassPointConst.State.Pending).Where(e => seqNo < int.Parse(e.SequenceNo.Substring(indexValue).TrimStart('0'))).OrderBy(e => e.SequenceNo).FirstOrDefault();//过点顺序号大于最后过点顺序号
|
||||
if (passPointDto is null)
|
||||
{
|
||||
_logger.LogDebug($"ServiceName:[BomInfoWarningJob],没有待处理的过点数据");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//2、得到上一个已处理完车辆的过点顺序号
|
||||
if (seqNo + 1 != int.Parse(passPointDto.SequenceNo.Substring(indexValue).TrimStart('0')))
|
||||
{
|
||||
return Task.CompletedTask;//车辆过点跳号了,不做处理
|
||||
}
|
||||
var configDto = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);//获取配置参数
|
||||
var threshold = configDto.BomInfoWarningLimit is null ? 30 : int.Parse(configDto.BomInfoWarningLimit);//预警阈值
|
||||
if (passPointDto.PassPointTime.AddMinutes(threshold) < DateTime.Now)
|
||||
{
|
||||
//过点未组单,并且过点时间与当前时间差值超过指定阈值,判定是否有配载单,无配载单开始报警
|
||||
var bomDtos = _lesOrderService.Set().Where(e => e.PrOrderNo == passPointDto.PrOrderNo && e.IsEnable && e.Vin == passPointDto.Vin).ToList();
|
||||
if (!bomDtos.Any())
|
||||
{
|
||||
_logger.LogError($"ServiceName:[BomInfoWarningJob],车辆VIN[{passPointDto.Vin}],过点顺序号[{passPointDto.SequenceNo}]已超[{threshold}]分钟未收到配载单。");
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.BomInfosTimeout,
|
||||
Code = passPointDto.Vin,
|
||||
Message = $"车辆VIN[{passPointDto.Vin}],过点顺序号[{passPointDto.SequenceNo}]已超[{threshold}]分钟未收到配载单。"
|
||||
};
|
||||
_alarmService.AddModel(model, 180);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Extend.Master;
|
||||
using FASS.Scheduler.Lite.Services.EventBuses.Model;
|
||||
using FASS.Scheduler.Services.Extends.Demo;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Dtos.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using Quartz;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 控制区域内第一个开始灯Job
|
||||
* 频次:3s轮询一次
|
||||
* 逻辑:
|
||||
* 检索每个区域的第一个分段是否存在正在执行的合单任务
|
||||
* 1)存在
|
||||
* 不点亮开始灯
|
||||
* 2)不存在
|
||||
* 判断是否待分拣的订单数 > 最大合单数
|
||||
* 2.1)小于最大合单数 不处理逻辑,不点亮开始灯
|
||||
* 2.2)大于等于最大合单数
|
||||
* 判定区域内灯是否都是关闭状态[state= 0]
|
||||
* 如果都是关闭状态[state= 0]:点亮开始灯
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class CheckAreaFirstLightJob : IJob
|
||||
{
|
||||
private readonly ILogger<CheckAreaFirstLightJob> _logger;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly ISegmentService _segmentService;
|
||||
private readonly ITaskService _mergeTaskService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IAreaService _areaService;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public CheckAreaFirstLightJob(
|
||||
ILogger<CheckAreaFirstLightJob> logger,
|
||||
ILightService lightService,
|
||||
ISegmentService segmentService,
|
||||
ITaskService mergeTaskService,
|
||||
IOrderService orderService,
|
||||
IAreaService areaService,
|
||||
IDataService dataService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lightService = lightService;
|
||||
_segmentService = segmentService;
|
||||
_mergeTaskService = mergeTaskService;
|
||||
_orderService = orderService;
|
||||
_areaService = areaService;
|
||||
_dataService = dataService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckAreaFirstLightJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var lightDtos = _lightService.ToList(e => e.IsEnable);//获取所有灯 --效率待优化
|
||||
//得到每个区域第一个分段,并且分段配置了开始灯/结束灯
|
||||
var firstSegmentDtos = _segmentService.ToList(e => e.SortNumber == 1 && e.IsEnable && !string.IsNullOrEmpty(e.StartLightId) && !string.IsNullOrEmpty(e.EndLightId));
|
||||
var areaDtos = _areaService.ToList(e => e.IsEnable);//获取所有区域
|
||||
//判定每条线首分段是否有正在执行的任务,如果没有点亮第一个开始灯
|
||||
var taskDtos = _mergeTaskService.ToList(e => e.State != OrderConst.State.Completed);//待执行或正在执行的分拣任务
|
||||
List<TaskDto> list = new List<TaskDto>();
|
||||
foreach (var segment in firstSegmentDtos)
|
||||
{
|
||||
//不存在正在分拣或准备分拣的合单任务时
|
||||
if (!taskDtos.Any(e => e.CurSegmentCode == segment.Code))
|
||||
{
|
||||
//判断首个分段内是否存在亮灯的情况
|
||||
if (lightDtos.Any(e => e.Id == segment.StartLightId) && lightDtos.FirstOrDefault(e => e.Id == segment.StartLightId)?.State != 0)
|
||||
{
|
||||
//开始灯亮着,检测下一条线
|
||||
continue;
|
||||
}
|
||||
var areaDto = areaDtos.Where(e => e.Id == segment.AreaId).ToList().FirstOrDefault();
|
||||
if (areaDto is null)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckAreaFirstLightJob],区域[{segment.AreaCode}] 未匹配到区域信息");
|
||||
continue;//检测下一条线
|
||||
}
|
||||
//存在待分拣的订单时(大于等于最大合单数)
|
||||
var orderDtos = _orderService.GetNewMergeTask(segment.AreaCode!, areaDto.MaxTask);
|
||||
if (orderDtos is null || orderDtos.Count() == 0)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckAreaFirstLightJob],区域[{segment.AreaCode}] 待分拣的拣配单数量为0");
|
||||
continue;//检测下一条线
|
||||
}
|
||||
if (orderDtos.Count() < areaDto.MaxTask! && !orderDtos.Any(e => e.IsForcedPicking))
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckAreaFirstLightJob],区域[{segment.AreaCode}] 待分拣的拣配单数量少于最大合单数");
|
||||
continue;//检测下一条线
|
||||
}
|
||||
var configServiceDto = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);//获取灯颜色
|
||||
var needScanning = false;
|
||||
if (!string.IsNullOrEmpty(configServiceDto?.ScannerControl))
|
||||
{
|
||||
var configArr = configServiceDto.ScannerControl.Split(',');
|
||||
if (configArr.Contains(areaDto.Code)) { needScanning = true; }
|
||||
}
|
||||
if (needScanning && orderDtos.Any(e => e.IsScanned == false))
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckAreaFirstLightJob],区域[{segment.AreaCode}] 待分拣的订单未扫描校验");
|
||||
continue;//检测下一条线
|
||||
}
|
||||
|
||||
//找到开始灯并点亮开始灯。//暂时没有判定有没有分段内有亮起的灯????
|
||||
var lightDto = lightDtos.FirstOrDefault(e => e.Id == segment.StartLightId);
|
||||
//要判定灯存在并且灯是否在线状态
|
||||
if (lightDto != null && lightDto.IsOnline)
|
||||
{
|
||||
ControlMessage controlMessage = new ControlMessage
|
||||
{
|
||||
Command = 0x01,
|
||||
SectionCount = 1,
|
||||
LightControlMessages = new List<LightControlMessage>
|
||||
{
|
||||
new LightControlMessage()
|
||||
{
|
||||
StartLightNo = Convert.ToByte(lightDto.Sequence, 16),
|
||||
EndLightNo = Convert.ToByte(lightDto.Sequence, 16),
|
||||
State = configServiceDto?.StartLightColour is null ? Extend.Master.Utility.SetLightYellow() : Extend.Master.Utility.SetLightColour(configServiceDto.StartLightColour,false),
|
||||
Led = 0
|
||||
}
|
||||
},
|
||||
State = configServiceDto?.StartLightColour is null ? Extend.Master.Utility.SetLightYellow() : Extend.Master.Utility.SetLightColour(configServiceDto.StartLightColour, false),
|
||||
TaskNo = Utility.Common.GetTaskSn(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")),
|
||||
PublishTime = DateTime.Now
|
||||
};
|
||||
if (ExtendUdpServerService.WaitSendData.ContainsKey(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")))
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData[IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")].Add(controlMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData.TryAdd(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}"), new List<ControlMessage> { controlMessage });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"CheckAreaFirstLightJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Extend.Master;
|
||||
using FASS.Scheduler.Lite.Services.EventBuses.Model;
|
||||
using FASS.Scheduler.Services.Extends.Demo;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Base;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using Quartz;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 控制区域内结束灯亮Job
|
||||
* 频次:1s轮询一次
|
||||
* 逻辑:
|
||||
* 检索正在执行的合单任务、以及当前正在的区域分段
|
||||
* 区域分段物料灯 IsMaterialLightOff = true IsEndLightOn = false
|
||||
* 物料灯拍完,结束灯未点亮时=> 点亮结束灯,同时更新 IsEndLightOn = true
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class CheckEndLightJob : IJob
|
||||
{
|
||||
private readonly ILogger<CheckEndLightJob> _logger;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly ITaskService _taskService;
|
||||
private readonly ITaskSegmentService _taskSegmentService;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public CheckEndLightJob(
|
||||
ILogger<CheckEndLightJob> logger,
|
||||
ILightService lightService,
|
||||
ITaskService taskService,
|
||||
ITaskSegmentService taskSegmentService,
|
||||
IDataService dataService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lightService = lightService;
|
||||
_taskService = taskService;
|
||||
_taskSegmentService = taskSegmentService;
|
||||
_dataService = dataService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckEndLightJob],CurrentTime: {DateTime.Now}");
|
||||
var taskDtos = _taskService.ToList(e => e.State != OrderConst.State.Completed);//正在执行的分拣任务
|
||||
if(taskDtos is null || taskDtos.Count == 0)
|
||||
return Task.CompletedTask;//没有执行中的任务,直接返回
|
||||
var taskSegmentDtos = _taskSegmentService.ToList(e => e.State != OrderConst.State.Completed);//所有分段任务
|
||||
var lightDtos = _lightService.ToList(e => e.IsEnable && e.Type == LightConst.Type.EndLight);
|
||||
foreach (var item in taskDtos)
|
||||
{
|
||||
//获取当前在执行的分段
|
||||
var taskSegmentDto = taskSegmentDtos.Where(e => e.TaskId == item.Id && item.CurSegmentCode == e.SegmentCode).FirstOrDefault();
|
||||
if (taskSegmentDto is null)
|
||||
continue;
|
||||
//子任务物料灯灯全部按下,结束未亮起
|
||||
if (taskSegmentDto.IsMaterialLightOff && !taskSegmentDto.IsEndLightOn)
|
||||
{
|
||||
var configServiceDto = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);//获取灯颜色
|
||||
var lightDto = lightDtos.FirstOrDefault(e => e.Id == taskSegmentDto.EndLightId);
|
||||
if (lightDto is null)
|
||||
continue;
|
||||
ControlMessage controlMessage = new ControlMessage
|
||||
{
|
||||
Command = 0x01,
|
||||
SectionCount = 1,
|
||||
LightControlMessages = new List<LightControlMessage>
|
||||
{
|
||||
new LightControlMessage()
|
||||
{
|
||||
StartLightNo = Convert.ToByte(lightDto.Sequence, 16),
|
||||
EndLightNo = Convert.ToByte(lightDto.Sequence, 16),
|
||||
State = configServiceDto?.EndLightColour is null ? Extend.Master.Utility.SetLightYellow() : Extend.Master.Utility.SetLightColour(configServiceDto.EndLightColour,false),
|
||||
Led = 0
|
||||
}
|
||||
},
|
||||
State = configServiceDto?.EndLightColour is null ? Extend.Master.Utility.SetLightYellow() : Extend.Master.Utility.SetLightColour(configServiceDto.EndLightColour, false),
|
||||
TaskNo = Utility.Common.GetTaskSn(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")),
|
||||
PublishTime = DateTime.Now
|
||||
};
|
||||
if (ExtendUdpServerService.WaitSendData.ContainsKey(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")))
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData[IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")].Add(controlMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData.TryAdd(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}"), new List<ControlMessage> { controlMessage });
|
||||
}
|
||||
//更新库状态IsEndLightOn = true
|
||||
_taskSegmentService.Repository.ExecuteUpdate(e => e.Id == taskSegmentDto.Id, s => s.SetProperty(b => b.IsEndLightOn, true));
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Extend.Master;
|
||||
using FASS.Scheduler.Lite.Services.EventBuses.Model;
|
||||
using FASS.Scheduler.Services.Extends.Demo;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using Quartz;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 控制区域内物料灯亮Job
|
||||
* 频次:1s轮询一次
|
||||
* 逻辑:
|
||||
* 检索正在执行的合单任务、以及当前正在的区域分段
|
||||
* 正在执行的分段任务:开始灯IsStartLightOn = true IsStartLightOff = true [开始灯已被点亮、且开始灯已被拍灭]
|
||||
* 物料灯IsMaterialLightOn = false [物料灯未被点亮]
|
||||
* 满足条件后:
|
||||
* 下发物料灯点亮指令(绿灯、数码管显示数值)
|
||||
* 更新物料灯状态物料灯 IsMaterialLightAlreadyOn = true
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class CheckMaterialLightJob : IJob
|
||||
{
|
||||
private readonly ILogger<CheckMaterialLightJob> _logger;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly ITaskService _taskService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly ITaskSegmentService _taskSegmentService;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public CheckMaterialLightJob(
|
||||
ILogger<CheckMaterialLightJob> logger,
|
||||
ILightService lightService,
|
||||
ITaskService taskService,
|
||||
IOrderService orderService,
|
||||
ITaskSegmentService taskSegmentService,
|
||||
IDataService dataService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lightService = lightService;
|
||||
_taskService = taskService;
|
||||
_orderService = orderService;
|
||||
_taskSegmentService = taskSegmentService;
|
||||
_dataService = dataService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckMaterialLightJob],CurrentTime: {DateTime.Now}");
|
||||
var taskDtos = _taskService.ToList(e => e.State != OrderConst.State.Completed);//正在执行的分拣任务
|
||||
var taskSegmentDtos = _taskSegmentService.ToList(e => e.State != OrderConst.State.Completed);//所有分段任务
|
||||
foreach (var item in taskDtos)
|
||||
{
|
||||
//获取当前在执行的分段
|
||||
var taskSegmentDto = taskSegmentDtos.Where(e => e.TaskId == item.Id && item.CurSegmentCode == e.SegmentCode).FirstOrDefault();
|
||||
if (taskSegmentDto is null)
|
||||
continue;
|
||||
if (taskSegmentDto.IsStartLightOn && taskSegmentDto.IsStartLightOff && !taskSegmentDto.IsMaterialLightOn)
|
||||
{
|
||||
var configServiceDto = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);//获取灯颜色
|
||||
//发送物料灯亮灯指令
|
||||
var orderDtos = _orderService.Set().Where(e => e.State == OrderConst.State.Picking && e.CurSegmentCode == taskSegmentDto.SegmentCode && e.IsEnable).OrderBy(e => e.SequenceNo).ToList();//添加排序
|
||||
if (orderDtos is null || orderDtos.Count == 0)
|
||||
continue;
|
||||
//获取批次号,分段号内的订单零件信息【不包括没有匹配到的订单】
|
||||
var orderDetailDtos = _orderService.GetOrderDetailsByBatchNoAndSegment(orderDtos[0].BatchNo!, taskSegmentDto.SegmentCode!).Where(e => e.IsMatchLight == true);
|
||||
if (orderDetailDtos is null || orderDetailDtos.Count() == 0)
|
||||
continue;
|
||||
|
||||
//得到所有用到的灯地址--当前区域
|
||||
var ptlAddrs = orderDetailDtos.Select(e => e.LightCode).Distinct().ToList();
|
||||
List<LightControlMessage> list = new List<LightControlMessage>();
|
||||
//得到当前分段每个灯对应订单的数量综合
|
||||
for (var i = 0; i < ptlAddrs.Count; i++)
|
||||
{
|
||||
var details = orderDetailDtos.Where(e => e.LightCode == ptlAddrs[i]).ToList();//得到当前灯的所有订单明细
|
||||
List<int> sumList = new List<int>();
|
||||
var isMultipleMaterial = false;
|
||||
foreach (var order in orderDtos)
|
||||
{
|
||||
var sumCount = details.Where(e => e.PullNo == order.PullNo).Sum(e => e.Quantity);
|
||||
sumList.Add(sumCount);
|
||||
if (details.Where(e => e.PullNo == order.PullNo).Select(e => e.MaterialCode).Distinct().Count() > 1)
|
||||
{
|
||||
isMultipleMaterial = true;
|
||||
}
|
||||
}
|
||||
var hexString = ptlAddrs[i]?.Substring(ptlAddrs[i]!.Length - 2);//获取灯id
|
||||
var index = Convert.ToInt32(hexString, 16);
|
||||
list.Add(new LightControlMessage
|
||||
{
|
||||
StartLightNo = (byte)index,
|
||||
EndLightNo = (byte)index,
|
||||
State = !isMultipleMaterial ? (configServiceDto?.MaterialLightColour is null ? Extend.Master.Utility.SetLightGreenWithLed() : Extend.Master.Utility.SetLightColour(configServiceDto.MaterialLightColour, true)) :
|
||||
(configServiceDto?.MultipleMaterialLightColour is null ? Extend.Master.Utility.SetLightMagentaWithLed() : Extend.Master.Utility.SetLightColour(configServiceDto.MultipleMaterialLightColour, true)),
|
||||
Led = BitConverter.ToUInt32(Extend.Master.Utility.GetLedBytes(orderDtos.Count, sumList), 0)//根据批次号关联的订单数,得到最大合单数
|
||||
});
|
||||
}
|
||||
if (list.Count == 0)
|
||||
continue;
|
||||
var lightDto = _lightService.Set().FirstOrDefault(e => e.Id == taskSegmentDto.StartLightId);
|
||||
if (lightDto is null)
|
||||
continue;
|
||||
var groups = list.Chunk(60).Select(c => c.ToList()).ToList();//按60个项分指令下发
|
||||
foreach (var group in groups)
|
||||
{
|
||||
ControlMessage controlMessage = new ControlMessage
|
||||
{
|
||||
Command = 0x01,
|
||||
SectionCount = (byte)group.Count,
|
||||
LightControlMessages = group,
|
||||
State = (configServiceDto?.MaterialLightColour is null ? Extend.Master.Utility.SetLightGreenWithLed() : Extend.Master.Utility.SetLightColour(configServiceDto.MaterialLightColour, true)),
|
||||
TaskNo = Utility.Common.GetTaskSn(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")),
|
||||
PublishTime = DateTime.Now
|
||||
};
|
||||
if (ExtendUdpServerService.WaitSendData.ContainsKey(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")))
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData[IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")].Add(controlMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData.TryAdd(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}"), new List<ControlMessage> { controlMessage });
|
||||
}
|
||||
|
||||
}
|
||||
//更新库状态IsStartLightAlreadyOn = true
|
||||
_taskSegmentService.Repository.ExecuteUpdate(e => e.Id == taskSegmentDto.Id, s => s.SetProperty(b => b.IsMaterialLightOn, true));
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Extend.Master;
|
||||
using FASS.Scheduler.Lite.Services.EventBuses.Model;
|
||||
using FASS.Scheduler.Services.Extends.Demo;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Base;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using Quartz;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 控制区域内亮开始灯[非区域起始灯]
|
||||
* 频次:1s轮询一次
|
||||
* 逻辑:
|
||||
* 判定分区开始灯是否点亮:
|
||||
* 如果分段开始灯没有点亮、且开始灯没有拍灭。点亮开始灯
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class CheckStartLightJob : IJob
|
||||
{
|
||||
private readonly ILogger<CheckStartLightJob> _logger;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly ITaskService _taskService;
|
||||
private readonly ITaskSegmentService _taskSegmentService;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public CheckStartLightJob(
|
||||
ILogger<CheckStartLightJob> logger,
|
||||
ILightService lightService,
|
||||
ITaskService taskService,
|
||||
ITaskSegmentService taskSegmentService,
|
||||
IDataService dataService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lightService = lightService;
|
||||
_taskService = taskService;
|
||||
_taskSegmentService = taskSegmentService;
|
||||
_dataService = dataService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckStartLightJob],CurrentTime: {DateTime.Now}");
|
||||
var taskDtos = _taskService.ToList(e => e.State != OrderConst.State.Completed);//正在执行的分拣任务
|
||||
if (taskDtos is null || taskDtos.Count == 0)
|
||||
return Task.CompletedTask;//没有执行中的任务,直接返回
|
||||
var taskSegmentDtos = _taskSegmentService.ToList(e => e.State != OrderConst.State.Completed);//所有分段任务
|
||||
var lightDtos = _lightService.ToList(e => e.IsEnable && e.Type == LightConst.Type.StartLight);
|
||||
foreach (var item in taskDtos)
|
||||
{
|
||||
//获取当前在执行的分段
|
||||
var taskSegmentDto = taskSegmentDtos.Where(e => e.TaskId == item.Id && item.CurSegmentCode == e.SegmentCode).FirstOrDefault();
|
||||
if (taskSegmentDto is null)
|
||||
continue;
|
||||
//分段开始灯没有点亮,分段开始灯也没有拍灭
|
||||
if (!taskSegmentDto.IsStartLightOn && !taskSegmentDto.IsStartLightOff)
|
||||
{
|
||||
var configServiceDto = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);//获取灯颜色
|
||||
var lightDto = lightDtos.FirstOrDefault(e => e.Id == taskSegmentDto.StartLightId);
|
||||
if (lightDto is null)
|
||||
continue;
|
||||
ControlMessage controlMessage = new ControlMessage
|
||||
{
|
||||
Command = 0x01,
|
||||
SectionCount = 1,
|
||||
LightControlMessages = new List<LightControlMessage>
|
||||
{
|
||||
new LightControlMessage()
|
||||
{
|
||||
StartLightNo = Convert.ToByte(lightDto.Sequence, 16),
|
||||
EndLightNo = Convert.ToByte(lightDto.Sequence, 16),
|
||||
State = configServiceDto?.StartLightColour is null ? Extend.Master.Utility.SetLightYellow() : Extend.Master.Utility.SetLightColour(configServiceDto.StartLightColour,false),
|
||||
Led = 0
|
||||
}
|
||||
},
|
||||
State = configServiceDto?.StartLightColour is null ? Extend.Master.Utility.SetLightYellow() : Extend.Master.Utility.SetLightColour(configServiceDto.StartLightColour, false),
|
||||
TaskNo = Utility.Common.GetTaskSn(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")),
|
||||
PublishTime = DateTime.Now
|
||||
};
|
||||
if (ExtendUdpServerService.WaitSendData.ContainsKey(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")))
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData[IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}")].Add(controlMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData.TryAdd(IPEndPoint.Parse($"{lightDto.MasterIp}:{lightDto.MasterPort}"), new List<ControlMessage> { controlMessage });
|
||||
}
|
||||
//IsStartLightOn = true
|
||||
_taskSegmentService.Repository.ExecuteUpdate(e => e.Id == taskSegmentDto.Id, s => s.SetProperty(b => b.IsStartLightOn, true));
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Dtos.Base;
|
||||
using FASS.Service.Lite.Dtos.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 控制区域分段切换Job
|
||||
* 频次:1s轮询一次
|
||||
* 当前执行的分段结束灯拍灭
|
||||
* 1、无下一个分段,直接结束分拣任务
|
||||
* 2、有下一个分段时
|
||||
* 2.1) 下一个分段无正在分拣的任务,切换道下一个分段任务
|
||||
* 2.2)下一个分段有正在分拣的任务,等待分拣任务完成
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class CheckSwitchSegmentJob : IJob
|
||||
{
|
||||
private readonly ILogger<CheckSwitchSegmentJob> _logger;
|
||||
private readonly ITaskService _taskService;
|
||||
private readonly ITaskSegmentService _taskSegmentService;
|
||||
private readonly ISegmentService _segmentService;
|
||||
private readonly IAreaService _areaService;
|
||||
public List<SegmentDto> SegmentDtos = null!;
|
||||
public List<AreaDto> AreaDtos = null!;
|
||||
|
||||
public CheckSwitchSegmentJob(
|
||||
ILogger<CheckSwitchSegmentJob> logger,
|
||||
ITaskService taskService,
|
||||
ITaskSegmentService taskSegmentService,
|
||||
ISegmentService segmentService,
|
||||
IAreaService areaService)
|
||||
{
|
||||
_logger = logger;
|
||||
_taskService = taskService;
|
||||
_taskSegmentService = taskSegmentService;
|
||||
_segmentService = segmentService;
|
||||
_areaService = areaService;
|
||||
SegmentDtos = _segmentService.Set().Where(e => e.IsEnable).ToList();//暂时预加载
|
||||
AreaDtos = _areaService.Set().Where(e => e.IsEnable).ToList(); // 暂时预加载
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[CheckSwitchSegmentJob],CurrentTime: {DateTime.Now}");
|
||||
var allTaskDtos = _taskService.ToList(e => e.State != OrderConst.State.Completed);//正在执行的分拣任务
|
||||
if (!allTaskDtos.Any())
|
||||
return Task.CompletedTask;
|
||||
var allTaskSegmentDtos = _taskSegmentService.ToList(e => allTaskDtos.Select(e => e.Id).Contains(e.TaskId));//所有分段任务
|
||||
foreach (var item in allTaskDtos)
|
||||
{
|
||||
//获取当前在执行的分段
|
||||
var currTaskSegmentDto = allTaskSegmentDtos.Where(e => e.TaskId == item.Id && item.CurSegmentCode == e.SegmentCode).FirstOrDefault();
|
||||
if (currTaskSegmentDto is null)
|
||||
{
|
||||
// 当前任务的 CurrentSegmentCode 未匹配到分段任务,尝试切换到下一个分段或结束任务
|
||||
TrySwitchToNextOrFinish(item, allTaskSegmentDtos, allTaskDtos);
|
||||
continue;
|
||||
}
|
||||
if (currTaskSegmentDto.IsEndLightOn && currTaskSegmentDto.IsEndLightOff)
|
||||
{
|
||||
//分段结束灯已经拍灭,尝试切换到下一个分段或结束任务
|
||||
TrySwitchToNextOrFinish(item, allTaskSegmentDtos, allTaskDtos);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void TrySwitchToNextOrFinish(TaskDto task, IEnumerable<TaskSegmentDto> allTaskSegments, IEnumerable<TaskDto> allTasks)
|
||||
{
|
||||
if (task is null)
|
||||
return;
|
||||
|
||||
var currentSegment = SegmentDtos.FirstOrDefault(a => a.Code == task.CurSegmentCode);
|
||||
if (currentSegment is null)
|
||||
return; // 区域分段信息不存在
|
||||
|
||||
//得到当前任务所有分段任务
|
||||
var curTaskSegments = allTaskSegments.Where(s => s.TaskId == task.Id).ToList();
|
||||
//分段任务关联的分段列表
|
||||
var segments = SegmentDtos
|
||||
.Where(a => a.AreaCode == currentSegment.AreaCode && curTaskSegments.Select(ts => ts.SegmentCode).Contains(a.Code))
|
||||
.ToList();
|
||||
|
||||
// 查找是否存在更后面的分段
|
||||
var nextSegment = segments
|
||||
.Where(a => a.SortNumber > currentSegment.SortNumber)
|
||||
.OrderBy(a => a.SortNumber)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (nextSegment is null)
|
||||
{
|
||||
// 没有下一个分段,结束整个任务
|
||||
_taskService.FinishMergeTask(task.BatchNo);
|
||||
return;
|
||||
}
|
||||
|
||||
// 下一个分段是否已有正在执行的任务(非完成状态且 CurSegmentCode == next.Code)
|
||||
var existsRunningOnNext = allTasks.Any(mt => mt.State != OrderConst.State.Completed && mt.CurSegmentCode == nextSegment.Code);
|
||||
if (!existsRunningOnNext)
|
||||
{
|
||||
_taskService.SwitchMergeTaskSegment(task.BatchNo, nextSegment.Code);
|
||||
}
|
||||
// 如果下一个分段已有任务在执行,则等待该任务完成
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using Common.Frame.Services.Trace.Interfaces;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
using IHisOrderService = FASS.Service.Lite.Services.History.Interfaces.IOrderService;
|
||||
using ITaskService = FASS.Service.Lite.Services.Data.Interfaces.ITaskService;
|
||||
using IHisTaskService = FASS.Service.Lite.Services.History.Interfaces.ITaskService;
|
||||
using ILesOrderService = FASS.Service.Lite.Services.Interface.Interfaces.IOrderService;
|
||||
using IOrderReportService = FASS.Service.Lite.Services.Interface.Interfaces.IOrderReportService;
|
||||
using IAgvControlService = FASS.Service.Lite.Services.Interface.Interfaces.IAgvControlService;
|
||||
using IMomPassPointService = FASS.Service.Lite.Services.Interface.Interfaces.IPassPointService;
|
||||
using ILesMaterialService = FASS.Service.Lite.Services.Interface.Interfaces.IMaterialService;
|
||||
using ILesMaterialStorageService = FASS.Service.Lite.Services.Interface.Interfaces.IMaterialStorageService;
|
||||
using ILesMaterialInventoryService = FASS.Service.Lite.Services.Interface.Interfaces.IMaterialInventoryService;
|
||||
using FASS.Service.Lite.Dtos.Setting;
|
||||
using Common.NETCore.Extensions;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 归档服务
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class DataArchivingJob : IJob
|
||||
{
|
||||
private readonly ILogger<DataArchivingJob> _logger;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IUserActionService _userActionService;
|
||||
private readonly IDataAuditService _dataAuditService;
|
||||
private readonly IDiaryService _diaryService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IHisOrderService _historyOrderService;
|
||||
private readonly IPassPointService _passPointService;
|
||||
private readonly IOrderPrintService _orderPrintService;
|
||||
private readonly ITaskService _taskService;
|
||||
private readonly IHisTaskService _hisTaskService;
|
||||
private readonly ILesOrderService _lesOrderService;
|
||||
private readonly IOrderReportService _orderReportService;
|
||||
private readonly IAgvControlService _agvControlService;
|
||||
private readonly IMomPassPointService _momPassPointService;
|
||||
private readonly ILesMaterialService _lesMaterialService;
|
||||
private readonly ILesMaterialStorageService _lesMaterialStorageService;
|
||||
private readonly ILesMaterialInventoryService _lesMaterialInventoryService;
|
||||
|
||||
public DataArchivingJob(
|
||||
ILogger<DataArchivingJob> logger,
|
||||
IDataService dataService,
|
||||
IUserActionService userActionService,
|
||||
IDataAuditService dataAuditService,
|
||||
IDiaryService diaryService,
|
||||
IAlarmService alarmService,
|
||||
IOrderService orderService,
|
||||
IHisOrderService historyOrderService,
|
||||
IPassPointService passPointService,
|
||||
IOrderPrintService orderPrintService,
|
||||
ITaskService taskService,
|
||||
IHisTaskService hisTaskService,
|
||||
ILesOrderService lesOrderService,
|
||||
IOrderReportService orderReportService,
|
||||
IAgvControlService agvControlService,
|
||||
IMomPassPointService momPassPointService,
|
||||
ILesMaterialService lesMaterialService,
|
||||
ILesMaterialStorageService lesMaterialStorageService,
|
||||
ILesMaterialInventoryService lesMaterialInventoryService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataService = dataService;
|
||||
_userActionService = userActionService;
|
||||
_dataAuditService = dataAuditService;
|
||||
_diaryService = diaryService;
|
||||
_alarmService = alarmService;
|
||||
_orderService = orderService;
|
||||
_historyOrderService = historyOrderService;
|
||||
_passPointService = passPointService;
|
||||
_orderPrintService = orderPrintService;
|
||||
_taskService = taskService;
|
||||
_hisTaskService = hisTaskService;
|
||||
_lesOrderService = lesOrderService;
|
||||
_orderReportService = orderReportService;
|
||||
_agvControlService = agvControlService;
|
||||
_momPassPointService = momPassPointService;
|
||||
_lesMaterialService = lesMaterialService;
|
||||
_lesMaterialStorageService = lesMaterialStorageService;
|
||||
_lesMaterialInventoryService = lesMaterialInventoryService;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[DataArchivingJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var configDataDto = await _dataService.GetConfigToDtoAsync<ConfigDataDto>(CacheKey.Setting.ConfigData);
|
||||
await _userActionService.DeleteDayAsync(day: configDataDto?.UserLogDayLimit is null ? 30 : configDataDto.UserLogDayLimit.ToDouble());//用户日志
|
||||
await _dataAuditService.DeleteDayAsync(day: configDataDto?.AuditLogDayLimit is null ? 30 : configDataDto.AuditLogDayLimit.ToDouble());//审计日志
|
||||
await _diaryService.DeleteDayAsync(day: configDataDto?.DiaryDayLimit is null ? 30 : configDataDto.DiaryDayLimit.ToDouble());//接口日志
|
||||
await _alarmService.DeleteDayAsync(day: configDataDto?.AlarmDayLimit is null ? 30 : configDataDto.AlarmDayLimit.ToDouble());//告警日志
|
||||
await _orderService.DeleteDayAsync(day: configDataDto?.OrderDayLimit is null ? 30 : configDataDto.OrderDayLimit.ToDouble());//实时订单记录
|
||||
await _historyOrderService.DeleteDayAsync(day: configDataDto?.HisOrderDayLimit is null ? 30 : configDataDto.HisOrderDayLimit.ToDouble());//历史订单记录
|
||||
await _passPointService.DeleteDayAsync(day: configDataDto?.PassPointDayLimit is null ? 30 : configDataDto.PassPointDayLimit.ToDouble());//过点记录
|
||||
await _taskService.DeleteDayAsync(day: configDataDto?.TaskDayLimit is null ? 90 : configDataDto.TaskDayLimit.ToDouble());//任务记录
|
||||
await _hisTaskService.DeleteDayAsync(day: configDataDto?.HisTaskDayLimit is null ? 30 : configDataDto.HisTaskDayLimit.ToDouble());//历史任务记录
|
||||
await _orderPrintService.DeleteDayAsync(day: configDataDto?.PrintRecordLimit is null ? 1 : configDataDto.PrintRecordLimit.ToDouble());//拣配单打印记录
|
||||
await _lesOrderService.DeleteDayAsync(day: configDataDto?.LesOrderDayLimit is null ? 30 : configDataDto.LesOrderDayLimit.ToDouble());//配载单记录
|
||||
await _orderReportService.DeleteDayAsync(day: configDataDto?.OrderReportDayLimit is null ? 30 : configDataDto.OrderReportDayLimit.ToDouble());//回调记录
|
||||
await _agvControlService.DeleteDayAsync(day: configDataDto?.AgvControlDayLimit is null ? 2 : configDataDto.AgvControlDayLimit.ToDouble());//AGV放行记录
|
||||
await _momPassPointService.DeleteDayAsync(day: configDataDto?.InterfaceDataDayLimit is null ? 30 : configDataDto.InterfaceDataDayLimit.ToDouble());//接口过点记录
|
||||
await _lesMaterialService.DeleteDayAsync(day: configDataDto?.InterfaceDataDayLimit is null ? 30 : configDataDto.InterfaceDataDayLimit.ToDouble());//接口物料信息
|
||||
await _lesMaterialStorageService.DeleteDayAsync(day: configDataDto?.InterfaceDataDayLimit is null ? 30 : configDataDto.InterfaceDataDayLimit.ToDouble());//接口物料库位关系
|
||||
await _lesMaterialInventoryService.DeleteDayAsync(day: configDataDto?.InterfaceDataDayLimit is null ? 30 : configDataDto.InterfaceDataDayLimit.ToDouble());//接口物料库存记录
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"DataArchivingJob ex=>{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Models.Record;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 判定灯离线服务
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class LightOfflineWarningJob : IJob
|
||||
{
|
||||
private readonly ILogger<LightOfflineWarningJob> _logger;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly IMasterService _masterService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly ILightStorageService _lightStorageService;
|
||||
|
||||
public LightOfflineWarningJob(
|
||||
ILogger<LightOfflineWarningJob> logger,
|
||||
ILightService lightService,
|
||||
IMasterService masterService,
|
||||
IAlarmService alarmService,
|
||||
IStorageService storageService,
|
||||
ILightStorageService lightStorageService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lightService = lightService;
|
||||
_masterService = masterService;
|
||||
_alarmService = alarmService;
|
||||
_storageService = storageService;
|
||||
_lightStorageService = lightStorageService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[LightOfflineWarningJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var lightDtos = _lightService.ToList(e => e.IsEnable);
|
||||
var masterDtos = _masterService.ToList(e => e.IsEnable);//主控在线时,判定灯的状态
|
||||
List<Alarm> alarms = new List<Alarm>();
|
||||
foreach (var master in masterDtos.Where(e => e.IsOnline == false).ToList())
|
||||
{
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.ControllerOffline,
|
||||
Code = master.IpAddress,
|
||||
Message = $"控制器[{master.Name}],IP[{master.IpAddress}]离线"
|
||||
};
|
||||
alarms.Add(model);
|
||||
}
|
||||
if (alarms.Count > 0)
|
||||
{
|
||||
_alarmService.AddModels(alarms, 300);
|
||||
}
|
||||
|
||||
alarms.Clear();//清空
|
||||
//主控在线时,存在灯离线
|
||||
var onlineMasters = masterDtos.Where(e => e.IsOnline == true).ToList();
|
||||
var offlineLights = lightDtos.Where(e => (e.IsOnline == false || (e.IsOnline == true && e.UpdateAt < DateTime.Now.AddSeconds(-5))) && onlineMasters.Select(e1 => e1.Id).Contains(e.MasterId)).ToList();
|
||||
//没有离线灯直接返回
|
||||
if (offlineLights is null || offlineLights.Count == 0) return Task.CompletedTask;
|
||||
//存在离线灯加载库位信息、库位灯绑定关系
|
||||
var storageDtos = _storageService.Set().Where(e => e.IsEnable).ToList();
|
||||
var lightStorageDtos = _lightStorageService.Set().Where(e => e.IsEnable).ToList();
|
||||
foreach (var master in onlineMasters)
|
||||
{
|
||||
var lights = lightDtos.Where(e => e.MasterId == master.IpAddress).ToList();//获取主控下所有的灯(非禁用)
|
||||
var offlines = lights.Where(e => e.IsOnline == false || (e.IsOnline == true && e.UpdateAt < DateTime.Now.AddSeconds(-5))).ToList();
|
||||
if (offlines.Count > 0)
|
||||
{
|
||||
foreach (var item in offlines)
|
||||
{
|
||||
var strorageDto = _lightService.GetStorageByLight(storageDtos, lightStorageDtos, item);
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.LightOffline,
|
||||
Code = item.Code,
|
||||
Message = $"灯编码[{item.Code}]离线,库位编号[{strorageDto?.Code}]"
|
||||
};
|
||||
alarms.Add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alarms.Count > 0)
|
||||
{
|
||||
_alarmService.AddModels(alarms, 300);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"LightOfflineWarningJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using FASS.Service.Lite.Consts.Base;
|
||||
using FASS.Service.Lite.Dtos.Base;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using Quartz;
|
||||
using ILesMaterialStorageService = FASS.Service.Lite.Services.Interface.Interfaces.IMaterialStorageService;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 同步零件库位关系Job
|
||||
* 频次:1min检索一次,存在新数据时同步一次
|
||||
* 业务逻辑:
|
||||
*
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class MaterialStorageUpdataJob : IJob
|
||||
{
|
||||
private readonly ILogger<MaterialStorageUpdataJob> _logger;
|
||||
private readonly ILesMaterialStorageService _lesMaterialStorageService;
|
||||
private readonly IMaterialService _materialService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IMaterialStorageService _materialStorageService;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly ILightStorageService _lightStorageService;
|
||||
private readonly ILightMaterialService _lightMaterialService;
|
||||
|
||||
|
||||
public MaterialStorageUpdataJob(
|
||||
ILogger<MaterialStorageUpdataJob> logger,
|
||||
ILesMaterialStorageService lesMaterialStorageService,
|
||||
IMaterialService materialService,
|
||||
IStorageService storageService,
|
||||
IMaterialStorageService materialStorageService,
|
||||
ILightService lightService,
|
||||
ILightStorageService lightStorageService,
|
||||
ILightMaterialService lightMaterialService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lesMaterialStorageService = lesMaterialStorageService;
|
||||
_materialService = materialService;
|
||||
_storageService = storageService;
|
||||
_materialStorageService = materialStorageService;
|
||||
_lightService = lightService;
|
||||
_lightStorageService = lightStorageService;
|
||||
_lightMaterialService = lightMaterialService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[MaterialStorageUpdataJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
//1、加载所有待同步的零件库位信息
|
||||
var lesMaterialStorageDtos = _lesMaterialStorageService.Set().Where(e => e.IsEnable).ToList();
|
||||
if (lesMaterialStorageDtos is null || lesMaterialStorageDtos.Count == 0)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var materialStorageDtos = _materialStorageService.Set().Where(e => e.IsEnable).ToList();
|
||||
var materialDtos = _materialService.Set().Where(e => e.IsEnable).ToList();
|
||||
var storageDtos = _storageService.Set().Where(e => e.IsEnable).ToList();
|
||||
var lightDtos = _lightService.Set().Where(e => e.IsEnable).ToList();
|
||||
var lightStorageDtos = _lightStorageService.Set().Where(e => e.IsEnable).ToList();
|
||||
var lightMaterialDtos = _lightMaterialService.Set().Where(e => e.IsEnable).ToList();
|
||||
var addRelDtos = new List<MaterialStorageDto>();
|
||||
var deleteRelDtos = new List<MaterialStorageDto>();
|
||||
var addLightRelDtos = new List<LightMaterialDto>();
|
||||
var deleteLightRelDtos = new List<LightMaterialDto>();
|
||||
//2、按库位分组
|
||||
var storageGroups = lesMaterialStorageDtos.GroupBy(e => e.StorageCode);
|
||||
foreach (var group in storageGroups)
|
||||
{
|
||||
if (materialStorageDtos.Any(e => e.StorageCode == group.Key))
|
||||
{
|
||||
//库位编码存在
|
||||
var oldRel = materialStorageDtos.Where(e => e.StorageCode == group.Key).ToList();
|
||||
var newRel = group.ToList();
|
||||
//存在新增
|
||||
var oldCodes = new HashSet<string>(oldRel.Select(item => item.MaterialCode!));
|
||||
var addRels = newRel.Where(item => !oldCodes.Contains(item.MaterialCode)).ToList();//新关系中存在,老关系中不存在
|
||||
//存在删除
|
||||
var newCodes = new HashSet<string>(newRel.Select(item => item.MaterialCode!));
|
||||
var deleteRels = oldRel.Where(item => !newCodes.Contains(item.MaterialCode!)).ToList();//新关系中不存在,老关系中存在
|
||||
if (addRels.Count > 0)
|
||||
{
|
||||
foreach (var item in addRels)
|
||||
{
|
||||
var materialStorageAddDto = new MaterialStorageDto
|
||||
{
|
||||
MaterialId = materialDtos.FirstOrDefault(e => e.Code == item.MaterialCode)?.Id ?? "",
|
||||
StorageId = storageDtos.FirstOrDefault(e => e.Code == item.StorageCode)?.Id ?? "",
|
||||
State = InventoryConst.State.Default
|
||||
};
|
||||
var lightMaterialAddDto = new LightMaterialDto
|
||||
{
|
||||
MaterialId = materialDtos.FirstOrDefault(e => e.Code == item.MaterialCode)?.Id ?? "",
|
||||
LightId = _lightService.GetLightByStorageCode(lightDtos, storageDtos, lightStorageDtos, item.StorageCode)?.Id ?? ""
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(materialStorageAddDto.MaterialId) && !string.IsNullOrEmpty(materialStorageAddDto.StorageId))
|
||||
{
|
||||
if (!addRelDtos.Any(e => e.MaterialId == materialStorageAddDto.MaterialId
|
||||
&& e.StorageId == materialStorageAddDto.StorageId))
|
||||
{
|
||||
addRelDtos.Add(materialStorageAddDto);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(lightMaterialAddDto.MaterialId) && !string.IsNullOrEmpty(lightMaterialAddDto.LightId))
|
||||
{
|
||||
if (!addLightRelDtos.Any(e => e.MaterialId == lightMaterialAddDto.MaterialId
|
||||
&& e.LightId == lightMaterialAddDto.LightId))
|
||||
{
|
||||
addLightRelDtos.Add(lightMaterialAddDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleteRels.Count > 0)
|
||||
{
|
||||
deleteRelDtos.AddRange(deleteRels);
|
||||
foreach (var item in deleteRels)
|
||||
{
|
||||
var lightMaterialAddDto = new LightMaterialDto
|
||||
{
|
||||
MaterialId = materialDtos.FirstOrDefault(e => e.Code == item.MaterialCode)?.Id ?? "",
|
||||
LightId = _lightService.GetLightByStorageCode(lightDtos, storageDtos, lightStorageDtos, item.StorageCode)?.Id ?? ""
|
||||
};
|
||||
if (!string.IsNullOrEmpty(lightMaterialAddDto.MaterialId) && !string.IsNullOrEmpty(lightMaterialAddDto.LightId))
|
||||
{
|
||||
var dto = lightMaterialDtos.FirstOrDefault(e => e.LightId == lightMaterialAddDto.LightId && e.MaterialId == lightMaterialAddDto.MaterialId);
|
||||
if (dto is not null) deleteLightRelDtos.Add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//库位编码不存在,新增库位与零件关系
|
||||
foreach (var item in group)
|
||||
{
|
||||
var materialStorageAddDto = new MaterialStorageDto
|
||||
{
|
||||
MaterialId = materialDtos.FirstOrDefault(e => e.Code == item.MaterialCode)?.Id ?? "",
|
||||
StorageId = storageDtos.FirstOrDefault(e => e.Code == item.StorageCode)?.Id ?? "",
|
||||
State = InventoryConst.State.Default
|
||||
};
|
||||
if (!string.IsNullOrEmpty(materialStorageAddDto.MaterialId) && !string.IsNullOrEmpty(materialStorageAddDto.StorageId))
|
||||
{
|
||||
if (!addRelDtos.Any(e => e.MaterialId == materialStorageAddDto.MaterialId
|
||||
&& e.StorageId == materialStorageAddDto.StorageId))
|
||||
{
|
||||
addRelDtos.Add(materialStorageAddDto);
|
||||
}
|
||||
}
|
||||
var lightMaterialAddDto = new LightMaterialDto
|
||||
{
|
||||
MaterialId = materialDtos.FirstOrDefault(e => e.Code == item.MaterialCode)?.Id ?? "",
|
||||
LightId = _lightService.GetLightByStorageCode(lightDtos, storageDtos, lightStorageDtos, item.StorageCode)?.Id ?? ""
|
||||
};
|
||||
if (!string.IsNullOrEmpty(lightMaterialAddDto.MaterialId) && !string.IsNullOrEmpty(lightMaterialAddDto.LightId))
|
||||
{
|
||||
if (!addLightRelDtos.Any(e => e.MaterialId == lightMaterialAddDto.MaterialId
|
||||
&& e.LightId == lightMaterialAddDto.LightId))
|
||||
{
|
||||
addLightRelDtos.Add(lightMaterialAddDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_lesMaterialStorageService.UpdateMaterialStorages(lesMaterialStorageDtos, deleteRelDtos, addRelDtos, deleteLightRelDtos, addLightRelDtos);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"MaterialStorageUpdataJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using FASS.Service.Lite.Consts.Base;
|
||||
using FASS.Service.Lite.Dtos.Base;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using Quartz;
|
||||
using ILesMaterialService = FASS.Service.Lite.Services.Interface.Interfaces.IMaterialService;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 同步零件主数据信息Job
|
||||
* 频次:1min检索一次,存在新数据时同步一次
|
||||
* 业务逻辑:
|
||||
* 1、加载所有未处理的接口零件信息,不存在新数据直接返回
|
||||
* 2、获取删除标记的零件信息
|
||||
* 3、获取新增零件信息
|
||||
* 4、更新物料接口表记录状态
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class MaterialUpdataJob : IJob
|
||||
{
|
||||
private readonly ILogger<MaterialUpdataJob> _logger;
|
||||
private readonly ILesMaterialService _lesMaterialService;
|
||||
private readonly IMaterialService _materialService;
|
||||
|
||||
public MaterialUpdataJob(
|
||||
ILogger<MaterialUpdataJob> logger,
|
||||
ILesMaterialService lesMaterialService,
|
||||
IMaterialService materialService)
|
||||
{
|
||||
_logger = logger;
|
||||
_lesMaterialService = lesMaterialService;
|
||||
_materialService = materialService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[MaterialUpdataJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
//1、加载所有未处理的接口零件信息
|
||||
var lesMaterialDtos = _lesMaterialService.Set().Where(e => e.IsEnable).ToList();
|
||||
if (lesMaterialDtos is null || lesMaterialDtos.Count == 0)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var materialDtos = _materialService.Set().Where(e => e.IsEnable).ToList();
|
||||
//2、获取删除标记的物料信息
|
||||
var materialDelDtos = materialDtos.Where(e => lesMaterialDtos.Where(e => e.IsDelete).Select(a => a.Code).Contains(e.Code)).ToList();
|
||||
//3、获取新增的物料信息
|
||||
var curMaterialCodes = new HashSet<string>(materialDtos.Select(item => item.Code));
|
||||
var materialLesAddDtos = lesMaterialDtos.Where(item => item.IsDelete == false && !curMaterialCodes.Contains(item.Code)).ToList();
|
||||
var materialAddDtos = new List<MaterialDto>();
|
||||
if (materialLesAddDtos.Count > 0)
|
||||
{
|
||||
foreach (var item in materialLesAddDtos)
|
||||
{
|
||||
var materialAddDto = new MaterialDto
|
||||
{
|
||||
Code = item.Code,
|
||||
Name = item.Name,
|
||||
Type = item.Type,
|
||||
State = MaterialConst.State.Default,
|
||||
Spec = item.Spec,
|
||||
Unit = item.Unit,
|
||||
FactoryCode = item.FactoryCode
|
||||
};
|
||||
materialAddDtos.Add(materialAddDto);
|
||||
}
|
||||
}
|
||||
//4、更新物料接口表记录状态
|
||||
_lesMaterialService.UpdateMaterials(lesMaterialDtos, materialDelDtos, materialAddDtos);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"PartUpdateJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
using Common.Frame.Dtos.Frame;
|
||||
using Common.Frame.Services.Frame.Interfaces;
|
||||
using Common.NETCore;
|
||||
using FASS.Scheduler.Lite.Utility;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Service.Dtos.Record;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Dtos.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using FASS.Service.Models.Record;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
using ILesOrderService = FASS.Service.Lite.Services.Interface.Interfaces.IOrderService;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 订单组单Job
|
||||
* 频次:500ms检索一次
|
||||
* 业务逻辑:
|
||||
*
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class OrderPackagingJob : IJob
|
||||
{
|
||||
private readonly ILogger<OrderPackagingJob> _logger;
|
||||
private readonly IPassPointService _passPointService;
|
||||
private readonly ILesOrderService _lesOrderService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly ILightService _lightService;
|
||||
private readonly ILightStorageService _lightStorageService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly AppSettings _appSettings;
|
||||
private readonly IAreaService _areaService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
|
||||
public OrderPackagingJob(
|
||||
ILogger<OrderPackagingJob> logger,
|
||||
IPassPointService passPointService,
|
||||
ILesOrderService lesOrderService,
|
||||
IStorageService storageService,
|
||||
ILightService lightService,
|
||||
ILightStorageService lightStorageService,
|
||||
IOrderService orderService,
|
||||
IConfigService configService,
|
||||
AppSettings appSettings,
|
||||
IAreaService areaService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_logger = logger;
|
||||
_passPointService = passPointService;
|
||||
_lesOrderService = lesOrderService;
|
||||
_storageService = storageService;
|
||||
_lightService = lightService;
|
||||
_lightStorageService = lightStorageService;
|
||||
_orderService = orderService;
|
||||
_configService = configService;
|
||||
_appSettings = appSettings;
|
||||
_areaService = areaService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[OrderPackagingJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
//1、获取最新的过点数据,和系统中配置的最后过点顺序号进行对比,判断是否有新的过点数据需要处理
|
||||
var configDtos = _configService.ToList(e => e.IsEnable);
|
||||
var seqConfigDto = configDtos.FirstOrDefault(e => e.Key =="LastMomSequenceNo");
|
||||
if (seqConfigDto is null)
|
||||
{
|
||||
_logger.LogError("ServiceName:[OrderPackagingJob],未设置过点顺序号初始值");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var ruleConfigDto = configDtos.FirstOrDefault(e => e.Key =="SequenceNoCaptureIndex");//流水号截取索引
|
||||
if (ruleConfigDto is null || string.IsNullOrEmpty(ruleConfigDto.Value))
|
||||
{
|
||||
_logger.LogError("ServiceName:[OrderPackagingJob],未设置过点截取规则的起始值");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!int.TryParse(ruleConfigDto.Value, out var indexValue))
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPackagingJob],系统设置的过点规则[{ruleConfigDto.Value}]不正确");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (seqConfigDto.Value!.Length <= indexValue)
|
||||
{
|
||||
//至少是大于规则值的长度
|
||||
_logger.LogError($"ServiceName:[OrderPackagingJob],系统配置的初始过点顺序号[{seqConfigDto.Value}]不正确");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var seqNo = int.Parse(seqConfigDto.Value!.Substring(indexValue).TrimStart('0').Length == 0 ? "0" : seqConfigDto.Value!.Substring(indexValue).TrimStart('0'));
|
||||
var passPointDto = _passPointService.ToList(e => e.State == PassPointConst.State.Pending).Where(e => seqNo < int.Parse(e.SequenceNo.Substring(indexValue).TrimStart('0'))).OrderBy(e => e.SequenceNo).FirstOrDefault();//过点顺序号大于最后过点顺序号
|
||||
if (passPointDto is null)
|
||||
{
|
||||
_logger.LogDebug($"ServiceName:[OrderPackagingJob],没有待处理的过点数据");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//2、得到上一个已处理完车辆的过点顺序号
|
||||
if (seqNo + 1 != int.Parse(passPointDto.SequenceNo.Substring(indexValue).TrimStart('0')))
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPackagingJob], 过点数据不连续 最后过点序号[{seqNo}],当前过点序号[{passPointDto.SequenceNo.Substring(indexValue).TrimStart('0')}],存在跳号情况");
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.SkipSerialNo,
|
||||
Code = passPointDto.SequenceNo,
|
||||
Message = $"过点数据不连续,最后过点序号[{seqConfigDto.Value}],当前过点序号[{passPointDto.SequenceNo}],存在跳号情况"
|
||||
};
|
||||
_alarmService.AddModel(model, 300);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//3、获取PTL自增长流水号
|
||||
var ptlSeqConfigDto = configDtos.FirstOrDefault(e => e.Key == "LastPtlSeqNo");
|
||||
var ptlSeqNo = string.Empty;
|
||||
if (ptlSeqConfigDto is null)
|
||||
{
|
||||
//插入字典值
|
||||
ptlSeqNo = DateTime.Now.ToString("yyyyMMdd") + "00001";
|
||||
_configService.Add(new ConfigDto { Key = "LastPtlSeqNo", Value = ptlSeqNo });
|
||||
}
|
||||
else
|
||||
{
|
||||
ptlSeqNo = ptlSeqConfigDto.Value!;
|
||||
}
|
||||
if (DateTime.Now.ToString("yyyyMMdd") == ptlSeqNo.Substring(0, 8) && ptlSeqConfigDto is not null)
|
||||
{
|
||||
ptlSeqNo = (long.Parse(ptlSeqNo) + 1).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
ptlSeqNo = DateTime.Now.ToString("yyyyMMdd") + "00001";//不是一天,切换都1开始累加
|
||||
}
|
||||
//4、获取订单bom
|
||||
//获取bom清单
|
||||
var bomDtos = _lesOrderService.Set().Where(e => e.PrOrderNo == passPointDto.PrOrderNo && e.IsEnable && e.Vin == passPointDto.Vin).OrderBy(e => e.StorageCode).ToList();//是否要匹配流水号??
|
||||
if (!bomDtos.Any())
|
||||
{
|
||||
_logger.LogInformation("ServiceName:[OrderPackagingJob],没有bom信息");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var addOrderDtos = new List<OrderDto>();
|
||||
var addDetailDtos = new List<OrderDetailDto>();
|
||||
var addOrderPrintDtos = new List<OrderPrintDto>();
|
||||
var addDiaryDtos = new List<DiaryDto>();
|
||||
var addPassPointDtos = new List<PassPointDto>();
|
||||
var storageDtos = _storageService.Set().Where(e => e.IsEnable).ToList();
|
||||
var lightDtos = _lightService.Set().Where(e => e.IsEnable).ToList();
|
||||
var lightStorageDtos = _lightStorageService.Set().Where(e => e.IsEnable).ToList();
|
||||
//按区域分组
|
||||
var areaDtos = _areaService.ToList(e => e.IsEnable);
|
||||
var areas = bomDtos.Select(e => e.AreaCode).Distinct().Order().ToList();
|
||||
foreach (var area in areas)
|
||||
{
|
||||
var areaName = areaDtos.Where(e => e.Code == area).FirstOrDefault()?.Name;
|
||||
var groupBoms = bomDtos.Where(e => e.AreaCode == area).OrderBy(e => e.StorageCode).ToList();
|
||||
//顺序号对,开始打包任务
|
||||
var orderDto = new OrderDto()
|
||||
{
|
||||
PullNo = groupBoms[0].PullNo,
|
||||
SequenceNo = groupBoms[0].SequenceNo,
|
||||
PrOrderNo = groupBoms[0].PrOrderNo,
|
||||
Vin = groupBoms[0].Vin,
|
||||
State = OrderConst.State.WaitPrinting,
|
||||
CarDetail = groupBoms[0].CarDetail,
|
||||
CarModel = passPointDto.CarModel,
|
||||
FactoryCode = groupBoms[0].FactoryCode,
|
||||
AreaCode = groupBoms[0].AreaCode,
|
||||
AreaName = areaName is null ? " " : areaName,
|
||||
PickSequenceNo = ptlSeqNo
|
||||
};
|
||||
var orderPrintDto = new OrderPrintDto
|
||||
{
|
||||
Id = orderDto.Id,
|
||||
PullNo = orderDto.PullNo,
|
||||
Vin = orderDto.Vin,
|
||||
AreaCode = orderDto.AreaCode,
|
||||
PrOrderNo = orderDto.PrOrderNo,
|
||||
SequenceNo = orderDto.SequenceNo,
|
||||
PickSequenceNo = orderDto.PickSequenceNo,
|
||||
IsPrint = false
|
||||
};
|
||||
//打包订单明细
|
||||
var orderDetailDtos = new List<OrderDetailDto>();
|
||||
foreach (var bom in groupBoms)
|
||||
{
|
||||
var lightDto = _lightService.GetLightByStorageCode(lightDtos, storageDtos, lightStorageDtos,bom.StorageCode);
|
||||
if (lightDto is null)
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPackagingJob],库位编号[{bom.StorageCode}] 未匹配到灯地址");
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.UnbindLight,
|
||||
Code = bom.StorageCode,
|
||||
Message = $"库位编号[{bom.StorageCode}]未匹配到灯地址"
|
||||
};
|
||||
_alarmService.AddModel(model, 300);
|
||||
}
|
||||
var orderDetail = new OrderDetailDto
|
||||
{
|
||||
OrderId = orderDto.Id,
|
||||
MaterialCode = bom.MaterialCode,
|
||||
MaterialName = bom.MaterialName!,
|
||||
Quantity = bom.Quantity,
|
||||
AreaCode = bom.AreaCode,
|
||||
PullNo = bom.PullNo,
|
||||
StorageCode = bom.StorageCode,
|
||||
StationCode = bom.StationCode,
|
||||
State = OrderConst.State.WaitPicking,
|
||||
SegmentCode = storageDtos.FirstOrDefault(e => e.Code == bom.StorageCode)?.SegmentCode,
|
||||
LightCode = lightDto?.Code,
|
||||
LightId = lightDto?.Id,
|
||||
IsMatchLight = lightDto is null ? false : true,
|
||||
Remark = lightDto is null ? "未匹配灯" : "",
|
||||
ShelfCode = storageDtos.FirstOrDefault(e => e.Code == bom.StorageCode)?.ShelfCode
|
||||
};
|
||||
orderDetailDtos.Add(orderDetail);
|
||||
}
|
||||
if (orderDetailDtos.Count() == 0)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[OrderPackagingJob], Vin[{passPointDto.Vin}], 订单号[{orderDto.PullNo}]无物料信息");
|
||||
continue;
|
||||
}
|
||||
//是否生成打印文件
|
||||
if (configDtos.Any(e => e.Key == "IsGeneratePrintFiles" && e.Value?.ToLower() == "true"))
|
||||
{
|
||||
var template = Guard.NotNull(_appSettings.PrintPath.Template);
|
||||
if (!Directory.Exists(_appSettings.PrintPath.PrintFilePath))
|
||||
{
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(_appSettings.PrintPath.PrintFilePath);
|
||||
directoryInfo.Create();
|
||||
}
|
||||
if (!Directory.Exists(_appSettings.PrintPath.PicPath))
|
||||
{
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(_appSettings.PrintPath.PicPath);
|
||||
directoryInfo.Create();
|
||||
}
|
||||
string printFilePath = Path.Combine(_appSettings.PrintPath.PrintFilePath, $"{orderDto.PullNo}_{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.docx");
|
||||
string barcodeFilePath = Path.Combine(_appSettings.PrintPath.PicPath, $"{orderDto.PullNo}.png");
|
||||
BarcodeHelper.GenerateBarcodeImage(orderDto.PullNo, barcodeFilePath);//生成图片
|
||||
TemplateReplacerHelper.ReplaceTextInDocument(template, printFilePath, barcodeFilePath, orderDto, orderDetailDtos.OrderBy(e => e.SegmentCode).ThenBy(e => e.StorageCode).ToList());//替换模板并生成打印文件
|
||||
if (!File.Exists(printFilePath))
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[OrderPackagingJob], 打印文件{printFilePath}获取失败");
|
||||
continue;
|
||||
}
|
||||
orderPrintDto.FilePath = printFilePath;//添加打印文件路径
|
||||
var printerDto = _areaService.GetEnablePrinter(orderDto.AreaCode!).FirstOrDefault();
|
||||
if (printerDto is null)
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPackagingJob], 打印文件{printFilePath}获取失败,区域:{orderDto.AreaCode} 无启用的打印机");
|
||||
orderDto.Remark = $"区域[{orderDto.AreaCode}]无启用的打印机";
|
||||
}
|
||||
}
|
||||
addOrderDtos.Add(orderDto);
|
||||
addDetailDtos.AddRange(orderDetailDtos);
|
||||
addOrderPrintDtos.Add(orderPrintDto);
|
||||
}
|
||||
//5、写入拣配单以及拣配单明细
|
||||
if (addOrderDtos.Count > 0 && addDetailDtos.Count > 0)
|
||||
{
|
||||
addPassPointDtos.Add(passPointDto);
|
||||
_orderService.AddOrders(addOrderDtos, addDetailDtos, addDiaryDtos, addPassPointDtos, addOrderPrintDtos);
|
||||
}
|
||||
addOrderDtos.Clear();
|
||||
addDetailDtos.Clear();
|
||||
addDiaryDtos.Clear();
|
||||
addPassPointDtos.Clear();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"OrderPackagingJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using Common.Frame.Dtos.Frame;
|
||||
using Common.Frame.Entities.Frame;
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using Common.NETCore;
|
||||
using FASS.Scheduler.Lite.Utility;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Dtos.Setting;
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Dtos.Data;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using FASS.Service.Models.Record;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 订单打印服务
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class OrderPrintJob : IJob
|
||||
{
|
||||
private readonly ILogger<OrderPrintJob> _logger;
|
||||
private readonly AppSettings _appSettings;
|
||||
private readonly IOrderPrintService _orderPrintService;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IAreaService _areaService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
|
||||
public OrderPrintJob(
|
||||
ILogger<OrderPrintJob> logger,
|
||||
AppSettings appSettings,
|
||||
IOrderPrintService orderPrintService,
|
||||
IDataService dataService,
|
||||
IAreaService areaService,
|
||||
IOrderService orderService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_logger = logger;
|
||||
_appSettings = appSettings;
|
||||
_orderPrintService = orderPrintService;
|
||||
_dataService = dataService;
|
||||
_areaService = areaService;
|
||||
_orderService = orderService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[OrderPrintJob],CurrentTime: {DateTime.Now}");
|
||||
//1、获取所有待打印的订单
|
||||
var orderPrintDtos = _orderPrintService.ToList(e => e.IsPrint == false).OrderBy(e => e.SequenceNo);//查询所有待打印订单
|
||||
if (orderPrintDtos.Count() == 0)
|
||||
{
|
||||
_logger.LogDebug($"ServiceName:[OrderPrintJob],Have No Print Order");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//2、获取存在待打印区间分组统计待打印数量
|
||||
var needPrintAreas = orderPrintDtos.Select(e => e.AreaCode).Distinct().ToList();
|
||||
var remaindOrders = _orderService.Set().Where(e => needPrintAreas.Contains(e.AreaCode!) && e.State == OrderConst.State.WaitPicking).ToList();//待拣选的数量
|
||||
//3、根据区域对打印订单进行分组,并取组内按过点顺序号升序前十
|
||||
var groupOrders = orderPrintDtos.GroupBy(e => e.AreaCode).Select(g => new
|
||||
{
|
||||
areaCode = g.Key,
|
||||
TopItems = g.OrderBy(x => x.SequenceNo).Take(10).ToList(),
|
||||
MinSeqNumber = g.Min(x => x.SequenceNo)
|
||||
});
|
||||
var Configs = _dataService.GetToList<ConfigDto, ConfigEntity>(CacheKey.Setting.Config, e => e.IsEnable).ToList();
|
||||
var retryCount = Configs.Where(e => e.Key == "PrinterRetryCount").FirstOrDefault()?.Value;
|
||||
var printQueueLimit = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService)?.PrintQueueLimit;
|
||||
var maxCompletedPrintOrders = _dataService.GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService)?.MaxCompletedPrintOrders ?? "10";//最少保持10张打印完成
|
||||
List<OrderPrintDto> updateList = new List<OrderPrintDto>();
|
||||
foreach (var groupOrder in groupOrders.OrderBy(e => e.MinSeqNumber))
|
||||
{
|
||||
//当已完成打印且未开始分拣的订单数大于设置的阈值时,不进行打印
|
||||
var areaWaitPickingCount = remaindOrders.Where(e => e.AreaCode == groupOrder.areaCode).Count();
|
||||
if (areaWaitPickingCount >= int.Parse(maxCompletedPrintOrders!))
|
||||
continue;
|
||||
var printerDto = _areaService.GetEnablePrinter(groupOrder.areaCode!).FirstOrDefault();
|
||||
if (printerDto is null)
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPrintJob], 区域:{groupOrder.areaCode} 无启用的打印机");
|
||||
continue;
|
||||
}
|
||||
if (!PrinterHelper.PingPrinter(printerDto.IpAddress!, retryCount is null ? 3 : int.Parse(retryCount)))
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPrintJob], 打印失败,打印机[{printerDto.Name}] IP[{printerDto.IpAddress}] 离线");
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.PrinterOffline,
|
||||
Code = printerDto.Name,
|
||||
Message = $"打印机[{printerDto.Name}]离线"
|
||||
};
|
||||
_alarmService.AddModel(model, 180);
|
||||
continue;
|
||||
}
|
||||
if (!PrinterHelper.CheckPrinterStatus(printerDto.Name!))
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPrintJob], 打印失败,打印机[{printerDto.Name}] IP[{printerDto.IpAddress}] 不可用");
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.PrinterOffline,
|
||||
Code = printerDto.Name,
|
||||
Message = $"打印机[{printerDto.Name}]不可用"
|
||||
};
|
||||
_alarmService.AddModel(model, 180);
|
||||
continue;
|
||||
}
|
||||
var waittingPrintCount = PrinterHelper.GetPrinterJobCount(printerDto.Name);
|
||||
var remindPrintCount = printQueueLimit is null ? 2 : int.Parse(printQueueLimit);
|
||||
if (waittingPrintCount >= remindPrintCount)//队列中已有10条待打印记录
|
||||
continue;
|
||||
var areaOrders = groupOrder.TopItems.Take(remindPrintCount - waittingPrintCount);//得到分组内所有订单
|
||||
foreach (var order in areaOrders)
|
||||
{
|
||||
if (string.IsNullOrEmpty(order.FilePath) || !File.Exists(order.FilePath))
|
||||
{
|
||||
#region 生成的打印文件不存在时,重新生成并打印
|
||||
_logger.LogInformation($"ServiceName:[OrderPrintJob], 打印文件{order.FilePath}获取失败");
|
||||
//获取打印文件失败后,重新生成打印文件
|
||||
var template = Guard.NotNull(_appSettings.PrintPath.Template);
|
||||
if (!Directory.Exists(_appSettings.PrintPath.PrintFilePath))
|
||||
{
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(_appSettings.PrintPath.PrintFilePath);
|
||||
directoryInfo.Create();
|
||||
}
|
||||
if (!Directory.Exists(_appSettings.PrintPath.PicPath))
|
||||
{
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(_appSettings.PrintPath.PicPath);
|
||||
directoryInfo.Create();
|
||||
}
|
||||
string printFilePath = Path.Combine(_appSettings.PrintPath.PrintFilePath, $"{order.PullNo}_{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.docx");
|
||||
string barcodeFilePath = Path.Combine(_appSettings.PrintPath.PicPath, $"{order.PullNo}.png");
|
||||
BarcodeHelper.GenerateBarcodeImage(order.PullNo, barcodeFilePath);
|
||||
//获取订单信息、获取订单明细信息、获取区域名称
|
||||
var orderDto = _orderService.Set().FirstOrDefault(e => e.Id == order.Id);
|
||||
if (orderDto is null)
|
||||
{
|
||||
//订单信息不存在、直接移除待打印记录
|
||||
_orderPrintService.ExecuteDelete(e => e.Id == order.Id);
|
||||
continue;
|
||||
}
|
||||
var areaDto = _areaService.ToList(e => e.IsEnable && e.Code == orderDto.AreaCode).FirstOrDefault();
|
||||
orderDto.AreaName = areaDto?.Name is null ? " " : areaDto?.Name;
|
||||
var orderDetailDtos = _orderService.GetOrderDetailList(orderDto.Id).OrderBy(e => e.SegmentCode).ThenBy(e => e.StorageCode);
|
||||
TemplateReplacerHelper.ReplaceTextInDocument(template, printFilePath, barcodeFilePath, orderDto, orderDetailDtos.ToList());
|
||||
order.FilePath = printFilePath;//替换新文件路径
|
||||
#endregion
|
||||
}
|
||||
PrinterHelper.PrintDocumentToPrinter(filePath: order.FilePath, printerName: printerDto.Name!);//批量打印区域内的订单
|
||||
}
|
||||
updateList.AddRange(areaOrders);
|
||||
}
|
||||
if (updateList.Count > 0)
|
||||
{
|
||||
//更新订单打印状态、更新打印记录状态
|
||||
_orderPrintService.UpdateOrderPrintState(updateList);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderPrintJob] 运行失败 ex=>{ex}");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using FASS.Scheduler.Lite.Controllers.Models.Response;
|
||||
using FASS.Scheduler.Lite.Utility;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Service.Dtos.Record;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Services.Interface.Interfaces;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 同步拣配单完成状态上报Job
|
||||
* 频次:3s同步一次
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class OrderReportJob : IJob
|
||||
{
|
||||
private readonly ILogger<OrderReportJob> _logger;
|
||||
private readonly AppSettings _appSettings;
|
||||
private readonly IOrderReportService _orderReportService;
|
||||
private readonly IDiaryService _diaryService;
|
||||
|
||||
public OrderReportJob(
|
||||
ILogger<OrderReportJob> logger,
|
||||
AppSettings appSettings,
|
||||
IOrderReportService orderReportService,
|
||||
IDiaryService diaryService)
|
||||
{
|
||||
_logger = logger;
|
||||
_appSettings = appSettings;
|
||||
_orderReportService = orderReportService;
|
||||
_diaryService = diaryService;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[OrderReportJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
//所有未同步的记录
|
||||
var dtos = await _orderReportService.ToListAsync(e => !e.SyncFlag && e.IsEnable);
|
||||
var dto = dtos.OrderBy(d => d.FinishedTime).FirstOrDefault();
|
||||
if (dto is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var logDto = new DiaryDto()
|
||||
{
|
||||
Level = DiaryConst.Level.Information,
|
||||
Type = DiaryConst.Type.InterfaceLog,
|
||||
Message = "Ptl => Esb",
|
||||
Code = "PS_PTL_PickingCompletionCallback"
|
||||
};
|
||||
var record = new OrderReportRecord
|
||||
{
|
||||
ORDERNO = dto.PullNo,
|
||||
LOCATION = dto.FactoryCode,
|
||||
VIN = dto.Vin,
|
||||
ReceiveTime = dto.FinishedTime.ToString("yyyy-MM-dd HH:mm:ss")!,
|
||||
PICKING_LINE_ID = 0
|
||||
};
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{"Authorization", $"Basic {_appSettings.InterfaceConfig.BasicSecretKey}"}
|
||||
};
|
||||
var requestData = Utility.Common.GetRequestData(Guid.NewGuid().ToString(), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), _appSettings.InterfaceConfig.Source, JsonSerializer.Serialize(record));
|
||||
logDto.Data = requestData;//存储请求参数
|
||||
var resonse = await HttpClientHelper.PostAsync(_appSettings.InterfaceConfig.EsbUrl, requestData, "application/xml", headers);
|
||||
if (!string.IsNullOrEmpty(resonse))
|
||||
{
|
||||
//拆解返回串的内容
|
||||
var status = Utility.Common.GetNodeValue(resonse, "STATUS", "http://www.oracle.com/esb");
|
||||
var message = Utility.Common.GetNodeValue(resonse, "MESSAGE", "http://www.oracle.com/esb");
|
||||
if (status is not null && status.ToUpper() == "S")
|
||||
{
|
||||
//同步成功
|
||||
_logger.LogInformation($"ServiceName:[OrderReportJob],esb接口回调成功。返回result:{message}");
|
||||
//接口调用正常,更新状态
|
||||
await _orderReportService.Repository.ExecuteUpdateAsync(e => e.Id == dto.Id, s => s.SetProperty(b => b.SyncFlag, true).SetProperty(b => b.Remark, message));
|
||||
logDto.Extend = resonse;
|
||||
}
|
||||
else
|
||||
{
|
||||
//同步失败
|
||||
_logger.LogError($"ServiceName:[OrderReportJob],esb接口回调失败。返回result:{message}");
|
||||
await _orderReportService.Repository.ExecuteUpdateAsync(e => e.Id == dto.Id, s => s.SetProperty(b => b.SyncFlag, true).SetProperty(b => b.Remark, message));
|
||||
logDto.Remark = resonse;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"ServiceName:[OrderReportJob],调用接口异常。返回result:{resonse}");
|
||||
logDto.Remark = resonse;
|
||||
}
|
||||
await _diaryService.AddAsync(logDto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"OrderReportJob ex=>{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Lite.Dtos.Setting;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 清理打印文件服务
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class PrintedFilesArchivingJob : IJob
|
||||
{
|
||||
private readonly ILogger<PrintedFilesArchivingJob> _logger;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly AppSettings _appSettings;
|
||||
|
||||
public PrintedFilesArchivingJob(
|
||||
ILogger<PrintedFilesArchivingJob> logger,
|
||||
IDataService dataService,
|
||||
AppSettings appSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataService = dataService;
|
||||
_appSettings = appSettings;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[PrintedFilesArchivingJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var limitDay = 15;
|
||||
var configDataDto = _dataService.GetConfigToDto<ConfigDataDto>(CacheKey.Setting.ConfigData);
|
||||
if (configDataDto is not null && configDataDto.FileDayLimit is not null)
|
||||
{
|
||||
limitDay = int.Parse(configDataDto.FileDayLimit);
|
||||
}
|
||||
string printFilePath = _appSettings.PrintPath.PrintFilePath;
|
||||
string barcodeFilePath = _appSettings.PrintPath.PicPath;
|
||||
CleanFiles(printFilePath, limitDay);//删除打印文件
|
||||
CleanFiles(barcodeFilePath, limitDay);//删除打印图片
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"ServiceName:[PrintedFilesArchivingJob], 清理打印文件失败 ex=>{ex}");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void CleanFiles(string folderPath, int retentionDays)
|
||||
{
|
||||
try
|
||||
{
|
||||
DirectoryInfo directory = new DirectoryInfo(folderPath);
|
||||
|
||||
if (!directory.Exists)
|
||||
{
|
||||
Console.WriteLine($"指定目录不存在: {folderPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime cutoffTime = DateTime.Now.AddDays(-retentionDays);
|
||||
FileInfo[] files = directory.GetFiles().Where(e => e.LastWriteTime < cutoffTime || e.CreationTime < cutoffTime).ToArray();
|
||||
|
||||
Console.WriteLine($"开始清理 {folderPath} 中 {cutoffTime} 之前的文件...");
|
||||
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 处理只读文件
|
||||
if (file.IsReadOnly)
|
||||
{
|
||||
file.IsReadOnly = false;
|
||||
}
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"删除 {file.Name} 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
Console.WriteLine("文件清理完成。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Common.Frame.Dtos.Frame;
|
||||
using Common.Frame.Entities.Frame;
|
||||
using Common.Frame.Services.Cache.Interfaces;
|
||||
using FASS.Scheduler.Lite.Utility;
|
||||
using FASS.Scheduler.Utility;
|
||||
using FASS.Service.Lite.Consts.Record;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Models.Record;
|
||||
using FASS.Service.Services.Record.Interfaces;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 打印机离线服务
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class PrinterOfflineWarningJob : IJob
|
||||
{
|
||||
private readonly ILogger<PrinterOfflineWarningJob> _logger;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IPrinterService _printerService;
|
||||
private readonly IAreaPrinterService _areaPrinterService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
|
||||
public PrinterOfflineWarningJob(
|
||||
ILogger<PrinterOfflineWarningJob> logger,
|
||||
IDataService dataService,
|
||||
IPrinterService printerService,
|
||||
IAreaPrinterService areaPrinterService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataService = dataService;
|
||||
_printerService = printerService;
|
||||
_areaPrinterService = areaPrinterService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[PrinterOfflineWarningJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var printerDtos = _printerService.ToList(e => e.IsEnable);
|
||||
var areaPrinterDtos = _areaPrinterService.ToList(e => e.IsEnable);//获取所有正在启用的打印机
|
||||
var enablePrinterIds = areaPrinterDtos.Select(x => x.PrinterId).ToList();
|
||||
List<Alarm> alarms = new List<Alarm>();
|
||||
var Configs = _dataService.GetToList<ConfigDto, ConfigEntity>(CacheKey.Setting.Config, e => e.IsEnable).ToList();
|
||||
var retryCount = Configs.Where(e => e.Key == "PrinterRetryCount").FirstOrDefault()?.Value;
|
||||
foreach (var printer in printerDtos.Where(e => enablePrinterIds.Contains(e.Id)))
|
||||
{
|
||||
if (!PrinterHelper.PingPrinter(printer.IpAddress!, retryCount is null ? 3 : int.Parse(retryCount)) || !PrinterHelper.CheckPrinterStatus(printer.Name!))
|
||||
{
|
||||
var model = new Alarm
|
||||
{
|
||||
Level = AlarmConst.Level.Warning,
|
||||
Type = AlarmConst.Type.PrinterOffline,
|
||||
Code = printer.Name,
|
||||
Message = $"打印机[{printer.Name}]离线"
|
||||
};
|
||||
alarms.Add(model);
|
||||
}
|
||||
}
|
||||
if (alarms.Count > 0)
|
||||
{
|
||||
_alarmService.AddModels(alarms, 300);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"PrinterOfflineWarningJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Consts.Module;
|
||||
using FASS.Service.Lite.Dtos.Module;
|
||||
using FASS.Service.Lite.Services.Base.Interfaces;
|
||||
using FASS.Service.Lite.Services.Module.Interfaces;
|
||||
using Quartz;
|
||||
using OrderDto = FASS.Service.Lite.Dtos.Interface.OrderDto;
|
||||
using PassPointDto = FASS.Service.Lite.Dtos.Data.PassPointDto;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 生产物料解析服务
|
||||
* 频次:5s轮询一次
|
||||
* 逻辑:
|
||||
* 根据生产计划中产品以及产品对应的BOM,解析出生产所需的物料清单
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class ProductionMaterialResolverJob : IJob
|
||||
{
|
||||
private readonly ILogger<ProductionMaterialResolverJob> _logger;
|
||||
private readonly IProductionPlanService _productionPlanService;
|
||||
private readonly IProductService _productService;
|
||||
private readonly IMbomService _mbomService;
|
||||
private readonly IStorageService _storageService;
|
||||
|
||||
public ProductionMaterialResolverJob(
|
||||
ILogger<ProductionMaterialResolverJob> logger,
|
||||
IProductionPlanService productionPlanService,
|
||||
IProductService productService,
|
||||
IMbomService mbomService,
|
||||
IStorageService storageService)
|
||||
{
|
||||
_logger = logger;
|
||||
_productionPlanService = productionPlanService;
|
||||
_productService = productService;
|
||||
_mbomService = mbomService;
|
||||
_storageService = storageService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[ProductionMaterialResolverJob],CurrentTime: {DateTime.Now}");
|
||||
try
|
||||
{
|
||||
var productionPlanDtos = _productionPlanService.ToList(e => e.State == ProductionPlanConst.State.Pending).OrderBy(e => e.CreateAt).ThenBy(e => e.SerialNo).Take(20).ToList();//每次处理20条待处理的生产计划
|
||||
if (productionPlanDtos is null || !productionPlanDtos.Any())
|
||||
{
|
||||
_logger.LogInformation("No pending production plans found.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var productIds = productionPlanDtos.Select(e => e.ProductId).Distinct().ToList();
|
||||
var productDtos = _productService.ToList(e => productIds.Contains(e.Id) && e.State == ProductConst.State.Active);
|
||||
var mbomDtos = _mbomService.ToList(e => productIds.Contains(e.ProductId) && e.State == MbomConst.State.Released);//已发布的bom
|
||||
var mbomItemDtos = _mbomService.GetMbomItemList(mbomDtos.Select(e => e.Id).ToList());//获取BOM物料明细项
|
||||
var storageDtos = _storageService.ToList(e => mbomItemDtos.Select(x => x.StorageId).Contains(e.Id));//所有关联库位
|
||||
//1.生成过点数据并写入过点表
|
||||
//2.根据生产计划中的产品ID,获取对应的BOM,解析出物料清单,写入物料表
|
||||
List<PassPointDto> PassPointDtos = new List<PassPointDto>();
|
||||
List<OrderDto> LesOrderDtos = new List<OrderDto>();
|
||||
List<ProductionPlanDto> productionPlanList = new List<ProductionPlanDto>();
|
||||
foreach (var plan in productionPlanDtos)
|
||||
{
|
||||
if (productDtos.FirstOrDefault(e => e.Id == plan.ProductId) is null)
|
||||
{
|
||||
_logger.LogWarning($"Product with ID {plan.ProductId} not found for Production Plan ID {plan.Id}.");
|
||||
continue;
|
||||
}
|
||||
var productDto = productDtos.FirstOrDefault(e => e.Id == plan.ProductId);
|
||||
var mbomDto = mbomDtos.FirstOrDefault(e => e.ProductId == plan.ProductId);
|
||||
if (mbomDto is null)
|
||||
{
|
||||
_logger.LogWarning($"Product with ID {plan.ProductId} not found Bom");
|
||||
continue;
|
||||
}
|
||||
var mbomItemArrDto = mbomItemDtos.Where(e => e.BomId == mbomDto.Id).ToList();
|
||||
//根据库位编码生成,库位-区域字典
|
||||
var areaDic = storageDtos.Select(e => new { e.Code, e.AreaCode }).Distinct().ToDictionary(e => e.Code, e => e.AreaCode);
|
||||
PassPointDtos.Add(new PassPointDto
|
||||
{
|
||||
PassPointTime = DateTime.Now,
|
||||
SequenceNo = plan.SerialNo,
|
||||
Vin = plan.Vin,
|
||||
PrOrderNo = plan.PrOrderNo,
|
||||
CarModel = productDto?.Model,
|
||||
State = PassPointConst.State.Pending
|
||||
});
|
||||
//根据区域生成拉动单号
|
||||
var pullNoDic = new Dictionary<string, string>();//区域编号-拉动单号字典
|
||||
foreach (var mbomItem in mbomItemArrDto)
|
||||
{
|
||||
areaDic.TryGetValue(mbomItem.StorageCode!, out var areaCode);//获取区域编号
|
||||
if (string.IsNullOrEmpty(areaCode))
|
||||
{
|
||||
_logger.LogWarning($"Storage with code {mbomItem.StorageCode} not found AreaCode");
|
||||
continue;
|
||||
}
|
||||
if (!pullNoDic.ContainsKey(areaCode))
|
||||
{
|
||||
pullNoDic[areaCode] = _mbomService.GetNextSeqNoByKey("PullNoSeqNo");
|
||||
}
|
||||
var dto = new OrderDto
|
||||
{
|
||||
PullNo = pullNoDic[areaCode]!,
|
||||
PrOrderNo = plan.PrOrderNo,
|
||||
Vin = plan.Vin,
|
||||
SequenceNo = plan.SerialNo,
|
||||
CarDetail = productDto?.Name!,//暂用产品名称
|
||||
MaterialCode = mbomItem.MaterialCode!,
|
||||
MaterialName = mbomItem.MaterialName!,
|
||||
StorageCode = mbomItem.StorageCode!,
|
||||
Quantity = mbomItem.Quantity,
|
||||
StationCode = mbomItem.StationCode,
|
||||
FactoryCode = "",//暂无
|
||||
AreaCode = areaCode,
|
||||
AreaName = storageDtos.FirstOrDefault(e => e.Id == mbomItem.StorageId)?.AreaName!
|
||||
};
|
||||
LesOrderDtos.Add(dto);
|
||||
}
|
||||
plan.TotalCount = pullNoDic.Keys.Count;
|
||||
plan.CompletedCount = 0;
|
||||
plan.State = ProductionPlanConst.State.Created;
|
||||
productionPlanList.Add(plan);
|
||||
areaDic.Clear();
|
||||
}
|
||||
if(PassPointDtos.Count > 0 && LesOrderDtos.Count > 0)
|
||||
{
|
||||
//写入过点数据表和拉动单明细接口表
|
||||
_productionPlanService.AddResolverBomData(PassPointDtos, LesOrderDtos, productionPlanList);
|
||||
}
|
||||
PassPointDtos.Clear();
|
||||
LesOrderDtos.Clear();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"ProductionMaterialResolverJob ex=>{ex}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using FASS.Service.Lite.Consts.Data;
|
||||
using FASS.Service.Lite.Dtos.Data;
|
||||
using FASS.Service.Lite.Services.Data.Interfaces;
|
||||
using Quartz;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.CronTasks.Jobs
|
||||
{
|
||||
/*
|
||||
* 实时任务归档服务
|
||||
* 将5min前完成的分拣任务及其分段任务进行归档到历史表中,并删除当前表中的数据
|
||||
*/
|
||||
[DisallowConcurrentExecution]
|
||||
public class TaskArchivingJob : IJob
|
||||
{
|
||||
private readonly ILogger<TaskArchivingJob> _logger;
|
||||
private readonly ITaskSegmentService _taskSegmentService;
|
||||
private readonly ITaskService _taskService;
|
||||
|
||||
|
||||
public TaskArchivingJob(
|
||||
ILogger<TaskArchivingJob> logger,
|
||||
ITaskSegmentService taskSegmentService,
|
||||
ITaskService taskService)
|
||||
{
|
||||
_logger = logger;
|
||||
_taskSegmentService = taskSegmentService;
|
||||
_taskService = taskService;
|
||||
}
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation($"ServiceName:[TaskArchivingJob],CurrentTime: {DateTime.Now}");
|
||||
var taskDtos = _taskService.ToList(e => e.State == OrderConst.State.Completed && e.CreateAt < DateTime.Now.AddMinutes(-5));//执行完成的分拣任务
|
||||
if (taskDtos is null || taskDtos.Count == 0)
|
||||
return Task.CompletedTask;
|
||||
var taskSegmentDtos = _taskSegmentService.ToList(e => e.State == OrderConst.State.Completed);//所有分段任务
|
||||
List<TaskDto> mergeList = new List<TaskDto>();
|
||||
List<TaskSegmentDto> mergeSegmentList = new List<TaskSegmentDto>();
|
||||
foreach (var task in taskDtos)
|
||||
{
|
||||
mergeList.Add(task);
|
||||
var mergeSegment = taskSegmentDtos.Where(e => e.TaskId == task.Id).ToList();
|
||||
mergeSegmentList.AddRange(mergeSegment);
|
||||
}
|
||||
if (mergeSegmentList.Count > 0)
|
||||
{
|
||||
_taskService.MergeTaskArchiving(mergeList, mergeSegmentList);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using DotNetCore.CAP;
|
||||
using FASS.Scheduler.Models;
|
||||
using FASS.Scheduler.Services.EventBus.Subscribes;
|
||||
|
||||
namespace FASS.Scheduler.Services.EventBus
|
||||
{
|
||||
public class EventBusService : ICapSubscribe
|
||||
{
|
||||
public ILogger<EventBusService> Logger { get; }
|
||||
public AppSettings AppSettings { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public DefaultSubscribe DefaultSubscribe { get; } = null!;
|
||||
|
||||
public EventBusService(
|
||||
ILogger<EventBusService> logger,
|
||||
AppSettings appSettings,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
Logger = logger;
|
||||
AppSettings = appSettings;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!AppSettings.Frame.EventBus.IsEnable)
|
||||
{
|
||||
Logger.LogInformation("事件总线未启用");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("事件总线已启动");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "错误");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!AppSettings.Frame.EventBus.IsEnable)
|
||||
{
|
||||
Logger.LogInformation("事件总线未启用");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("事件总线已停止");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "错误");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FASS.Extend.Master;
|
||||
|
||||
namespace FASS.Scheduler.Lite.Services.EventBuses.Model
|
||||
{
|
||||
public class ControlMessage
|
||||
{
|
||||
public byte Command { get; set; } = 0x01;
|
||||
public byte SectionCount { get; set; }
|
||||
public byte State { get; set; }
|
||||
public byte TaskNo { get; set; }
|
||||
public DateTime PublishTime { get; set; }
|
||||
public List<LightControlMessage> LightControlMessages { get; set; } = new List<LightControlMessage>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace FASS.Scheduler.Lite.Services.EventBuses.Model
|
||||
{
|
||||
public class RemoteControlMsg
|
||||
{
|
||||
public List<MaterMsg> MasterMsgList { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MaterMsg
|
||||
{
|
||||
public required string Operate { get; set; }
|
||||
public required string Remote { get; set; }
|
||||
public byte SectionCount { get; set; }
|
||||
public List<LightMsg> LightControlList { get; set; } = [];
|
||||
}
|
||||
|
||||
public class LightMsg
|
||||
{
|
||||
public byte StartLightNo { get; set; }
|
||||
public byte EndLightNo { get; set; }
|
||||
public byte State { get; set; }
|
||||
public uint Led { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Common.NETCore.Extensions;
|
||||
using DotNetCore.CAP;
|
||||
using FASS.Extend.Master;
|
||||
using FASS.Scheduler.Lite.Services.EventBuses.Model;
|
||||
using FASS.Scheduler.Services.Extends.Demo;
|
||||
using System.Net;
|
||||
|
||||
namespace FASS.Scheduler.Services.EventBus.Subscribes
|
||||
{
|
||||
public class DefaultSubscribe : ICapSubscribe
|
||||
{
|
||||
public EventBusService EventBusService { get; }
|
||||
|
||||
public DefaultSubscribe(
|
||||
EventBusService eventBusService)
|
||||
{
|
||||
EventBusService = eventBusService;
|
||||
}
|
||||
|
||||
[CapSubscribe("Light.Setting")]
|
||||
public void LightStateControl(RemoteControlMsg message)
|
||||
{
|
||||
EventBusService.Logger.LogDebug($"收到消息:{message.ToJson()}");
|
||||
foreach (var item in message.MasterMsgList)
|
||||
{
|
||||
|
||||
ControlMessage controlMessage = new ControlMessage
|
||||
{
|
||||
Command = 0x01,
|
||||
SectionCount = (byte)item.LightControlList.Count,
|
||||
LightControlMessages = item.LightControlList.DeepClone<List<LightControlMessage>>(),
|
||||
State = item.Operate == "on" ? Extend.Master.Utility.SetLightGreen() : (item.Operate == "display" ? Extend.Master.Utility.SetDisplayLightAddress() : Extend.Master.Utility.SetLightOff()),
|
||||
TaskNo = Lite.Utility.Common.GetTaskSn(IPEndPoint.Parse(item.Remote)),
|
||||
PublishTime = DateTime.Now
|
||||
};
|
||||
if (ExtendUdpServerService.WaitSendData.ContainsKey(IPEndPoint.Parse(item.Remote)))
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData[IPEndPoint.Parse(item.Remote)].Add(controlMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendUdpServerService.WaitSendData.TryAdd(IPEndPoint.Parse(item.Remote), new List<ControlMessage>
|
||||
{
|
||||
controlMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user