(转)mblog解读(一)

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

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

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

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

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

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

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

 

项目简介

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

技术选型

项目结构

 

项目模块切分

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

假设咱们有这么一个项目,整个项目构建一个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项目也会愈来愈大,那么会遇到下面的几个问题:

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

 

二、模块化体现不出来,若是咱们只负责维护某个模块,由于咱们全部的模块都在一个war包中,那么咱们能够随意修改其余模块(权限的控制),致使版本管理混乱,冲突。同时由于模块太多,太大,很差维护。不少人都在同时修改这个war包,将致使工做没法顺利进行。

 

三、pom.xml原本是能够继承、复用的,可是若是咱们新建一个项目,只能依赖于这个war包,那么会把这个war包的相关的前台的东西依赖过来,致使项目管理混乱。

 

这样的管理是混乱的,没有遵照一个设计模式原则:“高内聚,低耦合”。相反在代码内部,全部的东西都耦合在了一块儿。所以咱们须要划分模块。

 

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

 

模块化的好处

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

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

 

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

 

聚合:

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

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

继承

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

好比说相同的jar包咱们每一个人都须要依赖一遍,而且每一个人引用的版本号不一样,势必形成项目混乱,运行出问题。

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

二、子模块元素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登陆。
相关文章
相关标签/搜索