3

前言

最近有项目需求是通过gitlab的api获取其中的数据。来记录一下我的踩坑的过程。
首先先尝试了获取users数据,即所有用户的数据。

这里是gitlab官方API文档 https://docs.gitlab.com/ee/ap...

这里说一下我刚开始的做法和老师建议的做法。

最开始的做法

1. 获取Admin access token

首先我们需要获取gitlab的Admin access token, 并且在请求的时候附带这个token,gitlab才能认证我们的身份并返回数据。

1.登录 GitLab。 2.登录后,点击右上角的头像图标然后选择 Preferences。

3.在 access token 界面就可以新建token了。

当你是group的管理员之后,就可以通过该token获取users了。

2.使用spring boot单元测试发起请求

这里我使用了apache发起http请求,并使用了fastjson处理请求后所返回的json数据。

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.9</version>
</dependency>

 <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

以下是我测试的代码。来解释一下:

  1. 首先定义你的要请求的url,格式如代码。也可以看官方文档。
  2. 构建Http请求类,并设置参数。参数必须要有private_token。这里我加的 without_project_bots可以过滤机器人user。
  3. 使用execute函数发起get请求,并接收返回的json数据。
  4. 从json数组转换成实体数组

这样就获得了我们需要的数据。

@Test
  void getUsers() throws IOException, URISyntaxException {

    String url = "https://your gitlab address/api/v4/users";


    CloseableHttpClient httpclients = HttpClients.createDefault();
    HttpGet httpGet = new HttpGet(url);
    // 设置参数信息
    URI uri = new URIBuilder(httpGet.getURI())
            .addParameter("private_token", "your amind token")
            .addParameter("without_project_bots", "true")
            .build();
    ((HttpRequestBase) httpGet).setURI(uri);

    // 发起请求
    CloseableHttpResponse response = httpclients.execute(httpGet);

    // 处理数据
    HttpEntity httpEntity = (HttpEntity) response.getEntity();
    String result =  EntityUtils.toString(httpEntity,"UTF-8");

    // 从json转换成实体
    final ObjectMapper objectMapper = new ObjectMapper();
    User[] langList = objectMapper.readValue(result, User[].class);

    logger.debug(String.valueOf(langList));

    response.close();
  }

期间我遇到的问题

1. java需要添加证书

由于我们发起的请求地址是HTTPS的,而https是对链接加了安全证书SSL的。
对方的接口需要特定的证书才能请求。如果服务器中没有相关链接的SSL证书,就会报sun.security.validator.ValidatorException错误。

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

最开始我是把证书添加到本地中的,添加方法大概参考了如下博客
https://www.cnblogs.com/zoro-...

这样就把证书添加到了本地的java密钥库中。

但是这样只有本地能够正常使用这个项目。因为在并没有把证书添加到项目中。所以这也说明了这种方法并不普适。

所以我们需要把证书放在src/main/resourse中,部署https。下面我会说一下如何添加。

2. 没有配置数据源

Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]

image.png

由于我只是在单元测试中测试,项目刚初始化,并没有连接数据库,也就是并没有配置数据源。

所以因为没有向 Spring Boot 传递足够的数据来配置数据源,导致发生了该报错。

所以,如果我们暂时不需要数据源,就可以使用exclude={DataSourceAutoConfiguration.class}自动配置。 这样就可以解决报错

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class NonlycodeApplication {
}

3. json数据的属性多于实体的属性

在我请求到数据后,可以看到gitlab给我们返回来的数据有很多。有theme_id, note,is_admin等信息。
image.png


但是, 我定义的实体中目前只定义了几条需要的信息,比如id ,name,username等信息。

public class User implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  private Long id;

  private String email = "";

  private Integer following = 0;

  private Integer followers = 0;

  private String name = "";

  private  String password = "yunzhi";

  @Column(nullable = false, unique = true)
  private String username;

在json转化数据的时候,遇到这种情况就会报错。并且报错说:not marked as ignorable。
image.png

所以我们需要添加注释,把不需要的数据给忽略掉。

我的解决方法是添加 @JsonIgnoreProperties(ignoreUnknown = true)
最后报错消失。

@JsonIgnoreProperties(ignoreUnknown = true)
@Entity
public class User implements Serializable {

使用RestTemplate访问https

访问https需要认证。又分为单向认证和双向认证。

而访问gitlab API单向认证就可以了,也就是只要认证gitlab的CA证书就可以了。

而只要我们使用的HTTP请求库支持HTTPS,就可以自动做到单向认证。

传统情况下在java代码里访问restful服务,一般使用Apache的HttpClient。如同我上面的代码。不过此种方法使用起来太过繁琐。spring提供了一种简单便捷的模板类来进行操作,这就是RestTemplate。

由于RestTemplate 不支持HTTPS, 所以我们需要封装一下。

参考了这篇文章https://blog.csdn.net/MyronCh...

1. 新建工厂类

image.png

2. 配置工厂类

public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
  @Override
  protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
    try {
      if (!(connection instanceof HttpsURLConnection)) {
        throw new RuntimeException("An instance of HttpsURLConnection is expected");
      }

      HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;

      TrustManager[] trustAllCerts = new TrustManager[]{
              new X509TrustManager() {
                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                  return null;
                }
                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }

              }
      };
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
      httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));

      httpsConnection.setHostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String s, SSLSession sslSession) {
          return true;
        }
      });

      super.prepareConnection(httpsConnection, httpMethod);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  private static class MyCustomSSLSocketFactory extends SSLSocketFactory {

    private final SSLSocketFactory delegate;

    public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
      this.delegate = delegate;
    }

    // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
    // 这些默认的服务的最低质量要求保密保护和服务器身份验证
    @Override
    public String[] getDefaultCipherSuites() {
      return delegate.getDefaultCipherSuites();
    }

    // 返回的密码套件可用于SSL连接启用的名字
    @Override
    public String[] getSupportedCipherSuites() {
      return delegate.getSupportedCipherSuites();
    }


    @Override
    public Socket createSocket(final Socket socket, final String host, final int port,
                               final boolean autoClose) throws IOException {
      final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
      return overrideProtocol(underlyingSocket);
    }


    @Override
    public Socket createSocket(final String host, final int port) throws IOException {
      final Socket underlyingSocket = delegate.createSocket(host, port);
      return overrideProtocol(underlyingSocket);
    }

    @Override
    public Socket createSocket(final String host, final int port, final InetAddress localAddress,
                               final int localPort) throws
            IOException {
      final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
      return overrideProtocol(underlyingSocket);
    }

    @Override
    public Socket createSocket(final InetAddress host, final int port) throws IOException {
      final Socket underlyingSocket = delegate.createSocket(host, port);
      return overrideProtocol(underlyingSocket);
    }

    @Override
    public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
                               final int localPort) throws
            IOException {
      final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
      return overrideProtocol(underlyingSocket);
    }

    private Socket overrideProtocol(final Socket socket) {
      if (!(socket instanceof SSLSocket)) {
        throw new RuntimeException("An instance of SSLSocket is expected");
      }
      //((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
      ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
      return socket;
    }
  }

3. 编写代码

User 实体:

@JsonIgnoreProperties(ignoreUnknown = true)
@Entity
public class User implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @JsonView
  private Long id;

  @JsonView
  private String email = "";

  @JsonView
  private Integer following = 0;

  @JsonView
  private Integer followers = 0;

  @JsonView
  private String name = "";

  private  String password = "yunzhi";

  @JsonView
  private String username;


  public void setId(Long id) {this.id = id;}

  public Long getId() {return this.id;}

  public void setEmail(String email) {this.email = email;}

  public String getEmail() {return this.email;}

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

  public String getName() {return this.name;}

  public void setUsername(String username) {this.username = username;}

  public String getUsername() {return this.username;}

  public void setFollowing(Integer following) {this.following = following;}

  public Integer getFollowing() {return this.following;}

  public void setFollowers(Integer followers) {this.followers = followers;}

  public Integer getFollowers() {return this.followers;}



}

controller层:

@RestController
@RequestMapping("user")
public class UserController {

  @Autowired
  UserService userService;


  @GetMapping("/getUsers")
  @JsonView(GetUsersJsonView.class)
  public User[] getUsers() throws JsonProcessingException {
    return  this.userService.getUsers();
  }

  public class GetUsersJsonView {
  }
}

service层:
注意填写自己的url和token。 逻辑跟之前的代码差不多。

@Override
  public User[] getUsers() throws JsonProcessingException {
    String url = "https://your gitlab address /api/v4/users/" +
            "?private_token={private_token}&without_project_bots={without_project_bots}";

    final Map<String, String> variables = new HashMap<>();

    variables.put("private_token", "your token");
    variables.put("without_project_bots", "true");

    RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory());
    String response = restTemplate.getForObject(url, String.class, variables);


    // 从json转换成实体
    final ObjectMapper objectMapper = new ObjectMapper();
    User[] userList = objectMapper.readValue(response, User[].class);

    return userList;
  }

4. url访问

image.png

5. 结果

image.png

成功获取到信息。

总结

经过这次对gitlab API的请求尝试,感觉自己对如何进行请求、如何处理返回的json数据有了更深的理解。同时也感觉自己对https,以及SSL证书等的理解还不足,之后还需要补一下相关知识。


weiweiyi
1k 声望123 粉丝