Awesome Open Source
Awesome Open Source

Summer

  • 这是一个轻量级的、一站式的java游戏服务器框架,也可用于开发简单的web服务。
  • 基于netty实现了高性能通讯,支持tcp、http、websocket等协议,支持protobuf、json两种数据格式,支持以配置的形式轻松开启多端口网络服务。
  • 支持RPC远程调用,支持以优雅的方式实现远程调用,支持调用超时重试、幂等调用。
  • 封装了持久化层,提供数据访问和数据落地接口,支持异步新增、保存、删除数据,支持主键或指定其他字段进行数据缓存。
  • 支持简单的ioc,业务层使用依赖注入实现逻辑。
  • 封装了伪协程实现方案Promise,可用于解决地狱式回调等问题。
  • 支持MVC、领域、ECS架构。

summer2 coding ...

目录

更新说明

1.1.8 - 2021-04-30

  1. 将框架中用到的LinkedList替换为ArrayList。ArrayList与LinkedList对比
  2. RepositoryDao新增onLoadAfter、onSaveBefore方法,当对象从数据库加载后调用onLoadAfter,当对象在数据库写入前调用onSaveBefore。
  3. Rpc轮询机制调整,优先使用可用的client。

1.1.7 - 2021-03-31

  1. SessionHandler新增方法handleReady,在处理请求前触发此回调,与receive的不同之处是在此方法内可抛出异常,且在会话队列中执行。
  2. 新增接口RemoteHandler、RemoteProtobufHandler,其中RemoteProtobufHandler仅用于protobuf协议。在使用注解@Remote的类上实现此接口。即可进行一些拦截或其他业务处理。与SessionHandler.handleReady的区别是,触发范围缩小,仅会对进入此类寻求处理方法的协议进行触发。
  3. Repository新增支持过滤的list方法,新增stream方法获取数据。

1.1.6 - 2021-02-28

  1. 新增注解@ParamPacking,当接口的参数过多时,可使用一个类包装起来,并在参数前加上此注解。(建议对外开放的接口,不要传递JSON格式的数据)
  2. 新增WebTokenHandler,在http协议下可自定义token的生成和解析方式。
  3. 修复Web模式下,设置ErrorView无法渲染的问题。
  4. 新增WebContentTypes拓展名与内容类型的映射库。
  5. 优化Web模式下,Url的解析方式。
  6. 修复仓库模式,当字段设置为readOnly时,在某些情况下会构建出错误的sql语句的问题。
  7. AsyncCacheRepositoryDao新增showDelaySaveInfo方法,可用于关闭延迟保存数据信息的输出。

1.1.5 - 2021-01-31

  1. 修复在程序关闭时,异步事件中有推送异步消息的操作导致异常的问题。
  2. 优化ServerPush,新增简单的调用接口。
  3. 优化CacheRepositoryDao.list(Map<String, Object> optional),在筛选数据的过程中改用stream,在最后才进行collect操作。
  4. 新增默认配置,在未明确指定配置文件路径的时候,并且默认路径下的配置文件不存在,则会启用默认配置。例如,在项目中引入summer依赖,无需本地配置文件,即可直接使用Summer.hot启动。
  5. 通过Summer.hot启动时,未明确指定libPath时将不再通过默认路径"lib"去扫描加载jar。(mvn打包时可通过配置插件自动拷贝依赖以及在程序启动时加载指定依赖)

1.1.4 - 2020-12-31

  1. 修复异步响应Summer.asyncResponse无法发送错误码。

1.1.3 - 2020-11-30

  1. Repository中add操作不再执行insertSql语句,修改为replaceSql语句。
  2. Repository增加listSingleCache方法,当实体只包含一个cacheKey时,可直接调用此方法查询数据。
  3. Repository采用原子性、自旋等方式替换掉原有的同步锁。
  4. AsyncCacheRepository中的add、remove增加版本号控制,定时器触发时只会对最新版本的数据进行数据库操作。
  5. 新增@RequestMapping,可自定义请求映射的接口名称。
  6. Repository新增removalAll方法。
  7. EventBus新增订阅和派送方式,使用注解@AcceptEvent,无需设定事件名称,该注解下的方法只允许一个参数,并且该参数的类型将作为"事件名称"用于事件派送,详细请看test中的例子。
  8. 修复RPC在一个Cluster下多个不同serverName时出现的BUG。感谢 kingo132 大佬指出问题。

1.1.2 - 2020-10-31

  1. 异步请求远程接口增加RemoteCallbackQuick接口,可用于接收响应的数据。
  2. 新增meter包,可用于编写压测程序。
  3. 新增promise包,可用于异步有序执行。
  4. 优化SessionContextGroup,让SessionContext与Channel直接绑定。
  5. 优化SessionQueueMgr。
  6. 新增ecs包,实现了简单的ECS(Entity-Component-System)架构。
  7. 优化RPC,在底层确保接口幂等性,所有client共用worker、event线程池。
  8. CodeMsg、CodeException中的code由long调整为int。

1.1.1 - 2020-09-19

  1. 新增协议,支持protobuf。
  2. 新增标准的WebSocket协议,与原来的区别是去掉了包头的四个字节。
  3. remote中的方法限定符如果不是public将不对远程开放。

1.0.18 - 2020-09-13

  1. 心跳机制优化。
  2. 用户请求默认在用户队列中进行处理,@SessionQueue废弃。

1.0.17 - 2020-09-08

  1. SessionContext调整,新增属性token,token可以在用户登录以后手动设置为用户ID,以此作为用户的唯一标识。
  2. SessionQueueMgr、SingleQueueMgr优化。SessionQueueMgr,不再使用直接使用SessionContext分配队列,改为使用SessionContext中的token分配队列,当token未设置时使用sessionId。
  3. http协议下,sessionId不再作为用户的唯一标识,sessionId仅作为链路标识。当用户请求接口携带的cookie数据中不存在token时,响应时会下发通过UUID生成的32位字符串作为token,用户下一次请求时就会携带有token。通过sessionContext.getToken()获取token,通过sessionContext.clearToken()清空token。
  4. 修复部分因为hashcode引发的问题。修复方式,将set改为list,重写hashcode,map中的key如果无法重写hashcode则对结构进行调整。
  5. 修正SessionHandler中的accept拼写错误
  6. test例子取消lombok依赖。

1.0.14 - 2020-05-18

  1. 缓存仓库调整,当PrimaryKey为非自增模式时,使用CacheKey查询经历过remove,add的相同PrimaryKey的实体时,尽管实体中的CacheKey与查询的值不同也依旧能命中缓存,现将其进行修复。当主动remove时,会将存有对应PrimaryKey的CacheKey缓存清除。

1.0.13 - 2020-05-09

  1. ServerBootstrap启动参数中的ChannelOption.SO_BACKLOG改为读取配置表,并将ChannelOption.ALLOCATOR设为PooledByteBufAllocator.DEFAULT
  2. 仓库调整,增加getOrCreate方法。
  3. 当项目未能在预期情况下运行时(例如端口被占用),及时终止进程。

1.0.10 - 2020-04-28

  1. 当使用仓库时,实体的某个字段的类型如果为Array、Collection(List Set Queue)、Map、以及自定义的类时,需设定length。当length<=255,使用CHAR类型;当length<=16383,使用VARCHAR类型;当length>16383,使用TEXT类型。
  2. SessionContext中的address分为directAddress和realAddress,其中directAddress为链路IP,realAddress通过Head中的"X-Forwarded-For"获取客户端真实IP,仅在HTTP、WEBSOCKET协议且在使用反向代理的情况下生效。
  3. AsyncResponseMgr新增process方法可用于捕获异常以及自动发送响应。

1.0.3 - 2020-01-17

  1. 日志调整,由原来的log4j改为logback,并且移除直接调用log4j的所有代码,完成了日志解耦,可自由替换成slf4j的其他实现。
  2. 由于不再强制加载日志配置文件,Summer.hot移除了日志配置文件路径的参数。
  3. Remote新增异步响应请求,可参考test中的例子。

1.0.0 - 2019-12-27

使用mvn重构项目

0.9.1 - 2019-12-26

  1. 异步缓存仓库调整,修复了异步add、异步remove在一起使用时,可能导致remove失败的问题,并进行了一定的优化,对同一对象先后进行add、remove,当发生在定时器同一执行期内,其操作相互抵消,不再进行数据库操作
  2. Http协议下,抛出CodeException异常时,日志输出级别由error改为warn

0.9.0 - 2019-12-05

  1. 缓存仓库调整,移除查询列表时按主键的hash code排序,以此提高性能。如果有业务需求,可以考虑在前端进行排序,分散后端压力。

0.8.0 - 2019-11-07

  1. 异步缓存仓库调整,当有一个持久化对象,对它进行add、remove、add、remove操作时不能达到预期效果,因为id在第二次add时就发生变动,由于是同一个对象,所以当定时器触发写库时,第一个add的id已经变成了第二个add时的id,第一个remove的id也发生了变化。正确的用法是clone对象后再进行第二次add、remove,但考虑到有些场景是可以复用对象的,于是对异步缓存仓库层进行了调整,add或remove时会记录当时的id,再进行操作。

0.7.3 - 2019-10-25

  1. 修复某些情况下请求接口时会出现参数错误的问题

0.7.2 - 2019-10-17

  1. 修复部分线程池不能优雅关闭
  2. EventMgr改名为EventBusMgr
  3. 修改了业务线程池和推送线程池

0.7.1 - 2019-10-16

  1. 修复持久化层仓库模式,list无法正确返回数据,并进行优化,当多次list之间的间隔时间小于缓存过期时间时,可直接命中缓存。
  2. 从端口minor新增配置useMainServerThreadPool,默认为false,当置为true时,从端口将使用主端口的线程池 (监听线程池, 读写线程池, 业务线程池)。

0.7.0 - 2019-10-14

  1. http协议下,实现了cookie机制,sessionId是识别唯一用户的标志,而sessionContext只是链路。当用户请求接口携带的cookie数据中不存在sessionId时,响应时会下发通过UUID生成的32位字符串作为sessionId,用户下一次请求时就会携带有sessionId。通过sessionContext.getSessionId()获取sessionId,通过sessionContext.clearSessionId()清空sessionId。
  2. 新增一个用于统计接口调用情况的静态类RemoteStatistics。
  3. 修复持久化层仓库模式,无法解析Date的问题。

0.6.0 - 2019-08-30

  1. 移除之前实现的简单持久化层(2019.06.22的第三点)
  2. 新增SessionContent.getWaitWriteQueueSize,可获得待写队列的长度
  3. 接口SessionHandler新增抽象方法sending(SessionContext ctx),当尝试数据写入时会回调此方法,可配合第二点当待写队列长度过大时关闭会话
  4. 新增持久化层,采用仓库模式(com.swingfrog.summer.db.repository),实体上使用仓库相关的注解,以及dao继承RepositoryDao,即可实现仓库模式,自动建表,无需手动写SQL语句。除了基础的仓库功能还有缓存仓库CacheRepositoryDao、异步缓存仓库AsyncCacheRepositoryDao,推荐使用异步缓存仓库,支持异步/同步插入、更新、删除,以及同步查询。

0.5.1 - 2019-06-22

  1. 新增接口Lifecycle,当组件实现此接口后,即可在服务器启动时触发start方法,在服务器关闭时触发stop方法。
  2. 新增方法 Set Summer.listDeclaredComponent(Class clazz),可用于采集容器内的组件,例如第一点正是使用此方法采集所有实现Lifecycle的组件。
  3. 实现了一个简单的持久化层,支持缓存和异步写入,但对于结构有特定要求。使用时将实体继承AbstractDelayCacheEntity,dao继承DelayCacheDao,即可直接使用并自动建表。

0.5.0 - 2019-06-12

  1. 支持一个进程启动多个服务端口,仅需对配置进行修改,详细请见下方服务器配置文件。在使用@ServerHandler时,默认监听主端口,若需要监听其他端口,可使用@ServerHandler(serverName)指定服务器名。

0.4.0 - 2019-05-22

  1. 修复了http协议下请求资源css、js等,content-type错误的问题。
  2. 注解@SingleQueue(key),新增通配符${arg},arg为Remote上的参数。
  3. 修复注解@Synchronized不生效的问题。

0.3.1 - 2019-02-20

  1. 新增Summer.getServerEventLoopGroup,用于获取服务器业务线程池
  2. 新增Summer.getSessionQueueSize,用于获取会话队列长度
  3. 优化SessionQueue、SingleQueue队列,不再分配新线程,将以队列的形式逐个提交到服务器业务线程中(在此特别感谢一位大哥的支持与协助)

0.3.0 - 2019-02-17

  1. ClientRemote类,新增rsyncRemote方法,调用接口超时将自动重试直到成功为止。
  2. ClientRemote类,新增getServerName方法,用于获取连接其他服务器的节点名称。
  3. 新增Summer.getRemoteInvokeObjectWithRetry、Summer.getRandomRemoteInvokeObjectWithRetry,用于获取连接其他服务器的远程调用接口代理对象,超时将自动重试直到成功为止。

0.2.0 - 2019-01-28

  1. 修复了,服务器之间远程调用可能出现丢包的问题,原因是消息id不能正确的递增,解决办法是修改了消息id的判断。
  2. 当作为web服务器时,若接口返回的类型不是WebView,则将返数据序列化成Json,并返回TextView。

环境介绍与安装说明

JDK 1.8 以上
MySql 5.7 (仅供参考)
Redis 5.0 (仅供参考)

快捷上手

创建项目

  1. 创建mvn项目
  2. 添加summer到pom.xml
    <dependency>
        <groupId>com.swingfrog.summer</groupId>
        <artifactId>summer</artifactId>
        <version>1.1.7</version>
    </dependency>

项目结构

  • src
  • lib
  • config
  • Template #Web项目
  • WebContent #Web项目

src

包结构
  • com.test.summerDemo
    • bean #实体
    • constant #常量
    • dao #数据库操作
    • service #业务逻辑
    • event #事件触发器
    • handler #服务器会话回调
    • push #推送接口
    • remote #远程接口
    • task #定时任务
    • exception #异常信息
    • manager #对象管理
    • util #工具
    • SummerDemoApp.java #启动类
启动类
package com.test.summerDemo;

import com.swingfrog.summer.app.Summer;
import com.swingfrog.summer.app.SummerApp;

public class SummerDemoApp implements SummerApp {

	@Override
	public void init() {

	}

	@Override
	public void start() {

	}

	@Override
	public void stop() {

	}
	
	public static void main(String[] args) throws Exception {
		Summer.hot(new SummerDemoApp());
	}

}
其余的组件将在下文逐一介绍

lib

引用外部jar包请放在此目录下,并添加引用。
引用Summer.jar,此jar包依赖SummerServer库。

config

  • db.properties #数据库配置文件
  • redis.properties #缓存配置文件
  • task.properties #任务配置文件
  • server.properties #服务器配置文件
db.properties (druid的配置文件)
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
username=root
password=123456
filters=stat
initialSize=2
maxActive=300
maxWait=60000
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
poolPreparedStatements=false
maxPoolPreparedStatementPerConnectionSize=200

asyncCache.coreThread=0
redis.properties (jedis配置文件)
url=127.0.0.1
port=6379
timeout=3000
password=123456
blockWhenExhausted=true
evictionPolicyClassName=org.apache.commons.pool2.impl.DefaultEvictionPolicy
jmxEnabled=true
maxIdle=8
maxTotal=200
maxWaitMillis=100000
testOnBorrow=true
task.properties (quartz配置文件)
org.quartz.scheduler.instanceName=Task
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
server.properties (服务器配置文件) [2020.05.09更新]
#服务器集群名称
server.cluster=Gate
#服务器节点名称
server.serverName=gate_s1
#绑定地址
server.address=127.0.0.1
#绑定端口
server.port=8828
#通讯协议
server.protocol=Http
#消息编码
server.charset=UTF-8
#消息密码
server.password=123456
#侦听线程数
server.bossThread=0
#读写线程数
server.workerThread=0
#业务线程数
server.eventThread=0
#消息最大长度 单位字节
server.msgLength=1024000
#心跳时间 单位秒
server.heartSec=40
#请求的间隔时间
server.coldDownMs=10
#是否开启连接白名单
server.allowAddressEnable=true
#白名单允许连接的地址
server.allowAddressList=127.0.0.1,127.0.0.2
#SOCKET: SO_BACKLOG
server.optionSoBacklog=1024

#服务器的其他端口列表
server.minorList=gate_s2

minor.gate_s2.cluster=Gate
minor.gate_s2.serverName=gate_s2
minor.gate_s2.address=127.0.0.1
minor.gate_s2.port=8080
minor.gate_s2.protocol=Http
minor.gate_s2.charset=UTF-8
minor.gate_s2.password=
minor.gate_s2.bossThread=0
minor.gate_s2.workerThread=0
minor.gate_s2.eventThread=0
minor.gate_s2.msgLength=1024000
minor.gate_s2.heartSec=40
minor.gate_s2.coldDownMs=10
minor.gate_s2.allowAddressEnable=false
minor.gate_s2.allowAddressList=
minor.gate_s2.optionSoBacklog=1024
#使用主端口的线程池 (监听线程池, 读写线程池, 业务线程池) 默认为false
minor.gate_s2.useMainServerThreadPool=true


#连接其他服务器的列表
server.clientList=account_s1,account_s2

#其他服务器的集群名称
client.account_s1.cluster=Account
#其他服务器的节点名称
client.account_s1.serverName=account_s1
#连接地址
client.account_s1.address=127.0.0.1
#连接端口
client.account_s1.port=8828
#通讯协议
client.account_s1.protocol=StringLine
#消息编码
client.account_s1.charset=UTF-8
#消息密码
client.account_s1.password=123456
#读写线程数
client.account_s1.workerThread=0
#业务线程数
client.account_s1.eventThread=0
#消息最大长度 单位字节
client.account_s1.msgLength=1024
#心跳时间 单位秒
client.account_s1.heartSec=20
#断线重连间隔时间 单位毫秒
client.account_s1.reconnectMs=100
#远程调用超时时间 单位毫秒
client.account_s1.syncRemoteTimeOutMs=5000
#连接数
client.account_s1.connectNum=1

client.account_s2.cluster=Account
client.account_s2.serverName=account_s2
client.account_s2.address=127.0.0.1
client.account_s2.port=8828
client.account_s2.protocol=StringLine
client.account_s2.charset=UTF-8
client.account_s2.password=123456
client.account_s2.workerThread=0
client.account_s2.eventThread=0
client.account_s2.msgLength=1024
client.account_s2.heartSec=20
client.account_s2.reconnectMs=100
client.account_s2.syncRemoteTimeOutMs=5000
client.account_s2.connectNum=1

运行项目

开发环境

在eclipse中可直接运行或调试,启动类为SummerDemoApp.class

生产环境

打包

在Options中勾选Add directory entries。
注意,不要导出可运行的jar文件,因为会把lib中引用的jar和引用的库打包进jar中,造成jar体积巨大。

项目结构
  • SummerDemo
    • SummerDemo.jar
    • lib
    • config
    • Template
    • WebContent
  • SummerRuntime.jar
使用SummerRuntime.jar运行

java -jar SummerRuntime.jar SummerDemo/SummerDemo.jar com.test.summerDemo.SummerDemoApp

框架介绍

组件介绍

SummerApp由辅助组件和主要组件组成,其中bean、constant、manager、util、exception为辅助组件,dao、service、event、handler、push、remote、task、app为主要组件。

bean

javabean、数据表的实体映射

constant

常量声明

manager

对象管理,使用时在类上方使用注解@Bean

@Bean
public class LoginManager {

    private ConcurrentHashMap<Integer, SessionContext> accountIdMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<SessionContext, Integer> sessionContextMap = new ConcurrentHashMap<>();
    //省略...
	
}

util

工具类

exception

异常信息声明

dao

数据库操作,类需继承BaseDao并使用注解@Dao

@Dao
public class AccountDao extends BaseDao<Account> {

    public Account getById(int id) {
        return getBean("select * from t_account where id = ?", id);
    }
	
}
public abstract class BaseDao<T> {

    protected int update(String sql, Object... args){}
    protected Long insertAndGetGeneratedKeys(String sql, Object... args){}
    protected T getBean(String sql, Object... args) {}
    protected List<T> listBean(String sql, Object... args) {}
    protected <E> E getValue(String sql, Object... args) {}
    protected <E> List<E> listValue(String sql, Object... args) {}
    protected Map<String, Object> getMap(String sql, Object... args) {}
    protected List<Map<String, Object>> listMap(String sql, Object... args) {}
    protected <E> E getBeanByClass(String sql, Class<E> clazz, Object... args) {}
    protected <E> List<E> listBeanByClass(String sql, Class<E> clazz, Object... args) {}
    
}

service

业务处理,使用时在类上方使用注解@Service

@Service
public class AccountService {

    @Autowired
    private AccountDao accountDao;
	
    public Account getAccountById(int accountId) {
        return accountDao.getById(accountId);
    }
	
}

event

事件处理器,使用时在类上方使用注解@EventHandler
在对应方法上方使用注解@BindEvent,参数为监听的事件的名称。也可使用@BindEvent(value = "事件名称", index = 1),index表示同名事件处理器的先后顺序,index小到大,顺序先到后。
如果该方法的返回值不为viod、null,则表示对该事件进行拦截,因此后面的事件处理器便不会收到通知。

@EventHandler
public class FriendEvent {

    @BindEvent("登录事件")
    public void noticeFriend(int accountId) {}
	
}

handler

服务器会话回调,类需实现SessionHandler并使用注解@ServerHandler
此组件主要用于网关服务器,可对用户的连接和请求进行拦截或其他处理

@ServerHandler
public class LoginHandler implements SessionHandler {
	
    //是否允许该会话连接服务器 此处可进行黑名单拦截或白名单放行
    @Override
    public boolean accept(SessionContext sctx) {
        return true;
    }

    //会话连接成功
    @Override
    public void added(SessionContext sctx) {

    }

    //会话心跳超时
    @Override
    public void heartTimeOut(SessionContext sctx) {

    }

    //会话发来的消息长度大于配置
    @Override
	public void lengthTooLongMsg(SessionContext sctx) {

	}

    //是否接收会话发来的消息
    @Override
    public boolean receive(SessionContext sctx, SessionRequest request) {
        return true;
    }

    //是否接收会话发来的消息 用于protobuf
    @Override
    public boolean receive(SessionContext ctx, ProtobufRequest request) {
        return true;
    }

    //会话断开连接
    @Override
    public void removed(SessionContext sctx) {

    }

    //会话发送重复消息
    @Override
    public void repetitionMsg(SessionContext sctx) {
	
    }

    //会话发送消息次数间隔小于配置
    @Override
    public void sendTooFastMsg(SessionContext sctx) {

    }

    //会话发来的消息无法解析
    @Override
    public void unableParseMsg(SessionContext sctx) {

    }

}

push

推送接口,使用时在类上方使用注解@Push
用于将消息推送给连接本服务器的其他服务器或客户端

@Push
public class DataPush {
	
    public void pushDataToAll(DataPushMsg msg) {
        Summer.getServerPush().asyncPushToAll(msg.getRemote(), msg.getMethod(), msg.getData());
    }
}

remote

远程调用接口,使用时在类上方使用注解@Remote
SessionContext为调用此接口的会话,此参数可省略。
除了标记@Optional的参数外,皆为必填参数,如有遗漏会抛出异常。

@Remote
public class AccountRemote {

    @Autowired
    private AccountService accountService;
	
    public Account getAccount(SessionContext sctx, int accountId, @Optional remark) {
        return accountService.getAccountById(accountId);
    }
	
}

task

定时任务,使用时在类上方使用注解@Task
在对应的方法上方添加注解@CronTask、@IntervalTask,即表示该方法为一个任务。
@CronTask("cron 表达式")当时间满足cron表达式时执行该方法
@IntervalTask(1000)每隔1000毫秒执行该方法,或使用@IntervalTask(value = 1000, delay = 2000)2000毫秒后执行该方法,然后每隔1000毫秒执行该方法。

@Task
public class StatTask {

    @CronTask("0 0/5 * * * ? ")
    public void onlineStatTask() {

    }
	
    @IntervalTask(1000) 
    public void updateXX() {
	    
    }
	
    @IntervalTask(value = 1000, delay = 2000)
    public void waitAndUpdate() {
	    
    }
	
}

app

app启动类,此类需实现SummerApp且添加程序入口main方法,并在main方法中调用启动框架。
Summer.hot会在后面提到。

public class SupmersGateApp implements SummerApp {

    private static final Logger log = LoggerFactory.getLogger(SupmersGateApp.class);
	
	//框架初始化后回调
    @Override
    public void init() {
        log.info("gate init");
    }

    //框架启动后回调
    @Override
    public void start() {
        log.info("gate start");
    }

    //框架停止后回调
    @Override
    public void stop() {
        log.info("gate stop");
    }
	
    public static void main(String[] args) throws Exception {
        Summer.hot(new SupmersGateApp());
    }
	
}

组件之间的调用关系

remote 可调用service、util、manager、constant、bean、exception
push 可调用service、util、manager、constant、bean
handler 可调用service、util、manager、constant、bean
event 可调用service、util、manager、constant、bean
task 可调用service、util、manager、constant、bean、exception
service 可调用dao、service、util、manager、constant、bean、exception
util 可调用util、manager、constant、bean
manager 可调用util、manager、constant、bean
constant 无
bean 无

remote 由远程服务器或客户端调用
push 由远程服务器推送调用
handler 由框架根据会话信息调用
event 由事件驱动器调用
task 由任务处理器调用

注解介绍

注解主要分为两大类,组件类与辅助类。

组件类注解

@Bean、@Dao、@Service、@Remote、@Push、@Task、@ServerHandler、@EventHandler
此类注解只用于类
使用此注解的类,在框架启动时,会自动扫描进容器并实例化常驻于内存中。

辅助类注解

@Autowired、@Synchronized、@SingleQueue、@SessionQueue、@Optional、@Transaction、@CronTask、@IntervalTask、@BindEvent
此类注解只用于字段、方法、参数

@Bean

声明此类为容器中普通组件(manager、other)。

@Dao

声明此类为数据库操作(dao)。

@Service

声明此类为业务处理(service)。

@Remote

声明此类为远程接口(remote)。

@Push

声明此类为推送接口(push)。

@Task

声明此类为定时任务(task)。

@ServerHandler

声明此类为服务器会话回调(handler)。

@EventHandler

声明此类为事件处理器(event)。

@Autowired

在使用@Service、@Remote、@Push、@Task、@ServerHandler、@EventHandler这些注解的类中,其字段如果使用此注解,即可实现自动注入,注入的对象由容器提供。
组件中只有@Bean、@Dao、@Service支持被注入。

@Remote
public class AccountRemote {

    @Autowired
    private AccountService accountService;
	
    @Autowired
    private StatService statService;
	
    @Autowired
    private ItemService itemService;
	
    @Autowired
    private DanService danService;
	
    @Autowired
    private PushManager pushManager;
	
}

@Synchronized

在使用@Service、@Remote、@Task、@EventHandler这些注解的类中,其方法如果使用此注解,即可为该方法上分布式锁。当该方法被调用时,会尝试获取锁,一直等到获取成功,执行完方法或抛异常会自动释放锁。
此锁适用于多服务器同步。

@Remote
public class ShopRemote {
    
    @Synchronized("购物锁")
    public void buyGoods(int accountId, int goods) {}
    
}

@SingleQueue

在使用@Remote注解的类中,其方法如果使用此注解,在多个线程调用此方法是,会排进指定的队列中,依次完成调用。

@Remote
public class StatRemote {
    
    @SingleQueue("队列名称")
    public void peopleOnline(int accountId) {}

    // 使用 ${arg} arg为方法内参数名称
    @SingleQueue("队列名称-${accountId}-${a}")
    public void peopleOffline(int accountId, int a) {}

}

@Optional

在使用@Remote注解的类中,其方法参数如果使用此注解,即视为选填参数。

@Remote
public class AccountRemote {

    public Account getAccount(SessionContext sctx, int accountId, @Optional remark) {}
	
}

@Transaction

在使用@Remote、@Task、@EventHandler这些注解的类中,其方法如果使用此注解,即可开启mysql事务管理,方法执行完则提交事务,如抛出异常则回滚事务。

@Remote
public class FriendRemote {

    @Transaction
    public void addFriend(int accountId, String name) {}
	
}

@CronTask

在使用@Task注解的类中,其方法参数如果使用此注解,即视为定时任务。
@CronTask("cron 表达式")

@Task
public class StatTask {

    @CronTask("0 0/5 * * * ? ")
    public void onlineStatTask() {}
	
}

@IntervalTask

在使用@Task注解的类中,其方法参数如果使用此注解,即视为间隔任务。
@IntervalTask(1000) 立即执行并每1000毫秒再执行。
@IntervalTask(value = 1000, delay = 2000) 等待2000毫秒执行并每1000毫秒再执行。

@Task
public class StatTask {
	
    @IntervalTask(1000) 
    public void updateXX() {}
	
    @IntervalTask(value = 1000, delay = 2000)
    public void waitAndUpdate() {}
	
}

@BindEvent

在使用@EventHandler注解的类中,其方法参数如果使用此注解,即为该方法绑定了相应的事件,当有指定的事件发出时,事件驱动就会调用该方法。
@BindEvent("事件名称")
@BindEvent(value = "事件名称", index = 1) index表示同名事件处理器的先后顺序, index小到大,顺序先到后。

@EventHandler
public class FriendEvent {

    @BindEvent("登录事件")
    public void noticeFriendOnline(int accountId) {}
	
    @BindEvent(value = "登出事件", index = 1)
    public void noticeFriendoffline(int accountId) {}
	
}

核心方法介绍

核心方法可在任意地方使用。

Summer.hot

Summer框架启动方法

public static void hot(SummerApp app) throws Exception {}
public static void hot(SummerApp app, String projectPackage) throws Exception {}
public static void hot(SummerConfig config) {}

Summer.sync

分布式锁

public static void sync(String key, Runnable runnable) {}

Summer.execute

队列处理

public static void execute(Object key, Runnable runnable) {}

Summer.addComponent

添加组件到容器

public static void addComponent(Object obj) {}

Summer.removeComponent

从容器中移除组件

public static void removeComponent(Object obj) {}

Summer.getComponent

从容器中获取组件

public static <T> T getComponent(Class<?> clazz) {}

Summer.listDeclaredComponent

从容器中获取组件

public static <T> List<T> listDeclaredComponent(Class<T> clazz) {}

Summer.getProxyInstance

创建代理对象

public static <T> T getProxyInstance(Object target, ProxyMethodInterceptor interceptor) {}
public interface ProxyMethodInterceptor {

    Object intercept(Object obj, Method method, Object[] args) throws Throwable;
	
}

Summer.autowired

组件注入,为目标对象中使用@Autowired注解的字段,进行对象注入。

public static void autowired(Object obj) {}

Summer.getRedisSource

获取Redis源,可用于操作Redis。

public static RedisSource getRedisSource() {}

Summer.getIntervalTask

创建间隔任务

public static TaskTrigger getIntervalTask(long interval, long delay, String taskName, TaskJob taskJob) {}

Summer.getCronTask

创建定时任务

public static TaskTrigger getCronTask(String cron, String taskName, TaskJob taskJob) {}

Summer.startTask

开始任务

public static void startTask(TaskTrigger taskTrigger) {}

Summer.stopTask

停止任务

public static void stopTask(TaskTrigger taskTrigger) {}

Summer.getClientRemote

通过集群名称和服务器节点名称获取连接其他服务器的远程调用接口对象

public static ClientRemote getClientRemote(String cluster, String name) {}
public class ClientRemote {

    //异步调用远程接口
    public void asyncRemote(String remote, String method, Object data, RemoteCallback remoteCallback) {}
    
    //同步调用远程接口 (如果等待时间超出配置,则抛出异常)
    public <T> T syncRemote(String remote, String method, Object data, Type type) {}
    
}

Summer.getRandomClientRemote

通过集群名称,随机获取连接其他服务器的远程调用接口对象

public static ClientRemote getRandomClientRemote(String cluster) {}

Summer.getRemoteInvokeObject

通过集群名称和服务器节点名称获取连接其他服务器的远程调用接口代理对象

public static <T> T getRemoteInvokeObject(String cluster, String name, Class<?> clazz) {}

Summer.getRemoteInvokeObjectWithRetry

通过集群名称和服务器节点名称获取连接其他服务器的远程调用接口代理对象,超时将自动重试直到成功为止

public static <T> T getRemoteInvokeObjectWithRetry(String cluster, String name, Class<?> clazz) {}

Summer.getRandomRemoteInvokeObject

通过集群名称,随机获取连接其他服务器的远程调用接口代理对象

public static <T> T getRandomRemoteInvokeObject(String cluster, Class<?> clazz) {}

Summer.getRandomRemoteInvokeObjectWithRetry

通过集群名称,随机获取连接其他服务器的远程调用接口代理对象,超时将自动重试直到成功为止

public static <T> T getRandomRemoteInvokeObjectWithRetry(String cluster, Class<?> clazz) {}
账号服务器
@Remote
public class FriendRemote {
    @Autowired
    private FriendService friendService;

    @Transaction
    public void addFriend(int accountId, String name) {
        this.friendService.addFriend(accountId, name);
    }
}
public class AccountServerRemote {
    public static FriendRemote getFriendRemote() {
        return Summer.getRandomRemoteInvokeObject(ClusterConst.ACCOUNT, FriendRemote.class);
    }
}
网关服务器

将账号服务器的jar包引入网关服务器中,即可像调用本地方法一样调用远程接口。

@Remote
public class FriendRemote {

    @Autowired
    private LoginManager loginManager;
	
    public void addFriend(SessionContext sctx, String name) {
        int accountId = loginManager.getAccountId(sctx);
        AccountServerRemote.getFriendRemote().addFriend(accountId, name);
    }
	
}

Summer.getServerPush

获取服务器推送接口对象

public static ServerPush getServerPush() {}
public class ServerPush {
	
    //异步推送至该集群内所有服务器
    public void asyncPushToClusterAllServer(String cluster, String remote, String method, Object data) {}
	
    //同步推送至该集群内所有服务器
    public void syncPushToClusterAllServer(String cluster, String remote, String method, Object data) {}

    //异步推送至该集群内随机一台服务器
    public void asyncPushToClusterRandomServer(String cluster, String remote, String method, Object data) {}
	
    //同步推送至该集群内随机一台服务器
    public void syncPushToClusterRandomServer(String cluster, String remote, String method, Object data) {}
	
    //异步推送至该集群中指定的服务器
    public void asyncPushToClusterThisServer(String cluster, String serverName, String remote, String method, Object data) {}
	
    //同步推送至该集群中指定的服务器
    public void syncPushToClusterThisServer(String cluster, String serverName, String remote, String method, Object data) {}
	
    //异步推送至该会话
    public void asyncPushToSessionContext(SessionContext sessionContext, String remote, String method, Object data) {}

    //同步推送至该会话
    public void syncPushToSessionContext(SessionContext sessionContext, String remote, String method, Object data) {}
	
    //异步推送至部分会话
    public void asyncPushToSessionContexts(List<SessionContext> sessionContexts, String remote, String method, Object data) {}

    //同步推送至部分会话
    public void syncPushToSessionContexts(List<SessionContext> sessionContexts, String remote, String method, Object data) {}

    //异步推送至所有会话
    public void asyncPushToAll(String remote, String method, Object data) {}

    //同步推送至所有会话
    public void syncPushToAll(String remote, String method, Object data) {}

}

Summer.closeSession

关闭会话

public static void closeSession(SessionContext sctx) {}

Summer.getServerEventLoopGroup

获取服务器业务线程池

public static EventLoopGroup getServerEventLoopGroup() {}

Summer.getSessionQueueSize

获取会话队列大小

public static int getSessionQueueSize(SessionContext sctx){}

Summer.createCodeException

创建错误码异常对象

public static CodeException createCodeException(long code, String msg) {}
public static CodeException createCodeException(CodeMsg msg, Object ...args) {}

Summer.createCodeMsg

创建错误码消息

public static CodeMsg createCodeMsg(long code, String msg) {

Summer.getCluster

获取集群名称

public static String getCluster() {}

Summer.getServerName

获取服务器节点名称

public static String getServerName() {}

Summer.syncDispatch

同步发送消息事件

public static void syncDispatch(String eventName, Object ...args) {}

Summer.asyncDispatch

异步发送消息事件

public static void asyncDispatch(String eventName, Object ...args) {}

Summer.getWeb

获取Web相关接口

public static WebMgr getWeb() {}

异常介绍

Error Code 100

调用异常 invoke error
当调用远程接口出现异常并且非自定义ErrorCode时,就会抛出此异常。

Error Code 101

远程接口不存在 remote not exist

Error Code 102

远程方法不存在 method not exist

Error Code 103

参数错误 parameter error

Error Code 104

远程接口受保护 remote was protected

Error Code 105

Protobuf不存在 protobuf not exist

自定义 Error Code

异常声明
public class AccountException {

    /**金币不足*/
    public static final CodeMsg GOLD_NOT_ENOUGH = Summer.createCodeMsg(101005, "gold not enough, accountId[%s] own[%s] need[%s]");
	
}
异常使用

建议只在remote和service中使用

@Service
public class AccountService {

    @Autowired
    private AccountDao accountDao;
	
    public int gainGold(int accountId, int gainGold) {
        int ownGold = accountDao.getGoldByIdForUpdate(accountId);
        int gold = ownGold + gainGold;
        if (gold < 0) {
            throw Summer.createCodeException(AccountException.GOLD_NOT_ENOUGH, accountId, ownGold, gainGold);
        } else if (gold > AccountConst.GOLD_MAX) {
            gold = AccountConst.GOLD_MAX;
        }
        accountDao.updateGold(accountId, gold);
        return gold;
    }
	
}

协议介绍

消息定义

请求消息
{"id": 0, "remote": null, "method": null, "data": {}}

id 由客户端不断递增,由1开始
remote 远程接口 -> 类名
method 远程方法 -> 方法名
data 数据 -> 方法参数名与值

响应消息
{"code": 0, "id": 0, "remote": null, "method": null, "data": null, "time": 0}

code 错误码,为0标识无异常
id 与客户端请求消息的id一致
remote 请求的远程接口
method 请求的远程方法
data 返回的数据
time 时间戳

推送消息
{"code": 0, "id": 0, "remote": null, "method": null, "data": null, "time": 0}

code 为0
id 为0,可根据id是否为0来判断是否是推送消息
remote 推送接口
method 推送方法
data 推送的数据
time 时间戳

Protobuf消息定义

请求消息

proto名称格式: 类名_Req_协议消息ID
例如: HeartBeat_Req_0

响应消息

proto名称格式: 类名_Resp_协议消息ID
例如: HearBeat_Resp_0

推送消息

proto名称格式: 类名_Push_协议消息ID
例如: Test_Push_100

StringLine协议

本协议支持加解密,支持服务器之间使用。消息格式为字符串,在字符串末尾加入\r\n,因此通过判断分隔符\r\n来区分消息。

#通讯协议
server.protocol=StringLine

WebSocket协议

本协议支持加解密,基于WebSocket协议。消息格式为二进制,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。

#通讯协议
server.protocol=WebSocket

LengthField协议

本协议支持加解密,支持服务器之间使用。消息格式为二进制,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。

#通讯协议
server.protocol=LengthField

WebSocket-Protobuf协议

本协议不支持加解密,基于WebSocket协议。消息格式为Protobuf,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。包体前四个字节为协议消息ID,后面的字节为协议内容。

#通讯协议
server.protocol=WebSocket-Protobuf

LengthField-Protobuf协议

本协议不支持加解密,不支持服务器之间使用。消息格式为Protobuf,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。

#通讯协议
server.protocol=LengthField-Protobuf

WebSocket-Standard协议

本协议支持加解密,基于WebSocket协议。消息格式为二进制,数据包只包含包体。

#通讯协议
server.protocol=WebSocket-Standard

WebSocket-Protobuf-Standard协议

本协议不支持加解密,基于WebSocket协议。消息格式为Protobuf,数据包只包含包体,包体前四个字节为协议消息ID,后面的字节为协议内容。

#通讯协议
server.protocol=WebSocket-Protobuf-Standard

Http协议

本协议不支持加解密,基于Http协议。

@Remote
public class TestRemote {
    
    public void test(String msg) {
        
    }
    
}
//地址:端口/远程接口_远程方法?请求数据
http://127.0.0.1:8080/TestRemote_test?msg=hello
#通讯协议
server.protocol=Http

消息加解密算法

WebSoccket与LengthField
byte[] bytes = new byte[0];
String pass = "123456"; //密码由配置文件配置
int index = bytes.length % 10;
for (int i = 0; i < bytes.length; i++) {
    if (index >= pass.length)
        index = 0;
    int res = bytes[i] ^ pass[index];
    bytes[i] = (byte)res;
    index++;
}
StringLine
byte[] bytes = new byte[0];
String pass = "123456"; //密码由配置文件配置
int index = bytes.length % 10;
for (int i = 0; i < bytes.length; i++) {
    if (index >= pass.length)
        index = 0;
    int res = bytes[i] ^ pass[index];
    if (res != 10 && res != 13)
        bytes[i] = (byte)res;
    index++;
}

Web介绍

Web配置管理

public class WebMgr {
	
    //重新加载模板
    public void reloadTemplate() {}
	
    //获取模板
    public Template getTemplate(String templateName) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {}

    //获取Web内容文件路径
    public String getWebContentPath() {}

    //设置Web内容文件路径
    public void setWebContentPath(String webContentPath) {}

    //获取模板文件路径
    public String getTemplatePath() {}

    //设置模板文件路径
    public void setTemplatePath(String templatePath) {}

    //获取内部视图渲染工厂
    public InteriorViewFactory getInteriorViewFactory() {}

    //设置内部视图渲染工厂
    public void setInteriorViewFactory(InteriorViewFactory interiorViewFactory) {}

    //获取主页路径
    public String getIndex() {}

    //设置主页路径
    public void setIndex(String index) {}

    //获取图标路径
    public String getFavicon() {}

    //设置图标路径
    public void setFavicon(String favicon) {}
	
}
内部视图渲染工厂

如需自定义空白视图或错误视图,只需继承此类覆盖相应的方法。

public class InteriorViewFactory {

    //空白视图
    public BlankView createBlankView() {}
	
    //错误视图
    public ErrorView createErrorView(int status, long code, String msg) {}
	
    //错误视图
    public ErrorView createErrorView(int status, String msg) {}
	
}

WebView视图渲染

TextView

文字视图

new TextView(String text);
JSONView

JSON视图

new JSONView(JSON json);
FileView

文件视图

new FileView(String fileName);
ModelView

模型视图

ModelView model = new ModelView(String view);
model.put(String key, Object value);

view 模板地址
key 键
value 值

BlankView

空白视图

new BlankView();
ErrorView

错误视图

new ErrorView(int status, long code, String msg);
new ErrorView(int status, String msg);

status Http状态
code 错误码
msg 错误消息

Web异常介绍

Error Code 201

没有Web视图 not web view

数据提交

@Remote
public class UserRemote {
    
    public JSONView add(String name, Integer age, @Optional remark) {
        JSONObject json = new JSONObject();
        json.put("flag", true);
        return new JSONView(json);
    }
    
}
get
http://127.0.0.1:8080/UserRemote_add?name=toke&age=22 //remark为选填
post
<form action="http://127.0.0.1:8080/UserRemote_add" method="post">
    <input type="text" name="name"/>;
    <input type="text" name="age"/>;
    <input type="text" name="remark"/>;
    <input class="button" type="submit"/>
</form>

上传文件

@Remote
public class FileRemote {
    
    public void upload(WebFileUpload photo) {
        photo.saveToFile("photos/");
    }
    
}
<form action="http://127.0.0.1:8080/FileRemote_upload" method="post" enctype="multipart/form-data">
	<input type="file" name="photo"/>;
	<input class="button" type="submit"/>
</form>
public class WebFileUpload {

    //获取文件名
    public String getFileName() {}
	
    //获取数据
    public ByteBuf getByteBuf() {}
	
    //保存到指定路径
    public void saveToFile(String path) throws IOException {}
	
}

运行机制

通过Summer.hot启动框架。
初始化 -> 启动 -> 运行时 -> 停止
停止需要外部进行操作 kill -2 pid、kill -15 pid

初始化

  1. 加载jar包
  2. 加载server配置
  3. 加载redis配置
  4. 加载数据库配置
  5. 加载任务配置
  6. 扫描组件类注解,实例化并添加进容器
  7. 服务器管理初始化
  8. 客户端管理初始化 (连接其他服务器)
  9. 事件驱动初始化
  10. 组件注入对象
  11. service、remote、task生成代理对象

启动

  1. 服务器启动
  2. 客户端连接 (连接其他服务器)
  3. 任务启动

运行时

服务器管理

与会话保存心跳联系,心跳超时会通过handler通知。

客户端管理 (连接其他服务器)

与其他服务器保存心跳联系,心跳超时后断线会自动重连。

业务触发

两种方式触发业务处理,主动与被动。
主动,通过外部调用远程接口remote或会话的行为触发。
被动,通过内部任务处理器执行task触发。

停止

停止一切

其他介绍

负载均衡

随机远程调用和随机推送都是通过轮询实现

	public Client getClientWithNext() {
		int size = clientList.size();
		if (size == 0) {
			return null;
		}
		if (size == 1) {
			return clientList.get(0);
		}
		int n = next.getAndIncrement();
		n = Math.abs(n);
		n = n % size;
		return clientList.get(n);
	}

Redis操作

RedisSource
public class RedisSource {
	
    //通过key获取value
    public String get(String key) {}
	
    //设置key、value
    public String put(String key, String value) {}
	
    //设置key、value,返回是否成功
    public boolean putAndSuccess(String key, String value) {}
	
    //设置key、value,key过期时间
    public String putWithTime(String key, int seconds, String value) {}
	
    //设置key过期时间
    public boolean setExpireTime(String key, int seconds) {}
	
    //取消key过期时间
    public boolean delExpireTime(String key) {}
	
    //获取key剩余时间
    public long getRemainTime(String key) {}
	
    //判断key是否存在
    public boolean exists(String key) {}
	
    //移除key
    public boolean remove(String key) {}
	
    //获取key的类型
    public String getType(String key) {}
	
    //获取map
    public RedisMap getMap(String key) {}
	
    //获取list
    public RedisList getList(String key) {}
	
    //获取set
    public RedisSet getSet(String key) {}
	
    //获取deque
    public RedisDeque getDeque(String key) {}
	
    //清除redis
    public void clear() {}
}
RedisMap

封装了Redis的Hash

public class RedisMap implements Map<String, String> {}
RedisList

封装了Redis的List

public class RedisList extends RedisCollection implements List<String> {}
RedisSet

封装了Redis的Set

public class RedisSet implements Set<String> {}
RedisDeque

封装了Redis的List

public class RedisDeque extends RedisCollection implements Deque<String> {}
RedisCollection
public abstract class RedisCollection implements Collection<String> {}

Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
java (31,121
game (1,022
redis (940
server (525
orm (366
distributed (231
protobuf (201
mvc (161
netty (152
ioc (95
aop (85
rpg (68
quartz (58
druid (54
ioc-container (34
mmo (26
dao (17