在一些實(shí)際情況中,希望能夠直接像讀取本地文件一樣讀取遠(yuǎn)程倉庫中的文件內(nèi)容贷屎,避免git操作失敗的情況下讀取的本地緩存的文件內(nèi)容贺氓。由于項(xiàng)目使用gitLab管理配置文件,查詢了GitLabApi续捂,其提供了諸多API接口毫玖,包括常見的git操作、項(xiàng)目管理以及我們需要的獲取文件內(nèi)容等接口。
1.接口分析
查詢GitLab api宜雀,可以容易找到獲取文件內(nèi)容的API文檔:GitLab獲取倉庫中文件內(nèi)容,可以發(fā)現(xiàn)怕享,其格式要求為:
GET /projects/:id/repository/files/:file_path
curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'
通過分析可以發(fā)現(xiàn)提澎,如果想獲取倉庫中文件內(nèi)容,需要以下幾個(gè)要素:
- 倉庫地址
- 項(xiàng)目id
- 用戶的private token
- 經(jīng)過url編碼的文件全路徑
- 文件所在的分支
由此侮邀,我在實(shí)現(xiàn)時(shí)將API接口整理為一個(gè)常量:
private static String GITLAB_FILECONTENT_API = "http://#{REPO_IP}/api/v3/projects/#{PROJECT_ID}/repository/files?private_token=#{PRIVATE_TOKEN}&file_path=#{FILE_PATH}&ref=#{BRANCH_NAME}";
2.獲取用戶的private token
GitLabApi關(guān)于獲取個(gè)人口令的API可以整理為常量類:
private static String GITLAB_SESSION_API = "http://#{REPO_IP}/api/v3/session?login=#{USER_NAME}&password=#{PASSWORD}";
提供如下3個(gè)變量即可獲取指定用戶的token:
- 倉庫地址
- 用戶名
- 密碼
經(jīng)過簡(jiǎn)單的json解析就能獲取到結(jié)果坏怪,代碼如下:
/**
* 根據(jù)用戶名稱和密碼獲取gitlab的private token,為Post請(qǐng)求
*
* @param ip gitlab倉庫的ip
* @param userName 登陸gitlab的用戶名
* @param password 登陸gitlab的密碼
* @return 返回該用戶的private token
*/
public static String getPrivateTokenByPassword(String ip, String userName, String password) {
/** 1.參數(shù)替換绊茧,生成獲取指定用戶privateToken地址 */
// 校驗(yàn)參數(shù)
Objects.requireNonNull(ip, "參數(shù)ip不能為空铝宵!");
Objects.requireNonNull(userName, "參數(shù)userName不能為空!");
Objects.requireNonNull(password, "參數(shù)password不能為空华畏!");
// 參數(shù)準(zhǔn)備鹏秋,存入map
Map<String, String> params = new HashMap<String, String>(4);
params.put("REPO_IP", ip);
params.put("USER_NAME", userName);
params.put("PASSWORD", password);
// 調(diào)用工具類替換,得到具體的調(diào)用地址
String reqUserTokenUrl = PlaceholderUtil.anotherReplace(GITLAB_SESSION_API, params);
sysLogger.debug(String.format("獲取用戶:%s的private token地址為:%s", userName,reqUserTokenUrl));
/** 2.訪問url,獲取指定用戶的信息 */
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(reqUserTokenUrl, null,String.class);
sysLogger.debug(String.format("響應(yīng)頭為:%s亡笑,響應(yīng)體為:%s", response.getHeaders(), response.getBody()));
/** 3.解析結(jié)果 */
String body = response.getBody();
JSONObject jsonBody = JsonUtil.parseObjectToJSONObject(body);
String privateToken =jsonBody.getString("private_token");
sysLogger.debug(String.format("獲取到用戶:%s的privateToken為:%s", userName, privateToken));
/** 4.返回privateToken */
return privateToken;
}
注意侣夷,這個(gè)接口時(shí)POST請(qǐng)求,正確的HTTP響應(yīng)碼是201仑乌,而非200.
3.獲取項(xiàng)目的projectId
projectId可以直接到gitlab具體項(xiàng)目信息中查找百拓,但是也有相關(guān)的api可以依據(jù)項(xiàng)目名稱查詢。從使用的角度講绝骚,配置項(xiàng)目的名稱更加方便耐版,配置項(xiàng)目的projectId增加配置項(xiàng),且如果提供獲取projectId的方法压汪,內(nèi)部調(diào)用可以去掉該項(xiàng)的配置粪牲。
獲取projectId的api參考:獲取指定項(xiàng)目的projectId,需要如下3個(gè)要素:
- 倉庫ip
- 項(xiàng)目id:The ID or URL-encoded path of the project止剖,即提供id或者是項(xiàng)目path腺阳,需要經(jīng)url編碼(namespace + projectName)落君,參見:項(xiàng)目path的url編碼
- private token
提取出常量類:
private static String GITLAB_SINGLE_PROJECT_API = "http://#{REPO_IP}/api/v3/projects/#{PROJECT_PATH}?private_token=#{PRIVATE_TOKEN}";
然后對(duì)響應(yīng)碼為200的正確json返回結(jié)果信息解析即可得到,代碼如下:
/**
* 使用gitLab api獲取指定項(xiàng)目的projectId亭引,為Get請(qǐng)求
*
* @param ip 項(xiàng)目倉庫的ip地址
* @param projectPath 項(xiàng)目的path绎速,如:http://192.168.59.185/acountting/dispatcher-cloud.git,則projectPath為:acountting/dispatcher-cloud
* @param privateToken 用戶個(gè)人訪問gitlab庫時(shí)的privateToken焙蚓,可以通過{@link GitLabAPIUtils#getPrivateTokenByPassword}獲取
* @return 返回目的projectId
*/
public static String getProjectId(String ip, String projectPath, String privateToken) {
/** 1.參數(shù)替換纹冤,生成訪問獲取project信息的uri地址 */
// 校驗(yàn)參數(shù)
Objects.requireNonNull(ip, "參數(shù)ip不能為空!");
Objects.requireNonNull(projectPath, "參數(shù)projectPath不能為空购公!");
Objects.requireNonNull(privateToken, "參數(shù)privateToken不能為空萌京!");
// 參數(shù)準(zhǔn)備,存入map
Map<String, String> params = new HashMap<String, String>(4);
params.put("REPO_IP", ip);
params.put("PRIVATE_TOKEN", privateToken);
// gitlab api要求項(xiàng)目的path需要安裝uri編碼格式進(jìn)行編碼宏浩,比如"/"編碼為"%2F"
try {
params.put("PROJECT_PATH", URLEncoder.encode(projectPath, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(String.format("對(duì)%s進(jìn)行URI編碼出錯(cuò)知残!", projectPath));
}
// 調(diào)用工具類替換,得到具體的調(diào)用地址
String getSingleProjectUrl = PlaceholderUtil.anotherReplace(GITLAB_SINGLE_PROJECT_API, params);
sysLogger.debug(String.format("獲取projectId的url:%s", getSingleProjectUrl));
// 創(chuàng)建URI對(duì)象
URI url = null;
try {
url = new URI(getSingleProjectUrl);
} catch (URISyntaxException e) {
throw new RuntimeException(String.format("使用%s創(chuàng)建URI出錯(cuò)!", getSingleProjectUrl));
}
/** 2.訪問url比庄,獲取制定project的信息 */
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> reslut = restTemplate.getForEntity(url, String.class);
sysLogger.debug(String.format("響應(yīng)頭為:%s求妹,響應(yīng)體為:%s", reslut.getHeaders(), reslut.getBody()));
/** 3.解析結(jié)果 */
if (reslut.getStatusCode() != HttpStatus.OK ) {
throw new RuntimeException(String.format("請(qǐng)求%s出錯(cuò)!錯(cuò)誤碼為:%s", url, reslut.getStatusCode()));
}
// 如果響應(yīng)碼是200佳窑,說明正常拿到響應(yīng)結(jié)果制恍,解析出projectId返回即可
JSONObject responBody = JsonUtil.parseObjectToJSONObject(reslut.getBody());
String projectRepo = responBody.getString("http_url_to_repo");
String projectId = responBody.getString("id");
sysLogger.info(String.format("獲取到項(xiàng)目:%s的projectId為:%s", projectRepo, projectId));
/** 4.返回projectId */
return projectId;
}
需要特別注意,在方法中對(duì)配置好的project path進(jìn)行url編碼后华嘹,沒有直接使用RestTemplate創(chuàng)建get請(qǐng)求獲取項(xiàng)目信息吧趣,因?yàn)閷?shí)踐中發(fā)現(xiàn)會(huì)出現(xiàn)將本義編碼好的如:src%2FHelloWorld.java變?yōu)椋簊rc%256FHelloWorld.java法竞,具體沒有深入RestTemplate源碼耙厚,所以直接創(chuàng)建URI對(duì)象,避免這種情況出現(xiàn)岔霸。
4 獲取倉庫文件內(nèi)容
api參考:gitlab獲取倉庫文件內(nèi)容薛躬,需要提供3個(gè)參數(shù):
- private token
- projectId
- 文件全路徑,需經(jīng)過url編碼呆细,如: main%2Fclass%HelloWorld.java
- 文件所在分支branch
整理出具體api的常量類:
private static String GITLAB_FILECONTENT_API = "http://#{REPO_IP}/api/v3/projects/#{PROJECT_ID}/repository/files?private_token=#{PRIVATE_TOKEN}&file_path=#{FILE_PATH}&ref=#{BRANCH_NAME}";
返回的內(nèi)容json格式如下:
{
"file_name": "HelloWorld.java",
"file_path": "main/class/HelloWorld.java",
"size": 1476,
"encoding": "base64",
"content": "IyA9PSBTY2hlbWEgSW5mb3...",
"content_sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481",
"ref": "master",
"blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
"commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
"last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
}
特別需要注意的是返回的文件內(nèi)容是base64編碼型宝,拿到結(jié)果后需要解碼才能獲取原始內(nèi)容。如果想直接獲取原始文件內(nèi)容:獲取原始文件內(nèi)容
所以獲取方法如下:
public static String getFileContentFromRepository(String ip,String projectPath,String userName,String password,
String fileFullPath, String branchName) throws Exception {
// 校驗(yàn)參數(shù)
Objects.requireNonNull(ip, "參數(shù)ip不能為空絮爷!");
Objects.requireNonNull(projectPath, "參數(shù)projectPath不能為空趴酣!");
Objects.requireNonNull(userName, "參數(shù)userName不能為空!");
Objects.requireNonNull(password, "參數(shù)password不能為空坑夯!");
Objects.requireNonNull(fileFullPath, "參數(shù)fileFullPath不能為空岖寞!");
Objects.requireNonNull(branchName, "參數(shù)branchName不能為空!");
/** 1.依據(jù)用戶名柜蜈、密碼獲取到用戶的privateToken */
String privateToken = getPrivateTokenByPassword(ip, userName, password);
/** 2.使用privateToken獲取項(xiàng)目的projectId */
String projectId = getProjectId(ip, projectPath, privateToken);
/** 3.使用參數(shù)替換形成請(qǐng)求git庫中文件內(nèi)容的uri */
// 參數(shù)準(zhǔn)備仗谆,存入map
Map<String, String> params = new HashMap<String, String>(4);
params.put("REPO_IP", ip);
params.put("PROJECT_ID", projectId);
params.put("PRIVATE_TOKEN", privateToken);
params.put("FILE_PATH", fileFullPath);
params.put("BRANCH_NAME", branchName);
// 使用工具類替換參數(shù)
String reqFileCotnetUri = PlaceholderUtil.anotherReplace(GITLAB_FILECONTENT_API, params);
sysLogger.debug(String.format("獲取文件:%s的uri:%s", fileFullPath, reqFileCotnetUri));
/** 4.請(qǐng)求gitlab獲取文件內(nèi)容 */
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(reqFileCotnetUri, String.class);
sysLogger.debug(String.format("響應(yīng)頭為:%s指巡,響應(yīng)體為:%s", response.getHeaders(), response.getBody()));
/** 5.解析響應(yīng)結(jié)果內(nèi)容 */
String body = response.getBody();
JSONObject jsonBody = JsonUtil.parseObjectToJSONObject(body);
String fileName = jsonBody.getString("file_name");
String filePath = jsonBody.getString("file_path");
String encoding = jsonBody.getString("encoding");
String content = jsonBody.getString("content");
String commitId = jsonBody.getString("commit_id");
String lastCommitId = jsonBody.getString("last_commit_id");
// 內(nèi)容已經(jīng)base64編碼,如果需要獲取原始文件內(nèi)容可以參看api:https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository
content = new String(Base64.decode(content), "UTF-8");
sysLogger.debug(String.format(
"獲取http://%s 上%s項(xiàng)目 %s分支的%s文件,響應(yīng)信息是:fileName :%s ,filePath:%s , 編碼:%s ,內(nèi)容:%s , commitId:%s ,lastCommitId :%s",
ip,projectPath, branchName, fileFullPath, fileName, filePath, encoding, content, commitId,
lastCommitId));
/** 6.返回指定文件的內(nèi)容 */
sysLogger.debug(String.format("解析得到文件內(nèi)容為:%s", content));
return content;
}
大致經(jīng)過如下過程:
- 使用用戶名隶垮、密碼獲取private token
- 使用private token藻雪,project path獲取projectId
- 使用private token,projectId狸吞,結(jié)合file path勉耀、分支branch參數(shù),獲取文件base64編碼內(nèi)容蹋偏,然后解碼即可
此外瑰排,考慮到項(xiàng)目中實(shí)際獲取的是配置文件內(nèi)容,為了剔除不必要的空行暖侨、注釋行椭住,提供了工具類方法對(duì)解碼的原始文件內(nèi)容進(jìn)行處理:
public static String getCleanFileContentFromRepository(String ip,String projectPath,String userName,String password,
String fileFullPath, String branchName) throws Exception{
/** 1.獲取到原始文件內(nèi)容 */
String fileContent = getFileContentFromRepository(ip, projectPath, userName, password, fileFullPath, branchName);
if (fileContent.isEmpty()) {
return fileContent;
}
/** 2.所有行轉(zhuǎn)換為list,并過濾掉空行字逗、#開頭的注釋行 */
List<String> list = Arrays.asList(fileContent.split("\n")).stream().filter(a-> (!a.trim().isEmpty()&&!a.trim().startsWith("#"))).collect(Collectors.toList());
/** 3.轉(zhuǎn)為一整行字符串返回 */
StringBuilder sb = new StringBuilder();
int size =list.size();
for (int i = 0; i < size; i++) {
sb.append(list.get(i).trim());
}
return sb.toString();
}
5 其它相關(guān)
5.1 替換參數(shù)工具類
對(duì)#{}包裹的參數(shù)進(jìn)行替換京郑,代碼如下:
public class PlaceholderUtil {
/** 默認(rèn)替換形如#{param}的占位符 */
private static Pattern pattern = Pattern.compile("\\#\\{.*?\\}");
/**
* 替換字符串中形如#{}的占位符
* @param src
* @param parameters
* @return
*/
public static String replace(String src, Map<String, Object> parameters) {
Matcher paraMatcher = pattern.matcher(src);
// 存儲(chǔ)參數(shù)名
String paraName = "";
String result = new String(src);
while (paraMatcher.find()) {
paraName = paraMatcher.group().replaceAll("\\#\\{", "").replaceAll("\\}", "");
Object objParam = parameters.get(paraName);
if(objParam!=null){
result = result.replace(paraMatcher.group(), objParam.toString());
}
}
return result;
}
/**
* 替換字符串中形如#{}的占位符
* @param src
* @param parameters
* @return
*/
public static String anotherReplace(String str, Map<String, String> params) {
Map<String, Object> newParams = new HashMap<>(params);
return replace(str, newParams);
}
}
5.2 測(cè)試類:
public class GitLabAPIUtilsTest {
String repoIp;
String privateToken;
String projectPath1;
String projectPath2;
String userName;
String password;
String fileFullPath;
String branchName;
@Before
public void setUp() throws Exception {
repoIp = "gitlab倉庫ip";
projectPath1 = "acountting/accounting-config-repo";
projectPath2 = "acountting/csv-filefront-cloud";
userName = "用戶名";
password="密碼";
fileFullPath = "/apps/cmup-clearing/0055-account.config";
branchName = "develop";
}
@After
public void tearDown() throws Exception {
repoIp = null;
projectPath1 = null;
projectPath2 = null;
userName = null;
password = null;
fileFullPath = null;
branchName = null;
}
@Test
public void testGetProjectId() {
String privateToken = GitLabAPIUtils.getPrivateTokenByPassword(repoIp, userName, password);
String projectId = GitLabAPIUtils.getProjectId(repoIp, projectPath1, privateToken);
System.out.println("projectId = " + projectId);
}
@Test
public void testGetPrivateToken() {
String privateToken = GitLabAPIUtils.getPrivateTokenByPassword(repoIp, userName, password);
System.out.println("projectId = " + privateToken);
}
@Test
public void testGetFileContent() {
String fileContent;
try {
fileContent = GitLabAPIUtils.getCleanFileContentFromRepository(repoIp, projectPath1, userName, password, fileFullPath, branchName);
System.out.println("fileContent = " + fileContent);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}