From c185de12314b8733f23ed7856e6d1e87ee95c1ca Mon Sep 17 00:00:00 2001
From: jiangping <jp@doumee.com>
Date: 星期五, 14 二月 2025 15:09:59 +0800
Subject: [PATCH] jtt808初始化

---
 server/jtt808_parent/jtt808-server/src/main/java/org/yzh/web/endpoint/JT808Endpoint.java |    2 
 server/web/src/main/java/com/doumee/config/swagger/SwaggerConfig.java                    |   13 
 server/web/src/main/java/com/doumee/jtt808/t808/T8103.java                               |  229 ++++
 server/web/src/main/java/com/doumee/jtt808/web/controller/ExceptionController.java       |  111 ++
 server/services/src/main/java/com/doumee/service/business/impl/BikesServiceImpl.java     |   20 
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT1078Endpoint.java              |   37 
 server/web/src/main/java/com/doumee/jtt808/web/controller/JT808Controller.java           |  246 +++++
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTMultiPacketListener.java       |   41 
 server/web/src/main/java/com/doumee/jtt808/web/config/BeanConfig.java                    |   70 +
 server/services/src/main/java/com/doumee/core/wx/WxMiniConfig.java                       |   30 
 server/web/src/main/java/com/doumee/jtt808/web/model/entity/DeviceDO.java                |  107 ++
 server/web/src/main/java/com/doumee/jtt808/web/config/WebLogAdapter.java                 |  118 ++
 server/web/src/main/java/com/doumee/jtt808/web/config/JTConfig.java                      |   84 +
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/MessageManager.java              |   92 +
 server/web/src/main/java/com/doumee/InterfaceApplication.java                            |    2 
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTSessionListener.java           |   57 +
 server/web/src/main/java/com/doumee/jtt808/t808/T0200.java                               |  191 +++
 server/web/src/main/java/com/doumee/jtt808/t808/T0201_0500.java                          |   29 
 server/services/pom.xml                                                                  |    1 
 server/web/src/main/java/com/doumee/jtt808/web/config/JTBeanConfig.java                  |   65 +
 server/web/src/main/java/com/doumee/jtt808/web/controller/JT1078Controller.java          |  116 ++
 server/web/src/main/java/com/doumee/jtt808/web/model/vo/DeviceInfo.java                  |  103 ++
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTHandlerInterceptor.java        |  110 ++
 server/web/src/main/java/com/doumee/jtt808/web/config/WebMvcConfig.java                  |   61 +
 server/services/src/main/java/com/doumee/service/business/BikesService.java              |    1 
 server/jtt808_parent/jtt808-protocol/src/main/java/org/yzh/protocol/t808/T0200.java      |    1 
 server/web/src/main/java/com/doumee/jtt808/web/model/enums/SessionKey.java               |   17 
 server/web/src/main/java/com/doumee/jtt808/t808/T0A00_8A00.java                          |   72 +
 server/services/src/main/java/com/doumee/core/utils/DateUtil.java                        |   14 
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT808Endpoint.java               |  255 +++++
 server/services/src/main/java/com/doumee/dao/business/model/Bikes.java                   |   26 
 server/web/src/main/java/com/doumee/jtt808/web/service/FileService.java                  |  230 ++++
 server/web/src/main/java/com/doumee/jtt808/t808/T8900.java                               |   85 +
 server/web/src/main/resources/application.yml                                            |   19 
 server/web/src/main/java/com/doumee/jtt808/web/controller/OtherController.java           |  124 ++
 server/web/src/main/java/com/doumee/jtt808/web/endpoint/JSATL12Endpoint.java             |   89 +
 36 files changed, 2,852 insertions(+), 16 deletions(-)

diff --git a/server/jtt808_parent/jtt808-protocol/src/main/java/org/yzh/protocol/t808/T0200.java b/server/jtt808_parent/jtt808-protocol/src/main/java/org/yzh/protocol/t808/T0200.java
index 04e3596..d6e86a1 100644
--- a/server/jtt808_parent/jtt808-protocol/src/main/java/org/yzh/protocol/t808/T0200.java
+++ b/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;
 
diff --git a/server/jtt808_parent/jtt808-server/src/main/java/org/yzh/web/endpoint/JT808Endpoint.java b/server/jtt808_parent/jtt808-server/src/main/java/org/yzh/web/endpoint/JT808Endpoint.java
index e08f946..aeeae35 100644
--- a/server/jtt808_parent/jtt808-server/src/main/java/org/yzh/web/endpoint/JT808Endpoint.java
+++ b/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;
diff --git a/server/services/pom.xml b/server/services/pom.xml
index 2ec7073..a94673b 100644
--- a/server/services/pom.xml
+++ b/server/services/pom.xml
@@ -16,4 +16,5 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 
+
 </project>
\ No newline at end of file
diff --git a/server/services/src/main/java/com/doumee/core/utils/DateUtil.java b/server/services/src/main/java/com/doumee/core/utils/DateUtil.java
index bd40424..689c112 100644
--- a/server/services/src/main/java/com/doumee/core/utils/DateUtil.java
+++ b/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()); // 杞崲涓篋ate
+            return  date;
+        }catch (Exception e){
 
+        }
+        return null;
+
+    }
     public static Date StringToDate2(String DATE) {
         if(StringUtils.isBlank(DATE)){
             return null;
diff --git a/server/services/src/main/java/com/doumee/core/wx/WxMiniConfig.java b/server/services/src/main/java/com/doumee/core/wx/WxMiniConfig.java
index 6c48947..555023c 100644
--- a/server/services/src/main/java/com/doumee/core/wx/WxMiniConfig.java
+++ b/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();
+
+        }
     }
     /**
      * 鍒濆鍖栧井淇″皬绋嬪簭鏀粯
diff --git a/server/services/src/main/java/com/doumee/dao/business/model/Bikes.java b/server/services/src/main/java/com/doumee/dao/business/model/Bikes.java
index 6eaa715..0d8f16d 100644
--- a/server/services/src/main/java/com/doumee/dao/business/model/Bikes.java
+++ b/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 = "鐢佃溅鎺у埗鍣⊿N鐮�")
+    @ExcelColumn(name="鐢佃溅鎺у埗鍣⊿N鐮�")
+    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;
 
 }
diff --git a/server/services/src/main/java/com/doumee/service/business/BikesService.java b/server/services/src/main/java/com/doumee/service/business/BikesService.java
index 90bc756..785ac4e 100644
--- a/server/services/src/main/java/com/doumee/service/business/BikesService.java
+++ b/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);
 }
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/BikesServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/BikesServiceImpl.java
index d9e52f8..95f2de0 100644
--- a/server/services/src/main/java/com/doumee/service/business/impl/BikesServiceImpl.java
+++ b/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) {
diff --git a/server/web/src/main/java/com/doumee/InterfaceApplication.java b/server/web/src/main/java/com/doumee/InterfaceApplication.java
index d29475d..2628aeb 100644
--- a/server/web/src/main/java/com/doumee/InterfaceApplication.java
+++ b/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();
     }
diff --git a/server/web/src/main/java/com/doumee/config/swagger/SwaggerConfig.java b/server/web/src/main/java/com/doumee/config/swagger/SwaggerConfig.java
index 97c15be..50784fb 100644
--- a/server/web/src/main/java/com/doumee/config/swagger/SwaggerConfig.java
+++ b/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("銆怞TT808鎺ュ彛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();
diff --git a/server/web/src/main/java/com/doumee/jtt808/t808/T0200.java b/server/web/src/main/java/com/doumee/jtt808/t808/T0200.java
new file mode 100644
index 0000000..d7c9198
--- /dev/null
+++ b/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();
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/t808/T0201_0500.java b/server/web/src/main/java/com/doumee/jtt808/t808/T0201_0500.java
new file mode 100644
index 0000000..87bba9a
--- /dev/null
+++ b/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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/t808/T0A00_8A00.java b/server/web/src/main/java/com/doumee/jtt808/t808/T0A00_8A00.java
new file mode 100644
index 0000000..a97cf61
--- /dev/null
+++ b/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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/t808/T8103.java b/server/web/src/main/java/com/doumee/jtt808/t808/T8103.java
new file mode 100644
index 0000000..8040926
--- /dev/null
+++ b/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銆乄ORD)")
+    private Map<Integer, Integer> parametersInt;
+    @Schema(description = "鏁板�煎瀷鍙傛暟鍒楄〃(DWORD銆丵WORD)")
+    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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/t808/T8900.java b/server/web/src/main/java/com/doumee/jtt808/t808/T8900.java
new file mode 100644
index 0000000..1968bdd
--- /dev/null
+++ b/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;
+    /** 閬撹矾杩愯緭璇両C鍗′俊鎭笂浼犳秷鎭负64Byte,涓嬩紶娑堟伅涓�24Byte,閬撹矾杩愯緭璇両C鍗¤璇侀�忎紶瓒呮椂鏃堕棿涓�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 = "淇℃伅鏌ヨ(澶栬浼犳劅鍣ㄧ殑鍩烘湰淇℃伅锛氬叕鍙镐俊鎭�佷骇鍝佷唬鐮併�佺増鏈彿銆佸璁綢D銆佸鎴蜂唬鐮�)")
+    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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/config/BeanConfig.java b/server/web/src/main/java/com/doumee/jtt808/web/config/BeanConfig.java
new file mode 100644
index 0000000..7878959
--- /dev/null
+++ b/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);
+        };
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/config/JTBeanConfig.java b/server/web/src/main/java/com/doumee/jtt808/web/config/JTBeanConfig.java
new file mode 100644
index 0000000..e6c493d
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/config/JTConfig.java b/server/web/src/main/java/com/doumee/jtt808/web/config/JTConfig.java
new file mode 100644
index 0000000..42daa56
--- /dev/null
+++ b/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();
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/config/WebLogAdapter.java b/server/web/src/main/java/com/doumee/jtt808/web/config/WebLogAdapter.java
new file mode 100644
index 0000000..d8f8960
--- /dev/null
+++ b/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("<<<<<鏍¢獙鐮侀敊璇痵ession={},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);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/config/WebMvcConfig.java b/server/web/src/main/java/com/doumee/jtt808/web/config/WebMvcConfig.java
new file mode 100644
index 0000000..e3ed3dc
--- /dev/null
+++ b/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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/controller/ExceptionController.java b/server/web/src/main/java/com/doumee/jtt808/web/controller/ExceptionController.java
new file mode 100644
index 0000000..9aa26c5
--- /dev/null
+++ b/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());
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/controller/JT1078Controller.java b/server/web/src/main/java/com/doumee/jtt808/web/controller/JT1078Controller.java
new file mode 100644
index 0000000..016944a
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/controller/JT808Controller.java b/server/web/src/main/java/com/doumee/jtt808/web/controller/JT808Controller.java
new file mode 100644
index 0000000..e573e36
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/controller/OtherController.java b/server/web/src/main/java/com/doumee/jtt808/web/controller/OtherController.java
new file mode 100644
index 0000000..d9dd083
--- /dev/null
+++ b/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");
+    }
+
+
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JSATL12Endpoint.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JSATL12Endpoint.java
new file mode 100644
index 0000000..7a2176b
--- /dev/null
+++ b/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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT1078Endpoint.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT1078Endpoint.java
new file mode 100644
index 0000000..d245e2c
--- /dev/null
+++ b/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) {
+
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT808Endpoint.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JT808Endpoint.java
new file mode 100644
index 0000000..7e657ae
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTHandlerInterceptor.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTHandlerInterceptor.java
new file mode 100644
index 0000000..f6b8bcc
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTMultiPacketListener.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTMultiPacketListener.java
new file mode 100644
index 0000000..05e969d
--- /dev/null
+++ b/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;
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTSessionListener.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/JTSessionListener.java
new file mode 100644
index 0000000..6f931ba
--- /dev/null
+++ b/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) {
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/endpoint/MessageManager.java b/server/web/src/main/java/com/doumee/jtt808/web/endpoint/MessageManager.java
new file mode 100644
index 0000000..11972e6
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/model/entity/DeviceDO.java b/server/web/src/main/java/com/doumee/jtt808/web/model/entity/DeviceDO.java
new file mode 100644
index 0000000..51cb231
--- /dev/null
+++ b/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();
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/model/enums/SessionKey.java b/server/web/src/main/java/com/doumee/jtt808/web/model/enums/SessionKey.java
new file mode 100644
index 0000000..25d5c06
--- /dev/null
+++ b/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);
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/model/vo/DeviceInfo.java b/server/web/src/main/java/com/doumee/jtt808/web/model/vo/DeviceInfo.java
new file mode 100644
index 0000000..752f285
--- /dev/null
+++ b/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();
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/service/FileService.java b/server/web/src/main/java/com/doumee/jtt808/web/service/FileService.java
new file mode 100644
index 0000000..948ecd6
--- /dev/null
+++ b/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";
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/web/src/main/resources/application.yml b/server/web/src/main/resources/application.yml
index db6d33c..a483097 100644
--- a/server/web/src/main/resources/application.yml
+++ b/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
\ No newline at end of file

--
Gitblit v1.9.3