如何做一个简单的开放接口(4)-常见Handler的参考实现

1、概述

核心引擎搞定了,接下来的主要工作就是逐个开发 Handler 了。

常用的Handler包括授权(AuthHandler)、流量控制(TrafficControlHandler)、加解密(EncryptHandler)、安全(SecurityHandler)、压缩(ZipHandler)、序列化(KryoHandler)等。

其他外围功能还包括对调用方的管理功能,开放接口介绍网站等,不再冗述。

2、常用Handler

2.1、授权

实现一个达到实用级别的授权实现需要另开一个专题来讲,这里只讲一些基本原则,并提供一个简单的实现。

2.1.1、自定义Session

每次调用接口都传递调用方id和密码,是最简单粗暴的做法。使用定时失效的token向前走了一步。

Token 可以基于 HttpSession 实现,这种实现同Servlet API极其机密地耦合了。

我们完全可以自己实现类似Session的机制,采用 Redis 等分布式缓存中间件来实现,还自动具备了分布式属性。

代码极其简单,如下所示。

/**
 * @author liuhailong2008#foxmail
 */
public class ApiSession implements Serializable {

    private static final long serialVersionUID = 1055965810150154404L;

    /**Session ID*/
    private final String              id;
    /**Session创建时间*/
    private long                creationTime;
    /**Session最后一次访问时间*/
    private long                lastAccessedTime;
    /**Session的最大空闲时间间隔*/
    private int                 maxInactiveInterval;
    /**是否是新建Session*/
    private boolean             newSession;

    private static final String SESSION_KEY_PREFIX = "SESS_";
    //private Set<String> attrNameSet = Collections.synchronizedSet(new HashSet<String>());
    private final String sessionKey ;

    /**
     * 创建新的Session。
     * @param maxIdleSeconds
     */
    public ApiSession(int maxIdleSeconds){
        id = StringUtil.getUUID();
        long now = System.currentTimeMillis();
        creationTime = now;
        lastAccessedTime = now;
        this.maxInactiveInterval = maxIdleSeconds;
        newSession = true;
        //this.attrNameSet.clear();

        sessionKey = SESSION_KEY_PREFIX + id;
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        CacheElement ce = new CacheElement(sessionKey,this);
        ce.setTimeToIdleSeconds(this.getMaxInactiveInterval());
        cb.put(ce);
    }

    /**
     * 通过Session id获取已经存在的Session,如果没有,返回null。
     * @return
     */
    public static ApiSession get(String id){
        String sessionKey = SESSION_KEY_PREFIX + id;
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        ApiSession ret = (ApiSession) cb.get(sessionKey);
        if(ret!=null){
            ret.newSession = false;
            ret.refresh();
        }
        return ret;
    }
    /**
     * 更新 lastAccessedTime 。
     */
    public void refresh() {
        this.lastAccessedTime = System.currentTimeMillis();
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        CacheElement ce = new CacheElement(sessionKey,this);
        ce.setTimeToIdleSeconds(this.getMaxInactiveInterval());
        cb.put(ce);
    }
    /**
     * 是否超时过期。
     *
     * @param session
     * @return
     */
    public boolean isExpired() {
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        ApiSession _this = (ApiSession) cb.get(this.sessionKey);
        // 先查看缓存层面的超时控制
        if(_this==null){
            return false;
        }
        long now = System.currentTimeMillis();
        long last = this.getLastAccessedTime();
        long interal = now - last;
        if(interal>this.getMaxInactiveInterval()){
            this.invalidate();
            return true;
        }else{
            return false;
        }
    }
    /**
     * 强制Session立即失效。
     */
    public synchronized void invalidate() {
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        cb.remove(this.sessionKey);
    }

    /**
     * 移除属性。
     *
     * @param attrName
     * @return
     */
    public synchronized Object removeAttribute(String attrName){
        this.refresh();
        String attrSessionKey = getAttrSessionKey(attrName);
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        Object ret = cb.remove(attrSessionKey);
        return ret;
    }

    /**
     * 设置属性。
     * @param attrName
     * @param attrValue
     */
    public synchronized void setAttribute(String attrName,Object attrValue){
        this.refresh();
        String attrSessionKey = getAttrSessionKey(attrName);
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        CacheElement ce = new CacheElement(attrSessionKey,attrValue);
        ce.setTimeToIdleSeconds(this.getMaxInactiveInterval());
        cb.put(ce);
    }

    /**
     * 获取属性的值。
     * @param attrName
     * @return
     */
    public Object getAttribute(String attrName){
        this.refresh();
        String attrSessionKey = getAttrSessionKey(attrName);
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        Object retObject = cb.get(attrSessionKey);
        return retObject;
    }

    private String getAttrSessionKey(String attrName){
        String attrSessionKey = sessionKey + attrName;
        return attrSessionKey;
    }

    public int getMaxInactiveInterval() {
        if(maxInactiveInterval==-1){
            maxInactiveInterval = 3600;
        }
        return maxInactiveInterval;
    }

    public void setMaxInactiveInterval(int maxInactiveInterval) {
        this.maxInactiveInterval = maxInactiveInterval;
    }

    public String getId() {
        return id;
    }

    public long getCreationTime() {
        return creationTime;
    }

    public long getLastAccessedTime() {
        return lastAccessedTime;
    }

    public boolean isNewSession() {
        return newSession;
    }
}

2.1.2、基于 Token 的简单授权机制

调用方发送加密的用户名、密码消息,调用 getToken 接口。校验通过后,服务器返回 Token 。

调用方使用临时的 Token 访问其他接口。空闲固定时间间隔后,Token失效。

简单来说,Token 可以理解为 Session Id。

2.2、流量控制

可以提供3种流量控制方法,以减轻服务器压力。

2.2.1、访问间隔限制

避免大批量并发形成波峰。

long latestTime = parseLong(ApiCacheBlock.get(latestTimeCacheKey));//上次调用时间
long intervalLimit = (apiOrgBO.getIntervalLimit()==null)?0L:apiOrgBO.getIntervalLimit().longValue();
if(intervalLimit>0){// 需要执行时间间隔限制
    // 触发事件间隔限制
    if(now - latestTime < intervalLimit){
        throw new ApiException(String.format("连续两次调用之间的时间间隔不能小于%d毫秒。",intervalLimit));
    }
}

2.2.2、访问时间段限制

以下代码只是范例,需要根据实际服务器负载情况调整。

String timeString = timeFormat.format(nowDate);
String timeRangeLimitStart = StringUtil.safe2String(apiOrgBO.getTimeRangeLimitStart());
String timeRangeLimitEnd = StringUtil.safe2String(apiOrgBO.getTimeRangeLimitEnd());
if(!StringUtils.isEmpty(timeRangeLimitStart)){
    if(timeString!=null && timeString.compareTo(timeRangeLimitStart)<0){
        throw new ApiException(String.format("系统不早于%s提供服务。",timeRangeLimitStart));
    }
}
if(!StringUtils.isEmpty(timeRangeLimitEnd)){
    if(timeString!=null && timeString.compareTo(timeRangeLimitStart)>0){
        throw new ApiException(String.format("系统不晚于%s提供服务。",timeRangeLimitEnd));
    }
}

2.2.3、当天访问次数限制

计数器自增操作的原子性依赖于 Redis 的 INCR 命令。

long cntInDay = cntInDayPlus(latestDate,dateString,cntInDayCacheKey);
long dayCntLimit = (apiOrgBO.getDayCntLimit()==null)?0L:apiOrgBO.getDayCntLimit().longValue();
if(dayCntLimit>0){
    if(cntInDay>dayCntLimit){
        throw new ApiException(String.format("同一天内调用接口不能超过%d次。",dayCntLimit));
    }
}

3、欢迎探讨

未尽事宜,欢迎留言探讨。

时间: 2024-10-11 09:45:40

如何做一个简单的开放接口(4)-常见Handler的参考实现的相关文章

如何做一个简单的开放接口(2)-核心引擎(上)

1.要实现的功能 书接上回,本回书我们要完成开放接口平台核心引擎的多Handler支持机制. 如图1所示. 图1 开放接口服务器端架构 2.Filter还是装饰模式 装饰者模式貌似是一个实现的候选,类似Java的I/O实现. 多"装饰"一层,就获得了新的功能,原来的功能还在. 对我现在的应用场景来说,这种实现方式过于复杂了. 相对而言,Filter更简洁. 当前的应用场景对性能是有极高要求的,不适合使用哪怕稍微复杂的模式. 3.Handler接口定义 我的Handler接口定义如下.

如何做一个简单的开放接口(1)-功能设计

1.缘起 最初,系统系统间都是孤立的.业务是贯穿的,系统间也必然需要交互数据. 实现数据交互的方式有好多种,可以通过ftp交互Excel文件,可以通过互相读写的中间库,可以通过Web Services. 系统间可能是点对点交互,可能是一对多广播,可能是多对一汇总,可能是多对多协同. 在复杂IT场景中,多信息系统各司其职,协作完成工作.交互数据的事情怎样做呢? 数据交互有两个核心问题要解决:一是协议,二是数据格式.这两个都需要通信双方协商. 如果是企业内部的各信息系统,可以搭建统一的数据交互平台解

如何做一个简单的开放接口(3)-核心引擎(下)

1.要实现的功能 书接上回,本回书解决核心引擎的第二个问题:数据映射和数据校验. 我们把这个部分叫做数据转换模块. 2.输入数据的格式 输入数据的结构.属性名等,是接口发布方确定的. 出于安全.效率.调用方影响等方面的考虑,可能和自身系统中的结构和属性名不一致. 输入数据的格式可能有三种: 反序列化后得到的Java对象. JSON格式. XML格式. 我们将对输入的数据进行校验,转换成自身系统的数据格式. 3.配置 配置文件是数据转换模块的核心. 对于我方来说,数据的结构和格式是相对稳定的. 举

使用React并做一个简单的to-do-list

1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其visual-dom的这种技术创新,也算是早就有了初步了解.一来没有学的太深入,二来后来在工作中和业余项目中都没有用到,因此慢慢的就更加生疏了. 近期,因为我想把自己的开源项目wangEditor能放在React.angular和vuejs中使用.先从react开始,顺手自己也重试一下React的基础知识,顺便再做一个小d

C#做一个简单的进行串口通信的上位机

C#做一个简单的进行串口通信的上位机 1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来操作数据.上位机可以接收下位机的信号.下位机是一个控制器,是直接控制设备获取设备状况的计算机.上位机发出的命令首先给下位机,下位机再根据此命令解释成相应时序信号直接控制相应设备.下位机不时读取设备状态数据(一般为模拟量),转换成数字信号反馈给上位机.上位机不可以单独使用,而下位机可以单独使用. 2.串口通信 串口相当于硬件类型的接口.比如无线传感节

使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

猴子原创,欢迎转载.转载请注明: 转载自Cocos2Der-CSDN,谢谢! 原文地址: http://blog.csdn.net/cocos2der/article/details/51007512 使用Multiplayer Networking做一个简单的多人游戏例子-1/3 使用Multiplayer Networking做一个简单的多人游戏例子-2/3 使用Multiplayer Networking做一个简单的多人游戏例子-3/3 7. 在网络中控制Player移动 上一篇中,玩家操

[3] 用D3.js做一个简单的图表吧!

本人的个人博客为: www.ourd3js.com csdn博客为: blog.csdn.net/lzhlzz 转载请注明出处,谢谢. 前面说了几节,都是对文字进行处理,这一节中将用 D3.js 做一个简单的柱形图. 做柱形图有很多种方法,比如用 HTML 的 div 标签,或用 svg . 推荐用 SVG 来做各种图形.SVG 意为可缩放矢量图形(Scalable Vector Graphics),SVG 使用 XML 格式定义图像,不清楚什么是SVG的朋友请先在 w3cschools 学习下

用EF DataBase First做一个简单的MVC3报名页面

使用EF DataBase First做一个简单的MVC3报名网站 ORM(Object Relational Mapping)是面向对象语言中的一种数据访问技术,在ASP.NET中,可以通过ADO.NET Entity Framework技术来简化数据访问.在EF里,有Code First,Model First和DataBase First三种方法来实现. 百度百科关于ORM的介绍: http://baike.baidu.com/view/197951.htm?fr=aladdin 1.就像

【Python】 做一个简单的 http 服务器

# coding=utf-8 ''' Created on 2014年6月15日 @author: Yang ''' import socket import datetime # 初始化socket s = socket.socket() # 获取主机名, 也可以使用localhost # host = socket.gethostname() host = "localhost" # 默认的http协议端口号 port = 80 # 绑定服务器socket的ip和端口号 s.bin