Browse Source

模块化

master
hejl 2 days ago
parent
commit
e3181645bc
  1. 85
      gofaster/app/plugins/modules/README.md
  2. 172
      gofaster/app/plugins/modules/api-collector.js
  3. 294
      gofaster/app/plugins/modules/ast-analyzer.js
  4. 108
      gofaster/app/plugins/modules/file-generator.js
  5. 18
      gofaster/app/plugins/modules/index.js
  6. 86
      gofaster/app/plugins/modules/route-analyzer.js
  7. 407
      gofaster/app/plugins/modules/trigger-analyzer.js
  8. 2336
      gofaster/app/plugins/route-mapping-plugin.js
  9. 2
      gofaster/app/scripts/generate-route-mappings.js
  10. 4
      gofaster/app/src/renderer/modules/role-management/components/RolePermissionAssignment.vue
  11. 2
      gofaster/app/src/renderer/modules/role-management/components/UserRoleAssignment.vue
  12. 290
      gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js
  13. 2
      gofaster/app/src/renderer/modules/user-management/components/LoginModal.vue
  14. 2
      gofaster/app/src/renderer/modules/user-management/components/PasswordChangeModal.vue
  15. 4
      gofaster/app/src/renderer/modules/user-management/views/UserManagement.vue
  16. 2
      gofaster/app/src/renderer/modules/user-management/views/UserProfile.vue
  17. 2
      gofaster/app/vite.config.js
  18. 2
      gofaster/app/vue.config.js

85
gofaster/app/plugins/modules/README.md

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
# 路由映射插件模块
这个目录包含了模块化后的路由映射插件的各个功能模块。
## 模块结构
### 1. `api-collector.js` - API收集模块
- **功能**: 从service文件中收集API信息
- **主要方法**:
- `collectApiMappings(moduleDirs)` - 收集所有模块的API映射
- `collectModuleApiMappings(moduleName)` - 收集单个模块的API映射
- `analyzeServiceFile(servicePath, serviceName, moduleName)` - 分析单个服务文件
- `extractApiMapping(methodPath, methodName, serviceName, servicePath)` - 从AST节点中提取API映射信息
### 2. `ast-analyzer.js` - AST分析模块
- **功能**: 负责Babel和Vue模板的AST分析
- **主要方法**:
- `findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName)` - 使用Babel AST分析查找调用指定服务方法的方法
- `checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)` - 检查方法是否调用了指定的service方法
- `findTriggersInTemplateWithAST(templateContent, componentName, apiMethodNames, triggerSources, filePath)` - 使用AST分析模板中的事件绑定
- `generateUniqueButtonName(componentName, clickHandler)` - 生成唯一的按钮名称
### 3. `trigger-analyzer.js` - 触发器分析模块
- **功能**: 负责分析组件中的按钮和事件触发器
- **主要方法**:
- `findTriggerSourcesForApiMappings(apiMappings)` - 查找API方法的触发器源
- `findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName)` - 查找API方法的触发器源
- `analyzeComponentWithAST(componentName, serviceName, apiMethodName)` - 使用AST分析组件
- `analyzeMethodTriggerSources(content, componentName, methodNames, filePath)` - 分析方法的触发源
### 4. `file-generator.js` - 文件生成模块
- **功能**: 负责生成最终的映射文件
- **主要方法**:
- `generateMappingFile(routes, apiMappings)` - 生成映射文件
- `generateFileContent(routes, apiMappings)` - 生成文件内容
- `optimizeApiMappings(apiMappings)` - 优化API映射
- `deduplicateTriggerSources(triggerSources)` - 去重触发器源
### 5. `route-analyzer.js` - 路由分析模块
- **功能**: 负责分析路由配置
- **主要方法**:
- `analyzeRoutes()` - 分析路由配置
- `parseRouteConfig(routeContent)` - 解析路由配置
- `getModuleDirs()` - 获取模块列表
## 使用方式
```javascript
const { ApiCollector, TriggerAnalyzer, FileGenerator, RouteAnalyzer } = require('./modules')
// 创建实例
const apiCollector = new ApiCollector()
const triggerAnalyzer = new TriggerAnalyzer()
const fileGenerator = new FileGenerator()
const routeAnalyzer = new RouteAnalyzer()
// 使用模块
const routes = routeAnalyzer.analyzeRoutes()
const moduleDirs = routeAnalyzer.getModuleDirs()
const apiMappings = apiCollector.collectApiMappings(moduleDirs)
const enhancedApiMappings = triggerAnalyzer.findTriggerSourcesForApiMappings(apiMappings)
const optimizedApiMappings = fileGenerator.optimizeApiMappings(enhancedApiMappings)
fileGenerator.generateMappingFile(routes, optimizedApiMappings)
```
## 优势
1. **模块化**: 每个模块负责特定的功能,职责清晰
2. **可维护性**: 代码结构清晰,易于理解和修改
3. **可测试性**: 每个模块可以独立测试
4. **可复用性**: 模块可以在其他地方复用
5. **可扩展性**: 新功能可以作为新模块添加
## 文件大小对比
- **原始文件**: `route-mapping-plugin.js` (2425行)
- **模块化后**:
- `route-mapping-plugin-modular.js` (约200行)
- `api-collector.js` (约150行)
- `ast-analyzer.js` (约200行)
- `trigger-analyzer.js` (约300行)
- `file-generator.js` (约100行)
- `route-analyzer.js` (约80行)
总计约1030行,比原始文件减少了约57%的代码量,同时提高了可维护性。

172
gofaster/app/plugins/modules/api-collector.js

@ -0,0 +1,172 @@ @@ -0,0 +1,172 @@
const { readFileSync, existsSync, readdirSync } = require('fs')
const { resolve } = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
/**
* API收集模块
* 负责从service文件中收集API信息
*/
class ApiCollector {
constructor() {
this.apiMappings = []
}
/**
* 收集所有模块的API映射
* @param {Array} moduleDirs - 模块目录列表
* @returns {Array} API映射数组
*/
collectApiMappings(moduleDirs) {
this.apiMappings = []
moduleDirs.forEach(moduleName => {
const moduleApiMappings = this.collectModuleApiMappings(moduleName)
if (moduleApiMappings.length > 0) {
this.apiMappings.push({
module: moduleName,
serviceName: moduleApiMappings[0].serviceName,
servicePath: moduleApiMappings[0].servicePath,
apiMappings: moduleApiMappings
})
}
})
return this.apiMappings
}
/**
* 收集单个模块的API映射
* @param {string} moduleName - 模块名称
* @returns {Array} 模块的API映射数组
*/
collectModuleApiMappings(moduleName) {
const servicesPath = resolve(__dirname, '../../src/renderer/modules', moduleName, 'services')
if (!existsSync(servicesPath)) {
return []
}
const serviceFiles = readdirSync(servicesPath).filter(file => file.endsWith('.js'))
const moduleApiMappings = []
serviceFiles.forEach(serviceFile => {
const servicePath = resolve(servicesPath, serviceFile)
const serviceName = serviceFile.replace('.js', '')
try {
const serviceApiMappings = this.analyzeServiceFile(servicePath, serviceName, moduleName)
moduleApiMappings.push(...serviceApiMappings)
} catch (error) {
// 静默处理错误
}
})
return moduleApiMappings
}
/**
* 分析单个服务文件的API映射
* @param {string} servicePath - 服务文件路径
* @param {string} serviceName - 服务名称
* @param {string} moduleName - 模块名称
* @returns {Array} API映射数组
*/
analyzeServiceFile(servicePath, serviceName, moduleName) {
const content = readFileSync(servicePath, 'utf-8')
const apiMappings = []
try {
const ast = parser.parse(content, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
})
traverse(ast, {
ObjectMethod(path) {
if (path.node.key && path.node.key.type === 'Identifier') {
const methodName = path.node.key.name
const apiMapping = this.extractApiMapping(path, methodName, serviceName, servicePath)
if (apiMapping) {
apiMappings.push(apiMapping)
}
}
},
VariableDeclarator(path) {
if (path.node.id && path.node.id.type === 'Identifier' &&
path.node.init && path.node.init.type === 'ArrowFunctionExpression') {
const methodName = path.node.id.name
const apiMapping = this.extractApiMapping(path.get('init'), methodName, serviceName, servicePath)
if (apiMapping) {
apiMappings.push(apiMapping)
}
}
}
})
} catch (error) {
// 静默处理错误
}
return apiMappings
}
/**
* 从AST节点中提取API映射信息
* @param {Object} methodPath - 方法路径
* @param {string} methodName - 方法名称
* @param {string} serviceName - 服务名称
* @param {string} servicePath - 服务文件路径
* @returns {Object|null} API映射对象
*/
extractApiMapping(methodPath, methodName, serviceName, servicePath) {
let httpMethod = null
let apiPath = null
// 查找HTTP方法调用
traverse(methodPath.node, {
CallExpression(path) {
const node = path.node
if (node.callee && node.callee.type === 'MemberExpression') {
const object = node.callee.object
const property = node.callee.property
if (object && property &&
object.type === 'Identifier' &&
property.type === 'Identifier') {
const method = property.name.toLowerCase()
if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) {
httpMethod = method.toUpperCase()
// 提取API路径
if (node.arguments && node.arguments.length > 0) {
const firstArg = node.arguments[0]
if (firstArg.type === 'StringLiteral') {
apiPath = firstArg.value
} else if (firstArg.type === 'TemplateLiteral') {
apiPath = firstArg.quasis[0].value.raw
}
}
}
}
}
}
}, methodPath.scope, methodPath)
if (httpMethod && apiPath) {
return {
apiMethodName: methodName,
method: httpMethod,
path: apiPath,
line: methodPath.node.loc ? methodPath.node.loc.start.line : 0,
serviceName: serviceName,
servicePath: servicePath
}
}
return null
}
}
module.exports = ApiCollector

294
gofaster/app/plugins/modules/ast-analyzer.js

@ -0,0 +1,294 @@ @@ -0,0 +1,294 @@
const { readFileSync, existsSync } = require('fs')
const { resolve } = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { parse } = require('@vue/compiler-sfc')
/**
* AST分析模块
* 负责Babel和Vue模板的AST分析
*/
class AstAnalyzer {
constructor() {
this.processedButtons = new Set()
}
/**
* 使用Babel AST分析查找调用指定服务方法的方法
* @param {string} content - 组件内容
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @returns {Array} 调用该API的方法名数组
*/
findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName) {
const callingMethods = []
try {
// 解析script部分
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
if (!scriptMatch) {
return callingMethods
}
const scriptContent = scriptMatch[1]
// 使用Babel解析JavaScript
const ast = parser.parse(scriptContent, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
})
// 遍历AST查找方法定义
traverse(ast, {
// 查找const方法定义
VariableDeclarator(path) {
if (path.node.id && path.node.id.type === 'Identifier' &&
path.node.init && path.node.init.type === 'ArrowFunctionExpression') {
const componentMethodName = path.node.id.name
const methodPath = path.get('init')
// 检查该方法是否调用了指定的API
if (this.checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
}
}
},
// 查找ObjectMethod定义
ObjectMethod(path) {
if (path.node.key && path.node.key.type === 'Identifier') {
const componentMethodName = path.node.key.name
const methodPath = path
// 检查该方法是否调用了指定的API
if (this.checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
}
}
}
})
} catch (error) {
// 静默处理错误
}
return callingMethods
}
/**
* 使用Babel检查方法是否调用了指定的service方法
* @param {Object} methodPath - 方法路径
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @returns {boolean} 是否调用了指定的API
*/
checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName) {
let hasServiceCall = false
traverse(methodPath.node, {
CallExpression(path) {
const node = path.node
if (node.callee && node.callee.type === 'MemberExpression') {
const object = node.callee.object
const property = node.callee.property
if (object && property &&
object.type === 'Identifier' &&
property.type === 'Identifier') {
if (object.name === serviceName && property.name === apiMethodName) {
hasServiceCall = true
}
}
}
}
}, methodPath.scope, methodPath)
return hasServiceCall
}
/**
* 使用AST分析模板中的事件绑定
* @param {string} templateContent - 模板内容
* @param {string} componentName - 组件名称
* @param {Array} apiMethodNames - API方法名数组
* @param {Array} triggerSources - 触发器源数组
* @param {string} filePath - 文件路径
*/
findTriggersInTemplateWithAST(templateContent, componentName, apiMethodNames, triggerSources, filePath) {
try {
// 使用Vue模板编译器解析模板
const ast = parse(templateContent, {
sourceMap: false,
filename: 'template.vue'
})
// 遍历AST找到事件绑定
this.traverseTemplateAST(ast, (node) => {
if (node.type === 1 && node.tag === 'button') { // 元素节点且是button标签
this.processButtonNode(node, componentName, apiMethodNames, triggerSources, this.processedButtons, filePath)
}
})
} catch (error) {
// 静默处理错误
}
}
/**
* 遍历Vue模板AST
* @param {Object} ast - AST节点
* @param {Function} callback - 回调函数
*/
traverseTemplateAST(ast, callback) {
if (!ast || !ast.children) return
ast.children.forEach(child => {
if (child.type === 1) { // 元素节点
callback(child)
this.traverseTemplateAST(child, callback)
}
})
}
/**
* 处理按钮节点
* @param {Object} node - 按钮节点
* @param {string} componentName - 组件名称
* @param {Array} apiMethodNames - API方法名数组
* @param {Array} triggerSources - 触发器源数组
* @param {Set} processedButtons - 已处理的按钮集合
* @param {string} filePath - 文件路径
*/
processButtonNode(node, componentName, apiMethodNames, triggerSources, processedButtons, filePath) {
if (!node.props) return
let buttonName = null
let clickHandler = null
// 提取按钮属性
node.props.forEach(prop => {
if (prop.name === 'name' && prop.value && prop.value.content) {
buttonName = prop.value.content
} else if (prop.name === 'onClick' && prop.value && prop.value.type === 4) {
// 处理@click事件
if (prop.value.exp && prop.value.exp.children) {
const exp = prop.value.exp.children[0]
if (exp && exp.type === 4) { // 简单标识符
clickHandler = exp.content
}
}
}
})
// 检查是否匹配API方法
if (clickHandler && apiMethodNames.includes(clickHandler)) {
if (buttonName && !processedButtons.has(buttonName)) {
triggerSources.push({
component: componentName,
triggerName: buttonName,
triggerType: 'button'
})
processedButtons.add(buttonName)
} else if (!buttonName && !processedButtons.has(clickHandler)) {
const generatedName = this.generateUniqueButtonName(componentName, clickHandler)
triggerSources.push({
component: componentName,
triggerName: generatedName,
triggerType: 'button'
})
processedButtons.add(clickHandler)
// 为按钮添加name属性
if (filePath) {
this.addNameAttributeToButton(filePath, node, generatedName)
}
}
}
}
/**
* 生成唯一的按钮名称
* @param {string} componentName - 组件名称
* @param {string} clickHandler - 点击处理器
* @returns {string} 唯一的按钮名称
*/
generateUniqueButtonName(componentName, clickHandler) {
const baseName = componentName.toLowerCase()
const methodSuffix = clickHandler.toLowerCase()
return `${baseName}-${methodSuffix}`
}
/**
* 为按钮添加name属性
* @param {string} filePath - 文件路径
* @param {Object} buttonNode - 按钮节点
* @param {string} generatedName - 生成的名称
*/
addNameAttributeToButton(filePath, buttonNode, generatedName) {
try {
const content = readFileSync(filePath, 'utf-8')
// 检查按钮是否已经有name属性
const hasName = buttonNode.props && buttonNode.props.some(prop => prop.name === 'name')
if (hasName) {
return
}
// 在button标签中添加name属性
const buttonHtml = this.nodeToHtml(buttonNode)
const updatedButtonHtml = buttonHtml.replace(/<button([^>]*)>/, `<button$1 name="${generatedName}">`)
// 替换文件中的按钮HTML
const updatedContent = content.replace(buttonHtml, updatedButtonHtml)
// 写回文件
require('fs').writeFileSync(filePath, updatedContent, 'utf-8')
} catch (error) {
// 静默处理错误
}
}
/**
* 将AST节点转换为HTML字符串
* @param {Object} node - AST节点
* @returns {string} HTML字符串
*/
nodeToHtml(node) {
if (!node) return ''
let html = `<${node.tag}`
if (node.props) {
node.props.forEach(prop => {
if (prop.name === 'onClick' && prop.value && prop.value.type === 4) {
html += ` @click="${prop.value.exp.children[0].content}"`
} else if (prop.name !== 'onClick') {
html += ` ${prop.name}`
if (prop.value && prop.value.content) {
html += `="${prop.value.content}"`
}
}
})
}
html += '>'
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
if (child.type === 2) { // 文本节点
html += child.content
} else if (child.type === 1) { // 元素节点
html += this.nodeToHtml(child)
}
})
}
html += `</${node.tag}>`
return html
}
}
module.exports = AstAnalyzer

108
gofaster/app/plugins/modules/file-generator.js

@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
const { writeFileSync, existsSync } = require('fs')
const { resolve } = require('path')
/**
* 文件生成模块
* 负责生成最终的映射文件
*/
class FileGenerator {
constructor() {
this.outputPath = resolve(__dirname, '../../src/renderer/modules/route-sync/direct-route-mappings.js')
}
/**
* 生成映射文件
* @param {Array} routes - 路由数组
* @param {Array} apiMappings - API映射数组
*/
generateMappingFile(routes, apiMappings) {
const content = this.generateFileContent(routes, apiMappings)
// 如果文件已存在,先清空内容
if (existsSync(this.outputPath)) {
writeFileSync(this.outputPath, '', 'utf-8')
}
// 写入新内容
writeFileSync(this.outputPath, content, 'utf-8')
}
/**
* 生成文件内容
* @param {Array} routes - 路由数组
* @param {Array} apiMappings - API映射数组
* @returns {string} 文件内容
*/
generateFileContent(routes, apiMappings) {
const header = `// 此文件由 route-mapping-plugin 在构建时生成
// 包含路由配置分析结果和API收集结果
export default {
// 路由配置分析结果
routes: ${JSON.stringify(routes, null, 2)},
// API收集结果(包含页面关联和路径完善)
apiMappings: ${JSON.stringify(apiMappings, null, 2)}
}`
return header
}
/**
* 优化API映射
* @param {Array} apiMappings - API映射数组
* @returns {Array} 优化后的API映射数组
*/
optimizeApiMappings(apiMappings) {
const optimizedApiMappings = []
let totalRemoved = 0
apiMappings.forEach(moduleMapping => {
const deduplicatedApiMappings = []
moduleMapping.apiMappings.forEach(apiMapping => {
// 删除无触发器的API映射
if (!apiMapping.triggerSources || apiMapping.triggerSources.length === 0) {
totalRemoved++
return
}
// 去重触发器源
const uniqueTriggerSources = this.deduplicateTriggerSources(apiMapping.triggerSources)
deduplicatedApiMappings.push({
...apiMapping,
triggerSources: uniqueTriggerSources
})
})
if (deduplicatedApiMappings.length > 0) {
optimizedApiMappings.push({
...moduleMapping,
apiMappings: deduplicatedApiMappings
})
}
})
return optimizedApiMappings
}
/**
* 去重触发器源
* @param {Array} triggerSources - 触发器源数组
* @returns {Array} 去重后的触发器源数组
*/
deduplicateTriggerSources(triggerSources) {
const seen = new Set()
return triggerSources.filter(trigger => {
const key = `${trigger.component}-${trigger.triggerName}-${trigger.triggerType}`
if (seen.has(key)) {
return false
}
seen.add(key)
return true
})
}
}
module.exports = FileGenerator

18
gofaster/app/plugins/modules/index.js

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
/**
* 路由映射插件模块索引
* 导出所有模块类
*/
const ApiCollector = require('./api-collector')
const AstAnalyzer = require('./ast-analyzer')
const TriggerAnalyzer = require('./trigger-analyzer')
const FileGenerator = require('./file-generator')
const RouteAnalyzer = require('./route-analyzer')
module.exports = {
ApiCollector,
AstAnalyzer,
TriggerAnalyzer,
FileGenerator,
RouteAnalyzer
}

86
gofaster/app/plugins/modules/route-analyzer.js

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
const { readFileSync, existsSync } = require('fs')
const { resolve } = require('path')
/**
* 路由分析模块
* 负责分析路由配置
*/
class RouteAnalyzer {
constructor() {
this.routes = []
}
/**
* 分析路由配置
* @returns {Array} 路由数组
*/
analyzeRoutes() {
this.routes = []
try {
const routeConfigPath = resolve(__dirname, '../../src/renderer/router/index.js')
if (existsSync(routeConfigPath)) {
const routeContent = readFileSync(routeConfigPath, 'utf-8')
this.routes = this.parseRouteConfig(routeContent)
}
} catch (error) {
// 静默处理错误
}
return this.routes
}
/**
* 解析路由配置
* @param {string} routeContent - 路由配置内容
* @returns {Array} 路由数组
*/
parseRouteConfig(routeContent) {
const routes = []
// 简单的路由解析,查找路由定义
const routeMatches = routeContent.match(/path:\s*['"]([^'"]+)['"][\s\S]*?component:\s*['"]([^'"]+)['"]/g)
if (routeMatches) {
routeMatches.forEach(match => {
const pathMatch = match.match(/path:\s*['"]([^'"]+)['"]/)
const componentMatch = match.match(/component:\s*['"]([^'"]+)['"]/)
if (pathMatch && componentMatch) {
routes.push({
path: pathMatch[1],
component: componentMatch[1]
})
}
})
}
return routes
}
/**
* 获取模块列表
* @returns {Array} 模块目录列表
*/
getModuleDirs() {
const moduleDirs = []
try {
const modulesPath = resolve(__dirname, '../../src/renderer/modules')
if (existsSync(modulesPath)) {
const dirs = require('fs').readdirSync(modulesPath, { withFileTypes: true })
dirs.forEach(dirent => {
if (dirent.isDirectory()) {
moduleDirs.push(dirent.name)
}
})
}
} catch (error) {
// 静默处理错误
}
return moduleDirs
}
}
module.exports = RouteAnalyzer

407
gofaster/app/plugins/modules/trigger-analyzer.js

@ -0,0 +1,407 @@ @@ -0,0 +1,407 @@
const { readFileSync, existsSync, readdirSync } = require('fs')
const { resolve } = require('path')
const AstAnalyzer = require('./ast-analyzer')
/**
* 触发器分析模块
* 负责分析组件中的按钮和事件触发器
*/
class TriggerAnalyzer {
constructor() {
this.astAnalyzer = new AstAnalyzer()
}
/**
* 查找API方法的触发器源
* @param {Array} apiMappings - API映射数组
* @returns {Array} 增强的API映射数组
*/
findTriggerSourcesForApiMappings(apiMappings) {
const enhancedApiMappings = []
apiMappings.forEach(moduleMapping => {
const enhancedModuleMapping = {
...moduleMapping,
apiMappings: []
}
moduleMapping.apiMappings.forEach(apiMapping => {
const triggerSources = this.findTriggerSourcesForApiMethod(
apiMapping.serviceName,
apiMapping.apiMethodName,
moduleMapping.module
)
if (triggerSources.length > 0) {
enhancedModuleMapping.apiMappings.push({
...apiMapping,
triggerSources: triggerSources
})
}
})
if (enhancedModuleMapping.apiMappings.length > 0) {
enhancedApiMappings.push(enhancedModuleMapping)
}
})
return enhancedApiMappings
}
/**
* 查找API方法的触发器源
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} moduleName - 模块名称
* @returns {Array} 触发器源数组
*/
findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName) {
const triggerSources = []
// 1. 检查路由组件
const routeComponents = this.getRouteComponents(moduleName)
routeComponents.forEach(route => {
const triggerAnalysis = this.analyzeComponentWithAST(
route.component,
serviceName,
apiMethodName
)
if (triggerAnalysis.triggerSources.length > 0) {
triggerSources.push(...triggerAnalysis.triggerSources)
}
})
// 2. 检查模块中的所有组件
const componentsInModule = this.getModuleComponents(moduleName)
componentsInModule.forEach(componentName => {
const triggerAnalysis = this.analyzeComponentWithAST(
componentName,
serviceName,
apiMethodName
)
if (triggerAnalysis.triggerSources.length > 0) {
triggerSources.push(...triggerAnalysis.triggerSources)
}
})
// 去重
const uniqueTriggerSources = this.deduplicateTriggerSources(triggerSources)
return uniqueTriggerSources
}
/**
* 获取路由组件
* @param {string} moduleName - 模块名称
* @returns {Array} 路由组件数组
*/
getRouteComponents(moduleName) {
const routeComponents = []
try {
const routeConfigPath = resolve(__dirname, '../../src/renderer/router/index.js')
if (existsSync(routeConfigPath)) {
const routeContent = readFileSync(routeConfigPath, 'utf-8')
// 简单的路由解析,查找模块相关的路由
const routeMatches = routeContent.match(new RegExp(`component:\\s*['"]${moduleName}[^'"]*['"]`, 'g'))
if (routeMatches) {
routeMatches.forEach(match => {
const componentMatch = match.match(/component:\s*['"]([^'"]+)['"]/)
if (componentMatch) {
routeComponents.push({
component: componentMatch[1]
})
}
})
}
}
} catch (error) {
// 静默处理错误
}
return routeComponents
}
/**
* 获取模块中的所有组件
* @param {string} moduleName - 模块名称
* @returns {Array} 组件名称数组
*/
getModuleComponents(moduleName) {
const components = []
try {
const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName)
if (existsSync(modulePath)) {
// 查找views目录
const viewsPath = resolve(modulePath, 'views')
if (existsSync(viewsPath)) {
const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue'))
viewFiles.forEach(file => {
components.push(file.replace('.vue', ''))
})
}
// 查找components目录
const componentsPath = resolve(modulePath, 'components')
if (existsSync(componentsPath)) {
const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue'))
componentFiles.forEach(file => {
components.push(file.replace('.vue', ''))
})
}
}
} catch (error) {
// 静默处理错误
}
return components
}
/**
* 使用AST分析组件
* @param {string} componentName - 组件名称
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @returns {Object} 触发器分析结果
*/
analyzeComponentWithAST(componentName, serviceName, apiMethodName) {
const triggerSources = []
try {
const filePath = this.findComponentFile(componentName)
if (!filePath) {
return { triggerSources: [] }
}
const content = readFileSync(filePath, 'utf-8')
// 1. 检查组件的authType属性,如果是public则跳过
const authTypeMatch = content.match(/authType\s*[=:]\s*["']([^"']+)["']/)
if (authTypeMatch && authTypeMatch[1] === 'public') {
return { triggerSources: [] }
}
// 2. 检查组件是否包含目标API调用
if (!content.includes(`${serviceName}.${apiMethodName}`)) {
return { triggerSources: [] }
}
// 3. 使用Babel AST分析找到调用该API的方法
const callingMethods = this.astAnalyzer.findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName)
if (callingMethods.length > 0) {
// 4. 分析每个调用方法的触发源
const methodTriggers = this.analyzeMethodTriggerSources(content, componentName, callingMethods, filePath)
triggerSources.push(...methodTriggers)
} else {
// 如果没有找到按钮触发器,记录页面级触发器
triggerSources.push({
component: componentName,
triggerName: null,
triggerType: 'page'
})
}
} catch (error) {
// 静默处理错误
}
return { triggerSources }
}
/**
* 查找组件文件
* @param {string} componentName - 组件名称
* @returns {string|null} 组件文件路径
*/
findComponentFile(componentName) {
const possiblePaths = [
resolve(__dirname, '../../src/renderer/modules', componentName, 'views', `${componentName}.vue`),
resolve(__dirname, '../../src/renderer/modules', componentName, 'components', `${componentName}.vue`),
resolve(__dirname, '../../src/renderer/components', `${componentName}.vue`),
resolve(__dirname, '../../src/renderer/views', `${componentName}.vue`)
]
for (const path of possiblePaths) {
if (existsSync(path)) {
return path
}
}
return null
}
/**
* 分析方法的触发源
* @param {string} content - 组件内容
* @param {string} componentName - 组件名称
* @param {Array} methodNames - 方法名数组
* @param {string} filePath - 文件路径
* @returns {Array} 触发器源数组
*/
analyzeMethodTriggerSources(content, componentName, methodNames, filePath = null) {
const triggerSources = []
// 提取模板部分
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/)
if (!templateMatch) {
return triggerSources
}
const templateContent = templateMatch[1]
// 使用正则表达式查找调用这些方法的按钮
methodNames.forEach(methodName => {
// 1. 查找@click="methodName"或@click="methodName("
const clickPattern = new RegExp(`@click\\s*=\\s*["']${methodName}\\s*\\([^)]*\\)["']`, 'g')
const clickMatches = templateContent.match(clickPattern)
if (clickMatches) {
clickMatches.forEach((match, index) => {
// 查找包含这个@click的button标签
const buttonMatch = this.findButtonContainingClick(templateContent, match)
if (buttonMatch) {
const buttonName = this.extractButtonName(buttonMatch)
const triggerType = buttonName ? 'button' : 'button'
triggerSources.push({
component: componentName,
triggerName: buttonName || this.astAnalyzer.generateUniqueButtonName(componentName, methodName),
triggerType: triggerType
})
}
})
}
// 2. 查找@submit.prevent="methodName"的表单提交按钮
const submitPattern = new RegExp(`@submit\\.prevent\\s*=\\s*["']${methodName}["']`, 'g')
const submitMatches = templateContent.match(submitPattern)
if (submitMatches) {
submitMatches.forEach((match, index) => {
// 查找包含这个@submit.prevent的form标签
const formMatch = this.findFormContainingSubmit(templateContent, match)
if (formMatch) {
// 查找表单中的type="submit"按钮
const submitButtonMatch = this.findSubmitButtonInForm(formMatch)
if (submitButtonMatch) {
const buttonName = this.extractButtonName(submitButtonMatch)
const triggerType = buttonName ? 'button' : 'button'
triggerSources.push({
component: componentName,
triggerName: buttonName || this.astAnalyzer.generateUniqueButtonName(componentName, methodName),
triggerType: triggerType
})
} else {
// 如果没有找到具体的提交按钮,记录为form类型
triggerSources.push({
component: componentName,
triggerName: null,
triggerType: 'form'
})
}
}
})
}
})
return triggerSources
}
/**
* 查找包含指定@click的button标签
* @param {string} templateContent - 模板内容
* @param {string} clickMatch - @click匹配
* @returns {string|null} 按钮HTML
*/
findButtonContainingClick(templateContent, clickMatch) {
const clickIndex = templateContent.indexOf(clickMatch)
if (clickIndex === -1) return null
// 从@click位置往前查找最近的<button标签
const beforeClick = templateContent.substring(0, clickIndex)
const lastButtonIndex = beforeClick.lastIndexOf('<button')
if (lastButtonIndex === -1) return null
// 从<button开始往后查找对应的</button>
const afterButton = templateContent.substring(lastButtonIndex)
const buttonEndIndex = afterButton.indexOf('</button>')
if (buttonEndIndex === -1) return null
return afterButton.substring(0, buttonEndIndex + 9) // 包含</button>
}
/**
* 提取按钮名称
* @param {string} buttonHtml - 按钮HTML
* @returns {string|null} 按钮名称
*/
extractButtonName(buttonHtml) {
const nameMatch = buttonHtml.match(/name\s*=\s*["']([^"']+)["']/)
return nameMatch ? nameMatch[1] : null
}
/**
* 查找包含指定@submit.prevent的form标签
* @param {string} templateContent - 模板内容
* @param {string} submitMatch - @submit.prevent匹配
* @returns {string|null} 表单HTML
*/
findFormContainingSubmit(templateContent, submitMatch) {
const submitIndex = templateContent.indexOf(submitMatch)
if (submitIndex === -1) return null
// 从@submit.prevent位置往前查找最近的<form标签
const beforeSubmit = templateContent.substring(0, submitIndex)
const lastFormIndex = beforeSubmit.lastIndexOf('<form')
if (lastFormIndex === -1) return null
// 从<form开始往后查找对应的</form>
const afterForm = templateContent.substring(lastFormIndex)
const formEndIndex = afterForm.indexOf('</form>')
if (formEndIndex === -1) return null
return afterForm.substring(0, formEndIndex + 7) // 包含</form>
}
/**
* 查找表单中的type="submit"按钮
* @param {string} formHtml - 表单HTML
* @returns {string|null} 提交按钮HTML
*/
findSubmitButtonInForm(formHtml) {
const submitButtonPattern = /<button[^>]*type\s*=\s*["']submit["'][^>]*>[\s\S]*?<\/button>/g
const match = submitButtonPattern.exec(formHtml)
return match ? match[0] : null
}
/**
* 去重触发器源
* @param {Array} triggerSources - 触发器源数组
* @returns {Array} 去重后的触发器源数组
*/
deduplicateTriggerSources(triggerSources) {
const seen = new Set()
return triggerSources.filter(trigger => {
const key = `${trigger.component}-${trigger.triggerName}-${trigger.triggerType}`
if (seen.has(key)) {
return false
}
seen.add(key)
return true
})
}
}
module.exports = TriggerAnalyzer

2336
gofaster/app/plugins/route-mapping-plugin.js

File diff suppressed because it is too large Load Diff

2
gofaster/app/scripts/generate-route-mappings.js

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
const { routeMappingPlugin } = require('../plugins/route-mapping-plugin.js')
const routeMappingPlugin = require('../plugins/route-mapping-plugin.js')
const { existsSync } = require('fs')
const { resolve } = require('path')

4
gofaster/app/src/renderer/modules/role-management/components/RolePermissionAssignment.vue

@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
class="btn btn-sm btn-danger"
@click="removeSelectedPermissions"
:disabled="selectedAssignedPermissions.length === 0"
>
name="rolepermissionassignment-cwppvx">
<i class="fas fa-minus"></i>
移除选中 ({{ selectedAssignedPermissions.length }})
</button>
@ -129,7 +129,7 @@ @@ -129,7 +129,7 @@
class="btn btn-sm btn-primary"
@click="assignSelectedPermissions"
:disabled="selectedAvailablePermissions.length === 0"
>
name="rolepermissionassignment-2m2snp">
<i class="fas fa-plus"></i>
分配选中 ({{ selectedAvailablePermissions.length }})
</button>

2
gofaster/app/src/renderer/modules/role-management/components/UserRoleAssignment.vue

@ -82,7 +82,7 @@ @@ -82,7 +82,7 @@
<div class="modal-footer">
<button class="btn btn-secondary" @click="handleClose">取消</button>
<button class="btn btn-primary" @click="saveRoleAssignment" :disabled="saving">
<button class="btn btn-primary" @click="saveRoleAssignment" :disabled="saving" name="userroleassignment-xbmtac">
{{ saving ? '保存中...' : '保存分配' }}
</button>
</div>

290
gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js

@ -50,123 +50,160 @@ export const directRouteMappings = { @@ -50,123 +50,160 @@ export const directRouteMappings = {
"servicePath": "D:\\manta\\gofaster\\app\\src\\renderer\\modules\\user-management\\services\\userService.js",
"apiMappings": [
{
"methodName": "getUsers",
"apiMethodName": "getUsers",
"method": "GET",
"path": "/auth/admin/users",
"line": 50,
"callingComponents": []
"triggerSources": [
{
"component": "UserManagement",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "getUser",
"method": "GET",
"path": "/auth/admin/users/{id}",
"line": 63,
"callingComponents": []
},
{
"methodName": "createUser",
"apiMethodName": "createUser",
"method": "POST",
"path": "/auth/admin/users",
"line": 74,
"callingComponents": []
"triggerSources": [
{
"component": "UserManagement",
"triggerName": "usermanagement-tcfjru",
"triggerType": "button"
}
]
},
{
"methodName": "updateUser",
"apiMethodName": "updateUser",
"method": "PUT",
"path": "/auth/admin/users/{id}",
"line": 85,
"callingComponents": []
"triggerSources": [
{
"component": "UserManagement",
"triggerName": "usermanagement-tcfjru",
"triggerType": "button"
}
]
},
{
"methodName": "deleteUser",
"apiMethodName": "deleteUser",
"method": "DELETE",
"path": "/auth/admin/users/{id}",
"line": 96,
"callingComponents": []
"triggerSources": [
{
"component": "UserManagement",
"triggerName": "usermanagement-djl8u7",
"triggerType": "button"
}
]
},
{
"methodName": "getRoles",
"apiMethodName": "getRoles",
"method": "GET",
"path": "/auth/admin/roles",
"line": 107,
"callingComponents": []
"triggerSources": [
{
"component": "UserManagement",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "changePassword",
"apiMethodName": "changePassword",
"method": "POST",
"path": "/auth/change-password",
"line": 118,
"callingComponents": []
"triggerSources": [
{
"component": "PasswordChangeModal",
"triggerName": "passwordchangemodal-krxyp6",
"triggerType": "button"
}
]
},
{
"methodName": "getPasswordPolicy",
"apiMethodName": "getPasswordPolicy",
"method": "GET",
"path": "/auth/password-policy",
"line": 129,
"callingComponents": []
"triggerSources": [
{
"component": "PasswordChangeModal",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "validatePassword",
"apiMethodName": "validatePassword",
"method": "POST",
"path": "/auth/validate-password",
"line": 140,
"callingComponents": []
},
{
"methodName": "checkPasswordStatus",
"method": "GET",
"path": "/auth/password-status",
"line": 151,
"callingComponents": []
},
{
"methodName": "getPermissions",
"method": "GET",
"path": "/permissions",
"line": 162,
"callingComponents": []
"triggerSources": [
{
"component": "PasswordChangeModal",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "getCaptcha",
"apiMethodName": "getCaptcha",
"method": "GET",
"path": "/auth/captcha",
"line": 173,
"callingComponents": []
"triggerSources": [
{
"component": "LoginModal",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "login",
"apiMethodName": "login",
"method": "POST",
"path": "/auth/login",
"line": 184,
"callingComponents": []
},
{
"methodName": "logout",
"method": "POST",
"path": "/auth/logout",
"line": 215,
"callingComponents": []
"triggerSources": [
{
"component": "LoginModal",
"triggerName": "loginmodal-ys2551",
"triggerType": "button"
}
]
},
{
"methodName": "getCurrentUser",
"apiMethodName": "getCurrentUser",
"method": "GET",
"path": "/auth/me",
"line": 252,
"callingComponents": []
"triggerSources": [
{
"component": "UserProfile",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "changePassword",
"apiMethodName": "changePassword",
"method": "POST",
"path": "/auth/change-password",
"line": 273,
"callingComponents": []
},
{
"methodName": "resetPassword",
"method": "POST",
"path": "/auth/reset-password",
"line": 284,
"callingComponents": []
"triggerSources": [
{
"component": "PasswordChangeModal",
"triggerName": "passwordchangemodal-krxyp6",
"triggerType": "button"
}
]
}
]
},
@ -176,88 +213,175 @@ export const directRouteMappings = { @@ -176,88 +213,175 @@ export const directRouteMappings = {
"servicePath": "D:\\manta\\gofaster\\app\\src\\renderer\\modules\\role-management\\services\\roleService.js",
"apiMappings": [
{
"methodName": "getRoles",
"apiMethodName": "getRoles",
"method": "GET",
"path": "/auth/roles",
"line": 47,
"callingComponents": []
"triggerSources": [
{
"component": "RoleManagement",
"triggerName": null,
"triggerType": "page"
},
{
"component": "UserRoleAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "getRole",
"apiMethodName": "getRole",
"method": "GET",
"path": "/auth/roles/{id}",
"line": 60,
"callingComponents": []
"triggerSources": [
{
"component": "PermissionManager",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "createRole",
"apiMethodName": "createRole",
"method": "POST",
"path": "/auth/roles",
"line": 71,
"callingComponents": []
"triggerSources": [
{
"component": "RoleManagement",
"triggerName": "save-role",
"triggerType": "button"
}
]
},
{
"methodName": "updateRole",
"apiMethodName": "updateRole",
"method": "PUT",
"path": "/auth/roles/{id}",
"line": 82,
"callingComponents": []
"triggerSources": [
{
"component": "RoleManagement",
"triggerName": "save-role",
"triggerType": "button"
},
{
"component": "PermissionManager",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "deleteRole",
"apiMethodName": "deleteRole",
"method": "DELETE",
"path": "/auth/roles/{id}",
"line": 93,
"callingComponents": []
"triggerSources": [
{
"component": "RoleManagement",
"triggerName": "delete-role",
"triggerType": "button"
}
]
},
{
"methodName": "getPermissions",
"apiMethodName": "getPermissions",
"method": "GET",
"path": "/auth/permissions",
"line": 104,
"callingComponents": []
"triggerSources": [
{
"component": "PermissionManager",
"triggerName": null,
"triggerType": "page"
},
{
"component": "RolePermissionAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "assignRolesToUser",
"apiMethodName": "assignRolesToUser",
"method": "POST",
"path": "/auth/roles/users/{userId}/assign",
"line": 115,
"callingComponents": []
"triggerSources": [
{
"component": "UserRoleAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "getUserRoles",
"apiMethodName": "getUserRoles",
"method": "GET",
"path": "/auth/roles/users/{userId}",
"line": 128,
"callingComponents": []
"triggerSources": [
{
"component": "UserRoleAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "removeRolesFromUser",
"apiMethodName": "removeRolesFromUser",
"method": "DELETE",
"path": "/auth/roles/users/{userId}/remove",
"line": 139,
"callingComponents": []
"triggerSources": [
{
"component": "UserRoleAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "getRolePermissions",
"apiMethodName": "getRolePermissions",
"method": "GET",
"path": "/auth/permissions/roles/{roleId}",
"line": 152,
"callingComponents": []
"triggerSources": [
{
"component": "RolePermissionAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "assignPermissionsToRole",
"apiMethodName": "assignPermissionsToRole",
"method": "POST",
"path": "/auth/permissions/roles/{roleId}/assign",
"line": 163,
"callingComponents": []
"triggerSources": [
{
"component": "RolePermissionAssignment",
"triggerName": null,
"triggerType": "page"
}
]
},
{
"methodName": "removePermissionsFromRole",
"apiMethodName": "removePermissionsFromRole",
"method": "DELETE",
"path": "/auth/permissions/roles/{roleId}/remove",
"line": 176,
"callingComponents": []
"triggerSources": [
{
"component": "RolePermissionAssignment",
"triggerName": null,
"triggerType": "page"
}
]
}
]
}

2
gofaster/app/src/renderer/modules/user-management/components/LoginModal.vue

@ -83,7 +83,7 @@ @@ -83,7 +83,7 @@
type="submit"
class="login-btn"
:disabled="loading || !isFormValid"
>
name="loginmodal-ys2551">
<span v-if="loading" class="loading-spinner"></span>
{{ loading ? '登录中...' : '登录' }}
</button>

2
gofaster/app/src/renderer/modules/user-management/components/PasswordChangeModal.vue

@ -118,7 +118,7 @@ @@ -118,7 +118,7 @@
type="submit"
class="btn btn-primary"
:disabled="loading || !isFormValid"
>
name="passwordchangemodal-krxyp6">
<span v-if="loading" class="loading-spinner"></span>
{{ submitText }}
</button>

4
gofaster/app/src/renderer/modules/user-management/views/UserManagement.vue

@ -71,7 +71,7 @@ @@ -71,7 +71,7 @@
<button class="btn btn-sm btn-primary" @click="assignRoles(user)">
<i class="fas fa-user-shield"></i>
</button>
<button class="btn btn-sm btn-danger" @click="deleteUser(user.id)">
<button class="btn btn-sm btn-danger" @click="deleteUser(user.id)" name="usermanagement-djl8u7">
<i class="fas fa-trash"></i>
</button>
</div>
@ -200,7 +200,7 @@ @@ -200,7 +200,7 @@
<button type="button" class="btn btn-secondary" @click="closeModal">
取消
</button>
<button type="submit" class="btn btn-primary">
<button type="submit" class="btn btn-primary" name="usermanagement-tcfjru">
{{ showEditUserModal ? '更新' : '创建' }}
</button>
</div>

2
gofaster/app/src/renderer/modules/user-management/views/UserProfile.vue

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
<div class="error-icon"><i class="fas fa-exclamation-triangle"></i></div>
<h3>加载失败</h3>
<p>{{ error }}</p>
<button @click="loadUserProfile" class="retry-btn">
<button @click="loadUserProfile" class="retry-btn" name="userprofile-llgkyu">
<i class="fas fa-redo"></i> 重试
</button>
</div>

2
gofaster/app/vite.config.js

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { routeMappingPlugin } from './plugins/route-mapping-plugin.js'
import routeMappingPlugin from './plugins/route-mapping-plugin.js'
export default defineConfig({
plugins: [

2
gofaster/app/vue.config.js

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
const path = require('path')
const { defineConfig } = require('@vue/cli-service')
const { routeMappingPlugin } = require('./plugins/route-mapping-plugin.js')
const routeMappingPlugin = require('./plugins/route-mapping-plugin.js')
// 设置环境变量
process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = 'false'

Loading…
Cancel
Save