IT技术规范

297 阅读14分钟

背景

本规范文档是信息系统的技术标准,明确总体的信息技术的规范性、完整性、一致性。规范建立是逐步完善、提高并演进成熟的过程。通过系统建设确定应用系统的功能边界、系统间边界以及应遵守建设的规则,形成技术标准,再更新和完善架构规范。中科金所有系统的建设必须严格遵循规范制定的原则。

适用范围: 本文档适用于中科金技术部所有技术人员。

目标

  1. 统一技术架构设计原则:我们在做技术落地时,需要遵守的原则。

  2. 确定中间件选型的选型。

  3. 代码规范。

各类架构的关系

下图展示了各种架构的关系,技术架构处在整体技术体系的最底层,为上层提供技术支撑。

技术架构设计原则

  • 高可用性: 整体可用性达到4个9,全年不可用时间累积不超过50分钟,单个系统可用性达到5个9,全年不可用性累计不超过5分钟。

  • 易扩展性: 在较低的运维成本的基础上,实现负载能力的水平扩展,同时要保证数据的一致性。

  • 低耦合性:功能模块之间要尽量减少依赖,耦合性低意味着迭代成本的降低。

  • 可管理性: 功能模块要实现可管理性。分为数据面和控制面。

  • 可适配性: 为了满足与客户的系统或外部系统互通的需求,服务要有高效的方法实现适配。

中间件选型

下图是对中间件的选型以及各个中间件在整体技术栈的位置和功能。

  • 流量网关层:采用Ngnix + LUA 作为流量网关。用于验证请求的合法性,流控等

  • 分布式缓存:redis。缓存

  • 消息队列: Kafka(适用于业务复杂度低,消息主题数量少,单主题消息量巨大的场景)。Rocketmq(适用于复杂的业务场景,适合主题数量多的场景,同时提供事务消息和定时消息)

  • 服务治理-配置中心:Nacos。配置信息组件。用于保存配置信息,常用于开关,实时更新配置信息等。

  • 服务治理-注册中心:Nacos。用于服务的注册和订阅。

  • 服务治理-流控:Sentinel。通过事先设定限流规则来防范过高流量把服务打死。

  • 服务治理-熔断:Sentinel。用于保护下游服务避免被过高的流量打死。事后防范。

  • 服务治理-降级:Sentinel。在高峰期下线非核心服务的功能。或当被调用的服务已经不能提供正常的服务,需要主动关闭对这个服务的调用。目的是避免了服务调用的超时和不必要的异常从而影响主流程。

  • 服务治理-RPC: Dubbo。服务间的调用采用 Dubbo RPC。

  • 服务治理-分布式事务服务:Seata。提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

  • 分布式DAL(分布式数据访问层):ShardingJDBC。用于分库分表的场景。

  • 可监控平台-日志中心:ELK。包括Elasticsearch、Logstash、Kibana。

  • 可监控平台-全链路跟踪服务:Skywalking。用于全链路跟踪服务。

  • 可监控平台-指标监控与警告:Prometheus。用于运行指标的监控与警告。

  • 数据层-OLTP: mysql,postgreSQL

  • 数据层-搜索:Elastic Search

开发规范

Java开发规范

  • 命名风格

  • 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。

  • 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

  • 【强制】类名使用UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO / AO / PO等。

  • 【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。

  • 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

  • 【强制】抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类名开始,以Test结尾

  • 【强制】类型与中括号紧挨相连来定义数组。正例:定义整形数组int[] arrayDemo;反例:在main参数中,使用String args[]来定义

  • 【强制】POJO类中布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误。

  • 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

  • 【强制】杜绝完全不规范的缩写,避免望文不知义。

  • 【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。正例:从远程仓库拉取代码的类命名为PullCodeFromRemoteRepository。反例:变量int a; 的随意命名方式。

  • 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。

  • 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。

  • 【参考】各层命名规约:

    • Service/DAO层方法命名规约
    • 获取单个对象的方法用get作前缀
    • 获取多个对象的方法用list作前缀
    • 获取统计值的方法用count作前缀
    • 插入的方法用save/insert作前缀
    • 删除的方法用remove/delete作前缀
    • 修改的方法用update作前缀
  • 领域模型命名规约

    • 数据对象:xxxDO,xxx即为数据表名
    • 数据传输对象:xxxDTO,xxx为业务领域相关的名称
    • 展示对象:xxxVO,xxx一般为网页名称
    • POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO
  • 常量定义

  • 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中

  • 【推荐】不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。 说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。正例:缓存相关常量放在类CacheConsts下;系统配置相关常量放在类ConfigConsts下

  • 【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量

    • 跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下
    • 应用内共享常量:放置在一方库中,通常是子模块中的constant目录下
  • 【推荐】如果变量值仅在一个固定范围内变化用enum类型来定义

  • 【推荐】枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开

  • 代码格式

  • 【强制】if/for/while/switch/do等保留字与括号之间都必须加空格

  • 【强制】任何二目、三目运算符的左右两边都需要加一个空格。 说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等

  • 【强制】采用4个空格缩进,禁止使用tab字符

  • 【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式

  • 【推荐】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进。 2)运算符与下文一起换行。 3)方法调用的点符号与下文一起换行。4)方法调用中的多个参数需要换行时,在逗号后进行换行。

  • 【推荐】在项目代码中提供.EditorConfig 文件,项目组开发人员在进行项目开发前,先在IDE配置中打开使用本地代码的.EditorConfig,保持项目中代码格式统一(提供公司统一格式)。EditorConfig配置文件:

  • 【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性

  • OOP规约

  • 【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可

  • 【强制】所有的覆写方法,必须加@Override注解

  • 【强制】外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么

  • 【强制】不能使用过时的类或方法

  • 【强制】日期格式化时,传入 pattern 中表示年份统一使用小写的 y。在日期格式字符串中需区分清楚大写的 M 和小写的 m,大写的 H 和小写的 h 分别指代的意义。标准格式为:"yyyy-MM-dd HH:mm:ss"

  • 【强制】获取当前毫秒数:System.currentTimeMillis(),而不是 new Date().getTime()

  • 【强制】所有整型包装类对象(Short、Intger、Long、Float)之间值的比较,全部使用 equals 方法比较。

  • 【强制】浮点数之间的等值判断,基本数据类型不能使用 == 进行比较,包装数据类型不能使用 equals 进行判断。(1)指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。说明:float a = 1.0F - 0.9F; float b = 0.9F - 0.8F; float diff = 1e-6F; if (Math.abs(a - b) < diff) { System.out.println("true"); }

  • 【强制】BigDecimal 的等值比较应使用 compareTo() 方法,而不是 equals() 方法。原因:equals() 方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo() 则会忽略精度

  • 【强制】禁止使用构造方法 BigDecimal(double) 的方式把 double 值转化为 BigDecimal 对象(字符串)

  • 关于基本数据类型与包装数据类型的使用标准如下:

    • 【强制】所有的POJO类属性必须使用包装数据类型
    • 【强制】RPC方法的返回值和参数必须使用包装数据类型
    • 【推荐】所有的局部变量使用基本数据类型
  • 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中

  • 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读

  • 【推荐】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展

  • 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

  • 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯快速定位代码

  • 【强制】线程池不允许使用 Executors去创建,而是手动通过 ThreadPoolExecutor 创建线程池的方式,同时必须明确指定线程池的RejectRejectExecutionHandler策略

  • 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区代码块,就不要锁整个方法体;能用对象锁,就不要用类锁

  • 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁

  • 【强制】在使用阻塞等待获取锁的方式时,必须在try业务代码块之外执行lock方法获取锁,在代码块的finally中执行unlock方法来释放锁。说明:

Lock lock = new xxxLock()

lock.lock()

try {

doBiz()

} finally {

lock.unlock()

}

  • 【强制】在使用尝试机制来获取锁的方式时,必须在try业务代码块之前,先判断当前线程是否成功获取锁。在代码块的finally中执行unlock方法来释放锁。说明:

Lock lock = new xxxLock()

boolean lockResult = lock.tryLock()

if (lockResult) {

try {

doBiz()

} finally {

lock.unlock()

}

}

  • 控制语句
  • 【强制】在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使空代码。说明:break是退出switch语句块,而return是退出方法体
  • 【强制】在if/else/for/while/do语句中必须使用大括号。即使只有一行代码,也需避免采用单行的编码方式:if (condition) statements
  • 【推荐】避免采用取反逻辑运算符。说明:if(!(x >= 628)) 用于表达 x 小于 628,就可以改成 (x < 628)
  • 注释规约
  • 【强制】类、类属性、类方法的注释必须使用Javadoc规范,使用/*内容/格式,不得使用// xxx方式
  • 【推荐】代码修改的同时,注释也要进行相应的修改,尤其是Javadoc注释中参数、返回值、异常等的修改
  • 【强制】所有类都必须添加创建者和创建日期
  • 【参考】谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除
  • 【参考】对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息

应用结构规范

  • 【推荐】推荐新建立的项目使用Gradle进行构建,对依赖的管理和项目的构建都优于Maven

      1.   应用子模块结构规范

xxx表示当前应用名称,如meds

  • 【强制】xxx-sdk - 服务SDK,存放对外提供的SDK代码

  • 【强制】xxx-web - API/Controller,存放对外的REST服务代码

  • 【强制】xxx-common - 通用组件,存放公共的组件代码

  • 【强制】xxx-dal/repository - 数据访问,存放数据库访问相关的代码

  • 【强制】xxx-domain - 领域对象,存放数据交换VO、DTO相关实体类代码

  • 【强制】xxx-service - 服务层,存放具体的业务服务代码

  • 【强制】xxx-demo – 实例模块,存放相关的样例代码

  • 【推荐】xxx-main –打包启动模块,一般存放相关自动配置类以及启动主函数,一个项目存在多个打包启动模块可灵活配置

  1. 应用包规范

  • 【强制】以com.zkj.xxx.yyy命名,xxx为当前项目的主应用名,yyy为当前项目所在的子模块名

  • 【强制】单元测试包名规范:com.zkj.xxx.yyy.test

      1.   应用资源路径规范

  • 【强制】src/test/java - 集成测试,单元测试目录,所有测试类以xxxTest结尾

  • 【强制】src/main/java - java源码所在路径

  • 【强制】src/main/resources/config - 配置文件,存放相关属性文件/yaml文件

  • 【强制】src/main/resources/static - 静态资源,存放静态的资源文件

  • 【强制】src/main/resources/templates - 静态模版资源,存放模版语言代码

  1. SpringCloud项目目录结构规范

  • 【强制】新创建的应用,必须是基于SpringBoot的SpringCloud应用,需要满足以下项目结构:
├── pom.xml└── src├── main│ ├── java│ │ └── com│ │ └── zkj│ │ └── demo│ │ ├── DemoMain.java│ └── resources│ ├──bootstrap.yml│ ├── config│ │ ├── config.properties│ ├──logback-demo.xml│ └──server.jks└── test
  • DemoMain.java

springboot启动的主函数

  • resources/confg/config.properties

spring.profiles.active=local时读取的本地配置,本地启动调试时可用

  • logback-demo.xml

格式为logback-${spring.application.name}.xml,注意开启,可用于动态修改日志级别

  • server.jks

存放配置解密的公私钥信息,默认读取此文件,可在 bootstrap.yml中配置,适配不同的业务环境,配置属性为:encrypt.keyStore.location、encrypt.keyStore.password、encrypt.keyStore.secret、encrypt.keyStore.alias

  • bootstrap.yml

springcloud应用启动引导文件,所有可变的属性都按以下描述定义了环境变量,应用启动时,可通过设置环境变量,让同一份jar(镜像)在不同的环境发布运行:

  • 申明了当前的应名:${spring.application.name},默认情况下,注册到eureka的服务名也使用此属性;

  • 申明了连接配置中心地址/用户/密码的环境变量:CONFIG_SERVER_URI/CONFIG_SERVER_USERNAME/CONFIG_SERVER_PASSWORD

  • 申明了连接vault的IP/PORT/Token的环境变量:VAULT_TOKEN/VAULT_PORT/VAULT_HOST

  • 申明了使用配置解密的server.jks相关的文件地址/密码/密钥/别名

    • KEYSTORE_FILE/ KEYSTORE_PWD/ KEYSTORE_SECRET/ KEYSTORE_ALIAS
  • 特别的,spring.profiles.active取值非local时,会请求bootstrap.yml定义的配置中心服务地址,通过http接口由配置中心服务从git仓库中读取对应profile的配置文件,配置中心存放配置的git仓库目录结构为:

├── application.yml└── application-profile.properties├──+/{profile}.properties├──+/{spring.application.name}/├── spring.application.name.yml├──{spring.application.name}.yml├── {spring.application.name}-${profile}.properties
  • application.yml

公共的全局SpringCloud配置文件,基础配置,不能随意修改

  • application-${profile}.properties

公共的全局SpringCloud配置文件,可以根据不同的profile配置不同的属性,一般不建议修改

  • ${spring.application.name}

定义在项目boostrap.yml中的spring.application.name值,应用启动时根据此值定位属性配置

  • ${spring.application.name}.yml

项目对应的公共配置,一般存储不可变的配置,里面可以根据var获取定义在{var}获取定义在{spring.application.name}-${profile}.properties中的属性配置

  • spring.application.name{spring.application.name}{profile}.properties

项目对应的个性化配置,每个环境可配置一份,用于区分不同环境的属性配置