页脚展示前后端版本与构建信息
- 新增 internal/version 包维护服务端版本(默认 0.1.0,可用 -ldflags -X 覆盖),GET /api/health 返回 version 字段 - main.go Swagger @version 与常量对齐为 0.1.0,重新生成 docs;api_test 增加版本断言 - 前端 package.json 升至 0.1.0,Vite 注入版本、git 短哈希与构建时间(无 git 自动省略哈希) - 新增 api/version.ts 与 version store,启动时从 health 读取后端版本(失败显示 —) - 页脚左下角显示“前端 vX | 后端 vY | 构建 +hash · 时间”,三语文案补齐
This commit is contained in:
18 files changed
+128
-15
No files matched your search
+6
-2
@@ -308,7 +308,7 @@ const docTemplate = `{
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Check service and database connectivity; returns 503 when the database is unavailable.",
|
||||
"description": "Check service and database connectivity; returns 503 when the database is unavailable. The version field reports the current server version.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -2401,6 +2401,10 @@ const docTemplate = `{
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "ok"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"example": "0.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3229,7 +3233,7 @@ const docTemplate = `{
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Version: "0.1.0",
|
||||
Host: "",
|
||||
BasePath: "/api",
|
||||
Schemes: []string{},
|
||||
|
||||
+6
-2
@@ -4,7 +4,7 @@
|
||||
"description": "Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON.\nSwagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.\nEndpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user.",
|
||||
"title": "Rill API",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"basePath": "/api",
|
||||
"paths": {
|
||||
@@ -301,7 +301,7 @@
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Check service and database connectivity; returns 503 when the database is unavailable.",
|
||||
"description": "Check service and database connectivity; returns 503 when the database is unavailable. The version field reports the current server version.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -2394,6 +2394,10 @@
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "ok"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"example": "0.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+5
-2
@@ -8,6 +8,9 @@ definitions:
|
||||
status:
|
||||
example: ok
|
||||
type: string
|
||||
version:
|
||||
example: 0.1.0
|
||||
type: string
|
||||
type: object
|
||||
auth.LoginRequest:
|
||||
properties:
|
||||
@@ -584,7 +587,7 @@ info:
|
||||
Swagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.
|
||||
Endpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user.
|
||||
title: Rill API
|
||||
version: "1.0"
|
||||
version: 0.1.0
|
||||
paths:
|
||||
/auth/login:
|
||||
post:
|
||||
@@ -787,7 +790,7 @@ paths:
|
||||
/health:
|
||||
get:
|
||||
description: Check service and database connectivity; returns 503 when the database
|
||||
is unavailable.
|
||||
is unavailable. The version field reports the current server version.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
||||
Vendored
+4
@@ -1 +1,5 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare const __APP_VERSION__: string
|
||||
declare const __GIT_HASH__: string
|
||||
declare const __BUILD_TIME__: string
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { request } from './http'
|
||||
|
||||
export interface HealthInfo {
|
||||
status: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export function getHealth(): Promise<HealthInfo> {
|
||||
return request<HealthInfo>('/health')
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import LanguageSwitcher from '@/components/layout/LanguageSwitcher.vue'
|
||||
import ThemeSwitcher from '@/components/layout/ThemeSwitcher.vue'
|
||||
import { isInternalNavUrl, navLabel, useNavStore } from '@/stores/nav'
|
||||
import { useSiteStore } from '@/stores/site'
|
||||
import { useVersionStore } from '@/stores/version'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const nav = useNavStore()
|
||||
const site = useSiteStore()
|
||||
const version = useVersionStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,5 +46,9 @@ const site = useSiteStore()
|
||||
</template>
|
||||
</nav>
|
||||
<p>{{ site.footer || t('footer.copyright') }}</p>
|
||||
<p class="mt-4 text-left">
|
||||
{{ t('footer.versions', { frontend: version.frontend, backend: version.backendLabel })
|
||||
}}<span v-if="version.build"> | {{ t('footer.build', { build: version.build }) }}</span>
|
||||
</p>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -48,6 +48,8 @@ const enUS: MessageSchema = {
|
||||
},
|
||||
footer: {
|
||||
copyright: 'Rill streaming platform · Page frame placeholder · All images are blank placeholders',
|
||||
versions: 'Frontend {frontend} | Backend {backend}',
|
||||
build: 'Build {build}',
|
||||
},
|
||||
language: {
|
||||
title: 'Language',
|
||||
|
||||
@@ -48,6 +48,8 @@ const jaJP: MessageSchema = {
|
||||
},
|
||||
footer: {
|
||||
copyright: 'Rill 動画配信サービス · ページフレームのプレースホルダー · 画像はすべて空のプレースホルダーです',
|
||||
versions: 'フロントエンド {frontend} | バックエンド {backend}',
|
||||
build: 'ビルド {build}',
|
||||
},
|
||||
language: {
|
||||
title: '言語',
|
||||
|
||||
@@ -46,6 +46,8 @@ const zhCN = {
|
||||
},
|
||||
footer: {
|
||||
copyright: 'Rill 流媒体服务平台 · 页面框架占位 · 所有图片均为空白占位框',
|
||||
versions: '前端 {frontend} | 后端 {backend}',
|
||||
build: '构建 {build}',
|
||||
},
|
||||
language: {
|
||||
title: '语言',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { setUnauthorizedHandler } from './api/http'
|
||||
import { useAuthStore } from './stores/auth'
|
||||
import { useNavStore } from './stores/nav'
|
||||
import { useSiteStore } from './stores/site'
|
||||
import { useVersionStore } from './stores/version'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
@@ -27,6 +28,9 @@ void site.load()
|
||||
const nav = useNavStore()
|
||||
void nav.load()
|
||||
|
||||
const version = useVersionStore()
|
||||
void version.load()
|
||||
|
||||
setUnauthorizedHandler(() => {
|
||||
const wasAuthenticated = auth.isAuthenticated
|
||||
auth.logout()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getHealth } from '@/api/version'
|
||||
|
||||
function buildLabel(): string {
|
||||
const parts: string[] = []
|
||||
if (__GIT_HASH__) {
|
||||
parts.push(`+${__GIT_HASH__}`)
|
||||
}
|
||||
if (__BUILD_TIME__) {
|
||||
parts.push(__BUILD_TIME__)
|
||||
}
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
export const useVersionStore = defineStore('version', () => {
|
||||
const frontend = ref(`v${__APP_VERSION__}`)
|
||||
const build = ref(buildLabel())
|
||||
const backend = ref('')
|
||||
|
||||
const backendLabel = computed(() => (backend.value ? `v${backend.value}` : '—'))
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const info = await getHealth()
|
||||
backend.value = info.version
|
||||
} catch {
|
||||
// 加载失败时显示占位符
|
||||
}
|
||||
}
|
||||
|
||||
return { frontend, build, backend, backendLabel, load }
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import { execSync } from 'node:child_process'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
@@ -5,8 +7,33 @@ import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
function gitHash(): string {
|
||||
try {
|
||||
return execSync('git rev-parse --short HEAD', {
|
||||
cwd: fileURLToPath(new URL('.', import.meta.url)),
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
})
|
||||
.toString()
|
||||
.trim()
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8')) as {
|
||||
version: string
|
||||
}
|
||||
|
||||
// 构建时间(本地时区 YYYY-MM-DD HH:mm)
|
||||
const buildTime = new Date().toLocaleString('sv-SE').slice(0, 16)
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(pkg.version),
|
||||
__GIT_HASH__: JSON.stringify(gitHash()),
|
||||
__BUILD_TIME__: JSON.stringify(buildTime),
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
|
||||
+7
-5
@@ -20,6 +20,7 @@ import (
|
||||
"rill/internal/site"
|
||||
"rill/internal/user"
|
||||
"rill/internal/usergroup"
|
||||
"rill/internal/version"
|
||||
"rill/internal/videocategory"
|
||||
)
|
||||
|
||||
@@ -107,12 +108,13 @@ func RegisterRoutes(rg *gin.RouterGroup, db *gorm.DB, cfg *config.Config) {
|
||||
|
||||
// HealthResponse 健康检查响应。
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status" example:"ok"`
|
||||
Error string `json:"error,omitempty" example:"database unavailable"`
|
||||
Status string `json:"status" example:"ok"`
|
||||
Version string `json:"version" example:"0.1.0"`
|
||||
Error string `json:"error,omitempty" example:"database unavailable"`
|
||||
}
|
||||
|
||||
// @Summary Health check
|
||||
// @Description Check service and database connectivity; returns 503 when the database is unavailable.
|
||||
// @Description Check service and database connectivity; returns 503 when the database is unavailable. The version field reports the current server version.
|
||||
// @Tags public
|
||||
// @Produce json
|
||||
// @Success 200 {object} api.HealthResponse
|
||||
@@ -121,9 +123,9 @@ type HealthResponse struct {
|
||||
func health(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if err := database.Ping(c.Request.Context(), db); err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, HealthResponse{Status: "error", Error: "database unavailable"})
|
||||
c.JSON(http.StatusServiceUnavailable, HealthResponse{Status: "error", Version: version.Version, Error: "database unavailable"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HealthResponse{Status: "ok"})
|
||||
c.JSON(http.StatusOK, HealthResponse{Status: "ok", Version: version.Version})
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"rill/internal/testutil"
|
||||
"rill/internal/version"
|
||||
)
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
@@ -23,4 +24,7 @@ func TestHealth(t *testing.T) {
|
||||
if resp["status"] != "ok" {
|
||||
t.Errorf("status = %q, 期望 ok", resp["status"])
|
||||
}
|
||||
if resp["version"] != version.Version {
|
||||
t.Errorf("version = %q, 期望 %q", resp["version"], version.Version)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Package version 保存服务端版本号,构建时可通过
|
||||
// -ldflags "-X rill/internal/version.Version=x.y.z" 覆盖。
|
||||
package version
|
||||
|
||||
// Version 当前服务端版本。
|
||||
var Version = "0.1.0"
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
//go:generate go tool swag init -g main.go -o docs --parseInternal
|
||||
|
||||
// @title Rill API
|
||||
// @version 1.0
|
||||
// @version 0.1.0
|
||||
// @description Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON.
|
||||
// @description Swagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.
|
||||
// @description Endpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user.
|
||||
|
||||
Reference in New Issue
Block a user