Browse Source

feat(广西ai):增加广西ai处理模块,搭建日志,netty,处理与油机通讯完成

Zhenghanjv 1 year ago
parent
commit
7954a2759f
21 changed files with 863 additions and 0 deletions
  1. 107 0
      ai-fueling/pom.xml
  2. 13 0
      ai-fueling/src/main/java/com/tokheim/aifueling/AiFuelingApplication.java
  3. 87 0
      ai-fueling/src/main/java/com/tokheim/aifueling/communication/BaseAnalyzer.java
  4. 44 0
      ai-fueling/src/main/java/com/tokheim/aifueling/communication/analyzer/CardStatusAnalyzer.java
  5. 27 0
      ai-fueling/src/main/java/com/tokheim/aifueling/communication/entitys/BaseInfo.java
  6. 45 0
      ai-fueling/src/main/java/com/tokheim/aifueling/communication/entitys/CardStatus.java
  7. 12 0
      ai-fueling/src/main/java/com/tokheim/aifueling/communication/interfaces/IDataExtractor.java
  8. 26 0
      ai-fueling/src/main/java/com/tokheim/aifueling/netty/NettyInitializer.java
  9. 17 0
      ai-fueling/src/main/java/com/tokheim/aifueling/netty/NettyRunner.java
  10. 55 0
      ai-fueling/src/main/java/com/tokheim/aifueling/netty/NettyServer.java
  11. 43 0
      ai-fueling/src/main/java/com/tokheim/aifueling/netty/handler/AnalyzerHandler.java
  12. 85 0
      ai-fueling/src/main/java/com/tokheim/aifueling/netty/handler/UnpackHandler.java
  13. 82 0
      ai-fueling/src/main/java/com/tokheim/aifueling/utils/ByteArrayUtils.java
  14. 77 0
      ai-fueling/src/main/java/com/tokheim/aifueling/utils/SM4Utils.java
  15. 0 0
      ai-fueling/src/main/resources/application-dev.yml
  16. 0 0
      ai-fueling/src/main/resources/application-prod.yml
  17. 0 0
      ai-fueling/src/main/resources/application-test.yml
  18. 22 0
      ai-fueling/src/main/resources/application.yml
  19. 52 0
      ai-fueling/src/main/resources/logback-spring.xml
  20. 6 0
      ai-fueling/src/main/resources/static/index.html
  21. 63 0
      ai-fueling/src/test/java/com/tokheim/aifueling/AiFuelingApplicationTests.java

+ 107 - 0
ai-fueling/pom.xml

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.tokheim</groupId>
+    <artifactId>ai-fueling</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>ai-fueling</name>
+    <description>ai-fueling</description>
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>2.6.13</spring-boot.version>
+    </properties>
+    <dependencies>
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-data-jdbc</artifactId>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.mybatis.spring.boot</groupId>-->
+<!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
+<!--            <version>2.2.2</version>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.69.Final</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- 工具类 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.3.6</version>
+        </dependency>
+
+        <!-- SM4相关-->
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <version>1.70</version> <!-- 请检查最新版本 -->
+        </dependency>
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.tokheim.aifueling.AiFuelingApplication</mainClass>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/AiFuelingApplication.java

@@ -0,0 +1,13 @@
+package com.tokheim.aifueling;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AiFuelingApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(AiFuelingApplication.class, args);
+    }
+
+}

+ 87 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/communication/BaseAnalyzer.java

@@ -0,0 +1,87 @@
+package com.tokheim.aifueling.communication;
+
+import cn.hutool.core.convert.Convert;
+import com.tokheim.aifueling.communication.entitys.BaseInfo;
+import com.tokheim.aifueling.utils.ByteArrayUtils;
+
+public abstract class BaseAnalyzer {
+    public void analyze(String ip,byte[] data) {
+
+        if (data.length < 2) return;
+        int dataType = data[0];     //数据类型
+
+        BaseInfo baseInfo = getBaseInfo(ip,data); //获取基础信息
+        BaseInfo allInfo = getOtherInfo(baseInfo, ByteArrayUtils.slipt(data,2,data.length)); //获取完整信息
+        sendDataToThird(dataType,allInfo);
+    }
+
+    /**
+     * 发送数据给油机
+     * @param ip 要发生的油机ip
+     * @param data 要发送的数据
+     */
+    public void sendData(String ip,byte[] data) {
+
+    }
+
+    /**
+     * 获取基础信息
+     * @param ip 油机 ip
+     * @param data 原数据
+     * @return 基础信息
+     */
+    private BaseInfo getBaseInfo(String ip ,byte[] data) {
+        int fpoint = data[1] & 0xF0; //加油点
+
+        BaseInfo baseInfo = new BaseInfo();
+        byte[] slipBytes = ByteArrayUtils.slipt(data,2,data.length);//截掉命令字和加油点
+        int index = 0;
+        while (index < slipBytes.length) {
+            switch (slipBytes[index]) {
+                case 0x21:
+                    baseInfo.setEventType((int) slipBytes[index+1]);
+                    index += 2;
+                    break;
+                case 0x22:
+                    baseInfo.setCardType((int) slipBytes[index+1]);
+                    index += 2;
+                    break;
+                case 0x23:
+                    int len = slipBytes[index+1];
+                    byte[] oilCodeBytes = ByteArrayUtils.slipt(slipBytes,index+2,index+2+len);
+                    String oilCode = Convert.toHex(oilCodeBytes);
+                    baseInfo.setOilCode(oilCode);
+                    index = index+2+len;
+                    break;
+                case 0x24:
+                    int internalNum = slipBytes[index+1];//内部枪号
+                    //todo:通过配置,由ip,加油点,内部枪号来获取外部枪号
+//                    baseInfo.setNozzleNum();
+                    index += 2;
+                    break;
+                default:
+                    index++;
+                    break;
+            }
+        }
+
+        return baseInfo;
+    }
+
+    /**
+     * 发送数据到第三方系统
+     * @param dataType 数据类型,用于确定 baseInfo 可转换的子类
+     * @param baseInfo 数据(包含了全部数据)
+     */
+    private void sendDataToThird(Integer dataType,BaseInfo baseInfo) {
+
+    }
+
+    /**
+     * 获取其他数据
+     * @param baseInfo 基础信息
+     * @param data     原数据
+     * @return         包含其他数据的子类
+     */
+    protected abstract BaseInfo getOtherInfo(BaseInfo baseInfo, byte[] data);
+}

+ 44 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/communication/analyzer/CardStatusAnalyzer.java

@@ -0,0 +1,44 @@
+package com.tokheim.aifueling.communication.analyzer;
+
+import cn.hutool.core.convert.Convert;
+import com.tokheim.aifueling.communication.BaseAnalyzer;
+import com.tokheim.aifueling.communication.entitys.BaseInfo;
+import com.tokheim.aifueling.communication.entitys.CardStatus;
+import com.tokheim.aifueling.utils.ByteArrayUtils;
+
+/**
+ * 与油机协议解析类-卡类型相关信息解析类
+ */
+public class CardStatusAnalyzer extends BaseAnalyzer {
+    @Override
+    protected BaseInfo getOtherInfo(BaseInfo baseInfo, byte[] data) {
+        CardStatus cardStatus = new CardStatus(baseInfo);
+        int index = 0;
+        while (index < data.length) {
+            switch (data[index]) {
+                case 0x25:
+                    int balanceLen = data[index+1]; //获取数据长度
+                    int balanceIntLen = data[index+2] / 2; //获取整数部分长度
+                    String balanceIntPart = Convert.toHex(ByteArrayUtils.slipt(data,index+3,index+3+ balanceIntLen)); //获取整数部分
+                    String balanceDecimalsPart = Convert.toHex(ByteArrayUtils.slipt(data,index+3+ balanceIntLen,index+2+ balanceLen));//获取小数部分
+                    String balance = balanceIntPart + "." + balanceDecimalsPart; //拼接金额
+                    index = index + 2 + balanceLen;
+                    cardStatus.setBalance(balance);
+                    break;
+                case 0x26:
+                    int maxAmountLen = data[index+1]; //获取数据长度
+                    int maxAmountIntLen = data[index+2] / 2; //获取整数部分长度
+                    String maxAmountIntPart = Convert.toHex(ByteArrayUtils.slipt(data,index+3,index+3+maxAmountIntLen)); //获取整数部分
+                    String maxAmountDecimalsPart = Convert.toHex(ByteArrayUtils.slipt(data,index+3+maxAmountIntLen,index+2+maxAmountLen));//获取小数部分
+                    String maxAmount = maxAmountIntPart + "." + maxAmountDecimalsPart; //拼接金额
+                    index = index + 2 + maxAmountLen;
+                    cardStatus.setMaxRefuelingAmount(maxAmount);
+                    break;
+                default:
+                    index++;
+                    break;
+            }
+        }
+        return cardStatus;
+    }
+}

+ 27 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/communication/entitys/BaseInfo.java

@@ -0,0 +1,27 @@
+package com.tokheim.aifueling.communication.entitys;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 油机交互基础数据类
+ * <h1>eventType : 事件类型
+ * <h1>cardType : 卡类型
+ *      <p> 0:验证码加油方式或卡类型无效
+ *          1:司机卡
+ *          4:员工卡
+ *          5:验泵卡
+ *          6:维修卡
+*       </p>
+ * <h1>oilCode : 油品码
+ * <h1>nozzleNum : 枪号
+ */
+@Data
+public class BaseInfo {
+    private Integer eventType;
+    private Integer cardType;
+    private String oilCode;
+    private Integer nozzleNum;
+
+}

+ 45 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/communication/entitys/CardStatus.java

@@ -0,0 +1,45 @@
+package com.tokheim.aifueling.communication.entitys;
+
+import com.tokheim.aifueling.communication.interfaces.IDataExtractor;
+import lombok.Data;
+
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 卡状态信息交互类
+ * <p>balance: 卡余额</p>
+ * <p>maxRefuelingAmount: 卡最大可加金额</p>
+ */
+@Data
+public class CardStatus extends BaseInfo implements IDataExtractor {
+    Map<Integer,String> eventMap = Stream.of(
+            new AbstractMap.SimpleEntry<>(1,"refuel_no_password"),          //未设置密码
+            new AbstractMap.SimpleEntry<>(2,"refuel_password"),             //设置密码
+            new AbstractMap.SimpleEntry<>(3,"refuel_no_password_enter"),    //无密码卡点完确认键
+            new AbstractMap.SimpleEntry<>(4,"refuel_password_error"),       //密码输入错误
+            new AbstractMap.SimpleEntry<>(5,"refuel_password_current"),     //密码正确
+            new AbstractMap.SimpleEntry<>(6,"refuel_card_refund"),          //加油卡已退出
+            new AbstractMap.SimpleEntry<>(7,"refuel_card_max_oilamount")    //卡最大可加金额
+    ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+    private String balance;
+    private String maxRefuelingAmount;
+
+    public CardStatus(){}
+
+    public CardStatus(BaseInfo baseInfo) {
+        setCardType(baseInfo.getCardType());
+        setOilCode(baseInfo.getOilCode());
+        setNozzleNum(baseInfo.getNozzleNum());
+        setEventType(baseInfo.getEventType());
+    }
+
+    @Override
+    public String getEvent() {
+        return eventMap.get(getEventType()) != null ? eventMap.get(getEventType()) : "";
+    }
+
+}

+ 12 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/communication/interfaces/IDataExtractor.java

@@ -0,0 +1,12 @@
+package com.tokheim.aifueling.communication.interfaces;
+
+/**
+ * 定义数据子类的相关解析行为
+ */
+public interface IDataExtractor {
+    /**
+     * 获取事件类型
+     * @return 父类 eventType 对应事件类型的字符串
+     */
+    String getEvent();
+}

+ 26 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/netty/NettyInitializer.java

@@ -0,0 +1,26 @@
+package com.tokheim.aifueling.netty;
+
+import com.tokheim.aifueling.netty.handler.AnalyzerHandler;
+import com.tokheim.aifueling.netty.handler.UnpackHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.bytes.ByteArrayDecoder;
+import io.netty.handler.codec.bytes.ByteArrayEncoder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class NettyInitializer extends ChannelInitializer<SocketChannel> {
+    @Override
+    protected void initChannel(SocketChannel socketChannel) throws Exception {
+        ChannelPipeline pipeline = socketChannel.pipeline();
+
+        pipeline.addLast(new ByteArrayDecoder());
+        pipeline.addLast(new ByteArrayEncoder());
+
+        pipeline.addLast(new UnpackHandler());
+        pipeline.addLast(new AnalyzerHandler());
+    }
+}

+ 17 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/netty/NettyRunner.java

@@ -0,0 +1,17 @@
+package com.tokheim.aifueling.netty;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NettyRunner implements ApplicationRunner {
+    @Autowired
+    private NettyServer nettyServer;
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        new Thread(() -> nettyServer.start()).start();
+    }
+}

+ 55 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/netty/NettyServer.java

@@ -0,0 +1,55 @@
+package com.tokheim.aifueling.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class NettyServer {
+    @Autowired
+    private NettyInitializer nettyInitializer;
+
+    @Value("${nettyInfo.port}")
+    private int port;
+
+    @Value("${nettyInfo.boosThreadCount}")
+    private int boosThreadCount;
+
+    @Async
+    public void start() {
+        NioEventLoopGroup boosLoopGroup = new NioEventLoopGroup(boosThreadCount);
+        NioEventLoopGroup workLoopGroup = new NioEventLoopGroup();
+
+        try {
+            ServerBootstrap serverBootstrap = new ServerBootstrap();
+            serverBootstrap.group(boosLoopGroup, workLoopGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .childHandler(nettyInitializer)
+                    .option(ChannelOption.SO_BACKLOG,1024)
+                    .childOption(ChannelOption.SO_KEEPALIVE,true);
+
+            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
+            log.info("Server started on port {}", port);
+            channelFuture.channel().closeFuture().sync();
+
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            log.error(e.getMessage());
+        } finally {
+            boosLoopGroup.shutdownGracefully();
+            workLoopGroup.shutdownGracefully();
+        }
+
+
+    }
+
+}

+ 43 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/netty/handler/AnalyzerHandler.java

@@ -0,0 +1,43 @@
+package com.tokheim.aifueling.netty.handler;
+
+import com.tokheim.aifueling.communication.BaseAnalyzer;
+import com.tokheim.aifueling.communication.analyzer.CardStatusAnalyzer;
+import com.tokheim.aifueling.utils.ByteArrayUtils;
+import com.tokheim.aifueling.utils.SM4Utils;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 解密,分发到数据处理类
+ */
+@Slf4j
+@Component
+public class AnalyzerHandler extends ChannelInboundHandlerAdapter {
+
+    Map<Byte,BaseAnalyzer> eventMap = new HashMap<>();
+
+    public AnalyzerHandler() {
+        eventMap.put((byte) 0x01,new CardStatusAnalyzer());
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        byte[] dataBytes = (byte[]) msg;
+        byte[] data = SM4Utils.decrypt(dataBytes);
+        log.info("解密后信息:{}", ByteArrayUtils.bytesToHexString(data));
+        BaseAnalyzer analyzer = eventMap.get(data[0]);
+        analyzer.analyze(getIP(ctx.channel().remoteAddress().toString()),data);
+        ctx.flush();
+    }
+
+    private String getIP(String address) {
+        String[] split = address.split(":");
+        return split[0].replace("/","");
+    }
+}

+ 85 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/netty/handler/UnpackHandler.java

@@ -0,0 +1,85 @@
+package com.tokheim.aifueling.netty.handler;
+
+import cn.hutool.core.convert.Convert;
+import com.tokheim.aifueling.utils.ByteArrayUtils;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 处理粘包,去包头
+ */
+@Slf4j
+@Component
+@ChannelHandler.Sharable
+public class UnpackHandler extends ChannelInboundHandlerAdapter {
+
+    //数据包头
+    private byte[] dataHead = new byte[]{0x02,0x01,0x01,0x00,0x00,0x62};
+
+    //存储数据
+    private byte[] cacheData = new byte[0];
+
+    //客户端连接
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        super.handlerAdded(ctx);
+        log.info("客户端连接:" + ctx.channel().remoteAddress());
+        cacheData = new byte[0];
+    }
+
+    //客户端断开连接
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        super.handlerRemoved(ctx);
+        log.info("客户端断开连接" + ctx.channel().remoteAddress());
+        cacheData = new byte[0];
+    }
+
+    //报错
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        super.exceptionCaught(ctx, cause);
+        cacheData = new byte[0];
+    }
+
+    //接收到数据
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        byte[] bytes = (byte[]) msg;
+        log.info("接收到" + ctx.channel().remoteAddress() + "信息:" + ByteArrayUtils.bytesToHexString(bytes));
+
+        cacheData = ByteArrayUtils.add(cacheData,bytes);
+        getData(ctx);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+        ctx.flush();
+    }
+
+    //获取实际数据
+    private void getData(ChannelHandlerContext ctx) {
+        //缓存中没有包头 || 有包头但是没有数据长度
+        int headIndex = ByteArrayUtils.findSubarrayIndex(cacheData, dataHead);
+        if (headIndex == -1 || cacheData.length <= headIndex + 7) return;
+
+        //获取数据长度
+        byte[] dataLenBytes = ByteArrayUtils.slipt(cacheData, headIndex + 6, headIndex + 8);
+        byte[] coverBytes = {0x00,0x00};
+        int dataLen = Convert.bytesToInt(ByteArrayUtils.add(coverBytes,dataLenBytes));
+
+
+        //数据长度不够
+        if (cacheData.length <= dataLen) return;
+
+        //将加密截取(命令字+实际数据)发送到下一个 handler
+        byte[] dataBytes = ByteArrayUtils.slipt(cacheData, headIndex + 8, headIndex + 8 + dataLen);
+        ctx.fireChannelRead(dataBytes);
+
+        //将截取到的数据从缓存中删除
+        cacheData = ByteArrayUtils.slipt(cacheData,headIndex + 8 + dataLen,cacheData.length);
+    }
+}

+ 82 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/utils/ByteArrayUtils.java

@@ -0,0 +1,82 @@
+package com.tokheim.aifueling.utils;
+
+public class ByteArrayUtils {
+
+    /**
+     * 对 byte[] 进行截取
+     * @param source 原数组
+     * @param startIndex 开始截取的下标
+     * @param endIndex  结束截取的下标(不包括该下标)
+     * @return  截取后数据
+     */
+    public static byte[] slipt(byte[] source,int startIndex,int endIndex){
+        if(startIndex < 0 || endIndex < 0 || endIndex > source.length || startIndex > endIndex){
+            return new byte[0];
+        }
+        int length = endIndex-startIndex;
+        byte[] result = new byte[length];
+        System.arraycopy(source,startIndex,result,0,length);
+        return result;
+    }
+
+    /**
+     * 两个 byte[] 相加
+     * @param first 第一数组
+     * @param second 第二个数组
+     * @return 相加后的数组
+     */
+    public static byte[] add(byte[] first,byte[] second){
+        byte[] result = new byte[first.length+second.length];
+        System.arraycopy(first,0,result,0,first.length);
+        System.arraycopy(second,0,result,first.length,second.length);
+        return result;
+    }
+
+    /**
+     * 获取子数组匹配下标
+     * @param source 原数组
+     * @param target 目标匹配数组
+     * @return 匹配下标
+     */
+    public static int findSubarrayIndex(byte[] source, byte[] target) {
+        if (source == null || target == null || source.length < target.length) {
+            return -1; // 边界条件检查
+        }
+
+        for (int i = 0; i <= source.length - target.length; i++) {
+            int j;
+            for (j = 0; j < target.length; j++) {
+                if (source[i + j] != target[j]) {
+                    break; // 一旦发现不匹配,立即跳出内层循环
+                }
+            }
+
+            if (j == target.length) {
+                return i; // 如果完整匹配了 target,则返回起始索引
+            }
+        }
+
+        return -1; // 没有找到匹配项
+    }
+
+    /**
+     * byte[] 转 HEX
+     * @param src 待转数组
+     * @return hex
+     */
+    public static String bytesToHexString(byte[] src) {
+        StringBuilder stringBuilder = new StringBuilder();
+        if (src == null || src.length <= 0) {
+            return null;
+        }
+        for (byte b : src) {
+            int v = b & 0xFF;
+            String hv = Integer.toHexString(v);
+            if (hv.length() < 2) {
+                stringBuilder.append(0);
+            }
+            stringBuilder.append(hv).append(" ");
+        }
+        return stringBuilder.toString();
+    }
+}

+ 77 - 0
ai-fueling/src/main/java/com/tokheim/aifueling/utils/SM4Utils.java

@@ -0,0 +1,77 @@
+package com.tokheim.aifueling.utils;
+
+import cn.hutool.core.convert.Convert;
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.crypto.engines.SM4Engine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.springframework.beans.factory.annotation.Value;
+import org.bouncycastle.util.encoders.Hex;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.Security;
+
+@Slf4j
+public class SM4Utils {
+    // 确保在加密之前注册了 Bouncy Castle Provider
+    static {
+        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+    }
+
+    private static final String key = "DFSwwwtokheimcom";
+
+    private static String KEY_ALGORITHM = "SM4";            //获取密钥对象所对应的算法
+    private static String ALGORITHM = "SM4/ECB/NoPadding";  //指定加密的算法,模式,填充
+    private static String PROVIDER = "BC";                      //指定加密服务提供者,这里指 BouncyCastleProvider
+
+    /**
+     * SM4 加密
+     * @param plainText 明文
+     * @return 密文
+     * @throws Exception
+     */
+    public static byte[] encrypt(byte[] plainText) throws Exception {
+        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+        // 密钥必须是16字节(128位)
+        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
+
+        // 获取 Cipher 实例,指定算法为 "SM4/ECB/NoPadding"
+        Cipher cipher = Cipher.getInstance(ALGORITHM, PROVIDER);
+
+        // 初始化 Cipher 为加密模式
+        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+
+        //保证加密明文长度为 16 的倍数
+        int paddingLength = 16 - (plainText.length % 16);
+        if (paddingLength != 16) {
+            byte[] fillBytes = new byte[plainText.length + paddingLength];
+            System.arraycopy(plainText, 0, fillBytes, 0, plainText.length);
+            plainText = fillBytes;
+        }
+        // 执行加密
+        return cipher.doFinal(plainText);
+    }
+
+    /**
+     * 解密
+     * @param cipherText 密文
+     * @return 明文
+     * @throws Exception
+     */
+    public static byte[] decrypt(byte[] cipherText) throws Exception {
+        if (cipherText.length % 16 != 0) {
+            log.info("sm4解密,密文长度不为 16 倍数,解密失败\n" + ByteArrayUtils.bytesToHexString(cipherText));
+            return new byte[0];
+        }
+        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+        // 密钥必须是16字节(128位)
+        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
+
+        Cipher cipher = Cipher.getInstance(ALGORITHM, PROVIDER);
+        cipher.init(Cipher.DECRYPT_MODE, keySpec);
+        return cipher.doFinal(cipherText);
+    }
+}

+ 0 - 0
ai-fueling/src/main/resources/application-dev.yml


+ 0 - 0
ai-fueling/src/main/resources/application-prod.yml


+ 0 - 0
ai-fueling/src/main/resources/application-test.yml


+ 22 - 0
ai-fueling/src/main/resources/application.yml

@@ -0,0 +1,22 @@
+# server
+server:
+  port: 9001
+  tomcat:
+    uri-encoding: utf-8
+    #配置内置tomcat的访问日志
+    max-threads: 300 # Tomcat 最大线程数,默认值为 200
+    min-spare-threads: 30 # Tomcat 启动初始化的线程数,默认值10
+    basedir: /app_log/web/access
+    accesslog:
+      buffered: true
+      enabled: true
+      pattern: '%t %a %r %s (%D ms)'
+  servlet:
+    # 访问上下文 tms-master
+    context-path: /ai-fueling
+
+
+# netty 配置
+nettyInfo:
+  port: 20002
+  boosThreadCount: 1 # 主线程组数目

+ 52 - 0
ai-fueling/src/main/resources/logback-spring.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration  scan="true" scanPeriod="60 seconds" debug="false">
+    <contextName>aiFueling</contextName>
+    <include resource="org/springframework/boot/logging/logback/base.xml"/>
+
+    <property name="petroleum.log.path" value="./aiFuelingLog" />
+    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n" />
+    <!--输出到控制台-->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <!--    输出到文件-->
+    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${petroleum.log.path}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!--每个日志文件最大 20MB -->
+                <maxFileSize>20MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!-- 最大保存时间:30天-->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <root level="info">
+        <appender-ref ref="console" />
+        <appender-ref ref="file" />
+    </root>
+
+<!--    &lt;!&ndash; logback为java中的包 &ndash;&gt;-->
+<!--    <logger name="com.dudu.controller"/>-->
+<!--    &lt;!&ndash;logback.LogbackDemo:类的全路径 &ndash;&gt;-->
+<!--    <logger name="com.dudu.controller.LearnController" level="WARN" additivity="false">-->
+<!--        <appender-ref ref="console"/>-->
+<!--    </logger>-->
+</configuration>

+ 6 - 0
ai-fueling/src/main/resources/static/index.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+<h1>hello word!!!</h1>
+<p>this is a html page</p>
+</body>
+</html>

+ 63 - 0
ai-fueling/src/test/java/com/tokheim/aifueling/AiFuelingApplicationTests.java

@@ -0,0 +1,63 @@
+package com.tokheim.aifueling;
+
+import cn.hutool.core.convert.Convert;
+import com.tokheim.aifueling.utils.ByteArrayUtils;
+import com.tokheim.aifueling.utils.SM4Utils;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SpringBootTest
+class AiFuelingApplicationTests {
+
+    @Test
+    void testSm4Encryption() {
+        byte[] bytes = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,0x11,0x12};
+        try {
+            byte[] resultBytes = SM4Utils.encrypt(bytes);
+            System.out.println(Convert.toHex(resultBytes));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    void testSm4Decryption() {
+        byte[] bytes = {0x24,0x72,0x3f,0x04,0x24,0x1f, (byte) 0x88, (byte) 0xc4, (byte) 0xab,0x34,0x62, (byte) 0xf2,
+                (byte) 0xbc, (byte) 0x83, (byte) 0xac, (byte) 0x99, (byte) 0xce, 0x34,0x73, (byte) 0x9b, (byte) 0xaa,
+                0x72, (byte) 0xdf,0x77,0x68, (byte) 0xb0,0x35, (byte) 0xa7,0x2e,0x0e,0x57};
+
+        try {
+            byte[] resultbytes = SM4Utils.decrypt(bytes);
+            System.out.println(Convert.toHex(resultbytes));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    void testFindSubIndex(){
+        byte[] source = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09};
+        byte[] target = {0x02,0x03};
+        System.out.println(ByteArrayUtils.findSubarrayIndex(source,target));
+
+        byte[] source2 = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09};
+        byte[] target2 = {0x10,0x24};
+        System.out.println(ByteArrayUtils.findSubarrayIndex(source2,target2));
+
+        byte[] source3 = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09};
+        byte[] target3 = {0x02,0x03};
+        System.out.println(ByteArrayUtils.findSubarrayIndex(source3,target3));
+    }
+
+    @Test
+    void testBytes2Hex(){
+        byte[] bytes = {0x12,0x01,0x10, (byte) 0xaa, (byte) 0xff,0x00,0x1a,0x11, (byte) 0xab};
+        String s = ByteArrayUtils.bytesToHexString(bytes);
+        System.out.println(s);
+    }
+
+}