jiangping
2025-02-14 c185de12314b8733f23ed7856e6d1e87ee95c1ca
jtt808初始化
已添加25个文件
已修改11个文件
2868 ■■■■■ 文件已修改
server/jtt808_parent/jtt808-protocol/src/main/java/org/yzh/protocol/t808/T0200.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/jtt808_parent/jtt808-server/src/main/java/org/yzh/web/endpoint/JT808Endpoint.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/utils/DateUtil.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/wx/WxMiniConfig.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Bikes.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/BikesService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/BikesServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/InterfaceApplication.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/config/swagger/SwaggerConfig.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/t808/T0200.java 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/t808/T0201_0500.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/t808/T0A00_8A00.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/t808/T8103.java 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/t808/T8900.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/config/BeanConfig.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/config/JTBeanConfig.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/config/JTConfig.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/config/WebLogAdapter.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/config/WebMvcConfig.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/controller/ExceptionController.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/controller/JT1078Controller.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/controller/JT808Controller.java 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/controller/OtherController.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JSATL12Endpoint.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT1078Endpoint.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT808Endpoint.java 255 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTHandlerInterceptor.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTMultiPacketListener.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTSessionListener.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/endpoint/MessageManager.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/model/entity/DeviceDO.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/model/enums/SessionKey.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/model/vo/DeviceInfo.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/jtt808/web/service/FileService.java 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/resources/application.yml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/jtt808_parent/jtt808-protocol/src/main/java/org/yzh/protocol/t808/T0200.java
@@ -9,6 +9,7 @@
import org.yzh.protocol.commons.transform.AttributeConverter;
import org.yzh.protocol.commons.transform.AttributeConverterYue;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
server/jtt808_parent/jtt808-server/src/main/java/org/yzh/web/endpoint/JT808Endpoint.java
@@ -1,4 +1,4 @@
package org.yzh.web.endpoint;
package com.doumee.jtt808.web.endpoint;
import com.alibaba.fastjson.JSONObject;
import io.github.yezhihao.netmc.core.annotation.Async;
server/services/pom.xml
@@ -16,4 +16,5 @@
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>
server/services/src/main/java/com/doumee/core/utils/DateUtil.java
@@ -10,6 +10,9 @@
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.*;
@@ -39,7 +42,18 @@
    public DateUtil() {
    }
    public static Date getDateFromLocalDateTime(  LocalDateTime localDateTime) {
        try {
            ZoneId zoneId = ZoneId.systemDefault(); // èŽ·å–ç³»ç»Ÿé»˜è®¤æ—¶åŒº
            ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId); // è½¬æ¢ä¸ºå¸¦æ—¶åŒºçš„æ—¥æœŸæ—¶é—´
            Date date = Date.from(zonedDateTime.toInstant()); // è½¬æ¢ä¸ºDate
            return  date;
        }catch (Exception e){
        }
        return null;
    }
    public static Date StringToDate2(String DATE) {
        if(StringUtils.isBlank(DATE)){
            return null;
server/services/src/main/java/com/doumee/core/wx/WxMiniConfig.java
@@ -78,20 +78,24 @@
    /**
     * åˆå§‹åŒ–微信小程序支付
     */
    public void load_wxPayService()
    {
        Config config =
                new RSAAutoCertificateConfig.Builder()
                        .merchantId(wxPayProperties.getMchId())
                        .privateKeyFromPath(wxPayProperties.getPrivateKeyPath())
                        .merchantSerialNumber(wxPayProperties.getSerialNumer())
                        .apiV3Key(wxPayProperties.getApiV3Key())
                        .build();
        this.wxPayService =  new JsapiService.Builder().config(config).build();
    public void load_wxPayService()   {
        try {
            Config config =
                    new RSAAutoCertificateConfig.Builder()
                            .merchantId(wxPayProperties.getMchId())
                            .privateKeyFromPath(wxPayProperties.getPrivateKeyPath())
                            .merchantSerialNumber(wxPayProperties.getSerialNumer())
                            .apiV3Key(wxPayProperties.getApiV3Key())
                            .build();
            this.wxPayService =  new JsapiService.Builder().config(config).build();
        this.jsapiExtService =  new JsapiServiceExtension.Builder().config(config).build();
        this.refundService = new RefundService.Builder().config(config).build();
        this.billDownloadService = new BillDownloadService.Builder().config(config).build();;
            this.jsapiExtService =  new JsapiServiceExtension.Builder().config(config).build();
            this.refundService = new RefundService.Builder().config(config).build();
            this.billDownloadService = new BillDownloadService.Builder().config(config).build();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * åˆå§‹åŒ–微信小程序支付
server/services/src/main/java/com/doumee/dao/business/model/Bikes.java
@@ -9,6 +9,8 @@
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
@@ -97,5 +99,29 @@
    @ExcelColumn(name="")
    //@JsonFormat(pattern = "yyyy-MM-dd")
    private Date soldoutDate;
    @ApiModelProperty(value = "车辆类型 0自行车 1电车")
    @ExcelColumn(name="车辆类型 0自行车 1电车")
    private Integer type;
    @ApiModelProperty(value = "最近纬度")
    @ExcelColumn(name="最近纬度")
    private BigDecimal latitude;
    @ApiModelProperty(value = "最近经度")
    @ExcelColumn(name="最近经度")
    private BigDecimal longitude;
    @ApiModelProperty(value = "电车控制器SN码")
    @ExcelColumn(name="电车控制器SN码")
    private String deviceSn;
    @ApiModelProperty(value = "当前电压值")
    @ExcelColumn(name="当前电压值")
    private BigDecimal voltage;
    @ApiModelProperty(value = "最后心跳时间")
    @ExcelColumn(name="最后心跳时间")
    private Date heartDate;
    @ApiModelProperty(value = "终端通讯地址")
    @ExcelColumn(name="终端通讯地址")
    private String remoteAddress;
    @ApiModelProperty(value = "最近定位地址")
    @ExcelColumn(name="最近定位地址")
    private String location;
}
server/services/src/main/java/com/doumee/service/business/BikesService.java
@@ -97,4 +97,5 @@
    PageData<Bikes> findJoinPage(PageWrap<Bikes> pageWrap);
    void updateByJtt( Bikes m);
}
server/services/src/main/java/com/doumee/service/business/impl/BikesServiceImpl.java
@@ -91,6 +91,26 @@
    public Bikes findById(String id) {
        return bikesMapper.selectById(id);
    }
    @Override
    public  void updateByJtt( Bikes m){
        if(StringUtils.isBlank(m.getDeviceSn() )){
            return;
        }
        Bikes bikes = bikesJoinMapper.selectOne(new MPJLambdaWrapper<Bikes>()
                        .eq(Bikes::getDeviceSn,String.format("%012s",m.getDeviceSn()))
                        .eq(Bikes::getIsdeleted,Constants.ZERO)
                        .eq(Bikes::getType,Constants.ONE)
                        .last("limit 1"));
        if(bikes == null){
            return;
        }
        bikesJoinMapper.update(null,new UpdateWrapper<Bikes>().lambda()
                 .set(m.getLatitude()!=null,Bikes::getLatitude,m.getLatitude())
                 .set(m.getVoltage()!=null,Bikes::getVoltage,m.getVoltage())
                 .set(m.getLongitude()!=null,Bikes::getLongitude,m.getLongitude())
                 .set(m.getHeartDate()!=null,Bikes::getHeartDate,m.getHeartDate())
                .eq(Bikes::getId,bikes.getId()));
    }
    @Override
    public Bikes findOne(Bikes bikes) {
server/web/src/main/java/com/doumee/InterfaceApplication.java
@@ -17,8 +17,8 @@
@EnableAsync
@MapperScan("com.doumee.dao")
public class InterfaceApplication {
    public static void main(String[] args) {
        System.setProperty("com.zaxxer.hikari.aliveBypassWindowMs", "2000");
        ApplicationContext context = SpringApplication.run(InterfaceApplication.class);
        context.getEnvironment();
    }
server/web/src/main/java/com/doumee/config/swagger/SwaggerConfig.java
@@ -64,6 +64,19 @@
                .build()
                .globalOperationParameters(this.getParameterList());
    }
    @Bean
    public Docket getDocket4() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(this.getApiInfo()).groupName("【JTT808接口API】")
                .host(host)
                .select()
                .apis( basePackage("com.doumee.jtt808.web"))
                // è®¾ç½®éœ€è¦è¢«æ‰«æçš„类,这里设置为添加了@Api注解的类
//                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(this.getParameterList());
    }
    private List<Parameter> getParameterList() {
        ParameterBuilder tokenPar = new ParameterBuilder();
server/web/src/main/java/com/doumee/jtt808/t808/T0200.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,191 @@
package com.doumee.jtt808.t808;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.github.yezhihao.protostar.annotation.Field;
import io.github.yezhihao.protostar.annotation.Message;
import lombok.Data;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.commons.transform.AttributeConverter;
import org.yzh.protocol.commons.transform.AttributeConverterYue;
import java.time.LocalDateTime;
import java.util.Map;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@JsonIgnoreProperties({"attributes",
        "alarmList",
        "dateTime",
        "messageId",
        "properties",
        "protocolVersion",
        "clientId", "serialNo",
        "packageTotal", "packageNo",
        "verified",
        "bodyLength",
        "encryption",
        "subpackage",
        "version",
        "reserved"})
@Message(JT808.位置信息汇报)
@Data
public class T0200 extends JTMessage {
    @Field(length = 4, desc = "报警标志")
    private int warnBit;
    @Field(length = 4, desc = "状态")
    private int statusBit;
    @Field(length = 4, desc = "纬度")
    private int latitude;
    @Field(length = 4, desc = "经度")
    private int longitude;
    @Field(length = 2, desc = "高程(ç±³)")
    private int altitude;
    @Field(length = 2, desc = "速度(1/10公里每小时)")
    private int speed;
    @Field(length = 2, desc = "方向")
    private int direction;
//    @Field(length = 6, charset = "BCD", desc = "时间(YYMMDDHHMMSS)")
//    private LocalDateTime deviceTime;
    @Field(length = 6, charset = "BCD", desc = "时间(YYYY-MM-DDTHH-mm-ss)")
    private LocalDateTime deviceTime;
    @Field(converter = AttributeConverter.class, desc = "位置附加信息", version = {-1, 0})
    @Field(converter = AttributeConverterYue.class, desc = "位置附加信息(粤标)", version = 1)
    private Map<Integer, Object> attributes;
    public int getWarnBit() {
        return warnBit;
    }
    public void setWarnBit(int warnBit) {
        this.warnBit = warnBit;
    }
    public int getStatusBit() {
        return statusBit;
    }
    public void setStatusBit(int statusBit) {
        this.statusBit = statusBit;
    }
    public int getLatitude() {
        return latitude;
    }
    public void setLatitude(int latitude) {
        this.latitude = latitude;
    }
    public int getLongitude() {
        return longitude;
    }
    public void setLongitude(int longitude) {
        this.longitude = longitude;
    }
    public int getAltitude() {
        return altitude;
    }
    public void setAltitude(int altitude) {
        this.altitude = altitude;
    }
    public int getSpeed() {
        return speed;
    }
    public void setSpeed(int speed) {
        this.speed = speed;
    }
    public int getDirection() {
        return direction;
    }
    public void setDirection(int direction) {
        this.direction = direction;
    }
    public LocalDateTime getDeviceTime() {
        return deviceTime;
    }
    public void setDeviceTime(LocalDateTime deviceTime) {
        this.deviceTime = deviceTime;
    }
    public int getAttributeInt(int key) {
        if (attributes != null) {
            Integer value = (Integer) attributes.get(key);
            if (value != null) {
                return value;
            }
        }
        return 0;
    }
    public long getAttributeLong(int key) {
        if (attributes != null) {
            Long value = (Long) attributes.get(key);
            if (value != null) {
                return value;
            }
        }
        return 0L;
    }
    private boolean updated;
    private double lng;
    private double lat;
    private float speedKph;
    @Override
    public boolean transform() {
        if (deviceTime == null)
            return false;
        lng = longitude / 1000000d;
        lat = latitude / 1000000d;
        speedKph = speed / 10f;
        return true;
    }
    public boolean updated() {
        return updated || !(updated = true);
    }
    public double getLng() {
        return lng;
    }
    public double getLat() {
        return lat;
    }
    public float getSpeedKph() {
        return speedKph;
    }
    @Override
    public String toString() {
        StringBuilder sb = toStringHead();
        sb.append("T0200{deviceTime=").append(deviceTime);
        sb.append(",longitude=").append(longitude);
        sb.append(",latitude=").append(latitude);
        sb.append(",altitude=").append(altitude);
        sb.append(",speed=").append(speed);
        sb.append(",direction=").append(direction);
        sb.append(",warnBit=").append(Integer.toBinaryString(warnBit));
        sb.append(",statusBit=").append(Integer.toBinaryString(statusBit));
        sb.append(",attributes=").append(attributes);
        sb.append('}');
        return sb.toString();
    }
}
server/web/src/main/java/com/doumee/jtt808/t808/T0201_0500.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.doumee.jtt808.t808;
import io.github.yezhihao.netmc.core.model.Response;
import io.github.yezhihao.protostar.annotation.Field;
import io.github.yezhihao.protostar.annotation.MergeSuperclass;
import io.github.yezhihao.protostar.annotation.Message;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.t808.T0200;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@MergeSuperclass
@Message({JT808.位置信息查询应答, JT808.车辆控制应答})
public class T0201_0500 extends T0200 implements Response {
    @Field(length = 2, desc = "应答流水号")
    private int responseSerialNo;
    @Override
    public int getResponseSerialNo() {
        return responseSerialNo;
    }
    public void setResponseSerialNo(int responseSerialNo) {
        this.responseSerialNo = responseSerialNo;
    }
}
server/web/src/main/java/com/doumee/jtt808/t808/T0A00_8A00.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package com.doumee.jtt808.t808;
import io.github.yezhihao.protostar.annotation.Field;
import io.github.yezhihao.protostar.annotation.Message;
import io.swagger.v3.oas.annotations.media.Schema;
import org.yzh.commons.model.APICodes;
import org.yzh.commons.model.APIException;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import java.util.Base64;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@Message({JT808.平台RSA公钥, JT808.终端RSA公钥})
public class T0A00_8A00 extends JTMessage {
    @Field(length = 4, desc = "RSA公钥{e,n}中的e")
    private int e;
    @Field(length = 128, desc = "RSA公钥{e,n}中的n")
    private byte[] n;
    public T0A00_8A00() {
    }
    public T0A00_8A00(int e, byte[] n) {
        this.e = e;
        this.n = n;
    }
    public int getE() {
        return e;
    }
    public void setE(int e) {
        this.e = e;
    }
    public byte[] getN() {
        return n;
    }
    public void setN(byte[] n) {
        this.n = n;
    }
    @Schema(description = "n(BASE64编码)")
    private String nBase64;
    public String getnBase64() {
        return nBase64;
    }
    public void setnBase64(String nBase64) {
        this.nBase64 = nBase64;
    }
    public T0A00_8A00 build() {
        byte[] src = Base64.getDecoder().decode(n);
        if (src.length == 129) {
            byte[] dest = new byte[128];
            System.arraycopy(src, 1, dest, 0, 128);
            src = dest;
        }
        if (src.length != 128)
            throw new APIException(APICodes.InvalidParameter, "e length is not 128");
        this.n = src;
        return this;
    }
}
server/web/src/main/java/com/doumee/jtt808/t808/T8103.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
package com.doumee.jtt808.t808;
import io.github.yezhihao.netmc.util.AdapterMap;
import io.github.yezhihao.protostar.annotation.Field;
import io.github.yezhihao.protostar.annotation.Message;
import io.swagger.v3.oas.annotations.media.Schema;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.commons.transform.ParameterConverter;
import org.yzh.protocol.commons.transform.parameter.*;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@Message(JT808.设置终端参数)
public class T8103 extends JTMessage {
    @Field(length = 1, desc = "参数总数")
    private int total;
    @Field(desc = "参数项列表", converter = ParameterConverter.class)
    private Map<Integer, Object> parameters;
    public T8103() {
    }
    public T8103(Map<Integer, Object> parameters) {
        this.parameters = parameters;
        this.total = parameters.size();
    }
    public int getTotal() {
        return total;
    }
    public void setTotal(int total) {
        this.total = total;
    }
    public Map<Integer, Object> getParameters() {
        return parameters;
    }
    public void setParameters(Map<Integer, Object> parameters) {
        this.parameters = parameters;
        this.total = parameters.size();
    }
    public T8103 addParameter(Integer key, Object value) {
        if (parameters == null)
            parameters = new TreeMap();
        parameters.put(key, value);
        total = parameters.size();
        return this;
    }
    @Schema(description = "数值型参数列表(BYTE、WORD)")
    private Map<Integer, Integer> parametersInt;
    @Schema(description = "数值型参数列表(DWORD、QWORD)")
    private Map<Integer, String> parametersLong;
    @Schema(description = "字符型参数列表")
    private Map<Integer, String> parametersStr;
    @Schema(description = "图像分析报警参数设置(1078)")
    private ParamImageIdentifyAlarm paramImageIdentifyAlarm;
    @Schema(description = "特殊报警录像参数设置(1078)")
    private ParamVideoSpecialAlarm paramVideoSpecialAlarm;
    @Schema(description = "音视频通道列表设置(1078)")
    private ParamChannels paramChannels;
    @Schema(description = "终端休眠唤醒模式设置数据格式(1078)")
    private ParamSleepWake paramSleepWake;
    @Schema(description = "音视频参数设置(1078)")
    private ParamVideo paramVideo;
    @Schema(description = "单独视频通道参数设置(1078)")
    private ParamVideoSingle paramVideoSingle;
    @Schema(description = "盲区监测系统参数(苏标)")
    private ParamBSD paramBSD;
    @Schema(description = "胎压监测系统参数(苏标)")
    private ParamTPMS paramTPMS;
    @Schema(description = "驾驶员状态监测系统参数(苏标)")
    private ParamDSM paramDSM;
    @Schema(description = "高级驾驶辅助系统参数(苏标)")
    private ParamADAS paramADAS;
    public Map<Integer, Integer> getParametersInt() {
        return parametersInt;
    }
    public void setParametersInt(Map<Integer, Integer> parametersInt) {
        this.parametersInt = parametersInt;
    }
    public Map<Integer, String> getParametersLong() {
        return parametersLong;
    }
    public void setParametersLong(Map<Integer, String> parametersLong) {
        this.parametersLong = parametersLong;
    }
    public Map<Integer, String> getParametersStr() {
        return parametersStr;
    }
    public void setParametersStr(Map<Integer, String> parametersStr) {
        this.parametersStr = parametersStr;
    }
    public ParamADAS getParamADAS() {
        return paramADAS;
    }
    public void setParamADAS(ParamADAS paramADAS) {
        this.paramADAS = paramADAS;
    }
    public ParamBSD getParamBSD() {
        return paramBSD;
    }
    public void setParamBSD(ParamBSD paramBSD) {
        this.paramBSD = paramBSD;
    }
    public ParamChannels getParamChannels() {
        return paramChannels;
    }
    public void setParamChannels(ParamChannels paramChannels) {
        this.paramChannels = paramChannels;
    }
    public ParamDSM getParamDSM() {
        return paramDSM;
    }
    public void setParamDSM(ParamDSM paramDSM) {
        this.paramDSM = paramDSM;
    }
    public ParamImageIdentifyAlarm getParamImageIdentifyAlarm() {
        return paramImageIdentifyAlarm;
    }
    public void setParamImageIdentifyAlarm(ParamImageIdentifyAlarm paramImageIdentifyAlarm) {
        this.paramImageIdentifyAlarm = paramImageIdentifyAlarm;
    }
    public ParamSleepWake getParamSleepWake() {
        return paramSleepWake;
    }
    public void setParamSleepWake(ParamSleepWake paramSleepWake) {
        this.paramSleepWake = paramSleepWake;
    }
    public ParamTPMS getParamTPMS() {
        return paramTPMS;
    }
    public void setParamTPMS(ParamTPMS paramTPMS) {
        this.paramTPMS = paramTPMS;
    }
    public ParamVideo getParamVideo() {
        return paramVideo;
    }
    public void setParamVideo(ParamVideo paramVideo) {
        this.paramVideo = paramVideo;
    }
    public ParamVideoSingle getParamVideoSingle() {
        return paramVideoSingle;
    }
    public void setParamVideoSingle(ParamVideoSingle paramVideoSingle) {
        this.paramVideoSingle = paramVideoSingle;
    }
    public ParamVideoSpecialAlarm getParamVideoSpecialAlarm() {
        return paramVideoSpecialAlarm;
    }
    public void setParamVideoSpecialAlarm(ParamVideoSpecialAlarm paramVideoSpecialAlarm) {
        this.paramVideoSpecialAlarm = paramVideoSpecialAlarm;
    }
    public T8103 build() {
        Map<Integer, Object> map = new TreeMap<>();
        if (parametersInt != null && !parametersInt.isEmpty())
            map.putAll(parametersInt);
        if (parametersLong != null && !parametersLong.isEmpty())
            map.putAll(new AdapterMap(parametersLong, (Function<String, Long>) Long::parseLong));
        if (parametersStr != null && !parametersStr.isEmpty())
            map.putAll(parametersStr);
        if (paramADAS != null)
            map.put(paramADAS.key, paramADAS);
        if (paramBSD != null)
            map.put(paramBSD.key, paramBSD);
        if (paramChannels != null)
            map.put(paramChannels.key, paramChannels);
        if (paramDSM != null)
            map.put(paramDSM.key, paramDSM);
        if (paramImageIdentifyAlarm != null)
            map.put(paramImageIdentifyAlarm.key, paramImageIdentifyAlarm);
        if (paramSleepWake != null)
            map.put(paramSleepWake.key, paramSleepWake);
        if (paramTPMS != null)
            map.put(paramTPMS.key, paramTPMS);
        if (paramVideo != null)
            map.put(paramVideo.key, paramVideo);
        if (paramVideoSingle != null)
            map.put(paramVideoSingle.key, paramVideoSingle);
        if (paramVideoSpecialAlarm != null)
            map.put(paramVideoSpecialAlarm.key, paramVideoSpecialAlarm);
        this.total = map.size();
        this.parameters = map;
        return this;
    }
}
server/web/src/main/java/com/doumee/jtt808/t808/T8900.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
package com.doumee.jtt808.t808;
import io.github.yezhihao.protostar.annotation.Field;
import io.github.yezhihao.protostar.annotation.Message;
import io.github.yezhihao.protostar.util.KeyValuePair;
import io.swagger.v3.oas.annotations.media.Schema;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.commons.transform.PassthroughConverter;
import org.yzh.protocol.commons.transform.passthrough.PeripheralStatus;
import org.yzh.protocol.commons.transform.passthrough.PeripheralSystem;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@Message(JT808.数据下行透传)
public class T8900 extends JTMessage {
    /** GNSS模块详细定位数据 */
    public static final int GNSSLocation = 0x00;
    /** é“路运输证IC卡信息上传消息为64Byte,下传消息为24Byte,道路运输证IC卡认证透传超时时间为30s.超时后,不重发 */
    public static final int ICCardInfo = 0x0B;
    /** ä¸²å£1透传消息 */
    public static final int SerialPortOne = 0x41;
    /** ä¸²å£2透传消息 */
    public static final int SerialPortTow = 0x42;
    /** ç”¨æˆ·è‡ªå®šä¹‰é€ä¼  0xF0~0xFF */
    public static final int Custom = 0xF0;
    @Field(desc = "透传消息", converter = PassthroughConverter.class)
    private KeyValuePair<Integer, Object> message;
    public T8900() {
    }
    public T8900(KeyValuePair<Integer, Object> message) {
        this.message = message;
    }
    public KeyValuePair<Integer, Object> getMessage() {
        return message;
    }
    public void setMessage(KeyValuePair<Integer, Object> message) {
        this.message = message;
    }
    @Schema(description = "状态查询(外设状态信息:外设工作状态、设备报警信息)")
    private PeripheralStatus peripheralStatus;
    @Schema(description = "信息查询(外设传感器的基本信息:公司信息、产品代码、版本号、外设ID、客户代码)")
    private PeripheralSystem peripheralSystem;
    public PeripheralStatus getPeripheralStatus() {
        return peripheralStatus;
    }
    public void setPeripheralStatus(PeripheralStatus peripheralStatus) {
        this.peripheralStatus = peripheralStatus;
    }
    public PeripheralSystem getPeripheralSystem() {
        return peripheralSystem;
    }
    public void setPeripheralSystem(PeripheralSystem peripheralSystem) {
        this.peripheralSystem = peripheralSystem;
    }
    public T8900 build() {
        KeyValuePair<Integer, Object> message = new KeyValuePair<>();
        if (peripheralStatus != null) {
            message.setKey(PeripheralStatus.key);
            message.setValue(peripheralStatus);
        } else if (peripheralSystem != null) {
            message.setKey(PeripheralSystem.key);
            message.setValue(peripheralSystem);
        }
        this.message = message;
        return this;
    }
}
server/web/src/main/java/com/doumee/jtt808/web/config/BeanConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package com.doumee.jtt808.web.config;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.yzh.commons.util.DateUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
@Configuration
public class BeanConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(500L)
                .expireAfterWrite(30, TimeUnit.MINUTES));
        return manager;
    }
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizeJackson2ObjectMapper() {
        return builder -> {
            SimpleModule longModule = new SimpleModule();
            longModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
            longModule.addSerializer(Long.class, ToStringSerializer.instance);
            JavaTimeModule timeModule = new JavaTimeModule();
            timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateUtils.DATE_TIME_FORMATTER));
            timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateUtils.DATE_TIME_FORMATTER));
            timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
            timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
            timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
            timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
            builder.modules(longModule, timeModule);
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, true);//忽略循环引用
//            mapper.configure(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL, true);//循环引用写入null
            mapper.setSerializationInclusion(Include.NON_NULL);
            mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
            mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);// å…¼å®¹é«˜å¾·åœ°å›¾api
            builder.configure(mapper);
        };
    }
}
server/web/src/main/java/com/doumee/jtt808/web/config/JTBeanConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.doumee.jtt808.web.config;
import io.github.yezhihao.netmc.core.HandlerMapping;
import io.github.yezhihao.netmc.core.SpringHandlerMapping;
import io.github.yezhihao.netmc.session.SessionListener;
import io.github.yezhihao.netmc.session.SessionManager;
import io.github.yezhihao.protostar.SchemaManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.yzh.protocol.codec.DataFrameMessageDecoder;
import org.yzh.protocol.codec.JTMessageAdapter;
import org.yzh.protocol.codec.JTMessageEncoder;
import org.yzh.protocol.codec.MultiPacketDecoder;
import com.doumee.jtt808.web.endpoint.JTHandlerInterceptor;
import com.doumee.jtt808.web.endpoint.JTMultiPacketListener;
import com.doumee.jtt808.web.endpoint.JTSessionListener;
import com.doumee.jtt808.web.model.enums.SessionKey;
@Configuration
public class JTBeanConfig {
    @Bean
    public HandlerMapping handlerMapping() {
        return new SpringHandlerMapping();
    }
    @Bean
    public JTHandlerInterceptor handlerInterceptor() {
        return new JTHandlerInterceptor();
    }
    @Bean
    public SessionListener sessionListener() {
        return new JTSessionListener();
    }
    @Bean
    public SessionManager sessionManager(SessionListener sessionListener) {
        return new SessionManager(SessionKey.class, sessionListener);
    }
    @Bean
    public SchemaManager schemaManager() {
        return new SchemaManager("org.yzh.protocol");
    }
    @Bean
    public JTMessageAdapter messageAdapter(SchemaManager schemaManager) {
        JTMessageEncoder encoder = new JTMessageEncoder(schemaManager);
        MultiPacketDecoder decoder = new MultiPacketDecoder(schemaManager, new JTMultiPacketListener(10));
        return new WebLogAdapter(encoder, decoder);
    }
    @Bean
    public JTMessageAdapter alarmFileMessageAdapter(SchemaManager schemaManager) {
        JTMessageEncoder encoder = new JTMessageEncoder(schemaManager);
        DataFrameMessageDecoder decoder = new DataFrameMessageDecoder(schemaManager, new byte[]{0x30, 0x31, 0x63, 0x64});
        return new WebLogAdapter(encoder, decoder);
    }
    @Bean
    public MultiPacketDecoder multiPacketDecoder(SchemaManager schemaManager) {
        return new MultiPacketDecoder(schemaManager);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/config/JTConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
package com.doumee.jtt808.web.config;
import io.github.yezhihao.netmc.NettyConfig;
import io.github.yezhihao.netmc.Server;
import io.github.yezhihao.netmc.codec.Delimiter;
import io.github.yezhihao.netmc.codec.LengthField;
import io.github.yezhihao.netmc.core.HandlerMapping;
import io.github.yezhihao.netmc.session.SessionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.yzh.protocol.codec.JTMessageAdapter;
import com.doumee.jtt808.web.endpoint.JTHandlerInterceptor;
@Order(Integer.MIN_VALUE)
@Configuration
@ConditionalOnProperty(value = "jt-server.jt808.enable", havingValue = "true")
public class JTConfig {
    private final JTMessageAdapter messageAdapter;
    private final HandlerMapping handlerMapping;
    private final JTHandlerInterceptor handlerInterceptor;
    private final SessionManager sessionManager;
    public JTConfig(JTMessageAdapter messageAdapter, HandlerMapping handlerMapping, JTHandlerInterceptor handlerInterceptor, SessionManager sessionManager) {
        this.messageAdapter = messageAdapter;
        this.handlerMapping = handlerMapping;
        this.handlerInterceptor = handlerInterceptor;
        this.sessionManager = sessionManager;
    }
    @ConditionalOnProperty(value = "jt-server.jt808.port.tcp")
    @Bean(initMethod = "start", destroyMethod = "stop")
    public Server jt808TCPServer(@Value("${jt-server.jt808.port.tcp}") int port) {
        return NettyConfig.custom()
                //心跳超时(秒)
                .setIdleStateTime(180, 0, 0)
                .setPort(port)
                //标识位[2] + æ¶ˆæ¯å¤´[21] + æ¶ˆæ¯ä½“[1023 * 2(转义预留)]  + æ ¡éªŒç [1] + æ ‡è¯†ä½[2]
                .setMaxFrameLength(2 + 21 + 1023 * 3 + 1 + 2)
                .setDelimiters(new Delimiter(new byte[]{0x7e}, false))
                .setDecoder(messageAdapter)
                .setEncoder(messageAdapter)
                .setHandlerMapping(handlerMapping)
                .setHandlerInterceptor(handlerInterceptor)
                .setSessionManager(sessionManager)
                .setName("808-TCP")
                .build();
    }
    @ConditionalOnProperty(value = "jt-server.jt808.port.udp")
    @Bean(initMethod = "start", destroyMethod = "stop")
    public Server jt808UDPServer(@Value("${jt-server.jt808.port.udp}") int port) {
        return NettyConfig.custom()
                .setPort(port)
                .setDelimiters(new Delimiter(new byte[]{0x7e}, false))
                .setDecoder(messageAdapter)
                .setEncoder(messageAdapter)
                .setHandlerMapping(handlerMapping)
                .setHandlerInterceptor(handlerInterceptor)
                .setSessionManager(sessionManager)
                .setName("808-UDP")
                .setEnableUDP(true)
                .build();
    }
    @ConditionalOnProperty(value = "jt-server.alarm-file.enable", havingValue = "true")
    @Bean(initMethod = "start", destroyMethod = "stop")
    public Server alarmFileServer(@Value("${jt-server.alarm-file.port}") int port, JTMessageAdapter alarmFileMessageAdapter) {
        return NettyConfig.custom()
                .setPort(port)
                .setMaxFrameLength(2 + 21 + 1023 * 2 + 1 + 2)
                .setLengthField(new LengthField(new byte[]{0x30, 0x31, 0x63, 0x64}, 1024 * 65, 58, 4))
                .setDelimiters(new Delimiter(new byte[]{0x7e}, false))
                .setDecoder(alarmFileMessageAdapter)
                .setEncoder(alarmFileMessageAdapter)
                .setHandlerMapping(handlerMapping)
                .setHandlerInterceptor(handlerInterceptor)
                .setName("AlarmFile")
                .build();
    }
}
server/web/src/main/java/com/doumee/jtt808/web/config/WebLogAdapter.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package com.doumee.jtt808.web.config;
import io.github.yezhihao.netmc.session.Session;
import io.github.yezhihao.protostar.SchemaManager;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.codec.ServerSentEvent;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.codec.JTMessageAdapter;
import org.yzh.protocol.codec.JTMessageDecoder;
import org.yzh.protocol.codec.JTMessageEncoder;
import org.yzh.protocol.commons.JT808;
import reactor.core.publisher.FluxSink;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public class WebLogAdapter extends JTMessageAdapter {
    protected static final Logger log = LoggerFactory.getLogger(WebLogAdapter.class);
    public static final HashMap<String, Set<FluxSink<Object>>> clientIds = new HashMap<>();
    public static final HashSet<Integer> ignoreMsgs = new HashSet<>();
    static {
        ignoreMsgs.add(JT808.定位数据批量上传);
    }
    public WebLogAdapter(SchemaManager schemaManager) {
        super(schemaManager);
    }
    public WebLogAdapter(JTMessageEncoder messageEncoder, JTMessageDecoder messageDecoder) {
        super(messageEncoder, messageDecoder);
    }
    @Override
    public void encodeLog(Session session, JTMessage message, ByteBuf output) {
        Set<FluxSink<Object>> emitters = clientIds.get(message.getClientId());
        if (emitters != null) {
            ServerSentEvent<Object> event = ServerSentEvent.builder().event(message.getClientId())
                    .data(message + "hex:" + ByteBufUtil.hexDump(output, 0, output.writerIndex())).build();
            for (FluxSink<Object> emitter : emitters) {
                emitter.next(event);
            }
        }
        if ((!ignoreMsgs.contains(message.getMessageId())) && (emitters != null || clientIds.isEmpty()))
            super.encodeLog(session, message, output);
    }
    @Override
    public void decodeLog(Session session, JTMessage message, ByteBuf input) {
        if (message != null) {
            Set<FluxSink<Object>> emitters = clientIds.get(message.getClientId());
            if (emitters != null) {
                ServerSentEvent<Object> event = ServerSentEvent.builder().event(message.getClientId())
                        .data(message + "hex:" + ByteBufUtil.hexDump(input, 0, input.writerIndex())).build();
                for (FluxSink<Object> emitter : emitters) {
                    emitter.next(event);
                }
            }
            if (!ignoreMsgs.contains(message.getMessageId()) && (emitters != null || clientIds.isEmpty()))
                super.decodeLog(session, message, input);
            if (!message.isVerified())
                log.error("<<<<<校验码错误session={},payload={}", session, ByteBufUtil.hexDump(input, 0, input.writerIndex()));
        }
    }
    public static void clearMessage() {
        synchronized (ignoreMsgs) {
            ignoreMsgs.clear();
        }
    }
    public static void addMessage(int messageId) {
        if (!ignoreMsgs.contains(messageId)) {
            synchronized (ignoreMsgs) {
                ignoreMsgs.add(messageId);
            }
        }
    }
    public static void removeMessage(int messageId) {
        if (ignoreMsgs.contains(messageId)) {
            synchronized (ignoreMsgs) {
                ignoreMsgs.remove(messageId);
            }
        }
    }
    public static void clearClient() {
        synchronized (clientIds) {
            clientIds.clear();
        }
    }
    public static void addClient(String clientId, FluxSink<Object> emitter) {
        synchronized (clientIds) {
            clientIds.computeIfAbsent(clientId, k -> new HashSet<>()).add(emitter);
        }
    }
    public static void removeClient(String clientId, FluxSink<Object> emitter) {
        synchronized (clientIds) {
            Set<FluxSink<Object>> emitters = clientIds.get(clientId);
            if (emitters != null) {
                emitters.remove(emitter);
                if (emitters.isEmpty()) {
                    clientIds.remove(clientId);
                }
            }
        }
    }
}
server/web/src/main/java/com/doumee/jtt808/web/config/WebMvcConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package com.doumee.jtt808.web.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
//@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    }
    @Override
    public void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**").combine(corsConfig());
        registry.addMapping("/**").allowedOrigins(CorsConfiguration.ALL)
                .allowCredentials(true)
                .allowedMethods(CorsConfiguration.ALL)
                .maxAge(3600L)
                .allowedHeaders(CorsConfiguration.ALL);
    }
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig());
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
    @Bean
    public CorsConfiguration corsConfig() {
        CorsConfiguration config = new CorsConfiguration();
//        config.addAllowedOriginPattern(CorsConfiguration.ALL);
        config.addAllowedOrigin(CorsConfiguration.ALL);
        config.addAllowedMethod(CorsConfiguration.ALL);
        config.addAllowedHeader(CorsConfiguration.ALL);
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);
        return config;
    }
}
server/web/src/main/java/com/doumee/jtt808/web/controller/ExceptionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
package com.doumee.jtt808.web.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.yzh.commons.model.APICodes;
import org.yzh.commons.model.APIException;
import org.yzh.commons.model.APIResult;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@RestControllerAdvice
public class ExceptionController {
    private static final Logger log = LoggerFactory.getLogger(ExceptionController.class);
    private static final Pattern compile = Pattern.compile("'\\w*'");
    @ExceptionHandler(Exception.class)
    public APIResult<?> onException(Exception e) {
        log.error("系统异常", e);
        return new APIResult<>(e);
    }
    @ExceptionHandler(APIException.class)
    public APIResult<?> onAPIException(APIException e) {
        return new APIResult<>(e);
    }
    @ExceptionHandler(SQLException.class)
    public APIResult<?> onSQLException(SQLException e) {
        String message = e.getMessage();
        if (message.endsWith("have a default value"))
            return new APIResult<>(APICodes.MissingParameter, e);
        log.warn("系统异常:", e);
        return new APIResult<>(e);
    }
    @ExceptionHandler(DuplicateKeyException.class)
    public APIResult<?> onDuplicateKeyException(DuplicateKeyException e) {
        Matcher matcher = compile.matcher(e.getCause().getMessage());
        List<String> values = new ArrayList<>(4);
        while (matcher.find())
            values.add(matcher.group());
        int size = values.size();
        int len = size < 2 ? size : (size / 2);
        StringBuilder sb = new StringBuilder(20);
        sb.append("已存在的号码:");
        for (int i = 0; i < len; i++)
            sb.append(values.get(i)).append(',');
        return new APIResult<>(APICodes.InvalidParameter, sb.substring(0, sb.length() - 1));
    }
    @ExceptionHandler(IllegalArgumentException.class)
    public APIResult<?> onIllegalArgumentException(IllegalArgumentException e) {
        log.warn("系统异常:", e);
        return new APIResult<>(APICodes.InvalidParameter, e.getMessage());
    }
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public APIResult<?> onHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.warn("系统异常:", e);
        return new APIResult<>(APICodes.TypeMismatch, e);
    }
    @ExceptionHandler(BindException.class)
    public APIResult<?> onBindException(BindException e) {
        List<FieldError> fieldErrors = e.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError fieldError : fieldErrors)
            sb.append(fieldError.getField()).append(fieldError.getDefaultMessage());
        return new APIResult<>(APICodes.MissingParameter, sb.toString());
    }
    @ExceptionHandler(HttpMediaTypeException.class)
    public APIResult<?> onHttpMediaTypeException(HttpMediaTypeException e) {
        log.warn("系统异常:", e);
        return new APIResult<>(APICodes.NotSupportedType, e.getMessage());
    }
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public APIResult<?> onHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        return new APIResult<>(APICodes.NotImplemented);
    }
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public APIResult<?> onMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        return new APIResult<>(APICodes.MissingParameter, ":" + e.getParameterName());
    }
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public APIResult<?> onMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        return new APIResult<>(APICodes.TypeMismatch, ":" + e.getName() + "=" + e.getValue(), e.getMessage());
    }
}
server/web/src/main/java/com/doumee/jtt808/web/controller/JT1078Controller.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
package com.doumee.jtt808.web.controller;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.yzh.commons.model.APIResult;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT1078;
import org.yzh.protocol.jsatl12.T9208;
import org.yzh.protocol.t1078.*;
import org.yzh.protocol.t808.T0001;
import com.doumee.jtt808.web.endpoint.MessageManager;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("device")
@Api(tags = "JTT1078通信接口")
public class JT1078Controller {
    @Autowired
    private MessageManager messageManager;
    @Operation(summary = "9003 æŸ¥è¯¢ç»ˆç«¯éŸ³è§†é¢‘属性")
    @PostMapping("9003")
    public Mono<APIResult<T1003>> T9003(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT1078.查询终端音视频属性), T1003.class);
    }
    @Operation(summary = "9101 å®žæ—¶éŸ³è§†é¢‘传输请求")
    @PostMapping("9101")
    public Mono<APIResult<T0001>> T9101(@RequestBody T9101 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9102 éŸ³è§†é¢‘实时传输控制")
    @PostMapping("9102")
    public Mono<APIResult<T0001>> T9102(@RequestBody T9102 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9201 å¹³å°ä¸‹å‘远程录像回放请求")
    @PostMapping("9201")
    public Mono<APIResult<T1205>> T9201(@RequestBody T9201 request) {
        return messageManager.requestR(request, T1205.class);
    }
    @Operation(summary = "9202 å¹³å°ä¸‹å‘远程录像回放控制")
    @PostMapping("9202")
    public Mono<APIResult<T0001>> T9202(@RequestBody T9202 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9205 æŸ¥è¯¢èµ„源列表")
    @PostMapping("9205")
    public Mono<APIResult<T1205>> T9205(@RequestBody T9205 request) {
        return messageManager.requestR(request, T1205.class);
    }
    @Operation(summary = "9206 æ–‡ä»¶ä¸Šä¼ æŒ‡ä»¤")
    @PostMapping("9206")
    public Mono<APIResult<T0001>> T9206(@RequestBody T9206 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9207 æ–‡ä»¶ä¸Šä¼ æŽ§åˆ¶")
    @PostMapping("9207")
    public Mono<APIResult<T0001>> T9207(@RequestBody T9207 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9208 æŠ¥è­¦é™„件上传指令(苏标)")
    @PostMapping("9208")
    public Mono<APIResult<T0001>> T9208(@RequestBody T9208 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9301 äº‘台旋转")
    @PostMapping("9301")
    public Mono<APIResult<T0001>> T9301(@RequestBody T9301 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "9302 äº‘台调整焦距控制")
    @PostMapping("9302")
    public Mono<APIResult<T0001>> T9302(@RequestBody T9302 request) {
        return messageManager.requestR(request.messageId(JT1078.云台调整焦距控制), T0001.class);
    }
    @Operation(summary = "9303 äº‘台调整光圈控制")
    @PostMapping("9303")
    public Mono<APIResult<T0001>> T9303(@RequestBody T9302 request) {
        return messageManager.requestR(request.messageId(JT1078.云台调整光圈控制), T0001.class);
    }
    @Operation(summary = "9304 äº‘台雨刷控制")
    @PostMapping("9304")
    public Mono<APIResult<T0001>> T9304(@RequestBody T9302 request) {
        return messageManager.requestR(request.messageId(JT1078.云台雨刷控制), T0001.class);
    }
    @Operation(summary = "9305 çº¢å¤–补光控制")
    @PostMapping("9305")
    public Mono<APIResult<T0001>> T9305(@RequestBody T9302 request) {
        return messageManager.requestR(request.messageId(JT1078.红外补光控制), T0001.class);
    }
    @Operation(summary = "9306 äº‘台变倍控制")
    @PostMapping("9306")
    public Mono<APIResult<T0001>> T9306(@RequestBody T9302 request) {
        return messageManager.requestR(request.messageId(JT1078.云台变倍控制), T0001.class);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/controller/JT808Controller.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
package com.doumee.jtt808.web.controller;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.yzh.commons.model.APIResult;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.t808.*;
import com.doumee.jtt808.web.endpoint.MessageManager;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("jtt808/device")
@Api(tags = "JTT808通信接口")
public class JT808Controller {
    @Autowired
    private MessageManager messageManager;
    @Operation(summary = "8103 è®¾ç½®ç»ˆç«¯å‚æ•°")
    @PostMapping("8103")
    public Mono<APIResult<T0001>> T8103(@RequestBody com.doumee.jtt808.t808.T8103 request) {
        return messageManager.requestR(request.build(), T0001.class);
    }
    @Operation(summary = "8104 æŸ¥è¯¢ç»ˆç«¯å‚æ•°")
    @PostMapping("8104")
    public Mono<APIResult<T0104>> T8104(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT808.查询终端参数), T0104.class);
    }
    @Operation(summary = "8106 æŸ¥è¯¢æŒ‡å®šç»ˆç«¯å‚æ•°")
    @PostMapping("8106")
    public Mono<APIResult<T0104>> T8106(@RequestBody T8106 request) {
        return messageManager.requestR(request, T0104.class);
    }
    @Operation(summary = "8105 ç»ˆç«¯æŽ§åˆ¶")
    @PostMapping("8105")
    public Mono<APIResult<T0001>> T8105(@RequestBody T8105 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8107 æŸ¥è¯¢ç»ˆç«¯å±žæ€§")
    @PostMapping("8107")
    public Mono<APIResult<T0107>> T8107(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT808.查询终端属性), T0107.class);
    }
    @Operation(summary = "8201 ä½ç½®ä¿¡æ¯æŸ¥è¯¢")
    @PostMapping("8201")
    public Mono<APIResult<T0201_0500>> T8201(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT808.位置信息查询), T0201_0500.class);
    }
    @Operation(summary = "8202 ä¸´æ—¶ä½ç½®è·Ÿè¸ªæŽ§åˆ¶")
    @PostMapping("8202")
    public Mono<APIResult<T0001>> T8202(@RequestBody T8202 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8203 äººå·¥ç¡®è®¤æŠ¥è­¦æ¶ˆæ¯")
    @PostMapping("8203")
    public Mono<APIResult<T0001>> T8203(@RequestBody T8203 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8204 æœåŠ¡å™¨å‘ç»ˆç«¯å‘èµ·é“¾è·¯æ£€æµ‹è¯·æ±‚")
    @PostMapping("8204")
    public Mono<APIResult<T0001>> T8204(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT808.服务器向终端发起链路检测请求), T0001.class);
    }
    @Operation(summary = "8300 æ–‡æœ¬ä¿¡æ¯ä¸‹å‘")
    @PostMapping("8300")
    public Mono<APIResult<T0001>> T8300(@RequestBody T8300 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8301 äº‹ä»¶è®¾ç½®")
    @PostMapping("8301")
    public Mono<APIResult<T0001>> T8301(@RequestBody T8301 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8302 æé—®ä¸‹å‘")
    @PostMapping("8302")
    public Mono<APIResult<T0001>> T8302(@RequestBody T8302 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8303 ä¿¡æ¯ç‚¹æ’­èœå•设置")
    @PostMapping("8303")
    public Mono<APIResult<T0001>> T8303(@RequestBody T8303 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8304 ä¿¡æ¯æœåŠ¡")
    @PostMapping("8304")
    public Mono<APIResult<T0001>> T8304(@RequestBody T8304 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8400 ç”µè¯å›žæ‹¨")
    @PostMapping("8400")
    public Mono<APIResult<T0001>> T8400(@RequestBody T8400 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8401 è®¾ç½®ç”µè¯æœ¬")
    @PostMapping("8401")
    public Mono<APIResult<T0001>> T8401(@RequestBody T8401 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8500 è½¦è¾†æŽ§åˆ¶")
    @PostMapping("8500")
    public Mono<APIResult<T0201_0500>> T8500(@RequestBody T8500 request) {
        return messageManager.requestR(request, T0201_0500.class);
    }
    @Operation(summary = "8600 è®¾ç½®åœ†å½¢åŒºåŸŸ")
    @PostMapping("8600")
    public Mono<APIResult<T0001>> T8600(@RequestBody T8600 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8601 åˆ é™¤åœ†å½¢åŒºåŸŸ")
    @PostMapping("8601")
    public Mono<APIResult<T0001>> T8601(@RequestBody T8601 request) {
        return messageManager.requestR(request.messageId(JT808.删除圆形区域), T0001.class);
    }
    @Operation(summary = "8602 è®¾ç½®çŸ©å½¢åŒºåŸŸ")
    @PostMapping("8602")
    public Mono<APIResult<T0001>> T8602(@RequestBody T8602 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8603 åˆ é™¤çŸ©å½¢åŒºåŸŸ")
    @PostMapping("8603")
    public Mono<APIResult<T0001>> T8603(@RequestBody T8601 request) {
        return messageManager.requestR(request.messageId(JT808.删除矩形区域), T0001.class);
    }
    @Operation(summary = "8604 è®¾ç½®å¤šè¾¹å½¢åŒºåŸŸ")
    @PostMapping("8604")
    public Mono<APIResult<T0001>> T8604(@RequestBody T8604 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8605 åˆ é™¤å¤šè¾¹å½¢åŒºåŸŸ")
    @PostMapping("8605")
    public Mono<APIResult<T0001>> T8605(@RequestBody T8601 request) {
        return messageManager.requestR(request.messageId(JT808.删除多边形区域), T0001.class);
    }
    @Operation(summary = "8606 è®¾ç½®è·¯çº¿")
    @PostMapping("8606")
    public Mono<APIResult<T0001>> T8606(@RequestBody T8606 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8607 åˆ é™¤è·¯çº¿")
    @PostMapping("8607")
    public Mono<APIResult<T0001>> T8607(@RequestBody T8601 request) {
        return messageManager.requestR(request.messageId(JT808.删除路线), T0001.class);
    }
    @Operation(summary = "8608 æŸ¥è¯¢åŒºåŸŸæˆ–线路数据")
    @PostMapping("8608")
    public Mono<APIResult<T0608>> T8608(@RequestBody T8608 request) {
        return messageManager.requestR(request, T0608.class);
    }
    @Operation(summary = "8700 è¡Œé©¶è®°å½•仪数据采集命令")
    @PostMapping("8700")
    public Mono<APIResult<T0001>> T8700(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT808.行驶记录仪数据采集命令), T0001.class);
    }
    @Operation(summary =  "8701 è¡Œé©¶è®°å½•仪参数下传命令")
    @PostMapping("8701")
    public Mono<APIResult<T0001>> T8701(@RequestBody T8701 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8702 ä¸ŠæŠ¥é©¾é©¶å‘˜èº«ä»½ä¿¡æ¯è¯·æ±‚")
    @PostMapping("8702")
    public Mono<APIResult<T0702>> T8702(@RequestBody JTMessage request) {
        return messageManager.requestR(request.messageId(JT808.上报驾驶员身份信息请求), T0702.class);
    }
    @Operation(summary = "8801 æ‘„像头立即拍摄命令")
    @PostMapping("8801")
    public Mono<APIResult<T0805>> T8801(@RequestBody T8801 request) {
        return messageManager.requestR(request, T0805.class);
    }
    @Operation(summary = "8802 å­˜å‚¨å¤šåª’体数据检索")
    @PostMapping("8802")
    public Mono<APIResult<T0802>> T8802(@RequestBody T8802 request) {
        return messageManager.requestR(request, T0802.class);
    }
    @Operation(summary = "8803 å­˜å‚¨å¤šåª’体数据上传")
    @PostMapping("8803")
    public Mono<APIResult<T0001>> T8803(@RequestBody T8803 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8804 å½•音开始命令")
    @PostMapping("8804")
    public Mono<APIResult<T0001>> T8804(@RequestBody T8804 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8805 å•条存储多媒体数据检索上传命令")
    @PostMapping("8805")
    public Mono<APIResult<T0001>> T8805(@RequestBody T8805 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8108 ä¸‹å‘终端升级包")
    @PostMapping("8108")
    public Mono<APIResult<T0001>> T8108(@RequestBody T8108 request) {
        return messageManager.requestR(request, T0001.class);
    }
    @Operation(summary = "8900 æ•°æ®ä¸‹è¡Œé€ä¼ ")
    @PostMapping("8900")
    public Mono<APIResult<T0001>> T8900(@RequestBody com.doumee.jtt808.t808.T8900 request) {
        return messageManager.requestR(request.build(), T0001.class);
    }
    @Operation(summary = "8A00 å¹³å°RSA公钥")
    @PostMapping("8A00")
    public Mono<APIResult<T0A00_8A00>> T8A00(@RequestBody com.doumee.jtt808.t808.T0A00_8A00 request) {
        return messageManager.requestR(request.build(), T0A00_8A00.class);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/controller/OtherController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
package com.doumee.jtt808.web.controller;
import io.github.yezhihao.netmc.session.Session;
import io.github.yezhihao.netmc.session.SessionManager;
import io.github.yezhihao.netmc.util.AdapterCollection;
import io.github.yezhihao.protostar.util.Explain;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.yzh.commons.model.APIResult;
import org.yzh.protocol.codec.JTMessageDecoder;
import com.doumee.jtt808.web.config.WebLogAdapter;
import com.doumee.jtt808.web.model.entity.DeviceDO;
import com.doumee.jtt808.web.model.enums.SessionKey;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@RestController
@RequestMapping
@Api(tags = "其他通信接口")
public class OtherController {
    @Autowired
    private SessionManager sessionManager;
    @Autowired
    private JTMessageDecoder decoder;
    @Operation(summary = "终端实时信息查询")
    @GetMapping("device/all")
    public APIResult<Collection<Session>> all() {
        Collection<Session> all = sessionManager.all();
        return APIResult.ok(all);
    }
    @Operation(summary = "获得当前所有在线设备信息")
    @GetMapping("device/option")
    public APIResult<Collection<DeviceDO>> getClientId(HttpSession httpSession) {
        AdapterCollection<Session, DeviceDO> result = new AdapterCollection<>(sessionManager.all(), session -> {
            DeviceDO device = SessionKey.getDevice(session);
            if (device != null)
                return device;
            return new DeviceDO().mobileNo(session.getClientId());
        });
        return APIResult.ok(result);
    }
    @Operation(summary = "设备订阅")
    @PostMapping(value = "device/sse", produces = MediaType.TEXT_PLAIN_VALUE)
    public String sseSub(HttpSession httpSession, @RequestParam String clientId, @RequestParam boolean sub) {
        FluxSink<Object> emitter = (FluxSink<Object>) httpSession.getAttribute("emitter");
        if (emitter == null) {
            return "0";
        }
        if (sub) {
            WebLogAdapter.addClient(clientId, emitter);
            ((Set<String>) httpSession.getAttribute("clientIds")).add(clientId);
        } else {
            WebLogAdapter.removeClient(clientId, emitter);
            ((Set<String>) httpSession.getAttribute("clientIds")).remove(clientId);
        }
        return "1";
    }
    @Operation(summary = "设备监控")
    @GetMapping(value = "device/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<Object> sseConnect(HttpSession httpSession, String clientId) {
        return Flux.create(emitter -> {
            Set<String> clientIds = new HashSet<>();
            if (clientId != null) {
                WebLogAdapter.addClient(clientId, emitter);
                clientIds.add(clientId);
            }
            httpSession.setAttribute("clientIds", clientIds);
            httpSession.setAttribute("emitter", emitter);
            emitter.onDispose(() -> clientIds.forEach(id -> WebLogAdapter.removeClient(id, emitter)));
        });
    }
    @Operation(summary = "808协议分析工具")
    @RequestMapping(value = "message/explain", method = {RequestMethod.POST, RequestMethod.GET})
    public String decode(@Parameter(description = "16进制报文") @RequestParam String hex) {
        Explain explain = new Explain();
        hex = hex.replace(" ", "");
        String[] lines = hex.split("\n");
        for (String line : lines) {
            String[] msgs = line.split("7e7e");
            for (String msg : msgs) {
                ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(msg));
                decoder.decode(byteBuf, explain);
            }
        }
        return explain.toString();
    }
    @Operation(summary = "原始消息发送")
    @PostMapping("device/raw")
    public Mono<String> postRaw(@Parameter(description = "终端手机号") @RequestParam String clientId,
                                @Parameter(description = "16进制报文") @RequestParam String message) {
        Session session = sessionManager.get(clientId);
        if (session != null) {
            ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(message));
            return session.notify(byteBuf).map(unused -> "success")
                    .timeout(Duration.ofSeconds(10), Mono.just("timeout"))
                    .onErrorResume(throwable -> Mono.just("fail"));
        }
        return Mono.just("offline");
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JSATL12Endpoint.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
package com.doumee.jtt808.web.endpoint;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.github.yezhihao.netmc.core.annotation.Endpoint;
import io.github.yezhihao.netmc.core.annotation.Mapping;
import io.github.yezhihao.netmc.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.yzh.protocol.commons.JSATL12;
import org.yzh.protocol.jsatl12.DataPacket;
import org.yzh.protocol.jsatl12.T1210;
import org.yzh.protocol.jsatl12.T1211;
import org.yzh.protocol.jsatl12.T9212;
import com.doumee.jtt808.web.service.FileService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Endpoint
@Component
public class JSATL12Endpoint {
    @Autowired
    private FileService fileService;
    private final Cache<String, Map<String, T1210.Item>> cache = Caffeine.newBuilder().expireAfterAccess(20, TimeUnit.MINUTES).build();
    @Mapping(types = JSATL12.报警附件信息消息, desc = "报警附件信息消息")
    public void alarmFileInfoList(T1210 message, Session session) {
        session.register(message.getDeviceId(), message);
        List<T1210.Item> items = message.getItems();
        if (items == null) return;
        Map<String, T1210.Item> fileInfos = cache.get(message.getClientId(), s -> new HashMap<>((int) (items.size() / 0.75) + 1));
        for (T1210.Item item : items)
            fileInfos.put(item.getName(), item.parent(message));
        fileService.createDir(message);
    }
    @Mapping(types = JSATL12.文件信息上传, desc = "文件信息上传")
    public void alarmFileInfo(T1211 message, Session session) {
        if (!session.isRegistered()) session.register(message);
    }
    @Mapping(types = JSATL12.文件数据上传, desc = "文件数据上传")
    public Object alarmFile(DataPacket dataPacket, Session session) {
        Map<String, T1210.Item> fileInfos = cache.getIfPresent(session.getClientId());
        if (fileInfos != null) {
            T1210.Item fileInfo = fileInfos.get(dataPacket.getName().trim());
            if (fileInfo != null) {
                if (dataPacket.getOffset() == 0 && dataPacket.getLength() >= fileInfo.getSize()) {
                    fileService.writeFileSingle(fileInfo.parent(), dataPacket);
                } else {
                    fileService.writeFile(fileInfo.parent(), dataPacket);
                }
            }
        }
        return null;
    }
    @Mapping(types = JSATL12.文件上传完成消息, desc = "文件上传完成消息")
    public T9212 alarmFileComplete(T1211 message) {
        Map<String, T1210.Item> fileInfos = cache.getIfPresent(message.getClientId());
        T1210.Item fileInfo = fileInfos.get(message.getName());
        T9212 result = new T9212();
        result.setName(message.getName());
        result.setType(message.getType());
        int[] items = fileService.checkFile(fileInfo.parent(), message);
        if (items == null) {
            fileInfos.remove(message.getName());
            if (fileInfos.isEmpty()) {
                cache.invalidate(message.getClientId());
            }
            result.setResult(0);
        } else {
            result.setItems(items);
            result.setResult(1);
        }
        return result;
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT1078Endpoint.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.doumee.jtt808.web.endpoint;
import io.github.yezhihao.netmc.core.annotation.Endpoint;
import io.github.yezhihao.netmc.core.annotation.Mapping;
import io.github.yezhihao.netmc.session.Session;
import org.springframework.stereotype.Component;
import org.yzh.protocol.t1078.T1003;
import org.yzh.protocol.t1078.T1005;
import org.yzh.protocol.t1078.T1205;
import org.yzh.protocol.t1078.T1206;
import static org.yzh.protocol.commons.JT1078.*;
@Endpoint
@Component
public class JT1078Endpoint {
    @Mapping(types = ç»ˆç«¯ä¸Šä¼ éŸ³è§†é¢‘资源列表, desc = "终端上传音视频资源列表")
    public void T1205(T1205 message, Session session) {
        session.response(message);
    }
    @Mapping(types = ç»ˆç«¯ä¸Šä¼ éŸ³è§†é¢‘属性, desc = "终端上传音视频属性")
    public void T1003(T1003 message, Session session) {
        session.response(message);
    }
    @Mapping(types = æ–‡ä»¶ä¸Šä¼ å®Œæˆé€šçŸ¥, desc = "文件上传完成通知")
    public void T1206(T1206 message, Session session) {
        session.response(message);
    }
    @Mapping(types = ç»ˆç«¯ä¸Šä¼ ä¹˜å®¢æµé‡, desc = "终端上传乘客流量")
    public void T1005(T1005 message, Session session) {
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT808Endpoint.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,255 @@
package com.doumee.jtt808.web.endpoint;
import com.alibaba.fastjson.JSONObject;
import com.doumee.core.utils.DateUtil;
import com.doumee.dao.business.model.Bikes;
import com.doumee.service.business.BikesService;
import io.github.yezhihao.netmc.core.annotation.Async;
import io.github.yezhihao.netmc.core.annotation.AsyncBatch;
import io.github.yezhihao.netmc.core.annotation.Endpoint;
import io.github.yezhihao.netmc.core.annotation.Mapping;
import io.github.yezhihao.netmc.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.commons.transform.AttributeKey;
import org.yzh.protocol.commons.transform.attribute.Battery;
import org.yzh.protocol.t808.*;
import com.doumee.jtt808.web.model.entity.DeviceDO;
import com.doumee.jtt808.web.model.enums.SessionKey;
import com.doumee.jtt808.web.service.FileService;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;
import static org.yzh.protocol.commons.JT808.*;
@Endpoint
@Component
public class JT808Endpoint {
    @Autowired
    private BikesService bikesService;
    private static final Logger log = LoggerFactory.getLogger(JT808Endpoint.class);
    @Autowired
    private FileService fileService;
    @Mapping(types = ç»ˆç«¯é€šç”¨åº”ç­”, desc = "终端通用应答")
    public Object T0001(T0001 message, Session session) {
        session.response(message);
        return null;
    }
    @Mapping(types = ç»ˆç«¯å¿ƒè·³, desc = "终端心跳")
    public void T0002(JTMessage message, Session session) {
        log.info("终端心跳========={}", JSONObject.toJSONString(message));
    }
    @Mapping(types = ç»ˆç«¯æ³¨é”€, desc = "终端注销")
    public void T0003(JTMessage message, Session session) {
        session.invalidate();
    }
    @Mapping(types = æŸ¥è¯¢æœåŠ¡å™¨æ—¶é—´, desc = "查询服务器时间")
    public T8004 T0004(JTMessage message, Session session) {
        T8004 result = new T8004(LocalDateTime.now(ZoneOffset.UTC));
        return result;
    }
    @Mapping(types = ç»ˆç«¯è¡¥ä¼ åˆ†åŒ…请求, desc = "终端补传分包请求")
    public void T8003(T8003 message, Session session) {
    }
    @Mapping(types = ç»ˆç«¯æ ¡æ—¶è¯·æ±‚上行, desc = "终端校时请求上行")
    public void T0F01(JTMessage message, Session session) {
    }
    @Mapping(types = ç»ˆç«¯æ³¨å†Œ, desc = "终端注册")
    public T8100 T0100(T0100 message, Session session) {
        session.register(message);
        DeviceDO device = new DeviceDO();
        device.setProtocolVersion(message.getProtocolVersion());
        device.setMobileNo(message.getClientId());
        device.setDeviceId(message.getDeviceId());
        device.setPlateNo(message.getPlateNo());
        session.setAttribute(SessionKey.Device, device);
        T8100 result = new T8100();
        result.setResponseSerialNo(message.getSerialNo());
        result.setToken(message.getDeviceId() + "," + message.getPlateNo());
        result.setResultCode(T8100.Success);
        return result;
    }
    @Mapping(types = ç»ˆç«¯é‰´æƒ, desc = "终端鉴权")
    public T0001 T0102(T0102 message, Session session) {
        session.register(message);
        DeviceDO device = new DeviceDO();
        String[] token = message.getToken().split(",");
        device.setProtocolVersion(message.getProtocolVersion());
        device.setMobileNo(message.getClientId());
        device.setDeviceId(token[0]);
        if (token.length > 1)
            device.setPlateNo(token[1]);
        session.setAttribute(SessionKey.Device, device);
        T0001 result = new T0001();
        result.setResponseSerialNo(message.getSerialNo());
        result.setResponseMessageId(message.getMessageId());
        result.setResultCode(T0001.Success);
        return result;
    }
    @Mapping(types = æŸ¥è¯¢ç»ˆç«¯å‚数应答, desc = "查询终端参数应答")
    public void T0104(T0104 message, Session session) {
        session.response(message);
    }
    @Mapping(types = æŸ¥è¯¢ç»ˆç«¯å±žæ€§åº”ç­”, desc = "查询终端属性应答")
    public void T0107(T0107 message, Session session) {
        log.info("查询终端属性应答========={}", JSONObject.toJSONString(message));
        session.response(message);
    }
    @Mapping(types = ç»ˆç«¯å‡çº§ç»“果通知, desc = "终端升级结果通知")
    public void T0108(T0108 message, Session session) {
    }
    /**
     * å¼‚步批量处理
     * poolSize:参考数据库CPU核心数量
     * maxElements:最大累积4000条记录处理一次
     * maxWait:最大等待时间1秒
     */
    @AsyncBatch(poolSize = 2, maxElements = 4000, maxWait = 1000)
    @Mapping(types = ä½ç½®ä¿¡æ¯æ±‡æŠ¥, desc = "位置信息汇报")
    public void T0200(List<T0200> list) {
        for(T0200 m : list){
            Bikes bike = new Bikes();
            bike.setDeviceSn(m.getClientId());
            if(m.getLatitude()!=0){
                bike.setLatitude(new BigDecimal(m.getLatitude()).divide(new BigDecimal(1000000),2,BigDecimal.ROUND_HALF_UP));
            }
            if(m.getLongitude()!=0){
                bike.setLongitude(new BigDecimal(m.getLongitude()).divide(new BigDecimal(1000000),2,BigDecimal.ROUND_HALF_UP));
            }
            bike.setHeartDate(DateUtil.getDateFromLocalDateTime(m.getDeviceTime()));
            if(m.getAttributes()!=null ){
                Battery battery= (Battery) m.getAttributes().get(AttributeKey.Battery);
                if(battery !=null && battery.getVoltage()!=null){
                    bike.setVoltage(new BigDecimal(battery.getVoltage()));
                }
            }
            bikesService.updateByJtt(bike);
        }
        System.out.println(JSONObject.toJSONString(list)
      );
    }
    @Mapping(types = å®šä½æ•°æ®æ‰¹é‡ä¸Šä¼ , desc = "定位数据批量上传")
    public void T0704(T0704 message) {
    }
    public static String bcd2String(byte[] bytes) {
        StringBuilder temp = new StringBuilder(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            // é«˜å››ä½
            temp.append((bytes[i] & 0xf0) >>> 4);
            // ä½Žå››ä½
            temp.append(bytes[i] & 0x0f);
        }
        return temp.toString().substring(0, 1).equalsIgnoreCase("0") ? temp.toString().substring(1) : temp.toString();
    }
    @Mapping(types = {位置信息查询应答, è½¦è¾†æŽ§åˆ¶åº”ç­”}, desc = "位置信息查询应答/车辆控制应答")
    public void T0201_0500(T0201_0500 message, Session session) {
        session.response(message);
    }
    @Mapping(types = äº‹ä»¶æŠ¥å‘Š, desc = "事件报告")
    public void T0301(T0301 message, Session session) {
    }
    @Mapping(types = æé—®åº”ç­”, desc = "提问应答")
    public void T0302(T0302 message, Session session) {
    }
    @Mapping(types = ä¿¡æ¯ç‚¹æ’­_取消, desc = "信息点播/取消")
    public void T0303(T0303 message, Session session) {
    }
    @Mapping(types = æŸ¥è¯¢åŒºåŸŸæˆ–线路数据应答, desc = "查询区域或线路数据应答")
    public void T0608(T0608 message, Session session) {
        session.response(message);
    }
    @Mapping(types = è¡Œé©¶è®°å½•数据上传, desc = "行驶记录仪数据上传")
    public void T0700(T0700 message, Session session) {
        session.response(message);
    }
    @Mapping(types = ç”µå­è¿å•上报, desc = "电子运单上报")
    public void T0701(JTMessage message, Session session) {
    }
    @Mapping(types = é©¾é©¶å‘˜èº«ä»½ä¿¡æ¯é‡‡é›†ä¸ŠæŠ¥, desc = "驾驶员身份信息采集上报")
    public void T0702(T0702 message, Session session) {
        session.response(message);
    }
    @Mapping(types = CAN总线数据上传, desc = "CAN总线数据上传")
    public void T0705(T0705 message, Session session) {
    }
    @Mapping(types = å¤šåª’体事件信息上传, desc = "多媒体事件信息上传")
    public void T0800(T0800 message, Session session) {
    }
    @Async
    @Mapping(types = å¤šåª’体数据上传, desc = "多媒体数据上传")
    public JTMessage T0801(T0801 message, Session session) {
        if (message.getPacket() == null) {
            T0001 result = new T0001();
            result.copyBy(message);
            result.setMessageId(JT808.平台通用应答);
            result.setSerialNo(session.nextSerialNo());
            result.setResponseSerialNo(message.getSerialNo());
            result.setResponseMessageId(message.getMessageId());
            result.setResultCode(T0001.Success);
            return result;
        }
        fileService.saveMediaFile(message);
        T8800 result = new T8800();
        result.setMediaId(message.getId());
        return result;
    }
    @Mapping(types = å­˜å‚¨å¤šåª’体数据检索应答, desc = "存储多媒体数据检索应答")
    public void T0802(T0802 message, Session session) {
        session.response(message);
    }
    @Mapping(types = æ‘„像头立即拍摄命令应答, desc = "摄像头立即拍摄命令应答")
    public void T0805(T0805 message, Session session) {
        session.response(message);
    }
    @Mapping(types = æ•°æ®ä¸Šè¡Œé€ä¼ , desc = "数据上行透传")
    public void T0900(T0900 message, Session session) {
    }
    @Mapping(types = æ•°æ®åŽ‹ç¼©ä¸ŠæŠ¥, desc = "数据压缩上报")
    public void T0901(T0901 message, Session session) {
    }
    @Mapping(types = ç»ˆç«¯RSA公钥, desc = "终端RSA公钥")
    public void T0A00(T0A00_8A00 message, Session session) {
        session.response(message);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTHandlerInterceptor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
package com.doumee.jtt808.web.endpoint;
import io.github.yezhihao.netmc.core.HandlerInterceptor;
import io.github.yezhihao.netmc.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yzh.protocol.basics.JTMessage;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.commons.transform.AttributeKey;
import org.yzh.protocol.commons.transform.attribute.Battery;
import org.yzh.protocol.t808.T0001;
import org.yzh.protocol.t808.T0200;
import com.doumee.jtt808.web.model.entity.DeviceDO;
import com.doumee.jtt808.web.model.enums.SessionKey;
public class JTHandlerInterceptor implements HandlerInterceptor<JTMessage> {
    private static final Logger log = LoggerFactory.getLogger(JTHandlerInterceptor.class);
    /** æœªæ‰¾åˆ°å¯¹åº”çš„Handle */
    @Override
    public JTMessage notSupported(JTMessage request, Session session) {
        T0001 response = new T0001();
        response.copyBy(request);
        response.setMessageId(JT808.平台通用应答);
        response.setSerialNo(session.nextSerialNo());
        response.setResponseSerialNo(request.getSerialNo());
        response.setResponseMessageId(request.getMessageId());
        response.setResultCode(T0001.NotSupport);
        log.info("{}\n<<<<-未识别的消息{}\n>>>>-{}", session, request, response);
        return response;
    }
    /** è°ƒç”¨ä¹‹åŽï¼Œè¿”回值为void的 */
    @Override
    public JTMessage successful(JTMessage request, Session session) {
        T0001 response = new T0001();
        response.copyBy(request);
        response.setMessageId(JT808.平台通用应答);
        response.setSerialNo(session.nextSerialNo());
        response.setResponseSerialNo(request.getSerialNo());
        response.setResponseMessageId(request.getMessageId());
        response.setResultCode(T0001.Success);
        log.info(session.getId());
//        log.info("{}\n<<<<-{}\n>>>>-{}", session, request, response);
        return response;
    }
    /** è°ƒç”¨ä¹‹åŽæŠ›å‡ºå¼‚常的 */
    @Override
    public JTMessage exceptional(JTMessage request, Session session, Exception e) {
        T0001 response = new T0001();
        response.copyBy(request);
        response.setMessageId(JT808.平台通用应答);
        response.setSerialNo(session.nextSerialNo());
        response.setResponseSerialNo(request.getSerialNo());
        response.setResponseMessageId(request.getMessageId());
        response.setResultCode(T0001.Failure);
        log.warn(session + "\n<<<<-" + request + "\n>>>>-" + response + '\n', e);
        return response;
    }
    /** è°ƒç”¨ä¹‹å‰ */
    @Override
    public boolean beforeHandle(JTMessage request, Session session) {
        int messageId = request.getMessageId();
        if (messageId == JT808.终端注册 || messageId == JT808.终端鉴权)
            return true;
        boolean transform = request.transform();
        if (messageId == JT808.位置信息汇报) {
            DeviceDO device = SessionKey.getDevice(session);
            if (device != null){
                device.setLocation((T0200) request);
                if(device.getLocation()!=null && device.getLocation().getAttributes()!=null ){
                   Battery battery= (Battery) device.getLocation().getAttributes().get(AttributeKey.Battery);
                    if(battery !=null){
                        device.setBatteryVoltage(battery.getVoltage());
                    }
                }
            }
            return transform;
        }
        if (!session.isRegistered()) {
            log.info("{}未注册的设备<<<<-{}", session, request);
            return true;
        }
        return true;
    }
    /** è°ƒç”¨ä¹‹åŽ */
    @Override
    public void afterHandle(JTMessage request, JTMessage response, Session session) {
        if (response != null) {
            response.copyBy(request);
            response.setSerialNo(session.nextSerialNo());
            if (response.getMessageId() == 0) {
                response.setMessageId(response.reflectMessageId());
            }
        }
//        log.info("{}\n<<<<-{}\n>>>>-{}", session, request, response);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTMultiPacketListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.doumee.jtt808.web.endpoint;
import io.github.yezhihao.netmc.session.Session;
import org.yzh.protocol.codec.MultiPacket;
import org.yzh.protocol.codec.MultiPacketListener;
import org.yzh.protocol.commons.JT808;
import org.yzh.protocol.t808.T8003;
import java.util.List;
public class JTMultiPacketListener extends MultiPacketListener {
    public JTMultiPacketListener(int timeout) {
        super(timeout);
    }
    @Override
    public boolean receiveTimeout(MultiPacket multiPacket) {
        int retryCount = multiPacket.getRetryCount();
        if (retryCount > 5)
            return false;
        T8003 request = new T8003();
        request.setMessageId(JT808.服务器补传分包请求);
        request.copyBy(multiPacket.getFirstPacket());
        request.setResponseSerialNo(multiPacket.getSerialNo());
        List<Integer> notArrived = multiPacket.getNotArrived();
        short[] idList = new short[notArrived.size()];
        for (int i = 0; i < idList.length; i++) {
            idList[i] = notArrived.get(i).shortValue();
        }
        request.setId(idList);
        Session session = multiPacket.getFirstPacket().getSession();
        if (session != null) {
            session.notify(request).block();
            multiPacket.addRetryCount(1);
            return true;
        }
        return false;
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTSessionListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package com.doumee.jtt808.web.endpoint;
import io.github.yezhihao.netmc.core.model.Message;
import io.github.yezhihao.netmc.session.Session;
import io.github.yezhihao.netmc.session.SessionListener;
import org.yzh.protocol.basics.JTMessage;
import com.doumee.jtt808.web.model.entity.DeviceDO;
import com.doumee.jtt808.web.model.enums.SessionKey;
import java.util.function.BiConsumer;
public class JTSessionListener implements SessionListener {
    /**
     * ä¸‹è¡Œæ¶ˆæ¯æ‹¦æˆªå™¨
     */
    private static final BiConsumer<Session, Message> requestInterceptor = (session, message) -> {
        JTMessage request = (JTMessage) message;
        request.setClientId(session.getClientId());
        request.setSerialNo(session.nextSerialNo());
        if (request.getMessageId() == 0) {
            request.setMessageId(request.reflectMessageId());
        }
        DeviceDO device = SessionKey.getDevice(session);
        if (device != null) {
            int protocolVersion = device.getProtocolVersion();
            if (protocolVersion > 0) {
                request.setVersion(true);
                request.setProtocolVersion(protocolVersion);
            }
        }
    };
    /**
     * è®¾å¤‡è¿žæŽ¥
     */
    @Override
    public void sessionCreated(Session session) {
        session.requestInterceptor(requestInterceptor);
    }
    /**
     * è®¾å¤‡æ³¨å†Œ
     */
    @Override
    public void sessionRegistered(Session session) {
    }
    /**
     * è®¾å¤‡ç¦»çº¿
     */
    @Override
    public void sessionDestroyed(Session session) {
    }
}
server/web/src/main/java/com/doumee/jtt808/web/endpoint/MessageManager.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package com.doumee.jtt808.web.endpoint;
import io.github.yezhihao.netmc.session.Session;
import io.github.yezhihao.netmc.session.SessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.yzh.commons.model.APIException;
import org.yzh.commons.model.APIResult;
import org.yzh.protocol.basics.JTMessage;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@Component
public class MessageManager {
    private static final Logger log = LoggerFactory.getLogger(MessageManager.class);
    private static final Mono<Void> NEVER = Mono.never();
    private static final Mono OFFLINE_EXCEPTION = Mono.error(new APIException(4000, "离线的客户端(请检查设备是否注册或者鉴权)"));
    private static final Mono OFFLINE_RESULT = Mono.just(new APIResult<>(4000, "离线的客户端(请检查设备是否注册或者鉴权)"));
    private static final Mono SENDFAIL_RESULT = Mono.just(new APIResult<>(4001, "消息发送失败"));
    private static final Mono TIMEOUT_RESULT = Mono.just(new APIResult<>(4002, "消息发送成功,客户端响应超时(至于设备为什么不应答,请联系设备厂商)"));
    private SessionManager sessionManager;
    public MessageManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }
    public Mono<Void> notifyR(String sessionId, JTMessage request) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return OFFLINE_EXCEPTION;
        return session.notify(request);
    }
    public Mono<Void> notify(String sessionId, JTMessage request) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return NEVER;
        return session.notify(request);
    }
    public <T> Mono<APIResult<T>> requestR(String sessionId, JTMessage request, Class<T> responseClass) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return OFFLINE_RESULT;
        return session.request(request, responseClass)
                .map(message -> APIResult.ok(message))
                .timeout(Duration.ofSeconds(10), TIMEOUT_RESULT)
                .onErrorResume(e -> {
                    log.warn("消息发送失败", e);
                    return SENDFAIL_RESULT;
                });
    }
    public <T> Mono<APIResult<T>> requestR(JTMessage request, Class<T> responseClass) {
        Session session = sessionManager.get(request.getClientId());
        if (session == null)
            return OFFLINE_RESULT;
        return session.request(request, responseClass)
                .map(message ->  APIResult.ok(message)
                )
                .timeout(Duration.ofSeconds(10), TIMEOUT_RESULT)
                .onErrorResume(e -> {
                    log.warn("消息发送失败", e);
                    return SENDFAIL_RESULT;
                });
    }
    public <T> Mono<T> request(String sessionId, JTMessage request, Class<T> responseClass, long timeout) {
        return request(sessionId, request, responseClass).timeout(Duration.ofMillis(timeout));
    }
    public <T> Mono<T> request(String sessionId, JTMessage request, Class<T> responseClass) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return OFFLINE_EXCEPTION;
        return session.request(request, responseClass);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/model/entity/DeviceDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
package com.doumee.jtt808.web.model.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.yzh.protocol.t808.T0200;
import java.util.Objects;
@Data
public class DeviceDO {
    @Schema(description = "设备id")
    private String deviceId;
    @Schema(description = "设备手机号")
    private String mobileNo;
    @Schema(description = "车牌号")
    private String plateNo;
    @Schema(description = "机构id")
    protected int agencyId;
    @Schema(description = "司机id")
    protected int driverId;
    @Schema(description = "协议版本号")
    private int protocolVersion;
    @Schema(description = "电池电压")
    private Float batteryVoltage;
    @Schema(description = "实时状态")
    private T0200 location;
    public DeviceDO() {
    }
    public String getDeviceId() {
        return deviceId;
    }
    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }
    public String getMobileNo() {
        return mobileNo;
    }
    public void setMobileNo(String mobileNo) {
        this.mobileNo = mobileNo;
    }
    public String getPlateNo() {
        return plateNo;
    }
    public void setPlateNo(String plateNo) {
        this.plateNo = plateNo;
    }
    public int getProtocolVersion() {
        return protocolVersion;
    }
    public void setProtocolVersion(int protocolVersion) {
        this.protocolVersion = protocolVersion;
    }
    public T0200 getLocation() {
        return location;
    }
    public void setLocation(T0200 location) {
        this.location = location;
    }
    public DeviceDO mobileNo(String mobileNo) {
        this.mobileNo = mobileNo;
        return this;
    }
    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        DeviceDO other = (DeviceDO) that;
        return Objects.equals(this.deviceId, other.deviceId);
    }
    @Override
    public int hashCode() {
        return ((deviceId == null) ? 0 : deviceId.hashCode());
    }
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder(256);
        sb.append("DeviceDO{deviceId=").append(deviceId);
        sb.append(", mobileNo=").append(mobileNo);
        sb.append(", plateNo=").append(plateNo);
        sb.append(", protocolVersion=").append(protocolVersion);
        sb.append('}');
        return sb.toString();
    }
}
server/web/src/main/java/com/doumee/jtt808/web/model/enums/SessionKey.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.doumee.jtt808.web.model.enums;
import io.github.yezhihao.netmc.session.Session;
import com.doumee.jtt808.web.model.entity.DeviceDO;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
public enum SessionKey {
    Device;
    public static DeviceDO getDevice(Session session) {
        return (DeviceDO) session.getAttribute(Device);
    }
}
server/web/src/main/java/com/doumee/jtt808/web/model/vo/DeviceInfo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
package com.doumee.jtt808.web.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import org.yzh.protocol.commons.Charsets;
import java.io.*;
import java.time.LocalDate;
import static io.github.yezhihao.protostar.util.DateTool.BCD;
/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
public class DeviceInfo {
    @Schema(description = "签发日期")
    protected LocalDate issuedAt;
    @Schema(description = "预留字段")
    protected byte reserved;
    @Schema(description = "设备id")
    protected String deviceId;
    public DeviceInfo() {
    }
    public DeviceInfo(byte[] bytes) {
        formBytes(bytes);
    }
    public DeviceInfo(String deviceId, LocalDate issuedAt) {
        this.deviceId = deviceId;
        this.issuedAt = issuedAt;
    }
    public LocalDate getIssuedAt() {
        return issuedAt;
    }
    public void setIssuedAt(LocalDate issuedAt) {
        this.issuedAt = issuedAt;
    }
    public byte getReserved() {
        return reserved;
    }
    public void setReserved(byte reserved) {
        this.reserved = reserved;
    }
    public String getDeviceId() {
        return deviceId;
    }
    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }
    public DeviceInfo formBytes(byte[] bytes) {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
             DataInputStream dis = new DataInputStream(bis)) {
            byte[] temp;
            dis.read(temp = new byte[3]);
            this.issuedAt = BCD.toDate(temp);
            this.reserved = dis.readByte();
            int len = dis.readUnsignedByte();
            dis.read(temp = new byte[len]);
            this.deviceId = new String(temp, Charsets.GBK);
            return this;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public byte[] toBytes() {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream(32);
             DataOutputStream dos = new DataOutputStream(bos)) {
            dos.write(BCD.from(issuedAt));
            dos.writeByte(reserved);
            byte[] bytes = deviceId.getBytes(Charsets.GBK);
            dos.writeByte(bytes.length);
            dos.write(bytes);
            return bos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("DeviceInfo{");
        sb.append("issuedAt=").append(issuedAt);
        sb.append(", reserved=").append(reserved);
        sb.append(", deviceId=").append(deviceId);
        sb.append('}');
        return sb.toString();
    }
}
server/web/src/main/java/com/doumee/jtt808/web/service/FileService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,230 @@
package com.doumee.jtt808.web.service;
import io.netty.buffer.ByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.yzh.commons.util.DateUtils;
import org.yzh.commons.util.IOUtils;
import org.yzh.commons.util.StrUtils;
import org.yzh.protocol.jsatl12.DataPacket;
import org.yzh.protocol.jsatl12.T1210;
import org.yzh.protocol.jsatl12.T1211;
import org.yzh.protocol.t808.T0200;
import org.yzh.protocol.t808.T0801;
import com.doumee.jtt808.web.model.entity.DeviceDO;
import com.doumee.jtt808.web.model.enums.SessionKey;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
@Service
public class FileService {
    private static final Logger log = LoggerFactory.getLogger(FileService.class.getSimpleName());
    private static final Comparator<long[]> comparator = Comparator.comparingLong((long[] a) -> a[0]).thenComparingLong(a -> a[1]);
    @Value("${jt-server.alarm-file.path}")
    private String workDirPath;
    @Value("${jt-server.jt808.media-file.path}")
    private String mediaFileRoot;
    private String getDir(T1210 alarmId) {
        StringBuilder sb = new StringBuilder(80);
        sb.append(workDirPath).append('/');
        sb.append(alarmId.getClientId()).append('_');
        DateUtils.yyMMddHHmmss.formatTo(alarmId.getDateTime(), sb);
        sb.append('_').append(alarmId.getSerialNo()).append('/');
        return sb.toString();
    }
    /** åˆ›å»ºæŠ¥è­¦ç›®å½•及 é™„件列表日志 */
    public void createDir(T1210 alarmId) {
        String dirPath = getDir(alarmId);
        new File(dirPath).mkdirs();
        List<T1210.Item> items = alarmId.getItems();
        StringBuilder fileList = new StringBuilder(items.size() * 50);
        fileList.append(dirPath).append(IOUtils.Separator);
        for (T1210.Item item : items)
            fileList.append(item.getName()).append('\t').append(item.getSize()).append(IOUtils.Separator);
        IOUtils.write(fileList.toString(), new File(dirPath, "fs.txt"));
    }
    /** å°†æ•°æ®å—写入到报警文件,并记录日志 */
    public void writeFile(T1210 alarmId, DataPacket fileData) {
        String dir = getDir(alarmId);
        String name = dir + fileData.getName().trim();
        int offset = fileData.getOffset();
        int length = fileData.getLength();
        byte[] buffer = ByteBuffer.allocate(8)
                .putInt(offset).putInt(length).array();
        RandomAccessFile file = null;
        FileOutputStream filelog = null;
        ByteBuf data = fileData.getData();
        try {
            file = new RandomAccessFile(name + ".tmp", "rw");
            filelog = new FileOutputStream(name + ".log", true);
            data.readBytes(file.getChannel(), offset, data.readableBytes());
            filelog.write(buffer);
        } catch (IOException e) {
            log.error("写入报警文件", e);
        } finally {
            IOUtils.close(file, filelog);
        }
    }
    public void writeFileSingle(T1210 alarmId, DataPacket fileData) {
        String dir = getDir(alarmId);
        String name = dir + fileData.getName().trim();
        int offset = fileData.getOffset();
        RandomAccessFile file = null;
        ByteBuf data = fileData.getData();
        try {
            file = new RandomAccessFile(name, "rw");
            data.readBytes(file.getChannel(), offset, data.readableBytes());
        } catch (IOException e) {
            log.error("写入报警文件", e);
        } finally {
            IOUtils.close(file);
        }
    }
    /** æ ¹æ®æ—¥å¿—检查文件完整性,并返回缺少的数据块信息 */
    public int[] checkFile(T1210 alarmId, T1211 fileInfo) {
        String dir = getDir(alarmId);
        File logFile = new File(dir + fileInfo.getName() + ".log");
        byte[] bytes;
        FileInputStream in = null;
        try {
            in = new FileInputStream(logFile);
            bytes = new byte[in.available()];
            in.read(bytes);
        } catch (FileNotFoundException e) {
            return null;
        } catch (IOException e) {
            log.error("检查文件完整性", e);
            return null;
        } finally {
            IOUtils.close(in);
        }
        int size = bytes.length / 8;
        long[][] items = new long[size + 2][2];
        items[size + 1][0] = fileInfo.getSize();
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        for (int i = 1; i <= size; i++) {
            items[i][0] = buffer.getInt();
            items[i][1] = buffer.getInt();
        }
        List<Integer> result = new ArrayList<>(items.length);
        int len = items.length - 1;
        Arrays.sort(items, 1, len, comparator);
        for (int i = 0; i < len; ) {
            long a = items[i][0] + items[i][1];
            long b = items[++i][0] - a;
            if (b > 0) {
                result.add((int) a);
                result.add((int) b);
            }
        }
        if (result.isEmpty()) {
            File file = new File(dir + fileInfo.getName() + ".tmp");
            File dest = new File(dir + fileInfo.getName());
            if (file.renameTo(dest)) {
                logFile.delete();
            }
            return null;
        }
        return StrUtils.toArray(result);
    }
    /** å¤šåª’体数据上传 */
    public boolean saveMediaFile(T0801 message) {
        DeviceDO device = SessionKey.getDevice(message.getSession());
        T0200 location = message.getLocation();
        StringBuilder filename = new StringBuilder(32);
        filename.append(type(message.getType())).append('_');
        DateUtils.yyMMddHHmmss.formatTo(location.getDeviceTime(), filename);
        filename.append('_');
        filename.append(message.getChannelId()).append('_');
        filename.append(message.getEvent()).append('_');
        filename.append(message.getId()).append('.');
        filename.append(suffix(message.getFormat()));
        String deviceId;
        if (device == null)
            deviceId = message.getClientId();
        else
            deviceId = device.getDeviceId();
        File dir = new File(mediaFileRoot + '/' + deviceId);
        dir.mkdirs();
        ByteBuf packet = message.getPacket();
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(new File(dir, filename.toString()));
            packet.readBytes(fos.getChannel(), 0, packet.readableBytes());
            return true;
        } catch (IOException e) {
            log.error("多媒体数据保存失败", e);
            return false;
        } finally {
            IOUtils.close(fos);
            packet.release();
        }
    }
    private static String type(int type) {
        switch (type) {
            case 0:
                return "image";
            case 1:
                return "audio";
            case 2:
                return "video";
            default:
                return "unknown";
        }
    }
    private static String suffix(int format) {
        switch (format) {
            case 0:
                return "jpg";
            case 1:
                return "tif";
            case 2:
                return "mp3";
            case 3:
                return "wav";
            case 4:
                return "wmv";
            default:
                return "bin";
        }
    }
}
server/web/src/main/resources/application.yml
@@ -9,7 +9,7 @@
#  application:
#    name: parkbike
  profiles:
    active: proDev
    active: dev
  # JSON返回配置
  jackson:
    # é»˜è®¤æ—¶åŒº
@@ -65,3 +65,20 @@
mqtt:
  clientid: doumeeweb
  subclientid: doumeewebSub
jt-server:
  jt808:
    enable: true
    port:
      udp: 7611
      tcp: 7611
    media-file:
      path: D:/jt_data/media_file
    alarm-file:
      host: 127.0.0.1
      port: 7612
  alarm-file:
    enable: true
    port: 7612
    path: D:/jt_data/alarm_file