52 Star 193 Fork 53

HServer / HServer

Create your Gitee Account
Explore and code with more than 6 million developers,Free private repositories !:)
Sign up
Clone or download
HServer文档.md 41.85 KB
Copy Edit Web IDE Raw Blame History
黑小马 authored 2021-07-27 17:45 . 发布2.9.74

HServer文档

序言

HServer是什么

HServer是一个简单高效的web库基于Netty开发.它不遵循servlet规范.可以理解为mini版本的springboot的版本.使用它开发可以在初期获得很大的QPS。会随着业务变化QPS慢慢降低.它给我们的起点很高,剩下就是我们自己高效运用即可. 如果你正在使用该框架建议加入QQ群,我们可以更好的交流 QQ交流群:1065301527

Hserver的理念

极简、高性能、分布式

极简 代码只有200多KB 更多的案例会在gitee.com/Hserver 的组下给出案例源码.

高性能 使用Netty网络库作为核心,比起传统的web容器性能高数十倍.

分布式 支持RPC模式,可以实现分布式调用.

使用该框架的公司

深圳市快读科技,深圳市聚美良品科技,广州家庭医生在线,上海互软集团,深圳市巨和网络

原理与流程

原理

注解认识

根据上面的例子,大家应该理解是非常容易的,和springboot很相似。接下我们了解下注解这里,注解只是做简单描述,具体使用在后面的章节会演示出来

注解 描述信息
@Bean 将当前类加入IOC中,类似spring的@Component注解,可以名字放入ioc 如@Bean("Test")
@Autowired 将ioc里的某个对象注入给某个字段,和spring用法类似,可以名字注入ioc 如@Autowired("Test")
@RequestMapping 这个和springmvc的注解很相似
@GET 请求类型注解类似@GetMapping
@POST 请求类型注解
@PUT 请求类型注解
@HEAD 请求类型注解
@PATCH 请求类型注解
@DELETE 请求类型注解
@OPTIONS 请求类型注解
@CONNECT 请求类型注解
@TRACE 请求类型注解
@Order 排序注解 值越小,优先级越高 (FilterAdapter, GlobalException, InitRunner,ReInitRunner, ResponseAdapter,ProtocolDispatcherAdapter,RpcAdapter,ServerCloseAdapter) 这些子类支持排序
@Configuration 配置注解,这个和springboot有相似之处(这个类中可以注入 @NacosClass,@NacosValue,@Value,@ConfigurationProperties这些注解产生的对象)
@ConfigurationProperties 配置类,和springboot相似 将Properties转为对象放入IOC
@Controller 标记类为控制器 @Controller 参数可以指定一个URL 和 一个名字
@Hook AOP操作使用
@RequiresPermissions 权限注解
@RequiresRoles 角色注解
@Resource RPC对象的注入使用.可以按名字注入
@RpcService 标记一个Service是一个RPC服务,可以给一个名字
@Sign 作用在控制器方法上.可以根据他来实现sign检查当然你可以用拦截器自己处理
@Task 定时器使用,具体看例子
@Track 链路跟踪注解,如果你想检查某些方法的耗时或者其他监控,可以用这个注解,具体看下面的介绍
@Value 用来把Properties字段注入到类的字段里
@WebSocket websocket注解,具体看下面的介绍
@QueueListener 标记一个类为队列处理类
@QueueHandler 标记一个方法为队列处理方法
@AssertFalse 字段为必须为false
@AssertTrue 字段为必须为true
@Length 字段CharSequence 类型的长度必须是 length 长
@Max 字段值必须大于这个值,number
@Min 字段值必须小于这个值,number
@NotBlank 字段不能为null同时不是 ""
@NotEmpty CharSequence 集合 map 数组 不是null 长度或者size 大于0
@NotNull 字段不能为Null
@Null 字段必须为Null
@Pattern 字段CharSequence 必须满足这个正则
@Size 字段 CharSequence 集合 map 数组必须在这范围内
@ApiImplicitParams API生成标记
@ApiImplicitParam API生成标记

常见用例

//以下注解基本模拟Spring的功能

	//@Bean
	//将Bean对象加入IOC容器中比如
    //默认按类型加入IOC容器
    @Bean
    class TestService{}
    //指定名字加入容器,装配的时候就只能通过名字装配了
    @Bean("testService1")
    class Test{}
    
	//@Autowired
	//自动装配注解
    //按类型注入
    @Autowired
    private TestService testService;
    //按Bean名字注入
    @Autowired("testServer1")
    private TestService testService;
    
	//@Controller
	//控制器注解,将控制器加入IOC容器中,类似Spring mvc
	//注解在类上面直接加上即可比如
    //Index控制器
    @Controller
    class IndexController{}
    
	//@GET,@POST,@RequestMapping
	//方法注解,在@Controller注解类类中使用,标注一个方法为GET或者POST方法,例如
    @GET("/index")
    public void index(){}  
    @POST("/index")
    public void index(){}
    
    //url规则匹配
    @GET("/url1/{url}")
    public String url(HttpRequest httpRequest){
        String url = httpRequest.query("url");
        System.out.println(url);
        return url;
    }

    @GET("/url/{url}")
    public String url(String url){
        return "匹配到的URL:"+url;
    }

    @POST("/a/{url}/bb")
    public String ab(String url){
        return "匹配到的URL:"+url;
    } 
     
    @RequestMapping(value = "/PUT", method = RequestMethod.PUT)
    public JsonResult PUT() {
      return JsonResult.ok();
    }
    @RequestMapping(value = "/get_post", method = {RequestMethod.POST,RequestMethod.GET})
    public JsonResult get_post() {
      return JsonResult.ok();
    }
    
    //全类型
    @RequestMapping(value = "/all")
    public JsonResult all() {
      return JsonResult.ok();
    }
    
    
  
  	//拦截器注解,标注一个类为拦截器,和JavaEE的Filter类似
      @Bean
      public class MyFilter1 implements FilterAdapter {}
      //需要实现FilterAdapter接口
      
  	//@Hook
 	// hook注解就是Aop
      @Hook(value = Test.class)
      public class HookTest implements HookAdapter {}
      //value表示aop的类,method要hook的方法,必须实现HookAdapter


	//@Task
 	//定时任务
    @Task(name = "测试定时任务Cron", time ="*/5 * * * * ?")
    //标记在方法上,同时该类需要被@Bean 标记
    @Task(name = "测试定时任务1", time ="2000")
    public void timerTask() {}


	//@WebSocket
	//实现websocket通信
    @WebSocket("/ws")
    public class WebSocketTest implements WebSocketHandler {}
    //这样就可以完成基本的通信了

    
	//@Configuration
	//自定配置注解,需要配合@Bean注解一起使用,最后会把方法里面的返回的对象
	//存储到IOC容器中,同时可以通过Autowired注解注入
    @Configuration
    public class DataConfig {
    
        //自定义名字(用例:比如多数据源注入)
        @Bean("createUser")
        public User createUser(){
            User user = new User();
            user.setAge(999);
            user.setName("我是配置类自定义名字的数据");
            user.setSex("未知");
            return user;
        }
    
       //按类型存储 
        @Bean
        public User createUser1(){
            User user = new User();
            user.setAge(999);
            user.setName("我是配置类的默认数据");
            user.setSex("未知");
            return user;
        }
    
    }


	//@RpcService
	//标注一个Bean对象是一个rpc服务,也可以分配一个名字
    @Bean
    @RpcService
    public class RpcServiceTest {
        public String test(String name){
            return name+"我是RPC";
        }
    }  


	//@Resource
	//注入一个Rpc服务,也可以通过名字注入。详情,请看文档介绍   
    @Resource
    private RpcServiceTest rpcServiceTest;



    @Sign("MD5")
    @RequiresRoles("角色")
    @RequiresPermissions(value = {"/权限1","/权限2"}, logical=Logical.OR)
    //该注解用于标注控制器里面的方法,方便自己实现sign签名算法,
    //角色检查,权限检查,实现token等,详情下面的对应接口。

快速开始

1.建立一个maven项目,导入依赖

<dependency>
    <groupId>top.hserver</groupId>
    <artifactId>HServer</artifactId>
    <version>最新版</version>
</dependency>

2.建立一个java包,如 com.test

3.建立一个主函数

@HServerBoot
public class WebApp {
    public static void main(String[] args) {
        HServerApplication.run(WebApp.class,8888,args);
    }
}

4.建立一个控制器

@Controller
public class HelloController {

    @GET("/test1")
    public JsonResult test() {
        return JsonResult.ok();
    }
    
    @POST("/test2")
    public JsonResult b(HttpRequest request) {
        return JsonResult.ok().put("data",request.getRequestParams());
    }
    
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public JsonResult get() {
        return JsonResult.ok();
    }

    @RequestMapping(value = "/post", method = RequestMethod.POST)
    public JsonResult post(HttpRequest httpRequest) {
        return JsonResult.ok().put("data",httpRequest.getRequestParams());
    }
    
    /**
     * 模板测试
     * @param httpResponse
     */
    @GET("/template")
    public void template(HttpResponse httpResponse) {
        User user = new User();
        user.setAge(20);
        user.setName("xx");
        user.setSex("男");
        Map<String,Object> obj=new HashMap<>();
        obj.put("user",user);
//        httpResponse.sendTemplate("/admin/user/list.ftl", obj);
        httpResponse.sendTemplate("a.ftl", obj);
    }
}

5.运行主函数,访问8888端口即可

文件上传下载

    /**
     * 文件下载
     *
     * @param response
     * @return
     */
    @GET("/downFile")
    public void downFile(HttpRequest request, HttpResponse response) {
        response.setDownloadFile(new File("D:\\Java\\HServer\\README.md"));
    }

    @GET("/downInputStream")
    public void downInputStream(HttpRequest request, HttpResponse response) throws Exception {
        File file = new File("D:\\Java\\HServer\\README.md");
        InputStream fileInputStream = new FileInputStream(file);
        response.setDownloadFile(fileInputStream, "README.md");
    }

    /**
     * 上传文件测试
     *
     * @param request
     * @return
     */
    @POST("/file")
    public Map file(HttpRequest request) {

        Map<String, PartFile> fileItems = request.getMultipartFile();
        fileItems.forEach((k, v) -> {
            System.out.println(k);
            System.out.println(v);
            byte[] data = v.getData();
            System.out.println(data);
        });
        Map<String, Object> res = new HashMap<>();
        res.put("code", 200);
        res.put("res", request.getRequestParams());
        res.put("msg", test1q.show("xx"));
        return res;
    }

全局request,response获取

       只能在和控制器同级别的线程才能获取。
       Webkit webKit = HServerContextHolder.getWebKit();
            if (webKit != null) {
                return webKit.httpRequest.getRequestId();
            }

Hook操作

提供hook注解,它只能Hook在ioc中存在的bean对象. hook功能除了hook指定的类所有方法,还能hook注解,只要包含这个注解的类都会被hook.

import top.hserver.core.interfaces.HookAdapter;
import top.hserver.core.ioc.annotation.Autowired;
import top.hserver.core.ioc.annotation.Hook;
import test1.service.HelloService;
import test1.service.Test;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;

/**
 * hook 指定的Test类,在调用Test的所有方法 都会进入before 和 after,可以通过Method 选择处理或者不处理.
 */
@Slf4j
@Hook(Test.class)
public class HookTest implements HookAdapter {

    @Autowired
    private HelloService helloService;

    @Override
    public void before(Class clazz, Method method, Object[] objects) {
        log.debug("aop.-前置拦截111111111111111111111");
    }

    @Override
    public Object after(Class clazz, Method method,Object object) {
        return object + "aop-后置拦截1111111111111111"+helloService.sayHello();
    }

    @Override
    public void throwable(Class clazz, Method method, Throwable throwable) {
        System.out.println(throwable);
    }
}
import lombok.extern.slf4j.Slf4j;
import test1.log.Log;
import test1.service.HelloService;
import test1.service.Test;
import top.hserver.core.interfaces.HookAdapter;
import top.hserver.core.ioc.annotation.*;
import java.lang.reflect.Method;
/**
 * hook 指定用了@Log的类,只要用的@Log的类都会被Hook住,或者作用在方法得注解也会生效。这个功能主要用途在做一些 自定义注解时比较常用.比如做一个@log 日志打印注解 或者 耗时统计注解.
 */
@Slf4j
@Hook(value = Log.class)
public class HookTest2 implements HookAdapter {

    @Autowired
    private HelloService helloService;

    @Override
    public void before(Class clazz, Method method, Object[] objects) {
        log.debug("aop.-前置拦截 {}",method.getName());
    }

    @Override
    public Object after(Class clazz, Method method,Object object) {
        log.debug("aop.-后置拦截 {}",object);
        return object;
    }

    @Override
    public void throwable(Class clazz, Method method, Throwable throwable) {
        System.out.println(throwable);

    }
}

上面测试例子都是HServer Test包里的Test1文件中,有兴趣的可以去运行体验哈

Filter拦截器

拦截器的使用主要是用在跨域等操作,或者其他拦截,

接口可以实现多个 ,但是得只要有输出将会中断链式调用.

/**
 * @author hxm
 */
@Bean
public class GlobalPermissionFilter implements FilterAdapter {

    @Autowired
    private TokenService tokenService;

    @Override
    public void doFilter(Webkit webkit) throws Exception {
        webkit.httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        webkit.httpResponse.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE");
        webkit.httpResponse.setHeader("Access-Control-Allow-Credentials", " true");
        webkit.httpResponse.setHeader("Access-Control-Allow-Headers", " Content-Type,Content-Length,Accept-Encoding,Accept,X-Requested-with, Origin,Access-Token,X-Access-Token,x-access-token,miniType,mini-type");
        if (webkit.httpRequest.getRequestType().equals(HttpMethod.OPTIONS)) {
            webkit.httpResponse.sendHtml("");
        }
    }
}

响应拦截器

import io.netty.handler.codec.http.FullHttpResponse;
import top.hserver.core.interfaces.ResponseAdapter;
import top.hserver.core.ioc.annotation.Bean;

@Bean
public class MyResponse implements ResponseAdapter {
    @Override
    public String result(String response) {
        //可以拿到 String数据 ,可以做一些替换操作 ,比如 国际化之类的。
        // 文件操作不会进入这里
        System.out.println(response);
        return response;
    }

    @Override
    public FullHttpResponse response(FullHttpResponse response) {
        //Netty 的对象 最后一次经过这里 就会write出去
        System.out.println(response);
        return response;
    }
}

定时器

@Bean
public class TaskTest {
    
    @Autowired
    private TestService testService;

    private boolean flag = true;

    public void dynamicAddTimer() {
        System.out.println("动态添加定时任务");
        TaskManager.addTask("测试任务2", "2000", TestTask.class,"666");
    }
    
    
    @Task(name = "测试定时任务1", time ="*/5 * * * * ?")
    public void timerTask() {
        System.out.println("测试定时任务,注入的对象调用结果:" + testService.testa());
        if (flag) {
            dynamicAddTimer();
            flag = false;
        }
    }

    @Task(name = "测试定时任务2", time = "2000")
    public void removeTask() {
        //干掉方法注解版本
        boolean task1 = TaskManager.removeTask("测试定时任务1");
        //干掉动态添加的
        boolean task2 = TaskManager.removeTask("测试任务2");
        //干掉自己
        boolean task3 = TaskManager.removeTask("测试定时任务2");
        //结果
        System.out.println("任务已经被干掉了 tash1=" + task1 + ",task2=" + task2 + ",task3=" + task3);
    }

}

//动态添加定时任务的实现类必须要实现一个TaskJob,样才能被TaskManager管理
//添加任务 TaskManager.addTask("测试任务2", "2000", TestTask.class,"666");
//删除任务  boolean is_success = TaskManager.removeTask("测试任务2");
public class TestTask implements TaskJob {

    @Override
    public void exec(Object... args) {
        String args_ = "";
        for (Object arg : args) {
            args_ += arg.toString();
        }
        System.out.println("测试定时器动态添加任务,参数是:" + args_);
    }
}

WebSocket

需要被@WebSocket标注同时给一个连接地址,最后实现WebSocketHandler接口, Ws类定义了简单的发送方法,如果有其他的业务操作,可以获取ChannelHandlerContext,进行操作



@WebSocket("/ws")
public class WebSocketTest implements WebSocketHandler {

    @Autowired
    private TestService testService;

    @Override
    public void onConnect(Ws ws) {
        System.out.println("连接成功,分配的UID:" + ws.getUid());
    }

    @Override
    public void onMessage(Ws ws) {
        ws.send("666" + testService.testa() + ws.getUid());
        System.out.println("收到的消息,"+ws.getMessage()+",UID:" + ws.getUid());
    }

    @Override
    public void disConnect(Ws ws) {
        System.out.println("断开连接,UID:" + ws.getUid());
    }
}

ws类提供了Netty原始的HttpRequest对象,你可以自由处理,同时提供了 query 函数,帮助你快速查找到websocket URL的参数

Mqtt

请继承MqttAdapter类 并用@Bean 标记 其他操作可以重写父类的一些方法


@Bean
public class Mqtt extends MqttAdapter {

    @Override
    public void message(MqttMessageType mqttMessageType, MqttMessage mqttMessage, ChannelHandlerContext channelHandlerContext) {
        System.out.println(mqttMessageType);
    }
}

全局异常处理

类必须要被@Bean注解,同时实现GlobalException接口.

异常接口可以实现多个 ,但是得只要有输出将会中断链式调用.


@Bean
public class WebException implements GlobalException {
    @Override
    public void handler(Throwable throwable, int httpStatusCode, String errorDescription, Webkit webkit) {
        HttpRequest httpRequest = webkit.httpRequest;
        StringBuilder error = new StringBuilder();
        error.append("全局异常处理")
                .append("url")
                .append(httpRequest.getUri())
                .append("错误信息:")
                .append(throwable.getMessage())
                .append("错误描述:")
                .append(errorDescription);
        webkit.httpResponse.sendStatusCode(HttpResponseStatus.BAD_GATEWAY);
        webkit.httpResponse.sendText(error.toString());
    }
}

服务器启动完成执行的方法

类必须要被@Bean注解,同时实现InitRunner接口,

 @Bean
 public class RunInit implements InitRunner {
 
     @Autowired
     private User user;
 
     @Override
     public void init(String[] args) {
         System.out.println("初始化方法:注入的User对象的名字是-->"+user.getName());
     }
 }

服务器IOC重新初始化执行的方法

类必须要被@Bean注解,同时实现ReInitRunner接口,

 @Bean
 public class ReInit implements ReInitRunner {
 
     @Override
     public void reInit() {
         System.out.println("重新初始化之前,这个方法被执行,可以关闭一些线程或者或者叫资源,比如Redisson的相关内容");
     }
 }

服务器关闭回调执行的方法

类必须要被@Bean注解,同时实现ServerCloseAdapter接口,

@Bean
public class Close implements ServerCloseAdapter {
    @Override
    public void close() {
        System.out.println("服务开始关闭了,可以提前关闭资源或者有些没用处理完的,处理下");
        System.out.println("延时5秒关闭");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("关闭了");
    }
}

鉴权认证相关操作

@RequiresPermissions

@RequiresRoles

@Sign

请使用相关注解对控制器的方法做标记,这样在执行到被注解标记的方法就会执行下面的相关方法 List routerPermissions = PermissionAdapter.getRouterPermissions(); 通过上面的代码可以获取到所有标记的注解,他可以干嘛? 同步后台数据库里面的权限,后台管理面里面可以动态给角色分配权限。 自己做一个下拉选择列表,创建角色分配权限时,多选即可。

@Bean
public class TestPermission implements PermissionAdapter {

    @Override
    public void requiresPermissions(RequiresPermissions requiresPermissions, Webkit webkit) {
        //这里你可以实现一套自己的权限检查算法逻辑,判断,
        //如果满足权限,不用其他操作,如果不满足权限,那么你可以通过,Webkit里面的方法直接输出相关内容
        //或者自定义一个异常类,在全局异常类做相关操作
        System.out.println(requiresPermissions.value()[0]);
    }

    @Override
    public void requiresRoles(RequiresRoles requiresRoles, Webkit webkit) {
        //这里你可以实现一套自己的角色检查算法逻辑,判断,
        //其他逻辑同上
        System.out.println(requiresRoles.value()[0]);
    }

    @Override
    public void sign(Sign sign, Webkit webkit) {
       //这里你可以实现一套自己的接口签名算法检查算法逻辑,判断,
       //其他逻辑同上
       Map<String, String> requestParams = webkit.httpRequest.getRequestParams();
       String sign1 = webkit.httpRequest.getHeader("sign");
       System.out.println(sign.value());
    }
}

RPC调用

hserver 提供了RpcAdapter类,详细看代码里的实现 支持自定义注册中心完成RPC 目前提供了两种模式 第一种默认模式不需要注册中心 第二种模式是需要Nacos注册中心. 编码过程中没有什么差异 主要差异是在配置上. 文档我怕讲不清楚,详情看 test-rpc-*

ApiDoc生成功能

关于这个api文档生成目前只是一个简洁版,在未来日子里相信会变得更好

第一步

@Controller(value = "/v1/Api2", name = "Api接口2")
class ApiController{}
//value值会自动补全类中方法的URL,name值,在文档中有名字定义的作用,如果这个名字不定义,那么会采用控制器的全路径。

第二步

在需要生成注解的方法上添加这个注解这个注解类似swagger的注解

  @GET("/get")
  @ApiImplicitParams(
    value = {
      @ApiImplicitParam(name = "name", value = "名字", required = true, dataType = DataType.String),
      @ApiImplicitParam(name = "sex", value = "性别", required = true, dataType = DataType.Integer),
      @ApiImplicitParam(name = "age", value = "年龄", required = true, dataType = DataType.Integer),
    },
    note = "这是一个Api的Get方法",
    name = "api获取GET"
  )
  public JsonResult get(User user) {
    return JsonResult.ok().put("data", user);
  }

第三步

HServer提供了一个叫ApiDoc的类,对他进行实例化,就可以获取到生成文档的对象,你可以进行自己的文档生成定制,

或者使用HServer提供 的简洁版本的文档模板 hserver_doc.ftl 需要将依赖里的这个文件copy到你的模板里面.

下面就是例子,ApiDoc的构造器可以传入class类型,或者传入String类型,主要目的是进行扫包,可以直接传入包名,或者传入class,然后获取包名

  //官方模板输出
  @GET("/api")
  public void getApiData(HttpResponse httpResponse) {
    //ApiDoc apiDoc = new ApiDoc("top.test");
    ApiDoc apiDoc = new ApiDoc(TestWebApp.class);
    try {
      List<ApiResult> apiData = apiDoc.getApiData();
      HashMap<String,Object> stringObjectHashMap=new HashMap<>();
      stringObjectHashMap.put("data",apiData);
      httpResponse.sendTemplate("hserver_doc.ftl",stringObjectHashMap);
    }catch (Exception e){
      httpResponse.sendJson(JsonResult.error());
    }
  }
  
  //输出json,或者自己自定名字  
  @GET("/apiJson")
  public JsonResult getApiDataa() {
    ApiDoc apiDoc = new ApiDoc("top.test");
    try {
      List<ApiResult> apiData = apiDoc.getApiData();
      return JsonResult.ok().put("data",apiData);
    }catch (Exception e){
      return JsonResult.error();
    }
  }

AB测试

消息队列的使用

队列核心技术使用的是disruptor

生产者

  @GET("/event")
    public JsonResult event() {
    	//队列名字,方法参数
        HServerQueue.sendQueue("Queue", "666");
        return JsonResult.ok();
    }


    @GET("/eventInfo")
    public JsonResult eventInfo() {
        QueueInfo queueInfo = HServerQueue.queueInfo("Queue");
        return JsonResult.ok().put("data", queueInfo);
    }

消费者定义

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface QueueListener {
    /**
     * 队列名
     *
     * @return
     */
    String queueName() default "";

    /**
     * 消费者类型
     *
     * @return
     */
    QueueHandlerType type() default QueueHandlerType.NO_REPEAT;

    /**
     * 队列默认长度
     * @return
     */
    int bufferSize() default 1024;
}

我们在使用该注解时,会考虑到队列的数据 重复消费还是不重复消费。我们可以指定 type类型就可以了,默认是不重复消费的

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface QueueHandler {
    //消费者优先级 级别重小到大排序,小的有限,同一样的就并行操作
    int level() default 1;

    //消费者数量
    int size() default 1;

}

消费方法定义,消费者数量默认是1,我们可以定义多个,通过size参数处理

level级别,当里面出现两个 我们指定级别实现 顺序消费.

我们可以通过消费类型和消费级别可以自由组合,多种方案

import lombok.extern.slf4j.Slf4j;
import test1.service.HelloService;
import top.hserver.core.ioc.annotation.Autowired;
import top.hserver.core.ioc.annotation.queue.QueueHandler;
import top.hserver.core.ioc.annotation.queue.QueueListener;

import java.util.concurrent.atomic.LongAdder;

@Slf4j
@QueueListener(queueName = "Queue")
public class EventTest {

    @Autowired
    private HelloService helloService;

    LongAdder atomicLong = new LongAdder();

    @QueueHandler(level = 1, size = 2)
    public void aa(String name) {
        atomicLong.increment();
        System.out.println(atomicLong + "---------" + Thread.currentThread().getName());
    }


    @QueueHandler(level = 2, size = 2)
    public void cc(String name) {
        atomicLong.increment();
        System.out.println(atomicLong + "---------" + Thread.currentThread().getName());
    }
}

动态Queue使用方法


@QueueListener
public class QueueTest2 {

    @Autowired
    private TestService testService;

    @QueueHandler
    public void aVoid(String a) {
        String name = testService.getName();
        System.out.println(name + a);
    }
}


//动态queue中 QueueListener 不需要指定queueName
HServerQueue.addQueueListener("A", QueueTest2.class);
for (int i = 0; i < 10; i++) {
    HServerQueue.sendQueue("A", i + "");
}
HServerQueue.removeQueue("A");

Track跟踪

使用动态字节码技术,在初始化对需要跟踪的方法进行,字节码处理,可以跟踪任意方法 1.在任意方法上添加,@Track 注解:例如

@Track
@GET("/track")
public JsonResult track() {
    return JsonResult.ok();
}

2.实现TrackAdapter接口,并在类上用 @Bean标识

/**
 * @author hxm
 */
@Bean
@Slf4j
public class TrackImpl implements TrackAdapter {
    @Override
    public void track(Class clazz, CtMethod method, StackTraceElement[] stackTraceElements, long start, long end) throws Exception {
        log.info("当前类:{},当前方法:{},耗时:{}", clazz.getName(), stackTraceElements[1].getMethodName(), (end - start) + "ms");
        JvmStack.printMemoryInfo();
        JvmStack.printGCInfo();
    }
}

自定义JSON序列化


    //默认使用Jackson,可以自己实现这个接口进行替换,这段代码放在main 函数里
     HServerApplication.setJson(new JsonAdapter() {
            /**
             *  string 转对象
             * @param data
             * @param type
             * @return
             */
            @Override
            public Object convertObject(String data, Class type) {
                return null;
            }


            /**
             * 参数类型转换
             *
             * @param data
             * @param type
             * @return
             */
            @Override
            public Object convertObject(String data, Parameter type) {
                return null;
            }
            /**
             * map转对象
             *
             * @param data
             * @param type
             * @return
             */
            @Override
            public Object convertMapToObject(Map data, Class type) {
                return null;
            }
            /**
             * 对象转String
             *
             * @param data
             * @return
             */
            @Override
            public String convertString(Object data) {
                return null;
            }
        });
        

自定义协议

HServer实现的是同端口多协议处理方式,如果有需要还可以构建其他协议。 构建协议特点,需要包含包头用于区分协议,选择对应的解码器处理。 HServer默认集成 websocket http rpc mqtt websocketmqtt协议, 如果你还有更多的需求,可以在加协议。或者重写HServer提供的协议。 headers字节,最多提取头512个字节,所以定义头一定不要过长,不要过线.

比如重写Http协议;

@Order(3)
@Bean
public class HttpProtocol extends DispatchHttp {
    @Override
    public boolean dispatcher(ChannelHandlerContext ctx, ChannelPipeline pipeline, byte[] headers, ServerInitializer.ProtocolDispatcher protocolDispatcher) {
        return super.dispatcher(ctx, pipeline, headers, protocolDispatcher);
    }
}

这里需要注意一点的是@Order(3)注解,由于WebSocketMqtt是基于Http协议的,所以先判断的WebSocketMqtt协议,WebSocketMqtt为@Order(2),返回true者不向下执行。 Order值越越小,优先级越高。

如何自定义协议呢?比如实现一个MQTT或者其他自定义的协议


@Order(2)
@Bean
public class DispatchWebSocketMqtt implements ProtocolDispatcherAdapter {
    @Override
    public boolean dispatcher(ChannelHandlerContext ctx, ChannelPipeline pipeline, byte[] headers, ServerInitializer.ProtocolDispatcher protocolDispatcher) {
        if (headers[0] == 'G' && headers[1] == 'E' && new String(headers).indexOf("Sec-WebSocket-Protocol: mqtt") > 0) {
            pipeline.addLast(MqttEncoder.INSTANCE);
            pipeline.addLast(new MqttDecoder());
            pipeline.addLast(ConstConfig.BUSINESS_EVENT, MqttHeartBeatBrokerHandler.INSTANCE);
            return true;
        }
        return false;
    }
}

这里需要注意他的头包含GET 但是http协议也是包含GET,所以这个协议一定要优先于HTTP检查。自己在定义协议的时候,尽量采用特殊字符作为消息头,用来区分协议。 剩下的操作就和Netty原生开发方案类似了,定义编码解码器,最后到自己的Handler处理器里。 非常重要一点,一定不要在这里面有阻塞操作,不然会卡的批爆。切记切记。

单元测试

  1. web 服务测试
/**
 * @author hxm
 */
@RunWith(HServerTestServer.class)
public class TestWebApp {

  @Test
  public void start(){
  }

}
  1. 非web服务测试

@RunWith(HServerTest.class)
public class test2 {

  @Autowired
  private TestBean testBean;

  @Autowired
  private Tom tom;

  @Test
  public void test(){
    System.out.println(testBean.hello());
  }

  @Test
  public void test2(){
    System.out.println(tom.toString());
  }

}

自定义Banner

resources文件夹里存放一个banner.txt 里面放入你图标就可以了.

app.properties配置文件说明

环境切换

在app.properties文件中添加,env=dev

配置文件app-dev.properties也会加载在里面

或者java -jar -Denv=dev xxx.jar 启动参数指定env

SSL支持

在app.properties配置文件添加

#举例:nginx版本的证书下载可能会得到 (xxx.pem或者xxx.cert) xxx.key #注意下载的证书中 key文件需要转换成 pk8 文件 #因为netty4不支持pkcs12格式的私钥, 所以需要将私钥转换成pkcs8格式. #openssl pkcs8 -in my.key -topk8 -out my.pk8 #转换过程需要你设置一个密码.

方案一:

  #jar路径,证书文件应该放在\src\main\resources\ssl\ 目录里,打包会一起打包
  certPath=hserver.pem
  privateKeyPath=hserver.pk8
  privateKeyPwd=123

方案二:

  #外置路径,指定一个绝对路径即可
  certPath=/home/ssl/hserver.pem
  privateKeyPath=/home/ssl/hserver.pk8
  privateKeyPwd=123

然后监听443端口,你就能https 访问了。

定时器线程数

#taskPool定时任务线程池子配置,默认大小是cpu核心数+1
taskPool=5

BOSS线程组大小

#bossPool Netty boss线程组大小 默认2,可以按cpu 核心数来
bossPool=2

worker线程组大小

#workerPool Netty worker线程组大小 默认4
workerPool=4

日志设置

level=error
logbackName=logback-dev.xml

全局流量整形

#读取限制 byte 单位
readLimit=100

#写出限制 byte 单位
writeLimit=100

业务线程数

提示:使用了业务线程,整体QPS会有降低、 优点:可以处理更多的并发耗时任务 缺点:增加线程切换

#businessPool 业务线程大小,默是50,当添加这个配置,就视为生效,小于0 使用woker线程池(性能最高,一旦阻塞就完蛋,这将对编程有一定的要求,如果在控制器层全部设计成异步操作,使用这个配置是最香的)
businessPool=50

消息体大小,可以用于文件上传限制大小

#消息体最大值 默认int.maxValue
httpContentSize=999999

EPOLL模式

#可以开启Epoll时是否开启epoll 默认true
epoll=true

配置中心

在app.properties文件中添加
#配置中心地址
app.nacos.config.address=127.0.0.1:8848
就可以使用动态配置注解,配置中心更新,服务自动刷新.
标记一个类
@NacosClass
标记一个字段
@NacosValue

目前Nacos中Text类型是@NacosValue使用,Json和properties 被@NacosClass使用

配置类注解

  1. app.properties文件内容
app.name=张三

mysql.url=jdbc.....
mysql.userName=root
mysql.password=root
  1. 配置

@ConfigurationProperties( prefix = "mysql")
class MysqlConfig{
    private String name;
    private String userName;
    private String password;
    
    get...
    set...
}

  1. 使用
@Value("app.name")
private String name;

@Autowired
private MysqlConfig mysqlConfig;

参数校验器

控制器参数是一个Bean时,字段可以使用校验器注解
注解 描述
@AssertFalse 字段为必须为false
@AssertTrue 字段为必须为true
@Length 字段CharSequence 类型的长度必须是 length 长
@Max 字段值必须大于这个值,number
@Min 字段值必须小于这个值,number
@NotBlank 字段不能为null同时不是 ""
@NotEmpty CharSequence 集合 map 数组 不是null 长度或者size 大于0
@NotNull 字段不能为Null
@Null 字段必须为Null
@Pattern 字段CharSequence 必须满足这个正则
@Size 字段 CharSequence 集合 map 数组必须在这范围内

插件开发

  1. 添加POM依赖 scope 设置为 provided
        <dependency>
            <groupId>top.hserver</groupId>
            <artifactId>HServer</artifactId>
            <version>${HServer.version}</version>
            <scope>provided</scope>
        </dependency>
  1. 实现接口PluginAdapter
/**
 * @author hxm
 */
public class BeetLSqlPlugin implements PluginAdapter {

    private static final Logger log = LoggerFactory.getLogger(BeetLSqlPlugin.class);


    @Override
    public void startIocInit() {

    }

    @Override
    public void iocInitEnd() {

    }

    @Override
    public void startInjection() {

    }

    @Override
    public void injectionEnd() {
      
    }

}
  1. spi处理
建立一个文件  resources/META-INF/services/top.hserver.core.interfaces.PluginAdapter
文件内容     net.hserver.plugins.beetlsql.BeetLSqlPlugin
这个文件内容是你实现接口的包名+类名
  1. 参考插件 https://gitee.com/HServer/hserver-plugs-beetlsql,或者在厂库里去找关于Plugin的代码。后期的插件会越来越多

细节和注意事项

在Controller层中定义的方法

参数可以是基础数据类型或者bean对象,或者HttpRequest,HttpResponse,(需要是这个包下面的top.hserver.core.interfaces.的对象)当不是表单提交时,可以通过httpRequest.getRawData(),获取到请求的数据。默认也会尝试将内容转成对象

打包jar

Main函数类上添加@HServerBoot 注解用于标记是启动类。 只需要在pom.xml 添加打包命令即可,打包之前记得 clean

    <build>
        <plugins>
            <plugin>
                <groupId>net.hserver.plugins.maven</groupId>
                <artifactId>hserver-maven-plugin</artifactId>
                <version>3.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

静态资源路径

resources/static

模板路径

resources/template

查看相关实例代码

找到Maven的依赖包,在top.hserver.test.目录下是大量的测试案例和代码可以查询学习和使用。

Comment ( 0 )

Sign in for post a comment

Java
1
https://gitee.com/HServer/HServer.git
git@gitee.com:HServer/HServer.git
HServer
HServer
HServer
master

Search

010926 d3736ce6 1850385 010927 f77ebe53 1850385