├── docs ├── images │ └── 代理扭转流程.png └── init-db.sql ├── nat-plus-server ├── src │ └── main │ │ ├── resources │ │ ├── config.properties │ │ ├── application.yml │ │ └── log4j2.xml │ │ └── java │ │ └── org │ │ └── mysise │ │ └── natplus │ │ └── server │ │ ├── mapper │ │ ├── TunnelMapper.java │ │ └── SysUserMapper.java │ │ ├── common │ │ ├── model │ │ │ ├── TunnelRequest.java │ │ │ ├── TunnelSearch.java │ │ │ └── TunnelResponse.java │ │ ├── interceptor │ │ │ ├── InterceptorConfig.java │ │ │ └── AuthenticationInterceptor.java │ │ ├── SpringUtils.java │ │ ├── GlobalExceptionHandler.java │ │ ├── JWTUtils.java │ │ └── CodeGenerator.java │ │ ├── entity │ │ ├── SysUser.java │ │ ├── BaseEntity.java │ │ └── Tunnel.java │ │ ├── controller │ │ ├── SysUserController.java │ │ ├── LoginController.java │ │ └── TunnelController.java │ │ ├── service │ │ ├── ISysUserService.java │ │ ├── impl │ │ │ ├── SysUserServiceImpl.java │ │ │ └── TunnelServiceImpl.java │ │ └── ITunnelService.java │ │ ├── core │ │ ├── net │ │ │ └── TcpServer.java │ │ └── handler │ │ │ ├── ServerHandler.java │ │ │ └── ProxyHandler.java │ │ └── ServerApplication.java └── pom.xml ├── nat-plus-common ├── src │ └── main │ │ └── java │ │ └── org │ │ └── mysise │ │ └── natplus │ │ └── common │ │ ├── attribute │ │ └── Attributes.java │ │ ├── serializer │ │ ├── SerializerAlogrithm.java │ │ ├── SerializerDemo.java │ │ ├── Serializer.java │ │ └── impl │ │ │ └── JSONSerializer.java │ │ ├── session │ │ └── Session.java │ │ ├── exception │ │ ├── BaseException.java │ │ ├── Constant.java │ │ ├── CommonCode.java │ │ ├── SqlException.java │ │ └── BizException.java │ │ ├── protocol │ │ ├── MessagePacket.java │ │ ├── Command.java │ │ ├── ConnectPacket.java │ │ ├── DataPacket.java │ │ ├── Packet.java │ │ └── RegisterPacket.java │ │ ├── codec │ │ ├── PacketDecoder.java │ │ ├── PacketEncoder.java │ │ ├── Spliter.java │ │ └── PacketCode.java │ │ ├── vo │ │ └── Result.java │ │ └── utils │ │ ├── SessionUtil.java │ │ └── DtoUtils.java └── pom.xml ├── README.md ├── nat-plus-client ├── src │ └── main │ │ └── java │ │ └── org │ │ └── mysise │ │ └── natplus │ │ └── client │ │ ├── handler │ │ ├── LocalProxyHandler.java │ │ └── ClientHandler.java │ │ ├── net │ │ └── TcpConnection.java │ │ └── ClientApplication.java └── pom.xml └── pom.xml /docs/images/代理扭转流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mysise/nat-plus/HEAD/docs/images/代理扭转流程.png -------------------------------------------------------------------------------- /nat-plus-server/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | #����Ա�˺����� 2 | admin.username=admin 3 | admin.password=123456 4 | 5 | #jwt 6 | jwt.key=123456444 7 | jwt.ttl=1800 -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/attribute/Attributes.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.attribute; 2 | 3 | import io.netty.util.AttributeKey; 4 | import org.mysise.natplus.common.session.Session; 5 | 6 | public interface Attributes { 7 | AttributeKey SESSION = AttributeKey.newInstance("session"); 8 | } 9 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/serializer/SerializerAlogrithm.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.serializer; 2 | 3 | /** 4 | *

5 | * 序列化算法 6 | *

7 | * 8 | * @author fanwenjie 9 | * @since 2020/3/1 10:48 10 | */ 11 | public interface SerializerAlogrithm { 12 | /** 13 | * json 序列化 14 | */ 15 | byte JSON = 1; 16 | } 17 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/serializer/SerializerDemo.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.serializer; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class SerializerDemo { 8 | private int age; 9 | 10 | @JSONField(deserialize = false, serialize = false) 11 | private String name; 12 | 13 | private byte[] bytes; 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **1.0.0** 2 | 3 | 1. 实现基于netty的内网穿透功能,客户端,服务端实现 4 | 2. 实现基于用户的自动申请管理平台功能(未实现) 5 | 6 | (ps:):目前客户端使用,必须先安装JDK1.8,后续会做成可执行文件 7 | 8 | 隧道建立成功之后,其实已经不需要通过服务端转发数据,如果隧道断开,则会重新打洞 9 | 10 | ![穿透扭转流程](docs/images/代理扭转流程.png) 11 | 12 | **启动** 13 | 14 | 服务端: java -jar xxx.jar -server.port xxxx 15 | 客户端:java -jar xxx.jar -server_addr 127.0.0.1 -server_port 10240 -token 123456 -proxy_addr 127.0.0.1 -proxy_port 3306 -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/session/Session.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.session; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @author memory 9 | */ 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class Session { 14 | 15 | /** 16 | * 用户唯一性标识 17 | */ 18 | private String token; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/exception/BaseException.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.exception; 2 | 3 | /** 4 | *

5 | * 基础错误出口类 6 | *

7 | * @author fanwenjie@cvte.com 8 | * @since oversea v1.0.0 2019/11/14 7:12 下午 9 | */ 10 | public interface BaseException { 11 | 12 | /** 13 | * 获取错误码 14 | */ 15 | String getCode(); 16 | 17 | /** 18 | * 获取错误信息 19 | */ 20 | String getMessage(); 21 | } 22 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/protocol/MessagePacket.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.protocol; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | *

9 | * message 传输协议 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020/3/3 15:54 14 | */ 15 | @Data 16 | public class MessagePacket { 17 | 18 | private int cmd; 19 | 20 | private Map metaData; 21 | 22 | private byte[] data; 23 | } 24 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/protocol/Command.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.protocol; 2 | 3 | /** 4 | *

5 | * 协议指令集合 6 | *

7 | * 8 | * @author fanwenjie 9 | * @since 2020/3/1 10:35 10 | */ 11 | 12 | public interface Command { 13 | /** 14 | *

15 | * 登录指令 16 | *

17 | * 18 | * @since 2020/3/1 10:36 19 | */ 20 | Byte REGISTER = 1; 21 | 22 | Byte DATA = 2; 23 | 24 | Byte CONNECT = 3; 25 | } 26 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/mapper/TunnelMapper.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.mysise.natplus.server.entity.Tunnel; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | *

9 | * 隧道/通道 Mapper 接口 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020-03-07 14 | */ 15 | @Component 16 | public interface TunnelMapper extends BaseMapper { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/mapper/SysUserMapper.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.mapper; 2 | 3 | import org.mysise.natplus.server.entity.SysUser; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | *

9 | * 用户信息表 Mapper 接口 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020-03-09 14 | */ 15 | @Component 16 | public interface SysUserMapper extends BaseMapper { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/model/TunnelRequest.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | *

9 | * 隧道请求参数 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020/3/10 0:11 14 | */ 15 | @Data 16 | public class TunnelRequest implements Serializable{ 17 | 18 | private Integer id; 19 | 20 | private String name; 21 | 22 | private Integer type; 23 | 24 | private String hostName; 25 | } 26 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/model/TunnelSearch.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | *

9 | * 隧道请求参数 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020/3/10 0:11 14 | */ 15 | @Data 16 | public class TunnelSearch implements Serializable{ 17 | 18 | private String name; 19 | 20 | private Integer type; 21 | 22 | private String hostName; 23 | 24 | private String token; 25 | } 26 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/protocol/ConnectPacket.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.protocol; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | *

8 | * 连接协议 9 | *

10 | * 11 | * @author fanwenjie 12 | * @since 2020/3/2 23:42 13 | */ 14 | @EqualsAndHashCode(callSuper = true) 15 | @Data 16 | public class ConnectPacket extends Packet { 17 | 18 | private String channelId; 19 | 20 | @Override 21 | public Byte getCommand() { 22 | return Command.CONNECT; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/protocol/DataPacket.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.protocol; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | 7 | /** 8 | *

9 | * 数据转换 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020/3/2 22:05 14 | */ 15 | @EqualsAndHashCode(callSuper = true) 16 | @Data 17 | public class DataPacket extends Packet { 18 | 19 | private byte[] bytes; 20 | 21 | private String token; 22 | 23 | private String channelId; 24 | 25 | @Override 26 | public Byte getCommand() { 27 | return Command.DATA; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/codec/PacketDecoder.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToMessageDecoder; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | *

11 | * 解码 12 | *

13 | * 14 | * @author fanwenjie 15 | * @since 2020/3/2 15:33 16 | */ 17 | public class PacketDecoder extends MessageToMessageDecoder { 18 | 19 | @Override 20 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 21 | out.add(PacketCode.INSTANCE.decode(in)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/entity/SysUser.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.entity; 2 | 3 | import org.mysise.natplus.server.entity.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.experimental.Accessors; 7 | 8 | /** 9 | *

10 | * 用户信息表 11 | *

12 | * 13 | * @author fanwenjie 14 | * @since 2020-03-09 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | @Accessors(chain = true) 19 | public class SysUser extends BaseEntity { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | private String email; 24 | 25 | private String password; 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/codec/PacketEncoder.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.mysise.natplus.common.protocol.Packet; 7 | 8 | /** 9 | *

10 | * 编码 11 | *

12 | * 13 | * @author fanwenjie 14 | * @since 2020/3/2 15:33 15 | */ 16 | public class PacketEncoder extends MessageToByteEncoder { 17 | 18 | @Override 19 | protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) { 20 | PacketCode.INSTANCE.encode(out, packet); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/protocol/Packet.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.protocol; 2 | 3 | 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import lombok.Data; 6 | 7 | 8 | /** 9 | *

10 | * 通信抽象类 11 | *

12 | * 13 | * @author fanwenjie 14 | * @since 2020/3/1 10:33 15 | */ 16 | @Data 17 | public abstract class Packet { 18 | 19 | /** 20 | *

21 | * 协议版本 22 | *

23 | * 24 | * @since 2020/3/1 10:34 25 | */ 26 | @JSONField(deserialize = false, serialize = false) 27 | private Byte version = 1; 28 | 29 | 30 | @JSONField(serialize = false) 31 | public abstract Byte getCommand(); 32 | } 33 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/controller/SysUserController.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.controller; 2 | 3 | 4 | import org.mysise.natplus.server.common.JWTUtils; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | *

13 | * 用户信息表 前端控制器 14 | *

15 | * 16 | * @author fanwenjie 17 | * @since 2020-03-09 18 | */ 19 | @RestController 20 | @RequestMapping("/api/sys-user") 21 | public class SysUserController { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/service/ISysUserService.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.service; 2 | 3 | import org.mysise.natplus.server.entity.SysUser; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 用户信息表 服务类 9 | *

10 | * 11 | * @author fanwenjie 12 | * @since 2020-03-09 13 | */ 14 | public interface ISysUserService extends IService { 15 | 16 | /** 17 | * 根据email和密码获取用户 18 | * 19 | * @param email 20 | * @param passWord 21 | * @return SysUser 22 | * @author haizi 23 | * @since 2020/3/12 24 | */ 25 | 26 | SysUser getUser(String email, String passWord); 27 | } 28 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/serializer/Serializer.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.serializer; 2 | 3 | import org.mysise.natplus.common.serializer.impl.JSONSerializer; 4 | 5 | /** 6 | *

7 | * 消息序列化 8 | *

9 | * 10 | * @author fanwenjie 11 | * @since 2020/3/1 10:40 12 | */ 13 | public interface Serializer { 14 | 15 | Serializer DEFAULT = new JSONSerializer(); 16 | 17 | /** 18 | * 序列化算法 19 | * @return 20 | */ 21 | byte getSerializerAlogrithm(); 22 | 23 | /** 24 | * java 对象转换成二进制 25 | */ 26 | byte[] serialize(Object object); 27 | 28 | /** 29 | * 二进制转换成 java 对象 30 | */ 31 | T deserialize(Class clazz, byte[] bytes); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | *

12 | * 基础类 13 | *

14 | * 15 | * @author fanwenjie 16 | * @since 2020/3/6 23:58 17 | */ 18 | @Data 19 | public class BaseEntity implements Serializable { 20 | 21 | /** 22 | * 自增,本项目默认为自增,如果不是自增的请不要继承该接口 23 | */ 24 | @TableId(type = IdType.AUTO) 25 | private Integer id; 26 | 27 | private LocalDateTime createTime; 28 | 29 | private LocalDateTime updateTime; 30 | } -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/entity/Tunnel.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.entity; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | *

8 | * 隧道/通道 9 | *

10 | * 11 | * @author fanwenjie 12 | * @since 2020-03-07 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper = true) 16 | public class Tunnel extends BaseEntity { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | private String token; 21 | 22 | private String name; 23 | 24 | // private Integer uid; 25 | 26 | /** 27 | * 1:TCP 2:HTTP 28 | */ 29 | private Integer type; 30 | 31 | /** 32 | * 绑定的host 33 | */ 34 | private String hostName; 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/exception/Constant.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.exception; 2 | 3 | /** 4 | *

5 | * 常量 todo 错误码需要重新设计 6 | *

7 | * @author fanwenjie@cvte.com 8 | * @since oversea v1.0.0 2019/11/14 11:21 上午 9 | */ 10 | public class Constant { 11 | 12 | /** 13 | * 默认返回 14 | */ 15 | public static final String SUCCESS = "0"; 16 | 17 | /** 18 | * 程序未知异常 19 | */ 20 | public static final String SERVER_ERROR = "-1"; 21 | 22 | /** 23 | * 数据库异常 24 | */ 25 | public static final String SQL_ERROR = "-2"; 26 | 27 | /** 28 | * 是否删除 29 | */ 30 | public interface DELETED{ 31 | int YES = 1; 32 | int NO = 0; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/protocol/RegisterPacket.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.protocol; 2 | 3 | 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | 8 | /** 9 | *

10 | * 链接注册消息体 11 | *

12 | * 13 | * @author fanwenjie 14 | * @since 2020/3/1 10:40 15 | */ 16 | @EqualsAndHashCode(callSuper = true) 17 | @Data 18 | public class RegisterPacket extends Packet { 19 | 20 | 21 | /** 22 | *

23 | * 用户令牌 24 | *

25 | * 26 | * @since 2020/3/1 10:39 27 | */ 28 | private String token; 29 | 30 | private Boolean success; 31 | 32 | private String message; 33 | 34 | @Override 35 | public Byte getCommand() { 36 | return Command.REGISTER; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/model/TunnelResponse.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | *

10 | * 隧道信息返回 11 | *

12 | * 13 | * @author fanwenjie 14 | * @since 2020/3/10 0:21 15 | */ 16 | @Data 17 | public class TunnelResponse implements Serializable { 18 | 19 | private Integer id; 20 | 21 | private String token; 22 | 23 | private String name; 24 | 25 | // private Integer uid; 26 | 27 | /** 28 | * 1:TCP 2:HTTP 29 | */ 30 | private Integer type; 31 | 32 | /** 33 | * 绑定的host 34 | */ 35 | private String hostName; 36 | 37 | private LocalDateTime createTime; 38 | 39 | private LocalDateTime updateTime; 40 | } 41 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/interceptor/InterceptorConfig.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common.interceptor; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | import javax.annotation.Resource; 8 | 9 | /** 10 | * 11 | * 12 | * @author haizi 13 | * @since 2020/03/12 16:56 14 | */ 15 | @Configuration 16 | public class InterceptorConfig implements WebMvcConfigurer { 17 | 18 | @Resource 19 | private AuthenticationInterceptor authenticationInterceptor; 20 | 21 | @Override 22 | public void addInterceptors(InterceptorRegistry registry) { 23 | registry.addInterceptor(authenticationInterceptor) 24 | .addPathPatterns("/**").excludePathPatterns("/api/login/**"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/codec/Spliter.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 6 | 7 | /** 8 | *

9 | * 粘包拆包处理 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020/3/1 12:07 14 | */ 15 | public class Spliter extends LengthFieldBasedFrameDecoder { 16 | private static final int LENGTH_FIELD_OFFSET = 7; 17 | private static final int LENGTH_FIELD_LENGTH = 4; 18 | 19 | public Spliter() { 20 | super(Integer.MAX_VALUE, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH); 21 | } 22 | 23 | @Override 24 | protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 25 | if (in.getInt(in.readerIndex()) != PacketCode.MAGIC_NUMBER) { 26 | ctx.channel().close(); 27 | return null; 28 | } 29 | 30 | return super.decode(ctx, in); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.controller; 2 | 3 | import org.mysise.natplus.common.exception.BizException; 4 | import org.mysise.natplus.server.common.JWTUtils; 5 | import org.mysise.natplus.server.common.model.TunnelRequest; 6 | import org.mysise.natplus.server.entity.SysUser; 7 | import org.mysise.natplus.server.service.ISysUserService; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * 登陆 控制器 14 | * 15 | * @author haizi 16 | * @since 2020/03/12 18:23 17 | */ 18 | 19 | @RestController 20 | @RequestMapping("/api/login") 21 | public class LoginController { 22 | @Resource 23 | private ISysUserService sysUserService; 24 | 25 | @GetMapping("/login") 26 | public String login(@RequestParam("email") String email, @RequestParam("passWord") String passWord){ 27 | 28 | SysUser user = sysUserService.getUser(email,passWord); 29 | if(user == null){ 30 | throw new BizException("用户不存在"); 31 | } 32 | return JWTUtils.createToken(user); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/SpringUtils.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | *

10 | * 获取ioc容器中的Spring bean 11 | *

12 | * 13 | * @author fanwenjie 14 | * @since 2020/3/11 10:50 15 | */ 16 | @Component 17 | public class SpringUtils implements ApplicationContextAware { 18 | private static ApplicationContext applicationContext; 19 | public static ApplicationContext getApplicationContext() { 20 | return applicationContext; 21 | } 22 | 23 | @Override 24 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 25 | SpringUtils.applicationContext = applicationContext; 26 | } 27 | public static Object getBean(String name){ 28 | return getApplicationContext().getBean(name); 29 | } 30 | public static Object getBean(Class t){ 31 | return getApplicationContext().getBean(t); 32 | } 33 | } -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/service/impl/SysUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 5 | import org.mysise.natplus.server.entity.SysUser; 6 | import org.mysise.natplus.server.entity.Tunnel; 7 | import org.mysise.natplus.server.mapper.SysUserMapper; 8 | import org.mysise.natplus.server.service.ISysUserService; 9 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | *

16 | * 用户信息表 服务实现类 17 | *

18 | * 19 | * @author fanwenjie 20 | * @since 2020-03-09 21 | */ 22 | @Service 23 | public class SysUserServiceImpl extends ServiceImpl implements ISysUserService { 24 | 25 | @Override 26 | public SysUser getUser(String email, String passWord) { 27 | QueryWrapper queryWrapper = new QueryWrapper<>(); 28 | queryWrapper.eq("email", email) 29 | .eq("password", passWord); 30 | return this.getOne(queryWrapper); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/vo/Result.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.mysise.natplus.common.exception.BaseException; 7 | import org.mysise.natplus.common.exception.Constant; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | *

13 | * 通用数据返回接口 14 | *

15 | * @author fanwenjie@cvte.com 16 | * @since oversea v1.0.0 2019/11/14 11:13 上午 17 | */ 18 | @Data 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | public class Result implements Serializable { 22 | 23 | /** 24 | * 状态码,默认成功为0 25 | */ 26 | private String code = Constant.SUCCESS; 27 | 28 | /** 29 | * 消息内容 30 | */ 31 | private String message; 32 | 33 | /** 34 | * 结果集 35 | */ 36 | private T result; 37 | 38 | public Result(T result){ 39 | this.result = result; 40 | } 41 | 42 | public Result(String code, String message){ 43 | this.code = code; 44 | this.message = message; 45 | } 46 | public Result(BaseException baseCode){ 47 | this.code=baseCode.getCode(); 48 | this.message=baseCode.getMessage(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/exception/CommonCode.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.exception; 2 | 3 | 4 | /** 5 | *

6 | * 公用错误枚举类 7 | *

8 | * @author fanwenjie@cvte.com 9 | * @since oversea v1.0.0 2019/11/14 7:15 下午 10 | */ 11 | public enum CommonCode implements BaseException { 12 | SUCCESS(Constant.SUCCESS,"ok"), 13 | SERVER_ERROR(Constant.SERVER_ERROR,"server fail"), 14 | SQL_INSERT_FAIL(Constant.SQL_ERROR,"sql insert execute fail"), 15 | SQL_UPDATE_FAIL(Constant.SQL_ERROR,"sql update execute fail"), 16 | SQL_DELETE_FAIL(Constant.SQL_ERROR,"sql delete execute fail"), 17 | BIZ_DATA_REGION_SAME(Constant.SQL_ERROR,"name or nameEn or namePinYin is same"), 18 | BIZ_DATA_REGION_PID_NOT_FIND(Constant.SQL_ERROR,"pid not find"), 19 | BIZ_PARAM_FAIL(Constant.SQL_ERROR,"param has fail"), 20 | ; 21 | 22 | 23 | 24 | private String code; 25 | private String message; 26 | 27 | CommonCode(String code, String message){ 28 | this.code = code; 29 | this.message = message; 30 | } 31 | @Override 32 | public String getCode() { 33 | return code; 34 | } 35 | 36 | @Override 37 | public String getMessage() { 38 | return message; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nat-plus-client/src/main/java/org/mysise/natplus/client/handler/LocalProxyHandler.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.client.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import org.mysise.natplus.common.protocol.DataPacket; 6 | 7 | /** 8 | *

9 | * 本地代理处理 10 | *

11 | * 12 | * @author fanwenjie 13 | * @since 2020/3/2 22:50 14 | */ 15 | public class LocalProxyHandler extends ChannelInboundHandlerAdapter { 16 | 17 | private ClientHandler clientHandler; 18 | 19 | private String channelId; 20 | 21 | public LocalProxyHandler(ClientHandler clientHandler, String channelId) { 22 | this.clientHandler = clientHandler; 23 | this.channelId = channelId; 24 | } 25 | 26 | @Override 27 | public void channelRead(ChannelHandlerContext ctx, Object msg){ 28 | byte[] data = (byte[]) msg; 29 | DataPacket dataPacket = new DataPacket(); 30 | dataPacket.setBytes(data); 31 | dataPacket.setChannelId(channelId); 32 | clientHandler.getCtx().writeAndFlush(dataPacket); 33 | } 34 | 35 | 36 | 37 | @Override 38 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 39 | System.out.println("本地连接断开"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/serializer/impl/JSONSerializer.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.serializer.impl; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import org.mysise.natplus.common.serializer.Serializer; 6 | import org.mysise.natplus.common.serializer.SerializerAlogrithm; 7 | import org.mysise.natplus.common.serializer.SerializerDemo; 8 | 9 | /** 10 | *

11 | * json 序列化 阿里巴巴 fastjson 作为序列化框架 12 | *

13 | * 14 | * @author fanwenjie 15 | * @since 2020/3/1 10:42 16 | */ 17 | public class JSONSerializer implements Serializer { 18 | 19 | 20 | @Override 21 | public byte getSerializerAlogrithm() { 22 | return SerializerAlogrithm.JSON; 23 | } 24 | 25 | @Override 26 | public byte[] serialize(Object object) { 27 | 28 | return JSON.toJSONBytes(object); 29 | } 30 | 31 | @Override 32 | public T deserialize(Class clazz, byte[] bytes) { 33 | return JSON.parseObject(bytes, clazz); 34 | } 35 | 36 | 37 | public static void main(String[] args){ 38 | byte[] a = {1,2,3}; 39 | SerializerDemo demo = new SerializerDemo(); 40 | demo.setAge(11); 41 | demo.setName("fan"); 42 | demo.setBytes(a); 43 | JSONSerializer serializer = new JSONSerializer(); 44 | byte[] bytes = serializer.serialize(demo); 45 | System.out.println(JSON.toJSONString(serializer.deserialize(SerializerDemo.class,bytes))); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /nat-plus-client/src/main/java/org/mysise/natplus/client/net/TcpConnection.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.client.net; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | 11 | /** 12 | *

13 | * 连接服务端 14 | *

15 | * 16 | * @author fanwenjie 17 | * @since 2020/3/2 0:42 18 | */ 19 | public class TcpConnection { 20 | 21 | /** 22 | *

23 | * 创建链接 24 | *

25 | * 26 | * @author fanwenjie 27 | * @since 2020/3/2 0:43 28 | */ 29 | public void connect(String host, int port, ChannelInitializer channelInitializer) throws InterruptedException{ 30 | 31 | NioEventLoopGroup workerGroup = new NioEventLoopGroup(); 32 | 33 | try { 34 | Bootstrap b = new Bootstrap(); 35 | b.group(workerGroup); 36 | b.channel(NioSocketChannel.class); 37 | b.option(ChannelOption.SO_KEEPALIVE, true); 38 | b.handler(channelInitializer); 39 | 40 | Channel channel = b.connect(host, port).sync().channel(); 41 | channel.closeFuture().addListener((ChannelFutureListener) future -> workerGroup.shutdownGracefully()); 42 | } catch (Exception e) { 43 | workerGroup.shutdownGracefully(); 44 | throw e; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/service/ITunnelService.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.mysise.natplus.server.common.model.TunnelRequest; 5 | import org.mysise.natplus.server.common.model.TunnelResponse; 6 | import org.mysise.natplus.server.common.model.TunnelSearch; 7 | import org.mysise.natplus.server.entity.Tunnel; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | *

13 | * 隧道/通道 服务类 14 | *

15 | * 16 | * @author fanwenjie 17 | * @since 2020-03-07 18 | */ 19 | public interface ITunnelService extends IService { 20 | 21 | /** 22 | *

23 | * 判断令牌是否存在 24 | *

25 | * 26 | * @author fanwenjie 27 | * @since 2020/3/9 23:32 28 | */ 29 | Boolean tokenExist(String token); 30 | /** 31 | *

32 | * 通过域名查询 33 | *

34 | * 35 | * @author fanwenjie 36 | * @since 2020/3/9 23:32 37 | */ 38 | Tunnel queryTokenByHostName(String hostName); 39 | 40 | /** 41 | *

42 | * 保存 43 | *

44 | * 45 | * @author fanwenjie 46 | * @since 2020/3/10 0:13 47 | */ 48 | Integer save(TunnelRequest request); 49 | 50 | /** 51 | *

52 | * 修改隧道信息 53 | *

54 | * 55 | * @author fanwenjie 56 | * @since 2020/3/10 0:35 57 | */ 58 | Integer update(TunnelRequest request); 59 | 60 | /** 61 | *

62 | * 列表返回查询 63 | *

64 | * 65 | * @author fanwenjie 66 | * @since 2020/3/10 0:23 67 | */ 68 | List listTunnel(TunnelSearch request); 69 | } 70 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | mybatis-plus: 4 | mapper-locations: classpath:/mapper/*Mapper.xml 5 | type-aliases-package: org.mysise.demo1.entity 6 | configuration: 7 | map-underscore-to-camel-case: true 8 | cache-enabled: false 9 | jdbc-type-for-null: null 10 | 11 | logging: 12 | level: 13 | com.baomidou.mybatisplus.samples.quickstart: debug 14 | spring: 15 | datasource: 16 | druid: 17 | url: jdbc:mysql://localhost:3306/nat_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false 18 | username: root 19 | password: 123456 20 | driver-class-name: com.mysql.jdbc.Driver 21 | #初始化时建立物理连接的个数 22 | initial-size: 5 23 | # 最大连接池数量 24 | max-active: 30 25 | # 最小连接池数量 26 | min-idle: 5 27 | # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。 28 | max-pool-prepared-statement-per-connection-size: 50 29 | test-on-borrow: false 30 | # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 31 | time-between-eviction-runs-millis: 60000 32 | test-while-idle: true 33 | pool-prepared-statements: true 34 | # 用来检测连接是否有效的sql,要求是一个查询语句 35 | validation-query: SELECT 1 FROM DUAL 36 | # 合并多个DruidDataSource的监控数据 37 | use-global-data-source-stat: true 38 | # 配置监控统计拦截的filters,去掉后监控界面sql无法统计 39 | filters: stat,wall 40 | min-evictable-idle-time-millis: 300000 41 | # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 42 | connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000 43 | max-wait: 60000 44 | test-on-return: false 45 | application: 46 | name: nat-plus-server 47 | aop: 48 | auto: true 49 | proxy-target-class: true -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/controller/TunnelController.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.controller; 2 | 3 | 4 | import org.mysise.natplus.server.common.model.TunnelRequest; 5 | import org.mysise.natplus.server.common.model.TunnelResponse; 6 | import org.mysise.natplus.server.common.model.TunnelSearch; 7 | import org.mysise.natplus.server.service.ITunnelService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | *

15 | * 隧道/通道 前端控制器 16 | *

17 | * 18 | * @author fanwenjie 19 | * @since 2020-03-07 20 | */ 21 | @RestController 22 | @RequestMapping("/api/tunnel") 23 | public class TunnelController { 24 | 25 | 26 | @Autowired 27 | private ITunnelService tunnelService; 28 | /** 29 | *

30 | * 增加隧道 31 | *

32 | * 33 | * @author fanwenjie 34 | * @since 2020/3/10 0:01 35 | */ 36 | @PostMapping("") 37 | public Integer add(@RequestBody TunnelRequest request){ 38 | return tunnelService.save(request); 39 | } 40 | 41 | 42 | /** 43 | *

44 | * 隧道查询 45 | *

46 | * 47 | * @author fanwenjie 48 | * @since 2020/3/10 0:45 49 | */ 50 | @GetMapping("") 51 | public List listTunnel(TunnelSearch request){ 52 | return tunnelService.listTunnel(request); 53 | } 54 | 55 | /** 56 | *

57 | * 编辑隧道信息 58 | *

59 | * 60 | * @author fanwenjie 61 | * @since 2020/3/10 0:45 62 | */ 63 | @PutMapping("/{id}") 64 | public Integer update(@PathVariable(name = "id") Integer id, @RequestBody TunnelRequest request){ 65 | return tunnelService.update(request); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/interceptor/AuthenticationInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common.interceptor; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.exceptions.JWTDecodeException; 7 | import com.auth0.jwt.exceptions.JWTVerificationException; 8 | import com.auth0.jwt.interfaces.Claim; 9 | import org.mysise.natplus.common.exception.BizException; 10 | import org.mysise.natplus.server.common.JWTUtils; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.web.method.HandlerMethod; 13 | import org.springframework.web.servlet.HandlerInterceptor; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.lang.reflect.Method; 18 | import java.util.Map; 19 | 20 | /** 21 | * token拦截器 22 | * 23 | * @author haizi 24 | * @since 2020/03/12 16:25 25 | */ 26 | @Component 27 | public class AuthenticationInterceptor implements HandlerInterceptor { 28 | 29 | @Override 30 | public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { 31 | String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token 32 | 33 | // 执行认证 34 | if (token == null) { 35 | throw new BizException("无token,请重新登录"); 36 | } 37 | 38 | Map claimMap = JWTUtils.verifyToken(token); 39 | if (claimMap == null) { 40 | throw new BizException("401"); 41 | } 42 | 43 | if(JWTUtils.isTokenExpired(claimMap.get("exp").asDate())){ 44 | throw new BizException("token失效,请重新登录"); 45 | } 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/exception/SqlException.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.exception; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | *

8 | * 数据库异常 9 | *

10 | * @author fanwenjie@cvte.com 11 | * @since oversea v1.0.0 2019/11/14 7:10 下午 12 | */ 13 | @EqualsAndHashCode(callSuper = true) 14 | @Data 15 | public class SqlException extends RuntimeException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | /** 20 | * 错误码 21 | */ 22 | protected String errorCode; 23 | /** 24 | * 错误信息 25 | */ 26 | protected String errorMsg; 27 | 28 | public SqlException() { 29 | super(); 30 | } 31 | 32 | public SqlException(BaseException baseException) { 33 | super(baseException.getCode()); 34 | this.errorCode = baseException.getCode(); 35 | this.errorMsg = baseException.getMessage(); 36 | } 37 | 38 | public SqlException(BaseException baseException, String message) { 39 | super(baseException.getCode()); 40 | this.errorCode = baseException.getCode(); 41 | this.errorMsg = message; 42 | } 43 | 44 | public SqlException(String errorMsg) { 45 | super(errorMsg); 46 | this.errorMsg = errorMsg; 47 | } 48 | 49 | public SqlException(String errorCode, String errorMsg) { 50 | super(errorCode); 51 | this.errorCode = errorCode; 52 | this.errorMsg = errorMsg; 53 | } 54 | 55 | public SqlException(String errorCode, String errorMsg, Throwable cause) { 56 | super(errorCode, cause); 57 | this.errorCode = errorCode; 58 | this.errorMsg = errorMsg; 59 | } 60 | 61 | 62 | 63 | 64 | @Override 65 | public Throwable fillInStackTrace() { 66 | return this; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/exception/BizException.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.exception; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | *

8 | * 业务异常 9 | *

10 | * @author fanwenjie@cvte.com 11 | * @since oversea v1.0.0 2019/11/14 7:10 下午 12 | */ 13 | @EqualsAndHashCode(callSuper = true) 14 | @Data 15 | public class BizException extends RuntimeException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | /** 20 | * 错误码 21 | */ 22 | protected String errorCode; 23 | /** 24 | * 错误信息 25 | */ 26 | protected String errorMsg; 27 | 28 | public BizException() { 29 | super(); 30 | } 31 | 32 | public BizException(BaseException baseException) { 33 | super(baseException.getCode()); 34 | this.errorCode = baseException.getCode(); 35 | this.errorMsg = baseException.getMessage(); 36 | } 37 | 38 | public BizException(BaseException baseException, Throwable cause) { 39 | super(baseException.getCode(), cause); 40 | this.errorCode = baseException.getCode(); 41 | this.errorMsg = baseException.getMessage(); 42 | } 43 | 44 | public BizException(String errorMsg) { 45 | super(errorMsg); 46 | this.errorMsg = errorMsg; 47 | } 48 | 49 | public BizException(String errorCode, String errorMsg) { 50 | super(errorCode); 51 | this.errorCode = errorCode; 52 | this.errorMsg = errorMsg; 53 | } 54 | 55 | public BizException(String errorCode, String errorMsg, Throwable cause) { 56 | super(errorCode, cause); 57 | this.errorCode = errorCode; 58 | this.errorMsg = errorMsg; 59 | } 60 | 61 | 62 | 63 | 64 | @Override 65 | public Throwable fillInStackTrace() { 66 | return this; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /nat-plus-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | nat-plus 7 | org.mysise 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | nat-plus-common 13 | 14 | 15 | 1.8 16 | 17 | 18 | 19 | 20 | io.netty 21 | netty-all 22 | 4.1.42.Final 23 | 24 | 25 | 26 | com.alibaba 27 | fastjson 28 | 1.2.61 29 | 30 | 31 | 32 | commons-cli 33 | commons-cli 34 | 1.4 35 | 36 | 37 | 38 | cn.hutool 39 | hutool-all 40 | 5.2.0 41 | 42 | 43 | 44 | org.modelmapper 45 | modelmapper 46 | 2.3.5 47 | 48 | 49 | 50 | 51 | com.auth0 52 | java-jwt 53 | 3.10.0 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/utils/SessionUtil.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.utils; 2 | 3 | import io.netty.channel.Channel; 4 | import org.mysise.natplus.common.attribute.Attributes; 5 | import org.mysise.natplus.common.session.Session; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | *

12 | * 映射关系绑定 token -channel 13 | *

14 | * 15 | * @author fanwenjie 16 | * @since 2020/3/2 11:49 17 | */ 18 | public class SessionUtil { 19 | // token -> channel 的映射 20 | private static final Map TOKEN_CHANNEL = new ConcurrentHashMap<>(); 21 | 22 | // channel_id -> CHANNEL 23 | private static final Map CHANNEL_ID_CHANNEL = new ConcurrentHashMap<>(); 24 | public static void bindSession(Session session, Channel channel) { 25 | TOKEN_CHANNEL.put(session.getToken(), channel); 26 | channel.attr(Attributes.SESSION).set(session); 27 | } 28 | 29 | public static void unBindSession(Channel channel) { 30 | if (hasLogin(channel)) { 31 | TOKEN_CHANNEL.remove(getSession(channel).getToken()); 32 | channel.attr(Attributes.SESSION).set(null); 33 | } 34 | } 35 | 36 | public static boolean hasLogin(Channel channel) { 37 | 38 | return channel.hasAttr(Attributes.SESSION); 39 | } 40 | 41 | public static Session getSession(Channel channel) { 42 | 43 | return channel.attr(Attributes.SESSION).get(); 44 | } 45 | 46 | public static Channel getChannel(String token) { 47 | return TOKEN_CHANNEL.get(token); 48 | } 49 | 50 | public static void bindChannel(String channelId, Channel channel){ 51 | CHANNEL_ID_CHANNEL.put(channelId,channel); 52 | } 53 | 54 | public static void unBindChannel(String channelId){ 55 | CHANNEL_ID_CHANNEL.remove(channelId); 56 | } 57 | 58 | 59 | public static Channel getChannelByChannelId(String channelId){ 60 | return CHANNEL_ID_CHANNEL.get(channelId); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/core/net/TcpServer.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.core.net; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.*; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.channel.socket.nio.NioServerSocketChannel; 7 | import io.netty.channel.socket.nio.NioSocketChannel; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | /** 11 | *

12 | * TCP 服务端启动 13 | *

14 | * 15 | * @author fanwenjie 16 | * @since 2020/3/1 11:14 17 | */ 18 | @Slf4j 19 | public class TcpServer { 20 | 21 | private Channel channel; 22 | 23 | private int port; 24 | 25 | public TcpServer(int port) { 26 | this.port = port; 27 | } 28 | 29 | /** 30 | *

31 | * 绑定服务端口启动 32 | *

33 | * 34 | * @author fanwenjie 35 | * @since 2020/3/1 11:17 36 | */ 37 | public synchronized void bind(ChannelInitializer channelInitializer){ 38 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 39 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 40 | 41 | try { 42 | ServerBootstrap b = new ServerBootstrap(); 43 | b.group(bossGroup, workerGroup) 44 | .channel(NioServerSocketChannel.class) 45 | .childHandler(channelInitializer) 46 | .childOption(ChannelOption.SO_KEEPALIVE, true); 47 | channel = b.bind(port).sync().channel(); 48 | channel.closeFuture().addListener((ChannelFutureListener) future -> { 49 | workerGroup.shutdownGracefully(); 50 | bossGroup.shutdownGracefully(); 51 | }); 52 | } catch (Exception e) { 53 | workerGroup.shutdownGracefully(); 54 | bossGroup.shutdownGracefully(); 55 | log.error("服务启动失败:{}", e.getMessage()); 56 | } 57 | } 58 | 59 | 60 | public synchronized void close() { 61 | if (channel != null) { 62 | channel.close(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/utils/DtoUtils.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.utils; 2 | 3 | import org.modelmapper.Conditions; 4 | import org.modelmapper.ModelMapper; 5 | import org.modelmapper.convention.MatchingStrategies; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | 11 | /** 12 | *

13 | * 数据对象转换,从一个对象转成另外一个对象 14 | *

15 | * 16 | * @author fanwenjie 17 | * @since 2020/3/10 0:05 18 | */ 19 | public class DtoUtils { 20 | 21 | private static final ModelMapper INSTANCE = new ModelMapper(); 22 | private static final ModelMapper INSTANCE_STRICT = new ModelMapper(); 23 | 24 | static { 25 | //对象复制的时候不复制空值 26 | INSTANCE.getConfiguration().setPropertyCondition(Conditions.isNotNull()); 27 | 28 | //严格模式 29 | INSTANCE_STRICT.getConfiguration().setPropertyCondition(Conditions.isNotNull()); 30 | INSTANCE_STRICT.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); 31 | } 32 | 33 | private DtoUtils() { 34 | throw new InstantiationError("Must not instantiate this class"); 35 | } 36 | 37 | public static T map(S source, Class targetClass, boolean isStrict) { 38 | if (isStrict) { 39 | return INSTANCE_STRICT.map(source, targetClass); 40 | } else { 41 | return INSTANCE.map(source, targetClass); 42 | } 43 | } 44 | 45 | public static void mapTo(S source, T dist, boolean isStrict) { 46 | if (isStrict) { 47 | INSTANCE_STRICT.map(source, dist); 48 | } else { 49 | INSTANCE.map(source, dist); 50 | } 51 | } 52 | 53 | public static List mapList(List source, Class targetClass, boolean isStrict) { 54 | 55 | List list = new ArrayList<>(source.size()); 56 | 57 | if (isStrict) { 58 | for (int i = 0; i < source.size(); i++) { 59 | T target = INSTANCE_STRICT.map(source.get(i), targetClass); 60 | list.add(target); 61 | } 62 | } else { 63 | for (int i = 0; i < source.size(); i++) { 64 | T target = INSTANCE.map(source.get(i), targetClass); 65 | list.add(target); 66 | } 67 | } 68 | 69 | return list; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/resources/ log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex 7 | 8 | ./logs 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | ${LOG_PATTERN} 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 48 | 49 | 50 |      51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.mysise.natplus.common.exception.BizException; 7 | import org.mysise.natplus.common.exception.CommonCode; 8 | import org.mysise.natplus.common.exception.SqlException; 9 | import org.mysise.natplus.common.vo.Result; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.core.MethodParameter; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.http.server.ServerHttpRequest; 14 | import org.springframework.http.server.ServerHttpResponse; 15 | import org.springframework.web.bind.annotation.ControllerAdvice; 16 | import org.springframework.web.bind.annotation.ExceptionHandler; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | 22 | /** 23 | *

24 | * 全局异常 25 | *

26 | * @author fanwenjie@cvte.com 27 | * @since oversea v1.0.0 2019/11/14 7:10 下午 28 | */ 29 | @ControllerAdvice 30 | @Slf4j 31 | public class GlobalExceptionHandler implements ResponseBodyAdvice { 32 | 33 | 34 | @Autowired 35 | private ObjectMapper objectMapper; 36 | 37 | @ExceptionHandler(value = BizException.class) 38 | @ResponseBody 39 | public Result bizExceptionHandler(BizException e){ 40 | log.error("发生业务异常!原因是:{}",e.getErrorMsg()); 41 | return new Result<>(e.getErrorCode(),e.getErrorMsg()); 42 | } 43 | 44 | 45 | @ExceptionHandler(value = SqlException.class) 46 | @ResponseBody 47 | public Result sqlExceptionHandler(HttpServletRequest req, SqlException e){ 48 | log.error("未知异常!原因是:",e); 49 | return new Result<>(CommonCode.SERVER_ERROR); 50 | } 51 | 52 | @Override 53 | public boolean supports(MethodParameter methodParameter, Class aClass) { 54 | return true; 55 | } 56 | 57 | @SneakyThrows 58 | @Override 59 | public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 60 | if (body instanceof Result){ 61 | return body; 62 | }else if (body instanceof String || null == body) { 63 | // 为什么要特殊处理String https://jpanj.com/2018/SpringBoot-%E4%B8%AD%E7%BB%9F%E4%B8%80%E5%8C%85%E8%A3%85%E5%93%8D%E5%BA%94/ 64 | return objectMapper.writeValueAsString(new Result<>(body)); 65 | } 66 | return new Result<>(body); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /nat-plus-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | nat-plus 7 | org.mysise 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | nat-plus-client 13 | 14 | 15 | 16 | org.mysise 17 | nat-plus-common 18 | 1.0.0 19 | 20 | 21 | 22 | 23 | client 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-shade-plugin 46 | 3.2.2 47 | 48 | 49 | package 50 | 51 | shade 52 | 53 | 54 | 55 | 56 | org.mysise.natplus.client.ClientApplication 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/core/handler/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.core.handler; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.mysise.natplus.common.protocol.ConnectPacket; 9 | import org.mysise.natplus.common.protocol.DataPacket; 10 | import org.mysise.natplus.common.protocol.RegisterPacket; 11 | import org.mysise.natplus.common.session.Session; 12 | import org.mysise.natplus.common.utils.SessionUtil; 13 | import org.mysise.natplus.server.service.ITunnelService; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Component; 16 | 17 | /** 18 | *

19 | * 跟客户端 通信的拦截器 20 | *

21 | * 22 | * @author fanwenjie 23 | * @since 2020/3/1 23:55 24 | */ 25 | @Slf4j 26 | public class ServerHandler extends ChannelInboundHandlerAdapter { 27 | 28 | @Override 29 | public void channelActive(ChannelHandlerContext ctx) { 30 | log.info("有新客户端连接接入。。。"+ctx.channel().remoteAddress()); 31 | } 32 | 33 | 34 | private ITunnelService iTunnelService; 35 | 36 | public ServerHandler(ITunnelService iTunnelService) { 37 | this.iTunnelService = iTunnelService; 38 | } 39 | 40 | /** 41 | *

42 | * 数据响应 43 | *

44 | * 45 | * @author fanwenjie 46 | * @since 2020/3/2 0:31 47 | */ 48 | @Override 49 | public void channelRead(ChannelHandlerContext ctx, Object msg){ 50 | if (msg instanceof RegisterPacket) { 51 | RegisterPacket registerPacket = (RegisterPacket) msg; 52 | if (StringUtils.isNotEmpty(registerPacket.getToken()) && iTunnelService.tokenExist(registerPacket.getToken())){ 53 | SessionUtil.bindSession(new Session(registerPacket.getToken()),ctx.channel()); 54 | log.info("Token: {},链接服务器成功", registerPacket.getToken()); 55 | registerPacket.setSuccess(true); 56 | registerPacket.setMessage("connect success"); 57 | ctx.channel().writeAndFlush(registerPacket); 58 | }else { 59 | registerPacket.setSuccess(false); 60 | registerPacket.setMessage("Token is wrong"); 61 | ctx.writeAndFlush(registerPacket); 62 | } 63 | }else if (msg instanceof ConnectPacket){ 64 | log.info("链接信息"); 65 | }else if(msg instanceof DataPacket){ 66 | DataPacket dataPacket = (DataPacket) msg; 67 | Channel channel = SessionUtil.getChannelByChannelId(dataPacket.getChannelId()); 68 | channel.writeAndFlush(dataPacket.getBytes()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/JWTUtils.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.interfaces.Claim; 7 | import com.auth0.jwt.interfaces.DecodedJWT; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.mysise.natplus.server.entity.SysUser; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.context.properties.ConfigurationProperties; 13 | import org.springframework.stereotype.Component; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.Date; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | *

22 | * jwt 工具类 23 | *

24 | * 25 | * @author fanwenjie 26 | * @since 2020/3/11 23:43 27 | */ 28 | @Component 29 | @Slf4j 30 | public class JWTUtils { 31 | @Value("${jwt.key}") 32 | private String key; 33 | 34 | @Value("${jwt.ttl}") 35 | private Long ttl; 36 | public static String SECRET; 37 | public static Long TTL_MILLIS; 38 | 39 | 40 | @PostConstruct 41 | public void run(){ 42 | SECRET = key; 43 | TTL_MILLIS = ttl; 44 | } 45 | 46 | /** 47 | *

48 | * 生成jwt-token 49 | *

50 | * 51 | * @author haizi 52 | * @since 2020/3/11 23:49 53 | */ 54 | public static String createToken(SysUser user){ 55 | Date expireDate = new Date(System.currentTimeMillis() + TTL_MILLIS * 1000); 56 | Map map = new HashMap<>(); 57 | map.put("alg", "HS256"); 58 | map.put("typ", "JWT"); 59 | return JWT.create() 60 | .withHeader(map) 61 | .withClaim("id", user.getId())//userId 62 | .withClaim("email", user.getEmail()) 63 | .withExpiresAt(expireDate) //超时设置,设置过期的日期 64 | .withIssuedAt(new Date()) //签发时间 65 | .sign(Algorithm.HMAC256(SECRET)); //SECRET加密 66 | } 67 | 68 | /** 69 | * 校验token并解析token 70 | * 71 | * @author haizi 72 | * @Date 2020/3/12 73 | */ 74 | public static Map verifyToken(String token) { 75 | DecodedJWT jwt = null; 76 | try { 77 | JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); 78 | jwt = verifier.verify(token); 79 | } catch (Exception e) { 80 | log.error(e.getMessage()); 81 | log.error("token解码异常"); 82 | //解码异常则抛出异常 83 | return null; 84 | } 85 | return jwt.getClaims(); 86 | } 87 | 88 | /** 89 | * Token 是否过期验证 90 | * 91 | * @author haizi 92 | * @since 2020/3/12 93 | */ 94 | public static boolean isTokenExpired (Date expirationTime) { 95 | return expirationTime.before(new Date()); 96 | } 97 | } -------------------------------------------------------------------------------- /nat-plus-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | nat-plus 7 | org.mysise 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | nat-plus-server 13 | 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | 23 | com.alibaba 24 | druid-spring-boot-starter 25 | 26 | 27 | 28 | mysql 29 | mysql-connector-java 30 | 31 | 32 | 33 | com.baomidou 34 | mybatis-plus-boot-starter 35 | 36 | 37 | 38 | com.baomidou 39 | mybatis-plus-generator 40 | 41 | 42 | 43 | org.mysise 44 | nat-plus-common 45 | 1.0.0 46 | 47 | 48 | 49 | 50 | 51 | org.freemarker 52 | freemarker 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-log4j2 58 | 59 | 60 | 61 | 62 | server 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | 68 | 69 | ZIP 70 | 71 | org.mysise.natplus.server.ServerApplication 72 | 73 | 74 | 75 | 76 | repackage 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/ServerApplication.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.socket.nio.NioSocketChannel; 5 | import io.netty.handler.codec.bytes.ByteArrayDecoder; 6 | import io.netty.handler.codec.bytes.ByteArrayEncoder; 7 | import io.netty.handler.timeout.IdleStateHandler; 8 | import org.mybatis.spring.annotation.MapperScan; 9 | import org.mysise.natplus.common.codec.PacketDecoder; 10 | import org.mysise.natplus.common.codec.PacketEncoder; 11 | import org.mysise.natplus.common.codec.Spliter; 12 | import org.mysise.natplus.server.common.SpringUtils; 13 | import org.mysise.natplus.server.core.handler.ProxyHandler; 14 | import org.mysise.natplus.server.core.handler.ServerHandler; 15 | import org.mysise.natplus.server.core.net.TcpServer; 16 | import org.mysise.natplus.server.service.ITunnelService; 17 | import org.mysise.natplus.server.service.impl.TunnelServiceImpl; 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.context.annotation.ComponentScan; 21 | import org.springframework.context.annotation.PropertySource; 22 | 23 | /** 24 | *

25 | * 服務啟動器 26 | *

27 | * 28 | * @author fanwenjie 29 | * @since 2020/2/27 13:14 30 | */ 31 | @SpringBootApplication 32 | @MapperScan(value = "org.mysise.natplus.server.mapper") 33 | @ComponentScan(basePackages = {"org.mysise"}) 34 | @PropertySource(value={"classpath:config.properties"}) 35 | public class ServerApplication { 36 | 37 | 38 | public static void main(String[] args) { 39 | SpringApplication.run(ServerApplication.class, args); 40 | 41 | ITunnelService iTunnelService = (ITunnelService) SpringUtils.getBean(TunnelServiceImpl.class); 42 | TcpServer tcpServer = new TcpServer(10240); 43 | tcpServer.bind( new ChannelInitializer() { 44 | @Override 45 | protected void initChannel(NioSocketChannel nioSocketChannel) { 46 | //粘包拆包 47 | nioSocketChannel.pipeline().addLast(new Spliter()); 48 | //解码 49 | nioSocketChannel.pipeline().addLast(new PacketDecoder()); 50 | // 编码 51 | nioSocketChannel.pipeline().addLast(new PacketEncoder()); 52 | //心跳处理 53 | nioSocketChannel.pipeline().addLast(new IdleStateHandler(60, 30, 0)); 54 | // 处理器 55 | nioSocketChannel.pipeline().addLast(new ServerHandler(iTunnelService)); 56 | } 57 | }); 58 | 59 | TcpServer proxyServer = new TcpServer(80); 60 | proxyServer.bind(new ChannelInitializer() { 61 | @Override 62 | protected void initChannel(NioSocketChannel nioSocketChannel) { 63 | // 处理器 64 | nioSocketChannel.pipeline().addLast(new ByteArrayDecoder(), new ByteArrayEncoder(), new ProxyHandler(iTunnelService)); 65 | } 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/init-db.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.7.27, for Win64 (x86_64) 2 | -- 3 | -- Host: 127.0.0.1 Database: nat_plus 4 | -- ------------------------------------------------------ 5 | -- Server version 5.7.27-log 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `sys_user` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `sys_user`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `sys_user` ( 26 | `id` int(11) NOT NULL AUTO_INCREMENT, 27 | `email` varchar(100) NOT NULL, 28 | `password` varchar(255) NOT NULL, 29 | `create_time` datetime NOT NULL, 30 | `update_time` datetime NOT NULL, 31 | `type` int(11) NOT NULL DEFAULT '1' COMMENT '1:普通用户 2:管理员', 32 | PRIMARY KEY (`id`), 33 | UNIQUE KEY `sys_user_email_uindex` (`email`) 34 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='用户信息表'; 35 | /*!40101 SET character_set_client = @saved_cs_client */; 36 | 37 | -- 38 | -- Dumping data for table `sys_user` 39 | -- 40 | 41 | LOCK TABLES `sys_user` WRITE; 42 | /*!40000 ALTER TABLE `sys_user` DISABLE KEYS */; 43 | /*!40000 ALTER TABLE `sys_user` ENABLE KEYS */; 44 | UNLOCK TABLES; 45 | 46 | -- 47 | -- Table structure for table `tunnel` 48 | -- 49 | 50 | DROP TABLE IF EXISTS `tunnel`; 51 | /*!40101 SET @saved_cs_client = @@character_set_client */; 52 | /*!40101 SET character_set_client = utf8 */; 53 | CREATE TABLE `tunnel` ( 54 | `id` int(11) NOT NULL AUTO_INCREMENT, 55 | `token` varchar(255) NOT NULL, 56 | `name` varchar(100) NOT NULL, 57 | `uid` int(11) NOT NULL, 58 | `type` tinyint(4) DEFAULT '1' COMMENT '1:TCP 2:HTTP', 59 | `create_time` datetime DEFAULT NULL, 60 | `update_time` datetime DEFAULT NULL, 61 | `host_name` varchar(200) DEFAULT NULL COMMENT '绑定的host', 62 | PRIMARY KEY (`id`), 63 | UNIQUE KEY `tunnel_token_uindex` (`token`) 64 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 COMMENT='隧道/通道'; 65 | /*!40101 SET character_set_client = @saved_cs_client */; 66 | 67 | -- 68 | -- Dumping data for table `tunnel` 69 | -- 70 | 71 | LOCK TABLES `tunnel` WRITE; 72 | /*!40000 ALTER TABLE `tunnel` DISABLE KEYS */; 73 | INSERT INTO `tunnel` VALUES (1,'123456','mysql',1,1,'2020-03-11 00:06:03','2020-03-11 00:06:07','netty.host.com'); 74 | INSERT INTO `tunnel` VALUES (2,'1212','1212',1,1,'2020-03-11 00:30:30','2020-03-11 00:30:32','q1q'); 75 | /*!40000 ALTER TABLE `tunnel` ENABLE KEYS */; 76 | UNLOCK TABLES; 77 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 78 | 79 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 80 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 81 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 82 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 83 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 84 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 85 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 86 | 87 | -- Dump completed on 2020-03-12 0:50:11 88 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/core/handler/ProxyHandler.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.core.handler; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.mysise.natplus.common.protocol.ConnectPacket; 9 | import org.mysise.natplus.common.protocol.DataPacket; 10 | import org.mysise.natplus.common.utils.SessionUtil; 11 | import org.mysise.natplus.server.entity.Tunnel; 12 | import org.mysise.natplus.server.service.ITunnelService; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | 15 | import java.net.InetSocketAddress; 16 | 17 | /** 18 | *

19 | * 客户端入口拦截器 20 | *

21 | * 22 | * @author fanwenjie 23 | * @since 2020/3/2 21:05 24 | */ 25 | @Slf4j 26 | public class ProxyHandler extends ChannelInboundHandlerAdapter { 27 | 28 | private ITunnelService iTunnelService; 29 | 30 | public ProxyHandler(ITunnelService iTunnelService) { 31 | this.iTunnelService = iTunnelService; 32 | } 33 | 34 | @Override 35 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 36 | ConnectPacket connectPacket = new ConnectPacket(); 37 | connectPacket.setChannelId(ctx.channel().id().asLongText()); 38 | InetSocketAddress address = (InetSocketAddress)ctx.channel().localAddress(); 39 | String hostName = address.getHostName(); 40 | log.info("连接进来。host 为:[{}]", hostName); 41 | if(StringUtils.isNotEmpty(hostName)){ 42 | Tunnel tunnel = iTunnelService.queryTokenByHostName(hostName); 43 | if(tunnel == null){ 44 | log.warn("没有找到域名:[{}],绑定的token", hostName); 45 | return; 46 | } 47 | String token = tunnel.getToken(); 48 | // 获取对应连接的客户端channel 49 | Channel channel = SessionUtil.getChannel(token); 50 | if(channel == null){ 51 | log.warn("host :[{}], 绑定的客户端没有连接成功", hostName); 52 | return; 53 | } 54 | SessionUtil.bindChannel(connectPacket.getChannelId(), ctx.channel()); 55 | channel.writeAndFlush(connectPacket); 56 | } 57 | } 58 | 59 | /** 60 | *

63 | * 64 | * @author fanwenjie 65 | * @since 2020/3/2 21:08 66 | */ 67 | @Override 68 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{ 69 | byte[] data = (byte[]) msg; 70 | InetSocketAddress address = (InetSocketAddress)ctx.channel().localAddress(); 71 | String hostName = address.getHostName(); 72 | if(StringUtils.isNotEmpty(hostName)){ 73 | Tunnel tunnel = iTunnelService.queryTokenByHostName(hostName); 74 | if(tunnel == null){ 75 | log.warn("没有找到域名:[{}],绑定的token", hostName); 76 | return; 77 | } 78 | String token = tunnel.getToken(); 79 | // 获取对应连接的客户端channel 80 | Channel channel = SessionUtil.getChannel(token); 81 | if(channel == null){ 82 | log.warn("host :[{}], 绑定的客户端没有连接成功", hostName); 83 | return; 84 | } 85 | DataPacket dataPacket = new DataPacket(); 86 | dataPacket.setToken(token); 87 | dataPacket.setBytes(data); 88 | dataPacket.setChannelId(ctx.channel().id().asLongText()); 89 | SessionUtil.bindChannel(ctx.channel().id().asLongText(), ctx.channel()); 90 | channel.writeAndFlush(dataPacket); 91 | } 92 | } 93 | 94 | 95 | @Override 96 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 97 | SessionUtil.unBindChannel(ctx.channel().id().asLongText()); 98 | log.info("channelId:{},链接关闭", ctx.channel().id().asLongText()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/service/impl/TunnelServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import org.mysise.natplus.common.exception.CommonCode; 7 | import org.mysise.natplus.common.exception.SqlException; 8 | import org.mysise.natplus.common.utils.DtoUtils; 9 | import org.mysise.natplus.server.common.model.TunnelRequest; 10 | import org.mysise.natplus.server.common.model.TunnelResponse; 11 | import org.mysise.natplus.server.common.model.TunnelSearch; 12 | import org.mysise.natplus.server.entity.Tunnel; 13 | import org.mysise.natplus.server.mapper.TunnelMapper; 14 | import org.mysise.natplus.server.service.ITunnelService; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.time.LocalDateTime; 19 | import java.util.List; 20 | 21 | /** 22 | *

23 | * 隧道/通道 服务实现类 24 | *

25 | * 26 | * @author fanwenjie 27 | * @since 2020-03-07 28 | */ 29 | @Service 30 | public class TunnelServiceImpl extends ServiceImpl implements ITunnelService { 31 | 32 | @Autowired 33 | private TunnelMapper tunnelMapper; 34 | 35 | @Override 36 | public Boolean tokenExist(String token) { 37 | QueryWrapper queryWrapper = new QueryWrapper<>(); 38 | queryWrapper.eq("token", token); 39 | Tunnel tunnel = tunnelMapper.selectOne(queryWrapper); 40 | return tunnel != null; 41 | } 42 | 43 | @Override 44 | public Tunnel queryTokenByHostName(String hostName) { 45 | QueryWrapper queryWrapper = new QueryWrapper<>(); 46 | queryWrapper.eq("host_name", hostName); 47 | return tunnelMapper.selectOne(queryWrapper); 48 | } 49 | 50 | @Override 51 | public Integer save(TunnelRequest request) { 52 | 53 | Tunnel tunnel = queryTokenByHostName(request.getHostName()); 54 | if(tunnel != null){ 55 | throw new SqlException(CommonCode.SQL_INSERT_FAIL, "此域名已存在绑定的隧道信息"); 56 | } 57 | Tunnel entity = DtoUtils.map(request, Tunnel.class, false); 58 | LocalDateTime localDateTime = LocalDateTime.now(); 59 | entity.setCreateTime(localDateTime); 60 | entity.setUpdateTime(localDateTime); 61 | return tunnelMapper.insert(entity); 62 | } 63 | 64 | @Override 65 | public Integer update(TunnelRequest request) { 66 | Tunnel tunnel = queryTokenByHostName(request.getHostName()); 67 | if(tunnel != null){ 68 | throw new SqlException(CommonCode.SQL_INSERT_FAIL, "此域名已存在绑定的隧道信息"); 69 | } 70 | Tunnel entity = tunnelMapper.selectById(request.getId()); 71 | LocalDateTime localDateTime = LocalDateTime.now(); 72 | entity.setHostName(request.getHostName()); 73 | entity.setName(request.getName()); 74 | entity.setType(request.getType()); 75 | entity.setUpdateTime(localDateTime); 76 | return tunnelMapper.updateById(entity); 77 | } 78 | 79 | @Override 80 | public List listTunnel(TunnelSearch request) { 81 | QueryWrapper queryWrapper = new QueryWrapper<>(); 82 | if(StringUtils.isNotEmpty(request.getHostName())){ 83 | queryWrapper.eq("host_name", request.getHostName()); 84 | } 85 | if(StringUtils.isNotEmpty(request.getToken())){ 86 | queryWrapper.eq("token", request.getToken()); 87 | } 88 | if(StringUtils.isNotEmpty(request.getName())){ 89 | queryWrapper.eq("name", request.getName()); 90 | } 91 | if(request.getType() != null){ 92 | queryWrapper.eq("type", request.getType()); 93 | } 94 | List result = tunnelMapper.selectList(queryWrapper); 95 | return DtoUtils.mapList(result, TunnelResponse.class, false); 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /nat-plus-client/src/main/java/org/mysise/natplus/client/ClientApplication.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.client; 2 | 3 | 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.socket.nio.NioSocketChannel; 6 | import io.netty.handler.timeout.IdleStateHandler; 7 | import org.apache.commons.cli.*; 8 | import org.mysise.natplus.client.handler.ClientHandler; 9 | import org.mysise.natplus.client.net.TcpConnection; 10 | import org.mysise.natplus.common.codec.PacketDecoder; 11 | import org.mysise.natplus.common.codec.PacketEncoder; 12 | import org.mysise.natplus.common.codec.Spliter; 13 | 14 | /** 15 | *

16 | * 客户端启动器 17 | *

18 | * 19 | * @author fanwenjie 20 | * @since 2020/3/2 0:36 21 | */ 22 | public class ClientApplication { 23 | 24 | public static void main(String[] args) throws ParseException, InterruptedException { 25 | // args 26 | Options options = new Options(); 27 | options.addOption("help", false, "Help"); 28 | options.addOption("server_addr", true, "nat-plus server address"); 29 | options.addOption("server_port", true, "nat-plus server port"); 30 | options.addOption("token", true, "nat-plus server password"); 31 | options.addOption("proxy_addr", true, "Proxy server address"); 32 | options.addOption("proxy_port", true, "Proxy server port"); 33 | // options.addOption("remote_port", true, "Proxy server remote port"); 34 | 35 | CommandLineParser parser = new DefaultParser(); 36 | CommandLine cmd = parser.parse(options, args); 37 | 38 | if (cmd.hasOption("help")) { 39 | // print help 40 | HelpFormatter formatter = new HelpFormatter(); 41 | formatter.printHelp("options", options); 42 | } else { 43 | 44 | String serverAddress = cmd.getOptionValue("server_addr"); 45 | if (serverAddress == null) { 46 | System.out.println("server_addr cannot be null"); 47 | return; 48 | } 49 | String serverPort = cmd.getOptionValue("server_port"); 50 | if (serverPort == null) { 51 | System.out.println("server_port cannot be null"); 52 | return; 53 | } 54 | String token = cmd.getOptionValue("token"); 55 | if (token == null) { 56 | System.out.println("token cannot be null"); 57 | return; 58 | } 59 | String proxyAddress = cmd.getOptionValue("proxy_addr"); 60 | if (proxyAddress == null) { 61 | System.out.println("proxy_addr cannot be null"); 62 | return; 63 | } 64 | String proxyPort = cmd.getOptionValue("proxy_port"); 65 | if (proxyPort == null) { 66 | System.out.println("proxy_port cannot be null"); 67 | return; 68 | } 69 | // String remotePort = cmd.getOptionValue("remote_port"); 70 | // if (remotePort == null) { 71 | // System.out.println("remote_port cannot be null"); 72 | // return; 73 | // } 74 | 75 | TcpConnection connection = new TcpConnection(); 76 | connection.connect(serverAddress, Integer.parseInt(serverPort), new ChannelInitializer() { 77 | 78 | @Override 79 | public void initChannel(NioSocketChannel nioSocketChannel) { 80 | ClientHandler clientHandler = new ClientHandler(token, 81 | proxyAddress, Integer.parseInt(proxyPort)); 82 | //粘包拆包 83 | nioSocketChannel.pipeline().addLast(new Spliter()); 84 | // 解码 85 | nioSocketChannel.pipeline().addLast(new PacketDecoder()); 86 | //编码 87 | nioSocketChannel.pipeline().addLast(new PacketEncoder()); 88 | // 心跳 89 | nioSocketChannel.pipeline().addLast(new IdleStateHandler(60, 90 | 30, 0)); 91 | //处理器 92 | nioSocketChannel.pipeline().addLast(clientHandler); 93 | 94 | } 95 | }); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /nat-plus-common/src/main/java/org/mysise/natplus/common/codec/PacketCode.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.common.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufAllocator; 5 | import org.mysise.natplus.common.protocol.*; 6 | import org.mysise.natplus.common.serializer.Serializer; 7 | import org.mysise.natplus.common.serializer.impl.JSONSerializer; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | *

14 | * 消息编解码 15 | *

16 | * 17 | * @author fanwenjie 18 | * @since 2020/3/1 10:49 19 | */ 20 | public class PacketCode { 21 | 22 | /** 23 | * 魔数 写死 24 | */ 25 | public static final int MAGIC_NUMBER = 0x12345678; 26 | public static final PacketCode INSTANCE = new PacketCode(); 27 | private static final Map> PACKET_TYPE_MAP; 28 | private static final Map SERIALIZER_MAP; 29 | 30 | static { 31 | PACKET_TYPE_MAP = new HashMap<>(); 32 | PACKET_TYPE_MAP.put(Command.REGISTER, RegisterPacket.class); 33 | PACKET_TYPE_MAP.put(Command.DATA, DataPacket.class); 34 | PACKET_TYPE_MAP.put(Command.CONNECT, ConnectPacket.class); 35 | 36 | SERIALIZER_MAP = new HashMap<>(); 37 | Serializer serializer = new JSONSerializer(); 38 | SERIALIZER_MAP.put(serializer.getSerializerAlogrithm(), serializer); 39 | } 40 | 41 | 42 | 43 | public void encode(ByteBuf byteBuf, Packet packet) { 44 | // 1. 创建 ByteBuf 对象 45 | // ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer(); 46 | // // 2. 序列化 java 对象 47 | // byte[] bytes = Serializer.DEFAULT.serialize(packet); 48 | // 49 | // // 3. 实际编码过程 50 | // byteBuf.writeInt(MAGIC_NUMBER); 51 | // byteBuf.writeByte(packet.getVersion()); 52 | // byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlogrithm()); 53 | // byteBuf.writeByte(packet.getCommand()); 54 | // byteBuf.writeInt(bytes.length); 55 | // byteBuf.writeBytes(bytes); 56 | // 57 | // return byteBuf; 58 | 59 | // 1. 序列化 java 对象 60 | byte[] bytes = Serializer.DEFAULT.serialize(packet); 61 | 62 | // 2. 实际编码过程 63 | byteBuf.writeInt(MAGIC_NUMBER); 64 | byteBuf.writeByte(packet.getVersion()); 65 | byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlogrithm()); 66 | byteBuf.writeByte(packet.getCommand()); 67 | byteBuf.writeInt(bytes.length); 68 | byteBuf.writeBytes(bytes); 69 | } 70 | 71 | public ByteBuf encode(Packet packet) { 72 | //1. 创建 ByteBuf 对象 73 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer(); 74 | // 2. 序列化 java 对象 75 | byte[] bytes = Serializer.DEFAULT.serialize(packet); 76 | // 3. 实际编码过程 77 | byteBuf.writeInt(MAGIC_NUMBER); 78 | byteBuf.writeByte(packet.getVersion()); 79 | byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlogrithm()); 80 | byteBuf.writeByte(packet.getCommand()); 81 | byteBuf.writeInt(bytes.length); 82 | byteBuf.writeBytes(bytes); 83 | return byteBuf; 84 | } 85 | 86 | 87 | public Packet decode(ByteBuf byteBuf) { 88 | // 跳过 magic number 89 | byteBuf.skipBytes(4); 90 | 91 | // 跳过版本号 92 | byteBuf.skipBytes(1); 93 | 94 | // 序列化算法 95 | byte serializeAlgorithm = byteBuf.readByte(); 96 | 97 | // 指令 98 | byte command = byteBuf.readByte(); 99 | 100 | // 数据包长度 101 | int length = byteBuf.readInt(); 102 | 103 | byte[] bytes = new byte[length]; 104 | byteBuf.readBytes(bytes); 105 | 106 | Class requestType = getRequestType(command); 107 | Serializer serializer = getSerializer(serializeAlgorithm); 108 | 109 | if (requestType != null && serializer != null) { 110 | return serializer.deserialize(requestType, bytes); 111 | } 112 | 113 | return null; 114 | } 115 | 116 | private Serializer getSerializer(byte serializeAlgorithm) { 117 | 118 | return SERIALIZER_MAP.get(serializeAlgorithm); 119 | } 120 | 121 | private Class getRequestType(byte command) { 122 | 123 | return PACKET_TYPE_MAP.get(command); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /nat-plus-client/src/main/java/org/mysise/natplus/client/handler/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.client.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | import io.netty.handler.codec.bytes.ByteArrayDecoder; 10 | import io.netty.handler.codec.bytes.ByteArrayEncoder; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Data; 13 | import lombok.EqualsAndHashCode; 14 | import lombok.NoArgsConstructor; 15 | import org.mysise.natplus.client.net.TcpConnection; 16 | import org.mysise.natplus.common.protocol.ConnectPacket; 17 | import org.mysise.natplus.common.protocol.DataPacket; 18 | import org.mysise.natplus.common.protocol.RegisterPacket; 19 | 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | /** 23 | *

24 | * 客户端连接器 25 | *

26 | * 27 | * @author fanwenjie 28 | * @since 2020/3/2 0:45 29 | */ 30 | @EqualsAndHashCode(callSuper = true) 31 | @Data 32 | @NoArgsConstructor 33 | @AllArgsConstructor 34 | public class ClientHandler extends ChannelInboundHandlerAdapter { 35 | 36 | protected ChannelHandlerContext ctx; 37 | 38 | private ConcurrentHashMap channelHandlerMap = new ConcurrentHashMap<>(); 39 | 40 | /** 41 | * 令牌 42 | */ 43 | private String token; 44 | 45 | /** 46 | * 被代理的本地服务器ip地址 47 | */ 48 | private String proxyAddress; 49 | 50 | /** 51 | * 被代理程序端口 52 | */ 53 | private int proxyPort; 54 | 55 | public ClientHandler(String token, String proxyAddress, int proxyPort) { 56 | this.token = token; 57 | this.proxyAddress = proxyAddress; 58 | this.proxyPort = proxyPort; 59 | } 60 | 61 | /** 62 | *

63 | * 注册消息到服务端 64 | *

65 | * 66 | * @author fanwenjie 67 | * @since 2020/3/2 1:00 68 | */ 69 | @Override 70 | public void channelActive(ChannelHandlerContext ctx){ 71 | RegisterPacket registerPacket = new RegisterPacket(); 72 | registerPacket.setToken(token); 73 | ctx.writeAndFlush(registerPacket); 74 | this.ctx = ctx; 75 | } 76 | 77 | /** 78 | *

79 | * 数据响应 80 | *

81 | * 82 | * @author fanwenjie 83 | * @since 2020/3/2 0:31 84 | */ 85 | @Override 86 | public void channelRead(ChannelHandlerContext ctx, Object msg){ 87 | if (msg instanceof RegisterPacket){ 88 | RegisterPacket registerPacket = (RegisterPacket) msg; 89 | System.out.println(registerPacket.getMessage()); 90 | if (!registerPacket.getSuccess()){ 91 | ctx.close(); 92 | } 93 | }else if (msg instanceof ConnectPacket){ 94 | ConnectPacket connectPacket = (ConnectPacket) msg; 95 | TcpConnection connection = new TcpConnection(); 96 | try{ 97 | ClientHandler clientHandler = this; 98 | connection.connect(proxyAddress, proxyPort, new ChannelInitializer() { 99 | @Override 100 | protected void initChannel(NioSocketChannel channel){ 101 | LocalProxyHandler localProxyHandler = new LocalProxyHandler(clientHandler, connectPacket.getChannelId()); 102 | channel.pipeline().addLast(new ByteArrayDecoder(), new ByteArrayEncoder(), 103 | localProxyHandler); 104 | channelHandlerMap.put(connectPacket.getChannelId(),channel); 105 | } 106 | }); 107 | }catch (Exception e){ 108 | System.out.println("本地代理服务器连接失败"); 109 | channelHandlerMap.remove(connectPacket.getChannelId()); 110 | } 111 | }if (msg instanceof DataPacket){ 112 | DataPacket dataPacket = (DataPacket) msg; 113 | Channel channel = channelHandlerMap.get(dataPacket.getChannelId()); 114 | channel.writeAndFlush(dataPacket.getBytes()); 115 | // System.out.println(JSON.toJSONString(dataPacket)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.mysise 8 | nat-plus 9 | pom 10 | 1.0.0 11 | 12 | nat-plus-client 13 | nat-plus-server 14 | nat-plus-common 15 | 16 | 17 | 18 | 1.8 19 | 8.0.16 20 | 1.1.17 21 | 3.1.0 22 | 2.5.12 23 | 2.2.4.RELEASE 24 | 2.3.29 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | true 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-dependencies 55 | ${spring-boot-version} 56 | pom 57 | import 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-web 63 | ${spring-boot-starter-parent} 64 | 65 | 66 | 67 | 68 | mysql 69 | mysql-connector-java 70 | ${mysql.version} 71 | 72 | 73 | 74 | com.alibaba 75 | druid-spring-boot-starter 76 | ${druid.starter.version} 77 | 78 | 79 | 80 | com.baomidou 81 | mybatis-plus-boot-starter 82 | ${mybatis.plus.starter} 83 | 84 | 85 | 86 | 87 | com.baomidou 88 | mybatis-plus-generator 89 | ${mybatis.plus.starter} 90 | 91 | 92 | 93 | 94 | org.freemarker 95 | freemarker 96 | ${freemarker-version} 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-compiler-plugin 107 | 3.3 108 | 109 | 1.8 110 | 1.8 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /nat-plus-server/src/main/java/org/mysise/natplus/server/common/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | package org.mysise.natplus.server.common; 2 | 3 | import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; 4 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 5 | import com.baomidou.mybatisplus.generator.AutoGenerator; 6 | import com.baomidou.mybatisplus.generator.InjectionConfig; 7 | import com.baomidou.mybatisplus.generator.config.*; 8 | import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; 9 | import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Scanner; 14 | 15 | /** 16 | *

17 | * 通用代码生成器 18 | *

19 | * @author fanwenjie@cvte.com 20 | * @since oversea v1.0.0 2019/11/13 9:45 上午 21 | */ 22 | public class CodeGenerator { 23 | 24 | /** 25 | *

26 | * 读取控制台内容 27 | *

28 | */ 29 | public static String scanner(String tip) { 30 | Scanner scanner = new Scanner(System.in); 31 | System.out.println("请输入" + tip + ":"); 32 | if (scanner.hasNext()) { 33 | String ipt = scanner.next(); 34 | if (StringUtils.isNotEmpty(ipt)) { 35 | return ipt; 36 | } 37 | } 38 | throw new MybatisPlusException("请输入正确的" + tip + "!"); 39 | } 40 | 41 | public static void main(String[] args) { 42 | // 代码生成器 43 | AutoGenerator mpg = new AutoGenerator(); 44 | 45 | // 全局配置 46 | GlobalConfig gc = new GlobalConfig(); 47 | gc.setBaseResultMap(true); 48 | gc.setBaseColumnList(true); 49 | String projectPath = System.getProperty("user.dir"); 50 | // gc.setOutputDir(projectPath + "/src/main/java"); 51 | gc.setOutputDir("C:\\Users\\memory\\Desktop\\project\\nat-plus\\nat-plus-server\\src\\main\\java"); 52 | gc.setAuthor("fanwenjie"); 53 | gc.setOpen(false); 54 | // gc.setSwagger2(true); 实体属性 Swagger2 注解 55 | mpg.setGlobalConfig(gc); 56 | 57 | // 数据源配置 58 | DataSourceConfig dsc = new DataSourceConfig(); 59 | dsc.setUrl("jdbc:mysql://localhost:3306/nat_plus?characterEncoding=utf8&useSSL=false"); 60 | // dsc.setSchemaName("public"); 61 | dsc.setDriverName("com.mysql.jdbc.Driver"); 62 | dsc.setUsername("root"); 63 | dsc.setPassword("123456"); 64 | mpg.setDataSource(dsc); 65 | mpg.setTemplateEngine(new FreemarkerTemplateEngine()); 66 | // 包配置 67 | PackageConfig pc = new PackageConfig(); 68 | // pc.setModuleName(scanner("模块名")); 69 | pc.setParent("org.mysise.natplus.server"); 70 | mpg.setPackageInfo(pc); 71 | 72 | // 自定义配置 73 | InjectionConfig cfg = new InjectionConfig() { 74 | @Override 75 | public void initMap() { 76 | // to do nothing 77 | } 78 | }; 79 | 80 | // 如果模板引擎是 freemarker 81 | String templatePath = "/templates/mapper.xml.ftl"; 82 | // 如果模板引擎是 velocity 83 | // String templatePath = "/templates/mapper.xml.vm"; 84 | 85 | // 自定义输出配置 86 | List focList = new ArrayList<>(); 87 | // 自定义配置会被优先输出 88 | // focList.add(new FileOutConfig(templatePath) { 89 | // @Override 90 | // public String outputFile(com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) { 91 | // // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! 92 | // return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() 93 | // + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; 94 | // } 95 | // }); 96 | cfg.setFileOutConfigList(focList); 97 | mpg.setCfg(cfg); 98 | 99 | // 配置模板 100 | TemplateConfig templateConfig = new TemplateConfig(); 101 | 102 | // 配置自定义输出模板 103 | //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 104 | // templateConfig.setEntity("templates/entity2.java"); 105 | // templateConfig.setService(); 106 | // templateConfig.setController(); 107 | 108 | templateConfig.setXml(null); 109 | mpg.setTemplate(templateConfig); 110 | 111 | // 策略配置 112 | StrategyConfig strategy = new StrategyConfig(); 113 | strategy.setNaming(NamingStrategy.underline_to_camel); 114 | strategy.setColumnNaming(NamingStrategy.underline_to_camel); 115 | strategy.setSuperEntityClass("org.mysise.natplus.server.entity.BaseEntity"); 116 | strategy.setEntityLombokModel(true); 117 | strategy.setRestControllerStyle(true); 118 | // 公共父类 119 | // strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController"); 120 | // 写于父类中的公共字段 121 | strategy.setSuperEntityColumns("id","create_time","update_time"); 122 | strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); 123 | strategy.setControllerMappingHyphenStyle(true); 124 | strategy.setTablePrefix(pc.getModuleName() + "_"); 125 | mpg.setStrategy(strategy); 126 | mpg.setTemplateEngine(new FreemarkerTemplateEngine()); 127 | mpg.execute(); 128 | } 129 | } 130 | --------------------------------------------------------------------------------