co-eiv-devsecops/linker1

GitHub: co-eiv-devsecops/linker1

一个基于 Java/Javalin 的简易 URL 缩短服务,兼具完整的 CI/CD 流水线与云原生可观测性实践。

Stars: 0 | Forks: 0

[![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/39/39faa54be350a1dab8afd3b2fb8c1c83e4d9cff84abfef2374d19a18053687c4.svg)](https://github.com/co-eiv-devsecops/linker1/actions/workflows/ci.yml) [![Deployment Pipeline](https://static.pigsec.cn/wp-content/uploads/repos/cas/0f/0fcdadddacb45c6c71864106145f4c8a0811b0c74067a90a695871f3c6650dda.svg)](https://github.com/co-eiv-devsecops/linker1/actions/workflows/pipeline.yml) [![Release](https://static.pigsec.cn/wp-content/uploads/repos/cas/42/42ba98a60a0bb3b0ad908f024db145f9c5b831eb7df822f56ac578ee7d7215b3.svg)](https://github.com/co-eiv-devsecops/linker1/actions/workflows/release.yml) # Linker1 一个简单的 URL 缩短工具,使用 **Java** 和 **Javalin** 构建,搭配 SQLite 数据库和静态前端。 ## 它是什么? Linker1 是一个 Web 应用程序,它可以让你: - 输入一个长 URL - 生成一个短码(8 个字符),或者设置一个**自定义别名** - 当你访问该短码或别名时自动重定向 ## API - `POST /link` — 创建一个短链接。JSON body:`{"url": "https://..."}`。如果该 URL 已经存在,返回 `200` 以及相同的短码;如果是新 URL,返回 `201`。 - 自定义别名(可选):`{"url": "https://...", "alias": "my-alias"}`。别名规则:只能包含字母、数字、连字符(`-`)和下划线(`_`),长度在 1 到 64 个字符之间,不能包含空格。如果该别名已被其他 URL 占用,返回 `409`;如果别名无效,返回 `400`。 - `GET /{id}` — 重定向(`301`)到与短码或别名关联的 URL。如果不存在,返回 `404`。 - `HEAD /{id}` — 解析短链接。如果找到,返回 `200` 以及响应 body 中的真实 URL(不进行重定向);如果未找到,返回 `404`。 - `DELETE /{id}` — 删除短链接。成功返回 `204`,如果短码或别名不存在,返回 `404`。 ## 环境要求 - **Java 21** - **Maven 3.7+** - **Git** ## 在虚拟机(VM)上安装和运行 ### 1. 克隆仓库 ``` git clone cd linker1 ``` ### 2. 构建项目 ``` mvn clean package ``` ### 3. 运行应用程序 ``` java -jar target/linker1-1.0-jar-with-dependencies.jar ``` 应用程序将通过 `http://localhost:8080` 访问 ### 4. 部署 我们创建了一个脚本以简化部署过程;你只需在虚拟机上克隆仓库并运行: ``` bash deploy.sh ``` 此脚本会: - 从仓库拉取更改 - 安装依赖项(Java, Maven, Nginx) - 构建项目 - 配置 systemd 服务 - 启动 Nginx 作为反向代理 - 在 8080 端口暴露应用程序 ### 5. 环境对等 (IaC) 除了使用 `deploy.sh` 进行手动部署外,项目在 `infra/` 目录下还包含了使用 Terraform 的基础设施即代码,允许你以可复现的方式创建一个包含运行 Linker1 所需一切的新虚拟机。 **它的作用:** 在 OCI 上创建一个计算实例(与团队位于相同的子网和区间),并通过 cloud-init 自动配置:安装 Java 21、Maven、Nginx,克隆仓库、构建项目并启动服务——全程无需手动操作。 **要求:** Terraform >= 1.5,拥有 OCI 的访问权限(通过 OCI Cloud Shell,它自带身份验证)。 **使用方法:** ``` cd infra cp terraform.tfvars.example terraform.tfvars # 使用你的值填充 terraform.tfvars (compartment_id, subnet_id, image_id, ssh_public_key) terraform init terraform plan terraform apply ``` **销毁环境:** ``` terraform destroy ``` ### 验证是否正常运行 你应该能在以下 URL 看到静态页面: ``` https://1.n-la-c.app/ ``` 就像这样: ![替代文本](https://static.pigsec.cn/wp-content/uploads/repos/cas/9c/9cd92561bd3fef071674b186b679c5f4b0225c7acb097da161ce24f57560a733.png) ## 流水线与质量 CI 流水线(`.github/workflows/ci.yml`)会在每次向 `main`/`DEV` 分支 `push` 以及每次发起 `pull_request` 时运行,包含以下作业: - **Build**: 编译代码(`mvn compile`)。 - **Tests**: 运行单元测试和集成测试(`mvn test`),并使用 JaCoCo 强制执行覆盖率阈值(`mvn verify`)。 - **Package**: 生成可执行的 jar(`target/linker1-1.0-jar-with-dependencies.jar`)以及启动脚本(`scripts/linker1`)。 - **Summary**: 确认打包的构建产物存在且可执行。 - **Smoke Test**: 下载打包好的 jar,启动它,并进行主路由(静态资源、创建链接、别名、重定向、404)的实时测试。 - **API Tests (Live)**: 仅在向 `main`/`DEV` 分支 `push` 时运行,使用 [Newman](https://github.com/postmanlabs/newman) 对已部署的实例(`https://1.n-la-c.app/`)运行 Postman 集合。在 pull request 上不运行,以免影响共享的生产环境。 - **Publish GitHub Package**: 在向 `main`/`DEV` 分支 `push` 时,将构建好的 jar 发布到 **GitHub Packages**(见下文)。 `main` 和 `DEV` 分支均受保护:它们要求至少获得一次批准,并且必须通过 Build/Tests/Package/Summary/Smoke Test 检查后才能允许合并。有关完整的配置和基本原理,请参阅 [`docs/BRANCH_PROTECTION.md`](docs/BRANCH_PROTECTION.md)。 ### GitHub Packages 每次向 `main`/`DEV` 分支 push 时,都会通过 `pom.xml` 中的 `distributionManagement` 配置,将构建好的 jar 作为 Maven 包发布到 **GitHub Packages**(`https://maven.pkg.github.com/co-eiv-devsecops/linker1`)。每次 CI 发布的版本都会获得一个唯一的、无冲突的标识符(`-ci...`),因此重新运行 CI(包括手动重新运行同一作业)绝不会与之前发布的包发生冲突。这为团队提供了一种独立于 GitHub Releases 的、带版本号的、可下载的构建产物历史记录。 ### 持续部署 每次向 `main` 分支的 `push` 还会触发 `.github/workflows/pipeline.yml`,它会构建 jar,通过 OCI Bastion 将其部署到生产 VM(该 VM 没有公网 IP),验证服务是否响应,并在出现任何失败时自动回滚到之前稳定的 tag。完整细节(作业、所需密钥、堡垒机机制)请见 [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md);回滚策略请见 [`docs/ROLLBACK_STRATEGY.md`](docs/ROLLBACK_STRATEGY.md)。 ## 发布 (GitHub Release) 项目包含一个位于 `.github/workflows/release.yml` 的发布工作流,它会自动创建 **GitHub Release** 并附上可供下载的构建产物。 ### 何时运行? - 自动:当 push 一个格式为 `vMAJOR.MINOR.PATCH` 的 tag 时(例如 `v1.2.3`)。 - 手动:从 **Actions > Release > Run workflow** 运行,并指定一个符合该格式的现有 tag。 ### 它会验证和发布什么? 在发布之前,工作流会运行: - `mvn clean verify`(构建、测试和覆盖率规则) - `mvn package -DskipTests` 然后它会创建/更新 Release 并附上: - `linker1-1.0-jar-with-dependencies.jar` - `linker1`(Linux 启动脚本) ### 推荐的版本控制流程 1. 创建一个语义化 tag: git tag v1.2.3 git push origin v1.2.3 2. 等待 **Release** 工作流执行完毕。 3. 在 **GitHub > Releases** 中验证: - `Linker1 v1.2.3` 版本已存在 - 两个构建产物均已附上 - 自动生成的发布说明已生成(并且可以选择进行编辑) ### 功能开关 前端有两个版本,`public/v1/`(默认)和 `public/v2/`(重新设计主题的版本),在请求时由 **LaunchDarkly** 功能开关(`new-ui`)决定提供哪一个版本,而不是通过构建时的开关或硬编码的环境变量来决定。`src/linker/config/FeatureFlags.java` 将一个真实的 `LDClient` 封在一个小型接口(`isNewUiEnabled()`)中;`Main.java` 在启动时构建一次 LaunchDarkly 客户端(读取 `LD_SDK_KEY`,如果缺失则拒绝启动),并通过其构造函数将 `FeatureFlags` 注入到 `StaticRoutes` 中,随后由 `StaticRoutes` 在每次请求时选择 `v1` 或 `v2`。这让团队能够将新 UI 作为代码发布,然后从 LaunchDarkly 控制面板逐步为真实用户开启——无需部署——并在确认新路径健康后淘汰旧的(“糟糕的”)代码路径。完整设计请参阅 [`docs/FEATURE_FLAGS.md`](docs/FEATURE_FLAGS.md)。 ### 测试覆盖率 该项目使用 [JaCoCo](https://www.jacoco.org/jacoco/) 来测量代码覆盖率。目标是 100% 的行覆盖率(排除 `Main.class`,这个入口点会打开真实的数据库连接并启动真实的服务器——通常在单元覆盖率指标中将其排除在外)。报告通过 `mvn verify` 生成在 `target/site/jacoco/` 目录下。 ### 使用 Postman/Newman 进行 API 测试 `postman/linker1.postman_collection.json` 集合涵盖了:静态路由、链接创建(有效情况、重复、无效 URL)、别名(有效情况、已占用别名、无效别名)以及重定向(按 id、按别名、404)。要在本地运行它: ``` npx newman run postman/linker1.postman_collection.json --env-var "baseUrl=http://localhost:8080" ``` ## 可观测性 Linker1 集成了 [OpenTelemetry Java SDK](https://opentelemetry.io/docs/languages/java/):日志(通过 SLF4J/Logback,桥接到 OpenTelemetry)、指标(2 个 counters、2 个 gauges、2 个 histograms,涵盖 RED 方法及系统级 gauges)以及 traces(2 个 traces,每个包含一个父 span 和一个子 span),分布在链接创建和解析的周围。所有这些都是手动/编程式埋点——在 `src/` 中显式调用,而不是自动埋点的 javaagent——围绕一个小型可重用库(`linker.telemetry`)构建,因此代码库的其余部分仅通过少数几个窄范围的、构造函数注入的类与其交互,而不是直接调用原始的 OpenTelemetry API。 - **详细级别**:由 `LOG_LEVEL` 环境变量控制,在进程启动时读取——同一个可部署的 jar 可以在任何详细级别下运行,而无需重新构建。请参阅 [`docs/LOGGING.md`](docs/LOGGING.md)。 - **指标、traces 和埋点库的设计**:请参阅 [`docs/INSTRUMENTATION.md`](docs/INSTRUMENTATION.md)。 - **导出到后端(例如 Grafana Cloud)**:设置 `OTEL_EXPORTER_OTLP_ENDPOINT`(以及用于身份验证的 `OTEL_EXPORTER_OTLP_HEADERS`)——标准的 OpenTelemetry 环境变量,无需自定义配置机制。如果未设置,应用将正常运行,OTLP 导出只会在后台静默失败,而不是拒绝启动。 ### 健康检查 `GET /healthz` 针对配置好的 MySQL 连接(通过 `MYSQL_HOST`/`MYSQL_DATABASE`/`MYSQL_USER`/`MYSQL_PWD` 配置)运行 `SELECT 1`,并返回 `200 OK` 或 `503 Unhealthy: `,封装在其独立的 `mysql.healthcheck` 服务器类型 span 中。这与 Linker1 实际的数据存储(SQLite)是分开的——它的存在纯粹是为了让外部监控(例如 Grafana Cloud Synthetic Monitoring)能够轮询单个端点,以验证已部署实例的 MySQL 依赖项是否可访问。请参阅 [`docs/HEALTHCHECK.md`](docs/HEALTHCHECK.md)。 ### 监控仪表板 一个涵盖 RED 方法 HTTP 指标、JVM/进程 gauges、`/healthz` 的上下运行状态以及链接数量的 Grafana 仪表板已提交至 [`docs/grafana/linker1-dashboard.json`](docs/grafana/linker1-dashboard.json)。`pipeline.yml` 的 `deploy-prod` 作业也会在每次部署后运行 [`scripts/check-grafana-metrics.sh`](scripts/check-grafana-metrics.sh),以确认新实例确实在向 Grafana Cloud 发送遥测数据,而不仅仅是在本地响应 `/healthz`。有关如何导航仪表板、逐面板的事件指南以及自动化检查所需(但尚未配置)的密钥,请参阅 [`docs/MONITORING.md`](docs/MONITORING.md)。 ## 使用 Dev Container 运行 (Visual Studio Code) 作为本地安装的替代方案,该项目可以使用 **Dev Container** 运行。此选项提供了一个可复现的开发环境,避免了在你的机器上手动安装项目依赖项。 ### Dev Container 要求 - Docker Desktop (Windows/macOS) 或 Docker Engine (Linux)。 - Visual Studio Code。 - Microsoft 的 **Dev Containers** 扩展。 ### 1. 在 Dev Container 中打开项目 在 Visual Studio Code 中克隆并打开仓库后: 1. 下 `Ctrl + Shift + P`。 2. 运行命令: ``` Dev Containers: Reopen in Container ``` 如果是第一次以这种方式打开项目,Visual Studio Code 将自动构建容器镜像并配置开发环境。 ### 2. 等待环境配置完成 容器构建完成后,Visual Studio Code 将在配置好的开发环境中重新打开该项目。 项目所需的所有依赖项将自动可用。 ### 3. 构建项目 在 Visual Studio Code 的集成终端中,运行: ``` mvn clean package ``` ### 4. 运行应用程序 在同一个终端中,运行: ``` java -jar target/linker1-1.0-jar-with-dependencies.jar ``` ### 5. 测试应用程序 打开浏览器并访问: ``` http://localhost:8080 ``` ### 6. 验证其是否有效 1. 输入完整的 URL(例如:`https://www.google.com`)。 2. 生成短链接。 3. 复制生成的代码。 4. 访问: 以验证重定向是否正常工作。 ### 重建 Dev Container 如果对 `Dockerfile` 或 `devcontainer.json` 进行了更改,请通过运行以下命令从命令面板(`Ctrl + Shift + P`)重建环境: ``` Dev Containers: Rebuild and Reopen in Container ``` ## 项目结构 ``` linker1/ ├── .github/ # CI workflows and community health files │ ├── workflows/ │ │ ├── ci.yml # Pipeline: build, tests, package, summary, smoke test, API tests, GitHub Package publish │ │ ├── pipeline.yml # Continuous deployment: build, deploy-prod, validate-prod, rollback │ │ └── release.yml # GitHub Release on vMAJOR.MINOR.PATCH tags │ ├── CONTRIBUTING.md # Collaboration standards (branching, TDD, PRs) │ ├── PULL_REQUEST_TEMPLATE.md │ ├── ISSUE_TEMPLATE/ # Bug report / feature request templates │ └── CODEOWNERS ├── docs/ # Deployment, rollback, branch protection, and observability docs │ ├── DEPLOYMENT.md │ ├── ROLLBACK_STRATEGY.md │ ├── BRANCH_PROTECTION.md │ ├── FEATURE_FLAGS.md │ ├── LOGGING.md # LOG_LEVEL configuration │ ├── INSTRUMENTATION.md # OpenTelemetry metrics/traces/logs design │ ├── HEALTHCHECK.md # GET /healthz: MySQL SELECT 1, span, Grafana Synthetic Monitoring │ ├── MONITORING.md # Grafana dashboard guide + post-deploy telemetry check │ └── grafana/ │ └── linker1-dashboard.json # Exported/importable Grafana dashboard ├── infra/ # Infrastructure as code (Terraform) │ ├── main.tf │ ├── variables.tf │ ├── provider.tf │ ├── outputs.tf │ ├── cloud-init.yaml │ └── terraform.tfvars.example ├── resources/ │ └── logback.xml # Logging config: console + OTel appenders, LOG_LEVEL-driven verbosity ├── src/ │ ├── Main.java # Bootstrap: DB, telemetry SDK, LaunchDarkly client, Javalin server, route registration │ └── linker/ # Business logic and data access │ ├── Link.java │ ├── LinkService.java # Validation, id generation, alias │ ├── LinkRepository.java # JDBC access to SQLite │ ├── AliasConflictException.java │ ├── config/ │ │ └── FeatureFlags.java # LaunchDarkly wrapper, injected via constructor │ ├── telemetry/ # OpenTelemetry wrappers, injected via constructor │ │ ├── Telemetry.java # Builds the OpenTelemetry SDK from OTEL_* env vars │ │ ├── RequestMetrics.java # RED-method HTTP counters/histogram │ │ ├── LinkSpans.java # Traces for link create/resolve + DB duration histogram │ │ └── SystemMetrics.java # Link-count and JVM heap gauges │ ├── health/ # MySQL healthcheck, injected via constructor │ │ ├── HealthCheck.java # SELECT 1 against MySQL, wrapped in a SpanKind.SERVER span │ │ └── HealthRoutes.java # GET /healthz -> 200/503 │ └── routes/ # HTTP handlers (Javalin), constructor-injected dependencies │ ├── LinkRoutes.java │ └── StaticRoutes.java ├── test/ # Unit and integration tests (JUnit 5) ├── postman/ # Postman collection for API tests (Newman) ├── public/ │ ├── v1/ # Default frontend │ │ ├── index.html │ │ └── styles.css │ └── v2/ # Feature-flagged redesign │ ├── index-v2.html │ └── styles2.css ├── scripts/ │ ├── linker1 # Executable launcher script (packaged alongside the jar) │ ├── rollback.sh # Rolls the VM back to a previous stable tag │ └── check-grafana-metrics.sh # Post-deploy check: confirms telemetry reached Grafana Cloud ├── pom.xml # Maven configuration (JaCoCo, GitHub Packages, LaunchDarkly SDK, OpenTelemetry) ├── deploy.sh # Deployment script (OCI) └── README.md # This file ``` ## 团队 - ANDERSSON DAVID SÁNCHEZ MÉNDEZ - ANDERSON FABIAN GARCIA NIETO - DANIEL PATIÑO MEJIA - ESTEBAN AGUILERA CONTRERAS - JUAN JOSE DIAZ GOMEZ
标签:域名枚举, 用户代理, 请求拦截