在现代软件开发中,代码版本控制是团队协作的基石。虽然市面上有众多代码托管平台,但出于安全性、定制化和成本控制等多方面考虑,越来越多的企业选择搭建自己的私有代码仓库。gitlab 作为一款功能强大、开源免费的 devops 平台,无疑是私有代码仓库的最佳选择之一。
本文将带你从零开始,在 linux 环境下完整搭建 gitlab 私有代码仓库,并通过实际 java 项目演示如何使用这一平台进行日常开发工作。无论你是系统管理员、开发工程师还是技术负责人,都能从中获得实用的知识和技能。
环境准备与系统要求
在开始安装 gitlab 之前,我们需要确保服务器环境满足基本要求。gitlab 对硬件资源有一定要求,特别是在用户量较大或项目较多的情况下。
硬件要求
- cpu: 至少 2 核心(推荐 4 核心以上)
- 内存: 至少 4gb(推荐 8gb 以上)
- 存储: 至少 20gb 可用空间(根据项目数量和大小调整)
- 操作系统: ubuntu 20.04/22.04, centos 7/8, rhel 7/8 等主流 linux 发行版
# 检查当前系统信息 uname -a cat /etc/os-release free -h df -h
软件依赖
gitlab 需要以下基础软件支持:
# 更新系统包 sudo apt update && sudo apt upgrade -y # ubuntu/debian # 或 sudo yum update -y # centos/rhel # 安装必要依赖 sudo apt install -y curl openssh-server ca-certificates tzdata perl # 或 sudo yum install -y curl openssh-server ca-certificates tzdata perl
防火墙配置
确保必要的端口开放:
# 开放 http/https 和 ssh 端口 sudo ufw allow 80/tcp # http sudo ufw allow 443/tcp # https sudo ufw allow 22/tcp # ssh sudo ufw enable # 或使用 firewalld (centos/rhel) sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --permanent --add-service=ssh sudo firewall-cmd --reload
gitlab 安装与配置
现在我们开始正式安装 gitlab。gitlab 提供了多种安装方式,这里我们采用最简单直接的 omnibus 包安装方法。
添加 gitlab 仓库
首先添加 gitlab 的官方仓库:
# 下载并安装 gitlab 仓库配置 curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash # 或对于 rpm 系统 curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
安装 gitlab ce
执行安装命令:
# ubuntu/debian sudo external_url="http://your-domain.com" apt install gitlab-ce # centos/rhel sudo external_url="http://your-domain.com" yum install gitlab-ce
注意:将 your-domain.com 替换为你的实际域名或服务器 ip 地址
基础配置
安装完成后,编辑 gitlab 配置文件:
sudo vim /etc/gitlab/gitlab.rb
主要配置项:
# 基础 url 配置 external_url 'http://gitlab.yourcompany.com' # 邮件配置(可选但推荐) gitlab_rails['gitlab_email_enabled'] = true gitlab_rails['gitlab_email_from'] = 'gitlab@yourcompany.com' gitlab_rails['gitlab_email_display_name'] = 'gitlab' gitlab_rails['gitlab_email_reply_to'] = 'noreply@yourcompany.com' # smtp 设置示例 gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.yourcompany.com" gitlab_rails['smtp_port'] = 587 gitlab_rails['smtp_user_name'] = "gitlab@yourcompany.com" gitlab_rails['smtp_password'] = "your_password" gitlab_rails['smtp_domain'] = "yourcompany.com" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = false
应用配置并启动
# 重新配置 gitlab(这会重启服务) sudo gitlab-ctl reconfigure # 检查服务状态 sudo gitlab-ctl status # 查看日志 sudo gitlab-ctl tail
首次访问时,系统会要求设置 root 用户密码。请妥善保存这个密码!
用户管理与权限控制
gitlab 提供了完善的用户管理和权限控制系统,让我们能够精细地控制谁可以访问哪些资源。
创建用户
# 通过命令行创建用户(可选) sudo gitlab-rails console # 在 rails 控制台中执行: user = user.create!(email: 'developer@yourcompany.com', password: 'securepassword', username: 'developer', name: 'developer name') user.skip_confirmation! user.save!
或者通过 web 界面:
- 使用 root 用户登录
- 进入 admin area → users
- 点击 “new user” 按钮
- 填写用户信息并保存
用户组与项目权限
gitlab 的权限体系分为多个层级:

权限说明:
- owner: 拥有群组或项目的完全控制权
- maintainer: 可以管理项目设置、保护分支、添加成员等
- developer: 可以推送代码、创建合并请求、管理议题等
- reporter: 可以创建议题、评论、查看代码等
- guest: 基本只读权限
创建用户组
# 通过 web 界面创建群组 # 1. 点击右上角头像 → groups → create group # 2. 填写群组名称、路径、描述 # 3. 设置可见性级别(private/internal/public) # 4. 点击 "create group"
添加成员到群组
// 虽然这不是真正的 java 代码,但我们可以模拟一个用户管理的 api 调用
public class gitlabusermanager {
public static void main(string[] args) {
gitlabclient client = new gitlabclient("https://gitlab.yourcompany.com", "your-access-token");
try {
// 创建新用户
user newuser = client.createuser("john.doe@company.com", "john doe", "johndoe");
system.out.println("user created: " + newuser.getid());
// 将用户添加到群组
groupmembership membership = client.addusertogroup(123, newuser.getid(), accesslevel.developer);
system.out.println("user added to group with access level: " + membership.getaccesslevel());
// 获取群组成员列表
list<groupmembership> members = client.getgroupmembers(123);
for (groupmembership member : members) {
system.out.println("member: " + member.getuser().getname() +
" - access level: " + member.getaccesslevel());
}
} catch (gitlabapiexception e) {
system.err.println("error managing users: " + e.getmessage());
}
}
}创建和管理 java 项目
现在让我们创建一个实际的 java 项目,演示如何在 gitlab 上进行日常开发工作。
创建新项目
- 登录 gitlab
- 点击 “new project” 按钮
- 选择 “create blank project”
- 填写项目信息:
- project name:
java-sample-app - project description:
a sample java application demonstrating gitlab features - visibility level: private
- project name:
- 点击 “create project”
初始化本地 java 项目
# 创建项目目录 mkdir java-sample-app cd java-sample-app # 初始化 git 仓库 git init # 添加远程仓库(替换为你的实际 gitlab 项目地址) git remote add origin http://gitlab.yourcompany.com/your-username/java-sample-app.git
创建 maven 项目结构
<!-- pom.xml -->
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation="http://maven.apache.org/pom/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0</modelversion>
<groupid>com.yourcompany</groupid>
<artifactid>java-sample-app</artifactid>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>java sample application</name>
<description>a sample java application for gitlab demonstration</description>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceencoding>utf-8</project.build.sourceencoding>
</properties>
<dependencies>
<!-- junit 5 for testing -->
<dependency>
<groupid>org.junit.jupiter</groupid>
<artifactid>junit-jupiter</artifactid>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- slf4j for logging -->
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-api</artifactid>
<version>1.7.36</version>
</dependency>
<dependency>
<groupid>ch.qos.logback</groupid>
<artifactid>logback-classic</artifactid>
<version>1.2.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-compiler-plugin</artifactid>
<version>3.8.1</version>
</plugin>
<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-surefire-plugin</artifactid>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
</project>创建 java 类
// src/main/java/com/yourcompany/app.java
package com.yourcompany;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
/**
* 主应用程序类
*/
public class app {
private static final logger logger = loggerfactory.getlogger(app.class);
/**
* 主方法
* @param args 命令行参数
*/
public static void main(string[] args) {
logger.info("starting java sample application...");
app app = new app();
string result = app.processdata("hello gitlab!");
system.out.println(result);
logger.info("application finished successfully.");
}
/**
* 处理数据的示例方法
* @param input 输入字符串
* @return 处理后的结果
*/
public string processdata(string input) {
if (input == null || input.trim().isempty()) {
throw new illegalargumentexception("input cannot be null or empty");
}
logger.debug("processing input: {}", input);
return "processed: " + input.touppercase();
}
/**
* 计算两个数的和
* @param a 第一个数
* @param b 第二个数
* @return 两数之和
*/
public int addnumbers(int a, int b) {
logger.trace("adding numbers: {} + {}", a, b);
return a + b;
}
}创建测试类
// src/test/java/com/yourcompany/apptest.java
package com.yourcompany;
import org.junit.jupiter.api.beforeeach;
import org.junit.jupiter.api.test;
import org.junit.jupiter.api.displayname;
import org.junit.jupiter.api.nested;
import static org.junit.jupiter.api.assertions.*;
import static org.junit.jupiter.api.assumptions.assumetrue;
/**
* app 类的单元测试
*/
@displayname("app class tests")
class apptest {
private app app;
@beforeeach
void setup() {
app = new app();
}
@test
@displayname("should process string correctly")
void testprocessdata() {
// 测试正常情况
string result = app.processdata("test input");
assertequals("processed: test input", result);
// 测试边界情况
result = app.processdata(" spaces ");
assertequals("processed: spaces ", result);
}
@test
@displayname("should throw exception for null input")
void testprocessdatanull() {
assertthrows(illegalargumentexception.class, () -> {
app.processdata(null);
});
}
@test
@displayname("should throw exception for empty input")
void testprocessdataempty() {
assertthrows(illegalargumentexception.class, () -> {
app.processdata("");
});
}
@nested
@displayname("add numbers tests")
class addnumberstests {
@test
@displayname("should add positive numbers correctly")
void testaddpositivenumbers() {
int result = app.addnumbers(5, 3);
assertequals(8, result);
}
@test
@displayname("should add negative numbers correctly")
void testaddnegativenumbers() {
int result = app.addnumbers(-5, -3);
assertequals(-8, result);
}
@test
@displayname("should handle zero correctly")
void testaddwithzero() {
int result = app.addnumbers(5, 0);
assertequals(5, result);
}
@test
@displayname("should handle large numbers")
void testaddlargenumbers() {
int result = app.addnumbers(integer.max_value, 1);
assertequals(integer.min_value, result); // 溢出测试
}
}
@test
@displayname("should skip test in production environment")
void testconditionalexecution() {
// 假设我们只想在开发环境中运行某些测试
boolean isproduction = system.getproperty("env") != null &&
system.getproperty("env").equals("production");
assumetrue(!isproduction, "skipping test in production environment");
// 实际测试逻辑
asserttrue(true);
}
}创建日志配置
<!-- src/main/resources/logback.xml -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appender name="stdout" class="ch.qos.logback.core.consoleappender">
<encoder>
<pattern>%d{yyyy-mm-dd hh:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.yourcompany" level="debug"/>
<root level="info">
<appender-ref ref="stdout"/>
</root>
</configuration>提交代码到 gitlab
# 添加所有文件 git add . # 提交更改 git commit -m "initial commit: setup basic java project structure" # 推送到 gitlab git push -u origin master
分支策略与工作流
在团队协作中,合理的分支策略至关重要。gitlab 支持多种工作流,我们推荐使用 git flow 或类似的分支管理策略。
git flow 工作流

创建和管理分支
# 创建新分支 git checkout -b feature/new-feature # 推送分支到远程 git push origin feature/new-feature # 切换回主分支 git checkout main # 合并分支(在本地) git merge feature/new-feature # 删除本地分支 git branch -d feature/new-feature # 删除远程分支 git push origin --delete feature/new-feature
java 项目中的分支实践
让我们在之前的 java 项目中添加一个新功能:
// 在 feature/calculator-enhancement 分支上工作
// src/main/java/com/yourcompany/calculator.java
package com.yourcompany;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
/**
* 增强型计算器类
*/
public class calculator {
private static final logger logger = loggerfactory.getlogger(calculator.class);
/**
* 执行四则运算
* @param a 第一个操作数
* @param b 第二个操作数
* @param operation 运算符 (+, -, *, /)
* @return 计算结果
* @throws illegalargumentexception 当运算符不支持时
* @throws arithmeticexception 当除零时
*/
public double calculate(double a, double b, char operation) {
logger.info("calculating: {} {} {}", a, operation, b);
switch (operation) {
case '+':
return add(a, b);
case '-':
return subtract(a, b);
case '*':
return multiply(a, b);
case '/':
return divide(a, b);
default:
throw new illegalargumentexception("unsupported operation: " + operation);
}
}
private double add(double a, double b) {
return a + b;
}
private double subtract(double a, double b) {
return a - b;
}
private double multiply(double a, double b) {
return a * b;
}
private double divide(double a, double b) {
if (b == 0) {
throw new arithmeticexception("division by zero");
}
return a / b;
}
/**
* 计算平方根
* @param number 要计算平方根的数
* @return 平方根结果
* @throws illegalargumentexception 当输入为负数时
*/
public double squareroot(double number) {
if (number < 0) {
throw new illegalargumentexception("cannot calculate square root of negative number");
}
return math.sqrt(number);
}
/**
* 计算幂运算
* @param base 底数
* @param exponent 指数
* @return 幂运算结果
*/
public double power(double base, double exponent) {
return math.pow(base, exponent);
}
}对应的测试类:
// src/test/java/com/yourcompany/calculatortest.java
package com.yourcompany;
import org.junit.jupiter.api.beforeeach;
import org.junit.jupiter.api.test;
import org.junit.jupiter.params.parameterizedtest;
import org.junit.jupiter.params.provider.csvsource;
import org.junit.jupiter.params.provider.valuesource;
import static org.junit.jupiter.api.assertions.*;
class calculatortest {
private calculator calculator;
@beforeeach
void setup() {
calculator = new calculator();
}
@parameterizedtest
@csvsource({
"5.0, 3.0, +, 8.0",
"5.0, 3.0, -, 2.0",
"5.0, 3.0, *, 15.0",
"6.0, 3.0, /, 2.0"
})
@displayname("should perform basic arithmetic operations correctly")
void testbasicoperations(double a, double b, char operation, double expected) {
double result = calculator.calculate(a, b, operation);
assertequals(expected, result, 0.001);
}
@test
@displayname("should throw exception for unsupported operation")
void testunsupportedoperation() {
assertthrows(illegalargumentexception.class, () -> {
calculator.calculate(5, 3, '%');
});
}
@test
@displayname("should throw exception for division by zero")
void testdivisionbyzero() {
assertthrows(arithmeticexception.class, () -> {
calculator.calculate(5, 0, '/');
});
}
@parameterizedtest
@valuesource(doubles = {0.0, 1.0, 4.0, 9.0, 16.0})
@displayname("should calculate square root correctly")
void testsquareroot(double number) {
double result = calculator.squareroot(number);
assertequals(math.sqrt(number), result, 0.001);
}
@test
@displayname("should throw exception for negative square root")
void testnegativesquareroot() {
assertthrows(illegalargumentexception.class, () -> {
calculator.squareroot(-1);
});
}
@parameterizedtest
@csvsource({
"2.0, 3.0, 8.0",
"5.0, 2.0, 25.0",
"10.0, 0.0, 1.0",
"2.0, -1.0, 0.5"
})
@displayname("should calculate power correctly")
void testpower(double base, double exponent, double expected) {
double result = calculator.power(base, exponent);
assertequals(expected, result, 0.001);
}
}创建合并请求
在 gitlab 中,我们通常通过合并请求(merge request)来审查和合并代码:
# 推送功能分支 git push origin feature/calculator-enhancement # 在 gitlab web 界面创建合并请求: # 1. 进入项目页面 # 2. 点击 "merge requests" → "new merge request" # 3. 选择源分支和目标分支 # 4. 填写标题和描述 # 5. 指定评审人员 # 6. 点击 "create merge request"
ci/cd 流水线配置
gitlab 内置了强大的 ci/cd 功能,让我们可以通过 .gitlab-ci.yml 文件定义自动化构建、测试和部署流程。
基础 ci/cd 配置
# .gitlab-ci.yml
image: maven:3.8.6-openjdk-11
stages:
- build
- test
- deploy
variables:
maven_opts: "-dmaven.repo.local=.m2/repository"
maven_cli_opts: "--batch-mode --errors --fail-at-end --show-version"
cache:
paths:
- .m2/repository/
build:
stage: build
script:
- mvn $maven_cli_opts compile
artifacts:
paths:
- target/
expire_in: 1 week
test:
stage: test
script:
- mvn $maven_cli_opts test
coverage: '/total.*?([0-9]{1,3})%/'
deploy-dev:
stage: deploy
script:
- echo "deploying to development environment..."
- mvn $maven_cli_opts package
- cp target/java-sample-app-*.jar ./app.jar
- echo "deployment completed!"
environment:
name: development
url: http://dev.yourcompany.com
only:
- main
deploy-prod:
stage: deploy
script:
- echo "deploying to production environment..."
- mvn $maven_cli_opts package
- scp target/java-sample-app-*.jar production-server:/opt/app/
- ssh production-server "systemctl restart java-app"
environment:
name: production
url: https://yourcompany.com
when: manual
only:
- main高级 ci/cd 配置
# 更复杂的 .gitlab-ci.yml 示例
image: maven:3.8.6-openjdk-11
stages:
- lint
- build
- test
- security
- deploy
- notify
variables:
maven_opts: "-dmaven.repo.local=.m2/repository"
maven_cli_opts: "--batch-mode --errors --fail-at-end --show-version"
sonar_host_url: "http://sonarqube.yourcompany.com"
sonar_login: "$sonar_token"
cache:
key: ${ci_commit_ref_slug}
paths:
- .m2/repository/
before_script:
- export java_home=/usr/lib/jvm/java-11-openjdk
- java -version
# 代码质量检查
lint:
stage: lint
script:
- mvn $maven_cli_opts checkstyle:check
- mvn $maven_cli_opts spotbugs:check
allow_failure: true
# 构建阶段
build:
stage: build
script:
- mvn $maven_cli_opts clean compile
- mvn $maven_cli_opts package -dskiptests
artifacts:
paths:
- target/*.jar
expire_in: 1 week
except:
- tags
# 单元测试
unit-test:
stage: test
script:
- mvn $maven_cli_opts test
coverage: '/total.*?([0-9]{1,3})%/'
artifacts:
paths:
- target/surefire-reports/
reports:
junit: target/surefire-reports/test-*.xml
# 集成测试
integration-test:
stage: test
script:
- mvn $maven_cli_opts verify -p integration-test
when: on_success
dependencies:
- build
# 代码安全扫描
security-scan:
stage: security
script:
- mvn $maven_cli_opts dependency-check:check
- mvn $maven_cli_opts sonar:sonar
allow_failure: true
# 开发环境部署
deploy-dev:
stage: deploy
script:
- echo "deploying version ${ci_commit_tag:-$ci_commit_short_sha} to dev environment"
- ./scripts/deploy-dev.sh
environment:
name: development
url: http://dev.yourcompany.com
only:
- main
- develop
# 预生产环境部署
deploy-staging:
stage: deploy
script:
- echo "deploying version ${ci_commit_tag:-$ci_commit_short_sha} to staging environment"
- ./scripts/deploy-staging.sh
environment:
name: staging
url: https://staging.yourcompany.com
when: manual
only:
- main
# 生产环境部署
deploy-production:
stage: deploy
script:
- echo "deploying version ${ci_commit_tag:-$ci_commit_short_sha} to production environment"
- ./scripts/deploy-production.sh
environment:
name: production
url: https://yourcompany.com
when: manual
only:
- /^release\/.*/
- tags
# 通知阶段
notify-slack:
stage: notify
script:
- |
if [ "$ci_job_status" = "success" ]; then
curl -x post -h 'content-type: application/json' \
--data '{"text":"🚀 build succeeded: '"$ci_project_name"' - '"$ci_commit_ref_name"'"}' \
$slack_webhook_url
else
curl -x post -h 'content-type: application/json' \
--data '{"text":"❌ build failed: '"$ci_project_name"' - '"$ci_commit_ref_name"'"}' \
$slack_webhook_url
fi
when: always
only:
- main
- tagsjava 项目中的 ci/cd 实践
让我们为 java 项目创建一些实用的脚本:
# scripts/deploy-dev.sh #!/bin/bash set -e echo "starting deployment to development environment..." echo "project: $ci_project_name" echo "branch: $ci_commit_ref_name" echo "commit: $ci_commit_short_sha" # 构建应用 mvn clean package # 创建部署目录 deploy_dir="/opt/java-apps/$ci_project_name" mkdir -p $deploy_dir # 复制 jar 文件 cp target/*.jar $deploy_dir/app.jar # 创建或更新 systemd 服务文件 cat > /etc/systemd/system/java-app.service << eof [unit] description=java sample application after=network.target [service] type=simple user=appuser workingdirectory=$deploy_dir execstart=/usr/bin/java -jar $deploy_dir/app.jar restart=always restartsec=10 [install] wantedby=multi-user.target eof # 重新加载 systemd 配置 systemctl daemon-reload # 重启服务 systemctl restart java-app echo "deployment completed successfully!"
// 为了支持 ci/cd,我们可以添加一个健康检查端点
// src/main/java/com/yourcompany/healthcheckcontroller.java
package com.yourcompany;
import java.time.localdatetime;
import java.util.hashmap;
import java.util.map;
/**
* 健康检查控制器
*/
public class healthcheckcontroller {
/**
* 获取应用健康状态
* @return 包含健康信息的 map
*/
public map<string, object> gethealthstatus() {
map<string, object> healthinfo = new hashmap<>();
healthinfo.put("status", "up");
healthinfo.put("application", "java-sample-app");
healthinfo.put("version", "1.0.0");
healthinfo.put("timestamp", localdatetime.now().tostring());
healthinfo.put("checks", gethealthchecks());
return healthinfo;
}
private map<string, string> gethealthchecks() {
map<string, string> checks = new hashmap<>();
// 数据库连接检查(模拟)
checks.put("database", checkdatabaseconnection() ? "up" : "down");
// 磁盘空间检查(模拟)
checks.put("diskspace", checkdiskspace() ? "up" : "down");
// 内存使用检查(模拟)
checks.put("memory", checkmemoryusage() ? "up" : "down");
return checks;
}
private boolean checkdatabaseconnection() {
// 模拟数据库连接检查
try {
thread.sleep(100); // 模拟网络延迟
return true; // 假设连接成功
} catch (interruptedexception e) {
return false;
}
}
private boolean checkdiskspace() {
// 模拟磁盘空间检查
long freespace = runtime.getruntime().freememory();
long totalspace = runtime.getruntime().totalmemory();
double usagepercentage = ((double) (totalspace - freespace) / totalspace) * 100;
return usagepercentage < 90; // 如果使用率低于90%,则认为正常
}
private boolean checkmemoryusage() {
// 模拟内存使用检查
long maxmemory = runtime.getruntime().maxmemory();
long usedmemory = runtime.getruntime().totalmemory() - runtime.getruntime().freememory();
double usagepercentage = ((double) usedmemory / maxmemory) * 100;
return usagepercentage < 85; // 如果使用率低于85%,则认为正常
}
}对应的测试:
// src/test/java/com/yourcompany/healthcheckcontrollertest.java
package com.yourcompany;
import org.junit.jupiter.api.beforeeach;
import org.junit.jupiter.api.test;
import java.util.map;
import static org.junit.jupiter.api.assertions.*;
class healthcheckcontrollertest {
private healthcheckcontroller controller;
@beforeeach
void setup() {
controller = new healthcheckcontroller();
}
@test
void testgethealthstatus() {
map<string, object> healthstatus = controller.gethealthstatus();
assertnotnull(healthstatus);
assertequals("up", healthstatus.get("status"));
assertequals("java-sample-app", healthstatus.get("application"));
assertequals("1.0.0", healthstatus.get("version"));
@suppresswarnings("unchecked")
map<string, string> checks = (map<string, string>) healthstatus.get("checks");
assertnotnull(checks);
asserttrue(checks.containskey("database"));
asserttrue(checks.containskey("diskspace"));
asserttrue(checks.containskey("memory"));
}
@test
void testhealthchecks() {
@suppresswarnings("unchecked")
map<string, string> checks = (map<string, string>) controller.gethealthstatus().get("checks");
// 所有检查都应该返回 "up"(基于我们的模拟实现)
assertequals("up", checks.get("database"));
assertequals("up", checks.get("diskspace"));
assertequals("up", checks.get("memory"));
}
}代码质量与安全分析
gitlab 不仅提供版本控制,还内置了代码质量分析和安全扫描功能。
sonarqube 集成
# 在 .gitlab-ci.yml 中添加 sonarqube 分析
sonarqube-analysis:
stage: test
script:
- mvn $maven_cli_opts sonar:sonar \
-dsonar.host.url=$sonar_host_url \
-dsonar.login=$sonar_login \
-dsonar.projectkey=$ci_project_name \
-dsonar.projectname="$ci_project_title" \
-dsonar.projectversion=$ci_commit_tag \
-dsonar.branch.name=$ci_commit_ref_name
allow_failure: true
only:
- main
- develop依赖安全扫描
# 依赖漏洞扫描
dependency-scanning:
stage: security
script:
- mvn $maven_cli_opts dependency-check:check
artifacts:
reports:
dependency_scanning: target/dependency-check-report.json
allow_failure: truejava 代码质量实践
// 高质量 java 代码示例
package com.yourcompany.service;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import java.util.optional;
import java.util.concurrent.completablefuture;
import java.util.concurrent.executorservice;
import java.util.concurrent.executors;
import java.util.function.supplier;
/**
* 用户服务类
* 提供用户相关的业务逻辑
*/
public class userservice {
private static final logger logger = loggerfactory.getlogger(userservice.class);
private final userrepository userrepository;
private final executorservice executorservice;
/**
* 构造函数
* @param userrepository 用户仓库
*/
public userservice(userrepository userrepository) {
this.userrepository = userrepository;
this.executorservice = executors.newfixedthreadpool(5);
}
/**
* 异步获取用户信息
* @param userid 用户id
* @return completablefuture 包含用户信息
*/
public completablefuture<optional<user>> getuserasync(long userid) {
logger.info("fetching user asynchronously: {}", userid);
return completablefuture.supplyasync(() -> {
try {
logger.debug("starting async user fetch for id: {}", userid);
optional<user> user = userrepository.findbyid(userid);
logger.debug("completed async user fetch for id: {}", userid);
return user;
} catch (exception e) {
logger.error("error fetching user with id: {}", userid, e);
throw new userserviceexception("failed to fetch user", e);
}
}, executorservice);
}
/**
* 创建新用户
* @param userdata 用户数据
* @return 创建的用户
* @throws validationexception 当用户数据无效时
*/
public user createuser(userdata userdata) throws validationexception {
validateuserdata(userdata);
logger.info("creating new user: {}", userdata.getemail());
user user = new user();
user.setemail(userdata.getemail());
user.setname(userdata.getname());
user.setcreatedat(java.time.localdatetime.now());
user.setupdatedat(user.getcreatedat());
try {
user saveduser = userrepository.save(user);
logger.info("user created successfully: id {}", saveduser.getid());
return saveduser;
} catch (exception e) {
logger.error("failed to create user: {}", userdata.getemail(), e);
throw new userserviceexception("failed to create user", e);
}
}
/**
* 更新用户信息
* @param userid 用户id
* @param userdata 更新的用户数据
* @return 更新后的用户
* @throws usernotfoundexception 当用户不存在时
* @throws validationexception 当用户数据无效时
*/
public user updateuser(long userid, userdata userdata)
throws usernotfoundexception, validationexception {
validateuserdata(userdata);
logger.info("updating user: {}", userid);
optional<user> existinguser = userrepository.findbyid(userid);
if (!existinguser.ispresent()) {
logger.warn("user not found for update: {}", userid);
throw new usernotfoundexception("user not found: " + userid);
}
user user = existinguser.get();
user.setemail(userdata.getemail());
user.setname(userdata.getname());
user.setupdatedat(java.time.localdatetime.now());
try {
user updateduser = userrepository.save(user);
logger.info("user updated successfully: id {}", updateduser.getid());
return updateduser;
} catch (exception e) {
logger.error("failed to update user: {}", userid, e);
throw new userserviceexception("failed to update user", e);
}
}
/**
* 删除用户
* @param userid 用户id
* @throws usernotfoundexception 当用户不存在时
*/
public void deleteuser(long userid) throws usernotfoundexception {
logger.info("deleting user: {}", userid);
optional<user> existinguser = userrepository.findbyid(userid);
if (!existinguser.ispresent()) {
logger.warn("user not found for deletion: {}", userid);
throw new usernotfoundexception("user not found: " + userid);
}
try {
userrepository.deletebyid(userid);
logger.info("user deleted successfully: {}", userid);
} catch (exception e) {
logger.error("failed to delete user: {}", userid, e);
throw new userserviceexception("failed to delete user", e);
}
}
/**
* 验证用户数据
* @param userdata 用户数据
* @throws validationexception 当验证失败时
*/
private void validateuserdata(userdata userdata) throws validationexception {
if (userdata == null) {
throw new validationexception("user data cannot be null");
}
if (userdata.getemail() == null || userdata.getemail().trim().isempty()) {
throw new validationexception("email cannot be empty");
}
if (!isvalidemail(userdata.getemail())) {
throw new validationexception("invalid email format: " + userdata.getemail());
}
if (userdata.getname() == null || userdata.getname().trim().isempty()) {
throw new validationexception("name cannot be empty");
}
if (userdata.getname().length() < 2 || userdata.getname().length() > 100) {
throw new validationexception("name must be between 2 and 100 characters");
}
}
/**
* 验证邮箱格式
* @param email 邮箱地址
* @return 是否有效
*/
private boolean isvalidemail(string email) {
if (email == null) return false;
string emailregex = "^[a-za-z0-9_+&*-]+(?:\\.[a-za-z0-9_+&*-]+)*@(?:[a-za-z0-9-]+\\.)+[a-za-z]{2,7}$";
return email.matches(emailregex);
}
/**
* 关闭服务,释放资源
*/
public void shutdown() {
logger.info("shutting down userservice");
executorservice.shutdown();
}
}
/**
* 用户实体类
*/
class user {
private long id;
private string email;
private string name;
private java.time.localdatetime createdat;
private java.time.localdatetime updatedat;
// getters and setters
public long getid() { return id; }
public void setid(long id) { this.id = id; }
public string getemail() { return email; }
public void setemail(string email) { this.email = email; }
public string getname() { return name; }
public void setname(string name) { this.name = name; }
public java.time.localdatetime getcreatedat() { return createdat; }
public void setcreatedat(java.time.localdatetime createdat) { this.createdat = createdat; }
public java.time.localdatetime getupdatedat() { return updatedat; }
public void setupdatedat(java.time.localdatetime updatedat) { this.updatedat = updatedat; }
}
/**
* 用户数据传输对象
*/
class userdata {
private string email;
private string name;
public userdata() {}
public userdata(string email, string name) {
this.email = email;
this.name = name;
}
// getters and setters
public string getemail() { return email; }
public void setemail(string email) { this.email = email; }
public string getname() { return name; }
public void setname(string name) { this.name = name; }
}
/**
* 用户仓库接口
*/
interface userrepository {
optional<user> findbyid(long id);
user save(user user);
void deletebyid(long id);
}
/**
* 自定义异常类
*/
class userserviceexception extends runtimeexception {
public userserviceexception(string message) {
super(message);
}
public userserviceexception(string message, throwable cause) {
super(message, cause);
}
}
class validationexception extends exception {
public validationexception(string message) {
super(message);
}
}
class usernotfoundexception extends exception {
public usernotfoundexception(string message) {
super(message);
}
}对应的测试:
// src/test/java/com/yourcompany/service/userservicetest.java
package com.yourcompany.service;
import org.junit.jupiter.api.beforeeach;
import org.junit.jupiter.api.test;
import org.junit.jupiter.api.displayname;
import org.mockito.mock;
import org.mockito.mockitoannotations;
import java.util.optional;
import java.util.concurrent.completablefuture;
import java.util.concurrent.executionexception;
import static org.junit.jupiter.api.assertions.*;
import static org.mockito.mockito.*;
class userservicetest {
@mock
private userrepository userrepository;
private userservice userservice;
@beforeeach
void setup() {
mockitoannotations.openmocks(this);
userservice = new userservice(userrepository);
}
@test
@displayname("should create user with valid data")
void testcreateuservaliddata() throws validationexception {
userdata userdata = new userdata("test@example.com", "test user");
user saveduser = new user();
saveduser.setid(1l);
saveduser.setemail(userdata.getemail());
saveduser.setname(userdata.getname());
when(userrepository.save(any(user.class))).thenreturn(saveduser);
user result = userservice.createuser(userdata);
assertnotnull(result);
assertequals(1l, result.getid());
assertequals("test@example.com", result.getemail());
assertequals("test user", result.getname());
verify(userrepository).save(any(user.class));
}
@test
@displayname("should throw exception for null user data")
void testcreateusernulldata() {
assertthrows(validationexception.class, () -> {
userservice.createuser(null);
});
}
@test
@displayname("should throw exception for invalid email")
void testcreateuserinvalidemail() {
userdata userdata = new userdata("invalid-email", "test user");
assertthrows(validationexception.class, () -> {
userservice.createuser(userdata);
});
}
@test
@displayname("should update existing user")
void testupdateuserexisting() throws usernotfoundexception, validationexception {
long userid = 1l;
userdata userdata = new userdata("updated@example.com", "updated user");
user existinguser = new user();
existinguser.setid(userid);
existinguser.setemail("old@example.com");
existinguser.setname("old name");
user updateduser = new user();
updateduser.setid(userid);
updateduser.setemail(userdata.getemail());
updateduser.setname(userdata.getname());
when(userrepository.findbyid(userid)).thenreturn(optional.of(existinguser));
when(userrepository.save(any(user.class))).thenreturn(updateduser);
user result = userservice.updateuser(userid, userdata);
assertnotnull(result);
assertequals(userid, result.getid());
assertequals("updated@example.com", result.getemail());
assertequals("updated user", result.getname());
verify(userrepository).findbyid(userid);
verify(userrepository).save(any(user.class));
}
@test
@displayname("should throw exception for non-existent user update")
void testupdateusernonexistent() {
long userid = 999l;
userdata userdata = new userdata("test@example.com", "test user");
when(userrepository.findbyid(userid)).thenreturn(optional.empty());
assertthrows(usernotfoundexception.class, () -> {
userservice.updateuser(userid, userdata);
});
}
@test
@displayname("should delete existing user")
void testdeleteuserexisting() throws usernotfoundexception {
long userid = 1l;
user existinguser = new user();
existinguser.setid(userid);
when(userrepository.findbyid(userid)).thenreturn(optional.of(existinguser));
assertdoesnotthrow(() -> {
userservice.deleteuser(userid);
});
verify(userrepository).findbyid(userid);
verify(userrepository).deletebyid(userid);
}
@test
@displayname("should throw exception for non-existent user deletion")
void testdeleteusernonexistent() {
long userid = 999l;
when(userrepository.findbyid(userid)).thenreturn(optional.empty());
assertthrows(usernotfoundexception.class, () -> {
userservice.deleteuser(userid);
});
}
@test
@displayname("should fetch user asynchronously")
void testgetuserasync() throws executionexception, interruptedexception {
long userid = 1l;
user user = new user();
user.setid(userid);
user.setemail("test@example.com");
user.setname("test user");
when(userrepository.findbyid(userid)).thenreturn(optional.of(user));
completablefuture<optional<user>> future = userservice.getuserasync(userid);
optional<user> result = future.get();
asserttrue(result.ispresent());
assertequals(userid, result.get().getid());
assertequals("test@example.com", result.get().getemail());
assertequals("test user", result.get().getname());
verify(userrepository).findbyid(userid);
}
}团队协作与代码评审
gitlab 提供了完善的团队协作功能,包括议题跟踪、代码评审、讨论等。
创建和管理议题
// 我们可以创建一个简单的议题管理系统来演示协作功能
package com.yourcompany.issue;
import java.time.localdatetime;
import java.util.*;
import java.util.concurrent.concurrenthashmap;
import java.util.stream.collectors;
/**
* 简单的议题管理系统
*/
public class issuemanager {
private final map<long, issue> issues;
private long nextid;
public issuemanager() {
this.issues = new concurrenthashmap<>();
this.nextid = 1;
}
/**
* 创建新议题
* @param title 标题
* @param description 描述
* @param author 作者
* @param labels 标签
* @return 创建的议题
*/
public issue createissue(string title, string description, string author, set<string> labels) {
if (title == null || title.trim().isempty()) {
throw new illegalargumentexception("title cannot be empty");
}
issue issue = new issue();
issue.setid(nextid++);
issue.settitle(title.trim());
issue.setdescription(description != null ? description.trim() : "");
issue.setauthor(author != null ? author.trim() : "anonymous");
issue.setlabels(labels != null ? new hashset<>(labels) : new hashset<>());
issue.setstatus(issuestatus.open);
issue.setcreatedat(localdatetime.now());
issue.setupdatedat(issue.getcreatedat());
issues.put(issue.getid(), issue);
return issue;
}
/**
* 更新议题
* @param id 议题id
* @param updater 更新者
* @param title 新标题(可选)
* @param description 新描述(可选)
* @param status 新状态(可选)
* @param labels 新标签(可选)
* @return 更新后的议题
* @throws issuenotfoundexception 当议题不存在时
*/
public issue updateissue(long id, string updater, string title, string description,
issuestatus status, set<string> labels) throws issuenotfoundexception {
issue issue = getissue(id);
if (title != null && !title.trim().isempty()) {
issue.settitle(title.trim());
}
if (description != null) {
issue.setdescription(description.trim());
}
if (status != null) {
issue.setstatus(status);
}
if (labels != null) {
issue.setlabels(new hashset<>(labels));
}
issue.setupdatedat(localdatetime.now());
issue.addcomment(new comment(updater, "issue updated", issue.getupdatedat()));
return issue;
}
/**
* 关闭议题
* @param id 议题id
* @param closer 关闭者
* @return 关闭后的议题
* @throws issuenotfoundexception 当议题不存在时
*/
public issue closeissue(long id, string closer) throws issuenotfoundexception {
issue issue = getissue(id);
issue.setstatus(issuestatus.closed);
issue.setupdatedat(localdatetime.now());
issue.addcomment(new comment(closer, "issue closed", issue.getupdatedat()));
return issue;
}
/**
* 重新打开议题
* @param id 议题id
* @param opener 重新打开者
* @return 重新打开后的议题
* @throws issuenotfoundexception 当议题不存在时
*/
public issue reopenissue(long id, string opener) throws issuenotfoundexception {
issue issue = getissue(id);
issue.setstatus(issuestatus.open);
issue.setupdatedat(localdatetime.now());
issue.addcomment(new comment(opener, "issue reopened", issue.getupdatedat()));
return issue;
}
/**
* 为议题添加评论
* @param id 议题id
* @param author 评论作者
* @param content 评论内容
* @return 添加评论后的议题
* @throws issuenotfoundexception 当议题不存在时
*/
public issue addcomment(long id, string author, string content) throws issuenotfoundexception {
if (content == null || content.trim().isempty()) {
throw new illegalargumentexception("comment content cannot be empty");
}
issue issue = getissue(id);
comment comment = new comment(author, content.trim(), localdatetime.now());
issue.addcomment(comment);
issue.setupdatedat(localdatetime.now());
return issue;
}
/**
* 获取议题
* @param id 议题id
* @return 议题
* @throws issuenotfoundexception 当议题不存在时
*/
public issue getissue(long id) throws issuenotfoundexception {
issue issue = issues.get(id);
if (issue == null) {
throw new issuenotfoundexception("issue not found: " + id);
}
return issue;
}
/**
* 获取所有议题
* @return 所有议题的列表
*/
public list<issue> getallissues() {
return new arraylist<>(issues.values());
}
/**
* 根据状态获取议题
* @param status 状态
* @return 指定状态的议题列表
*/
public list<issue> getissuesbystatus(issuestatus status) {
return issues.values().stream()
.filter(issue -> issue.getstatus() == status)
.collect(collectors.tolist());
}
/**
* 根据标签获取议题
* @param label 标签
* @return 包含指定标签的议题列表
*/
public list<issue> getissuesbylabel(string label) {
if (label == null) return new arraylist<>();
return issues.values().stream()
.filter(issue -> issue.getlabels().contains(label))
.collect(collectors.tolist());
}
/**
* 根据作者获取议题
* @param author 作者
* @return 指定作者创建的议题列表
*/
public list<issue> getissuesbyauthor(string author) {
if (author == null) return new arraylist<>();
return issues.values().stream()
.filter(issue -> author.equals(issue.getauthor()))
.collect(collectors.tolist());
}
/**
* 删除议题
* @param id 议题id
* @throws issuenotfoundexception 当议题不存在时
*/
public void deleteissue(long id) throws issuenotfoundexception {
issue issue = getissue(id);
issues.remove(id);
}
/**
* 获取议题总数
* @return 议题总数
*/
public int gettotalissuecount() {
return issues.size();
}
/**
* 获取开放议题数
* @return 开放议题数
*/
public int getopenissuecount() {
return (int) issues.values().stream()
.filter(issue -> issue.getstatus() == issuestatus.open)
.count();
}
/**
* 获取关闭议题数
* @return 关闭议题数
*/
public int getclosedissuecount() {
return (int) issues.values().stream()
.filter(issue -> issue.getstatus() == issuestatus.closed)
.count();
}
}
/**
* 议题类
*/
class issue {
private long id;
private string title;
private string description;
private string author;
private issuestatus status;
private set<string> labels;
private localdatetime createdat;
private localdatetime updatedat;
private list<comment> comments;
public issue() {
this.comments = new arraylist<>();
this.labels = new hashset<>();
}
// getters and setters
public long getid() { return id; }
public void setid(long id) { this.id = id; }
public string gettitle() { return title; }
public void settitle(string title) { this.title = title; }
public string getdescription() { return description; }
public以上就是linux环境下完整搭建gitlab私有代码仓库的详细流程的详细内容,更多关于linux搭建gitlab私有代码仓库的资料请关注代码网其它相关文章!
发表评论