大数据平台后端一些开发规范

Posted 大数据指北

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大数据平台后端一些开发规范相关的知识,希望对你有一定的参考价值。

大家好,我是脚丫先生 (o^^o)

集团大数据平台之流批一体的建设之后。

一直想着能研发一套自己沉淀的小型大数据平台项目。

之后能与小伙伴们多多交流。

在研发的过程中,总结一些开发规范(参考全网),希望可以帮助到小伙伴们。

一、编程规范

1.1 好代码的原则

参考 Kent Beck 的简单设计四原则来指导我们的如何写出优秀的代码,如何有效地判断我们的代码是优秀的。

(1)通过所有测试(Passes its tests):强调的是外部需求,这是代码实现最重要的。

(2)尽可能消除重复 (Minimizes duplication):代码的模块架构设计,保证代码的正交性,保证代码更容易修改。

(3)尽可能清晰表达 (Maximizes clarity):代码的可阅读性,保证代码是容易阅读的。

(4)更少代码元素 (Has fewer elements):保证代码是简洁的,在简洁和表达力之间,我们更看重表达力。

以上四个原则的重要程度依次降低, 这组定义被称做简单设计原则。

1.2 命名风格

(1)代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。

反例:_name / name / n a m e / n a m e / n a m e name / name_ / name name/name/name / name

(2)代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

(3)类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等。

正例:JavaServerlessPlatform / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:javaserverlessplatform / UserDo / XMLService / TCPUDPDeal / TAPromotion

(4) 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。

正例: localValue / getHttpMessage() / inputUserId

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

正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME

(6)抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

(7)包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。

正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils(此规则参考 spring 的框架结构)

(8)避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。

反例:


public class ConfusingName  
 public int age; 
 // 非 setter/getter 的参数名称,不允许与本类成员变量同名 
 public void getData(String alibaba)  
 if(condition)  
 final int money = 531; 
 // ... 
  
 for (int i = 0; i < 10; i++)  
 // 在同一方法体中,不允许与其它代码块中的 money 命名相同 
 final int money = 615; 
 // ... 
  
  
 
class Son extends ConfusingName  
 // 不允许与父类的成员变量名称相同 
 public int age; 
 

(9) 在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。

正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT

反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD

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

正例:接口方法签名 void commit();
接口基础常量 String COMPANY = “alibaba”;

反例:接口方法定义 public abstract void f();

(11) 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。

正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。

1.3 项目命名规范

全部采用小写方式, 以中划线分隔。

正例:mall-management-system / order-service-client / user-api

反例:mall_management-system / mallManagementSystem / orderServiceClient

1.4 方法参数规范

无论是 controller,service,manager,dao 亦或是其他的代码,每个方法最多 3 个参数,如果超出 3 个参数的话,要封装成 javabean 对象。

(1) 方便他人调用,降低出错几率。尤其是当参数是同一种类型,仅仅依靠顺序区分,稍有不慎便是灾难性后果,而且排查起来也极其恶心。

(2) 保持代码整洁、清晰度。当一个个方法里充斥着一堆堆参数的时候,再坚强的人,也会身心疲惫。

反例:

/**
* 使用证书加密数据工具方法
*
* @param param
* @param password 加密密码
* @param priCert 私钥
* @param pubCert 公钥
* @return 返回加密后的字符串
*/
public String signEnvelop(JdRequestParam param, String password, String priCert, String pubCert)

二、项目规范

2.1 1、代码目录结构

统一的目录结构是所有项目的基础。

src                               源码目录
|-- common                            各个项目的通用类库
|-- config                            项目的配置信息
|-- constant                          全局公共常量
|-- handler                           全局处理器
|-- interceptor                       全局连接器
|-- listener                          全局监听器
|-- module                            各个业务
|-- |--- employee                         员工模块
|-- |--- role                             角色模块
|-- |--- login                            登录模块
|-- third                             三方服务,比如redis, oss,微信sdk等等
|-- util                              全局工具类
|-- Application.java                  启动类

2.1.2 common 目录规范

common 目录用于存放各个项目通用的项目,但是又可以依照项目进行特定的修改。

src 源码目录
|-- common 各个项目的通用类库
|-- |--- anno          通用注解,比如权限,登录等等
|-- |--- constant      通用常量,比如 ResponseCodeConst
|-- |--- domain        全局的 javabean,比如 BaseEntity,PageParamDTO 等
|-- |--- exception     全局异常,如 BusinessException
|-- |--- json          json 类库,如 LongJsonDeserializer,LongJsonSerializer
|-- |--- swagger       swagger 文档
|-- |--- validator     适合各个项目的通用 validator,如 CheckEnum,CheckBigDecimal 等

2.1.3 config 目录规范

config 目录用于存放各个项目通用的项目,但是又可以依照项目进行特定的修改。

src                               源码目录
|-- config                            项目的所有配置信息
|-- |--- MvcConfig                    mvc的相关配置,如interceptor,filter等
|-- |--- DataSourceConfig             数据库连接池的配置
|-- |--- MybatisConfig                mybatis的配置
|-- |--- ....                         其他

2.1.4 module 目录规范

module 目录里写项目的各个业务,每个业务一个独立的顶级文件夹,在文件里进行 mvc 的相关划分。
其中,domain 包里存放 entity, dto, vo,bo 等 javabean 对象

src
|-- module                         所有业务模块
|-- |-- role                          角色模块
|-- |-- |--RoleController.java              controller
|-- |-- |--RoleConst.java                   role相关的常量
|-- |-- |--RoleService.java                 service
|-- |-- |--RoleDao.java                     dao
|-- |-- |--domain                           domain
|-- |-- |-- |-- RoleEntity.java                  表对应实体
|-- |-- |-- |-- RoleDTO.java                     dto对象
|-- |-- |-- |-- RoleVO.java                      返回对象
|-- |-- employee                      员工模块
|-- |-- login                         登录模块
|-- |-- email                         邮件模块
|-- |-- ....                          其他

2.1.5 domain 包中的 javabean 命名规范

建议对象使用 lombok 的 @Builder ,@NoArgsConstructor,同时使用这两个注解,简化对象构造方法以及set方法。

正例:


@Builder
@NoArgsConstructor
@Data
public class DemoDTO 

    private String name;
    
    private Integer age;


// 使用示例:

DemoDTO demo = DemoDTO.builder()
                .name("yeqiu")
                .age(66)
                .build();

2.1.6 数据对象;XxxxEntity,要求:

(1) 以 Entity 为结尾(阿里是为 DO 为结尾)

(2) Xxxx 与数据库表名保持一致

(3) 类中字段要与数据库字段保持一致,不能缺失或者多余

(4) 类中的每个字段添加注释,并与数据库注释保持一致不允许有组合

项目内的日期类型必须统一,建议使用 java.util.Date,java.sql.Timestamp,java.time.LocalDateTime 其中只一。

三、MVC 规范

3.1 整体分层

controller 层

service 层

impl 层

dao 层

3.2 controller 层规范

(1) 只允许在 method 上添加 RequestMapping 注解,不允许加在 class 上(为了方便的查找 url,放到 url 不能一次性查找出来)

正例:

@RestController
public class DepartmentController 

    @GetMapping("/department/list")
    public ResponseDTO<List<DepartmentVO>> listDepartment() 
        return departmentService.listDepartment();
    

反例

@RequestMapping ("/department")
public class DepartmentController 

    @GetMapping("/list")
    public ResponseDTO<List<DepartmentVO>> listDepartment() 
        return departmentService.listDepartment();
    

(2) 不推荐使用 rest 命名 url, 只能使用 get/post 方法。url 命名上规范如下:

/业务模块/子模块/动作

正例:

GET  /department/get/id      查询某个部门详细信息
POST /department/query         复杂查询
POST /department/add           添加部门
POST /department/update        更新部门
GET  /department/delete/id   删除部门

(3) 每个方法必须添加 swagger 文档注解 @ApiOperation ,并填写接口描述信息,描述最后必须加上作者信息 @author 作者 。

正例:

    @ApiOperation("更新部门信息 @author 作者")
    @PostMapping("/department/update")
    public ResponseDTO<String> updateDepartment(@Valid @RequestBody DeptUpdateDTO deptUpdateDTO) 
        return departmentService.updateDepartment(deptUpdateDTO);
    


(4) controller 负责协同和委派业务,充当路由的角色,每个方法要保持简洁:

不做任何的业务逻辑操作

不做任何的参数、业务校验,参数校验只允许使用@Valid 注解做简单的校验

不做任何的数据组合、拼装、赋值等操作

正例:

    @ApiOperation("添加部门 @author 哪吒")
    @PostMapping("/department/add")
    public ResponseDTO<String> addDepartment(@Valid @RequestBody DepartmentCreateDTO departmentCreateDTO) 
        return departmentService.addDepartment(departmentCreateDTO);
    

(5) 只能在 controller 层获取当前请求用户,并传递给 service 层。

正例

    @ApiOperation("添加员工 @author yandanyang")
    @PostMapping("/employee/add")
    public ResponseDTO<String> addEmployee(@Valid @RequestBody EmployeeAddDTO employeeAddDTO) 
        LoginTokenBO requestToken = SmartRequestTokenUtil.getRequestUser();
        return employeeService.addEmployee(employeeAddDTO, requestToken);
    

3.3 service 层规范

(1) 合理拆分 service 文件,如果业务较大,请拆分为多个 service

如订单业务,所有业务都写到 OrderService 中会导致文件过大,故需要进行拆分如下:

OrderQueryService 订单查询业务

OrderCreateService 订单新建业务

OrderDeliverService 订单发货业务

OrderValidatorService 订单验证业务

3.4 iml层规范一些开发规范

(1) 封装业务逻辑

如果你看过“屎山”你就会有深刻的感触,这特么一个方法能写几千行代码,还无任何规则可言…往往负责的人会说,这个业务太复杂,没有办法改善,实际上这都是懒的借口。不管业务再复杂,我们都能够用合理的设计、封装去提升代码可读性。下面贴两段高级开发(假装自己是高级开发)写的代码

正例 1:

@Transactional
public ChildOrder submit(Long orderId, OrderSubmitRequest.Shop shop) 
    ChildOrder childOrder = this.generateOrder(shop);
    childOrder.setOrderId(orderId);
    //订单来源 APP/微信小程序
    childOrder.setSource(userService.getOrderSource());
    // 校验优惠券
    orderAdjustmentService.validate(shop.getOrderAdjustments());
    // 订单商品
    orderProductService.add(childOrder, shop);
    // 订单附件
    orderAnnexService.add(childOrder.getId(), shop.getOrderAnnexes());
    // 处理订单地址信息
    processAddress(childOrder, shop);
    // 最后插入订单
    childOrderMapper.insert(childOrder);
    this.updateSkuInventory(shop, childOrder);
    // 发送订单创建事件
    applicationEventPublisher.publishEvent(new ChildOrderCreatedEvent(this, shop, childOrder));
    return childOrder;


正例 2:

@Transactional
public void clearBills(Long customerId) 
    // 获取清算需要的账单、deposit等信息
    ClearContext context = getClearContext(customerId);
    // 校验金额合法
    checkAmount(context);
    // 判断是否可用优惠券,返回可抵扣金额
    CouponDeductibleResponse deductibleResponse = couponDeducted(context);
    // 清算所有账单
    DepositClearResponse response = clearBills(context);
    // 更新 l_pay_deposit
    lPayDepositService.clear(context.getDeposit(), response);
    // 发送还款对账消息
    repaymentService.sendVerifyBillMessage(customerId, context.getDeposit(), EventName.DEPOSIT_SUCCEED_FLOW_REMINDER);
    // 更新账户余额
    accountService.clear(context, response);
    // 处理清算的优惠券,被用掉或者解绑
    couponService.clear(deductibleResponse);
    // 保存券抵扣记录
    clearCouponDeductService.add(context, deductibleResponse);


这段两代码里面其实业务很复杂,内部估计保守干了五万件事情,但是不同水平的人写出来就完全不同,不得不赞一下这个注释,这个业务的拆分和方法的封装。一个大业务里面有多个小业务,不同的业务调用不同的 service 方法即可,后续接手的人即使没有流程图等相关文档也能快速理解这里的业务,而很多初级开发写出来的业务方法就是上一行代码是 A 业务的,下一行代码是 B业务的,在下面一行代码又是 A 业务的,业务调用之间还嵌套这一堆单元逻辑,显得非常混乱,代码还多。

(2) 判断集合类型不为空的正确方式

很多人喜欢写这样的代码去判断集合

if (list == null || list.size() == 0) 
  return null;

当然你硬要这么写也没什么问题…但是不觉得难受么,现在框架中随便一个 jar 包都有集合工具类,比如

org.springframework.util.CollectionUtils、com.baomidou.mybatisplus.core.toolkit.CollectionUtils 。

以后请这么写

if (CollectionUtils.isEmpty(list) || CollectionUtils.isNotEmpty(list)) 
  return null;

(3) 集合类型返回值不要 return null

当你的业务方法返回值是集合类型时,请不要返回 null,正确的操作是返回一个空集合。你看 mybatis 的列表查询,如果没查询到元素返回的就是一个空集合,而不是 null。否则调用方得去做 NULL 判断,多数场景下对于对象也是如此。

(4) 映射数据库的属性尽量不要用基本类型

我们都知道 int/long 等基本数据类型作为成员变量默认值是 0。现在流行使用 mybatisplus 、mybatis 等 ORM 框架,在进行插入或者更新的时候很容易会带着默认值插入更新到数据库。我特么真想砍了之前的开发,重构的项目里面实体类里面全都是基本数据类型。

(5)封装判断条件

反例:

public void method(LoanAppEntity loanAppEntity, long operatorId) 
  if (LoanAppEntity.LoanAppStatus.OVERDUE != loanAppEntity.getStatus()
          && LoanAppEntity.LoanAppStatus.CURRENT != loanAppEntity.getStatus()
          && LoanAppEntity.LoanAppStatus.GRACE_PERIOD != loanAppEntity.getStatus()) 
    //...
    return;
  

这段代码的可读性很差,这 if 里面谁知道干啥的?我们用面向对象的思想去给 loanApp 这个对象里面封装个方法不就行了么?

public void method(LoanAppEntity loan, long operatorId) 
  if (!loan.finished()) 
    //...
    return;
  

LoanApp 这个类中封装一个方法,简单来说就是这个逻辑判断细节不该出现在业务方法中。

/**
 * 贷款单是否完成
 */
public boolean finished() 
  return LoanAppEntity.LoanAppStatus.OVERDUE != this.getStatus()
          && LoanAppEntity.LoanAppStatus.CURRENT != this.getStatus()
          && LoanAppEntity.LoanAppStatus.GRACE_PERIOD != this.getStatus();

(6) 参数传递不要使用Map、JSONObject或者实体类,使用DTO传递

使用Map或者JSONObject传递,后面维护的人员不知道传递的参数是什么,还要一一和前端核实,非常麻烦;实体类是用来封装数据的,而不是用来传递数据的,不能将它们两个混着使用,代码会很乱

(7)for里不建议写io。io包括:数据库、缓存,文件读写等

对数据库的操作是操作IO的,频繁的操作IO是非常耗性能的,因此代码当中,要杜绝编写循环操作数据库的代码,这里举个例子。

上图,改进后的代码虽然变多了一些,但是,避免了循环操作数据库,特别是当userIdList数据比较多或者数据库当中users表数据量比较大的时候,方式2的速度相比方式1肉眼可见的快。

(8) 禁止使用魔法值

反例

if (users.getType()==1)
            
 

正例: 定义一个枚举

@AllArgsConstructor
@Getter
public enum UserTypeEnum 
    ADMIN(1,"管理员"),
    TEACHER(2,"老师"),
    STUDENT(3,"学生");

    private Integer type;
    private String desc;


然后再使用

 if (users.getType()== UserTypeEnum.ADMIN.getType())
            
        

(9) 异常处理

捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

大量try catch 对代码阅读性和性能解析都不太友好

反例:

  @GetMapping("/list")
    public Result<Map<String, Object>> getPerformanceList(AdminPerformanceSearchVO searchVO, PageVO pageVO,
                                                          HttpServletRequest request) 
        logger.info("PC端订单查询请求参数:"+searchVO.toString());
        UserDO user = ShiroUtils.getUser();
        Query query = new Query(pageVO);
        Map<String, Object> params = MapperUtils.obj2Map(searchVO);
        query.putAll(params);
        try 
            formatParams(query);
         catch (DataFormatException e) 
            e.printStackTrace();
            logger.error(e.getMsg());
            return new ResultUtil<Map<String, Object>>().setErrorMsg(e.getMsg());
        
​
        Map<String, Object> map = null;
        try 
            map = adminAlyPerformanceService.getList(query, user);
         catch (BusinessException e) 
            e.printStackTrace();
            logger.error(e.getMessage());
            return new ResultUtil<Map<String, Object>>().setErrorMsg(e.getMessage());
        
        return new ResultUtil<Map<String, Object>>().setData(map);
    

正例:采用全局统一异常处理

全局统一异常
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler 
​
    @ExceptionHandler(value = DataFormatException.class)
    public Result<?> validationExceptionHandler(DataFormatException e) 
        log.error("系统异常DataFormatException:",e);
        BindingResult bindingResult = e.getBindingResult();
        String errorMessage = bindingResult.getFieldErrors().get(0).getDefaultMessage();
        return Result.failure(errorMessage);
    
    
     @ExceptionHandler(value = BusinessException.class)
    public Result<?> validationExceptionHandler(BusinessException e) 
        log.error("系统异常BusinessException:",e);
        BindingResult bindingResult = e.getBindingResult();
        String errorMessage = bindingResult.getFieldErrors().get(0).getDefaultMessage();
        return Result.failure(errorMessage);
    

然后使用:

@GetMapping("/list")
    public Result<Map<String, Object>> getPerformanceList(AdminPerformanceSearchVO searchVO, PageVO pageVO,
                                                  HttpServletRequest request) 
        UserDO user = ShiroUtils.getUser();
        Query query = new Query(pageVO);
        Map<String, Object> params = MapperUtils.obj2Map(searchVO);
        query.putAll(params);
        formatParams(query);
        Map<String, Object> map map = adminAlyPerformanceService.getList(query, user);
        return new ResultUtil<Map<String, Object>>().setData(map);
    

3.5 dao 层规范

(1) 优先使用 mybatis-plus 框架。如果需要多个数据源操作的,可以选择使用 SmartDb 框架。

1)所有 Dao 继承自 BaseMapper

2)禁止使用 Mybatis-plus 的 Wrapper 条件构建器

3)禁止直接在 mybatis xml 中写死常量,应从 dao 中传入到 xml 中

4)建议不要使用星号 * 代替所有字段

正例:

    NoticeDao.java

    Integer noticeCount(@Param("sendStatus") Integer sendStatus);
---------------------------------------------
    NoticeMapper.xml

    <select id="noticeCount" resultType="integer">
        select
        count(1)
        from t_notice
        where
        send_status = #sendStatus
    </select>

反例:

    NoticeDao.java

    Integer noticeCount();
---------------------------------------------
    NoticeMapper.xml

    <select id="noticeCount" resultType="integer">
        select
        count(1)
        from t_notice
        where
        send_status = 0
    </select>

(2) dao层方法命名规范

获取单个对象的方法用 get 做前缀。

获取多个对象的方法用 list 做前缀。

获取统计值的方法用 count 做前缀。

插入的方法用 save/insert 做前缀。

删除的方法用 remove/delete 做前缀。

修改的方法用 update 做前缀。

建议:dao层方法命名尽量以sql语义命名,避免与业务关联。

正例:

List<PerformanceDTO> listByMonthAndItemId(@Param("month") String month, @Param("itemId") Integer itemId);

反例:

List<PerformanceDTO> getInternalData(@Param("month") String month, @Param("itemId") Integer itemId);

反例中出现的不规范操作:

get代表单个查询,批量查询的应该 list 开头。
命名与业务关联,局限了dao方法的使用场景和范围,降低了方法的复用性,造成他人困惑以及重复造轮子。

四 数据库 规范

4.1 建表规范

表必备三字段:id, create_time, update_time

id 字段 Long 类型,单表自增,自增长度为 1

create_time 字段 datetime 类型,默认值 CURRENT_TIMESTAMP

update_time 字段 datetime 类型,默认值 CURRENT_TIMESTAMP, On update CURRENT_TIMESTAMP

4.2 枚举类表字段注释需要将所有枚举含义进行注释

修改或增加字段的状态描述,必须要及时同步更新注释。
如下表的 sync_status 字段 同步状态 0 未开始 1同步中 2同步成功 3失败。

正例:

CREATE TABLE `t_change_data` (
	`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
	`sync_status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '同步状态 0 未开始 1同步中 2同步成功 3失败',
	`sync_time` DATETIME NULL DEFAULT NULL COMMENT '同步时间',
	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`update_time` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (`change_data_id`)
)

反例:

CREATE TABLE `t_change_data` (
	`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
	`sync_status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '同步状态 ',
	`sync_time` DATETIME NULL DEFAULT NULL COMMENT '同步时间',
	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`update_time` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (`change_data_id`)
)

4.3 合理结合业务给表字段添加索引和唯一索引

具体索引规范请参照《阿里巴巴 Java 开发手册》索引规约

4.4 数据库常用

(1)命名风格:小写字母,下划线命名风格

(2)数据库参数
数据库引擎:InnoDB;
字符集:utf8;
字符校对集:generic_utf8_ci

(3)数据库表
表名:前缀为项目名称首字母缩写,单词之间用下划线分割,单数形式;

必备三字段:id,create_time,update_time

id:逻辑主键

create_time:创建时间

update_time:更新时间

(4)主键

逻辑主键:无实际含义,用于唯一标识每个记录

业务主键:有实际含义

(5)属性类型

逻辑主键:bigint,无符号自增

业务主键:varchar,后端生成随机uuid

逻辑值:tinyint

日期:date

时间:timestamp(时间戳

字符串:varchar,需要指定长度(字段长度应该统一并且适当大一点,否则未知的变化会导致前后端的频繁修改)。

好了,今天就聊到这里,祝各位终有所成,收获满满!

我是脚丫先生,我们下期见~

以上是关于大数据平台后端一些开发规范的主要内容,如果未能解决你的问题,请参考以下文章

什么是移动前端开发

Java后端开发(经理)-全中国最大肿瘤基因组数据库!!医疗大数据平台

好岗位2:大数据/java/后端/.net你要的好岗位,慢慢看

大数据平台中,数据仓库如何分层?

SAAS后端开发技术总监| 北京,杭州

中国电信大数据找后端架构师和后端开发经理