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