SpringBoot+Mybatis+Mysql+Netty+Redis+Uni-app开发的充电桩小程序管理后台

由于之前用Php+WorkMan开发的新能源汽车充电管理系统,(有兴趣的可以查看这里),公司说这技术淘汰了,需要用java重新架构一套,适应市场需求。

毕竟入职时候,说自己php、java都能撸出冒火星,这个任务无可厚非放在我的身上了。

经过小组讨论,决定架构用:SpringBoot+Mybatis+Mysql+Netty+Redis+Uni-app

有什么不明白地方,大家一起交流:150-5606-9927

技术解决方案

硬件跟服务器通讯

硬件厂商提供的充电桩和充电运营管理系统之间的通信接口采用基于 TCP/IP Socket 的通信方式实现,按照长连接工作模式。

我们使用Netty跟硬件桩子(TCP/IP)通讯

应用层报文桢格式

  1. 应用层数据结构

起始标志

数据长度

序列号域

加密标志

桢类型标志

消息体

桢校验域

1字节

1字节

2字节

1字节

1字节

N字节

2字节

数据结构定义说明:

  1. 起始标识符代表一数据的开始,固定为0x68。
  2. 数据域字节数,数据域长度不超过200字节。不加密时为原数据长度,加密时,为加密后数据长度。其值为“序列号域+加密标志+桢类型标志+消息体”字节数之和。
  3. 序列号域即为数据包的发送顺序号,从 0 开始顺序增加,如是应答数据包,则与询问数据包序号保持一致,当桩与平台网络断开重新建立连接或者溢出归0。
  4. 加密标志只针对消息体(数据单元)。0x00:不加密,0x01:3DES
  5. 桢类志定了上下行数据

校验域:从序列号域到数据域的 CRC 校验,校验多项式为 0x180D,低字节在前,高字节在后,

  1. 数据格式定义

数据格式包括BCD码、BIN码、ASCII,BIN码均为低位在前高位在后。协议中小数值均乘倍率(保留小数点位数)上送平台(例如:电压为225.1,保留一位小数,上送到平台值为2251,即0x8CB)。CP56Time2a格式如下:

Miliseconds(D7-D0)

Miliseconds(D15-D8)

IV(D7)

RES1

Minutes(D5-D0)

SU(D7)

RES2

Hours(D4-D0)

DAY of WEEK

DAY of MONTH(D4-D0)

RES3

Month(D3-D0)

RES4

Years(D6-D0)

有了充电桩硬件厂商提供的数据协议,

我们开始写Netty服务器端,贴出核心代码

package com.icojoo.chargeNetty.tcpIp;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class NettyTcpServer {
    /**
     * boss事件轮询线程组
     * 处理Accept连接事件的线程,这里线程数设置为1即可,netty处理链接事件默认为单线程,过度设置反而浪费cpu资源
     */
    private EventLoopGroup boss = new NioEventLoopGroup(1);

    /**
     * worker事件轮询线程组
     * 处理handler的工作线程,其实也就是处理IO读写 。线程数据默认为 CPU 核心数乘以2
     */
    private EventLoopGroup worker = new NioEventLoopGroup();

    @Autowired
    ServerChannelInitializer serverChannelInitializer;

    @Value("${netty.tcp.client.port}")
    private Integer port;

    /**
     *
     * 与客户端建立连接后得到的通道对象
     */
    private Channel channel;

    /**
     * 存储client的channel
     * key:ip value:Channel
     */
    public static Map<String, Channel> map = new ConcurrentHashMap<String, Channel>();

    /**
     * 开启Netty tcp server服务
     *
     * @return
     */
    public ChannelFuture start() {
        // 启动类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //组配置,初始化ServerBootstrap的线程组
        serverBootstrap.group(boss, worker)
                //构造channel通道工厂 bossGroup的通道,只是负责连接
                .channel(NioServerSocketChannel.class)
                //设置通道处理者ChannelHandlerWorkerGroup的处理器
                .childHandler(serverChannelInitializer)
                //socket参数,当服务器请求处理程全满时,用于临时存放已完成三次握手请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
                .option(ChannelOption.SO_BACKLOG, 1024)
                //启用心跳保活机制,tcp,默认2小时发一次心跳
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        //Future:异步任务的生命周期,可用来获取任务结果
        // 绑定端口 开启监听 同步等待
        ChannelFuture channelFuture1 = serverBootstrap.bind(port).syncUninterruptibly();
        if (channelFuture1 != null && channelFuture1.isSuccess()) {
            // 获取通道
            channel = channelFuture1.channel();
            log.info("Netty tcp server start success,port={}",port);
        }else {
            log.error("Netty tcp server start fail");
        }
        return channelFuture1;
    }

    /**
     * 停止Netty tcp server服务
     */
    public void destroy(){
        if (channel != null) {
            channel.close();
        }
        try {
            Future<?> future = worker.shutdownGracefully().await();
            if (!future.isSuccess()) {
                log.error("netty tcp workerGroup shutdown fail,{}",future.cause());
            }
        } catch (InterruptedException e) {
            log.error(e.toString());
        }
        log.info("Netty tcp server shutdown success");
    }
}
package com.icojoo.chargeNetty.tcpIp;

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 io.netty.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    ServerChannelHandler serverChannelHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //IdleStateHandler心跳机制,如果超时触发Handle中userEventTrigger()方法
        pipeline.addLast("idleStateHandler",
                new IdleStateHandler(15,0,0, TimeUnit.MINUTES));
        // 字符串编解码器
        pipeline.addLast(
                new ByteArrayDecoder(),
                new ByteArrayEncoder()
        );






        // 自定义Handler
        pipeline.addLast("serverChannelHandler",serverChannelHandler);
    }
}
package com.icojoo.chargeNetty.tcpIp;


import com.icojoo.chargeNetty.service.ITBookService;
import com.icojoo.chargeNetty.utils.Crc3Util;
import com.icojoo.chargeNetty.utils.JuDianUtil;
import com.icojoo.chargeNetty.utils.RedisUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;



@Slf4j
@Component
@ChannelHandler.Sharable
public class ServerChannelHandler extends SimpleChannelInboundHandler<byte[]> {
    @Autowired
    private ITBookService itBookService;
    @Autowired
    RedisUtil redisUtil;


    /**
     * 拿到传过来的msg数据,开始处理
     * @param channelHandlerContext
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, byte[]  msg) throws Exception {


        String vv=JuDianUtil.bytesToHexString(msg);



      log.info("Netty tcp server receive--- message: {}",  vv);


//      效验
//
String nn="000000010361002001101300020a3232303432320000011920168010203000000000";

      byte[] bb= JuDianUtil.hexStringToBytes(nn);

      log.info("Netty tcp server receive message------: {}", Crc3Util.getCRC3(bb,null));


//        String cmd = JuDianUtil.getMsgCmd(msg);
//        String pileCode = JuDianUtil.getPileNum(msg);
//
//        log.info("Netty tcp: {}", cmd);
//        log.info("Netty tcp: {}", pileCode);



//        itBookService.save(new TBook("1231", (String) msg));
//        redisUtil.set("123",new TBook(1,"1231", (String) msg),60);

//       channelHandlerContext.writeAndFlush(JuDianUtil.hexStringToBytes("680c000000020361002001101300c17f")).syncUninterruptibly();

    }




    /**
     * 活跃的、有效的通道
     * 第一次连接成功后进入的方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        log.info("tcp client "+getRemoteAddress(ctx)+" connect success");
        NettyTcpServer.map.put(getIPString(ctx),ctx.channel());



    }




    /**
     * 不活动的通道
     * 连接丢失后执行的方法(client端可据此实现断线重连)
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 删除Channel Map中失效的Client
        log.info("tcp client "+getRemoteAddress(ctx)+" 删除Channel");
        NettyTcpServer.map.remove(getIPString(ctx));
        ctx.close();
    }

掉线移除
//    @Override
//    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//
//        log.info("tcp client "+getRemoteAddress(ctx)+" handlerRemoved");
//        NettyTcpServer.map.remove(getIPString(ctx));
//        ctx.close();
//    }




    /**
     * 异常处理
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        // 发生异常 关闭连接
        log.error("引擎{}的通道发生异常,断开连接",getRemoteAddress(ctx));
        ctx.close();
    }

    /**
     * 心跳机制 超时处理
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        String socketString = ctx.channel().remoteAddress().toString();
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state()== IdleState.READER_IDLE) {
                log.info("Client: "+socketString+" READER_IDLE读超时");
                ctx.disconnect();
            }else if (event.state()==IdleState.WRITER_IDLE){
                log.info("Client: "+socketString+" WRITER_IDLE写超时");
                ctx.disconnect();
            }else if (event.state()==IdleState.ALL_IDLE){
                log.info("Client: "+socketString+" ALL_IDLE总超时");
                ctx.disconnect();
            }
        }
    }

    /**
     * 获取client对象:ip+port
     * @param channelHandlerContext
     * @return
     */
    public String getRemoteAddress(ChannelHandlerContext channelHandlerContext){
        String socketString = "";
        socketString = channelHandlerContext.channel().remoteAddress().toString();
        return socketString;
    }

    /**
     * 获取client的ip
     * @param channelHandlerContext
     * @return
     */
    public String getIPString(ChannelHandlerContext channelHandlerContext){
        String ipString = "";
        String socketString = channelHandlerContext.channel().remoteAddress().toString();
        int colonAt = socketString.indexOf(":");
        ipString = socketString.substring(1,colonAt);

        return ipString;
    }



}

 CRC 校验工具类

package com.icojoo.chargeNetty.utils;

public class Crc3Util {
    /**
     * 查表法计算CRC16校验
     *
     * @param data 需要计算的字节数组
     */
    public static String getCRC3(byte[] data,String order) {
        byte[] crc16_h = {
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
                (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40
        };

        byte[] crc16_l = {
                (byte) 0x00, (byte) 0xC0, (byte) 0xC1, (byte) 0x01, (byte) 0xC3, (byte) 0x03, (byte) 0x02, (byte) 0xC2, (byte) 0xC6, (byte) 0x06, (byte) 0x07, (byte) 0xC7, (byte) 0x05, (byte) 0xC5, (byte) 0xC4, (byte) 0x04,
                (byte) 0xCC, (byte) 0x0C, (byte) 0x0D, (byte) 0xCD, (byte) 0x0F, (byte) 0xCF, (byte) 0xCE, (byte) 0x0E, (byte) 0x0A, (byte) 0xCA, (byte) 0xCB, (byte) 0x0B, (byte) 0xC9, (byte) 0x09, (byte) 0x08, (byte) 0xC8,
                (byte) 0xD8, (byte) 0x18, (byte) 0x19, (byte) 0xD9, (byte) 0x1B, (byte) 0xDB, (byte) 0xDA, (byte) 0x1A, (byte) 0x1E, (byte) 0xDE, (byte) 0xDF, (byte) 0x1F, (byte) 0xDD, (byte) 0x1D, (byte) 0x1C, (byte) 0xDC,
                (byte) 0x14, (byte) 0xD4, (byte) 0xD5, (byte) 0x15, (byte) 0xD7, (byte) 0x17, (byte) 0x16, (byte) 0xD6, (byte) 0xD2, (byte) 0x12, (byte) 0x13, (byte) 0xD3, (byte) 0x11, (byte) 0xD1, (byte) 0xD0, (byte) 0x10,
                (byte) 0xF0, (byte) 0x30, (byte) 0x31, (byte) 0xF1, (byte) 0x33, (byte) 0xF3, (byte) 0xF2, (byte) 0x32, (byte) 0x36, (byte) 0xF6, (byte) 0xF7, (byte) 0x37, (byte) 0xF5, (byte) 0x35, (byte) 0x34, (byte) 0xF4,
                (byte) 0x3C, (byte) 0xFC, (byte) 0xFD, (byte) 0x3D, (byte) 0xFF, (byte) 0x3F, (byte) 0x3E, (byte) 0xFE, (byte) 0xFA, (byte) 0x3A, (byte) 0x3B, (byte) 0xFB, (byte) 0x39, (byte) 0xF9, (byte) 0xF8, (byte) 0x38,
                (byte) 0x28, (byte) 0xE8, (byte) 0xE9, (byte) 0x29, (byte) 0xEB, (byte) 0x2B, (byte) 0x2A, (byte) 0xEA, (byte) 0xEE, (byte) 0x2E, (byte) 0x2F, (byte) 0xEF, (byte) 0x2D, (byte) 0xED, (byte) 0xEC, (byte) 0x2C,
                (byte) 0xE4, (byte) 0x24, (byte) 0x25, (byte) 0xE5, (byte) 0x27, (byte) 0xE7, (byte) 0xE6, (byte) 0x26, (byte) 0x22, (byte) 0xE2, (byte) 0xE3, (byte) 0x23, (byte) 0xE1, (byte) 0x21, (byte) 0x20, (byte) 0xE0,
                (byte) 0xA0, (byte) 0x60, (byte) 0x61, (byte) 0xA1, (byte) 0x63, (byte) 0xA3, (byte) 0xA2, (byte) 0x62, (byte) 0x66, (byte) 0xA6, (byte) 0xA7, (byte) 0x67, (byte) 0xA5, (byte) 0x65, (byte) 0x64, (byte) 0xA4,
                (byte) 0x6C, (byte) 0xAC, (byte) 0xAD, (byte) 0x6D, (byte) 0xAF, (byte) 0x6F, (byte) 0x6E, (byte) 0xAE, (byte) 0xAA, (byte) 0x6A, (byte) 0x6B, (byte) 0xAB, (byte) 0x69, (byte) 0xA9, (byte) 0xA8, (byte) 0x68,
                (byte) 0x78, (byte) 0xB8, (byte) 0xB9, (byte) 0x79, (byte) 0xBB, (byte) 0x7B, (byte) 0x7A, (byte) 0xBA, (byte) 0xBE, (byte) 0x7E, (byte) 0x7F, (byte) 0xBF, (byte) 0x7D, (byte) 0xBD, (byte) 0xBC, (byte) 0x7C,
                (byte) 0xB4, (byte) 0x74, (byte) 0x75, (byte) 0xB5, (byte) 0x77, (byte) 0xB7, (byte) 0xB6, (byte) 0x76, (byte) 0x72, (byte) 0xB2, (byte) 0xB3, (byte) 0x73, (byte) 0xB1, (byte) 0x71, (byte) 0x70, (byte) 0xB0,
                (byte) 0x50, (byte) 0x90, (byte) 0x91, (byte) 0x51, (byte) 0x93, (byte) 0x53, (byte) 0x52, (byte) 0x92, (byte) 0x96, (byte) 0x56, (byte) 0x57, (byte) 0x97, (byte) 0x55, (byte) 0x95, (byte) 0x94, (byte) 0x54,
                (byte) 0x9C, (byte) 0x5C, (byte) 0x5D, (byte) 0x9D, (byte) 0x5F, (byte) 0x9F, (byte) 0x9E, (byte) 0x5E, (byte) 0x5A, (byte) 0x9A, (byte) 0x9B, (byte) 0x5B, (byte) 0x99, (byte) 0x59, (byte) 0x58, (byte) 0x98,
                (byte) 0x88, (byte) 0x48, (byte) 0x49, (byte) 0x89, (byte) 0x4B, (byte) 0x8B, (byte) 0x8A, (byte) 0x4A, (byte) 0x4E, (byte) 0x8E, (byte) 0x8F, (byte) 0x4F, (byte) 0x8D, (byte) 0x4D, (byte) 0x4C, (byte) 0x8C,
                (byte) 0x44, (byte) 0x84, (byte) 0x85, (byte) 0x45, (byte) 0x87, (byte) 0x47, (byte) 0x46, (byte) 0x86, (byte) 0x82, (byte) 0x42, (byte) 0x43, (byte) 0x83, (byte) 0x41, (byte) 0x81, (byte) 0x80, (byte) 0x40
        };

        int crc = 0x0000ffff;
        int ucCRCHi = 0x00ff;
        int ucCRCLo = 0x00ff;
        int iIndex;
        for (int i = 0; i < data.length; ++i) {
            iIndex = (ucCRCLo ^ data[i]) & 0x00ff;
            ucCRCLo = ucCRCHi ^ crc16_h[iIndex];
            ucCRCHi = crc16_l[iIndex];
        }

        crc = ((ucCRCHi & 0x00ff) << 8) | (ucCRCLo & 0x00ff) & 0xffff;

        //高低位互换,输出符合相关工具对Modbus CRC16的运算
        if("H".equals(order)) {
            crc = ( (crc & 0xFF00) >> 8) | ( (crc & 0x00FF ) << 8);
        }
        return String.format("%04X", crc);
    }

}

ui界面重新设计了

 

 

 

 

 

 

 

 

2.5.1、总后台管理

权限管理、管理员管理、角色管理、系统基本管理、互联互通管理、支付管理、短信通知管理。

2.5.2、用户管理

APP、小程序会员管理、代理商管理、商户管理、公司账户管理。

2.5.3、设备管理

站场网站管理、设备管理、功率监控、故障管理、告警管理、摄像头管理。

2.5.4、订单管理

充电订单、充值订单、卡充电单、退款订单等

2.5.5、财务管理

平台流水、代理商流水、代理商提现、商户流水、商户提现、会员流水、电子发票自助开票管理

2.5.6、数据分析

订单分析、会员分析、财务分析、运营数据分析等

2.6、App、小程序端功能点

2.6.1、首页

充电站列表、导航、当前价格、距离远近、设备是否有空闲、是否有停车位、停车收费标准等。

2.6.2、充电

扫码充电、插枪充电、刷卡充电、充满推送、远程停充、占位费计算、充电订单等。

2.6.3、会员中心

显示余额、充值、充电卡、积分、优惠券、每日签到、故障申报、发票申领、联系客服。

代理商或商户:收益统计、佣金查看、提现、收支流水等

2.7、数据大屏

可视化数据大屏数据 通过地图、柱状图、折线图、饼图把场站位置、场站数量。充电桩的类型、充电桩状态、当月/当日充值金额,当月/当日充电金额,电站告警等相对复杂、抽象的数据通过可视的方式以一种人们更容易理解的、更直观的方式来展示出来,将数据转换成图或表等。将业务的关键指标以可视化的方式展示到一个或多个LED屏幕上,不仅使业务人员能够从复杂的业务数据中快速、直接地找到重要数据,而且能对决策者起到辅助作用。

 

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
Spring Boot是一个用于快速构建基于Spring框架的Java应用程序的开发框架。它简化了Spring应用程序的配置和部署过程,提供了一种快速开发的方式。 MyBatis是一个持久层框架,它可以将Java对象与数据库表进行映射,并提供了灵活的SQL查询和更新操作。MyBatis通过XML或注解的方式来配置SQL语句和映射关系。 Redis是一个开源的内存数据库,它支持多种数据结构(如字符串、哈希、列表、集合、有序集合等),并提供了丰富的操作命令。Redis具有高性能、高可用性和可扩展性的特点,常用于缓存、消息队列、计数器等场景。 MySQL是一个开源的关系型数据库管理系统,它支持多用户、多线程和多表操作。MySQL具有良好的性能和稳定性,并且拥有丰富的功能和工具。 将Spring BootMyBatisRedisMySQL结合使用可以实现一个完整的Java应用程序。Spring Boot提供了便捷的配置和集成方式,可以轻松地将MyBatisMySQL集成到应用程序中。同时,通过使用Redis作为缓存,可以提高应用程序的性能和响应速度。 具体来说,可以使用Spring Boot的自动配置功能来集成MyBatisMySQL。通过配置数据源和MyBatis的Mapper接口,可以实现对数据库的访问和操作。同时,可以使用Redis作为缓存,提高数据的读取速度和响应性能。 总结起来,Spring Boot+MyBatis+Redis+MySQL的组合可以实现一个高性能、可扩展的Java应用程序,提供了方便的开发和部署方式,适用于各种类型的应用场景。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

触角云科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值