1 Star 1 Fork 0

Rran / easy-chat-server

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

easy-chat-server

介绍

基于netty和spring手写一个聊天服务

####软件架构说明 服务端和客户端采用websocket长连接的方式进行服务的请求和推送。

如果采用传统的netty接收请求,根据请求的某个字段判断到底应该走哪个具体业务类,得写各种if else, 显然这么做是不优雅的,可以采用策略模式,每一种类型的消息写一个处理类,但是策略模式的缺点就是类会很多 ,那么可以做到像spring mvc那种根据url去映射走具体的controller方式吗,于是自己封装了框架,把这些 if else 从代码种解耦了出来

1.自定义注解
@accept
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Accept {
}
@AcceptMapping
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AcceptMapping {
    String value() default "";
}
2.核心代码
//基于spring框架 扫描带有自定义注解的类缓存起来
@Component
public class AcceptPostProcess implements InitializingBean, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        String[] beanNames = applicationContext.getBeanNamesForAnnotation(Accept.class);
        try {
            for (String beanName : beanNames) {
                String baseUrl = "";
                Object bean = applicationContext.getBean(beanName);
                Class<?> clazz = bean.getClass();
                if (clazz.isAnnotationPresent(AcceptMapping.class)) {
                    AcceptMapping acceptMapping = clazz.getAnnotation(AcceptMapping.class);
                    baseUrl = acceptMapping.value();
                }

                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(AcceptMapping.class)) {
                        continue;
                    }
                    AcceptMapping acceptMapping = method.getAnnotation(AcceptMapping.class);
                    String regex = ("/" + baseUrl + acceptMapping.value()).replaceAll("/+", "/");
                    Pattern pattern = Pattern.compile(regex);

                    AcceptRespository.addBean(new AcceptBean(bean, method, pattern));
                    System.out.println("mapping " + regex + "," + method);
                }

            }


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//通过反射技术,根据入参的messageUrl字段找到对应的accept并且执行方法
@Component
public class AcceptInvoke<T extends ChatMessage> {

    public Response invoke(JSONObject param, ChannelHandlerContext ctx) throws Exception {
        String messageType = param.getString("messageUrl");
        if (StringUtils.isEmpty(messageType)) {
            throw new RuntimeException("messageUrl字段不能为空");
        }
        AcceptBean acceptBean = getAcceptBean(messageType);
        Method method = acceptBean.getMethod();
        if (acceptBean == null) {
            throw new RuntimeException("message路径不正确");
        }

        Class<?>[] paramTypes = method.getParameterTypes();
        Object[] paramValues = new Object[paramTypes.length];
        Map<String, Integer> paramMap = acceptBean.getParamIndexMapping();
        //容器启动时已经做过判断了,这里只有两种入参 ChatMessage和 ChannelHandlerContext
        if (paramMap.containsKey(ChannelHandlerContext.class.getName())) {
            int index = paramMap.get(ChannelHandlerContext.class.getName());
            paramValues[index] = ctx;
        }
        Class<T> c = getClass(acceptBean.getMethod());
        if (paramMap.containsKey(c.getName())) {
            int index = paramMap.get(c.getName());
            paramValues[index] = JSON.toJavaObject(param, c);
        }
        //反射执行方法
        Object result = method.invoke(acceptBean.getAccept(),paramValues);
        if (result != null) {
            if (!(result instanceof Response)) {
                throw new RuntimeException("返回类必须为Response");
            }
        }
        return (Response) result;
    }


    private AcceptBean getAcceptBean(String messageUrl) {
        List<AcceptBean> acceptBeans = AcceptRespository.getAcceptBeans();

        if (CollectionUtils.isEmpty(acceptBeans)) {
            return null;
        }
        messageUrl = messageUrl.replaceAll("/+", "/");
        for (AcceptBean acceptBean : acceptBeans) {
            try {
                Matcher matcher = acceptBean.getPattern().matcher(messageUrl);
                if (!matcher.matches()) {
                    continue;
                }
                return acceptBean;
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }

    private Class getClass(Method method) {
        Class<?>[] paramTypes = method.getParameterTypes();
        for (Class<?> c : paramTypes) {
            if (ChatMessage.class.isAssignableFrom(c)) {
                return c;
            }
        }
        throw new RuntimeException("accept方法参数不正确");
    }
}
3.accept层展示(类似于controller)
@AcceptMapping(value = "/chat")
@Accept
@Component
public class ChatAccept {

    @Autowired
    private ChatService singleChatService;

    /**
     * 单聊
     * @param msg
     */
    @AcceptMapping(value = "/single")
    public void singleChat(SingleMsg msg){
        singleChatService.singleChat(msg);
    }

    /**
     * 群聊
     * @param msg
     */
    @AcceptMapping(value = "/group")
    public void groupChat(GroupChatMsg msg){
        singleChatService.groupChat(msg);
    }

}

整个过程和spring mvc类似,但是为了避免长连接的资源利用,实际工作中不建议所有的通信都走socket ,还是建议走http协议

空文件

简介

手写一个基于netty和spring实现的聊天服务 展开 收起
Java
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/yuanyang888/easy-chat-server.git
git@gitee.com:yuanyang888/easy-chat-server.git
yuanyang888
easy-chat-server
easy-chat-server
master

搜索帮助