• (转)mblog解读(一)


    (二期)11、开源博客项目mblog解读(一)

    【课程11】图片上传模块.xmind54.6KB

    【课程11】消息发...通知.xmind55.2KB

    【课程11】异常处理分析.xmind95.4KB

    【课程11预习】多...解读.xmind0.4MB

    【课程11】第三方...模块.xmind0.2MB

    【课程11】多人博...解读.xmind0.3MB

    项目简介

    mblog (mtons blog)开源免费的Java多人博客系统。

    技术选型

    项目结构
    项目模块切分

    我们先来想一下,我们能不能一个项目就用一个模块。这样开起来很方便,简单明了,那么做起来呢,接下来我们分析一下。

    假设我们有这么一个项目,整个项目构建一个war包,而每一层放到各自的Package里面。如下:

    Itoo-Exam

    com.tgb.itoo.exam.dao —–负责与数据库的交互,封装了hibernate的交互类

    com.tgb.itoo.exam.service—-负责处理业务逻辑,放Service接口及其实现类

    com.tgb.itoo.exam.web——-负责与客户端的交互,主要放action/controller,jsp等等

    com.tgb.itoo.exam.util——–工具类

    那么随着我们项目的扩大,Maven项目也会越来越大,那么会遇到下面的几个问题:

    1、首先build整个项目的时间越来越长,尽管你一直在web层工作,但你不得不build整个项目。

    2、模块化体现不出来,如果我们只负责维护某个模块,因为我们所有的模块都在一个war包中,那么我们可以随意修改其他模块(权限的控制),导致版本管理混乱,冲突。同时因为模块太多,太大,不好维护。很多人都在同时修改这个war包,将导致工作无法顺利进行。

    3、pom.xml本来是可以继承、复用的,但是如果我们新建一个项目,只能依赖于这个war包,那么会把这个war包的相关的前台的东西依赖过来,导致项目管理混乱。

    这样的管理是混乱的,没有遵守一个设计模式原则:“高内聚,低耦合”。相反在代码内部,所有的东西都耦合在了一起。因此我们需要划分模块。

    另外,随着技术的飞速发展和各类用户对软件的要求越来越高,软件本身变得越来越复杂,设计人员开始采用各种方式进行开发,于是就有了我们的分层架构、分层模块来提高代码的清晰和重用。从而实现了系统内部的高内聚、低耦合。

    模块化的好处

    1、方便重用,当我们再开发一条teacher线的时候,我们只需要引用itoo-base,itoo-exam-api,这些包都是复用的,称为我们平台复用的基础类库,供所有的项目使用。这是模块化最重要的一个目的。

    2、灵活性。比如我们这些公共的jar包,itoo-base,itoo-tool,itoo-exam-api等这些jar包,我们不需要再当源码,只需要deploy到nexus,其他人从nexus下载即可。代码的可维护性、可扩展性好,并且保证了项目独立性与完整性。

    使用模块化配置,复用性强,防止pom变得过于庞大,方便构建;针对项目的管理更方便,每一个模块都是独立的,抽象出一个父类来管理第三方jar的版本,开发人员只需要开发自己的线,其他的都不用管,灵活;基于此种基础我们还可以做分布式。

    聚合:

    Maven聚合:当我们的模块非常多的时候,我们想要一次构建多个项目,而不是到多个模块的目录下分别执行命令。Maven的聚合特性就是为该需求服务的。

    Maven构建: Maven首先解析聚合模块pom、分析要构建的模块、并计算出一个反应堆构建顺序,然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。 

    继承

    Maven继承也是为了防止重复,让项目的jar包版本一致,在项目管理上起了很大的作用。

    比如说相同的jar包我们每个人都需要依赖一遍,并且每个人引用的版本号不同,势必造成项目混乱,运行出问题。

    1、子模块省略grouopId和version,都会从父模块依赖下来。

    2、子模块元素pom.xml。

    总之,聚合是为了方便快速构建项目,继承是为了消除重复配置,在简化pom的同时还能促进各个模块配置的一致性。

    异常处理机制
    关键类-HandlerExceptionResolver

    Spring MVC通过HandlerExceptionResolver处理程序的异常,包括Handler的映射、数据绑定以及目标方法的执行。HandlerExceptionResolver时一个接口,该接口的实现类都有处理异常的功能。HandlerExceptionResolver是该接口应用广泛的一个实现类,并且DispatcherServlet默认装配了HandlerExceptionResolver 的Bean。

    SpringMVC 提供的异常处理主要有两种方式:

    • 一种是直接实现自己的HandlerExceptionResolver
    • 一种是使用注解

    通过注解的方式实现处理异常主要有以下两种方式:

    • 1 @ControllerAdvice+@ExceptionHandler:配置对全局异常进行处理
    • 2 @Controller + @ExceptionHandler:配置对当前所在Controller的异常进行处理

    在SpringMVC中,处理异常类实际上是HandlerExceptionResolver子类。HandlerExceptionResolver处理所有controller类在执行过程中抛出的未被处理的异常。

    本文演示如何使用以上多种处理异常的方式,最后演示以不同的方式将异常结果返回给调用者

    • 返回 modelAndView
    • 返回一个页面的地址
    • 返回 JSON
    • 返回 http 错误码
    系统默认实现的HandlerExceptionResolver

    以下是系统默认加载到spring mvc容器中的HandlerExceptionResolver

    • ExceptionHandlerExceptionResolver: 根据@ExceptionHandler注解的方法处理对应的异常。其实上文的通过注解的方式处理异常,实际就是在这个类中实现
    • ResponseStatusExceptionResolver: 根据@ResponseStatus注解的方法处理异常
    • DefaultHandlerExceptionResolver: 将异常转化为特定的HTTP的状态码
    • HandlerExceptionResolverComposite:此类通过列表包含以上3个HandlerExceptionResolver,当捕获异常时,会循环调用以上3个HandlerExceptionResolver进行处理

    ExceptionHandlerExceptionResolver处理过程总结一下:

    • 根据用户调用Controller中相应的方法得到HandlerMethod,之后构造ExceptionHandlerMethodResolver,
    • 构造ExceptionHandlerMethodResolver有2种选择,
    • 1.通过HandlerMethod拿到Controller,找出Controller中带有@ExceptionHandler注解的方法(局部)
    • 2.找到@ControllerAdvice注解配置的类中的@ExceptionHandler注解的方法(全局)。
    • 这2种方式构造的ExceptionHandlerMethodResolver中都有1个key为Throwable,value为Method的缓存。之后通过发生的异常找出对应的Method,然后调用这个方法进行处理。
    • 这里异常还有个优先级的问题,比如发生的是NullPointerException,但是声明的异常有Throwable和Exception,这时候ExceptionHandlerMethodResolver找Method的时候会根据异常的最近继承关系找到继承深度最浅的那个异常,即Exception。
    发布与通知
    常见场景

    在实际开发过程中,常常遇到这种场景: 

    做完某一件事情以后,需要广播一些消息或者通知,告诉其他的模块进行一些事件处理,一般来说,可以一个一个发送请求去通知,但是这种方式比较消耗业务时间。那种更好解决方法呢,那就是事件监听,事件监听也是设计模式中 发布-订阅模式、观察者模式的一种实现。

    观察者模式:在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。

    Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息,操作记录,发送短信、邮件等情况。 

    Spring的事件遵循的流程

    自定义事件,继承ApplicationEvent(org.springframework.context.ApplicationEvent)

    定义监听事件,实现ApplicationListener(org.springframework.context.ApplicationListener)

    使用容器触发事件。

    发布事件,使用applicationContext发布事件。

    ApplicationEvent中,在自定义事件的构造方法中除了第一个source参数,其他参数都可以去自定义,

    可以根据项目实际情况进行监听传参,这里就只定义个简单的String字符串的透传。

    逻辑整理

    这里使用一种简单的单机实现Spring的事件模型ApplicationEvent。

    第一步、分别自定义订阅和通知事件,继承ApplicationEvent

    第二步、分别定义事件监听器,实现ApplicationListener

    第三步、使用容器发布事件(订阅事件、通知事件)

    项目运用
    • 定义通知事件NotifyEvent。
    • FollowController.sendNotify()-->发送关注通知。
    • NotifyEventHandler,关注事件监听处理类,保存通知到数据库
    spring中的运用
    知识拓展--@EventListener

    有条件的事件处理

    为了使注释@EventListener的功能更强大,Spring 4.2支持用SpEL表达式表达事件类型的方式

    定义事件

    public class TestEvent extends ApplicationEvent {
    
    
        public boolean isImport;
    
    
        public TestEvent(Object source, boolean isImport) {
            super(source);
            this.isImport = isImport;
        }
    
    
        public boolean isImport() {
            return isImport;
        }
    
    
        public void setImport(boolean anImport) {
            isImport = anImport;
        }
    
    
        @Override
        public String toString() {
            return "TestEvent{" +
                    "isImport=" + isImport +
                    '}';
        }
    }

    定义监听

    @Component
    public class EventHandler {
    
    
        @EventListener(condition="#testEvent.isImport")
        public void TestEventTest(TestEvent testEvent) {
            System.out.println("==============TestEvent==============" + testEvent.toString());
        }
    }
    知识拓展--guava的EventBus

    定义事件

    public class GuavaEvent {
    
    
        private final int message;
    
    
        public GuavaEvent(int message) {
            this.message = message;
            System.out.println("event message:"+message);
        }
    
    
        public int getMessage() {
            return message;
        }
    
    
    }

    定义事件监听

    public class GuavaEventListener {
        public int lastMessage = 0;
    
    
        @Subscribe
        public void listen(GuavaEvent event) {
    
    
            lastMessage = event.getMessage();
            System.out.println("guava--------Message:"+lastMessage);
        }
    
    
        public int getLastMessage() {      
            return lastMessage;
        }
    
    
    }

    发布事件

    //guava test
    EventBus eventBus = new EventBus();
    GuavaEventListener listener = new GuavaEventListener();
    eventBus.register(listener);
    
    
    eventBus.post(new GuavaEvent(200));
    eventBus.post(new GuavaEvent(300));
    
    
    System.out.println("LastMessage:"+listener.getLastMessage());
    事件监听者[Listeners]

    监听特定事件(如,CustomerChangeEvent)

    • 传统实现:定义相应的事件监听者类,如CustomerChangeEventListener;
    • EventBus实现:以CustomerChangeEvent为唯一参数创建方法,并用Subscribe注解标记。

    把事件监听者注册到事件生产者:

    • 传统实现:调用事件生产者的registerCustomerChangeEventListener方法;这些方法很少定义在公共接口中,因此开发者必须知道所有事件生产者的类型,才能正确地注册监听者;
    • EventBus实现:在EventBus实例上调用EventBus.register(Object)方法;请保证事件生产者和监听者共享相同的EventBus实例。

    按事件超类监听(如,EventObject甚至Object):

    • 传统实现:很困难,需要开发者自己去实现匹配逻辑;
    • EventBus实现:EventBus自动把事件分发给事件超类的监听者,并且允许监听者声明监听接口类型和泛型的通配符类型(wildcard,如 ? super XXX)。

    检测没有监听者的事件:

    • 传统实现:在每个事件分发方法中添加逻辑代码(也可能适用AOP);
    • EventBus实现:监听DeadEvent;EventBus会把所有发布后没有监听者处理的事件包装为DeadEvent(对调试很便利)。
    事件生产者[Producers]

    管理和追踪监听者:

    • 传统实现:用列表管理监听者,还要考虑线程同步;或者使用工具类,如EventListenerList;
    • EventBus实现:EventBus内部已经实现了监听者管理。

    向监听者分发事件:

    • 传统实现:开发者自己写代码,包括事件类型匹配、异常处理、异步分发;
    • EventBus实现:把事件传递给 EventBus.post(Object)方法。异步分发可以直接用EventBus的子类AsyncEventBus。
    图片上传模块

    @PostContruct

    是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。

    从这个文件开始看:

    • UploadController
    • 图片上传的入口,通过upload方法上传图片,并返回上传结果
    • FileRepo
    • 图片上传接口,定义图片上传应该拥有的所有相关方法
    • AbstractFileRepo
    • 实现FileRepo接口,把实现类基础公用部分的方法实现。
    • FileRepoImpl
    • 继承抽象类AbstractFileRepo,重写getRoot()方法,为绝对路径保存图片
    DateFormatUtils.format(new Date(), YYYYMMDDHHMMSS);
    //输出值格式例如:/2018/0527/27160051
    图片压缩关键类-- Thumbnails
    • 指定大小进行缩放
    • 按照比例进行缩放
    • 不按照比例,指定大小进行缩放
    • 旋转
    • 水印
    • 裁剪
    • 转化图像格式
    • 输出到OutputStream
    • 输出到BufferedImage
    <dependency>
       <groupId>net.coobird</groupId>
       <artifactId>thumbnailator</artifactId>
       <version>0.4.8</version>
    </dependency>

    使用例子:

    Oauth2.0协议运用

    传统授权方式缺点:

    (1)网站为了后续的服务,会保存用户的密码,这样很不安全。

    (2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

    (3)网站拥有了获取用户储存在Google所有资料的权力,用户没法限制网站获得授权的范围和有效期。

    (4)用户只有修改密码,才能收回赋予网站的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

    (5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

    OAuth就是为了解决上面这些问题而诞生的。

    协议原理

    (A)用户打开客户端以后,客户端要求用户给予授权。

    (B)用户同意给予客户端授权。

    (C)客户端使用上一步获得的授权,向认证服务器申请令牌。

    (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

    (E)客户端使用令牌,向资源服务器申请获取资源。

    (F)资源服务器确认令牌无误,同意向客户端开放资源。

    OAuth的校验流程为什么这么复杂,直接授权之后redirect回accessToken不就结了吗?为什么还要返回auth_code之后请求accessToken?

    首先,redirect是不安全的,随时可以暂停回调而拿到accessToken,拿到了accessToken也就意味着拿到了授权,但是auth_code是和client相对应的,那么即使拿到了auth_code还需要再次申请accessToken,申请accessToken时需要校验Client和state。同时协议设计的原则就是只有Client能拿到accessToken而用户是拿不到的。

    Oauth2.0安全使用建议

    资源提供方:

    • 对client_id和回调地址做严格校验
    • 获取access token的code仅能使用一次,且与授权用户关联
    • 尽量避免直接读取当前用户session进行绑定
    • 有效使用client_secret参数

    资源使用方:

    • 使用Authorization Code方式进行授权
    • 授权过程使用state随机哈希,并在服务端进行判断
    • 尽量使用HTTPS保证授权过程的安全性
    • 最后,对oauth2.0有详细的了解,严格按照流程进行开发。
    QQ授权登录

    QQ登录OAuth2.0总体处理流程如下:

    Step1:申请接入,获取appid和apikey

    Step2:开发应用,并设置协作者帐号进行测试联调;

    Step3放置QQ登录按钮

    Step4:通过用户登录验证和授权,获取Access Token

    Step5:通过Access Token获取用户的OpenID

    Step6调用OpenAPI,来请求访问或修改用户授权的资源。

    QQ互联官网开发攻略:http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side

    项目接入:

    • QQ登录按钮链接
    • http://localhost:8080/oauth/callback/call_qq
    • 获取Authorization Code
    • https://graph.qq.com/oauth2.0/authorize?response_type=code&redirect_uri=&state=0vnuc37nwskcs9cr3yo1wvaq&client_id=
    • 通过Authorization Code获取Access Token
    • https://graph.qq.com/oauth2.0/token?code=&client_id=&client_secret=&grant_type=&authorization_code&redirect_uri=
    • 通过accessToken获取openid
    • https://graph.qq.com/oauth2.0/me?access_token=
    • 通过accessToken和openid获取用户信息
    • https://graph.qq.com/user/get_user_info?access_token=&oauth_consumer_key=&openid=&format=json
    • 判断是否已经注册,为注册跳转到/bind_oauth方法进行账号注册与绑定。然后使用shiro登录。
  • 相关阅读:
    mysql自动补齐
    重置oracle 11G的system、sys密码《亲测》
    细说业务逻辑(前篇)<转>
    当我们把这么一个“狭义的概念”与“这个概念本身”等同起来时,误会、迷茫、困惑、不屑就出现了。
    设计模式就三个准则
    面向对象编程(OOP)的三大特点
    在计算机中,“透明”一词的理解。
    关于ArrayList中添加对象的一个有趣问题~~~~
    MyEclipse 代码自动提示功能失效 提示No Default Proposals 或 no completions available 的解决方法
    面向对象的三大基本特征
  • 原文地址:https://www.cnblogs.com/free-wings/p/9841818.html
Copyright © 2020-2023  润新知