- 这是一个轻量级的、一站式的java游戏服务器框架,也可用于开发简单的web服务。
- 基于netty实现了高性能通讯,支持tcp、http、websocket等协议,支持protobuf、json两种数据格式,支持以配置的形式轻松开启多端口网络服务。
- 支持RPC远程调用,支持以优雅的方式实现远程调用,支持调用超时重试、幂等调用。
- 封装了持久化层,提供数据访问和数据落地接口,支持异步新增、保存、删除数据,支持主键或指定其他字段进行数据缓存。
- 支持简单的ioc,业务层使用依赖注入实现逻辑。
- 封装了伪协程实现方案Promise,可用于解决地狱式回调等问题。
- 支持MVC、领域、ECS架构。
- 修复Http协议下,数据未发完链路关闭的问题。
1.1.14 - 2022-07-07
- Http协议下,只能处理get/post方法,其他方法将直接关闭链路。 - 2022-02-08
- 修复StringLine协议下会导致内存泄漏的问题,感谢 whxlxl 大佬指出问题。 - 2022-06-16
- Http协议下,处理完请求发送响应后自动关闭链路。 - 2022-07-07
1.1.13 - 2022-01-24
- 修复在RemoteTinyDispatchMgr抛出异常RemoteRuntimeException时,参数显示错误的问题。
- 新增AsyncAddRepository,可用于日志异步插入。
- 新增RepositoryEntity,仓库中的实体类实现此接口,即可直接执行 实体对象.add() 实体对象.save() 实体对象.remove()操作,而不再需要像以前 仓库对象.add(实体对象) 仓库对象.save(实体对象) 仓库对象.remove(实体对象),在一些场景下或许有助于提高开发速度。
1.1.12 - 2021-09-30
- SummerConfig中新增字段enableServiceRemoteProxy,用以控制Summer启动时是否启用service和remote的代理。增强后的service、remote对于现在项目来说不一定有帮助,例如remote中的数据库事务处理操作,在异步仓库的环境下用处不大。故默认设置为不开启,如需启用请将此字段设置为true。
- 新增@Dao注解下的类可使用@Autowired注解注入对象,为了使用起来更方便。之前的设计是从remote->service->dao,在业务不复杂的情况下,可以忽略service,直接remote->dao。
- 对WebView进行简单的抽象。
1.1.11 - 2021-07-31
- 新增网络协议,Tiny JSON。
- 修复RemoteDispatchMgr.invoke解析方法参数,AutowireParam无效的问题。
- 修复AsyncCacheRepository的removeAll不能达到期望效果的问题。在数据库还有旧数据时,刚启动程序先执行removeAll,再执行get或者list等查询操作,依旧可以查询到数据。由于removeAll是异步操作,所以会出现此问题,目前解决方案就是当执行removeAll时,任何查询操作只在缓存中查找不从数据库加载,只有removeAll执行完毕才恢复。
1.1.10 - 2021-06-30
- Lifecycle新增destroy,触发时机为在EventBus关闭后,在Repository关闭前执行,所以destroy中不应该再抛出事件。
- RepositoryBeanProcessor在极端条件下遍历columnHandlers可能出现并发问题,原因是DBUtils的ServiceLoader的遍历操作不是线程安全的,所以现调整为在类加载时先遍历一次ServiceLoader并存入ArrayList,此后只遍历ArrayList。
1.1.9 - 2021-05-31
- 修复SessionHandler.handleReady在http协议下不生效的问题。
1.1.8 - 2021-04-30
- 将框架中用到的LinkedList替换为ArrayList。ArrayList与LinkedList对比
- RepositoryDao新增onLoadAfter、onSaveBefore方法,当对象从数据库加载后调用onLoadAfter,当对象在数据库写入前调用onSaveBefore。
- Rpc轮询机制调整,优先使用可用的client。
1.1.7 - 2021-03-31
- SessionHandler新增方法handleReady,在处理请求前触发此回调,与receive的不同之处是在此方法内可抛出异常,且在会话队列中执行。
- 新增接口RemoteHandler、RemoteProtobufHandler,其中RemoteProtobufHandler仅用于protobuf协议。在使用注解@Remote的类上实现此接口。即可进行一些拦截或其他业务处理。与SessionHandler.handleReady的区别是,触发范围缩小,仅会对进入此类寻求处理方法的协议进行触发。
- Repository新增支持过滤的list方法,新增stream方法获取数据。
1.1.6 - 2021-02-28
- 新增注解@ParamPacking,当接口的参数过多时,可使用一个类包装起来,并在参数前加上此注解。(建议对外开放的接口,不要传递JSON格式的数据)
- 新增WebTokenHandler,在http协议下可自定义token的生成和解析方式。
- 修复Web模式下,设置ErrorView无法渲染的问题。
- 新增WebContentTypes拓展名与内容类型的映射库。
- 优化Web模式下,Url的解析方式。
- 修复仓库模式,当字段设置为readOnly时,在某些情况下会构建出错误的sql语句的问题。
- AsyncCacheRepositoryDao新增showDelaySaveInfo方法,可用于关闭延迟保存数据信息的输出。
1.1.5 - 2021-01-31
- 修复在程序关闭时,异步事件中有推送异步消息的操作导致异常的问题。
- 优化ServerPush,新增简单的调用接口。
- 优化CacheRepositoryDao.list(Map<String, Object> optional),在筛选数据的过程中改用stream,在最后才进行collect操作。
- 新增默认配置,在未明确指定配置文件路径的时候,并且默认路径下的配置文件不存在,则会启用默认配置。例如,在项目中引入summer依赖,无需本地配置文件,即可直接使用Summer.hot启动。
- 通过Summer.hot启动时,未明确指定libPath时将不再通过默认路径"lib"去扫描加载jar。(mvn打包时可通过配置插件自动拷贝依赖以及在程序启动时加载指定依赖)
1.1.4 - 2020-12-31
- 修复异步响应Summer.asyncResponse无法发送错误码。
1.1.3 - 2020-11-30
- Repository中add操作不再执行insertSql语句,修改为replaceSql语句。
- Repository增加listSingleCache方法,当实体只包含一个cacheKey时,可直接调用此方法查询数据。
- Repository采用原子性、自旋等方式替换掉原有的同步锁。
- AsyncCacheRepository中的add、remove增加版本号控制,定时器触发时只会对最新版本的数据进行数据库操作。
- 新增@RequestMapping,可自定义请求映射的接口名称。
- Repository新增removalAll方法。
- EventBus新增订阅和派送方式,使用注解@AcceptEvent,无需设定事件名称,该注解下的方法只允许一个参数,并且该参数的类型将作为"事件名称"用于事件派送,详细请看test中的例子。
- 修复RPC在一个Cluster下多个不同serverName时出现的BUG。感谢 kingo132 大佬指出问题。
1.1.2 - 2020-10-31
- 异步请求远程接口增加RemoteCallbackQuick接口,可用于接收响应的数据。
- 新增meter包,可用于编写压测程序。
- 新增promise包,可用于异步有序执行。
- 优化SessionContextGroup,让SessionContext与Channel直接绑定。
- 优化SessionQueueMgr。
- 新增ecs包,实现了简单的ECS(Entity-Component-System)架构。
- 优化RPC,在底层确保接口幂等性,所有client共用worker、event线程池。
- CodeMsg、CodeException中的code由long调整为int。
1.1.1 - 2020-09-19
- 新增协议,支持protobuf。
- 新增标准的WebSocket协议,与原来的区别是去掉了包头的四个字节。
- remote中的方法限定符如果不是public将不对远程开放。
1.0.18 - 2020-09-13
- 心跳机制优化。
- 用户请求默认在用户队列中进行处理,@SessionQueue废弃。
1.0.17 - 2020-09-08
- SessionContext调整,新增属性token,token可以在用户登录以后手动设置为用户ID,以此作为用户的唯一标识。
- SessionQueueMgr、SingleQueueMgr优化。SessionQueueMgr,不再使用直接使用SessionContext分配队列,改为使用SessionContext中的token分配队列,当token未设置时使用sessionId。
- http协议下,sessionId不再作为用户的唯一标识,sessionId仅作为链路标识。当用户请求接口携带的cookie数据中不存在token时,响应时会下发通过UUID生成的32位字符串作为token,用户下一次请求时就会携带有token。通过sessionContext.getToken()获取token,通过sessionContext.clearToken()清空token。
- 修复部分因为hashcode引发的问题。修复方式,将set改为list,重写hashcode,map中的key如果无法重写hashcode则对结构进行调整。
- 修正SessionHandler中的accept拼写错误
- test例子取消lombok依赖。
1.0.14 - 2020-05-18
- 缓存仓库调整,当PrimaryKey为非自增模式时,使用CacheKey查询经历过remove,add的相同PrimaryKey的实体时,尽管实体中的CacheKey与查询的值不同也依旧能命中缓存,现将其进行修复。当主动remove时,会将存有对应PrimaryKey的CacheKey缓存清除。
1.0.13 - 2020-05-09
- ServerBootstrap启动参数中的ChannelOption.SO_BACKLOG改为读取配置表,并将ChannelOption.ALLOCATOR设为PooledByteBufAllocator.DEFAULT
- 仓库调整,增加getOrCreate方法。
- 当项目未能在预期情况下运行时(例如端口被占用),及时终止进程。
1.0.10 - 2020-04-28
- 当使用仓库时,实体的某个字段的类型如果为Array、Collection(List Set Queue)、Map、以及自定义的类时,需设定length。当length<=255,使用CHAR类型;当length<=16383,使用VARCHAR类型;当length>16383,使用TEXT类型。
- SessionContext中的address分为directAddress和realAddress,其中directAddress为链路IP,realAddress通过Head中的"X-Forwarded-For"获取客户端真实IP,仅在HTTP、WEBSOCKET协议且在使用反向代理的情况下生效。
- AsyncResponseMgr新增process方法可用于捕获异常以及自动发送响应。
1.0.3 - 2020-01-17
- 日志调整,由原来的log4j改为logback,并且移除直接调用log4j的所有代码,完成了日志解耦,可自由替换成slf4j的其他实现。
- 由于不再强制加载日志配置文件,Summer.hot移除了日志配置文件路径的参数。
- Remote新增异步响应请求,可参考test中的例子。
1.0.0 - 2019-12-27
使用mvn重构项目
- 异步缓存仓库调整,修复了异步add、异步remove在一起使用时,可能导致remove失败的问题,并进行了一定的优化,对同一对象先后进行add、remove,当发生在定时器同一执行期内,其操作相互抵消,不再进行数据库操作
- Http协议下,抛出CodeException异常时,日志输出级别由error改为warn
- 缓存仓库调整,移除查询列表时按主键的hash code排序,以此提高性能。如果有业务需求,可以考虑在前端进行排序,分散后端压力。
- 异步缓存仓库调整,当有一个持久化对象,对它进行add、remove、add、remove操作时不能达到预期效果,因为id在第二次add时就发生变动,由于是同一个对象,所以当定时器触发写库时,第一个add的id已经变成了第二个add时的id,第一个remove的id也发生了变化。正确的用法是clone对象后再进行第二次add、remove,但考虑到有些场景是可以复用对象的,于是对异步缓存仓库层进行了调整,add或remove时会记录当时的id,再进行操作。
- 修复某些情况下请求接口时会出现参数错误的问题
- 修复部分线程池不能优雅关闭
- EventMgr改名为EventBusMgr
- 修改了业务线程池和推送线程池
- 修复持久化层仓库模式,list无法正确返回数据,并进行优化,当多次list之间的间隔时间小于缓存过期时间时,可直接命中缓存。
- 从端口minor新增配置useMainServerThreadPool,默认为false,当置为true时,从端口将使用主端口的线程池 (监听线程池, 读写线程池, 业务线程池)。
- http协议下,实现了cookie机制,sessionId是识别唯一用户的标志,而sessionContext只是链路。当用户请求接口携带的cookie数据中不存在sessionId时,响应时会下发通过UUID生成的32位字符串作为sessionId,用户下一次请求时就会携带有sessionId。通过sessionContext.getSessionId()获取sessionId,通过sessionContext.clearSessionId()清空sessionId。
- 新增一个用于统计接口调用情况的静态类RemoteStatistics。
- 修复持久化层仓库模式,无法解析Date的问题。
- 移除之前实现的简单持久化层(2019.06.22的第三点)
- 新增SessionContent.getWaitWriteQueueSize,可获得待写队列的长度
- 接口SessionHandler新增抽象方法sending(SessionContext ctx),当尝试数据写入时会回调此方法,可配合第二点当待写队列长度过大时关闭会话
- 新增持久化层,采用仓库模式(com.swingfrog.summer.db.repository),实体上使用仓库相关的注解,以及dao继承RepositoryDao,即可实现仓库模式,自动建表,无需手动写SQL语句。除了基础的仓库功能还有缓存仓库CacheRepositoryDao、异步缓存仓库AsyncCacheRepositoryDao,推荐使用异步缓存仓库,支持异步/同步插入、更新、删除,以及同步查询。
- 新增接口Lifecycle,当组件实现此接口后,即可在服务器启动时触发start方法,在服务器关闭时触发stop方法。
- 新增方法 Set Summer.listDeclaredComponent(Class clazz),可用于采集容器内的组件,例如第一点正是使用此方法采集所有实现Lifecycle的组件。
- 实现了一个简单的持久化层,支持缓存和异步写入,但对于结构有特定要求。使用时将实体继承AbstractDelayCacheEntity,dao继承DelayCacheDao,即可直接使用并自动建表。
- 支持一个进程启动多个服务端口,仅需对配置进行修改,详细请见下方服务器配置文件。在使用@ServerHandler时,默认监听主端口,若需要监听其他端口,可使用@ServerHandler(serverName)指定服务器名。
- 修复了http协议下请求资源css、js等,content-type错误的问题。
- 注解@SingleQueue(key),新增通配符${arg},arg为Remote上的参数。
- 修复注解@Synchronized不生效的问题。
- 新增Summer.getServerEventLoopGroup,用于获取服务器业务线程池
- 新增Summer.getSessionQueueSize,用于获取会话队列长度
- 优化SessionQueue、SingleQueue队列,不再分配新线程,将以队列的形式逐个提交到服务器业务线程中(在此特别感谢一位大哥的支持与协助)
- ClientRemote类,新增rsyncRemote方法,调用接口超时将自动重试直到成功为止。
- ClientRemote类,新增getServerName方法,用于获取连接其他服务器的节点名称。
- 新增Summer.getRemoteInvokeObjectWithRetry、Summer.getRandomRemoteInvokeObjectWithRetry,用于获取连接其他服务器的远程调用接口代理对象,超时将自动重试直到成功为止。
- 修复了,服务器之间远程调用可能出现丢包的问题,原因是消息id不能正确的递增,解决办法是修改了消息id的判断。
- 当作为web服务器时,若接口返回的类型不是WebView,则将返数据序列化成Json,并返回TextView。
JDK 1.8 以上
MySql 5.7 (仅供参考)
Redis 5.0 (仅供参考)
- 创建mvn项目
- 添加summer到pom.xml
<dependency>
<groupId>com.swingfrog.summer</groupId>
<artifactId>summer</artifactId>
<version>1.1.14</version>
</dependency>
- src
- lib
- config
- Template #Web项目
- WebContent #Web项目
- 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());
}
}
引用外部jar包请放在此目录下,并添加引用。
引用Summer.jar,此jar包依赖SummerServer库。
- db.properties #数据库配置文件
- redis.properties #缓存配置文件
- task.properties #任务配置文件
- server.properties #服务器配置文件
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
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
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.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
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为主要组件。
javabean、数据表的实体映射
常量声明
对象管理,使用时在类上方使用注解@Bean
@Bean
public class LoginManager {
private ConcurrentHashMap<Integer, SessionContext> accountIdMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<SessionContext, Integer> sessionContextMap = new ConcurrentHashMap<>();
//省略...
}
工具类
异常信息声明
数据库操作,类需继承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
public class AccountService {
@Autowired
private AccountDao accountDao;
public Account getAccountById(int accountId) {
return accountDao.getById(accountId);
}
}
事件处理器,使用时在类上方使用注解@EventHandler
在对应方法上方使用注解@BindEvent,参数为监听的事件的名称。也可使用@BindEvent(value = "事件名称", index = 1),index表示同名事件处理器的先后顺序,index小到大,顺序先到后。
如果该方法的返回值不为viod、null,则表示对该事件进行拦截,因此后面的事件处理器便不会收到通知。
@EventHandler
public class FriendEvent {
@BindEvent("登录事件")
public void noticeFriend(int accountId) {}
}
服务器会话回调,类需实现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
public class DataPush {
public void pushDataToAll(DataPushMsg msg) {
Summer.getServerPush().asyncPushToAll(msg.getRemote(), msg.getMethod(), msg.getData());
}
}
远程调用接口,使用时在类上方使用注解@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
在对应的方法上方添加注解@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启动类,此类需实现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
此类注解只用于字段、方法、参数
声明此类为容器中普通组件(manager、other)。
声明此类为数据库操作(dao)。
声明此类为业务处理(service)。
声明此类为远程接口(remote)。
声明此类为推送接口(push)。
声明此类为定时任务(task)。
声明此类为服务器会话回调(handler)。
声明此类为事件处理器(event)。
在使用@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;
}
在使用@Service、@Remote、@Task、@EventHandler这些注解的类中,其方法如果使用此注解,即可为该方法上分布式锁。当该方法被调用时,会尝试获取锁,一直等到获取成功,执行完方法或抛异常会自动释放锁。
此锁适用于多服务器同步。
@Remote
public class ShopRemote {
@Synchronized("购物锁")
public void buyGoods(int accountId, int goods) {}
}
在使用@Remote注解的类中,其方法如果使用此注解,在多个线程调用此方法是,会排进指定的队列中,依次完成调用。
@Remote
public class StatRemote {
@SingleQueue("队列名称")
public void peopleOnline(int accountId) {}
// 使用 ${arg} arg为方法内参数名称
@SingleQueue("队列名称-${accountId}-${a}")
public void peopleOffline(int accountId, int a) {}
}
在使用@Remote注解的类中,其方法参数如果使用此注解,即视为选填参数。
@Remote
public class AccountRemote {
public Account getAccount(SessionContext sctx, int accountId, @Optional remark) {}
}
在使用@Remote、@Task、@EventHandler这些注解的类中,其方法如果使用此注解,即可开启mysql事务管理,方法执行完则提交事务,如抛出异常则回滚事务。
@Remote
public class FriendRemote {
@Transaction
public void addFriend(int accountId, String name) {}
}
在使用@Task注解的类中,其方法参数如果使用此注解,即视为定时任务。
@CronTask("cron 表达式")
@Task
public class StatTask {
@CronTask("0 0/5 * * * ? ")
public void onlineStatTask() {}
}
在使用@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() {}
}
在使用@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框架启动方法
public static void hot(SummerApp app) throws Exception {}
public static void hot(SummerApp app, String projectPackage) throws Exception {}
public static void hot(SummerConfig config) {}
分布式锁
public static void sync(String key, Runnable runnable) {}
队列处理
public static void execute(Object key, Runnable runnable) {}
添加组件到容器
public static void addComponent(Object obj) {}
从容器中移除组件
public static void removeComponent(Object obj) {}
从容器中获取组件
public static <T> T getComponent(Class<?> clazz) {}
从容器中获取组件
public static <T> List<T> listDeclaredComponent(Class<T> clazz) {}
创建代理对象
public static <T> T getProxyInstance(Object target, ProxyMethodInterceptor interceptor) {}
public interface ProxyMethodInterceptor {
Object intercept(Object obj, Method method, Object[] args) throws Throwable;
}
组件注入,为目标对象中使用@Autowired注解的字段,进行对象注入。
public static void autowired(Object obj) {}
获取Redis源,可用于操作Redis。
public static RedisSource getRedisSource() {}
创建间隔任务
public static TaskTrigger getIntervalTask(long interval, long delay, String taskName, TaskJob taskJob) {}
创建定时任务
public static TaskTrigger getCronTask(String cron, String taskName, TaskJob taskJob) {}
开始任务
public static void startTask(TaskTrigger taskTrigger) {}
停止任务
public static void stopTask(TaskTrigger taskTrigger) {}
通过集群名称和服务器节点名称获取连接其他服务器的远程调用接口对象
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) {}
}
通过集群名称,随机获取连接其他服务器的远程调用接口对象
public static ClientRemote getRandomClientRemote(String cluster) {}
通过集群名称和服务器节点名称获取连接其他服务器的远程调用接口代理对象
public static <T> T getRemoteInvokeObject(String cluster, String name, Class<?> clazz) {}
通过集群名称和服务器节点名称获取连接其他服务器的远程调用接口代理对象,超时将自动重试直到成功为止
public static <T> T getRemoteInvokeObjectWithRetry(String cluster, String name, Class<?> clazz) {}
通过集群名称,随机获取连接其他服务器的远程调用接口代理对象
public static <T> T getRandomRemoteInvokeObject(String cluster, Class<?> clazz) {}
通过集群名称,随机获取连接其他服务器的远程调用接口代理对象,超时将自动重试直到成功为止
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);
}
}
获取服务器推送接口对象
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) {}
}
关闭会话
public static void closeSession(SessionContext sctx) {}
获取服务器业务线程池
public static EventLoopGroup getServerEventLoopGroup() {}
获取会话队列大小
public static int getSessionQueueSize(SessionContext sctx){}
创建错误码异常对象
public static CodeException createCodeException(long code, String msg) {}
public static CodeException createCodeException(CodeMsg msg, Object ...args) {}
创建错误码消息
public static CodeMsg createCodeMsg(long code, String msg) {
获取集群名称
public static String getCluster() {}
获取服务器节点名称
public static String getServerName() {}
同步发送消息事件
public static void syncDispatch(String eventName, Object ...args) {}
异步发送消息事件
public static void asyncDispatch(String eventName, Object ...args) {}
获取Web相关接口
public static WebMgr getWeb() {}
调用异常 invoke error
当调用远程接口出现异常并且非自定义ErrorCode时,就会抛出此异常。
远程接口不存在 remote not exist
远程方法不存在 method not exist
参数错误 parameter error
远程接口受保护 remote was protected
Protobuf不存在 protobuf not exist
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 时间戳
proto名称格式: 类名_Req_协议消息ID
例如: HeartBeat_Req_0
proto名称格式: 类名_Resp_协议消息ID
例如: HearBeat_Resp_0
proto名称格式: 类名_Push_协议消息ID
例如: Test_Push_100
本协议支持加解密,支持服务器之间使用。消息格式为字符串,在字符串末尾加入\r\n,因此通过判断分隔符\r\n来区分消息。
#通讯协议
server.protocol=StringLine
本协议支持加解密,基于WebSocket协议。消息格式为二进制,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。
#通讯协议
server.protocol=WebSocket
本协议支持加解密,支持服务器之间使用。消息格式为二进制,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。
#通讯协议
server.protocol=LengthField
本协议不支持加解密,基于WebSocket协议。消息格式为Protobuf,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。包体前四个字节为协议消息ID,后面的字节为协议内容。
#通讯协议
server.protocol=WebSocket-Protobuf
本协议不支持加解密,不支持服务器之间使用。消息格式为Protobuf,数据包分为包头和包体,包头占四个字节,用来表示包体的长度。
#通讯协议
server.protocol=LengthField-Protobuf
本协议支持加解密,基于WebSocket协议。消息格式为二进制,数据包只包含包体。
#通讯协议
server.protocol=WebSocket-Standard
本协议不支持加解密,基于WebSocket协议。消息格式为Protobuf,数据包只包含包体,包体前四个字节为协议消息ID,后面的字节为协议内容。
#通讯协议
server.protocol=WebSocket-Protobuf-Standard
本协议不支持加解密,基于Http协议。
@Remote
public class TestRemote {
public void test(String msg) {
}
}
//地址:端口/远程接口_远程方法?请求数据
http://127.0.0.1:8080/TestRemote_test?msg=hello
#通讯协议
server.protocol=Http
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++;
}
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++;
}
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) {}
}
文字视图
new TextView(String text);
JSON视图
new JSONView(JSON json);
文件视图
new FileView(String fileName);
模型视图
ModelView model = new ModelView(String view);
model.put(String key, Object value);
view 模板地址
key 键
value 值
空白视图
new BlankView();
错误视图
new ErrorView(int status, long code, String msg);
new ErrorView(int status, String msg);
status Http状态
code 错误码
msg 错误消息
没有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);
}
}
http://127.0.0.1:8080/UserRemote_add?name=toke&age=22 //remark为选填
<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
- 加载jar包
- 加载server配置
- 加载redis配置
- 加载数据库配置
- 加载任务配置
- 扫描组件类注解,实例化并添加进容器
- 服务器管理初始化
- 客户端管理初始化 (连接其他服务器)
- 事件驱动初始化
- 组件注入对象
- service、remote、task生成代理对象
- 服务器启动
- 客户端连接 (连接其他服务器)
- 任务启动
与会话保存心跳联系,心跳超时会通过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);
}
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() {}
}
封装了Redis的Hash
public class RedisMap implements Map<String, String> {}
封装了Redis的List
public class RedisList extends RedisCollection implements List<String> {}
封装了Redis的Set
public class RedisSet implements Set<String> {}
封装了Redis的List
public class RedisDeque extends RedisCollection implements Deque<String> {}
public abstract class RedisCollection implements Collection<String> {}