自动化立体仓库 - WMS系统
zwl
4 天以前 632bde0f32999a2b319b706e23d90bc1b5dd8cc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package com.zy.asrs.service.impl;
 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.zy.asrs.entity.LocDetl;
import com.zy.asrs.service.InboundCameraCaptureService;
import com.zy.asrs.service.LocDetlService;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
 
@Slf4j
@Service
public class InboundCameraCaptureServiceImpl implements InboundCameraCaptureService {
 
    private static final String CONFIG_CODE = "InboundCameraCapture";
    private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json;charset=utf-8");
    private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.SECONDS)
            .build();
 
    @Autowired
    private ConfigService configService;
 
    @Autowired
    private LocDetlService locDetlService;
 
    @Override
    public String capture(Integer sourceStaNo, String locNo, String barcode) {
        CaptureConfig config = loadConfig();
        if (config == null || !config.enabled) {
            return null;
        }
        if (sourceStaNo == null) {
            log.warn("入库抓拍配置跳过:sourceStaNo为空");
            return null;
        }
        JSONArray cameras = config.stations.getJSONArray(String.valueOf(sourceStaNo));
        if (Cools.isEmpty(cameras)) {
            log.warn("入库抓拍配置缺失:sourceStaNo={}", sourceStaNo);
            return null;
        }
        if (cameras.size() != 2) {
            log.warn("入库抓拍摄像头数量不是2:sourceStaNo={}, count={}", sourceStaNo, cameras.size());
        }
 
        String baseName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
                + "_" + sanitize(String.valueOf(sourceStaNo))
                + "_" + sanitize(locNo)
                + "_" + sanitize(barcode);
        List<String> successPics = new ArrayList<>();
        for (int i = 0; i < cameras.size(); i++) {
            JSONObject camera = cameras.getJSONObject(i);
            if (camera == null) {
                continue;
            }
            String picName = baseName + "-" + (i + 1) + ".jpg";
            if (captureOne(config.captureUrl, sourceStaNo, i + 1, camera, picName)) {
                successPics.add(picName);
            }
        }
        return successPics.isEmpty() ? null : JSON.toJSONString(successPics);
    }
 
    @Override
    public List<String> resolvePicUrls(String pic) {
        CaptureConfig config = loadConfig();
        String prefix = config == null ? "" : config.imageUrlPrefix;
        Set<String> urls = new LinkedHashSet<>();
        for (String picName : parsePicNames(pic)) {
            if (Cools.isEmpty(picName)) {
                continue;
            }
            if (picName.startsWith("http://") || picName.startsWith("https://")) {
                urls.add(picName);
            } else if (!Cools.isEmpty(prefix)) {
                urls.add(prefix + picName);
            }
        }
        return new ArrayList<>(urls);
    }
 
    @Override
    public List<String> resolveLocPicUrls(String locNo) {
        if (Cools.isEmpty(locNo)) {
            return new ArrayList<>();
        }
        List<LocDetl> locDetls = locDetlService.selectList(new EntityWrapper<LocDetl>().eq("loc_no", locNo));
        Set<String> urls = new LinkedHashSet<>();
        for (LocDetl locDetl : locDetls) {
            urls.addAll(resolvePicUrls(locDetl.getPic()));
        }
        return new ArrayList<>(urls);
    }
 
    private boolean captureOne(String captureUrl, Integer sourceStaNo, int index, JSONObject camera, String picName) {
        String ip = camera.getString("ip");
        if (Cools.isEmpty(ip)) {
            log.warn("入库抓拍摄像头IP为空:sourceStaNo={}, cameraIndex={}", sourceStaNo, index);
            return false;
        }
        JSONObject requestJson = new JSONObject();
        requestJson.put("ip", ip);
        requestJson.put("port", defaultInt(camera.getInteger("port"), 554));
        requestJson.put("user", defaultString(camera.getString("user"), "admin"));
        requestJson.put("password", defaultString(camera.getString("password"), "a327482030"));
        requestJson.put("picName", picName);
 
        try {
            RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, requestJson.toJSONString());
            Request request = new Request.Builder()
                    .url(captureUrl)
                    .post(body)
                    .build();
            try (Response response = HTTP_CLIENT.newCall(request).execute()) {
                ResponseBody responseBody = response.body();
                String bodyText = responseBody == null ? "" : responseBody.string();
                if (!response.isSuccessful()) {
                    log.warn("入库抓拍HTTP失败:sourceStaNo={}, cameraIndex={}, ip={}, httpCode={}, picName={}",
                            sourceStaNo, index, ip, response.code(), picName);
                    return false;
                }
                JSONObject result = JSON.parseObject(bodyText);
                Integer code = result == null ? null : result.getInteger("code");
                if (code != null && code == 200) {
                    return true;
                }
                String msg = result == null ? bodyText : result.getString("msg");
                log.warn("入库抓拍返回失败:sourceStaNo={}, cameraIndex={}, ip={}, code={}, msg={}, picName={}",
                        sourceStaNo, index, ip, code, msg, picName);
                return false;
            }
        } catch (Exception e) {
            log.warn("入库抓拍异常:sourceStaNo={}, cameraIndex={}, ip={}, picName={}",
                    sourceStaNo, index, ip, picName, e);
            return false;
        }
    }
 
    private CaptureConfig loadConfig() {
        Config config = configService.selectConfigByCode(CONFIG_CODE);
        if (config == null || Cools.isEmpty(config.getValue())) {
            return null;
        }
        if (config.getStatus() != null && config.getStatus() == 0) {
            return null;
        }
        try {
            JSONObject json = JSON.parseObject(config.getValue());
            CaptureConfig captureConfig = new CaptureConfig();
            Boolean enabled = json.getBoolean("enabled");
            captureConfig.enabled = enabled == null || enabled;
            captureConfig.captureUrl = json.getString("captureUrl");
            captureConfig.imageUrlPrefix = normalizePrefix(json.getString("imageUrlPrefix"), captureConfig.captureUrl);
            captureConfig.stations = json.getJSONObject("stations");
            if (Cools.isEmpty(captureConfig.captureUrl) || captureConfig.stations == null) {
                log.warn("入库抓拍配置不完整:code={}", CONFIG_CODE);
                return null;
            }
            return captureConfig;
        } catch (Exception e) {
            log.warn("入库抓拍配置解析失败:code={}", CONFIG_CODE, e);
            return null;
        }
    }
 
    private List<String> parsePicNames(String pic) {
        List<String> names = new ArrayList<>();
        if (Cools.isEmpty(pic)) {
            return names;
        }
        try {
            Object parsed = JSON.parse(pic);
            if (parsed instanceof JSONArray) {
                JSONArray array = (JSONArray) parsed;
                for (int i = 0; i < array.size(); i++) {
                    names.add(array.getString(i));
                }
                return names;
            }
            if (parsed instanceof String) {
                names.add((String) parsed);
                return names;
            }
        } catch (Exception ignored) {
            // 兼容旧数据中直接存单个文件名或逗号分隔文件名的情况。
        }
        String[] split = pic.split(",");
        for (String item : split) {
            if (!Cools.isEmpty(item)) {
                names.add(item.trim());
            }
        }
        return names;
    }
 
    private String normalizePrefix(String imageUrlPrefix, String captureUrl) {
        String prefix = imageUrlPrefix;
        if (Cools.isEmpty(prefix) && !Cools.isEmpty(captureUrl)) {
            int idx = captureUrl.lastIndexOf("/");
            prefix = idx > "http://".length() ? captureUrl.substring(0, idx) + "/image/" : "";
        }
        if (!Cools.isEmpty(prefix) && !prefix.endsWith("/")) {
            prefix = prefix + "/";
        }
        return prefix;
    }
 
    private String sanitize(String value) {
        if (value == null) {
            return "null";
        }
        return value.replaceAll("[\\\\/:*?\"<>|\\s]+", "_");
    }
 
    private int defaultInt(Integer value, int defaultValue) {
        return value == null ? defaultValue : value;
    }
 
    private String defaultString(String value, String defaultValue) {
        return Cools.isEmpty(value) ? defaultValue : value;
    }
 
    private static class CaptureConfig {
        private boolean enabled;
        private String captureUrl;
        private String imageUrlPrefix;
        private JSONObject stations;
    }
}