引子
拿到一个功能需求,高级工程师恐怕会说:“惟手熟尔”。那么,你是否仔细思考过,自己是如何完成一个功能的呢?你是否建立了适合自己的开发方法呢?
本文将以一个“爆破检测”的功能需求,来阐述要完成一个功能需求,需要经过哪些步骤,有哪些考量。
分解功能点
拿到 PRD 如下所示。第一步是分解功能点。
分解功能点,可以采用“分而治之”的策略,结合逻辑树方法、思维导图工具来实现。
看到 PRD ,我们可以先将功能分解为如下四部分:
- 检测配置。1. 开关控制,用于控制主机是否参与检测;2. 内外网爆破,对检测流程进行区分处理。
- 阈值配置:1. 要支持的爆破服务,需要在规则平台配置,然后同步到检测配置页面,用户不可控制; 2. 阈值配置,用于控制达到什么条件触发告警。用户可控制。
- 告警详情:展示生成的告警详细信息。点击“查看更多”会展示历史攻击记录。这里又是一个功能点。
- 告警检测流程: 要生成告警详情,肯定需要一个告警检测流程来生成告警。
注意到,告警详情页面左右上角还有三个按钮:“加入白名单”、“容器响应”、“未处置”。这都是对告警的操作。
将控制功能、检测功能、展示类功能与操作类功能分开,这样,我们可以将功能点分解如下:
仔细阅读 PRD, 进行功能点分解,并挖掘出各种细小的功能点,是程序员开发功能的基本素养之一。
接下来,咱们来“各个击破”。
开关控制
开关控制,是主机维度的,即:登录事件所对应的主机是否参与爆破检测。也就是说,每来一个登录事件,需要查询开关配置来判断这个登录事件是否参与检测。这个是结合整个检测配置(用户自定义配置)来设计的。咱们不展开。
要注意的是,这里会存在一个潜在的稳定性问题。因为登录事件是短时间密集上报的,频繁查询数据库会把数据库打爆的。这说明,程序员需要对产品设计里可能对性能和稳定性等系统质量有影响的地方保持敏感,及时提出并沟通解决方案。别到了要上线或上线后才发现有问题。这是合格程序员的一个基本专业素养。
阈值配置
定义内存对象结构
对于阈值配置,需要定义一个内存对象结构,如下所示:
public class BruteCrackConfig extends BaseGlobalConfig {
/**
* 暴力破解阈值
*/
private List<BruteCrackThreshold> thresholds;
/**
* 爆破服务
*/
private List<BruteCrackSerivceSwitch> serviceSwitches;
}
public class BruteCrackSerivceSwitch {
/**
* 服务名称
*/
private String service;
/**
* 服务描述
*/
private String desc;
/**
* 服务爆破是否开启
*/
private Boolean enable;
}
public class BruteCrackThreshold {
/** 阈值类型。 */
private ThresholdTypeEnum thresholdType;
/** 多少分钟 */
private Integer minutes;
/** 失败次数 */
private Integer failedCount;
}
根据需求,定义合适的对象结构,是基本功。这个对象结构通常是要存储在数据库中的。对于 mongo 来说,可以直接将这个嵌套结构存储在 Document 里。接下来就是 CRUD 的事情了。
定义API
注意到,这里页面展示爆破阈值配置,因此,需要给前端定义API接口。 接口采用 Restful 形式,采用 swagger 自动生成 API 文档。比如查询爆破阈值配置接口,类似这样:
@ApiOperation(value = "查询爆破配置", notes = "查询爆破配置", produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "/configs/brute-config")
public BruteCrackConfigVO getBruteCrackConfig(@RequestBody @Validated BruteCrackConfigQueryParam param) {
// code implementation
}
@Setter
@Getter
@ApiModel(description = "暴力破解阈值配置")
public class BruteCrackConfigVO {
@ApiModelProperty("配置ID")
private String configId;
@ApiModelProperty("平台类型")
private PlatformTypeEnum platformType;
@ApiModelProperty("配置类型")
private GlobalConfigTypeEnum configType;
@ApiModelProperty("爆破服务配置")
private List<BruteCrackSerivceSwitch> serviceSwitches;
@ApiModelProperty("阈值配置")
private List<BruteCrackThreshold> thresholds;
}
定义 API 是服务端开发的基本功,不可不重视之。
爆破检测流程
爆破检测流程涉及客户端、服务端和大数据端的三端交互。
- 客户端: 上报登录事件;
- 服务端: 进行过滤处理,将主机开启的登录事件发送给大数据端;
- 大数据端:使用 Flink 进行统计计算,将达到阈值的登录事件聚合成告警,发送给服务端。服务端接受到告警后走通用告警生成流程。
那么,这里涉及哪些技能点呢?
定义数据交互格式
多端交互,肯定要定义数据交互格式。其中登录事件是 agent 上报的,因此这个是 agent 制定的。服务端制定的与大数据之间的交互数据及格式。这里,我们不讨论具体的交互数据,只讨论如何制定出比较适宜的数据格式。
数据格式要求:
- 满足需求
- 简单,易于理解
- 易于解析和使用
- 可复用可扩展
- 通用数据与业务数据分离
数据格式:
采用 JSON 。
依据: JSON 是标准的 API 和消息通信数据格式。
数据字段:
- 命名贴近业务含义,无拼写错误
- 字段命名与工程中的命名统一
- 对于业界常用业务用语,与业界命名保持统一
数据字段类型:
- 进程ID、端口、金额等数值型,采用数值;
- 如果可能有多项,采用数组;
- 如果是扩展类信息,可采用嵌套 JSON,但嵌套不宜超过两层;
- 通用性的采用字符串。
消息:
- topic 命名与公司规范保持一致;
- 消息体尽量只包含必要的信息,扩展类或详情类信息可提供 API 查询(除非性能关键场景)。
注意这里要考虑数据是否能够复用。比如一份登录事件,可以为多个业务所用(除了爆破,还有爆破登录、一对多失败后登录、一对多成功登录)。由于登录事件往往是非常密集大量的,每个业务都订阅一份相同的登录事件数据,会造成很大的传输开销。因此,需要设计一个结构,来表示一个登录事件可以被哪些业务所处理。
存储设计
一项重要的功能,服务端总少不了存储设计。存储设计是功能设计和实现的核心和基石。不可不重视。
在爆破检测流程中,需要记录登录事件,也需要记录具体告警对应的登录事件。
存储设计通常采用表格来表示。如下所示:
字段名 | 字段类型 | 字段含义 | 是否非空 | 备注 |
---|---|---|---|---|
id | Long | 主键 | 是 | |
loginTime | Long | 登录时间 | 是 | 10 位时间戳 |
loginType | String | 登录类型 | ||
srcIp | String | 登录来源 | 是 | |
service | String | 爆破服务名 | 是 | 规则平台配置 |
port | Integer | 爆破服务端口 | 是 | |
detectionId | String | 登录事件对应的告警ID |
表及字段的设计通常会尽可能遵循数据库范式1NF, 2NF, 3NF 或 BCNF,适当使用外键。mongo 的表设计可阅:Mongo-data-modeling-introduction
对于内外网爆破的区分处理,需要咨询下其它同学,怎么去判断一个IP是内外网的。涉及到沟通能力。
规则推送
由于是大数据来做统计计算,因此,需要将爆破配置规则推送给大数据。这是一个细小的点。
爆破配置规则推送有两个时机:1. 服务端应用启动时; 2. 大数据应用启动或重启时。
这里也涉及到服务端与大数据之间的交互,需要定义好数据交互格式和交互流程。
服务端告警生成流程
服务端告警生成流程是通用的,只需要写两个类,搜集检测结果、填充告警标题即可。
这说明复用是多么的重要!服务端告警生成流程改动很少,节省了很多力气(评估工作量和时间的时候可以稍微留多一点
标签:需求,功能,需要,爆破,登录,开发方法,告警,服务端 From: https://www.cnblogs.com/lovesqcc/p/16967559.html