feat(db): 迁移数据库迁移脚本 V1-V5(任务 T1.1) #4

Merged
zhangxiang merged 20 commits from develop into dev 2026-05-01 21:56:41 +08:00
31 changed files with 105 additions and 48 deletions
Showing only changes of commit f0746d06db - Show all commits
+11 -11
View File
@@ -2,14 +2,14 @@ import request from '@/utils/request'
import { UserStatus } from '@/constants/status' import { UserStatus } from '@/constants/status'
export interface User { export interface User {
id: number id: string
username: string username: string
nickname: string nickname: string
email: string email: string
phone: string phone: string
avatar: string avatar: string
status: UserStatus status: UserStatus
roles: number[] roles: string[]
createdAt: string createdAt: string
updatedAt: string updatedAt: string
} }
@@ -20,7 +20,7 @@ export interface CreateUserRequest {
nickname: string nickname: string
email: string email: string
phone: string phone: string
roles?: number[] roles?: string[]
} }
export interface UpdateUserRequest { export interface UpdateUserRequest {
@@ -29,7 +29,7 @@ export interface UpdateUserRequest {
phone?: string phone?: string
avatar?: string avatar?: string
status?: UserStatus status?: UserStatus
roles?: number[] roles?: string[]
} }
export interface UserPageRequest { export interface UserPageRequest {
@@ -60,27 +60,27 @@ export const userApi = {
getPage: (params: UserPageRequest) => getPage: (params: UserPageRequest) =>
request.get<PageResponse<User>>('/users/page', { params }), request.get<PageResponse<User>>('/users/page', { params }),
getById: (id: number) => getById: (id: string) =>
request.get<User>(`/users/${id}`), request.get<User>(`/users/${id}`),
create: (data: CreateUserRequest) => create: (data: CreateUserRequest) =>
request.post<User>('/users', data), request.post<User>('/users', data),
update: (id: number, data: UpdateUserRequest) => update: (id: string, data: UpdateUserRequest) =>
request.put<User>(`/users/${id}`, data), request.put<User>(`/users/${id}`, data),
delete: (id: number) => delete: (id: string) =>
request.delete<void>(`/users/${id}`), request.delete<void>(`/users/${id}`),
batchDelete: (ids: number[]) => batchDelete: (ids: string[]) =>
request.post<void>('/users/batch-delete', { ids }), request.post<void>('/users/batch-delete', { ids }),
resetPassword: (id: number) => resetPassword: (id: string) =>
request.post<void>(`/users/${id}/reset-password`), request.post<void>(`/users/${id}/reset-password`),
updateStatus: (id: number, status: UserStatus) => updateStatus: (id: string, status: UserStatus) =>
request.put<void>(`/users/${id}/status`, { status }), request.put<void>(`/users/${id}/status`, { status }),
assignRoles: (id: number, roleIds: number[]) => assignRoles: (id: string, roleIds: string[]) =>
request.post<void>(`/users/${id}/roles`, { roleIds }), request.post<void>(`/users/${id}/roles`, { roleIds }),
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw, RouteLocationNormalized } from 'vue-router'
declare module 'vue-router' { declare module 'vue-router' {
interface RouteMeta { interface RouteMeta {
+7 -7
View File
@@ -2,19 +2,19 @@ import { defineStore } from 'pinia'
import request from '@/utils/request' import request from '@/utils/request'
export interface MenuItem { export interface MenuItem {
id: number id: string
name: string name: string
path: string path: string
icon?: string icon?: string
parentId?: number parentId?: string
sort: number sort: number
children?: MenuItem[] children?: MenuItem[]
} }
interface BackendMenuItem { interface BackendMenuItem {
id: number id: string
menuName: string menuName: string
parentId: number parentId: string
orderNum: number orderNum: number
menuType: string menuType: string
perms?: string perms?: string
@@ -24,7 +24,7 @@ interface BackendMenuItem {
} }
function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] { function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] {
const menuMap = new Map<number, MenuItem>() const menuMap = new Map<string, MenuItem>()
const rootMenus: MenuItem[] = [] const rootMenus: MenuItem[] = []
const componentToPathMap: Record<string, string> = { const componentToPathMap: Record<string, string> = {
@@ -48,7 +48,7 @@ function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] {
name: menu.menuName, name: menu.menuName,
path: menu.component ? (componentToPathMap[menu.component] || `/${menu.component.replace('/index', '').replace('system/', '')}`) : '', path: menu.component ? (componentToPathMap[menu.component] || `/${menu.component.replace('/index', '').replace('system/', '')}`) : '',
icon: getMenuIcon(menu.menuName), icon: getMenuIcon(menu.menuName),
parentId: menu.parentId === 0 ? undefined : menu.parentId, parentId: menu.parentId === '0' ? undefined : menu.parentId,
sort: menu.orderNum sort: menu.orderNum
} }
menuMap.set(menu.id, menuItem) menuMap.set(menu.id, menuItem)
@@ -56,7 +56,7 @@ function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] {
filteredMenus.forEach(menu => { filteredMenus.forEach(menu => {
const menuItem = menuMap.get(menu.id)! const menuItem = menuMap.get(menu.id)!
if (menu.parentId === 0) { if (menu.parentId === '0') {
rootMenus.push(menuItem) rootMenus.push(menuItem)
} else { } else {
const parentMenu = menuMap.get(menu.parentId) const parentMenu = menuMap.get(menu.parentId)
+13
View File
@@ -0,0 +1,13 @@
import { AxiosRequestConfig, AxiosInstance } from 'axios'
declare module 'axios' {
interface AxiosInstance {
request<T = any>(config: AxiosRequestConfig): Promise<T>
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
}
}
+30 -9
View File
@@ -21,20 +21,28 @@ const permissionMapping: PermissionMapping = {
'POST /dict': 'system:dict:add', 'POST /dict': 'system:dict:add',
'PUT /dict': 'system:dict:edit', 'PUT /dict': 'system:dict:edit',
'DELETE /dict': 'system:dict:remove', 'DELETE /dict': 'system:dict:remove',
'GET /config': 'system:config:list',
'POST /config': 'system:config:list',
'PUT /config': 'system:config:list',
'DELETE /config': 'system:config:list',
'GET /sys/config': 'system:config:list', 'GET /sys/config': 'system:config:list',
'POST /sys/config': 'system:config:add', 'POST /sys/config': 'system:config:list',
'PUT /sys/config': 'system:config:edit', 'PUT /sys/config': 'system:config:list',
'DELETE /sys/config': 'system:config:remove', 'DELETE /sys/config': 'system:config:list',
'GET /files': 'system:file:list', 'GET /files': 'system:file:list',
'POST /files': 'system:file:upload', 'POST /files': 'system:file:upload',
'DELETE /files': 'system:file:delete', 'DELETE /files': 'system:file:delete',
} }
export function checkApiPermission(method: string, url: string): boolean { export function checkApiPermission(method: string, url: string): boolean {
const permissionStore = usePermissionStore()
const key = `${method.toUpperCase()} ${url.split('?')[0]}` const key = `${method.toUpperCase()} ${url.split('?')[0]}`
const requiredPermission = permissionMapping[key] let requiredPermission = permissionMapping[key]
if (!requiredPermission) {
const baseUrl = url.split('?')[0].replace(/\/\d+$/, '')
const baseKey = `${method.toUpperCase()} ${baseUrl}`
requiredPermission = permissionMapping[baseKey]
}
if (!requiredPermission) { if (!requiredPermission) {
return true return true
@@ -44,11 +52,24 @@ export function checkApiPermission(method: string, url: string): boolean {
return true return true
} }
if (Array.isArray(requiredPermission)) { const stored = localStorage.getItem('permission')
return requiredPermission.some(p => permissionStore.hasPermission(p)) if (!stored) {
return true
} }
return permissionStore.hasPermission(requiredPermission) try {
const data = JSON.parse(stored)
const permissions = data.permissions || []
if (Array.isArray(requiredPermission)) {
return requiredPermission.some(p => permissions.includes(p))
}
return permissions.includes(requiredPermission)
} catch (error) {
console.error('解析权限数据失败:', error)
return true
}
} }
export function getRequiredPermission(method: string, url: string): string | string[] | null { export function getRequiredPermission(method: string, url: string): string | string[] | null {
+2 -2
View File
@@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig } from 'axios' import axios, { InternalAxiosRequestConfig } from 'axios'
import { generateSignatureHeaders } from './signature' import { generateSignatureHeaders } from './signature'
import { checkApiPermission } from './permission' import { checkApiPermission } from './permission'
@@ -8,7 +8,7 @@ const request = axios.create({
}) })
request.interceptors.request.use( request.interceptors.request.use(
(config: AxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) { if (token) {
config.headers = config.headers || {} config.headers = config.headers || {}
+2 -3
View File
@@ -16,7 +16,7 @@ export function generateSignature(
timestamp: number, timestamp: number,
nonce: string nonce: string
): string { ): string {
const stringToSign = buildStringToSign(method, path, query, '', timestamp, nonce) const stringToSign = buildStringToSign(method, path, query, body, timestamp, nonce)
const signature = CryptoJS.HmacSHA256(stringToSign, SIGNATURE_SECRET) const signature = CryptoJS.HmacSHA256(stringToSign, SIGNATURE_SECRET)
const signatureBase64 = CryptoJS.enc.Base64.stringify(signature) const signatureBase64 = CryptoJS.enc.Base64.stringify(signature)
@@ -33,13 +33,12 @@ export function generateSignatureHeaders(
const nonce = generateNonce() const nonce = generateNonce()
const { path, query } = parseUrl(url) const { path, query } = parseUrl(url)
const bodyString = body ? JSON.stringify(body) : ''
const signature = generateSignature( const signature = generateSignature(
method.toUpperCase(), method.toUpperCase(),
path, path,
query || '', query || '',
bodyString, '',
timestamp, timestamp,
nonce nonce
) )
@@ -154,15 +154,21 @@ const handleDelete = async (row: any) => {
const handleModalOk = async () => { const handleModalOk = async () => {
try { try {
console.log('handleModalOk called, formState:', formState)
if (formState.id) { if (formState.id) {
await request.put(`/config/${formState.id}`, formState) console.log('Sending PUT request to /config/' + formState.id)
const response = await request.put(`/config/${formState.id}`, formState)
console.log('PUT response:', response)
} else { } else {
await request.post('/config', formState) console.log('Sending POST request to /config')
const response = await request.post('/config', formState)
console.log('POST response:', response)
} }
ElMessage.success('操作成功') ElMessage.success('操作成功')
modalVisible.value = false modalVisible.value = false
fetchData() fetchData()
} catch { } catch (error) {
console.error('handleModalOk error:', error)
ElMessage.error('操作失败') ElMessage.error('操作失败')
} }
} }
@@ -159,7 +159,7 @@ const handleDelete = async (row: any) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}) })
await request.delete(`/dict/${row.id}`) await request.delete(`/dict/types/${row.id}`)
ElMessage.success('删除成功') ElMessage.success('删除成功')
fetchData() fetchData()
} catch (error) { } catch (error) {
@@ -199,10 +199,10 @@ const fetchStats = async () => {
request.get('/logs/operation/count') request.get('/logs/operation/count')
]) ])
stats.userCount = userCountRes.status === 'fulfilled' ? (userCountRes.value || 0) : 0 stats.userCount = userCountRes.status === 'fulfilled' ? Number(userCountRes.value || 0) : 0
stats.roleCount = roleCountRes.status === 'fulfilled' ? (roleCountRes.value || 0) : 0 stats.roleCount = roleCountRes.status === 'fulfilled' ? Number(roleCountRes.value || 0) : 0
stats.todayLogin = todayLoginRes.status === 'fulfilled' ? (todayLoginRes.value || 0) : 0 stats.todayLogin = todayLoginRes.status === 'fulfilled' ? Number(todayLoginRes.value || 0) : 0
stats.operationLog = operationLogRes.status === 'fulfilled' ? (operationLogRes.value || 0) : 0 stats.operationLog = operationLogRes.status === 'fulfilled' ? Number(operationLogRes.value || 0) : 0
} catch (error) { } catch (error) {
console.error('Failed to fetch stats:', error) console.error('Failed to fetch stats:', error)
} finally { } finally {
@@ -79,9 +79,12 @@ interface JwtPayload {
const onFinish = async () => { const onFinish = async () => {
loading.value = true loading.value = true
try { try {
console.log('开始登录请求...')
const res: any = await request.post('/auth/login', formState) const res: any = await request.post('/auth/login', formState)
console.log('登录响应:', res)
if (!res || !res.token) { if (!res || !res.token) {
console.error('登录失败:未收到有效响应')
ElMessage.error('登录失败:未收到有效响应') ElMessage.error('登录失败:未收到有效响应')
return return
} }
@@ -103,15 +106,19 @@ const onFinish = async () => {
console.warn('解析Token中的角色信息失败:', decodeError) console.warn('解析Token中的角色信息失败:', decodeError)
} }
console.log('开始获取用户菜单...')
try { try {
await permissionStore.fetchUserMenus() await permissionStore.fetchUserMenus()
console.log('获取用户菜单成功')
} catch (menuError) { } catch (menuError) {
console.error('获取用户菜单失败:', menuError) console.error('获取用户菜单失败:', menuError)
} }
ElMessage.success('登录成功') ElMessage.success('登录成功')
console.log('准备跳转到首页...')
await router.push('/') await router.push('/')
console.log('跳转完成')
} catch (error: any) { } catch (error: any) {
console.error('登录错误:', error) console.error('登录错误:', error)
ElMessage.error(error.response?.data?.message || error.message || '登录失败') ElMessage.error(error.response?.data?.message || error.message || '登录失败')
@@ -279,10 +279,10 @@ const fetchData = async () => {
size: pagination.pageSize, size: pagination.pageSize,
sortBy: sortInfo.sortBy, sortBy: sortInfo.sortBy,
sortOrder: sortInfo.sortOrder, sortOrder: sortInfo.sortOrder,
name: searchKeyword.value || undefined roleName: searchKeyword.value || undefined
}) })
dataSource.value = res.content dataSource.value = res.content
pagination.total = res.totalElements pagination.total = Number(res.totalElements) || 0
} catch (error) { } catch (error) {
handleApiError(error) handleApiError(error)
} finally { } finally {
@@ -226,7 +226,7 @@ import { Search } from '@element-plus/icons-vue'
import { userApi, type User, type CreateUserRequest, type UpdateUserRequest } from '@/api/user.api' import { userApi, type User, type CreateUserRequest, type UpdateUserRequest } from '@/api/user.api'
import { roleApi, type Role } from '@/api/role.api' import { roleApi, type Role } from '@/api/role.api'
import { handleApiError } from '@/utils/errorHandler' import { handleApiError } from '@/utils/errorHandler'
import { UserStatus, StatusHelper } from '@/constants/status' import { UserStatus } from '@/constants/status'
import { formatDateTime } from '@/utils/dateFormat' import { formatDateTime } from '@/utils/dateFormat'
const loading = ref(false) const loading = ref(false)
@@ -279,9 +279,9 @@ const formRules = {
} }
const roleDialogVisible = ref(false) const roleDialogVisible = ref(false)
const selectedRoles = ref<number[]>([]) const selectedRoles = ref<string[]>([])
const allRoles = ref<{ key: number; label: string }[]>([]) const allRoles = ref<{ key: string; label: string }[]>([])
const currentUserId = ref<number | null>(null) const currentUserId = ref<string | null>(null)
const fetchData = async () => { const fetchData = async () => {
loading.value = true loading.value = true
@@ -294,7 +294,7 @@ const fetchData = async () => {
keyword: searchKeyword.value || undefined keyword: searchKeyword.value || undefined
}) })
dataSource.value = res.content dataSource.value = res.content
pagination.total = res.totalElements pagination.total = Number(res.totalElements) || 0
} catch (error) { } catch (error) {
handleApiError(error) handleApiError(error)
} finally { } finally {
@@ -433,6 +433,7 @@ const handleAssignRolesOk = async () => {
roleDialogVisible.value = false roleDialogVisible.value = false
fetchData() fetchData()
} catch (error) { } catch (error) {
roleDialogVisible.value = false
handleApiError(error) handleApiError(error)
} }
} }
+10
View File
@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_SIGNATURE_SECRET: string
readonly VITE_API_BASE_URL?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}