diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..74b11f1 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,76 @@ +pipeline { + agent any + + parameters { + choice(name: 'APP', choices: ['tenant-platform', 'ops-platform'], description: '要构建的 Web 应用') + string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag') + booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署') + } + + environment { + ACR_REGISTRY = 'registry.cn-hangzhou.aliyuncs.com' // 替换为你的 ACR 地址 + ACR_NAMESPACE = 'xuqmgroup' + ACR_USERNAME = 'your-acr-username' + PROD_HOST = '106.54.23.149' + PROD_USER = 'ubuntu' + COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml' + } + + stages { + stage('Checkout') { + steps { checkout scm } + } + + stage('Install & Build') { + steps { + dir("${params.APP}") { + sh ''' + yarn install --frozen-lockfile + yarn build + ''' + } + } + } + + stage('Docker Build & Push') { + steps { + withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) { + script { + def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/web-${params.APP}:${params.IMAGE_TAG}" + sh """ + docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p \${ACR_PASS} + docker build -f ${params.APP}/Dockerfile -t ${imageName} . + docker push ${imageName} + docker rmi ${imageName} + """ + } + } + } + } + + stage('Deploy to Production') { + when { expression { return params.DEPLOY } } + steps { + withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) { + script { + def svcName = params.APP == 'tenant-platform' ? 'web' : 'ops-web' + def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/web-${params.APP}:${params.IMAGE_TAG}" + sh """ + ssh -i \${SSH_KEY} -o StrictHostKeyChecking=no ${PROD_USER}@${PROD_HOST} " + docker pull ${imageName} && + cd /opt/xuqm/deploy && + docker compose -f ${COMPOSE_FILE} up -d --no-deps ${svcName} && + docker image prune -f + " + """ + } + } + } + } + } + + post { + success { echo "✅ ${params.APP}:${params.IMAGE_TAG} 构建部署成功" } + failure { echo "❌ 构建失败,请检查日志" } + } +} diff --git a/tenant-platform/src/api/app.ts b/tenant-platform/src/api/app.ts index a21fff4..b0b3e83 100644 --- a/tenant-platform/src/api/app.ts +++ b/tenant-platform/src/api/app.ts @@ -25,7 +25,6 @@ export interface FeatureService { platform: 'ANDROID' | 'IOS' | 'HARMONY' serviceType: 'IM' | 'PUSH' | 'UPDATE' enabled: boolean - secretKey: string createdAt: string } @@ -48,6 +47,14 @@ export const appApi = { params: { platform, serviceType, enable }, }), - regenerateKey: (appId: string, serviceId: string) => - client.post<{ data: FeatureService }>(`/apps/${appId}/services/${serviceId}/regenerate-key`), + requestSecretVerify: (appId: string, purpose: 'REVEAL_SECRET' | 'RESET_SECRET') => + client.post<{ data: null }>(`/apps/${appId}/request-secret-verify`, null, { + params: { purpose }, + }), + + revealSecret: (appId: string, code: string) => + client.post<{ data: { appSecret: string } }>(`/apps/${appId}/reveal-secret`, { code }), + + resetSecret: (appId: string, code: string) => + client.post<{ data: { appSecret: string } }>(`/apps/${appId}/reset-secret`, { code }), } diff --git a/tenant-platform/src/api/im.ts b/tenant-platform/src/api/im.ts index 7c2a434..00219bc 100644 --- a/tenant-platform/src/api/im.ts +++ b/tenant-platform/src/api/im.ts @@ -57,4 +57,20 @@ export const imAdminApi = { getStats(appId: string) { return imClient.get<{ data: ImStats }>('/api/im/admin/stats', { params: { appId } }) }, + + registerUser(appId: string, userId: string, nickname?: string, avatar?: string) { + return imClient.post<{ data: ImUser }>( + '/api/im/admin/users', + { userId, nickname, avatar }, + { params: { appId } }, + ) + }, + + createGroup(appId: string, name: string, creatorId: string, memberIds: string[]) { + return imClient.post<{ data: ImGroup }>( + '/api/im/admin/groups', + { name, creatorId, memberIds }, + { params: { appId } }, + ) + }, } diff --git a/tenant-platform/src/views/apps/AppDetailView.vue b/tenant-platform/src/views/apps/AppDetailView.vue index 001cb9d..33734b1 100644 --- a/tenant-platform/src/views/apps/AppDetailView.vue +++ b/tenant-platform/src/views/apps/AppDetailView.vue @@ -11,8 +11,14 @@ - {{ app.appSecret }} - + {{ revealedSecret ?? '••••••••••••••••' }} + + + + + + + 重置 {{ app.description ?? '-' }} @@ -32,23 +38,10 @@ {{ serviceLabel(svcType) }} + + + + +
+

+ {{ verifyPurpose === 'REVEAL_SECRET' + ? '为保护账号安全,查看 AppSecret 需要通过邮箱验证。' + : '重置后旧 AppSecret 立即失效,请确认后继续。' }} +

+ + 发送验证码到邮箱 + +
+
+

验证码已发送至您的注册邮箱,请查收并输入:

+ +
+ 重新发送 +
+
+ +
+ + + + + {{ activationForm.platform }} / {{ serviceLabel(activationForm.serviceType) }} + + + + + +