refactor: 整理项目根目录结构

整理内容:
- 删除 60+ 临时测试输出文件 (*.txt)
- 移动二进制文件到 bin/ 目录
- 移动 Shell 脚本到 scripts/ 目录
  - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh
  - scripts/deploy/: deploy_*.sh, simple_deploy.sh
  - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh
  - scripts/test/: test_*.sh, test_*.bat
- 移动批处理文件到 scripts/
- 移动 Python 脚本到 tools/
- 清理临时日志文件

保留根目录必要文件:
- go.mod, go.sum, go.work
- Makefile, docker-compose.yml
- .env.example, .gitignore
- README.md, AGENTS.md, DEPLOY_GUIDE.md

验证: go build ./... && go test ./... 通过
This commit is contained in:
2026-04-07 18:10:36 +08:00
parent 5dbb530b76
commit 5b6bd93179
152 changed files with 8775 additions and 4084 deletions

View File

@@ -9,6 +9,7 @@ import { chromium, expect } from '@playwright/test'
const TEXT = {
accessControl: '\u8bbf\u95ee\u63a7\u5236',
active: '\u542f\u7528',
adminBootstrapTitle: '\u7cfb\u7edf\u5c1a\u672a\u521d\u59cb\u5316\u9996\u4e2a\u7ba1\u7406\u5458\u8d26\u53f7',
adminRoleName: '\u7ba1\u7406\u5458',
adminBootstrapAction: '\u521d\u59cb\u5316\u7ba1\u7406\u5458',
@@ -23,21 +24,41 @@ const TEXT = {
bootstrapAdminPasswordPlaceholder: '\u7ba1\u7406\u5458\u5bc6\u7801',
bootstrapAdminSubmit: '\u5b8c\u6210\u521d\u59cb\u5316\u5e76\u8fdb\u5165\u7cfb\u7edf',
bootstrapAdminUsernamePlaceholder: '\u7ba1\u7406\u5458\u7528\u6237\u540d',
changePassword: '\u4fee\u6539\u5bc6\u7801',
confirmPasswordPlaceholder: '\u786e\u8ba4\u5bc6\u7801',
createAccount: '\u521b\u5efa\u8d26\u53f7',
createUser: '\u521b\u5efa\u7528\u6237',
createUser: '\u521b\u5efa\u7528\u5458',
createUserEmailPlaceholder: '\u90ae\u7bb1\u5730\u5740',
createUserPasswordPlaceholder: '\u8bf7\u8f93\u5165\u521d\u59cb\u5bc6\u7801',
createUserUsernamePlaceholder: '\u8bf7\u8f93\u5165\u7528\u6237\u540d',
createRole: '\u521b\u5efa\u89d2\u8272',
createPermission: '\u521b\u5efa\u6743\u9650',
dashboard: '\u603b\u89c8',
delete: '\u5220\u9664',
deleteConfirm: '\u786e\u5b9a\u5220\u9664',
deviceManagement: '\u8bbe\u5907\u7ba1\u7406',
devices: '\u8bbe\u5907',
disabled: '\u7981\u7528',
edit: '\u7f16\u8f91',
editUser: '\u7f16\u8f91\u7528\u6237',
emailCodeLogin: '\u90ae\u7bb1\u9a8c\u8bc1\u7801',
emailActivationSuccess: '\u90ae\u7bb1\u9a8c\u8bc1\u6210\u529f',
export: '\u5bfc\u51fa',
forgotPassword: '\u5fd8\u8bb0\u5bc6\u7801\uff1f',
loginAction: '\u767b\u5f55',
loginLogs: '\u767b\u5f55\u65e5\u5fd7',
loginNow: '\u7acb\u5373\u767b\u5f55',
logout: '\u9000\u51fa\u767b\u5f55',
logoutOthers: '\u9000\u51fa\u5176\u4ed6\u8bbe\u5907',
name: '\u540d\u79f0',
newPassword: '\u65b0\u5bc6\u7801',
newPasswordPlaceholder: '\u8bf7\u8f93\u5165\u65b0\u5bc6\u7801',
nickname: '\u6635\u79f0',
oldPassword: '\u5f53\u524d\u5bc6\u7801',
oldPasswordPlaceholder: '\u8bf7\u8f93\u5165\u5f53\u524d\u5bc6\u7801',
operationLogs: '\u64cd\u4f5c\u65e5\u5fd7',
passwordPlaceholder: '\u5bc6\u7801',
permissions: '\u6743\u9650\u7ba1\u7406',
permissionsAction: '\u6743\u9650',
permissionsHint: '\u9009\u62e9\u8981\u5206\u914d\u7ed9\u8be5\u89d2\u8272\u7684\u6743\u9650',
profile: '\u4e2a\u4eba\u8d44\u6599',
@@ -45,15 +66,22 @@ const TEXT = {
registerSuccess: '\u6ce8\u518c\u6210\u529f',
roleFilter: '\u89d2\u8272\u540d\u79f0/\u4ee3\u7801',
roles: '\u89d2\u8272\u7ba1\u7406',
save: '\u4fdd\u5b58',
security: '\u5b89\u5168\u8bbe\u7f6e',
smsCodeLogin: '\u77ed\u4fe1\u9a8c\u8bc1\u7801',
status: '\u72b6\u6001',
systemManagement: '\u7cfb\u7edf\u7ba1\u7406',
todaySuccessLogins: '\u4eca\u65e5\u6210\u529f\u767b\u5f55',
totalUsers: '\u7528\u6237\u603b\u6570',
trust: '\u4fe1\u4efb',
untrust: '\u53d6\u6d88\u4fe1\u4efb',
userDetail: '\u7528\u6237\u8be6\u60c5',
userDetailAction: '\u8be6\u60c5',
userId: '\u7528\u6237 ID',
usernamePlaceholder: '\u7528\u6237\u540d',
users: '\u7528\u6237\u7ba1\u7406',
usersFilter: '\u7528\u6237\u540d/\u90ae\u7bb1/\u624b\u673a\u53f7',
webhooks: 'Webhooks',
welcomeLogin: '\u6b22\u8fce\u767b\u5f55',
}
@@ -1125,6 +1153,198 @@ async function verifyDesktopAndMobileNavigation(page) {
await expect(page.locator('body')).toContainText(TEXT.todaySuccessLogins, { timeout: 10 * 1000 })
}
async function verifyUserManagementCRUD(page) {
logDebug('verifyUserManagementCRUD: login /login')
await loginFromLoginPage(page)
await expandSidebarGroup(page, TEXT.accessControl)
await clickSidebarMenu(page, TEXT.users)
await expect(page).toHaveURL(/\/users$/)
const testUsername = `e2e_crud_${Date.now()}`
const testEmail = `${testUsername}@example.com`
const createUserModal = page.locator('.ant-modal').last()
await forceClick(page.getByRole('button', { name: TEXT.createUser }).first())
await expect(createUserModal).toBeVisible({ timeout: 10 * 1000 })
const createUserResponsePromise = page.waitForResponse((response) => {
return response.url().includes('/api/v1/users') && response.request().method() === 'POST'
})
await forceFillInput(
createUserModal.locator(`input[placeholder="${TEXT.createUserUsernamePlaceholder}"]`).first(),
testUsername,
)
await forceFillInput(
createUserModal.locator(`input[placeholder="${TEXT.createUserPasswordPlaceholder}"]`).first(),
'Crud123!@#',
)
await forceFillInput(
createUserModal.locator(`input[placeholder="${TEXT.createUserEmailPlaceholder}"]`).first(),
testEmail,
)
await forceClick(createUserModal.locator('.ant-btn-primary').last())
const createUserResponse = await createUserResponsePromise
await assertApiSuccessResponse(createUserResponse, 'create user CRUD')
await expect(page.locator('tbody tr').filter({ hasText: testUsername }).first()).toBeVisible({ timeout: 20 * 1000 })
const userRow = page.locator('tbody tr').filter({ hasText: testUsername }).first()
await forceClick(userRow.getByRole('button', { name: TEXT.edit }))
const editDrawer = page.locator('.ant-drawer')
await expect(editDrawer).toBeVisible({ timeout: 10 * 1000 })
const editResponsePromise = page.waitForResponse((response) => {
return response.url().includes(`/api/v1/users/`) && response.request().method() === 'PUT'
})
await forceClick(editDrawer.locator('.ant-btn-primary').last())
const editResponse = await editResponsePromise
await assertApiSuccessResponse(editResponse, 'edit user CRUD')
await forceClick(userRow.getByRole('button', { name: TEXT.userDetailAction }))
const detailDrawer = page.locator('.ant-drawer')
await expect(detailDrawer).toBeVisible({ timeout: 10 * 1000 })
await expect(detailDrawer).toContainText(testUsername)
await page.goto(appUrl('/users'))
await forceFillInput(page.getByPlaceholder(TEXT.usersFilter), testUsername)
await expect(page.locator('tbody tr').filter({ hasText: testUsername }).first()).toBeVisible({ timeout: 10 * 1000 })
await forceClick(userRow.getByRole('button', { name: TEXT.delete }))
const deleteConfirmModal = page.locator('.ant-modal-confirm')
await expect(deleteConfirmModal).toBeVisible({ timeout: 10 * 1000 })
const deleteResponsePromise = page.waitForResponse((response) => {
return response.url().includes(`/api/v1/users/`) && response.request().method() === 'DELETE'
})
await forceClick(deleteConfirmModal.locator('.ant-btn-primary').last())
const deleteResponse = await deleteResponsePromise
await assertApiSuccessResponse(deleteResponse, 'delete user CRUD')
await expect(page.locator('tbody tr').filter({ hasText: testUsername }).first()).toHaveCount(0, { timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyRoleManagementCRUD(page) {
logDebug('verifyRoleManagementCRUD: login /login')
await loginFromLoginPage(page)
await expandSidebarGroup(page, TEXT.accessControl)
await clickSidebarMenu(page, TEXT.roles)
await expect(page).toHaveURL(/\/roles$/)
await expect(page.getByRole('button', { name: TEXT.createRole })).toBeVisible()
await expect(page.locator('tbody tr').filter({ hasText: TEXT.adminRoleName }).first()).toBeVisible({ timeout: 10 * 1000 })
const adminRoleRow = page.locator('tbody tr').filter({ hasText: TEXT.adminRoleName }).first()
await forceClick(adminRoleRow.getByRole('button', { name: TEXT.permissionsAction }))
const permissionsModal = page.locator('.ant-modal')
await expect(permissionsModal.locator('.ant-modal-title')).toContainText(TEXT.assignPermissions)
await forceClick(permissionsModal.locator('.ant-modal-close'))
await expect(permissionsModal).not.toBeVisible({ timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyDeviceManagement(page) {
logDebug('verifyDeviceManagement: login /login')
await loginFromLoginPage(page)
await expandSidebarGroup(page, TEXT.systemManagement)
await clickSidebarMenu(page, TEXT.devices)
await expect(page).toHaveURL(/\/devices$/)
await expect(page.getByText(TEXT.deviceManagement)).toBeVisible({ timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyLoginLogs(page) {
logDebug('verifyLoginLogs: login /login')
await loginFromLoginPage(page)
await expandSidebarGroup(page, TEXT.systemManagement)
await clickSidebarMenu(page, TEXT.loginLogs)
await expect(page).toHaveURL(/\/login-logs$/)
await expect(page.getByText(TEXT.loginLogs)).toBeVisible({ timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyOperationLogs(page) {
logDebug('verifyOperationLogs: login /login')
await loginFromLoginPage(page)
await expandSidebarGroup(page, TEXT.systemManagement)
await clickSidebarMenu(page, TEXT.operationLogs)
await expect(page).toHaveURL(/\/operation-logs$/)
await expect(page.getByText(TEXT.operationLogs)).toBeVisible({ timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyWebhookManagement(page) {
logDebug('verifyWebhookManagement: login /login')
await loginFromLoginPage(page)
await expandSidebarGroup(page, TEXT.systemManagement)
await clickSidebarMenu(page, TEXT.webhooks)
await expect(page).toHaveURL(/\/webhooks$/)
await expect(page.getByText(TEXT.webhooks)).toBeVisible({ timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyProfileAndSecurity(page) {
logDebug('verifyProfileAndSecurity: login /login')
const credentials = await loginFromLoginPage(page)
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.profile, { exact: true }))
await expect(page).toHaveURL(/\/profile$/)
await expect(page.locator('body')).toContainText(credentials.username, { timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.security))
await expect(page).toHaveURL(/\/profile\/security$/)
await expect(page.getByText(TEXT.changePassword)).toBeVisible({ timeout: 10 * 1000 })
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function verifyDashboardStats(page) {
logDebug('verifyDashboardStats: login /login')
await loginFromLoginPage(page)
await expect(page).toHaveURL(/\/dashboard$/)
await expect(page.getByText(TEXT.todaySuccessLogins)).toBeVisible({ timeout: 10 * 1000 })
await expect(page.getByText(TEXT.totalUsers)).toBeVisible()
await forceClick(page.locator('[class*="userTrigger"]'))
await forceClick(page.getByText(TEXT.logout, { exact: true }))
await expect(page).toHaveURL(/\/login$/)
}
async function main() {
let browser = null
let managedBrowser = null
@@ -1159,6 +1379,14 @@ async function main() {
await runScenario(browser, context, 'auth-workflow', verifyAuthWorkflow)
await runScenario(browser, context, 'responsive-login', verifyResponsiveLogin)
await runScenario(browser, context, 'desktop-mobile-navigation', verifyDesktopAndMobileNavigation)
await runScenario(browser, context, 'user-management-crud', verifyUserManagementCRUD)
await runScenario(browser, context, 'role-management-crud', verifyRoleManagementCRUD)
await runScenario(browser, context, 'device-management', verifyDeviceManagement)
await runScenario(browser, context, 'login-logs', verifyLoginLogs)
await runScenario(browser, context, 'operation-logs', verifyOperationLogs)
await runScenario(browser, context, 'webhook-management', verifyWebhookManagement)
await runScenario(browser, context, 'profile-and-security', verifyProfileAndSecurity)
await runScenario(browser, context, 'dashboard-stats', verifyDashboardStats)
console.log('Playwright CDP E2E completed successfully')
} finally {
await browser?.close().catch(() => {})