1
zhang
4 天以前 5a6a6c94d0c02732e0be3410d126d652acb8ad10
1
1个文件已删除
15个文件已修改
3个文件已添加
833 ■■■■■ 已修改文件
component/component-Influxdb/src/main/java/com/zy/component/influxdb/service/InfluxDBService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/JobService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/DevpServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/JobServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WmsMainServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WrkLastnoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/core/operation/handler/AppleLocOperationHandler.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/core/operation/handler/FakeUserOperationHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/core/operation/handler/SendTaskOperationHandler.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/resources/application.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/resources/mapper/JobMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/HexApplication.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/TestController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/Device.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/influxdb/task/InfluxDbScheduler.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/HttpGo.java 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/resources/application.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/webapp/views/dashboard.html 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
component/component-Influxdb/src/main/java/com/zy/component/influxdb/service/InfluxDBService.java
@@ -29,8 +29,6 @@
    @Autowired
    private InfluxDBClient influxDBClient;
    /**
     * 写入数据
     *
zy-acs-cv/src/main/java/com/zy/asrs/service/JobService.java
@@ -12,7 +12,7 @@
    Job getJobByBarcodeAndJobSts(String barcode, Integer jobSts);
    Job getJobByBarcode(String barcode);
    Job getJobByBarcode(String barcode, Integer jobSts);
    Job getJobByJobNo(Integer jobNo);
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/DevpServiceImpl.java
@@ -19,6 +19,8 @@
            if (de != null) {
                de.setSqlData(devp);
                baseMapper.updateById(de);
            }else {
                baseMapper.insert(devp);
            }
        }
    }
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/JobServiceImpl.java
@@ -41,8 +41,8 @@
    }
    @Override
    public Job getJobByBarcode(String barcode) {
        return baseMapper.getJobByBarcode(barcode);
    public Job getJobByBarcode(String barcode, Integer jobSts) {
        return baseMapper.getJobByBarcodeAndJobSts(barcode,jobSts);
    }
    @Override
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WmsMainServiceImpl.java
@@ -74,4 +74,11 @@
        }
        return null;
    }
    public static void main(String[] args) {
        String s= "{\"msg\":\"Success\",\"code\":200,\"data\":{\"locNo\":\"A102400201\",\"batchNo\":\"TK2603104428\",\"taskNo\":\"TK2603104428\"}}";
        //System.out.println(JSON.parseObject(s).getString("data"));
        ApplyInRepsonseDto applyInRepsonseDto = JSONObject.parseObject(JSON.parseObject(s).getString("data"), ApplyInRepsonseDto.class);
        System.out.println(applyInRepsonseDto);
    }
}
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WrkLastnoServiceImpl.java
@@ -37,7 +37,7 @@
        do {
            workNo = workNo >= eNo ? sNo : workNo + 1;
        } while (jobService.getJobByJobNo(workNo) == null);
        } while (jobService.getJobByJobNo(workNo) != null);
        if (workNo > 0) {
            wrkLastno.setWrkNo(workNo);
zy-acs-cv/src/main/java/com/zy/core/operation/handler/AppleLocOperationHandler.java
@@ -85,18 +85,21 @@
                        }
                        // 9991是空板,9992是满板
                        if (staProtocol.getWorkNo() >= 9991 && staProtocol.getWorkNo() <= 9992) {
                            Job job = jobService.getJobByBarcode(barcode);
                            Job job = jobService.getJobByBarcode(barcode ,ConveyorStateType.INBOUND.getStatus());
                            // 申请入库
                            if (job == null || (job != null && job.getJobSts() == ConveyorStateType.CLEARSIGNAL.getStatus())) {
                                ApplyInRepsonseDto locOfWms = wmsMainService.getLocOfWms(applyIn(barcode, inSta.getStaNo() + "", staProtocol));
                                if (locOfWms != null) {
                                    staProtocol.setWorkNo(job.getJobNo());
                                    Integer workNo = getWorkNo();
                                    staProtocol.setWorkNo(workNo);
                                    staProtocol.setStaNo(inSta.getTargetSta());
                                    if (MessageQueue.offer(SlaveType.Devp, devp.getId(), new Task(TaskType.WRITE, staProtocol))) {
                                        if (!jobService.insert(initJob(locOfWms, barcode, inSta.getTargetSta() + ""))) {
                                        if (!jobService.insert(initJob(locOfWms, barcode, workNo,inSta.getTargetSta() + ""))) {
                                            throw new CoolException("更新输送线任务失败," + " - " + staProtocol.getWorkNo());
                                        }
                                        log.info("入库前进:{},{}", staProtocol.getWorkNo(), inSta.getTargetSta());
                                    }else {
                                        log.info("下发失败:{},{}", staProtocol.getWorkNo(), inSta.getTargetSta());
                                    }
                                } else {
                                    log.info("WMS未返回库位信息,条码:{},站点:{}", barcode, inSta.getStaNo());
@@ -112,14 +115,15 @@
        }
    }
    private Job initJob(ApplyInRepsonseDto locOfWms, String barcode, String staNo) {
    private Job initJob(ApplyInRepsonseDto locOfWms, String barcode,Integer workNo, String staNo) {
        Job  job = new Job();
        job.setLoc(locOfWms.getLocNo());
        job.setTaskNo(locOfWms.getTaskNo());
        job.setBatchNo(locOfWms.getBatchNo());
        job.setBarcode(barcode);
        job.setStaNo(staNo);
        job.setJobNo(getWorkNo());
        job.setJobNo(workNo);
        job.setJobSts(ConveyorStateType.INBOUND.getStatus());
        job.setWmsTime(new Date());
        return job;
zy-acs-cv/src/main/java/com/zy/core/operation/handler/FakeUserOperationHandler.java
@@ -60,7 +60,7 @@
                return;
            }
            if (staProtocol.getWorkNo() > 0 && staProtocol.isAutoing()) {
                Job jobByWorkNo = jobService.getJobByJobNo(staProtocol.getWorkNo());
                Job jobByWorkNo = jobService.getJobByJobNoAndJobSts(staProtocol.getWorkNo(),ConveyorStateType.OUTBOUND.getStatus());
                if (jobByWorkNo != null && jobByWorkNo.getJobSts() == ConveyorStateType.OUTBOUND.getStatus()) {
                    staProtocol.setWorkNo(9992);
                    staProtocol.setStaNo(1005);
zy-acs-cv/src/main/java/com/zy/core/operation/handler/SendTaskOperationHandler.java
@@ -73,8 +73,8 @@
                        continue;
                    }
                    if (staProtocol.isAutoing()) {
                        Job job = jobService.getJobByJobNo(staProtocol.getWorkNo());
                        if (job != null && job.getJobSts() == ConveyorStateType.INBOUND.getStatus()) {
                        Job job = jobService.getJobByJobNoAndJobSts(staProtocol.getWorkNo(),ConveyorStateType.INBOUND.getStatus());
                        if (job != null  ) {
                            if (ctuMainService.sendTask(process(job))) {
                                job.setJobSts(ConveyorStateType.SENDTASK.getStatus());
                                job.setRcsTime(new Date());
zy-acs-cv/src/main/resources/application.yml
@@ -10,7 +10,7 @@
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    #url: jdbc:mysql://127.0.0.1:3306/rcs_ctu_stable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    url: jdbc:mysql://192.168.133.173:3306/cv?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    url: jdbc:mysql://127.0.0.1:3306/cv?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: xltys1995
  mvc:
@@ -18,7 +18,7 @@
logging:
  file:
    path: stock/out/cv/logs
    path: /stock/out/cv/logs
wms:
  url: 10.10.10.220:8081
zy-acs-cv/src/main/resources/mapper/JobMapper.xml
@@ -61,6 +61,7 @@
        select *
        from cv_job
        where barcode = #{barcode}
          and job_sts = #{jobSts}
        order by id desc limit 1
    </select>
zy-acs-hex/pom.xml
@@ -64,6 +64,12 @@
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.12.0</version>
        </dependency>
    </dependencies>
    <build>
zy-acs-hex/src/main/java/com/zy/acs/hex/HexApplication.java
@@ -4,7 +4,9 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@ComponentScan(basePackages = {"com.zy.component", "com.zy.acs"})
@SpringBootApplication
public class HexApplication {
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/TestController.java
@@ -1,7 +1,7 @@
package com.zy.acs.hex.controller;
import com.zy.acs.common.domain.mq.DeviceMessage;
import com.zy.acs.hex.constant.RabbitConstant;
import com.zy.acs.hex.domain.Device;
import com.zy.component.influxdb.service.InfluxDBService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@@ -31,10 +31,9 @@
     */
    @GetMapping(value = "/test1")
    public void sendTest1() {
        Device device = new Device();
        DeviceMessage device = new DeviceMessage("121212121212");
        //device.setEvent("online");
        //device.setDeviceId("123");
        device.setProtocol("212121212121212");
        String router = RabbitConstant.ROUTING_KEY_UP.replaceFirst("\\*", "123").replaceFirst("\\*", "online");
        rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE, router, device);
    }
@@ -69,7 +68,7 @@
    @GetMapping(value = "/query2")
    @ResponseBody
    public Object queryTest2() {
        return influxDBService.queryPoints("select * from device order by time desc limit 10", Device.class);
        return influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/Device.java
File was deleted
zy-acs-hex/src/main/java/com/zy/acs/hex/influxdb/task/InfluxDbScheduler.java
New file
@@ -0,0 +1,85 @@
package com.zy.acs.hex.influxdb.task;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zy.acs.hex.utils.HttpGo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class InfluxDbScheduler {
    @Value("${influxdb3.createDatabaseUrl}")
    private String createDatabaseUrl;
    @Value("${influxdb3.database}")
    private String databaseName;
    @Value("${influxdb3.token}")
    private String token;
    @Value("${influxdb3.retention-period}")
    private String retentionPeriod;
    private static Long timeoutSeconds = 30L;
    private HttpGo http;
    @PostConstruct
    public void init() {
        this.http = HttpGo.builder()
                .connectTimeout(Duration.ofSeconds(timeoutSeconds))
                .readTimeout(Duration.ofSeconds(timeoutSeconds))
                .build();
        createDatabase();
    }
    public void createDatabase() {
        // headers
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + token);
        headers.put("Content-Type", "application/json;charset=UTF-8");
        try {
            HttpGo.HttpResponse response = this.http.get(createDatabaseUrl + "?format=json", headers, null);
            if (!isExist(response.body())) {
                Map<String, String> parames = new HashMap<>();
                parames.put("db", databaseName);
                parames.put("retention-period", retentionPeriod);
                HttpGo.HttpResponse postResponse = this.http.postJson(createDatabaseUrl, headers, JSON.toJSONString(parames));
                log.info("是否创建数据库:{}", postResponse);
            }else {
                log.info("数据库:{}", response.body());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private boolean isExist(String databases) {
        JSONArray objects = JSON.parseArray(databases);
        for (Object object : objects) {
            JSONObject obj = (JSONObject) object;
            if (obj.getString("iox::database").equals(databaseName)) {
                return true;
            }
        }
        return false;
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/HttpGo.java
New file
@@ -0,0 +1,322 @@
package com.zy.acs.hex.utils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * Minimal OkHttp wrapper: GET / POST only.
 *
 * - fluent API (get / postJson / postForm)
 * - default singleton instance (thread-safe)
 * - per-request headers + default headers
 * - simple response wrapper with tookMs
 * - optional trust-all SSL (ONLY for internal test)
 */
@Slf4j
public final class HttpGo {
    private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private final OkHttpClient client;
    private final Map<String, String> defaultHeaders;
    private HttpGo(OkHttpClient client, Map<String, String> defaultHeaders) {
        this.client = Objects.requireNonNull(client, "client");
        this.defaultHeaders = defaultHeaders == null
                ? Collections.emptyMap()
                : Collections.unmodifiableMap(new LinkedHashMap<>(defaultHeaders));
    }
    /** Shared default instance (safe SSL by default). */
    public static HttpGo defaults() {
        return Holder.DEFAULT;
    }
    public static Builder builder() {
        return new Builder();
    }
    // ===================== GET =====================
    public HttpResponse get(String url) throws IOException {
        return get(url, null, null);
    }
    public HttpResponse get(String url, Map<String, String> queryParams) throws IOException {
        return get(url, null, queryParams);
    }
    public HttpResponse get(String url, Map<String, String> headers, Map<String, String> queryParams) throws IOException {
        HttpUrl parsed = HttpUrl.parse(url);
        if (parsed == null) throw new IllegalArgumentException("Invalid url: " + url);
        HttpUrl.Builder ub = parsed.newBuilder();
        if (queryParams != null) {
            queryParams.forEach((k, v) -> {
                if (k != null && v != null) ub.addQueryParameter(k, v);
            });
        }
        Request.Builder rb = new Request.Builder().url(ub.build()).get();
        applyHeaders(rb, headers);
        return execute(rb.build());
    }
    // ===================== POST =====================
    /** POST JSON string payload (null/blank -> "{}"). */
    public HttpResponse postJson(String url, String json) throws IOException {
        return postJson(url, null, json);
    }
    /** POST JSON string payload (null/blank -> "{}"). */
    public HttpResponse postJson(String url, Map<String, String> headers, String json) throws IOException {
        String payload = (json == null || json.trim().isEmpty()) ? "{}" : json;
        RequestBody body = RequestBody.create(payload, JSON);
        Request.Builder rb = new Request.Builder().url(url).post(body);
        applyHeaders(rb, headers);
        // ensure content-type unless caller overrides
        if (rb.build().header("Content-Type") == null) {
            rb.header("Content-Type", "application/json; charset=utf-8");
        }
        return execute(rb.build());
    }
    /** POST x-www-form-urlencoded fields. */
    public HttpResponse postForm(String url, Map<String, String> formFields) throws IOException {
        return postForm(url, null, formFields);
    }
    /** POST x-www-form-urlencoded fields. */
    public HttpResponse postForm(String url, Map<String, String> headers, Map<String, String> formFields) throws IOException {
        FormBody.Builder fb = new FormBody.Builder(DEFAULT_CHARSET);
        if (formFields != null) {
            formFields.forEach((k, v) -> {
                if (k != null && v != null) fb.add(k, v);
            });
        }
        Request.Builder rb = new Request.Builder().url(url).post(fb.build());
        applyHeaders(rb, headers);
        return execute(rb.build());
    }
    // ===================== Internals =====================
    private HttpResponse execute(Request request) throws IOException {
        long start = System.nanoTime();
        try (Response resp = client.newCall(request).execute()) {
            long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
            String body = null;
            ResponseBody rb = resp.body();
            if (rb != null) body = rb.string(); // one-shot
            return new HttpResponse(resp.code(), resp.headers(), body, tookMs);
        } catch (IOException e) {
            log.error("HttpGo request failed: {} {}", request.method(), request.url(), e);
            throw e;
        }
    }
    private void applyHeaders(Request.Builder rb, Map<String, String> headers) {
        // defaults first, then per-request overrides
        if (defaultHeaders != null) {
            defaultHeaders.forEach((k, v) -> {
                if (k != null && v != null) rb.header(k, v);
            });
        }
        if (headers != null) {
            headers.forEach((k, v) -> {
                if (k != null && v != null) rb.header(k, v);
            });
        }
    }
    private static final class Holder {
        private static final HttpGo DEFAULT = HttpGo.builder().build();
    }
    // ===================== Response =====================
    public static final class HttpResponse {
        private final int statusCode;
        private final Headers headers;
        private final String body;
        private final long tookMs;
        public HttpResponse(int statusCode, Headers headers, String body, long tookMs) {
            this.statusCode = statusCode;
            this.headers = headers == null ? new Headers.Builder().build() : headers;
            this.body = body;
            this.tookMs = tookMs;
        }
        public int statusCode() { return statusCode; }
        public Headers headers() { return headers; }
        public String body() { return body; }
        public long tookMs() { return tookMs; }
        public boolean is2xx() {
            return statusCode >= 200 && statusCode < 300;
        }
        public String header(String name) {
            return headers.get(name);
        }
        @Override
        public String toString() {
            return "HttpResponse{status=" + statusCode + ", tookMs=" + tookMs
                    + ", bodyLen=" + (body == null ? 0 : body.length()) + "}";
        }
    }
    // ===================== Builder =====================
    public static final class Builder {
        private Duration connectTimeout = Duration.ofSeconds(10);
        private Duration readTimeout = Duration.ofSeconds(20);
        private Duration writeTimeout = Duration.ofSeconds(20);
        private boolean trustAllSsl = false;
        private final Map<String, String> defaultHeaders = new LinkedHashMap<>();
        public Builder defaultHeader(String name, String value) {
            if (name != null && value != null) defaultHeaders.put(name, value);
            return this;
        }
        public Builder connectTimeout(Duration d) {
            if (d != null) connectTimeout = d;
            return this;
        }
        public Builder readTimeout(Duration d) {
            if (d != null) readTimeout = d;
            return this;
        }
        public Builder writeTimeout(Duration d) {
            if (d != null) writeTimeout = d;
            return this;
        }
        /** Trust ALL certificates. ONLY for internal testing/self-signed endpoints. */
        public Builder trustAllSsl(boolean enable) {
            this.trustAllSsl = enable;
            return this;
        }
        public HttpGo build() {
            OkHttpClient.Builder cb = new OkHttpClient.Builder()
                    .connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS)
                    .readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS)
                    .writeTimeout(writeTimeout.toMillis(), TimeUnit.MILLISECONDS);
            if (trustAllSsl) {
                TrustAll trustAll = new TrustAll();
                cb.sslSocketFactory(trustAll.sslSocketFactory, trustAll.trustManager)
                        .hostnameVerifier((hostname, session) -> true);
            }
            return new HttpGo(cb.build(), defaultHeaders);
        }
    }
    // ===================== Trust-all SSL helper =====================
    private static final class TrustAll {
        final X509TrustManager trustManager;
        final SSLSocketFactory sslSocketFactory;
        TrustAll() {
            try {
                this.trustManager = new X509TrustManager() {
                    @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { }
                    @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { }
                    @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
                this.sslSocketFactory = sslContext.getSocketFactory();
            } catch (Exception e) {
                throw new IllegalStateException("Failed to init trust-all SSL", e);
            }
        }
    }
    // ======================== tools ========================
    public String buildUrl(String host, Integer port, String path) {
        return buildUrl(host, port, path, false);
    }
    public String buildUrl(String host, Integer port, String path, boolean ssl) {
        String p = (path == null) ? "" : (path.startsWith("/") ? path : ("/" + path));
        return (ssl ? "https" : "http") + "://" + host + ":" + port + p;
    }
    // ===================== Demo (main) =====================
    public static void main(String[] args) throws Exception {
        HttpGo http = HttpGo.builder()
                .connectTimeout(Duration.ofSeconds(8))
                .readTimeout(Duration.ofSeconds(15))
                .defaultHeader("User-Agent", "HttpGo/1.0")
                // .trustAllSsl(true) // ONLY if you really need it
                .build();
        // 1) GET with query params + per-request headers
        String getUrl = "https://httpbin.org/get";
        Map<String, String> query = new HashMap<>();
        query.put("q", "vincent");
        query.put("page", "1");
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Trace-Id", "trace-001");
        HttpResponse r1 = http.get(getUrl, headers, query);
        System.out.println("GET status=" + r1.statusCode() + ", tookMs=" + r1.tookMs());
        System.out.println(r1.body());
        // 2) POST JSON
        String postUrl = "https://httpbin.org/post";
        String json = "{\"name\":\"Vincent\",\"role\":\"engineer\"}";
        Map<String, String> postHeaders = new HashMap<>();
        postHeaders.put("X-Trace-Id", "trace-002");
        HttpResponse r2 = http.postJson(postUrl, postHeaders, json);
        System.out.println("POST(JSON) status=" + r2.statusCode() + ", tookMs=" + r2.tookMs());
        System.out.println(r2.body());
        // 3) POST Form
        Map<String, String> form = new HashMap<>();
        form.put("username", "vincent");
        form.put("password", "123456");
        HttpResponse r3 = http.postForm(postUrl, null, form);
        System.out.println("POST(Form) status=" + r3.statusCode() + ", tookMs=" + r3.tookMs());
        System.out.println(r3.body());
    }
}
zy-acs-hex/src/main/resources/application.yml
@@ -30,3 +30,5 @@
  #token: apiv3_Jx1SvmBMV_kikGhc4eZJQbeGmNYN7KX1GdpoR9MClkKzMxSJ0MPKM_O2Xt3o1hVyRikMmlxZ_h9zfy6ybC5Idg
  database: rcs
  token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA
  retention-period: 30d
  createDatabaseUrl: ${influxdb3.url}/api/v3/configure/database
zy-acs-hex/src/main/webapp/views/dashboard.html
New file
@@ -0,0 +1,354 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CTU小车数据监控</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#3b82f6',
                        secondary: '#10b981',
                        warning: '#f59e0b',
                        danger: '#ef4444',
                        dark: '#1e293b',
                    },
                    fontFamily: {
                        inter: ['Inter', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .card-shadow {
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            }
            .animate-pulse-slow {
                animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
            }
        }
    </style>
</head>
<body class="bg-gray-50 font-inter">
    <div class="min-h-screen">
        <!-- 顶部导航栏 -->
        <header class="bg-white card-shadow sticky top-0 z-10">
            <div class="container mx-auto px-4 py-3 flex justify-between items-center">
                <div class="flex items-center space-x-2">
                    <i class="fa fa-car text-primary text-2xl"></i>
                    <h1 class="text-xl font-bold text-dark">CTU小车数据监控</h1>
                </div>
                <div class="flex items-center space-x-4">
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3">
                            <i class="fa fa-search text-gray-400"></i>
                        </span>
                        <input type="text" placeholder="搜索设备..." class="pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
                    </div>
                    <button class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors">
                        <i class="fa fa-refresh mr-1"></i> 刷新
                    </button>
                </div>
            </div>
        </header>
        <!-- 主内容区 -->
        <main class="container mx-auto px-4 py-6">
            <!-- 状态概览 -->
            <section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
                <div class="bg-white rounded-lg p-4 card-shadow">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-500 text-sm">总设备数</p>
                            <h3 class="text-2xl font-bold text-dark">24</h3>
                        </div>
                        <div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center">
                            <i class="fa fa-microchip text-primary text-xl"></i>
                        </div>
                    </div>
                </div>
                <div class="bg-white rounded-lg p-4 card-shadow">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-500 text-sm">在线设备</p>
                            <h3 class="text-2xl font-bold text-secondary">18</h3>
                        </div>
                        <div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center">
                            <i class="fa fa-check-circle text-secondary text-xl"></i>
                        </div>
                    </div>
                </div>
                <div class="bg-white rounded-lg p-4 card-shadow">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-500 text-sm">离线设备</p>
                            <h3 class="text-2xl font-bold text-danger">6</h3>
                        </div>
                        <div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center">
                            <i class="fa fa-exclamation-circle text-danger text-xl"></i>
                        </div>
                    </div>
                </div>
                <div class="bg-white rounded-lg p-4 card-shadow">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-500 text-sm">今日数据量</p>
                            <h3 class="text-2xl font-bold text-warning">1.2k</h3>
                        </div>
                        <div class="w-12 h-12 rounded-full bg-yellow-100 flex items-center justify-center">
                            <i class="fa fa-database text-warning text-xl"></i>
                        </div>
                    </div>
                </div>
            </section>
            <!-- 数据图表 -->
            <section class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
                <!-- 上行数据图表 -->
                <div class="bg-white rounded-lg p-4 card-shadow">
                    <div class="flex justify-between items-center mb-4">
                        <h2 class="text-lg font-semibold text-dark">上行数据趋势</h2>
                        <div class="flex space-x-2">
                            <button class="px-3 py-1 text-sm bg-blue-100 text-primary rounded-md">小时</button>
                            <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">天</button>
                            <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">周</button>
                        </div>
                    </div>
                    <div class="h-64">
                        <canvas id="upDataChart"></canvas>
                    </div>
                </div>
                <!-- 下行数据图表 -->
                <div class="bg-white rounded-lg p-4 card-shadow">
                    <div class="flex justify-between items-center mb-4">
                        <h2 class="text-lg font-semibold text-dark">下行数据趋势</h2>
                        <div class="flex space-x-2">
                            <button class="px-3 py-1 text-sm bg-blue-100 text-primary rounded-md">小时</button>
                            <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">天</button>
                            <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">周</button>
                        </div>
                    </div>
                    <div class="h-64">
                        <canvas id="downDataChart"></canvas>
                    </div>
                </div>
            </section>
            <!-- 设备数据表格 -->
            <section class="bg-white rounded-lg p-4 card-shadow">
                <div class="flex justify-between items-center mb-4">
                    <h2 class="text-lg font-semibold text-dark">CTU小车数据列表</h2>
                    <div class="flex space-x-2">
                        <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">
                            <i class="fa fa-filter mr-1"></i> 筛选
                        </button>
                        <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">
                            <i class="fa fa-download mr-1"></i> 导出
                        </button>
                    </div>
                </div>
                <div class="overflow-x-auto">
                    <table class="min-w-full divide-y divide-gray-200">
                        <thead>
                            <tr>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">设备ID</th>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">设备名称</th>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">上行数据</th>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">下行数据</th>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后通信</th>
                                <th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
                            </tr>
                        </thead>
                        <tbody class="bg-white divide-y divide-gray-200" id="deviceTableBody">
                            <!-- 表格数据将通过JavaScript动态生成 -->
                        </tbody>
                    </table>
                </div>
                <div class="flex justify-between items-center mt-4">
                    <p class="text-sm text-gray-500">显示 1-10 条,共 24 条</p>
                    <div class="flex space-x-1">
                        <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md disabled:opacity-50" disabled>上一页</button>
                        <button class="px-3 py-1 text-sm bg-primary text-white rounded-md">1</button>
                        <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">2</button>
                        <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">3</button>
                        <button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">下一页</button>
                    </div>
                </div>
            </section>
            <!-- 实时数据更新 -->
            <section class="bg-white rounded-lg p-4 card-shadow mt-6">
                <h2 class="text-lg font-semibold text-dark mb-4">实时数据更新</h2>
                <div class="h-40 overflow-y-auto p-2 border border-gray-200 rounded-lg" id="realtimeData">
                    <!-- 实时数据将通过JavaScript动态生成 -->
                </div>
            </section>
        </main>
        <!-- 页脚 -->
        <footer class="bg-white card-shadow mt-6 py-4">
            <div class="container mx-auto px-4 text-center text-gray-500 text-sm">
                <p>© 2026 物联网设备数据监控系统 | 版本 1.0.0</p>
            </div>
        </footer>
    </div>
    <script>
        // 模拟CTU小车数据
        const devices = [
            { id: 'CTU-001', name: '配送小车1号', status: 'online', upData: '2.4KB', downData: '0.8KB', lastComm: '2分钟前' },
            { id: 'CTU-002', name: '配送小车2号', status: 'online', upData: '1.8KB', downData: '0.5KB', lastComm: '5分钟前' },
            { id: 'CTU-003', name: '巡检小车1号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '2小时前' },
            { id: 'CTU-004', name: '配送小车3号', status: 'online', upData: '3.2KB', downData: '1.2KB', lastComm: '1分钟前' },
            { id: 'CTU-005', name: '巡检小车2号', status: 'online', upData: '1.5KB', downData: '0.6KB', lastComm: '3分钟前' },
            { id: 'CTU-006', name: '配送小车4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '5小时前' },
            { id: 'CTU-007', name: '巡检小车3号', status: 'online', upData: '2.1KB', downData: '0.9KB', lastComm: '4分钟前' },
            { id: 'CTU-008', name: '配送小车5号', status: 'online', upData: '2.8KB', downData: '1.1KB', lastComm: '2分钟前' },
            { id: 'CTU-009', name: '巡检小车4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '1天前' },
            { id: 'CTU-010', name: '配送小车6号', status: 'online', upData: '1.9KB', downData: '0.7KB', lastComm: '6分钟前' }
        ];
        // 生成表格数据
        function generateTableData() {
            const tbody = document.getElementById('deviceTableBody');
            tbody.innerHTML = '';
            devices.forEach(device => {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${device.id}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.name}</td>
                    <td class="px-6 py-4 whitespace-nowrap">
                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${device.status === 'online' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
                            ${device.status === 'online' ? '在线' : '离线'}
                        </span>
                    </td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.upData}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.downData}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.lastComm}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                        <button class="text-primary hover:text-blue-700 mr-3">
                            <i class="fa fa-eye"></i>
                        </button>
                        <button class="text-warning hover:text-yellow-700 mr-3">
                            <i class="fa fa-edit"></i>
                        </button>
                        <button class="text-danger hover:text-red-700">
                            <i class="fa fa-trash"></i>
                        </button>
                    </td>
                `;
                tbody.appendChild(row);
            });
        }
        // 初始化图表
        function initCharts() {
            // 上行数据图表
            const upCtx = document.getElementById('upDataChart').getContext('2d');
            const upDataChart = new Chart(upCtx, {
                type: 'line',
                data: {
                    labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                    datasets: [{
                        label: '上行数据 (KB)',
                        data: [12, 19, 15, 25, 22, 30, 28, 35],
                        borderColor: '#3b82f6',
                        backgroundColor: 'rgba(59, 130, 246, 0.1)',
                        tension: 0.4,
                        fill: true
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: {
                            display: false
                        }
                    },
                    scales: {
                        y: {
                            beginAtZero: true
                        }
                    }
                }
            });
            // 下行数据图表
            const downCtx = document.getElementById('downDataChart').getContext('2d');
            const downDataChart = new Chart(downCtx, {
                type: 'line',
                data: {
                    labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                    datasets: [{
                        label: '下行数据 (KB)',
                        data: [5, 8, 6, 12, 10, 15, 13, 18],
                        borderColor: '#10b981',
                        backgroundColor: 'rgba(16, 185, 129, 0.1)',
                        tension: 0.4,
                        fill: true
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: {
                            display: false
                        }
                    },
                    scales: {
                        y: {
                            beginAtZero: true
                        }
                    }
                }
            });
        }
        // 模拟实时数据更新
        function simulateRealtimeData() {
            const realtimeContainer = document.getElementById('realtimeData');
            const messages = [
                'CTU-001 配送小车1号: 运行中,位置: A1区',
                'CTU-002 配送小车2号: 待机中,位置: B2区',
                'CTU-004 配送小车3号: 充电中,电量: 85%',
                'CTU-005 巡检小车2号: 巡检中,已完成3/5任务',
                'CTU-007 巡检小车3号: 待机中,位置: C3区',
                'CTU-008 配送小车5号: 运行中,位置: D4区'
            ];
            setInterval(() => {
                const message = messages[Math.floor(Math.random() * messages.length)];
                const timestamp = new Date().toLocaleTimeString();
                const div = document.createElement('div');
                div.className = 'py-1 border-b border-gray-100';
                div.innerHTML = `<span class="text-gray-500 text-xs">${timestamp}</span> <span class="text-gray-900">${message}</span>`;
                realtimeContainer.prepend(div);
                // 限制显示条数
                if (realtimeContainer.children.length > 20) {
                    realtimeContainer.removeChild(realtimeContainer.lastChild);
                }
            }, 2000);
        }
        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            generateTableData();
            initCharts();
            simulateRealtimeData();
        });
    </script>
</body>
</html>