基于netty-socketio消息推送

本项目使用springboot架构作为web服务,意在提供一个rest接口给第三方应用程序使用,第三方只需要按照所给示例连接socket之后,第三方服务端就可以通过rest接口给目标推送消息,目前只支持单个推送。
本示例项目还有消息的持久化,使用阿里的Druid作为连接池将所有发送的消息存储。
项目结构图如下:


15095357.png

依赖jar,请参照pom文件



    4.0.0

    com.msg.push
    msgpush
    0.0.1-SNAPSHOT
    jar

    msgpush
    Demo project for Spring Boot

    
        org.springframework.boot
        spring-boot-starter-parent
        1.5.13.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.2
        

        
            mysql
            mysql-connector-java
            runtime
        
        
            com.alibaba
            fastjson
            1.2.17
        
        
            com.alibaba
            druid
            1.1.5
        
        
            org.apache.commons
            commons-lang3
            3.4
        
        
            com.corundumstudio.socketio
            netty-socketio
            1.7.7
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


基础配置

public class FastJsonMessageConverter extends FastJsonHttpMessageConverter4 {
    public static final FastJsonMessageConverter INSTANCE = new FastJsonMessageConverter();

    private FastJsonMessageConverter() {
        this.setDefaultCharset(Charset.forName("UTF-8"));
        this.setSupportedMediaTypes(Arrays.asList(new MediaType[]{MediaType.APPLICATION_JSON, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN}));
    }
}

public class StringMessageConverter extends StringHttpMessageConverter {
    public static final StringMessageConverter INSTANCE = new StringMessageConverter();

    private StringMessageConverter() {
        super(Charset.forName("UTF-8"));
        super.setWriteAcceptCharset(false);
    }
}

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {

    @Override
    public void configureMessageConverters(List> converters) {
        converters.add(FastJsonMessageConverter.INSTANCE);
        converters.add(StringMessageConverter.INSTANCE);
        super.addDefaultHttpMessageConverters(converters);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**").addResourceLocations("classpath:/images/");
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        super.addResourceHandlers(registry);
    }

}

@MapperScan("com.msg.push.mapper")
@EnableScheduling
@SpringBootApplication
public class MsgpushApplication {

    public static void main(String[] args) {
        SpringApplication.run(MsgpushApplication.class, args);
    }
}

Mysql持久化配置存储

@Configuration
public class DruidSourceConfig {
    @Value("${jdbc.master.database}")
    private String masterDataBase;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource init(){
        DruidDataSource dataSource= new DruidDataSource();
        dataSource.setDriverClassLoader(Thread.currentThread().getContextClassLoader());
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setInitialSize(2);//初始化连接大小
        dataSource.setMaxActive(200);//连接池最大使用连接数量
        dataSource.setMinIdle(2);//连接池最小空闲
        dataSource.setMaxWait(2000);//获取连接最大等待时间
        return dataSource;
    }
}

@Configuration
public class MybatisConfig implements TransactionManagementConfigurer {
    //mybatis 配置路径
    private static String MYBATIS_CONFIG = "mybatis-config.xml";
    //mybatis mapper resource 路径
    private static String MAPPER_PATH = "/mapper/**.xml";
    private String typeAliasPackage = "com.msg.push.mapper";

    @Autowired
    private DataSource dataSource;

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
        //添加mapper 扫描路径
        PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + MAPPER_PATH;
        bean.setMapperLocations(pathMatchingResourcePatternResolver.getResources(packageSearchPath));
        //设置datasource
        bean.setDataSource(dataSource);
        //设置typeAlias 包扫描路径
        bean.setTypeAliasesPackage(typeAliasPackage);
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

public interface MessageHistoryMapper {

    void saveMessage(@Param("account") String sessionId,@Param("content") String content);
}

Mapper文件



    
        insert into t_message_his (account,content) VALUES (#{account},#{content})
    


配置mybatis-config.xml

            
            
                
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                    
         

SocketServer实现

public class EventPushServer {

    private static final Logger logger= LoggerFactory.getLogger(EventPushServer.class);

    protected SocketIOServer server;
    //存储用户和sessionId关联关系
    protected final static HashMap aurs= new HashMap<>();

    @PostConstruct
    public void initServer(){
        Configuration config = new Configuration();
        config.setHostname("localhost");
        config.setPort(9000);
        config.setAuthorizationListener(new AuthorizationListener() {
            @Override
            public boolean isAuthorized(HandshakeData handshakeData) {
                return true;
            }
        });
        server= new SocketIOServer(config);
        server.addConnectListener(new ConnectListener() {
            @Override
            public void onConnect(SocketIOClient client) {
                String account= client.getHandshakeData().getSingleUrlParam("account");
                if(StringUtils.isNotEmpty(account)){
                    String sessionId= client.getSessionId().toString();
                    aurs.put(account,sessionId);
                    client.sendEvent("connectEvent",sessionId);
                    return;
                }
                client.disconnect();
            }
        });
        server.addDisconnectListener(new DisconnectListener() {
            @Override
            public void onDisconnect(SocketIOClient socketIOClient) {
                removeClient(socketIOClient.getSessionId().toString());
            }
        });
        server.start();
    }

    private void removeClient(String sessionId){
        Set set= aurs.keySet();
        for(String account:set){
            if(sessionId.equals(aurs.get(account))){
                aurs.remove(account);
                logger.info("account:{} session timeout be removed.",account);
                return;
            }
        }
    }

    @Scheduled(initialDelay =5000, fixedDelayString= "${scheduled.cron}")
    public void aursClean(){
        Set keys= aurs.keySet();
        for(String account:keys){
            if(null==server.getClient(UUID.fromString(aurs.get(account)))){
                keys.remove(account);
                logger.info("account:{} session timeout be removed.",account);
            }
        }
    }
}

@Component
public class EventPushServerImpl extends EventPushServer {

    public void pushMessageBySessionId(String sessionId,String data){
        SocketIOClient client= server.getClient(UUID.fromString(sessionId));
        client.sendEvent("pushEvent",data);
    }

    public void pushMessageByAccount(String account,String data){
        SocketIOClient client= server.getClient(UUID.fromString(aurs.get(account)));
        client.sendEvent("pushEvent",data);
    }
}

提供REST接口

@RequestMapping("/push")
@RestController
public class PushMessageCtrl {

    @Autowired
    private PushService pushService;

    @RequestMapping(value = "/{account}",method = RequestMethod.GET)
    public Result sendMessage(@PathVariable("account") String account,
                              @RequestParam("message") String message){
        try{
            if(StringUtils.isEmpty(message)){
                return Result.fail();
            }
            pushService.pushMessage(account,message);
            return Result.success();
        }catch (Exception e){
            return Result.fail(ExceptionUtils.getStackTrace(e));
        }
    }
}

@Service
public class PushService {

    @Autowired
    private EventPushServerImpl eventPushServer;
    @Autowired
    private MessageHistoryMapper messageHistoryMapper;

    @Autowired
    private MessagePersistenceServer persistenceServer;

    public void pushMessage(String account,String content){
        eventPushServer.pushMessageByAccount(account,content);
        persistenceServer.executeTask(new PersistenceMsgTask(account,content,messageHistoryMapper));
    }
}

@Component
public class MessagePersistenceServer {
    private ExecutorService executorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void executeTask(Thread t){
        executorService.execute(t);
    }
}

其他辅助

public class Result {
    public int code;
    public Object data;
    public String msg;
    public String ex;

    Result(int code,String msg,String ex){
        this.code= code;
        this.msg= msg;
        this.ex=ex;
    }

    Result(int code,Object data){
        this.code= code;
        this.data= data;
    }

    public static Result fail(String ex){
        return new Result(-1,"发送消息失败。",ex);
    }
    public static Result fail(){
        return new Result(-1,"发送消息失败。",null);
    }
    public static Result success(){
        return new Result(1,"发送消息成功。",null);
    }
    public static Result success(Object data){
        return new Result(1,data);
    }
}

public class Message {
    public String sessionId;
    public String data;
    private String time;

    public String getTime() {
        SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return dateFormat.format(new Date());
    }

    public void setTime(String time) {
        this.time = time;
    }
}

日志配置logback.xml



    
        
            %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
        
    

    

    
        ${log.folder}/process.log
        
            
            ${log.folder}/process.log-%d{yyyy-MM-dd}.%i
            
            120
            
                100MB
            
        
        
            UTF-8
            %date{yyyy-MM-dd'T'HH:mm:ss.SSS} %level [%thread] %logger{36} [%file : %line] %msg%n
        
        false
    

    
        
        
    



Springboot配置application.properties
server.port= 8080
spring.application.name=lexun-batch

jdbc.master.database=master
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&&useSSL=false
jdbc.username=root
jdbc.password=abc123

scheduled.cron=6000

测试消息推送页面pushboard.html




    
    PUSH BOARD
    
    
    
    
    
    




    

Demo Board


运行结果示例:


15100759.png

你可能感兴趣的:(基于netty-socketio消息推送)