@@ -381,5 +381,5 @@ fmt.Println(decodeBytes, err)
## 10. 注意事项
-请严格遵守Apache 2.0协议并保留作品声明,去除版权信息请务必[获取授权](https://www.gin-vue-admin.com/empower/)
+请严格遵守Apache 2.0协议并保留作品声明,去除版权信息请务必[获取授权](https://plugin.gin-vue-admin.com/license)
未授权去除版权信息将依法追究法律责任
diff --git a/server/api/v1/system/auto_code_plugin.go b/server/api/v1/system/auto_code_plugin.go
index 8428607f..c751b8b0 100644
--- a/server/api/v1/system/auto_code_plugin.go
+++ b/server/api/v1/system/auto_code_plugin.go
@@ -2,9 +2,14 @@ package system
import (
"fmt"
+ "os"
+ "path/filepath"
+
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
+ "github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
@@ -141,3 +146,73 @@ func (a *AutoCodePluginApi) InitDictionary(c *gin.Context) {
}
response.OkWithMessage("文件变更成功", c)
}
+
+// GetPluginList
+// @Tags AutoCodePlugin
+// @Summary 获取插件列表
+// @Security ApiKeyAuth
+// @Produce application/json
+// @Success 200 {object} response.Response{data=[]systemRes.PluginInfo} "获取插件列表成功"
+// @Router /autoCode/getPluginList [get]
+func (a *AutoCodePluginApi) GetPluginList(c *gin.Context) {
+ serverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin")
+ webDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin")
+
+ serverEntries, _ := os.ReadDir(serverDir)
+ webEntries, _ := os.ReadDir(webDir)
+
+ configMap := make(map[string]string)
+
+ for _, entry := range serverEntries {
+ if entry.IsDir() {
+ configMap[entry.Name()] = "server"
+ }
+ }
+
+ for _, entry := range webEntries {
+ if entry.IsDir() {
+ if val, ok := configMap[entry.Name()]; ok {
+ if val == "server" {
+ configMap[entry.Name()] = "full"
+ }
+ } else {
+ configMap[entry.Name()] = "web"
+ }
+ }
+ }
+
+ var list []systemRes.PluginInfo
+ for k, v := range configMap {
+ apis, menus, dicts := utils.GetPluginData(k)
+ list = append(list, systemRes.PluginInfo{
+ PluginName: k,
+ PluginType: v,
+ Apis: apis,
+ Menus: menus,
+ Dictionaries: dicts,
+ })
+ }
+
+ response.OkWithDetailed(list, "获取成功", c)
+}
+
+// Remove
+// @Tags AutoCodePlugin
+// @Summary 删除插件
+// @Security ApiKeyAuth
+// @Produce application/json
+// @Param pluginName query string true "插件名称"
+// @Param pluginType query string true "插件类型"
+// @Success 200 {object} response.Response{msg=string} "删除插件成功"
+// @Router /autoCode/removePlugin [post]
+func (a *AutoCodePluginApi) Remove(c *gin.Context) {
+ pluginName := c.Query("pluginName")
+ pluginType := c.Query("pluginType")
+ err := autoCodePluginService.Remove(pluginName, pluginType)
+ if err != nil {
+ global.GVA_LOG.Error("删除失败!", zap.Error(err))
+ response.FailWithMessage("删除失败"+err.Error(), c)
+ return
+ }
+ response.OkWithMessage("删除成功", c)
+}
diff --git a/server/api/v1/system/enter.go b/server/api/v1/system/enter.go
index e6d37e2c..cde513b5 100644
--- a/server/api/v1/system/enter.go
+++ b/server/api/v1/system/enter.go
@@ -24,6 +24,9 @@ type ApiGroup struct {
SysParamsApi
SysVersionApi
SysErrorApi
+ LoginLogApi
+ ApiTokenApi
+ SkillsApi
}
var (
@@ -48,4 +51,7 @@ var (
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
sysErrorService = service.ServiceGroupApp.SystemServiceGroup.SysErrorService
+ loginLogService = service.ServiceGroupApp.SystemServiceGroup.LoginLogService
+ apiTokenService = service.ServiceGroupApp.SystemServiceGroup.ApiTokenService
+ skillsService = service.ServiceGroupApp.SystemServiceGroup.SkillsService
)
diff --git a/server/api/v1/system/sys_api_token.go b/server/api/v1/system/sys_api_token.go
new file mode 100644
index 00000000..14582d8e
--- /dev/null
+++ b/server/api/v1/system/sys_api_token.go
@@ -0,0 +1,81 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+ sysReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+type ApiTokenApi struct{}
+
+// CreateApiToken 签发Token
+func (s *ApiTokenApi) CreateApiToken(c *gin.Context) {
+ var req struct {
+ UserID uint `json:"userId"`
+ AuthorityID uint `json:"authorityId"`
+ Days int `json:"days"` // -1为永久, 其他为天数
+ Remark string `json:"remark"`
+ }
+ err := c.ShouldBindJSON(&req)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+
+ token := system.SysApiToken{
+ UserID: req.UserID,
+ AuthorityID: req.AuthorityID,
+ Remark: req.Remark,
+ }
+
+ jwtStr, err := apiTokenService.CreateApiToken(token, req.Days)
+ if err != nil {
+ global.GVA_LOG.Error("签发失败!", zap.Error(err))
+ response.FailWithMessage("签发失败: "+err.Error(), c)
+ return
+ }
+
+ response.OkWithDetailed(gin.H{"token": jwtStr}, "签发成功", c)
+}
+
+// GetApiTokenList 获取列表
+func (s *ApiTokenApi) GetApiTokenList(c *gin.Context) {
+ var pageInfo sysReq.SysApiTokenSearch
+ err := c.ShouldBindJSON(&pageInfo)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ list, total, err := apiTokenService.GetApiTokenList(pageInfo)
+ if err != nil {
+ global.GVA_LOG.Error("获取失败!", zap.Error(err))
+ response.FailWithMessage("获取失败", c)
+ return
+ }
+ response.OkWithDetailed(response.PageResult{
+ List: list,
+ Total: total,
+ Page: pageInfo.Page,
+ PageSize: pageInfo.PageSize,
+ }, "获取成功", c)
+}
+
+// DeleteApiToken 作废Token
+func (s *ApiTokenApi) DeleteApiToken(c *gin.Context) {
+ var req system.SysApiToken
+ err := c.ShouldBindJSON(&req)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ err = apiTokenService.DeleteApiToken(req.ID)
+ if err != nil {
+ global.GVA_LOG.Error("作废失败!", zap.Error(err))
+ response.FailWithMessage("作废失败", c)
+ return
+ }
+ response.OkWithMessage("作废成功", c)
+}
diff --git a/server/api/v1/system/sys_login_log.go b/server/api/v1/system/sys_login_log.go
new file mode 100644
index 00000000..6a6b391c
--- /dev/null
+++ b/server/api/v1/system/sys_login_log.go
@@ -0,0 +1,82 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+ systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+type LoginLogApi struct{}
+
+func (s *LoginLogApi) DeleteLoginLog(c *gin.Context) {
+ var loginLog system.SysLoginLog
+ err := c.ShouldBindJSON(&loginLog)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ err = loginLogService.DeleteLoginLog(loginLog)
+ if err != nil {
+ global.GVA_LOG.Error("删除失败!", zap.Error(err))
+ response.FailWithMessage("删除失败", c)
+ return
+ }
+ response.OkWithMessage("删除成功", c)
+}
+
+func (s *LoginLogApi) DeleteLoginLogByIds(c *gin.Context) {
+ var SDS request.IdsReq
+ err := c.ShouldBindJSON(&SDS)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ err = loginLogService.DeleteLoginLogByIds(SDS)
+ if err != nil {
+ global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
+ response.FailWithMessage("批量删除失败", c)
+ return
+ }
+ response.OkWithMessage("批量删除成功", c)
+}
+
+func (s *LoginLogApi) FindLoginLog(c *gin.Context) {
+ var loginLog system.SysLoginLog
+ err := c.ShouldBindQuery(&loginLog)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ reLoginLog, err := loginLogService.GetLoginLog(loginLog.ID)
+ if err != nil {
+ global.GVA_LOG.Error("查询失败!", zap.Error(err))
+ response.FailWithMessage("查询失败", c)
+ return
+ }
+ response.OkWithDetailed(reLoginLog, "查询成功", c)
+}
+
+func (s *LoginLogApi) GetLoginLogList(c *gin.Context) {
+ var pageInfo systemReq.SysLoginLogSearch
+ err := c.ShouldBindQuery(&pageInfo)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ list, total, err := loginLogService.GetLoginLogInfoList(pageInfo)
+ if err != nil {
+ global.GVA_LOG.Error("获取失败!", zap.Error(err))
+ response.FailWithMessage("获取失败", c)
+ return
+ }
+ response.OkWithDetailed(response.PageResult{
+ List: list,
+ Total: total,
+ Page: pageInfo.Page,
+ PageSize: pageInfo.PageSize,
+ }, "获取成功", c)
+}
diff --git a/server/api/v1/system/sys_skills.go b/server/api/v1/system/sys_skills.go
new file mode 100644
index 00000000..b443ff63
--- /dev/null
+++ b/server/api/v1/system/sys_skills.go
@@ -0,0 +1,149 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+type SkillsApi struct{}
+
+func (s *SkillsApi) GetTools(c *gin.Context) {
+ data, err := skillsService.Tools(c.Request.Context())
+ if err != nil {
+ global.GVA_LOG.Error("获取工具列表失败", zap.Error(err))
+ response.FailWithMessage("获取工具列表失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"tools": data}, "获取成功", c)
+}
+
+func (s *SkillsApi) GetSkillList(c *gin.Context) {
+ var req request.SkillToolRequest
+ _ = c.ShouldBindJSON(&req)
+ data, err := skillsService.List(c.Request.Context(), req.Tool)
+ if err != nil {
+ global.GVA_LOG.Error("获取技能列表失败", zap.Error(err))
+ response.FailWithMessage("获取技能列表失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"skills": data}, "获取成功", c)
+}
+
+func (s *SkillsApi) GetSkillDetail(c *gin.Context) {
+ var req request.SkillDetailRequest
+ _ = c.ShouldBindJSON(&req)
+ data, err := skillsService.Detail(c.Request.Context(), req.Tool, req.Skill)
+ if err != nil {
+ global.GVA_LOG.Error("获取技能详情失败", zap.Error(err))
+ response.FailWithMessage("获取技能详情失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"detail": data}, "获取成功", c)
+}
+
+func (s *SkillsApi) SaveSkill(c *gin.Context) {
+ var req request.SkillSaveRequest
+ _ = c.ShouldBindJSON(&req)
+ if err := skillsService.Save(c.Request.Context(), req); err != nil {
+ global.GVA_LOG.Error("保存技能失败", zap.Error(err))
+ response.FailWithMessage("保存技能失败", c)
+ return
+ }
+ response.OkWithMessage("保存成功", c)
+}
+
+func (s *SkillsApi) CreateScript(c *gin.Context) {
+ var req request.SkillScriptCreateRequest
+ _ = c.ShouldBindJSON(&req)
+ fileName, content, err := skillsService.CreateScript(c.Request.Context(), req)
+ if err != nil {
+ global.GVA_LOG.Error("创建脚本失败", zap.Error(err))
+ response.FailWithMessage("创建脚本失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
+}
+
+func (s *SkillsApi) GetScript(c *gin.Context) {
+ var req request.SkillFileRequest
+ _ = c.ShouldBindJSON(&req)
+ content, err := skillsService.GetScript(c.Request.Context(), req)
+ if err != nil {
+ global.GVA_LOG.Error("读取脚本失败", zap.Error(err))
+ response.FailWithMessage("读取脚本失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
+}
+
+func (s *SkillsApi) SaveScript(c *gin.Context) {
+ var req request.SkillFileSaveRequest
+ _ = c.ShouldBindJSON(&req)
+ if err := skillsService.SaveScript(c.Request.Context(), req); err != nil {
+ global.GVA_LOG.Error("保存脚本失败", zap.Error(err))
+ response.FailWithMessage("保存脚本失败", c)
+ return
+ }
+ response.OkWithMessage("保存成功", c)
+}
+
+func (s *SkillsApi) CreateResource(c *gin.Context) {
+ var req request.SkillResourceCreateRequest
+ _ = c.ShouldBindJSON(&req)
+ fileName, content, err := skillsService.CreateResource(c.Request.Context(), req)
+ if err != nil {
+ global.GVA_LOG.Error("创建资源失败", zap.Error(err))
+ response.FailWithMessage("创建资源失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
+}
+
+func (s *SkillsApi) GetResource(c *gin.Context) {
+ var req request.SkillFileRequest
+ _ = c.ShouldBindJSON(&req)
+ content, err := skillsService.GetResource(c.Request.Context(), req)
+ if err != nil {
+ global.GVA_LOG.Error("读取资源失败", zap.Error(err))
+ response.FailWithMessage("读取资源失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
+}
+
+func (s *SkillsApi) SaveResource(c *gin.Context) {
+ var req request.SkillFileSaveRequest
+ _ = c.ShouldBindJSON(&req)
+ if err := skillsService.SaveResource(c.Request.Context(), req); err != nil {
+ global.GVA_LOG.Error("保存资源失败", zap.Error(err))
+ response.FailWithMessage("保存资源失败", c)
+ return
+ }
+ response.OkWithMessage("保存成功", c)
+}
+
+func (s *SkillsApi) GetGlobalConstraint(c *gin.Context) {
+ var req request.SkillToolRequest
+ _ = c.ShouldBindJSON(&req)
+ content, exists, err := skillsService.GetGlobalConstraint(c.Request.Context(), req.Tool)
+ if err != nil {
+ global.GVA_LOG.Error("读取全局约束失败", zap.Error(err))
+ response.FailWithMessage("读取全局约束失败", c)
+ return
+ }
+ response.OkWithDetailed(gin.H{"content": content, "exists": exists}, "获取成功", c)
+}
+
+func (s *SkillsApi) SaveGlobalConstraint(c *gin.Context) {
+ var req request.SkillGlobalConstraintSaveRequest
+ _ = c.ShouldBindJSON(&req)
+ if err := skillsService.SaveGlobalConstraint(c.Request.Context(), req); err != nil {
+ global.GVA_LOG.Error("保存全局约束失败", zap.Error(err))
+ response.FailWithMessage("保存全局约束失败", c)
+ return
+ }
+ response.OkWithMessage("保存成功", c)
+}
diff --git a/server/api/v1/system/sys_user.go b/server/api/v1/system/sys_user.go
index ae6ab022..12b28493 100644
--- a/server/api/v1/system/sys_user.go
+++ b/server/api/v1/system/sys_user.go
@@ -51,6 +51,14 @@ func (b *BaseApi) Login(c *gin.Context) {
// 验证码次数+1
global.BlackCache.Increment(key, 1)
response.FailWithMessage("验证码错误", c)
+ // 记录登录失败日志
+ loginLogService.CreateLoginLog(system.SysLoginLog{
+ Username: l.Username,
+ Ip: c.ClientIP(),
+ Agent: c.Request.UserAgent(),
+ Status: false,
+ ErrorMessage: "验证码错误",
+ })
return
}
@@ -61,6 +69,14 @@ func (b *BaseApi) Login(c *gin.Context) {
// 验证码次数+1
global.BlackCache.Increment(key, 1)
response.FailWithMessage("用户名不存在或者密码错误", c)
+ // 记录登录失败日志
+ loginLogService.CreateLoginLog(system.SysLoginLog{
+ Username: l.Username,
+ Ip: c.ClientIP(),
+ Agent: c.Request.UserAgent(),
+ Status: false,
+ ErrorMessage: "用户名不存在或者密码错误",
+ })
return
}
if user.Enable != 1 {
@@ -68,6 +84,15 @@ func (b *BaseApi) Login(c *gin.Context) {
// 验证码次数+1
global.BlackCache.Increment(key, 1)
response.FailWithMessage("用户被禁止登录", c)
+ // 记录登录失败日志
+ loginLogService.CreateLoginLog(system.SysLoginLog{
+ Username: l.Username,
+ Ip: c.ClientIP(),
+ Agent: c.Request.UserAgent(),
+ Status: false,
+ ErrorMessage: "用户被禁止登录",
+ UserID: user.ID,
+ })
return
}
b.TokenNext(c, *user)
@@ -81,6 +106,15 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
response.FailWithMessage("获取token失败", c)
return
}
+ // 记录登录成功日志
+ loginLogService.CreateLoginLog(system.SysLoginLog{
+ Username: user.Username,
+ Ip: c.ClientIP(),
+ Agent: c.Request.UserAgent(),
+ Status: true,
+ UserID: user.ID,
+ ErrorMessage: "登录成功",
+ })
if !global.GVA_CONFIG.System.UseMultipoint {
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
response.OkWithDetailed(systemRes.LoginResponse{
diff --git a/server/initialize/ensure_tables.go b/server/initialize/ensure_tables.go
index 3a6c4540..d9e6cccb 100644
--- a/server/initialize/ensure_tables.go
+++ b/server/initialize/ensure_tables.go
@@ -55,6 +55,8 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
sysModel.SysParams{},
sysModel.SysVersion{},
sysModel.SysError{},
+ sysModel.SysLoginLog{},
+ sysModel.SysApiToken{},
adapter.CasbinRule{},
example.ExaFile{},
diff --git a/server/initialize/plugin_biz_v2.go b/server/initialize/plugin_biz_v2.go
index 9d13bbe0..bde58fa1 100644
--- a/server/initialize/plugin_biz_v2.go
+++ b/server/initialize/plugin_biz_v2.go
@@ -1,7 +1,7 @@
package initialize
import (
- "github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement"
+ _ "github.com/flipped-aurora/gin-vue-admin/server/plugin"
"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2"
"github.com/gin-gonic/gin"
)
@@ -12,5 +12,5 @@ func PluginInitV2(group *gin.Engine, plugins ...plugin.Plugin) {
}
}
func bizPluginV2(engine *gin.Engine) {
- PluginInitV2(engine, announcement.Plugin)
+ PluginInitV2(engine, plugin.Registered()...)
}
diff --git a/server/initialize/router.go b/server/initialize/router.go
index e5334d60..83438a9a 100644
--- a/server/initialize/router.go
+++ b/server/initialize/router.go
@@ -34,12 +34,12 @@ func (fs justFilesFilesystem) Open(name string) (http.File, error) {
// 初始化总路由
func Routers() *gin.Engine {
- Router := gin.New()
- // 使用自定义的 Recovery 中间件,记录 panic 并入库
- Router.Use(middleware.GinRecovery(true))
- if gin.Mode() == gin.DebugMode {
- Router.Use(gin.Logger())
- }
+ Router := gin.New()
+ // 使用自定义的 Recovery 中间件,记录 panic 并入库
+ Router.Use(middleware.GinRecovery(true))
+ if gin.Mode() == gin.DebugMode {
+ Router.Use(gin.Logger())
+ }
if !global.GVA_CONFIG.MCP.Separate {
@@ -109,6 +109,9 @@ func Routers() *gin.Engine {
systemRouter.InitSysExportTemplateRouter(PrivateGroup, PublicGroup) // 导出模板
systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理
systemRouter.InitSysErrorRouter(PrivateGroup, PublicGroup) // 错误日志
+ systemRouter.InitLoginLogRouter(PrivateGroup) // 登录日志
+ systemRouter.InitApiTokenRouter(PrivateGroup) // apiToken签发
+ systemRouter.InitSkillsRouter(PrivateGroup) // Skills 定义器
exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由
exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由
exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类
diff --git a/server/mcp/gva_analyze.go b/server/mcp/gva_analyze.go
index 3611b543..1e974714 100644
--- a/server/mcp/gva_analyze.go
+++ b/server/mcp/gva_analyze.go
@@ -349,7 +349,16 @@ func (g *GVAAnalyzer) removeDirectoryIfExists(dirPath string) error {
return err // 其他错误
}
- return os.RemoveAll(dirPath)
+ // 检查目录中是否包含go文件
+ noGoFiles, err := g.hasGoFilesRecursive(dirPath)
+ if err != nil {
+ return err
+ }
+ // hasGoFilesRecursive 返回 false 表示发现了 go 文件
+ if noGoFiles {
+ return os.RemoveAll(dirPath)
+ }
+ return nil
}
// cleanupRelatedApiAndMenus 清理相关的API和菜单记录
diff --git a/server/model/system/request/sys_api_token.go b/server/model/system/request/sys_api_token.go
new file mode 100644
index 00000000..e9e1c020
--- /dev/null
+++ b/server/model/system/request/sys_api_token.go
@@ -0,0 +1,12 @@
+package request
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+)
+
+type SysApiTokenSearch struct {
+ system.SysApiToken
+ request.PageInfo
+ Status *bool `json:"status" form:"status"`
+}
diff --git a/server/model/system/request/sys_login_log.go b/server/model/system/request/sys_login_log.go
new file mode 100644
index 00000000..48d7256f
--- /dev/null
+++ b/server/model/system/request/sys_login_log.go
@@ -0,0 +1,11 @@
+package request
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+)
+
+type SysLoginLogSearch struct {
+ system.SysLoginLog
+ request.PageInfo
+}
diff --git a/server/model/system/request/sys_skills.go b/server/model/system/request/sys_skills.go
new file mode 100644
index 00000000..b61d91e2
--- /dev/null
+++ b/server/model/system/request/sys_skills.go
@@ -0,0 +1,52 @@
+package request
+
+import "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+
+type SkillToolRequest struct {
+ Tool string `json:"tool"`
+}
+
+type SkillDetailRequest struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+}
+
+type SkillSaveRequest struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+ Meta system.SkillMeta `json:"meta"`
+ Markdown string `json:"markdown"`
+ SyncTools []string `json:"syncTools"`
+}
+
+type SkillScriptCreateRequest struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+ FileName string `json:"fileName"`
+ ScriptType string `json:"scriptType"`
+}
+
+type SkillResourceCreateRequest struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+ FileName string `json:"fileName"`
+}
+
+type SkillFileRequest struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+ FileName string `json:"fileName"`
+}
+
+type SkillFileSaveRequest struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+ FileName string `json:"fileName"`
+ Content string `json:"content"`
+}
+
+type SkillGlobalConstraintSaveRequest struct {
+ Tool string `json:"tool"`
+ Content string `json:"content"`
+ SyncTools []string `json:"syncTools"`
+}
diff --git a/server/model/system/response/sys_auto_code.go b/server/model/system/response/sys_auto_code.go
index 9e99bde3..e1f1d8b0 100644
--- a/server/model/system/response/sys_auto_code.go
+++ b/server/model/system/response/sys_auto_code.go
@@ -1,5 +1,7 @@
package response
+import "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+
type Db struct {
Database string `json:"database" gorm:"column:database"`
}
@@ -15,3 +17,11 @@ type Column struct {
ColumnComment string `json:"columnComment" gorm:"column:column_comment"`
PrimaryKey bool `json:"primaryKey" gorm:"column:primary_key"`
}
+
+type PluginInfo struct {
+ PluginName string `json:"pluginName"`
+ PluginType string `json:"pluginType"` // web, server, full
+ Apis []system.SysApi `json:"apis"`
+ Menus []system.SysBaseMenu `json:"menus"`
+ Dictionaries []system.SysDictionary `json:"dictionaries"`
+}
diff --git a/server/model/system/sys_api_token.go b/server/model/system/sys_api_token.go
new file mode 100644
index 00000000..2bfbde8a
--- /dev/null
+++ b/server/model/system/sys_api_token.go
@@ -0,0 +1,17 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "time"
+)
+
+type SysApiToken struct {
+ global.GVA_MODEL
+ UserID uint `json:"userId" gorm:"comment:用户ID"`
+ User SysUser `json:"user" gorm:"foreignKey:UserID;"`
+ AuthorityID uint `json:"authorityId" gorm:"comment:角色ID"`
+ Token string `json:"token" gorm:"type:text;comment:Token"`
+ Status bool `json:"status" gorm:"default:true;comment:状态"` // true有效 false无效
+ ExpiresAt time.Time `json:"expiresAt" gorm:"comment:过期时间"`
+ Remark string `json:"remark" gorm:"comment:备注"`
+}
diff --git a/server/model/system/sys_login_log.go b/server/model/system/sys_login_log.go
new file mode 100644
index 00000000..b1fd08be
--- /dev/null
+++ b/server/model/system/sys_login_log.go
@@ -0,0 +1,16 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+)
+
+type SysLoginLog struct {
+ global.GVA_MODEL
+ Username string `json:"username" gorm:"column:username;comment:用户名"`
+ Ip string `json:"ip" gorm:"column:ip;comment:请求ip"`
+ Status bool `json:"status" gorm:"column:status;comment:登录状态"`
+ ErrorMessage string `json:"errorMessage" gorm:"column:error_message;comment:错误信息"`
+ Agent string `json:"agent" gorm:"column:agent;comment:代理"`
+ UserID uint `json:"userId" gorm:"column:user_id;comment:用户id"`
+ User SysUser `json:"user" gorm:"foreignKey:UserID"`
+}
diff --git a/server/model/system/sys_skills.go b/server/model/system/sys_skills.go
new file mode 100644
index 00000000..76f6c341
--- /dev/null
+++ b/server/model/system/sys_skills.go
@@ -0,0 +1,23 @@
+package system
+
+type SkillMeta struct {
+ Name string `json:"name" yaml:"name"`
+ Description string `json:"description" yaml:"description"`
+ AllowedTools string `json:"allowedTools" yaml:"allowed-tools,omitempty"`
+ Context string `json:"context" yaml:"context,omitempty"`
+ Agent string `json:"agent" yaml:"agent,omitempty"`
+}
+
+type SkillDetail struct {
+ Tool string `json:"tool"`
+ Skill string `json:"skill"`
+ Meta SkillMeta `json:"meta"`
+ Markdown string `json:"markdown"`
+ Scripts []string `json:"scripts"`
+ Resources []string `json:"resources"`
+}
+
+type SkillTool struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+}
diff --git a/server/plugin/announcement/initialize/menu.go b/server/plugin/announcement/initialize/menu.go
index 40aff2b5..58144bc4 100644
--- a/server/plugin/announcement/initialize/menu.go
+++ b/server/plugin/announcement/initialize/menu.go
@@ -9,7 +9,7 @@ import (
func Menu(ctx context.Context) {
entities := []model.SysBaseMenu{
{
- ParentId: 24,
+ ParentId: 9,
Path: "anInfo",
Name: "anInfo",
Hidden: false,
diff --git a/server/plugin/announcement/plugin.go b/server/plugin/announcement/plugin.go
index 5c2532d9..68e2464b 100644
--- a/server/plugin/announcement/plugin.go
+++ b/server/plugin/announcement/plugin.go
@@ -13,6 +13,10 @@ var Plugin = new(plugin)
type plugin struct{}
+func init() {
+ interfaces.Register(Plugin)
+}
+
func (p *plugin) Register(group *gin.Engine) {
ctx := context.Background()
// 如果需要配置文件,请到config.Config中填充配置结构,且到下方发放中填入其在config.yaml中的key
diff --git a/server/plugin/plugin-tool/utils/check.go b/server/plugin/plugin-tool/utils/check.go
index c9aedeae..70631d77 100644
--- a/server/plugin/plugin-tool/utils/check.go
+++ b/server/plugin/plugin-tool/utils/check.go
@@ -4,12 +4,47 @@ import (
"github.com/pkg/errors"
"go.uber.org/zap"
"gorm.io/gorm"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
)
+var (
+ ApiMap = make(map[string][]system.SysApi)
+ MenuMap = make(map[string][]system.SysBaseMenu)
+ DictMap = make(map[string][]system.SysDictionary)
+ rw sync.Mutex
+)
+
+func getPluginName() string {
+ _, file, _, ok := runtime.Caller(2)
+ pluginName := ""
+ if ok {
+ file = filepath.ToSlash(file)
+ const key = "server/plugin/"
+ if idx := strings.Index(file, key); idx != -1 {
+ remain := file[idx+len(key):]
+ parts := strings.Split(remain, "/")
+ if len(parts) > 0 {
+ pluginName = parts[0]
+ }
+ }
+ }
+ return pluginName
+}
+
func RegisterApis(apis ...system.SysApi) {
+ name := getPluginName()
+ if name != "" {
+ rw.Lock()
+ ApiMap[name] = apis
+ rw.Unlock()
+ }
+
err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
for _, api := range apis {
err := tx.Model(system.SysApi{}).Where("path = ? AND method = ? AND api_group = ? ", api.Path, api.Method, api.ApiGroup).FirstOrCreate(&api).Error
@@ -26,6 +61,13 @@ func RegisterApis(apis ...system.SysApi) {
}
func RegisterMenus(menus ...system.SysBaseMenu) {
+ name := getPluginName()
+ if name != "" {
+ rw.Lock()
+ MenuMap[name] = menus
+ rw.Unlock()
+ }
+
parentMenu := menus[0]
otherMenus := menus[1:]
err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
@@ -43,7 +85,6 @@ func RegisterMenus(menus ...system.SysBaseMenu) {
return errors.Wrap(err, "注册菜单失败")
}
}
-
return nil
})
if err != nil {
@@ -53,6 +94,13 @@ func RegisterMenus(menus ...system.SysBaseMenu) {
}
func RegisterDictionaries(dictionaries ...system.SysDictionary) {
+ name := getPluginName()
+ if name != "" {
+ rw.Lock()
+ DictMap[name] = dictionaries
+ rw.Unlock()
+ }
+
err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
for _, dict := range dictionaries {
details := dict.SysDictionaryDetails
@@ -81,3 +129,10 @@ func RegisterDictionaries(dictionaries ...system.SysDictionary) {
func Pointer[T any](in T) *T {
return &in
}
+
+func GetPluginData(pluginName string) ([]system.SysApi, []system.SysBaseMenu, []system.SysDictionary) {
+ rw.Lock()
+ defer rw.Unlock()
+ return ApiMap[pluginName], MenuMap[pluginName], DictMap[pluginName]
+}
+
diff --git a/server/plugin/register.go b/server/plugin/register.go
new file mode 100644
index 00000000..11399d07
--- /dev/null
+++ b/server/plugin/register.go
@@ -0,0 +1,5 @@
+package plugin
+
+import (
+ _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement"
+)
diff --git a/server/resource/plugin/server/plugin.go.tpl b/server/resource/plugin/server/plugin.go.tpl
index e718e569..43ed0569 100644
--- a/server/resource/plugin/server/plugin.go.tpl
+++ b/server/resource/plugin/server/plugin.go.tpl
@@ -13,6 +13,11 @@ var Plugin = new(plugin)
type plugin struct{}
+func init() {
+ interfaces.Register(Plugin)
+}
+
+
// 如果需要配置文件,请到config.Config中填充配置结构,且到下方发放中填入其在config.yaml中的key并添加如下方法
// initialize.Viper()
// 安装插件时候自动注册的api数据请到下方法.Api方法中实现并添加如下方法
diff --git a/server/router/system/enter.go b/server/router/system/enter.go
index 970f6630..d379c2ab 100644
--- a/server/router/system/enter.go
+++ b/server/router/system/enter.go
@@ -21,6 +21,9 @@ type RouterGroup struct {
SysParamsRouter
SysVersionRouter
SysErrorRouter
+ LoginLogRouter
+ ApiTokenRouter
+ SkillsRouter
}
var (
@@ -45,4 +48,5 @@ var (
exportTemplateApi = api.ApiGroupApp.SystemApiGroup.SysExportTemplateApi
sysVersionApi = api.ApiGroupApp.SystemApiGroup.SysVersionApi
sysErrorApi = api.ApiGroupApp.SystemApiGroup.SysErrorApi
+ skillsApi = api.ApiGroupApp.SystemApiGroup.SkillsApi
)
diff --git a/server/router/system/sys_api_token.go b/server/router/system/sys_api_token.go
new file mode 100644
index 00000000..ebedeb06
--- /dev/null
+++ b/server/router/system/sys_api_token.go
@@ -0,0 +1,19 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/api/v1"
+ "github.com/flipped-aurora/gin-vue-admin/server/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+type ApiTokenRouter struct{}
+
+func (s *ApiTokenRouter) InitApiTokenRouter(Router *gin.RouterGroup) {
+ apiTokenRouter := Router.Group("sysApiToken").Use(middleware.OperationRecord())
+ apiTokenApi := v1.ApiGroupApp.SystemApiGroup.ApiTokenApi
+ {
+ apiTokenRouter.POST("createApiToken", apiTokenApi.CreateApiToken) // 签发Token
+ apiTokenRouter.POST("getApiTokenList", apiTokenApi.GetApiTokenList) // 获取列表
+ apiTokenRouter.POST("deleteApiToken", apiTokenApi.DeleteApiToken) // 作废Token
+ }
+}
diff --git a/server/router/system/sys_auto_code.go b/server/router/system/sys_auto_code.go
index acb49d26..261196d4 100644
--- a/server/router/system/sys_auto_code.go
+++ b/server/router/system/sys_auto_code.go
@@ -35,7 +35,8 @@ func (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPubli
{
autoCodeRouter.POST("pubPlug", autoCodePluginApi.Packaged) // 打包插件
autoCodeRouter.POST("installPlugin", autoCodePluginApi.Install) // 自动安装插件
-
+ autoCodeRouter.POST("removePlugin", autoCodePluginApi.Remove) // 自动删除插件
+ autoCodeRouter.GET("getPluginList", autoCodePluginApi.GetPluginList) // 获取插件列表
}
{
publicAutoCodeRouter.POST("llmAuto", autoCodeApi.LLMAuto)
diff --git a/server/router/system/sys_login_log.go b/server/router/system/sys_login_log.go
new file mode 100644
index 00000000..3ef672e0
--- /dev/null
+++ b/server/router/system/sys_login_log.go
@@ -0,0 +1,23 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/api/v1"
+ "github.com/flipped-aurora/gin-vue-admin/server/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+type LoginLogRouter struct{}
+
+func (s *LoginLogRouter) InitLoginLogRouter(Router *gin.RouterGroup) {
+ loginLogRouter := Router.Group("sysLoginLog").Use(middleware.OperationRecord())
+ loginLogRouterWithoutRecord := Router.Group("sysLoginLog")
+ sysLoginLogApi := v1.ApiGroupApp.SystemApiGroup.LoginLogApi
+ {
+ loginLogRouter.DELETE("deleteLoginLog", sysLoginLogApi.DeleteLoginLog) // 删除登录日志
+ loginLogRouter.DELETE("deleteLoginLogByIds", sysLoginLogApi.DeleteLoginLogByIds) // 批量删除登录日志
+ }
+ {
+ loginLogRouterWithoutRecord.GET("findLoginLog", sysLoginLogApi.FindLoginLog) // 根据ID获取登录日志(详情)
+ loginLogRouterWithoutRecord.GET("getLoginLogList", sysLoginLogApi.GetLoginLogList) // 获取登录日志列表
+ }
+}
diff --git a/server/router/system/sys_skills.go b/server/router/system/sys_skills.go
new file mode 100644
index 00000000..f631f62e
--- /dev/null
+++ b/server/router/system/sys_skills.go
@@ -0,0 +1,23 @@
+package system
+
+import "github.com/gin-gonic/gin"
+
+type SkillsRouter struct{}
+
+func (s *SkillsRouter) InitSkillsRouter(Router *gin.RouterGroup) {
+ skillsRouter := Router.Group("skills")
+ {
+ skillsRouter.GET("getTools", skillsApi.GetTools)
+ skillsRouter.POST("getSkillList", skillsApi.GetSkillList)
+ skillsRouter.POST("getSkillDetail", skillsApi.GetSkillDetail)
+ skillsRouter.POST("saveSkill", skillsApi.SaveSkill)
+ skillsRouter.POST("createScript", skillsApi.CreateScript)
+ skillsRouter.POST("getScript", skillsApi.GetScript)
+ skillsRouter.POST("saveScript", skillsApi.SaveScript)
+ skillsRouter.POST("createResource", skillsApi.CreateResource)
+ skillsRouter.POST("getResource", skillsApi.GetResource)
+ skillsRouter.POST("saveResource", skillsApi.SaveResource)
+ skillsRouter.POST("getGlobalConstraint", skillsApi.GetGlobalConstraint)
+ skillsRouter.POST("saveGlobalConstraint", skillsApi.SaveGlobalConstraint)
+ }
+}
diff --git a/server/service/system/auto_code_llm.go b/server/service/system/auto_code_llm.go
index 76b4fb4b..83c370f8 100644
--- a/server/service/system/auto_code_llm.go
+++ b/server/service/system/auto_code_llm.go
@@ -22,7 +22,7 @@ func (s *AutoCodeService) LLMAuto(ctx context.Context, llm common.JSONMap) (inte
// 构建调用路径:{AiPath} 中的 {FUNC} 由 mode 替换
mode := fmt.Sprintf("%v", llm["mode"]) // 统一转字符串,避免 nil 造成路径异常
- path := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", fmt.Sprintf("api/chat/%s", mode))
+ path := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", mode)
res, err := request.HttpRequest(
path,
diff --git a/server/service/system/auto_code_plugin.go b/server/service/system/auto_code_plugin.go
index efeef1c1..be3dff68 100644
--- a/server/service/system/auto_code_plugin.go
+++ b/server/service/system/auto_code_plugin.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
+ goast "go/ast"
"go/parser"
"go/printer"
"go/token"
@@ -16,8 +17,9 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ pluginUtils "github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
- "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
+ ast "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/mholt/archives"
cp "github.com/otiai10/copy"
"github.com/pkg/errors"
@@ -71,6 +73,8 @@ func (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, e
var serverIndex = -1
webPlugin := ""
serverPlugin := ""
+ serverPackage := ""
+ serverRootName := ""
for i := range paths {
paths[i] = filepath.ToSlash(paths[i])
@@ -80,8 +84,16 @@ func (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, e
if ln < 4 {
continue
}
- if pathArr[2]+"/"+pathArr[3] == `server/plugin` && len(serverPlugin) == 0 {
- serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
+ if pathArr[2]+"/"+pathArr[3] == `server/plugin` {
+ if len(serverPlugin) == 0 {
+ serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
+ }
+ if serverRootName == "" && ln > 1 && pathArr[1] != "" {
+ serverRootName = pathArr[1]
+ }
+ if ln > 4 && serverPackage == "" && pathArr[4] != "" {
+ serverPackage = pathArr[4]
+ }
}
if pathArr[2]+"/"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 {
webPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
@@ -93,10 +105,17 @@ func (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, e
}
if len(serverPlugin) != 0 {
+ if serverPackage == "" {
+ serverPackage = serverRootName
+ }
err = installation(serverPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Server)
if err != nil {
return webIndex, serverIndex, err
}
+ err = ensurePluginRegisterImport(serverPackage)
+ if err != nil {
+ return webIndex, serverIndex, err
+ }
}
if len(webPlugin) != 0 {
@@ -127,6 +146,64 @@ func installation(path string, formPath string, toPath string) error {
return cp.Copy(form, to, cp.Options{Skip: skipMacSpecialDocument})
}
+func ensurePluginRegisterImport(packageName string) error {
+ module := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Module)
+ if module == "" {
+ return errors.New("autocode module is empty")
+ }
+ if packageName == "" {
+ return errors.New("plugin package is empty")
+ }
+
+ registerPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "register.go")
+ src, err := os.ReadFile(registerPath)
+ if err != nil {
+ return err
+ }
+ fileSet := token.NewFileSet()
+ astFile, err := parser.ParseFile(fileSet, registerPath, src, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+
+ importPath := fmt.Sprintf("%s/plugin/%s", module, packageName)
+ if ast.CheckImport(astFile, importPath) {
+ return nil
+ }
+
+ importSpec := &goast.ImportSpec{
+ Name: goast.NewIdent("_"),
+ Path: &goast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", importPath)},
+ }
+ var importDecl *goast.GenDecl
+ for _, decl := range astFile.Decls {
+ genDecl, ok := decl.(*goast.GenDecl)
+ if !ok {
+ continue
+ }
+ if genDecl.Tok == token.IMPORT {
+ importDecl = genDecl
+ break
+ }
+ }
+ if importDecl == nil {
+ astFile.Decls = append([]goast.Decl{
+ &goast.GenDecl{
+ Tok: token.IMPORT,
+ Specs: []goast.Spec{importSpec},
+ },
+ }, astFile.Decls...)
+ } else {
+ importDecl.Specs = append(importDecl.Specs, importSpec)
+ }
+
+ var out []byte
+ bf := bytes.NewBuffer(out)
+ printer.Fprint(bf, fileSet, astFile)
+
+ return os.WriteFile(registerPath, bf.Bytes(), 0666)
+}
+
func filterFile(paths []string) []string {
np := make([]string, 0, len(paths))
for _, path := range paths {
@@ -289,3 +366,147 @@ func (s *autoCodePlugin) InitDictionary(dictInfo request.InitDictionary) (err er
os.WriteFile(dictPath, bf.Bytes(), 0666)
return nil
}
+
+func (s *autoCodePlugin) Remove(pluginName string, pluginType string) (err error) {
+ // 1. 删除前端代码
+ if pluginType == "web" || pluginType == "full" {
+ webDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", pluginName)
+ err = os.RemoveAll(webDir)
+ if err != nil {
+ return errors.Wrap(err, "删除前端插件目录失败")
+ }
+ }
+
+ // 2. 删除后端代码
+ if pluginType == "server" || pluginType == "full" {
+ serverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", pluginName)
+ err = os.RemoveAll(serverDir)
+ if err != nil {
+ return errors.Wrap(err, "删除后端插件目录失败")
+ }
+
+ // 移除注册
+ removePluginRegisterImport(pluginName)
+ }
+
+ // 通过utils 获取 api 菜单 字典
+ apis, menus, dicts := pluginUtils.GetPluginData(pluginName)
+
+ // 3. 删除菜单 (递归删除)
+ if len(menus) > 0 {
+ for _, menu := range menus {
+ var dbMenu system.SysBaseMenu
+ if err := global.GVA_DB.Where("name = ?", menu.Name).First(&dbMenu).Error; err == nil {
+ // 获取该菜单及其所有子菜单的ID
+ var menuIds []int
+ GetMenuIds(dbMenu, &menuIds)
+ // 逆序删除,先删除子菜单
+ for i := len(menuIds) - 1; i >= 0; i-- {
+ err := BaseMenuServiceApp.DeleteBaseMenu(menuIds[i])
+ if err != nil {
+ zap.L().Error("删除菜单失败", zap.Int("id", menuIds[i]), zap.Error(err))
+ }
+ }
+ }
+ }
+ }
+
+ // 4. 删除API
+ if len(apis) > 0 {
+ for _, api := range apis {
+ var dbApi system.SysApi
+ if err := global.GVA_DB.Where("path = ? AND method = ?", api.Path, api.Method).First(&dbApi).Error; err == nil {
+ err := ApiServiceApp.DeleteApi(dbApi)
+ if err != nil {
+ zap.L().Error("删除API失败", zap.String("path", api.Path), zap.Error(err))
+ }
+ }
+ }
+ }
+
+ // 5. 删除字典
+ if len(dicts) > 0 {
+ for _, dict := range dicts {
+ var dbDict system.SysDictionary
+ if err := global.GVA_DB.Where("type = ?", dict.Type).First(&dbDict).Error; err == nil {
+ err := DictionaryServiceApp.DeleteSysDictionary(dbDict)
+ if err != nil {
+ zap.L().Error("删除字典失败", zap.String("type", dict.Type), zap.Error(err))
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func GetMenuIds(menu system.SysBaseMenu, ids *[]int) {
+ *ids = append(*ids, int(menu.ID))
+ var children []system.SysBaseMenu
+ global.GVA_DB.Where("parent_id = ?", menu.ID).Find(&children)
+ for _, child := range children {
+ // 先递归收集子菜单
+ GetMenuIds(child, ids)
+ }
+}
+
+func removePluginRegisterImport(packageName string) error {
+ module := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Module)
+ if module == "" {
+ return errors.New("autocode module is empty")
+ }
+ if packageName == "" {
+ return errors.New("plugin package is empty")
+ }
+
+ registerPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "register.go")
+ src, err := os.ReadFile(registerPath)
+ if err != nil {
+ return err
+ }
+ fileSet := token.NewFileSet()
+ astFile, err := parser.ParseFile(fileSet, registerPath, src, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+
+ importPath := fmt.Sprintf("%s/plugin/%s", module, packageName)
+ importLit := fmt.Sprintf("%q", importPath)
+
+ // 移除 import
+ var newDecls []goast.Decl
+ for _, decl := range astFile.Decls {
+ genDecl, ok := decl.(*goast.GenDecl)
+ if !ok {
+ newDecls = append(newDecls, decl)
+ continue
+ }
+ if genDecl.Tok == token.IMPORT {
+ var newSpecs []goast.Spec
+ for _, spec := range genDecl.Specs {
+ importSpec, ok := spec.(*goast.ImportSpec)
+ if !ok {
+ newSpecs = append(newSpecs, spec)
+ continue
+ }
+ if importSpec.Path.Value != importLit {
+ newSpecs = append(newSpecs, spec)
+ }
+ }
+ // 如果还有其他import,保留该 decl
+ if len(newSpecs) > 0 {
+ genDecl.Specs = newSpecs
+ newDecls = append(newDecls, genDecl)
+ }
+ } else {
+ newDecls = append(newDecls, decl)
+ }
+ }
+ astFile.Decls = newDecls
+
+ var out []byte
+ bf := bytes.NewBuffer(out)
+ printer.Fprint(bf, fileSet, astFile)
+
+ return os.WriteFile(registerPath, bf.Bytes(), 0666)
+}
diff --git a/server/service/system/enter.go b/server/service/system/enter.go
index f09123cf..6d68bb7b 100644
--- a/server/service/system/enter.go
+++ b/server/service/system/enter.go
@@ -18,9 +18,12 @@ type ServiceGroup struct {
SysExportTemplateService
SysParamsService
SysVersionService
+ SkillsService
AutoCodePlugin autoCodePlugin
AutoCodePackage autoCodePackage
AutoCodeHistory autoCodeHistory
AutoCodeTemplate autoCodeTemplate
SysErrorService
+ LoginLogService
+ ApiTokenService
}
diff --git a/server/service/system/sys_api_token.go b/server/service/system/sys_api_token.go
new file mode 100644
index 00000000..b2ebdde9
--- /dev/null
+++ b/server/service/system/sys_api_token.go
@@ -0,0 +1,106 @@
+package system
+
+import (
+ "errors"
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+ sysReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ "github.com/flipped-aurora/gin-vue-admin/server/utils"
+ "github.com/golang-jwt/jwt/v5"
+ "time"
+)
+
+type ApiTokenService struct{}
+
+func (apiVersion *ApiTokenService) CreateApiToken(apiToken system.SysApiToken, days int) (string, error) {
+ var user system.SysUser
+ if err := global.GVA_DB.Where("id = ?", apiToken.UserID).First(&user).Error; err != nil {
+ return "", errors.New("用户不存在")
+ }
+
+ hasAuth := false
+ for _, auth := range user.Authorities {
+ if auth.AuthorityId == apiToken.AuthorityID {
+ hasAuth = true
+ break
+ }
+ }
+ if !hasAuth && user.AuthorityId != apiToken.AuthorityID {
+ return "", errors.New("用户不具备该角色权限")
+ }
+
+ j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一不同的部分是过期时间
+
+ expireTime := time.Duration(days) * 24 * time.Hour
+ if days == -1 {
+ expireTime = 100 * 365 * 24 * time.Hour
+ }
+
+ bf, _ := utils.ParseDuration(global.GVA_CONFIG.JWT.BufferTime)
+
+ claims := sysReq.CustomClaims{
+ BaseClaims: sysReq.BaseClaims{
+ UUID: user.UUID,
+ ID: user.ID,
+ Username: user.Username,
+ NickName: user.NickName,
+ AuthorityId: apiToken.AuthorityID,
+ },
+ BufferTime: int64(bf / time.Second), // 缓冲时间
+ RegisteredClaims: jwt.RegisteredClaims{
+ Audience: jwt.ClaimStrings{"GVA"},
+ NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)),
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(expireTime)),
+ Issuer: global.GVA_CONFIG.JWT.Issuer,
+ },
+ }
+
+ token, err := j.CreateToken(claims)
+ if err != nil {
+ return "", err
+ }
+
+ apiToken.Token = token
+ apiToken.Status = true
+ apiToken.ExpiresAt = time.Now().Add(expireTime)
+ err = global.GVA_DB.Create(&apiToken).Error
+ return token, err
+}
+
+func (apiVersion *ApiTokenService) GetApiTokenList(info sysReq.SysApiTokenSearch) (list []system.SysApiToken, total int64, err error) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.GVA_DB.Model(&system.SysApiToken{})
+
+ db = db.Preload("User")
+
+ if info.UserID != 0 {
+ db = db.Where("user_id = ?", info.UserID)
+ }
+ if info.Status != nil {
+ db = db.Where("status = ?", *info.Status)
+ }
+
+ err = db.Count(&total).Error
+ if err != nil {
+ return
+ }
+ err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&list).Error
+ return list, total, err
+}
+
+func (apiVersion *ApiTokenService) DeleteApiToken(id uint) error {
+ var apiToken system.SysApiToken
+ err := global.GVA_DB.First(&apiToken, id).Error
+ if err != nil {
+ return err
+ }
+
+ jwtService := JwtService{}
+ err = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: apiToken.Token})
+ if err != nil {
+ return err
+ }
+
+ return global.GVA_DB.Model(&apiToken).Update("status", false).Error
+}
diff --git a/server/service/system/sys_error.go b/server/service/system/sys_error.go
index e8a1a355..065b6241 100644
--- a/server/service/system/sys_error.go
+++ b/server/service/system/sys_error.go
@@ -106,16 +106,15 @@ func (sysErrorService *SysErrorService) GetSysErrorSolution(ctx context.Context,
}
llmReq := common.JSONMap{
- "mode": "solution",
- "command": "solution",
- "info": info,
- "form": form,
+ "mode": "solution",
+ "info": info,
+ "form": form,
}
// 调用服务层 LLMAuto,忽略错误但尽量写入方案
var solution string
if data, err := (&AutoCodeService{}).LLMAuto(context.Background(), llmReq); err == nil {
- solution = fmt.Sprintf("%v", data)
+ solution = fmt.Sprintf("%v", data.(map[string]interface{})["text"])
_ = global.GVA_DB.Model(&system.SysError{}).Where("id = ?", id).Updates(map[string]interface{}{"status": "处理完成", "solution": solution}).Error
} else {
// 即使生成失败也标记为完成,避免任务卡住
diff --git a/server/service/system/sys_login_log.go b/server/service/system/sys_login_log.go
new file mode 100644
index 00000000..79fee93c
--- /dev/null
+++ b/server/service/system/sys_login_log.go
@@ -0,0 +1,53 @@
+package system
+
+import (
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+ systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+)
+
+type LoginLogService struct{}
+
+var LoginLogServiceApp = new(LoginLogService)
+
+func (loginLogService *LoginLogService) CreateLoginLog(loginLog system.SysLoginLog) (err error) {
+ err = global.GVA_DB.Create(&loginLog).Error
+ return err
+}
+
+func (loginLogService *LoginLogService) DeleteLoginLogByIds(ids request.IdsReq) (err error) {
+ err = global.GVA_DB.Delete(&[]system.SysLoginLog{}, "id in (?)", ids.Ids).Error
+ return err
+}
+
+func (loginLogService *LoginLogService) DeleteLoginLog(loginLog system.SysLoginLog) (err error) {
+ err = global.GVA_DB.Delete(&loginLog).Error
+ return err
+}
+
+func (loginLogService *LoginLogService) GetLoginLog(id uint) (loginLog system.SysLoginLog, err error) {
+ err = global.GVA_DB.Where("id = ?", id).First(&loginLog).Error
+ return
+}
+
+func (loginLogService *LoginLogService) GetLoginLogInfoList(info systemReq.SysLoginLogSearch) (list interface{}, total int64, err error) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.GVA_DB.Model(&system.SysLoginLog{})
+ var loginLogs []system.SysLoginLog
+ // 如果有条件搜索 下方会自动创建搜索语句
+ if info.Username != "" {
+ db = db.Where("username LIKE ?", "%"+info.Username+"%")
+ }
+ if info.Status != false {
+ db = db.Where("status = ?", info.Status)
+ }
+ err = db.Count(&total).Error
+ if err != nil {
+ return
+ }
+ err = db.Limit(limit).Offset(offset).Order("id desc").Preload("User").Find(&loginLogs).Error
+ return loginLogs, total, err
+}
diff --git a/server/service/system/sys_skills.go b/server/service/system/sys_skills.go
new file mode 100644
index 00000000..51b79c65
--- /dev/null
+++ b/server/service/system/sys_skills.go
@@ -0,0 +1,512 @@
+package system
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/flipped-aurora/gin-vue-admin/server/global"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system"
+ "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+ "gopkg.in/yaml.v3"
+)
+
+const (
+ skillFileName = "SKILL.md"
+ globalConstraintFileName = "README.md"
+)
+
+var skillToolOrder = []string{"copilot", "claude", "cursor", "trae", "codex"}
+
+var skillToolDirs = map[string]string{
+ "copilot": ".aone_copilot",
+ "claude": ".claude",
+ "trae": ".trae",
+ "codex": ".codex",
+ "cursor": ".cursor",
+}
+
+var skillToolLabels = map[string]string{
+ "copilot": "Copilot",
+ "claude": "Claude",
+ "trae": "Trae",
+ "codex": "Codex",
+ "cursor": "Cursor",
+}
+
+const defaultSkillMarkdown = "## 技能用途\n请在这里描述技能的目标、适用场景与限制条件。\n\n## 输入\n- 请补充输入格式与示例。\n\n## 输出\n- 请补充输出格式与示例。\n\n## 关键步骤\n1. 第一步\n2. 第二步\n\n## 示例\n在此补充一到两个典型示例。\n"
+
+const defaultResourceMarkdown = "# 资源说明\n请在这里补充资源内容。\n"
+
+const defaultGlobalConstraintMarkdown = "# 全局约束\n请在这里补充该工具的统一约束与使用规范。\n"
+
+type SkillsService struct{}
+
+func (s *SkillsService) Tools(_ context.Context) ([]system.SkillTool, error) {
+ tools := make([]system.SkillTool, 0, len(skillToolOrder))
+ for _, key := range skillToolOrder {
+ if _, err := s.toolSkillsDir(key); err != nil {
+ return nil, err
+ }
+ tools = append(tools, system.SkillTool{Key: key, Label: skillToolLabels[key]})
+ }
+ return tools, nil
+}
+
+func (s *SkillsService) List(_ context.Context, tool string) ([]string, error) {
+ skillsDir, err := s.toolSkillsDir(tool)
+ if err != nil {
+ return nil, err
+ }
+ entries, err := os.ReadDir(skillsDir)
+ if err != nil {
+ return nil, err
+ }
+ var skills []string
+ for _, entry := range entries {
+ if entry.IsDir() {
+ skills = append(skills, entry.Name())
+ }
+ }
+ sort.Strings(skills)
+ return skills, nil
+}
+
+func (s *SkillsService) Detail(_ context.Context, tool, skill string) (system.SkillDetail, error) {
+ var detail system.SkillDetail
+ if !isSafeName(skill) {
+ return detail, errors.New("技能名称不合法")
+ }
+ detail.Tool = tool
+ detail.Skill = skill
+
+ skillDir, err := s.skillDir(tool, skill)
+ if err != nil {
+ return detail, err
+ }
+
+ skillFilePath := filepath.Join(skillDir, skillFileName)
+ content, err := os.ReadFile(skillFilePath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return detail, err
+ }
+ detail.Meta = system.SkillMeta{Name: skill}
+ detail.Markdown = defaultSkillMarkdown
+ } else {
+ meta, body, parseErr := parseSkillContent(string(content))
+ if parseErr != nil {
+ meta = system.SkillMeta{Name: skill}
+ body = string(content)
+ }
+ if meta.Name == "" {
+ meta.Name = skill
+ }
+ detail.Meta = meta
+ detail.Markdown = body
+ }
+
+ detail.Scripts = listFiles(filepath.Join(skillDir, "scripts"))
+ detail.Resources = listFiles(filepath.Join(skillDir, "resources"))
+ return detail, nil
+}
+
+func (s *SkillsService) Save(_ context.Context, req request.SkillSaveRequest) error {
+ if !isSafeName(req.Skill) {
+ return errors.New("技能名称不合法")
+ }
+ skillDir, err := s.ensureSkillDir(req.Tool, req.Skill)
+ if err != nil {
+ return err
+ }
+ if req.Meta.Name == "" {
+ req.Meta.Name = req.Skill
+ }
+ content, err := buildSkillContent(req.Meta, req.Markdown)
+ if err != nil {
+ return err
+ }
+ if err := os.WriteFile(filepath.Join(skillDir, skillFileName), []byte(content), 0644); err != nil {
+ return err
+ }
+
+ if len(req.SyncTools) > 0 {
+ for _, tool := range req.SyncTools {
+ if tool == req.Tool {
+ continue
+ }
+ targetDir, err := s.ensureSkillDir(tool, req.Skill)
+ if err != nil {
+ return err
+ }
+ if err := copySkillDir(skillDir, targetDir); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (s *SkillsService) CreateScript(_ context.Context, req request.SkillScriptCreateRequest) (string, string, error) {
+ if !isSafeName(req.Skill) {
+ return "", "", errors.New("技能名称不合法")
+ }
+ fileName, lang, err := buildScriptFileName(req.FileName, req.ScriptType)
+ if err != nil {
+ return "", "", err
+ }
+ if lang == "" {
+ return "", "", errors.New("脚本类型不支持")
+ }
+ skillDir, err := s.ensureSkillDir(req.Tool, req.Skill)
+ if err != nil {
+ return "", "", err
+ }
+ filePath := filepath.Join(skillDir, "scripts", fileName)
+ if _, err := os.Stat(filePath); err == nil {
+ return "", "", errors.New("脚本已存在")
+ }
+ if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
+ return "", "", err
+ }
+ content := scriptTemplate(lang)
+ if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
+ return "", "", err
+ }
+ return fileName, content, nil
+}
+
+func (s *SkillsService) GetScript(_ context.Context, req request.SkillFileRequest) (string, error) {
+ return s.readSkillFile(req.Tool, req.Skill, "scripts", req.FileName)
+}
+
+func (s *SkillsService) SaveScript(_ context.Context, req request.SkillFileSaveRequest) error {
+ return s.writeSkillFile(req.Tool, req.Skill, "scripts", req.FileName, req.Content)
+}
+
+func (s *SkillsService) CreateResource(_ context.Context, req request.SkillResourceCreateRequest) (string, string, error) {
+ if !isSafeName(req.Skill) {
+ return "", "", errors.New("技能名称不合法")
+ }
+ fileName, err := buildResourceFileName(req.FileName)
+ if err != nil {
+ return "", "", err
+ }
+ skillDir, err := s.ensureSkillDir(req.Tool, req.Skill)
+ if err != nil {
+ return "", "", err
+ }
+ filePath := filepath.Join(skillDir, "resources", fileName)
+ if _, err := os.Stat(filePath); err == nil {
+ return "", "", errors.New("资源已存在")
+ }
+ if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
+ return "", "", err
+ }
+ content := defaultResourceMarkdown
+ if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
+ return "", "", err
+ }
+ return fileName, content, nil
+}
+
+func (s *SkillsService) GetResource(_ context.Context, req request.SkillFileRequest) (string, error) {
+ return s.readSkillFile(req.Tool, req.Skill, "resources", req.FileName)
+}
+
+func (s *SkillsService) SaveResource(_ context.Context, req request.SkillFileSaveRequest) error {
+ return s.writeSkillFile(req.Tool, req.Skill, "resources", req.FileName, req.Content)
+}
+
+func (s *SkillsService) GetGlobalConstraint(_ context.Context, tool string) (string, bool, error) {
+ skillsDir, err := s.toolSkillsDir(tool)
+ if err != nil {
+ return "", false, err
+ }
+ filePath := filepath.Join(skillsDir, globalConstraintFileName)
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return defaultGlobalConstraintMarkdown, false, nil
+ }
+ return "", false, err
+ }
+ return string(content), true, nil
+}
+
+func (s *SkillsService) SaveGlobalConstraint(_ context.Context, req request.SkillGlobalConstraintSaveRequest) error {
+ if strings.TrimSpace(req.Tool) == "" {
+ return errors.New("工具类型不能为空")
+ }
+ writeConstraint := func(tool, content string) error {
+ skillsDir, err := s.toolSkillsDir(tool)
+ if err != nil {
+ return err
+ }
+ filePath := filepath.Join(skillsDir, globalConstraintFileName)
+ if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
+ return err
+ }
+ return os.WriteFile(filePath, []byte(content), 0644)
+ }
+ if err := writeConstraint(req.Tool, req.Content); err != nil {
+ return err
+ }
+ if len(req.SyncTools) == 0 {
+ return nil
+ }
+ for _, tool := range req.SyncTools {
+ if tool == "" || tool == req.Tool {
+ continue
+ }
+ if err := writeConstraint(tool, req.Content); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *SkillsService) toolSkillsDir(tool string) (string, error) {
+ toolDir, ok := skillToolDirs[tool]
+ if !ok {
+ return "", errors.New("工具类型不支持")
+ }
+ root := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Root)
+ if root == "" {
+ root = "."
+ }
+ skillsDir := filepath.Join(root, toolDir, "skills")
+ if err := os.MkdirAll(skillsDir, os.ModePerm); err != nil {
+ return "", err
+ }
+ return skillsDir, nil
+}
+
+func (s *SkillsService) skillDir(tool, skill string) (string, error) {
+ skillsDir, err := s.toolSkillsDir(tool)
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(skillsDir, skill), nil
+}
+
+func (s *SkillsService) ensureSkillDir(tool, skill string) (string, error) {
+ if !isSafeName(skill) {
+ return "", errors.New("技能名称不合法")
+ }
+ skillDir, err := s.skillDir(tool, skill)
+ if err != nil {
+ return "", err
+ }
+ if err := os.MkdirAll(skillDir, os.ModePerm); err != nil {
+ return "", err
+ }
+ return skillDir, nil
+}
+
+func (s *SkillsService) readSkillFile(tool, skill, subDir, fileName string) (string, error) {
+ if !isSafeName(skill) {
+ return "", errors.New("技能名称不合法")
+ }
+ if !isSafeFileName(fileName) {
+ return "", errors.New("文件名不合法")
+ }
+ skillDir, err := s.skillDir(tool, skill)
+ if err != nil {
+ return "", err
+ }
+ filePath := filepath.Join(skillDir, subDir, fileName)
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ return "", err
+ }
+ return string(content), nil
+}
+
+func (s *SkillsService) writeSkillFile(tool, skill, subDir, fileName, content string) error {
+ if !isSafeName(skill) {
+ return errors.New("技能名称不合法")
+ }
+ if !isSafeFileName(fileName) {
+ return errors.New("文件名不合法")
+ }
+ skillDir, err := s.ensureSkillDir(tool, skill)
+ if err != nil {
+ return err
+ }
+ filePath := filepath.Join(skillDir, subDir, fileName)
+ if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
+ return err
+ }
+ return os.WriteFile(filePath, []byte(content), 0644)
+}
+
+func parseSkillContent(content string) (system.SkillMeta, string, error) {
+ clean := strings.TrimPrefix(content, "\ufeff")
+ lines := strings.Split(clean, "\n")
+ if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" {
+ return system.SkillMeta{}, clean, nil
+ }
+ end := -1
+ for i := 1; i < len(lines); i++ {
+ if strings.TrimSpace(lines[i]) == "---" {
+ end = i
+ break
+ }
+ }
+ if end == -1 {
+ return system.SkillMeta{}, clean, nil
+ }
+ yamlText := strings.Join(lines[1:end], "\n")
+ body := strings.Join(lines[end+1:], "\n")
+ var meta system.SkillMeta
+ if err := yaml.Unmarshal([]byte(yamlText), &meta); err != nil {
+ return system.SkillMeta{}, body, err
+ }
+ return meta, body, nil
+}
+
+func buildSkillContent(meta system.SkillMeta, markdown string) (string, error) {
+ if meta.Name == "" {
+ return "", errors.New("name不能为空")
+ }
+ data, err := yaml.Marshal(meta)
+ if err != nil {
+ return "", err
+ }
+ yamlText := strings.TrimRight(string(data), "\n")
+ body := strings.TrimLeft(markdown, "\n")
+ if body != "" {
+ body = body + "\n"
+ }
+ return fmt.Sprintf("---\n%s\n---\n%s", yamlText, body), nil
+}
+
+func listFiles(dir string) []string {
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return []string{}
+ }
+ files := make([]string, 0, len(entries))
+ for _, entry := range entries {
+ if entry.Type().IsRegular() {
+ files = append(files, entry.Name())
+ }
+ }
+ sort.Strings(files)
+ return files
+}
+
+func isSafeName(name string) bool {
+ if strings.TrimSpace(name) == "" {
+ return false
+ }
+ if strings.Contains(name, "..") {
+ return false
+ }
+ if strings.ContainsAny(name, "/\\") {
+ return false
+ }
+ return name == filepath.Base(name)
+}
+
+func isSafeFileName(name string) bool {
+ if strings.TrimSpace(name) == "" {
+ return false
+ }
+ if strings.Contains(name, "..") {
+ return false
+ }
+ if strings.ContainsAny(name, "/\\") {
+ return false
+ }
+ return name == filepath.Base(name)
+}
+
+func buildScriptFileName(fileName, scriptType string) (string, string, error) {
+ clean := strings.TrimSpace(fileName)
+ if clean == "" {
+ return "", "", errors.New("文件名不能为空")
+ }
+ if !isSafeFileName(clean) {
+ return "", "", errors.New("文件名不合法")
+ }
+ base := strings.TrimSuffix(clean, filepath.Ext(clean))
+ if base == "" {
+ return "", "", errors.New("文件名不合法")
+ }
+
+ switch strings.ToLower(scriptType) {
+ case "py", "python":
+ return base + ".py", "python", nil
+ case "js", "javascript", "script":
+ return base + ".js", "javascript", nil
+ case "sh", "shell", "bash":
+ return base + ".sh", "sh", nil
+ default:
+ return "", "", errors.New("脚本类型不支持")
+ }
+}
+
+func buildResourceFileName(fileName string) (string, error) {
+ clean := strings.TrimSpace(fileName)
+ if clean == "" {
+ return "", errors.New("文件名不能为空")
+ }
+ if !isSafeFileName(clean) {
+ return "", errors.New("文件名不合法")
+ }
+ base := strings.TrimSuffix(clean, filepath.Ext(clean))
+ if base == "" {
+ return "", errors.New("文件名不合法")
+ }
+ return base + ".md", nil
+}
+
+func scriptTemplate(lang string) string {
+ switch lang {
+ case "python":
+ return "# -*- coding: utf-8 -*-\n# TODO: 在这里实现脚本逻辑\n"
+ case "javascript":
+ return "// TODO: 在这里实现脚本逻辑\n"
+ case "sh":
+ return "#!/usr/bin/env bash\nset -euo pipefail\n\n# TODO: 在这里实现脚本逻辑\n"
+ default:
+ return ""
+ }
+}
+
+func copySkillDir(src, dst string) error {
+ return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ rel, err := filepath.Rel(src, path)
+ if err != nil {
+ return err
+ }
+ if rel == "." {
+ return nil
+ }
+ target := filepath.Join(dst, rel)
+ if d.IsDir() {
+ return os.MkdirAll(target, os.ModePerm)
+ }
+ if !d.Type().IsRegular() {
+ return nil
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {
+ return err
+ }
+ return os.WriteFile(target, data, 0644)
+ })
+}
diff --git a/server/source/system/api.go b/server/source/system/api.go
index 863847bd..3632840e 100644
--- a/server/source/system/api.go
+++ b/server/source/system/api.go
@@ -46,6 +46,15 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {
entities := []sysModel.SysApi{
{ApiGroup: "jwt", Method: "POST", Path: "/jwt/jsonInBlacklist", Description: "jwt加入黑名单(退出,必选)"},
+ {ApiGroup: "登录日志", Method: "DELETE", Path: "/sysLoginLog/deleteLoginLog", Description: "删除登录日志"},
+ {ApiGroup: "登录日志", Method: "DELETE", Path: "/sysLoginLog/deleteLoginLogByIds", Description: "批量删除登录日志"},
+ {ApiGroup: "登录日志", Method: "GET", Path: "/sysLoginLog/findLoginLog", Description: "根据ID获取登录日志"},
+ {ApiGroup: "登录日志", Method: "GET", Path: "/sysLoginLog/getLoginLogList", Description: "获取登录日志列表"},
+
+ {ApiGroup: "API Token", Method: "POST", Path: "/sysApiToken/createApiToken", Description: "签发API Token"},
+ {ApiGroup: "API Token", Method: "POST", Path: "/sysApiToken/getApiTokenList", Description: "获取API Token列表"},
+ {ApiGroup: "API Token", Method: "POST", Path: "/sysApiToken/deleteApiToken", Description: "作废API Token"},
+
{ApiGroup: "系统用户", Method: "DELETE", Path: "/user/deleteUser", Description: "删除用户"},
{ApiGroup: "系统用户", Method: "POST", Path: "/user/admin_register", Description: "用户注册"},
{ApiGroup: "系统用户", Method: "POST", Path: "/user/getUserList", Description: "获取用户列表"},
@@ -105,6 +114,19 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {
{ApiGroup: "系统服务", Method: "POST", Path: "/system/getSystemConfig", Description: "获取配置文件内容"},
{ApiGroup: "系统服务", Method: "POST", Path: "/system/setSystemConfig", Description: "设置配置文件内容"},
+ {ApiGroup: "skills", Method: "GET", Path: "/skills/getTools", Description: "获取技能工具列表"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/getSkillList", Description: "获取技能列表"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/getSkillDetail", Description: "获取技能详情"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/saveSkill", Description: "保存技能定义"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/createScript", Description: "创建技能脚本"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/getScript", Description: "读取技能脚本"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/saveScript", Description: "保存技能脚本"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/createResource", Description: "创建技能资源"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/getResource", Description: "读取技能资源"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/saveResource", Description: "保存技能资源"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/getGlobalConstraint", Description: "读取全局约束"},
+ {ApiGroup: "skills", Method: "POST", Path: "/skills/saveGlobalConstraint", Description: "保存全局约束"},
+
{ApiGroup: "客户", Method: "PUT", Path: "/customer/customer", Description: "更新客户"},
{ApiGroup: "客户", Method: "POST", Path: "/customer/customer", Description: "创建客户"},
{ApiGroup: "客户", Method: "DELETE", Path: "/customer/customer", Description: "删除客户"},
@@ -118,6 +140,8 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {
{ApiGroup: "代码生成器", Method: "GET", Path: "/autoCode/getColumn", Description: "获取所选table的所有字段"},
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/installPlugin", Description: "安装插件"},
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/pubPlug", Description: "打包插件"},
+ {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/removePlugin", Description: "卸载插件"},
+ {ApiGroup: "代码生成器", Method: "GET", Path: "/autoCode/getPluginList", Description: "获取已安装插件"},
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcp", Description: "自动生成 MCP Tool 模板"},
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcpTest", Description: "MCP Tool 测试"},
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcpList", Description: "获取 MCP ToolList"},
diff --git a/server/source/system/casbin.go b/server/source/system/casbin.go
index a7e49d75..0cdf22ec 100644
--- a/server/source/system/casbin.go
+++ b/server/source/system/casbin.go
@@ -47,6 +47,15 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
entities := []adapter.CasbinRule{
{Ptype: "p", V0: "888", V1: "/user/admin_register", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/sysLoginLog/deleteLoginLog", V2: "DELETE"},
+ {Ptype: "p", V0: "888", V1: "/sysLoginLog/deleteLoginLogByIds", V2: "DELETE"},
+ {Ptype: "p", V0: "888", V1: "/sysLoginLog/findLoginLog", V2: "GET"},
+ {Ptype: "p", V0: "888", V1: "/sysLoginLog/getLoginLogList", V2: "GET"},
+
+ {Ptype: "p", V0: "888", V1: "/sysApiToken/createApiToken", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/sysApiToken/getApiTokenList", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/sysApiToken/deleteApiToken", V2: "POST"},
+
{Ptype: "p", V0: "888", V1: "/api/createApi", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/api/getApiList", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/api/getApiById", V2: "POST"},
@@ -107,6 +116,19 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
{Ptype: "p", V0: "888", V1: "/system/setSystemConfig", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/system/getServerInfo", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/getTools", V2: "GET"},
+ {Ptype: "p", V0: "888", V1: "/skills/getSkillList", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/getSkillDetail", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/saveSkill", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/createScript", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/getScript", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/saveScript", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/createResource", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/getResource", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/saveResource", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/getGlobalConstraint", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/skills/saveGlobalConstraint", V2: "POST"},
+
{Ptype: "p", V0: "888", V1: "/customer/customer", V2: "GET"},
{Ptype: "p", V0: "888", V1: "/customer/customer", V2: "PUT"},
{Ptype: "p", V0: "888", V1: "/customer/customer", V2: "POST"},
@@ -129,6 +151,8 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
{Ptype: "p", V0: "888", V1: "/autoCode/createPlug", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/autoCode/installPlugin", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/autoCode/pubPlug", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/autoCode/removePlugin", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/autoCode/getPluginList", V2: "GET"},
{Ptype: "p", V0: "888", V1: "/autoCode/addFunc", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/autoCode/mcp", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/autoCode/mcpTest", V2: "POST"},
diff --git a/server/source/system/menu.go b/server/source/system/menu.go
index 078716bb..66b133a3 100644
--- a/server/source/system/menu.go
+++ b/server/source/system/menu.go
@@ -96,9 +96,12 @@ func (i *initMenu) InitializeData(ctx context.Context) (next context.Context, er
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "formCreate", Name: "formCreate", Component: "view/systemTools/formCreate/index.vue", Sort: 3, Meta: Meta{Title: "表单生成器", Icon: "magic-stick", KeepAlive: true}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "system", Name: "system", Component: "view/systemTools/system/system.vue", Sort: 4, Meta: Meta{Title: "系统配置", Icon: "operation"}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "autoCodeAdmin", Name: "autoCodeAdmin", Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 2, Meta: Meta{Title: "自动化代码管理", Icon: "magic-stick"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "loginLog", Name: "loginLog", Component: "view/systemTools/loginLog/index.vue", Sort: 5, Meta: Meta{Title: "登录日志", Icon: "monitor"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "apiToken", Name: "apiToken", Component: "view/systemTools/apiToken/index.vue", Sort: 6, Meta: Meta{Title: "API Token", Icon: "key"}},
{MenuLevel: 1, Hidden: true, ParentId: menuNameMap["systemTools"], Path: "autoCodeEdit/:id", Name: "autoCodeEdit", Component: "view/systemTools/autoCode/index.vue", Sort: 0, Meta: Meta{Title: "自动化代码-${id}", Icon: "magic-stick"}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "autoPkg", Name: "autoPkg", Component: "view/systemTools/autoPkg/autoPkg.vue", Sort: 0, Meta: Meta{Title: "模板配置", Icon: "folder"}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "exportTemplate", Name: "exportTemplate", Component: "view/systemTools/exportTemplate/exportTemplate.vue", Sort: 5, Meta: Meta{Title: "导出模板", Icon: "reading"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "skills", Name: "skills", Component: "view/systemTools/skills/index.vue", Sort: 6, Meta: Meta{Title: "Skills管理", Icon: "document"}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "picture", Name: "picture", Component: "view/systemTools/autoCode/picture.vue", Sort: 6, Meta: Meta{Title: "AI页面绘制", Icon: "picture-filled"}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "mcpTool", Name: "mcpTool", Component: "view/systemTools/autoCode/mcp.vue", Sort: 7, Meta: Meta{Title: "Mcp Tools模板", Icon: "magnet"}},
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "mcpTest", Name: "mcpTest", Component: "view/systemTools/autoCode/mcpTest.vue", Sort: 7, Meta: Meta{Title: "Mcp Tools测试", Icon: "partly-cloudy"}},
diff --git a/server/utils/plugin/v2/registry.go b/server/utils/plugin/v2/registry.go
new file mode 100644
index 00000000..4ec5fcea
--- /dev/null
+++ b/server/utils/plugin/v2/registry.go
@@ -0,0 +1,27 @@
+package plugin
+
+import "sync"
+
+var (
+ registryMu sync.RWMutex
+ registry []Plugin
+)
+
+// Register records a plugin for auto initialization.
+func Register(p Plugin) {
+ if p == nil {
+ return
+ }
+ registryMu.Lock()
+ registry = append(registry, p)
+ registryMu.Unlock()
+}
+
+// Registered returns a snapshot of all registered plugins.
+func Registered() []Plugin {
+ registryMu.RLock()
+ defer registryMu.RUnlock()
+ out := make([]Plugin, len(registry))
+ copy(out, registry)
+ return out
+}
diff --git a/web/package.json b/web/package.json
index 5531dce3..d31bf569 100644
--- a/web/package.json
+++ b/web/package.json
@@ -79,6 +79,7 @@
"sass": "^1.78.0",
"terser": "^5.31.6",
"vite": "^6.2.3",
+ "vite-check-multiple-dom": "0.2.1",
"vite-plugin-banner": "^0.8.0",
"vite-plugin-importer": "^0.2.5",
"vite-plugin-vue-devtools": "^7.0.16"
diff --git a/web/src/api/autoCode.js b/web/src/api/autoCode.js
index 789e92c8..8d540d34 100644
--- a/web/src/api/autoCode.js
+++ b/web/src/api/autoCode.js
@@ -143,7 +143,7 @@ export const llmAuto = (data) => {
return service({
url: '/autoCode/llmAuto',
method: 'post',
- data: { ...data, mode: 'ai' },
+ data: { ...data },
timeout: 1000 * 60 * 10,
loadingOption: {
lock: true,
@@ -153,36 +153,6 @@ export const llmAuto = (data) => {
})
}
-export const butler = (data) => {
- return service({
- url: '/autoCode/llmAuto',
- method: 'post',
- data: { ...data, mode: 'butler' },
- timeout: 1000 * 60 * 10
- })
-}
-
-export const eye = (data) => {
- return service({
- url: '/autoCode/llmAuto',
- method: 'post',
- data: { ...data, mode: 'eye' },
- timeout: 1000 * 60 * 10
- })
-}
-
-
-export const createWeb = (data) => {
- return service({
- url: '/autoCode/llmAuto',
- method: 'post',
- data: { ...data, mode: 'painter' },
- timeout: 1000 * 60 * 10
- })
-}
-
-
-
export const addFunc = (data) => {
return service({
url: '/autoCode/addFunc',
@@ -240,3 +210,33 @@ export const mcpTest = (data) => {
data
})
}
+
+// @Tags SysApi
+// @Summary 获取插件列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /autoCode/getPluginList [get]
+export const getPluginList = (params) => {
+ return service({
+ url: '/autoCode/getPluginList',
+ method: 'get',
+ params
+ })
+}
+
+// @Tags SysApi
+// @Summary 删除插件
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /autoCode/removePlugin [post]
+export const removePlugin = (params) => {
+ return service({
+ url: '/autoCode/removePlugin',
+ method: 'post',
+ params
+ })
+}
diff --git a/web/src/api/skills.js b/web/src/api/skills.js
new file mode 100644
index 00000000..0a9dbb21
--- /dev/null
+++ b/web/src/api/skills.js
@@ -0,0 +1,96 @@
+import service from '@/utils/request'
+
+export const getSkillTools = () => {
+ return service({
+ url: '/skills/getTools',
+ method: 'get'
+ })
+}
+
+export const getSkillList = (data) => {
+ return service({
+ url: '/skills/getSkillList',
+ method: 'post',
+ data
+ })
+}
+
+export const getSkillDetail = (data) => {
+ return service({
+ url: '/skills/getSkillDetail',
+ method: 'post',
+ data
+ })
+}
+
+export const saveSkill = (data) => {
+ return service({
+ url: '/skills/saveSkill',
+ method: 'post',
+ data
+ })
+}
+
+export const createSkillScript = (data) => {
+ return service({
+ url: '/skills/createScript',
+ method: 'post',
+ data
+ })
+}
+
+export const getSkillScript = (data) => {
+ return service({
+ url: '/skills/getScript',
+ method: 'post',
+ data
+ })
+}
+
+export const saveSkillScript = (data) => {
+ return service({
+ url: '/skills/saveScript',
+ method: 'post',
+ data
+ })
+}
+
+export const createSkillResource = (data) => {
+ return service({
+ url: '/skills/createResource',
+ method: 'post',
+ data
+ })
+}
+
+export const getSkillResource = (data) => {
+ return service({
+ url: '/skills/getResource',
+ method: 'post',
+ data
+ })
+}
+
+export const saveSkillResource = (data) => {
+ return service({
+ url: '/skills/saveResource',
+ method: 'post',
+ data
+ })
+}
+
+export const getGlobalConstraint = (data) => {
+ return service({
+ url: '/skills/getGlobalConstraint',
+ method: 'post',
+ data
+ })
+}
+
+export const saveGlobalConstraint = (data) => {
+ return service({
+ url: '/skills/saveGlobalConstraint',
+ method: 'post',
+ data
+ })
+}
diff --git a/web/src/api/sysApiToken.js b/web/src/api/sysApiToken.js
new file mode 100644
index 00000000..f95c714a
--- /dev/null
+++ b/web/src/api/sysApiToken.js
@@ -0,0 +1,25 @@
+import service from '@/utils/request'
+
+export const createApiToken = (data) => {
+ return service({
+ url: '/sysApiToken/createApiToken',
+ method: 'post',
+ data
+ })
+}
+
+export const getApiTokenList = (data) => {
+ return service({
+ url: '/sysApiToken/getApiTokenList',
+ method: 'post',
+ data
+ })
+}
+
+export const deleteApiToken = (data) => {
+ return service({
+ url: '/sysApiToken/deleteApiToken',
+ method: 'post',
+ data
+ })
+}
diff --git a/web/src/api/sysLoginLog.js b/web/src/api/sysLoginLog.js
new file mode 100644
index 00000000..4c96de20
--- /dev/null
+++ b/web/src/api/sysLoginLog.js
@@ -0,0 +1,33 @@
+import service from '@/utils/request'
+
+export const deleteLoginLog = (data) => {
+ return service({
+ url: '/sysLoginLog/deleteLoginLog',
+ method: 'delete',
+ data
+ })
+}
+
+export const deleteLoginLogByIds = (data) => {
+ return service({
+ url: '/sysLoginLog/deleteLoginLogByIds',
+ method: 'delete',
+ data
+ })
+}
+
+export const getLoginLogList = (params) => {
+ return service({
+ url: '/sysLoginLog/getLoginLogList',
+ method: 'get',
+ params
+ })
+}
+
+export const findLoginLog = (params) => {
+ return service({
+ url: '/sysLoginLog/findLoginLog',
+ method: 'get',
+ params
+ })
+}
diff --git a/web/src/main.js b/web/src/main.js
index 5855b570..e5b5cc28 100644
--- a/web/src/main.js
+++ b/web/src/main.js
@@ -3,6 +3,7 @@ import 'element-plus/theme-chalk/dark/css-vars.css'
import 'uno.css'
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
+import { setupVueRootValidator } from 'vite-check-multiple-dom/client';
import 'element-plus/dist/index.css'
// 引入gin-vue-admin前端初始化相关内容
@@ -21,6 +22,10 @@ const app = createApp(App)
app.config.productionTip = false
+setupVueRootValidator(app, {
+ lang: 'zh'
+ })
+
app
.use(run)
.use(ElementPlus)
diff --git a/web/src/utils/env.js b/web/src/utils/env.js
new file mode 100644
index 00000000..7053375c
--- /dev/null
+++ b/web/src/utils/env.js
@@ -0,0 +1,3 @@
+export const isDev = import.meta.env.DEV;
+
+export const isProd = import.meta.env.PROD;
diff --git a/web/src/view/dashboard/components/table.vue b/web/src/view/dashboard/components/table.vue
index 262f11a7..05f2c370 100644
--- a/web/src/view/dashboard/components/table.vue
+++ b/web/src/view/dashboard/components/table.vue
@@ -10,12 +10,23 @@
+
+
diff --git a/web/src/view/systemTools/autoCode/index.vue b/web/src/view/systemTools/autoCode/index.vue
index a9c4f5c4..f6b24426 100644
--- a/web/src/view/systemTools/autoCode/index.vue
+++ b/web/src/view/systemTools/autoCode/index.vue
@@ -822,7 +822,7 @@
preview,
getMeta,
getPackageApi,
- llmAuto, butler, eye
+ llmAuto
} from '@/api/autoCode'
import { getDict } from '@/utils/dictionary'
import { ref, watch, toRaw, onMounted, nextTick } from 'vue'
@@ -860,9 +860,9 @@
const reader = new FileReader();
reader.onload =async (e) => {
const base64String = e.target.result;
- const res = await eye({ picture: base64String,command: 'eye' })
+ const res = await llmAuto({ _file_path: base64String,mode:"eye" })
if (res.code === 0) {
- prompt.value = res.data
+ prompt.value = res.data.text
llmAutoFunc()
}
};
@@ -893,9 +893,9 @@
reader.onload = async (e) => {
const base64String = e.target.result;
- const res = await eye({ picture: base64String,command: 'eye' })
+ const res = await llmAuto({ _file_path: base64String,mode:'eye' })
if (res.code === 0) {
- prompt.value = res.data
+ prompt.value = res.data.text
llmAutoFunc()
}
};
@@ -933,11 +933,12 @@
}
const res = await llmAuto({
- prompt: flag ? '结构体名称为' + form.value.structName : prompt.value
+ prompt: flag ? '结构体名称为' + form.value.structName : prompt.value,
+ mode: "ai"
})
if (res.code === 0) {
form.value.fields = []
- const json = JSON.parse(res.data)
+ const json = JSON.parse(res.data.text)
json.fields?.forEach((item) => {
item.fieldName = toUpperCase(item.fieldName)
})
diff --git a/web/src/view/systemTools/autoCode/picture.vue b/web/src/view/systemTools/autoCode/picture.vue
index 4863d362..56a3b1c6 100644
--- a/web/src/view/systemTools/autoCode/picture.vue
+++ b/web/src/view/systemTools/autoCode/picture.vue
@@ -1,7 +1,7 @@