netty-netty与api进行合并,这样可以方便我们进行之后的人脸切换到二维码的操作

This commit is contained in:
limqhz
2020-07-14 01:00:16 +08:00
parent fb9fa0ed4f
commit 7f2e9ffdb3
48 changed files with 238 additions and 514 deletions

View File

@@ -1,31 +0,0 @@
package com;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 项目入口
*
* @author xingkong1221
* @since 2017-06-19
*/
@SpringBootApplication
@EnableScheduling
@EnableAsync
@MapperScan(value = {"com.sv.mapper"})
public class Application {
/**
* 启动项目
*
* @param args 启动参数
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,65 @@
package com;
import com.sv.netty.config.SpringContextHolder;
import com.sv.netty.netty.BootService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 项目入口
*/
@SpringBootApplication
@EnableScheduling
@EnableAsync
@MapperScan(value = {"com.sv.mapper"})
public class NettyWeiXinApplication {
/**
* 启动项目
*
* @param args 启动参数
*/
public static void main(String[] args) {
// SpringApplication.run(NettyWeiXinApplication.class, args);
ConfigurableApplicationContext context = SpringApplication.run(NettyWeiXinApplication.class, args);
SpringContextHolder.setContext(context);
//启动netty
BootService bootService = (BootService) context.getBean("bootService");
bootService.run();
}
/**
* 项目可能又使用计划任务的
*/
// private int corePoolSize = 5;//线程池维护线程的最少数量
//
// private int maxPoolSize = 15;//线程池维护线程的最大数量
//
// private int queueCapacity = 5; //缓存队列
//
// private int keepAlive = 60;//允许的空闲时间
// @Bean("scheduledExecutorService")
// public ScheduledExecutorService initScheduledExecutorService() {
// ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(400,
// new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
// return executorService;
// }
// @Bean
// public Executor executor() {
// ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// executor.setCorePoolSize(corePoolSize);
// executor.setMaxPoolSize(maxPoolSize);
// executor.setQueueCapacity(queueCapacity);
// executor.setThreadNamePrefix("mqExecutor-");
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
// executor.setKeepAliveSeconds(keepAlive);
// executor.initialize();
// return executor;
// }
}

View File

@@ -0,0 +1,40 @@
package com.sv.api.controller;
import com.sv.service.api.QRCodeService;
import com.ydd.framework.core.common.dto.ResponseDTO;
import com.ydd.framework.core.controller.BaseApiController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 二维码进场Controller
*/
@RestController
public class QREnterController extends BaseApiController {
private final Logger logger = LoggerFactory.getLogger(QREnterController.class);
@Resource
private QRCodeService qrCodeService;
/**
* 进场指令发布
*/
@RequestMapping(value = "/qr/enter", method = RequestMethod.GET)
public ResponseDTO enter(@RequestParam("deviceId") String deviceId) {
qrCodeService.enter(deviceId);
return ResponseDTO.ok();
}
/**
* 扫码结果页初始化
* 需要进场的场馆信息
*/
@RequestMapping(value = "/qr/init", method = RequestMethod.GET)
public ResponseDTO initEnter(@RequestParam("deviceId")String deviceId) {
return ResponseDTO.ok().addAttribute("init",qrCodeService.initEnter(deviceId));
}
}

View File

@@ -33,17 +33,21 @@ public class MemberCardUpdate {
public void execute(){
// 查询用户已过期的会员卡
List<MemberCard> cards = memberCardService.findAllCard();
// 查询出用户顺延的会员卡,正好是今日
List<MemberCard> memberCards = memberCardService.findAllMemberCard(cards);
//修改过期的会员卡为未激活
memberCardService.updateStatus(cards,0);
//修改用户默认会员卡
for (MemberCard memberCard : memberCards){
MemberCard lists = memberCardService.findMemberCardByVenue(memberCard.getMemberId(),memberCard.getVenueId(),memberCard.getVeneuType());
lists.setStatus(1);
memberCardService.update(lists);
if (cards != null && cards.size() > 0){
//修改过期的会员卡为未激活
memberCardService.updateStatus(cards,0);
// 查询出用户顺延的会员卡,正好是今日
List<MemberCard> memberCards = memberCardService.findAllMemberCard(cards);
//修改用户默认会员卡
if (memberCards!= null && memberCards.size() > 0){
for (MemberCard memberCard : memberCards){
MemberCard mem = memberCardService.findMemberCardByVenue(memberCard.getMemberId(),memberCard.getVenueId(),memberCard.getVeneuType());
if (mem != null){
mem.setStatus(1);
memberCardService.update(mem);
}
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.sv.netty.config;
import io.netty.channel.Channel;
import io.netty.util.internal.PlatformDependent;
import java.util.concurrent.ConcurrentMap;
public class ClientChannelCache {
/**
* 存储设备通道类型
* 此处存储客户端的channel 信息key 为 【deviceId + venueId】
* value: 1Tcp 2WebSocket
*/
public static final ConcurrentMap<String, Channel> channelTypes = PlatformDependent.newConcurrentHashMap();
/**
* 缓存通道
*/
public static void putChannelType(String clientId, Channel channel) {
channelTypes.put(clientId, channel);
}
/**
* 获取通道
*/
public static Channel getChannelType(String clientId) {
return channelTypes.get(clientId);
}
/**
* 判断通道是否存在
*/
public static boolean contains(String clientId) {
return channelTypes.containsKey(clientId);
}
/**
* 移除通道
*/
public static void removeChannelType(String clientId) {
channelTypes.remove(clientId);
}
}

View File

@@ -0,0 +1,29 @@
package com.sv.netty.config;
import com.sv.netty.netty.message.ChannelParam;
import io.netty.util.AttributeKey;
/**
* Created by ranfi on 2/22/16.
*/
public class Constant {
public static final String ACCESS_TOKEN_KEY = "access_token_key";
//消息队列发送消息到netty
public static final String sendToMachine = "netty-doll-machine";
//消息队列发送消息到netty
public static final String sendToService = "netty-doll-service";
//消息队列发送消息到netty
public static final String ROOM_SERVICE_TOPIC = "gt-room-service-topic";
/**
* session中存储终端发送的额外参数
*/
public static AttributeKey<ChannelParam> CHANNEL_PARAM = AttributeKey.newInstance("CHANNEL_PARAM");
public final static String DELIMITER_WORD = "$_$";
}

View File

@@ -0,0 +1,31 @@
package com.sv.netty.config;
public enum ErrorCode {
MEMBER_NOT_EXIST(10001,"用户校验失败"),
NO_ENOUGH_AMOUNT(10002,"余额不足");
private int code;
private String msg;
ErrorCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}

View File

@@ -0,0 +1,72 @@
/**
* Description:
* SpringContextHolder.java Create on 2013-3-8 上午10:19:20
* @author xiaopin
* @version 1.0
* Copyright (c) 2013 BMS,Inc. All Rights Reserved.
*/
package com.sv.netty.config;
import org.springframework.context.ApplicationContext;
/**
* 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext.
*
* @author calvin
*/
public class SpringContextHolder {
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext; // NOSONAR
}
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
public static void setContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext; // NOSONAR
}
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋于对象的类
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋对象的类 如果有多个Bean符合Class, 取出第一个
*/
public static <T> T getBean(Class<T> requiredType) {
checkApplicationContext();
return SpringContextHolder.applicationContext.getBean(requiredType);
}
/**
* 清除applicationContext静态变量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
if (SpringContextHolder.applicationContext == null) {
throw new IllegalStateException("applicaitonContext未注册请在applicationContext.xml中定义SpringContextHolder");
}
}
}

View File

@@ -0,0 +1,26 @@
package com.sv.netty.controller;
import com.sv.netty.dto.ResponseDTO;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MessageControler {
@RequestMapping("/message/send/t")
public ResponseDTO sendMessage1() {
// FaceRecognizeResponse recognizeResponse = new FaceRecognizeResponse();
// recognizeResponse.setPerson(new FaceRecognizeResponse.PersonBeanX());
// recognizeResponse.getPerson().setId(49);
// messageService.enter(deviceService.findById(1), recognizeResponse, venueService.findById(32));
return ResponseDTO.ok();
}
//
// @RequestMapping("/upload/image")
// public ResponseDTO sendMessage(){
// messageService.sendMessage(memberDto);
// return ResponseDTO.ok();
// }
}

View File

@@ -0,0 +1,47 @@
package com.sv.netty.controller;
import com.sv.netty.config.ClientChannelCache;
import com.sv.netty.config.ErrorCode;
import com.sv.netty.dto.ResponseDTO;
import io.netty.channel.Channel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class QRCodeControler {
/**
* 微信小程序扫码之后,通知对应的线程进入加载界面,然后随即开始判断逻辑
* @return
*/
@RequestMapping("/scanQRCode")
public ResponseDTO scanQRCode(@RequestParam String venueId,
@RequestParam String cardType,@RequestParam String memberId) {
// 1、获取到请求说明扫码成功预校验余额成功通知客户端(Android)进行加载状态。
// 2、进行预校验判断是否可以进行扫码通行 // TODO check余额方法
boolean checkResult = true;
if(checkResult){
// 如果是成功的 通知客户端(Android)进行开门
return ResponseDTO.ok();
}else {
return new ResponseDTO(ErrorCode.MEMBER_NOT_EXIST.getCode(),
ErrorCode.MEMBER_NOT_EXIST.getMsg(),System.currentTimeMillis());
}
}
/**
* 检验客户端读取能力
* @return
*/
@RequestMapping("/checkAlive")
public ResponseDTO checkAlive(@RequestParam String id) {
System.out.println(ClientChannelCache.channelTypes.size());
Channel channel = ClientChannelCache.getChannelType(id);
channel.writeAndFlush("check client alive ! ");
return ResponseDTO.ok();
}
}

View File

@@ -0,0 +1,148 @@
package com.sv.netty.dto;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 会员基本信息
* MemberDto.java
*
* @author peakren
* @date 2018/12/20 8:39 PM
*/
public class MemberDto implements Serializable {
/**
* 头像
*/
private String avatar;
/**
* 姓名
*/
private String name;
/**
* 手机号码
*/
private String mobile;
/**
* 余额
*/
private BigDecimal amount;
/**
* 是否第一次进入
*/
private boolean first = false;
/**
* 场地价格
*/
private BigDecimal placePrice;
/**
* 场地名称
*/
private String placeName;
/**
* 会员卡名称
*/
private String cardName;
private String message;
/**
* 1成功进场 0不允许进场
*/
private int code;
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getPlaceName() {
return placeName;
}
public void setPlaceName(String placeName) {
this.placeName = placeName;
}
public String getCardName() {
return cardName;
}
public void setCardName(String cardName) {
this.cardName = cardName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public BigDecimal getPlacePrice() {
return placePrice;
}
public void setPlacePrice(BigDecimal placePrice) {
this.placePrice = placePrice;
}
public boolean isFirst() {
return first;
}
public void setFirst(boolean first) {
this.first = first;
}
}

View File

@@ -0,0 +1,60 @@
package com.sv.netty.dto;
import org.springframework.util.Assert;
import java.util.LinkedHashMap;
public class ResponseDTO extends LinkedHashMap<String, Object> {
public static final String ERR_CODE = "err_code";
public static final String ERR_MSG = "err_msg";
public static final String TIMESTAMP = "timestamp";
private static final long serialVersionUID = 8410965932046471023L;
public ResponseDTO() {
this(0, "OK");
}
public ResponseDTO(Integer errCode, String errMsg) {
this(errCode, errMsg, System.currentTimeMillis() / 1000L);
}
public ResponseDTO(Integer errCode, String errMsg, Long timestamp) {
this.addAttribute("err_code", errCode);
this.addAttribute("err_msg", errMsg);
this.addAttribute("timestamp", timestamp);
}
public ResponseDTO(Integer errCode, String errMsg, String attrName, Object attrValue) {
this(errCode, errMsg);
this.addAttribute(attrName, attrValue);
}
public static ResponseDTO ok() {
return new ResponseDTO();
}
public static ResponseDTO ok(String msg) {
ResponseDTO ret = ok();
ret.setErrorMsg(msg);
return ret;
}
public ResponseDTO addAttribute(String attrName, Object attrValue) {
Assert.notNull(attrName, "属性名称不能为空");
this.put(attrName, attrValue);
return this;
}
public void setErrorCode(Integer errCode) {
this.addAttribute("err_code", errCode);
}
public void setErrorMsg(String errMsg) {
this.addAttribute("err_msg", errMsg);
}
}

View File

@@ -0,0 +1,134 @@
package com.sv.netty.netty;
import com.google.common.collect.Maps;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* netty启动服务
*
* @Author peakren
* @Date 03/05/2017 6:18 PM
*/
@Component
public class BootService {
private Logger logger = LoggerFactory.getLogger(BootService.class);
private volatile EventLoopGroup workerGroup;
private volatile EventLoopGroup bossGroup;
private volatile ServerBootstrap bootstrap;
private ChannelFuture serverChannelFuture;
/**
* 启动netty的控制线程
*/
private Executor messageExecutor;
/**
* 下面几个是由spring注入
*/
@Value("${netty.port}")
private int port;
@Value("${so.keepalive}")
private Boolean keepalive;
@Value("${so.backlog}")
private int backlog;
@Value("${tcp_nodelay}")
private boolean nodelay;
@Value("${so.reuseaddr}")
private boolean reuseaddr;
@Value("${boss.thread.count}")
private int bossCount;
@Value("${worker.thread.count}")
private int workerCount;
private Map<ChannelOption, Object> channelOptions;
private final ServerProtocolInitializer serverProtocolInitializer;
/**
* 初始化netty启动配置
*/
public void init() {
// bossGroup 只处理连接请求真正的客户端业务处理是由workerGroup 处理的 都是无效循环的
workerGroup = new NioEventLoopGroup();
bossGroup = new NioEventLoopGroup();
channelOptions = Maps.newHashMap();
try {
// channelOptions.put(ChannelOption.SO_KEEPALIVE,keepalive);
channelOptions.put(ChannelOption.SO_BACKLOG,backlog);
// channelOptions.put(ChannelOption.TCP_NODELAY,TCP_NODELAY);
channelOptions.put(ChannelOption.SO_REUSEADDR,reuseaddr);
bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // 设置服务器的通道是NIOServerSocketChannel
.childHandler(serverProtocolInitializer);
for (Map.Entry<ChannelOption, Object> entry : channelOptions.entrySet()) {
bootstrap.option(entry.getKey(), entry.getValue());
}
bootstrap.childOption(ChannelOption.TCP_NODELAY,nodelay);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,keepalive);
// 绑定一个端口并且同步 启动服务器
serverChannelFuture = bootstrap.bind(new InetSocketAddress(port)).sync();
// serverChannelFuture的用处
serverChannelFuture.addListener(f -> {
if (f.isSuccess()){
logger.info("成功bind端口:" + port);
}
});
// 对关闭通道进行监听 (异步模型)
serverChannelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.error("启动NETTY TCP异常:", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/**
* 开启单独的线程运行netty服务,避免和spring mvc冲突
*/
public void run() {
// messageExecutor = Executors.newFixedThreadPool(1);
messageExecutor = Executors.newSingleThreadExecutor();
messageExecutor.execute(() -> init());
}
public void destroy() throws Exception {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
serverChannelFuture.channel().closeFuture().sync();
}
public void setPort(int port) {
this.port = port;
}
public void setChannelOptions(Map<ChannelOption, Object> channelOptions) {
this.channelOptions = channelOptions;
}
public BootService(ServerProtocolInitializer serverProtocolInitializer) {
this.serverProtocolInitializer = serverProtocolInitializer;
}
}

View File

@@ -0,0 +1,34 @@
package com.sv.netty.netty;
import com.sv.netty.config.Constant;
import com.sv.netty.utils.JsonUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
/**
* 自定义消息编码器
*
* @Author peakren
* @Date 07/05/2017 10:43 PM
*/
public class MessageEncoder extends MessageToByteEncoder {
private static Logger logger = LoggerFactory.getLogger(MessageEncoder.class);
Charset charset = Charset.forName("UTF-8");
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
String message = JsonUtils.encode(msg);
logger.info("send message content:" + msg);
message = message + Constant.DELIMITER_WORD;
byte[] content = message.getBytes(charset.name());
out.writeBytes(content); //发送消息内容
}
}

View File

@@ -0,0 +1,128 @@
package com.sv.netty.netty;
import com.sv.netty.config.Constant;
import com.sv.netty.config.SpringContextHolder;
import com.sv.netty.netty.message.ChannelParam;
import com.sv.netty.netty.message.HeartBeat;
import com.sv.netty.netty.service.MessageService;
import com.sv.netty.utils.JsonUtils;
import io.netty.channel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.Set;
/**
* 服务器接受TCP协议数据处理器
*
* @author ranfi
*/
@ChannelHandler.Sharable
public class ServerHandler extends SimpleChannelInboundHandler<String> {
private static Logger logger = LoggerFactory.getLogger(ServerHandler.class);
private MessageService messageService;
public ServerHandler() {
messageService = SpringContextHolder.getBean("messageService");
}
/**
* 通道就绪事件
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
/**
* 客户端只会报心跳
* @param ctx
* @param msg
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
String clientIp = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getClientIp();
try {
HeartBeat hb = JsonUtils.decode(msg,HeartBeat.class);
logger.info("客户端" + hb.getDeviceName() + "&" + hb.getVenueId() + "" + clientIp + "】上报心跳...");
ctx.channel().attr(Constant.CHANNEL_PARAM).get().setVenueId(hb.getVenueId());
ctx.channel().attr(Constant.CHANNEL_PARAM).get().setDeviceName(hb.getDeviceName());
messageService.online(clientIp,ctx.channel(), hb);
} catch (Exception e) {
logger.error("[" + clientIp + "] host unknown error",e);
}
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
String clientIP = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
logger.info("There is a client Registered. ip:" + clientIP);
ctx.channel().attr(Constant.CHANNEL_PARAM).set(new ChannelParam(clientIP));
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
super.channelUnregistered(ctx);
}
/**
* 当通道失效就会触发
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
String clientIP = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
logger.error("Client ip [" + clientIP + "] has inactive");
Integer venueId = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getVenueId();
String deviceName = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getDeviceName();
messageService.Offline(deviceName,venueId);
}
/**
* 处理异常、一般需要关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error("ServerHandler exceptionCaught",cause);
Channel channel = ctx.channel();
Integer venueId = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getVenueId();
String deviceName = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getDeviceName();
messageService.Offline(deviceName,venueId);
if(channel.isActive()) {
// 错误产生,关闭连接
ctx.close();
}
}
/**
* IdleStateHandler 如果几秒之后没有读操作,那么就会触发这个方法
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
// 服务器统计现在有的客户端数量, 客户端这个方法用来发心跳
// if (evt instanceof IdleStateEvent){
// IdleState state = ((IdleStateEvent) evt).state();
// if (state == IdleState.READER_IDLE){
// logger.info("IdleStateEvent READER_IDLE 超时");
// Integer venueId = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getVenueId();
// String deviceName = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getDeviceName();
// messageService.Offline(deviceName,venueId);
// ctx.channel().close();
// }
// }
Set<String> conns = messageService.countConnection();
logger.info("count connected device ! the count is " + conns.size() + " and they are + [" + conns.toString() + "]" );
}
}

View File

@@ -0,0 +1,42 @@
package com.sv.netty.netty;
import com.sv.netty.config.Constant;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.stereotype.Component;
/**
* 服务器端协议初始化适配器
*
* @Author peakren
* @Date 09/05/2017 5:19 PM
*/
@Component
public class ServerProtocolInitializer extends ChannelInitializer<SocketChannel> {
private final int IDLE_TIME = 120; //连接检测空闲时间
// private final int READ_TIMEOUT = 30; //读超时时间
@Override
protected void initChannel(SocketChannel ch){
ChannelPipeline pipeline = ch.pipeline();
// 超时设置(如果客户端频繁有人操作,则不会报心跳,这样就会触发超时,关闭链接)
// pipeline.addLast(new ReadTimeoutHandler(READ_TIMEOUT));
// 通过指定的长度来标识整包的信息,这样就可以自动的处理粘包和半包的问题
pipeline.addLast(new DelimiterBasedFrameDecoder(2048,
Unpooled.wrappedBuffer(Constant.DELIMITER_WORD.getBytes())));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new MessageEncoder());
// 心跳检测机制通过调用触发下一个handler userEventTriggered 方法
pipeline.addLast(new IdleStateHandler(IDLE_TIME, IDLE_TIME,IDLE_TIME));
pipeline.addLast(new ServerHandler());
}
}

View File

@@ -0,0 +1,53 @@
package com.sv.netty.netty.message;
/**
* 会话中存储的客户端对象
*
* @author peakren
* @since 16/05/2017 11:09 PM
*/
public class ChannelParam {
/**
* 设备ip
*/
private String clientIp;
/**
* 设备编号
*/
private String deviceName;
/**
* 设备所在场馆
*/
private Integer venueId;
public ChannelParam(String clientIP) {
this.clientIp = clientIP;
}
public String getClientIp() {
return clientIp;
}
public void setClientIp(String clientIp) {
this.clientIp = clientIp;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public Integer getVenueId() {
return venueId;
}
public void setVenueId(Integer venueId) {
this.venueId = venueId;
}
}

View File

@@ -0,0 +1,36 @@
package com.sv.netty.netty.message;
import com.google.gson.annotations.Expose;
import java.io.Serializable;
/**
* 客户端心跳数据包
* HeartBeat.java
*
* @author peakren
* @date 07/12/2017 10:23 PM
*/
public class HeartBeat implements Serializable {
private Integer venueId; //场馆号
private String deviceName; //设备号
public Integer getVenueId() {
return venueId;
}
public void setVenueId(Integer venueId) {
this.venueId = venueId;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
}

View File

@@ -0,0 +1,39 @@
package com.sv.netty.netty.message;
/**
* 请求门禁的灯光管理
*/
public class Result {
/**
* Code : 0
* Message : success
* Data : false
*/
private int Code;
private String Message;
private boolean Data;
public int getCode() {
return Code;
}
public void setCode(int Code) {
this.Code = Code;
}
public String getMessage() {
return Message;
}
public void setMessage(String Message) {
this.Message = Message;
}
public boolean isData() {
return Data;
}
public void setData(boolean Data) {
this.Data = Data;
}
}

View File

@@ -0,0 +1,37 @@
package com.sv.netty.netty.service;
import com.sv.netty.netty.message.HeartBeat;
import io.netty.channel.Channel;
import java.util.Set;
/**
* 消息实现接口
*
* @author peakren
* @since 03/05/2017 10:04 PM
*/
public interface MessageService {
/**
* 接受设备心跳,设备上线
*
* @param channel
* @param heartBeat
*/
void online(String clientId, Channel channel, HeartBeat heartBeat);
/**
* 设备下线
*
* @param deviceName
* @param venueId
*/
void Offline(String deviceName, Integer venueId);
/**
* 统计目前的链接数
* @return
*/
Set<String> countConnection();
}

View File

@@ -0,0 +1,275 @@
package com.sv.netty.netty.service.impl;
import com.sv.dto.EnterResult;
import com.sv.entity.Device;
import com.sv.entity.Member;
import com.sv.entity.MemberEnterVenueLog;
import com.sv.entity.Venue;
import com.sv.entity.face.FaceRecognizeResponse;
import com.sv.netty.config.ClientChannelCache;
import com.sv.netty.dto.MemberDto;
import com.sv.netty.netty.message.HeartBeat;
import com.sv.netty.netty.message.Result;
import com.sv.netty.netty.service.MessageService;
import com.sv.netty.utils.JsonMapper;
import com.sv.service.api.MemberEnterVenueLogService;
import com.sv.service.api.MemberService;
import com.sv.service.api.VenueService;
import com.sv.service.oms.ConfigService;
import com.sv.service.oms.DeviceService;
import com.ydd.oms.entity.sys.Config;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 消息实现类
*
* @Author peakren
* @Date 08/05/2017 10:43 PM
*/
@Service("messageService")
public class TcpMessageHandlerAdapter implements MessageService {
private Logger logger = LoggerFactory.getLogger(TcpMessageHandlerAdapter.class);
@Resource
private MemberService memberService;
@Resource
private VenueService venueService;
@Resource
private DeviceService deviceService;
@Resource
private ConfigService configService;
// @Resource(name = "scheduledExecutorService")
// private ScheduledExecutorService scheduledExecutorService;
@Resource
private MemberEnterVenueLogService memberEnterVenueLogService;
@Resource
private RestTemplate restTemplate;
public void sendMessage(MemberDto memberDto, Integer deviceId) {
// MessageDto messageDto = new MessageDto();
// messageDto.setCmdId(Cmd.FACEID.id);
// messageDto.setResult(memberDto);
// logger.info("发送消息" + channels.size() + "deviceId:" + deviceId);
// if (deviceId == 1) {
// if (enter != null) {
// enter.writeAndFlush(messageDto);
// }
// } else {
// out.writeAndFlush(messageDto);
// }
}
/**
* 处理心跳信息,存储心跳信息
* @param clientId
* @param channel
* @param heartBeat
*/
@Override
public void online(String clientId, Channel channel, HeartBeat heartBeat) {
// 处理心跳信息
if (!ClientChannelCache.contains(heartBeat.getDeviceName()+heartBeat.getVenueId())){
// 此处存储客户端的channel 信息key 为 deviceId + venueId
Venue thisVenue = venueService.findById(heartBeat.getVenueId());
if (thisVenue == null ){
logger.error("this client choose venue Error! venueId == " + heartBeat.getVenueId());
} else {
ClientChannelCache.putChannelType(heartBeat.getDeviceName()+heartBeat.getVenueId(),channel);
deviceService.online(heartBeat.getDeviceName(),heartBeat.getVenueId(),thisVenue.getType(),clientId);
}
}
}
/**
* 设备下线
* @param deviceName
* @param venueId
*/
@Override
public void Offline(String deviceName, Integer venueId) {
if (deviceName != null && venueId != null){
ClientChannelCache.removeChannelType(deviceName+ venueId);
deviceService.offline(deviceName,venueId);
}
}
/**
* 统计目前有多少链接数
* @return
*/
@Override
public Set<String> countConnection() {
return ClientChannelCache.channelTypes.keySet();
}
public void enter(Device device, FaceRecognizeResponse response, Venue venue) {
try {
int code = 1;
Member member = memberService.findByFaceId(response.getPerson().getId());
if (member != null) {
//最后进场记录
MemberEnterVenueLog enterVenueLog = memberEnterVenueLogService.findMemberLastLogNoType(member.getId(), device.getVenueId());
if (enterVenueLog != null) {
//有记录 查看 最后一次是否是出场
if (enterVenueLog.getType() == 1) {
//是出场 不用限制
} else {
//是进场
Date date = new Date();
//本日连续进场次数
int continuityEnterCount = memberEnterVenueLogService.countEnterByDate(member.getId(), device.getVenueId(), date);
logger.info("连续进场次数:" + continuityEnterCount);
//距离上次入场差多少秒
int differenceSeconds = (int) (date.getTime() - enterVenueLog.getCreatedTime().getTime()) / (1000);
if (continuityEnterCount <= 0) {
//没有连续进场 间隔5分钟
Config config = configService.findById(1);
if (differenceSeconds <= (Integer.parseInt(config.getValue()) * 60)) {
//小于5分钟 不允许入场
code = -1;
}
} else if (continuityEnterCount == 1) {
//有过一次连续进场 间隔20分钟
Config config = configService.findById(2);
if (differenceSeconds <= (Integer.parseInt(config.getValue()) * 60)) {
//小于20分钟 不允许入场
code = -1;
}
} else {
//超过两次连续入场 入场失败
code = -1;
}
}
}
MemberDto memberMessageDto = new MemberDto();
memberMessageDto.setName(member.getNickname());
memberMessageDto.setAvatar(member.getAvatar());
memberMessageDto.setMobile(member.getMobile());
memberMessageDto.setPlaceName("");
member = memberService.findByFaceId(response.getPerson().getId());
memberMessageDto.setAmount(member.getMoney());
if (code > 0) {
EnterResult result = venueService.enterVenue(response.getPerson().getId(), device.getId());
if (result.getStatus() >= 0) {
memberMessageDto.setCode(1);
if (result.getStatus() == 1) {
memberMessageDto.setCardName("会员卡");
}
if (result.getStatus() == 2) {
memberMessageDto.setPlacePrice(result.getMoney());
memberMessageDto.setFirst(true);
logger.info(member.getId() + "入场成功:" + member.getMoney().toString());
member = memberService.findByFaceId(response.getPerson().getId());
memberMessageDto.setAmount(member.getMoney());
//5秒后开门
// scheduledExecutorService.schedule(new Runnable() {
// @Override
// public void run() {
//// MessageDto messageDto = new MessageDto();
//// messageDto.setCmdId(Cmd.OPEN_DOOR.id);
//// messageDto.setDoor(1);
//// sendOpenMessage(messageDto, 1);
// }
// }, 5, TimeUnit.SECONDS);
} else {
//开门
// MessageDto messageDto = new MessageDto();
// messageDto.setCmdId(Cmd.OPEN_DOOR.id);
// messageDto.setDoor(1);
// sendOpenMessage(messageDto, 1);
logger.info(member.getId() + "入场成功:" + member.getMoney().toString());
}
memberMessageDto.setMessage("门禁已开,请入门");
venueService.addNumber(venue.getId(), 1, member.getId());
venue = venueService.findById(venue.getId());
sendNumberChange(venue.getNumber());
} else {
logger.info(member.getId() + "入场失败:" + member.getMoney().toString());
memberMessageDto.setCode(-1);
memberMessageDto.setMessage(member.getName() + "您好,您的余额不足,请扫描门禁上张贴的小程序二维码充值");
}
} else {
logger.info(member.getId() + "入场失败:连续入场");
memberMessageDto.setCode(0);
Config config = configService.findById(1);
memberMessageDto.setMessage("无出门记录连续入场,请" + config.getValue() + "分钟之后再试");
}
sendMessage(memberMessageDto, device.getId());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void out(Device device, FaceRecognizeResponse response, Venue venue) {
Member member = memberService.findByFaceId(response.getPerson().getId());
if (member != null) {
//出场 不用判断直接出
MemberEnterVenueLog memberEnterVenueLog = new MemberEnterVenueLog();
memberEnterVenueLog.setOrderSn("");
memberEnterVenueLog.setType(1);
memberEnterVenueLog.setMemberId(member.getId());
memberEnterVenueLog.setVeneuType(device.getVenueType());
memberEnterVenueLog.setVenueId(device.getVenueId());
memberEnterVenueLog.setPlatformId(member.getPlatformId());
memberEnterVenueLogService.save(memberEnterVenueLog);
logger.info("用户" + member.getNickname() + "出场");
MemberDto memberMessageDto = new MemberDto();
memberMessageDto.setAmount(member.getMoney());
memberMessageDto.setName(member.getNickname());
memberMessageDto.setAvatar(member.getAvatar());
memberMessageDto.setMobile(member.getMobile());
memberMessageDto.setPlaceName("");
memberMessageDto.setMessage("欢迎下次再来" + venue.getName());
memberMessageDto.setCode(2);
// MessageDto messageDto = new MessageDto();
// messageDto.setCmdId(Cmd.OPEN_DOOR.id);
// messageDto.setDoor(2);
// sendOpenMessage(messageDto, device.getId());
sendMessage(memberMessageDto, device.getId());
venueService.addNumber(venue.getId(), -1, member.getId());
venue = venueService.findById(venue.getId());
sendNumberChange(venue.getNumber());
}
}
/**
* 控制硬件,门禁灯光控制
* @param number
*/
public void sendNumberChange(Integer number) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("LingtekID", "5d14229fcb1f5c1a9046f429");
param.add("Number", number.toString());
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(param, headers);
ResponseEntity<Result> result = restTemplate.exchange("http://lingtek.jalasmart.com/api/v1/lingtek/number", HttpMethod.PUT, request, Result.class);
logger.info("灯光结果" + JsonMapper.nonDefaultMapper().toJson(result));
}
}

View File

@@ -0,0 +1,76 @@
package com.sv.netty.utils;
import java.nio.charset.Charset;
/**
* USER: douya
* DATE: 2017-08-28
*/
public class CommonUtils {
public static byte[] intToBytes2(int n){
byte[] b = new byte[4];
for(int i = 0;i < 4;i++)
{
b[i]=(byte)(n>>(24-i*8));
}
return b;
}
/**
* byte数组转换成16进制字符串
* @param src
* @return
*/
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* byte数组转换成16进制字符数组
* @param src
* @return
*/
public static String[] bytesToHexStrings(byte[] src){
if (src == null || src.length <= 0) {
return null;
}
String[] str = new String[src.length];
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
str[i] = "0";
}
str[i] = hv;
}
return str;
}
public static void main(String[] arg){
String message = "{\"cmd\":\"text_message\",\"content\":\"开心开心\",\"member_count\":0,\"system\":0,\"vmc_no\":\"322af403825a\"";
byte[] bodys = message.getBytes(Charset.forName("UTF-8"));
String magic = bytesToHexString("doll".getBytes(Charset.forName("UTF-8")));
String length = bytesToHexString(intToBytes2(bodys.length+8));
System.out.println(magic+length+bytesToHexString(bodys));
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2005-2009 springside.org.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
*
* $Id: EncodeUtils.java 984 2010-03-21 13:02:44Z calvinxiu $
*/
package com.sv.netty.utils;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
/**
* 各种格式的编码加码工具类.
*
* 集成Commons-Codec,Commons-Lang及JDK提供的编解码方法.
*
* @author ranfi
*/
public class EncodeUtils {
private static final String DEFAULT_URL_ENCODING = "UTF-8";
private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
/**
* Hex编码.
*/
public static String encodeHex(byte[] input) {
return Hex.encodeHexString(input);
}
/**
* Hex解码.
*/
public static byte[] decodeHex(String input) {
try {
return Hex.decodeHex(input.toCharArray());
} catch (DecoderException e) {
return null;
}
}
/**
* Base64编码.
*/
public static String encodeBase64(byte[] input) {
return Base64.encodeBase64String(input);
}
/**
* Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).
*/
public static String encodeUrlSafeBase64(byte[] input) {
return Base64.encodeBase64URLSafeString(input);
}
/**
* Base64解码.
*/
public static byte[] decodeBase64(String input) {
return Base64.decodeBase64(input);
}
/**
* Base62编码。
*/
public static String encodeBase62(byte[] input) {
char[] chars = new char[input.length];
for (int i = 0; i < input.length; i++) {
chars[i] = BASE62[(input[i] & 0xFF) % BASE62.length];
}
return new String(chars);
}
}

View File

@@ -0,0 +1,204 @@
/**
* Copyright (c) 2005-2012 springside.org.cn
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
*/
package com.sv.netty.utils;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
/**
* 简单封装Jackson实现JSON String<->Java Object的Mapper.
* <p>
* 封装不同的输出风格, 使用不同的builder函数创建实例.
*
* @author calvin
*/
public class JsonMapper {
private static Logger logger = LoggerFactory.getLogger(JsonMapper.class);
private final ObjectMapper mapper;
public JsonMapper() {
this(null);
}
public JsonMapper(Include include) {
mapper = new ObjectMapper();
// 设置输出时包含属性的风格
if (include != null) {
mapper.setSerializationInclusion(include);
}
// 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
/**
* 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper,建议在外部接口中使用.
*/
public static JsonMapper nonEmptyMapper() {
return new JsonMapper(Include.NON_EMPTY);
}
/**
* 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式,建议在内部接口中使用。
*/
public static JsonMapper nonDefaultMapper() {
return new JsonMapper(Include.NON_DEFAULT);
}
public static JsonMapper nonNullMapper() {
return new JsonMapper(Include.NON_NULL);
}
/**
* Object可以是POJO也可以是Collection或数组。 如果对象为Null, 返回"null". 如果集合为空集合, 返回"[]".
*/
public String toJson(Object object) {
try {
return mapper.writeValueAsString(object);
} catch (IOException e) {
logger.warn("write to json string error:" + object, e);
return null;
}
}
/**
* 反序列化POJO或简单Collection如List<String>.
* <p>
* 如果JSON字符串为Null或"null"字符串, 返回Null. 如果JSON字符串为"[]", 返回空集合.
* <p>
* 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String, JavaType)
*
* @see #fromJson(String, JavaType)
*/
public <T> T fromJson(String jsonString, Class<T> clazz) {
if (StringUtils.isEmpty(jsonString)) {
return null;
}
try {
return mapper.readValue(jsonString, clazz);
} catch (IOException e) {
logger.warn("parse json string error:" + jsonString, e);
return null;
}
}
/**
* 反序列化复杂Collection如List<Bean>,
* 先使用createCollectionType()或contructMapType()构造类型, 然后调用本函数.
*
* @see #createCollectionType(Class, Class...)
*/
public <T> T fromJson(String jsonString, JavaType javaType) {
if (StringUtils.isEmpty(jsonString)) {
return null;
}
try {
return (T) mapper.readValue(jsonString, javaType);
} catch (IOException e) {
logger.warn("parse json string error:" + jsonString, e);
return null;
}
}
/**
* 构造Collection类型.
*/
public JavaType contructCollectionType(Class<? extends Collection> collectionClass, Class<?> elementClass) {
return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass);
}
/**
* 构造Map类型.
*/
public JavaType contructMapType(Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {
return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);
}
/**
* map 转 bean
*
* @param map
* @param beanClass
* @return
*/
public <T> T convertMapToBean(Map<String, ?> map, Class<T> beanClass) {
try {
return mapper.convertValue(map, beanClass);
} catch (Exception e) {
logger.warn("convertMapToBean error! ", e);
}
return null;
}
/**
* map 转 bean
*
* @param bean
* @param objectClass
* @return
*/
public <T> T convertBeanToOther(Object bean, Class<T> objectClass) {
try {
return mapper.convertValue(bean, objectClass);
} catch (Exception e) {
logger.warn("convertMapToBean error! ", e);
}
return null;
}
/**
* 当JSON里只含有Bean的部分屬性時更新一個已存在Bean只覆蓋該部分的屬性.
*/
public void update(String jsonString, Object object) {
try {
mapper.readerForUpdating(object).readValue(jsonString);
} catch (JsonProcessingException e) {
logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e);
} catch (IOException e) {
logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e);
}
}
/**
* 輸出JSONP格式數據.
*/
public String toJsonP(String functionName, Object object) {
return toJson(new JSONPObject(functionName, object));
}
/**
* 設定是否使用Enum的toString函數來讀寫Enum, 為False時時使用Enum的name()函數來讀寫Enum, 默認為False.
* 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用.
*/
public void enableEnumUseToString() {
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
}
/**
* 取出Mapper做进一步的设置或使用其他序列化API.
*/
public ObjectMapper getMapper() {
return mapper;
}
}

View File

@@ -0,0 +1,94 @@
package com.sv.netty.utils;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* USER: douya
* DATE: 2017-11-01
*/
public class JsonUtils {
/**
* Logger for this class
*/
private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
private final static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private JsonUtils() {
}
public static String encode(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonGenerationException e) {
logger.error("encode(Object)", e); //$NON-NLS-1$
} catch (JsonMappingException e) {
logger.error("encode(Object)", e); //$NON-NLS-1$
} catch (IOException e) {
logger.error("encode(Object)", e); //$NON-NLS-1$
}
return null;
}
/**
* 将json string反序列化成对象
*
* @param json
* @param valueType
* @return
*/
public static <T> T decode(String json, Class<T> valueType) {
try {
return objectMapper.readValue(json, valueType);
} catch (JsonParseException e) {
logger.error("decode(String, Class<T>)", e);
} catch (JsonMappingException e) {
logger.error("decode(String, Class<T>)", e);
} catch (IOException e) {
logger.error("decode(String, Class<T>)", e);
}
return null;
}
/**
* 将json array反序列化为对象
*
* @param json
* @param typeReference
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T decode(String json, TypeReference<T> typeReference) {
try {
return (T) objectMapper.readValue(json, typeReference);
} catch (JsonParseException e) {
logger.error("decode(String, JsonTypeReference<T>)", e);
} catch (JsonMappingException e) {
logger.error("decode(String, JsonTypeReference<T>)", e);
} catch (IOException e) {
logger.error("decode(String, JsonTypeReference<T>)", e);
}
return null;
}
}