Startup in MeiTuan

本文记录了一个简易Java开发流程和服务部署和调用的学习过程

介绍

流程

  • 在A服务写一套CRUD,对外提供RPC接口
  • 在B服务中调用A服务提供的接口,完成数据CRUD
  • 利用这个项目对接http网关

涉及的技术

  • MDP(spring boot)
  • RPC接口用到thrift,公司内部为MTthrift
  • 服务注册
  • kafka
  • redis

CRUD实现

DO, Example, mapper生成

使用MyBatis-generator自动生成,注意在generatorConfig.xml中配置文件路径,表名和实体类名。

另外注意插件的使用必须在对应的模块(dao模块)下(我一开始直接使用主目录下的generator导致出现了找不到路径的错误)

一共会生成四个文件:XX.java(实体DO),XXExample.java, XXMapper.java, XXMapper.xml

测试类

测试CRUD的API和example相关API,注意测试类要继承BaseTest类

提供RPC接口

(注意这里的代码都不符合工程的规范,仅仅只是简单的尝试使用)

在client模块下:

  • 写一个CRUDService接口,提供所需要的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ThriftService
    public interface CRUDService {

    @ThriftMethod
    public CRUDResponse method1(CRUDRequest testRequest) throws TException;

    @ThriftMethod
    public Long method2(int i) throws TException;

    }
  • 写一个CRUDResponse和CRUDRequest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @ThriftStruct
    public class CRUDRequest {
    private Integer id;
    private String name;

    @ThriftField(value = 1, requiredness = ThriftField.Requiredness.REQUIRED)
    public Integer getId(){
    return id;
    }

    @ThriftField
    public void setId(Integer id){
    this.id = id;
    }

    @ThriftField(2)
    public String getName(){
    return name;
    }

    @ThriftField
    public void setName(String name){
    this.name = name;
    }

    @Override
    public String toString(){
    return "CRUDRequest{" +
    "id=" + id +
    ", name=" + name +
    '}';
    }
    }

在service模块下:

  • 对CRUDService进行实现,写一个CRUDServiceImpl类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Slf4j
    @MdpThriftServer
    public class CRUDServiceImpl implements CRUDService {

    @Resource
    TempDataDOMapper tempDataDOMapper;

    @Resource
    FunctionConfigDOMapper functionConfigDOMapper;

    @Override
    public CRUDResponse method1(CRUDRequest testRequest) throws TException {
    return null;
    }

    @Override
    public Long method2(int i) throws TException {
    System.out.println(tempDataDOMapper.selectByPrimaryKey(i));
    return (long)i;
    }
    }

    @Slf4j:slf4j是一个日志标准,使用它可以完美的桥接到具体的日志框架,必要时可以简便的更换底层的日志框架,而不需要关心具体的日志框架的实现(slf4j-simple、logback等)。

    @Override 在java中如果方法上加@Override的注解的话,表示子类重写了父类的方法。当然也可以不写,写的好处是:

    • 可读性提高
    • 编译器会校验写的方法在父类中是否存在

测试

在测试类中可以直接调用CRUDService接口进行方法的调用

1
2
3
4
5
6
7
8
@Resource
CRUDService crudService;

@Test
public void testImpl() throws TException {
Long id = crudService.method2(1);
System.out.println(id);
}

服务的部署

注意点

  • 注意修改版本号,如从0.0.5到0.0.6-SNAPSHOT

  • 注意一定不要合并到master分支上去,要使用自己的分支

    1
    2
    3
    4
    5
    6
    # 查看分支
    git branch
    #切换到自己的分支
    git checkout -b zhangqi-local

    git push --set-upstream origin zhangqi-local
  • 使用git进行提交和push

    1
    2
    git add .
    git commit -a -m "描述文字"

    注意也可以通过非命令的方式提交和push,可以查看修改的地方 【根目录】==》【Git】 ==》【提交目录】

服务的发布流程

  • Devtools中的Services中找到对应的服务

    【To Maven】==》【To Maven】==》【代码配置】(填写自己的分支)==》【To Maven】

  • Devtools中的Cargo中,进入自己的机器。构建服务,选择自己的分支,进行构建,直到状态为success,在【机器列表】中可以看到主机名

  • 进入OCTO,调整为线下模式。【用户自检】==》【MTthrift自检】==》【切换到test】==》【接口调用】==》选择对应泳道和主机==》选择要进行测试的服务接口和调用方法==》选择【不配置】压测标识==》设置方法的参数,点击【执行】即可查看调用结果

服务的调用

  • 在新的服务下,首先在service模块下的pom.xml和主目录下的pom.xml中要引入相应的dependency

  • 写一个service,注意@Service注解需要使用spring包下的,否则会扫描不到。在类内使用@MdpThriftClient调用远程的服务,注意要写上remoteAppKey,其可以在上传的服务的resource目录下找到,也可以在devtools网站下找到,注意不需要设置端口号,让服务自己去配置。另外可能会报无法自动装配的问题(红线),可以不用理会。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import com.meituan.mdp.boot.starter.thrift.annotation.MdpThriftClient;
    import com.sankuai.grocerymkthd.interact.tools.client.request.test.CRUDRequest;
    import com.sankuai.grocerymkthd.interact.tools.client.response.test.CRUDResponse;
    import com.sankuai.grocerymkthd.interact.tools.client.service.CRUDService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.thrift.TException;
    import org.springframework.stereotype.Service;

    /**
    * @author zhu
    */
    @Slf4j
    @Service
    public class CRUDMTService {
    @MdpThriftClient(
    remoteAppKey = "com.sankuai.grocerymkthd.interact.tools",
    timeout = 10000
    )
    private CRUDService crudService;

    public CRUDResponse getResponse(CRUDRequest request) throws TException {
    return crudService.method1(request);
    }

    public int getSelection(int id) throws TException{
    return crudService.selectById(id);
    }
    }
  • 在单元测试UT中,要指定访问的泳道,因为我们只在自己的泳道上部署了服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import com.meituan.mtrace.Tracer;
    import com.sankuai.grocerymkthd.interact.mapi.BaseTest;
    import com.sankuai.grocerymkthd.interact.mapi.simple.CRUDMTService;
    import com.sankuai.grocerymkthd.interact.tools.client.request.test.CRUDRequest;
    import org.apache.thrift.TException;
    import org.junit.Test;

    import javax.annotation.Resource;

    public class CRUDServiceTest extends BaseTest {

    @Resource
    private CRUDMTService crudmtService;

    @Test
    public void test01() throws TException {
    // 要指定访问的泳道,因为只在自己的泳道上部署了服务
    Tracer.setSwimlane("zhuzhangqi-yzwgj");
    System.out.println(crudmtService.getResponse(new CRUDRequest()));
    System.out.println(crudmtService.getSelection(1));
    }
    }

与HTTP网关的对接

公司内部使用Shepherd

  • 创建分组

  • 创建API,两种方式

    • 【API分组管理】==》【API管理】==》【新建API】(传统方式)
    • 【API分组管理】==》【API管理】==》【快速创建API】(推荐),输入API名称、后端服务Appkey、服务名、方法名即可完成创建
  • 创建完API后,可根据需要将路径、服务参数、返回值进行个性化修改

    • 注意要使用自己的服务要配置泳道:

      • 方法1:在【http header】或【url query】信息里加swimlane字段,字段值为需要路由的泳道服务
      • 方法2:如果不希望前端传递泳道信息,可以配置Tracer组件泳道参数,Tracer-Key固定为INF_SWIMLANE,Tracer_Value为泳道名称
    • 注意要在【后端请求定义】配置相应的入参,注意DSL表达式的写法

  • 发布API

  • 用POSTMAN测试API,通过查看API文档,可以看到请求路径和path信息

    • 通过POSTMAN请求url(其中{id}为路径参数,类型为整数型,要用实际参数替换)
    • 可以在【Params】中给相应的参数赋值
    • DSL指南见appendix

Appendix

Mybatis中的example类简介

什么是example类

mybatis-generator会为每个字段产生Criterion,为底层的mapper.xml创建动态sql。如果表的字段比较多,产生的example类会十分庞大。理论上通过example类可以构造你想到的任何筛选条件。在mybatis-generator中加以配置,配置数据表的生成操作就可以自动生成example了。

了解example成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//作用:升序还是降序
//参数格式:字段+空格+asc(desc)
protected String orderByClause;
//作用:去除重复
//true是选择不重复记录,false,反之
protected boolean distinct;
//自定义查询条件
//Criteria的集合,集合中对象是由or连接
protected List<Criteria> oredCriteria;
//内部类Criteria包含一个Cretiron的集合,
//每一个Criteria对象内包含的Cretiron之间是由 AND连接的
public static class Criteria extends GeneratedCriteria {
protected Criteria() {super();}
}
//是mybatis中逆向工程中的代码模型
protected abstract static class GeneratedCriteria {......}
//是最基本,最底层的Where条件,用于字段级的筛选
public static class Criterion {......}

example使用前的准备

比如我的TempDataDOExample是根据TempData表生成的,TempDataDOMapper属于dao层,TempDataDOMapper.xml是对应的映射文件。

1
2
3
4
5
6
7
8
9
TempDataDOMapper tempDataDOMapper = new TempDataDOMapper();

//UserMapper接口:
long countByExample(CompetingStoreExample example);
List<CompetingStore> selectByExample(CompetingStoreExample example);

//在我们的测试类里:
TempDataDOExample example = new TempDataDOExample();
TempDataDOExample.Criteria criteria = example.createCriteria();

查询用户数量

1
2
// select count(*) from temp_data
long count = tempDataDOMapper.countByExample(example);

where条件查询或多条件查询

(注意以下不是对应TempData的代码)

1
2
3
4
5
6
7
8
9
10
// select * from user where name={#user.name} and sex={#user.sex} order by age asc;
example.setOrderByClause(“age asc"); //升序
example.setDistinct(false); //不去重
if(!StringUtils.isNotBlank(user.getName())){
Criteria.andNameEqualTo(user.getName());
}
if(!StringUtils.isNotBlank(user.getSex())){
Criteria.andSexEqualTo(user.getSex());
}
List<User> userList=userMapper.selectByExample(example);

模糊查询

1
2
3
4
5
// select * from user where name like %{#user.name}%
if(!StringUtils.isNotBlank(user.getName())){
criteria.andNameLIke(‘%’+name+’%’);
}
List<User> userList=userMapper.selectByExample(example);

分页查询

1
2
3
4
5
6
7
// select * from user limit start to rows
int start = (currentPage - 1) * rows;
//分页查询中的一页数量
example.setPageSize(rows);
//开始查询的位置
example.setStartRow(start);
List<User> userList=userMapper.selectByExample(example);

POSTMAN的简单使用

使用POSTMAN进行接口测试

  • 新建Collections,相当于在POSTMAN中的一个文件夹
  • 添加request
  • 点击发送

显示的内容

  • Params:显示get请求参数
  • Authorization:身份认证
  • Headers:请求头信息

DSL的使用

参数类型

参数类型主要分为三种类型:请求参数、上下⽂参数、服务返回结果参数

\1. 请求参数:请求参数⽀持path路径取值、query取值、header取值、cookie取值、body取值(⽀持 json、表单等)。

\2. 上下⽂参数:上下⽂参数主要⽀持⽤户⾃定义插件中可编程的取值⽅式。Shepherd Context取值汇总

\3. 服务返回结果参数:请求后端服务后返回的结果可作为参数以待取值。

表达式类型

\1. JsonPath(默认):

表达式类型⽀持JsonPath标准:JSONPath-简单⼊⻔

Jayway JsonPath - A Java DSL for reading JSON documents.

\2. Freemarker:

对于表达式有特殊要求,⽐如说在返回的Bean列表中,希望屏蔽Bean的某⼀个field时,可以使⽤

Freemarker模板语⾔实现。

具体请参⻅:Freemarker 表达式使⽤指南

JsonPath表达式取值示例

服务调⽤参数和返回值Schema填写举例:

后端服务输⼊参数部分采⽤RPC框架泛化json-simple调⽤格式。

先⽤com.dianping.pigeon.remoting.common.codec.json.SimpleJacksonUtils#serialize⽣成Json格式。

然后对需要从请求参数或上下⽂取值的部分⽤DSL表达式替换。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022 ZHU
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信