由于之前用Php+WorkMan开发的新能源汽车充电管理系统,(有兴趣的可以查看这里),公司说这技术淘汰了,需要用java重新架构一套,适应市场需求。
毕竟入职时候,说自己php、java都能撸出冒火星,这个任务无可厚非放在我的身上了。
经过小组讨论,决定架构用:SpringBoot+Mybatis+Mysql+Netty+Redis+Uni-app
有什么不明白地方,大家一起交流:150-5606-9927
技术解决方案
硬件跟服务器通讯
硬件厂商提供的充电桩和充电运营管理系统之间的通信接口采用基于 TCP/IP Socket 的通信方式实现,按照长连接工作模式。
我们使用Netty跟硬件桩子(TCP/IP)通讯
应用层报文桢格式
起始标志 | 数据长度 | 序列号域 | 加密标志 | 桢类型标志 | 消息体 | 桢校验域 |
1字节 | 1字节 | 2字节 | 1字节 | 1字节 | N字节 | 2字节 |
数据结构定义说明:
- 起始标识符代表一帧数据的开始,固定为0x68。
- 数据域字节数,数据域长度不超过200字节。不加密时为原数据长度,加密时,为加密后数据长度。其值为“序列号域+加密标志+桢类型标志+消息体”字节数之和。
- 序列号域即为数据包的发送顺序号,从 0 开始顺序增加,如是应答数据包,则与询问数据包序号保持一致,当桩与平台网络断开重新建立连接或者溢出后归0。
- 加密标志只针对消息体(数据单元)。0x00:不加密,0x01:3DES
- 桢类型标志定义了上下行数据桢。
桢校验域:从序列号域到数据域的 CRC 校验,校验多项式为 0x180D,低字节在前,高字节在后,
数据格式包括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屏幕上,不仅使业务人员能够从复杂的业务数据中快速、直接地找到重要数据,而且能对决策者起到辅助作用。