{{- if not .IsTree }}
-
+
{{- if .GvaModel }}
-
+
创建日期
@@ -383,124 +107,26 @@ getDataSourceFunc()
-
- —
-
-
+
+
+
{{ end -}}
- {{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }} {{- if eq .FieldType "bool" }}
-
-
-
-
-
-
-
-
- {{- else if .DictType}}
-
- {searchInfo.{{.FieldJson}}=undefined}">
-
-
-
- {{- else if .CheckDataSource}}
-
-
-
-
-
- {{- else}}
-
- {{- if eq .FieldType "float64" "int"}}
- {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
-
- —
-
- {{- else}}
-
- {{- end}}
- {{- else if eq .FieldType "time.Time"}}
- {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
-
-
- {{.FieldDesc}}
-
-
-
-
-
-
- —
-
- {{- else}}
-
- {{- end}}
- {{- else}}
-
- {{- end}}
- {{ end }}{{ end }}{{ end }}{{ end }}
+ {{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}
+ {{ GenerateSearchFormItem .}}
+ {{ end }}{{ end }}{{ end }}
- {{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }} {{- if eq .FieldType "bool" }}
-
-
-
-
-
-
-
-
- {{- else if .DictType}}
-
- {searchInfo.{{.FieldJson}}=undefined}">
-
-
-
- {{- else}}
-
- {{- if eq .FieldType "float64" "int"}}
- {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
-
- —
-
- {{- else}}
-
- {{- end}}
- {{- else if eq .FieldType "time.Time"}}
- {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
-
-
- {{.FieldDesc}}
-
-
-
-
-
-
- —
-
- {{- else}}
-
- {{- end}}
- {{- else}}
-
- {{- end}}
-
-
- {{ end }}{{ end }}{{ end }}{{ end }}
+ {{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }}
+ {{ GenerateSearchFormItem .}}
+ {{ end }}{{ end }}{{ end }}
@@ -518,7 +144,7 @@ getDataSourceFunc()
删除
{{ if .HasExcel -}}
-
+
{{- end }}
@@ -535,97 +161,13 @@ getDataSourceFunc()
>
{{ if .GvaModel }}
-
+
{{ "{{ formatDate(scope.row.CreatedAt) }}" }}
{{ end }}
{{- range .Fields}}
{{- if .Table}}
- {{- if .CheckDataSource }}
-
-
- {{- if eq .DataSource.Association 2}}
-
- {{ "{{ item }}" }}
-
- {{- else }}
- {{"{{"}} filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}}) {{"}}"}}
- {{- end }}
-
-
- {{- else if .DictType}}
-
-
- {{if eq .FieldType "array"}}
- {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}
- {{- else }}
- {{"{{"}} filterDict(scope.row.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
- {{end}}
-
-
- {{- else if eq .FieldType "bool" }}
-
- {{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}
-
- {{- else if eq .FieldType "time.Time" }}
-
- {{"{{"}} formatDate(scope.row.{{.FieldJson}}) {{"}}"}}
-
- {{- else if eq .FieldType "picture" }}
-
-
-
-
-
- {{- else if eq .FieldType "pictures" }}
-
-
-
-
-
-
-
- {{- else if eq .FieldType "video" }}
-
-
-
-
-
- {{- else if eq .FieldType "richtext" }}
-
-
- [富文本内容]
-
-
- {{- else if eq .FieldType "file" }}
-
-
-
- {{"{{"}}file.name{{"}}"}}
-
-
-
- {{- else if eq .FieldType "json" }}
-
-
- [JSON]
-
-
- {{- else if eq .FieldType "array" }}
-
-
-
-
-
- {{- else }}
-
- {{- end }}
+ {{ GenerateTableColumn . }}
{{- end }}
{{- end }}
@@ -679,78 +221,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Form}}
-
- {{- if .CheckDataSource}}
-
-
-
- {{- else }}
- {{- if eq .FieldType "bool" }}
-
- {{- end }}
- {{- if eq .FieldType "string" }}
- {{- if .DictType}}
-
-
-
- {{- else }}
-
- {{- end }}
- {{- end }}
- {{- if eq .FieldType "richtext" }}
-
- {{- end }}
- {{- if eq .FieldType "json" }}
- // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
- {{"{{"}} formData.{{.FieldJson}} {{"}}"}}
- {{- end }}
- {{- if eq .FieldType "array" }}
- {{- if .DictType}}
-
-
-
- {{- else }}
-
- {{- end }}
- {{- end }}
- {{- if eq .FieldType "int" }}
-
- {{- end }}
- {{- if eq .FieldType "time.Time" }}
-
- {{- end }}
- {{- if eq .FieldType "float64" }}
-
- {{- end }}
- {{- if eq .FieldType "enum" }}
-
-
-
- {{- end }}
- {{- if eq .FieldType "picture" }}
-
- {{- end }}
- {{- if eq .FieldType "pictures" }}
-
- {{- end }}
- {{- if eq .FieldType "video" }}
-
- {{- end }}
- {{- if eq .FieldType "file" }}
-
- {{- end }}
- {{- end }}
-
+ {{ GenerateFormItem . }}
{{- end }}
{{- end }}
@@ -761,7 +232,7 @@ getDataSourceFunc()
{{- if .IsTree }}
- {{- if .CheckDataSource }}
- {{- if eq .DataSource.Association 2}}
-
- {{ "{{ item }}" }}
-
- {{- else }}
- {{"{{"}} filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}}) {{"}}"}}
- {{- end }}
- {{- else if .DictType}}
- {{if eq .FieldType "array"}}
- {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}
- {{- else }}
- {{"{{"}} filterDict(detailFrom.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
- {{end}}
- {{- else if and (ne .FieldType "picture" ) (ne .FieldType "richtext" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }}
- {{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}}
- {{- else }}
- {{- if eq .FieldType "picture" }}
-
- {{- end }}
- {{- if eq .FieldType "array" }}
-
- {{- end }}
- {{- if eq .FieldType "pictures" }}
-
- {{- end }}
- {{- if eq .FieldType "richtext" }}
-
- {{- end }}
- {{- if eq .FieldType "file" }}
-
-
-
- {{"{{"}}item.name{{"}}"}}
-
-
- {{- end }}
- {{- end }}
-
+ {{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
@@ -906,42 +338,7 @@ const formData = ref({
{{- end }}
{{- range .Fields}}
{{- if .Form}}
- {{- if eq .FieldType "bool" }}
- {{.FieldJson}}: false,
- {{- end }}
- {{- if eq .FieldType "string" }}
- {{.FieldJson}}: '',
- {{- end }}
- {{- if eq .FieldType "richtext" }}
- {{.FieldJson}}: '',
- {{- end }}
- {{- if eq .FieldType "int" }}
- {{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }},
- {{- end }}
- {{- if eq .FieldType "time.Time" }}
- {{.FieldJson}}: new Date(),
- {{- end }}
- {{- if eq .FieldType "float64" }}
- {{.FieldJson}}: 0,
- {{- end }}
- {{- if eq .FieldType "picture" }}
- {{.FieldJson}}: "",
- {{- end }}
- {{- if eq .FieldType "video" }}
- {{.FieldJson}}: "",
- {{- end }}
- {{- if eq .FieldType "pictures" }}
- {{.FieldJson}}: [],
- {{- end }}
- {{- if eq .FieldType "file" }}
- {{.FieldJson}}: [],
- {{- end }}
- {{- if eq .FieldType "json" }}
- {{.FieldJson}}: {},
- {{- end }}
- {{- if eq .FieldType "array" }}
- {{.FieldJson}}: [],
- {{- end }}
+ {{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})
@@ -982,39 +379,6 @@ const rule = reactive({
{{- end }}
})
-const searchRule = reactive({
- createdAt: [
- { validator: (rule, value, callback) => {
- if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
- callback(new Error('请填写结束日期'))
- } else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
- callback(new Error('请填写开始日期'))
- } else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
- callback(new Error('开始日期应当早于结束日期'))
- } else {
- callback()
- }
- }, trigger: 'change' }
- ],
- {{- range .Fields }}
- {{- if .FieldSearchType}}
- {{- if eq .FieldType "time.Time" }}
- {{.FieldJson }} : [{ validator: (rule, value, callback) => {
- if (searchInfo.value.start{{.FieldName}} && !searchInfo.value.end{{.FieldName}}) {
- callback(new Error('请填写结束日期'))
- } else if (!searchInfo.value.start{{.FieldName}} && searchInfo.value.end{{.FieldName}}) {
- callback(new Error('请填写开始日期'))
- } else if (searchInfo.value.start{{.FieldName}} && searchInfo.value.end{{.FieldName}} && (searchInfo.value.start{{.FieldName}}.getTime() === searchInfo.value.end{{.FieldName}}.getTime() || searchInfo.value.start{{.FieldName}}.getTime() > searchInfo.value.end{{.FieldName}}.getTime())) {
- callback(new Error('开始日期应当早于结束日期'))
- } else {
- callback()
- }
- }, trigger: 'change' }],
- {{- end }}
- {{- end }}
- {{- end }}
-})
-
const elFormRef = ref()
const elSearchFormRef = ref()
@@ -1029,6 +393,8 @@ const searchInfo = ref({})
// 排序
const sortChange = ({ prop, order }) => {
const sortMap = {
+ CreatedAt:"created_at",
+ ID:"id",
{{- range .Fields}}
{{- if .Table}}
{{- if and .Sort}}
@@ -1229,42 +595,7 @@ const closeDialog = () => {
formData.value = {
{{- range .Fields}}
{{- if .Form}}
- {{- if eq .FieldType "bool" }}
- {{.FieldJson}}: false,
- {{- end }}
- {{- if eq .FieldType "string" }}
- {{.FieldJson}}: '',
- {{- end }}
- {{- if eq .FieldType "richtext" }}
- {{.FieldJson}}: '',
- {{- end }}
- {{- if eq .FieldType "int" }}
- {{.FieldJson}}: {{- if .DataSource }} undefined{{ else }} 0{{- end }},
- {{- end }}
- {{- if eq .FieldType "time.Time" }}
- {{.FieldJson}}: new Date(),
- {{- end }}
- {{- if eq .FieldType "float64" }}
- {{.FieldJson}}: 0,
- {{- end }}
- {{- if eq .FieldType "picture" }}
- {{.FieldJson}}: "",
- {{- end }}
- {{- if eq .FieldType "video" }}
- {{.FieldJson}}: "",
- {{- end }}
- {{- if eq .FieldType "pictures" }}
- {{.FieldJson}}: [],
- {{- end }}
- {{- if eq .FieldType "file" }}
- {{.FieldJson}}: [],
- {{- end }}
- {{- if eq .FieldType "json" }}
- {{.FieldJson}}: {},
- {{- end }}
- {{- if eq .FieldType "array" }}
- {{.FieldJson}}: [],
- {{- end }}
+ {{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
}
@@ -1298,7 +629,7 @@ const enterDialog = async () => {
})
}
-const detailFrom = ref({})
+const detailForm = ref({})
// 查看详情控制标记
const detailShow = ref(false)
@@ -1315,7 +646,7 @@ const getDetails = async (row) => {
// 打开弹窗
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
if (res.code === 0) {
- detailFrom.value = res.data
+ detailForm.value = res.data
openDetailShow()
}
}
@@ -1324,7 +655,7 @@ const getDetails = async (row) => {
// 关闭详情弹窗
const closeDetailShow = () => {
detailShow.value = false
- detailFrom.value = {}
+ detailForm.value = {}
}
diff --git a/resource/plugin/server/api/api.go.tpl b/resource/plugin/server/api/api.go.tpl
new file mode 100644
index 0000000..e69ae82
--- /dev/null
+++ b/resource/plugin/server/api/api.go.tpl
@@ -0,0 +1,255 @@
+package api
+
+import (
+{{if not .OnlyTemplate}}
+ "{{.Module}}/global"
+ "{{.Module}}/model/common/response"
+ "{{.Module}}/plugin/{{.Package}}/model"
+ {{- if not .IsTree}}
+ "{{.Module}}/plugin/{{.Package}}/model/request"
+ {{- end }}
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+ {{- if .AutoCreateResource}}
+ "{{.Module}}/utils"
+ {{- end }}
+{{- else }}
+ "{{.Module}}/model/common/response"
+ "github.com/gin-gonic/gin"
+{{- end }}
+)
+
+var {{.StructName}} = new({{.Abbreviation}})
+
+type {{.Abbreviation}} struct {}
+{{if not .OnlyTemplate}}
+// Create{{.StructName}} 创建{{.Description}}
+// @Tags {{.StructName}}
+// @Summary 创建{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "创建{{.Description}}"
+// @Success 200 {object} response.Response{msg=string} "创建成功"
+// @Router /{{.Abbreviation}}/create{{.StructName}} [post]
+func (a *{{.Abbreviation}}) Create{{.StructName}}(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ var info model.{{.StructName}}
+ err := c.ShouldBindJSON(&info)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ {{- if .AutoCreateResource }}
+ info.CreatedBy = utils.GetUserID(c)
+ {{- end }}
+ err = service{{ .StructName }}.Create{{.StructName}}(ctx,&info)
+ if err != nil {
+ global.GVA_LOG.Error("创建失败!", zap.Error(err))
+ response.FailWithMessage("创建失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithMessage("创建成功", c)
+}
+
+// Delete{{.StructName}} 删除{{.Description}}
+// @Tags {{.StructName}}
+// @Summary 删除{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "删除{{.Description}}"
+// @Success 200 {object} response.Response{msg=string} "删除成功"
+// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
+func (a *{{.Abbreviation}}) Delete{{.StructName}}(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ {{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}")
+{{- if .AutoCreateResource }}
+ userID := utils.GetUserID(c)
+{{- end }}
+ err := service{{ .StructName }}.Delete{{.StructName}}(ctx,{{.PrimaryField.FieldJson}} {{- if .AutoCreateResource -}},userID{{- end -}})
+ if err != nil {
+ global.GVA_LOG.Error("删除失败!", zap.Error(err))
+ response.FailWithMessage("删除失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithMessage("删除成功", c)
+}
+
+// Delete{{.StructName}}ByIds 批量删除{{.Description}}
+// @Tags {{.StructName}}
+// @Summary 批量删除{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Success 200 {object} response.Response{msg=string} "批量删除成功"
+// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete]
+func (a *{{.Abbreviation}}) Delete{{.StructName}}ByIds(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ {{.PrimaryField.FieldJson}}s := c.QueryArray("{{.PrimaryField.FieldJson}}s[]")
+{{- if .AutoCreateResource }}
+ userID := utils.GetUserID(c)
+{{- end }}
+ err := service{{ .StructName }}.Delete{{.StructName}}ByIds(ctx,{{.PrimaryField.FieldJson}}s{{- if .AutoCreateResource }},userID{{- end }})
+ if err != nil {
+ global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
+ response.FailWithMessage("批量删除失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithMessage("批量删除成功", c)
+}
+
+// Update{{.StructName}} 更新{{.Description}}
+// @Tags {{.StructName}}
+// @Summary 更新{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "更新{{.Description}}"
+// @Success 200 {object} response.Response{msg=string} "更新成功"
+// @Router /{{.Abbreviation}}/update{{.StructName}} [put]
+func (a *{{.Abbreviation}}) Update{{.StructName}}(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ var info model.{{.StructName}}
+ err := c.ShouldBindJSON(&info)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+{{- if .AutoCreateResource }}
+ info.UpdatedBy = utils.GetUserID(c)
+{{- end }}
+ err = service{{ .StructName }}.Update{{.StructName}}(ctx,info)
+ if err != nil {
+ global.GVA_LOG.Error("更新失败!", zap.Error(err))
+ response.FailWithMessage("更新失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithMessage("更新成功", c)
+}
+
+// Find{{.StructName}} 用id查询{{.Description}}
+// @Tags {{.StructName}}
+// @Summary 用id查询{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param {{.PrimaryField.FieldJson}} query {{.PrimaryField.FieldType}} true "用id查询{{.Description}}"
+// @Success 200 {object} response.Response{data=model.{{.StructName}},msg=string} "查询成功"
+// @Router /{{.Abbreviation}}/find{{.StructName}} [get]
+func (a *{{.Abbreviation}}) Find{{.StructName}}(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ {{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}")
+ re{{.Abbreviation}}, err := service{{ .StructName }}.Get{{.StructName}}(ctx,{{.PrimaryField.FieldJson}})
+ if err != nil {
+ global.GVA_LOG.Error("查询失败!", zap.Error(err))
+ response.FailWithMessage("查询失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithData(re{{.Abbreviation}}, c)
+}
+
+{{- if .IsTree }}
+// Get{{.StructName}}List 分页获取{{.Description}}列表
+// @Tags {{.StructName}}
+// @Summary 分页获取{{.Description}}列表
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
+// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
+func (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ list, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx)
+ if err != nil {
+ global.GVA_LOG.Error("获取失败!", zap.Error(err))
+ response.FailWithMessage("获取失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithDetailed(list, "获取成功", c)
+}
+{{- else }}
+// Get{{.StructName}}List 分页获取{{.Description}}列表
+// @Tags {{.StructName}}
+// @Summary 分页获取{{.Description}}列表
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data query request.{{.StructName}}Search true "分页获取{{.Description}}列表"
+// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
+// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
+func (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ var pageInfo request.{{.StructName}}Search
+ err := c.ShouldBindQuery(&pageInfo)
+ if err != nil {
+ response.FailWithMessage(err.Error(), c)
+ return
+ }
+ list, total, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx,pageInfo)
+ if err != nil {
+ global.GVA_LOG.Error("获取失败!", zap.Error(err))
+ response.FailWithMessage("获取失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithDetailed(response.PageResult{
+ List: list,
+ Total: total,
+ Page: pageInfo.Page,
+ PageSize: pageInfo.PageSize,
+ }, "获取成功", c)
+}
+{{- end }}
+
+{{- if .HasDataSource }}
+// Get{{.StructName}}DataSource 获取{{.StructName}}的数据源
+// @Tags {{.StructName}}
+// @Summary 获取{{.StructName}}的数据源
+// @Accept application/json
+// @Produce application/json
+// @Success 200 {object} response.Response{data=object,msg=string} "查询成功"
+// @Router /{{.Abbreviation}}/get{{.StructName}}DataSource [get]
+func (a *{{.Abbreviation}}) Get{{.StructName}}DataSource(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ // 此接口为获取数据源定义的数据
+ dataSource, err := service{{ .StructName }}.Get{{.StructName}}DataSource(ctx)
+ if err != nil {
+ global.GVA_LOG.Error("查询失败!", zap.Error(err))
+ response.FailWithMessage("查询失败:" + err.Error(), c)
+ return
+ }
+ response.OkWithData(dataSource, c)
+}
+{{- end }}
+{{- end }}
+// Get{{.StructName}}Public 不需要鉴权的{{.Description}}接口
+// @Tags {{.StructName}}
+// @Summary 不需要鉴权的{{.Description}}接口
+// @Accept application/json
+// @Produce application/json
+// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
+// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]
+func (a *{{.Abbreviation}}) Get{{.StructName}}Public(c *gin.Context) {
+ // 创建业务用Context
+ ctx := c.Request.Context()
+
+ // 此接口不需要鉴权 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
+ service{{ .StructName }}.Get{{.StructName}}Public(ctx)
+ response.OkWithDetailed(gin.H{"info": "不需要鉴权的{{.Description}}接口信息"}, "获取成功", c)
+}
diff --git a/resource/plugin/server/api/enter.go.tpl b/resource/plugin/server/api/enter.go.tpl
new file mode 100644
index 0000000..989fb35
--- /dev/null
+++ b/resource/plugin/server/api/enter.go.tpl
@@ -0,0 +1,6 @@
+package api
+
+var Api = new(api)
+
+type api struct {
+}
diff --git a/resource/plugin/server/config/config.go.tpl b/resource/plugin/server/config/config.go.tpl
new file mode 100644
index 0000000..809bc99
--- /dev/null
+++ b/resource/plugin/server/config/config.go.tpl
@@ -0,0 +1,4 @@
+package config
+
+type Config struct {
+}
diff --git a/resource/plugin/server/gen/gen.go.tpl b/resource/plugin/server/gen/gen.go.tpl
new file mode 100644
index 0000000..5639d4a
--- /dev/null
+++ b/resource/plugin/server/gen/gen.go.tpl
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "gorm.io/gen"
+ "path/filepath"
+)
+
+//go:generate go mod tidy
+//go:generate go mod download
+//go:generate go run gen.go
+func main() {
+ g := gen.NewGenerator(gen.Config{
+ OutPath: filepath.Join("..", "..", "..", "{{ .Package }}", "blender", "model", "dao"),
+ Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
+ })
+ g.ApplyBasic()
+ g.Execute()
+}
diff --git a/resource/plugin/server/initialize/api.go.tpl b/resource/plugin/server/initialize/api.go.tpl
new file mode 100644
index 0000000..dfbea23
--- /dev/null
+++ b/resource/plugin/server/initialize/api.go.tpl
@@ -0,0 +1,12 @@
+package initialize
+
+import (
+ "context"
+ model "{{.Module}}/model/system"
+ "{{.Module}}/plugin/plugin-tool/utils"
+)
+
+func Api(ctx context.Context) {
+ entities := []model.SysApi{}
+ utils.RegisterApis(entities...)
+}
diff --git a/resource/plugin/server/initialize/gorm.go.tpl b/resource/plugin/server/initialize/gorm.go.tpl
new file mode 100644
index 0000000..52c8183
--- /dev/null
+++ b/resource/plugin/server/initialize/gorm.go.tpl
@@ -0,0 +1,17 @@
+package initialize
+
+import (
+ "context"
+ "fmt"
+ "{{.Module}}/global"
+ "github.com/pkg/errors"
+ "go.uber.org/zap"
+)
+
+func Gorm(ctx context.Context) {
+ err := global.GVA_DB.WithContext(ctx).AutoMigrate()
+ if err != nil {
+ err = errors.Wrap(err, "注册表失败!")
+ zap.L().Error(fmt.Sprintf("%+v", err))
+ }
+}
diff --git a/resource/plugin/server/initialize/menu.go.tpl b/resource/plugin/server/initialize/menu.go.tpl
new file mode 100644
index 0000000..8774f35
--- /dev/null
+++ b/resource/plugin/server/initialize/menu.go.tpl
@@ -0,0 +1,12 @@
+package initialize
+
+import (
+ "context"
+ model "{{.Module}}/model/system"
+ "{{.Module}}/plugin/plugin-tool/utils"
+)
+
+func Menu(ctx context.Context) {
+ entities := []model.SysBaseMenu{}
+ utils.RegisterMenus(entities...)
+}
diff --git a/resource/plugin/server/initialize/router.go.tpl b/resource/plugin/server/initialize/router.go.tpl
new file mode 100644
index 0000000..fbf03a3
--- /dev/null
+++ b/resource/plugin/server/initialize/router.go.tpl
@@ -0,0 +1,14 @@
+package initialize
+
+import (
+ "{{.Module}}/global"
+ "{{.Module}}/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func Router(engine *gin.Engine) {
+ public := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("")
+ public.Use()
+ private := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("")
+ private.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
+}
diff --git a/resource/plugin/server/initialize/viper.go.tpl b/resource/plugin/server/initialize/viper.go.tpl
new file mode 100644
index 0000000..e759ad6
--- /dev/null
+++ b/resource/plugin/server/initialize/viper.go.tpl
@@ -0,0 +1,17 @@
+package initialize
+
+import (
+ "fmt"
+ "{{.Module}}/global"
+ "{{.Module}}/plugin/{{ .Package }}/plugin"
+ "github.com/pkg/errors"
+ "go.uber.org/zap"
+)
+
+func Viper() {
+ err := global.GVA_VP.UnmarshalKey("{{ .Package }}", &plugin.Config)
+ if err != nil {
+ err = errors.Wrap(err, "初始化配置文件失败!")
+ zap.L().Error(fmt.Sprintf("%+v", err))
+ }
+}
diff --git a/resource/plugin/server/model/model.go.tpl b/resource/plugin/server/model/model.go.tpl
new file mode 100644
index 0000000..283841c
--- /dev/null
+++ b/resource/plugin/server/model/model.go.tpl
@@ -0,0 +1,76 @@
+{{- if .IsAdd}}
+// 在结构体中新增如下字段
+{{- range .Fields}}
+ {{ GenerateField . }}
+{{- end }}
+
+{{ else }}
+package model
+
+{{- if not .OnlyTemplate}}
+import (
+ {{- if .GvaModel }}
+ "{{.Module}}/global"
+ {{- end }}
+ {{- if or .HasTimer }}
+ "time"
+ {{- end }}
+ {{- if .NeedJSON }}
+ "gorm.io/datatypes"
+ {{- end }}
+)
+{{- end }}
+
+// {{.StructName}} {{.Description}} 结构体
+type {{.StructName}} struct {
+{{- if not .OnlyTemplate}}
+{{- if .GvaModel }}
+ global.GVA_MODEL
+{{- end }}
+{{- range .Fields}}
+ {{ GenerateField . }}
+{{- end }}
+ {{- if .AutoCreateResource }}
+ CreatedBy uint `gorm:"column:created_by;comment:创建者"`
+ UpdatedBy uint `gorm:"column:updated_by;comment:更新者"`
+ DeletedBy uint `gorm:"column:deleted_by;comment:删除者"`
+ {{- end }}
+ {{- if .IsTree }}
+ Children []*{{.StructName}} `json:"children" gorm:"-"` //子节点
+ ParentID int `json:"parentID" gorm:"column:parent_id;comment:父节点"`
+ {{- end }}
+ {{- end }}
+}
+
+{{ if .TableName }}
+// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}}
+func ({{.StructName}}) TableName() string {
+ return "{{.TableName}}"
+}
+{{ end }}
+
+
+{{if .IsTree }}
+// GetChildren 实现TreeNode接口
+func (s *{{.StructName}}) GetChildren() []*{{.StructName}} {
+ return s.Children
+}
+
+// SetChildren 实现TreeNode接口
+func (s *{{.StructName}}) SetChildren(children *{{.StructName}}) {
+ s.Children = append(s.Children, children)
+}
+
+// GetID 实现TreeNode接口
+func (s *{{.StructName}}) GetID() int {
+ return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}})
+}
+
+// GetParentID 实现TreeNode接口
+func (s *{{.StructName}}) GetParentID() int {
+ return s.ParentID
+}
+{{ end }}
+
+
+{{ end }}
diff --git a/resource/plugin/server/model/request/request.go.tpl b/resource/plugin/server/model/request/request.go.tpl
new file mode 100644
index 0000000..60cf677
--- /dev/null
+++ b/resource/plugin/server/model/request/request.go.tpl
@@ -0,0 +1,38 @@
+{{- if .IsAdd}}
+// 在结构体中新增如下字段
+{{- range .Fields}}
+ {{- if ne .FieldSearchType ""}}
+ {{ GenerateSearchField . }}
+ {{- end}}
+{{- end }}
+{{- if .NeedSort}}
+Sort string `json:"sort" form:"sort"`
+Order string `json:"order" form:"order"`
+{{- end}}
+{{- else }}
+package request
+{{- if not .OnlyTemplate}}
+import (
+ "{{.Module}}/model/common/request"
+ {{ if or .HasSearchTimer .GvaModel }}"time"{{ end }}
+)
+{{- end}}
+type {{.StructName}}Search struct{
+{{- if not .OnlyTemplate}}
+
+{{- if .GvaModel }}
+ CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
+{{- end }}
+{{- range .Fields}}
+ {{- if ne .FieldSearchType ""}}
+ {{ GenerateSearchField . }}
+ {{- end}}
+{{- end }}
+ request.PageInfo
+ {{- if .NeedSort}}
+ Sort string `json:"sort" form:"sort"`
+ Order string `json:"order" form:"order"`
+ {{- end}}
+{{- end }}
+}
+{{- end }}
diff --git a/resource/plugin/server/plugin.go.tpl b/resource/plugin/server/plugin.go.tpl
new file mode 100644
index 0000000..255b7af
--- /dev/null
+++ b/resource/plugin/server/plugin.go.tpl
@@ -0,0 +1,26 @@
+package {{ .Package }}
+
+import (
+ "context"
+ "{{.Module}}/plugin/{{ .Package }}/initialize"
+ interfaces "{{.Module}}/utils/plugin/v2"
+ "github.com/gin-gonic/gin"
+)
+
+var _ interfaces.Plugin = (*plugin)(nil)
+
+var Plugin = new(plugin)
+
+type plugin struct{}
+
+// 如果需要配置文件,请到config.Config中填充配置结构,且到下方发放中填入其在config.yaml中的key并添加如下方法
+// initialize.Viper()
+// 安装插件时候自动注册的api数据请到下方法.Api方法中实现并添加如下方法
+// initialize.Api(ctx)
+// 安装插件时候自动注册的api数据请到下方法.Menu方法中实现并添加如下方法
+// initialize.Menu(ctx)
+func (p *plugin) Register(group *gin.Engine) {
+ ctx := context.Background()
+ initialize.Gorm(ctx)
+ initialize.Router(group)
+}
diff --git a/resource/plugin/server/plugin/plugin.go.tpl b/resource/plugin/server/plugin/plugin.go.tpl
new file mode 100644
index 0000000..7e25e07
--- /dev/null
+++ b/resource/plugin/server/plugin/plugin.go.tpl
@@ -0,0 +1,5 @@
+package plugin
+
+import "{{.Module}}/plugin/{{ .Package }}/config"
+
+var Config config.Config
diff --git a/resource/plugin/server/router/enter.go.tpl b/resource/plugin/server/router/enter.go.tpl
new file mode 100644
index 0000000..78517b3
--- /dev/null
+++ b/resource/plugin/server/router/enter.go.tpl
@@ -0,0 +1,6 @@
+package router
+
+var Router = new(router)
+
+type router struct {
+}
diff --git a/resource/plugin/server/router/router.go.tpl b/resource/plugin/server/router/router.go.tpl
new file mode 100644
index 0000000..34bf4d8
--- /dev/null
+++ b/resource/plugin/server/router/router.go.tpl
@@ -0,0 +1,46 @@
+package router
+
+import (
+ {{if .OnlyTemplate }} // {{end}}"{{.Module}}/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+var {{.StructName}} = new({{.Abbreviation}})
+
+type {{.Abbreviation}} struct {}
+
+// Init 初始化 {{.Description}} 路由信息
+func (r *{{.Abbreviation}}) Init(public *gin.RouterGroup, private *gin.RouterGroup) {
+{{- if not .OnlyTemplate }}
+ {
+ group := private.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
+ group.POST("create{{.StructName}}", api{{.StructName}}.Create{{.StructName}}) // 新建{{.Description}}
+ group.DELETE("delete{{.StructName}}", api{{.StructName}}.Delete{{.StructName}}) // 删除{{.Description}}
+ group.DELETE("delete{{.StructName}}ByIds", api{{.StructName}}.Delete{{.StructName}}ByIds) // 批量删除{{.Description}}
+ group.PUT("update{{.StructName}}", api{{.StructName}}.Update{{.StructName}}) // 更新{{.Description}}
+ }
+ {
+ group := private.Group("{{.Abbreviation}}")
+ group.GET("find{{.StructName}}", api{{.StructName}}.Find{{.StructName}}) // 根据ID获取{{.Description}}
+ group.GET("get{{.StructName}}List", api{{.StructName}}.Get{{.StructName}}List) // 获取{{.Description}}列表
+ }
+ {
+ group := public.Group("{{.Abbreviation}}")
+ {{- if .HasDataSource}}
+ group.GET("get{{.StructName}}DataSource", api{{.StructName}}.Get{{.StructName}}DataSource) // 获取{{.Description}}数据源
+ {{- end}}
+ group.GET("get{{.StructName}}Public", api{{.StructName}}.Get{{.StructName}}Public) // {{.Description}}开放接口
+ }
+{{- else}}
+ // {
+ // group := private.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
+ // }
+ // {
+ // group := private.Group("{{.Abbreviation}}")
+ // }
+ {
+ group := public.Group("{{.Abbreviation}}")
+ group.GET("get{{.StructName}}Public", api{{.StructName}}.Get{{.StructName}}Public) // {{.Description}}开放接口
+ }
+{{- end}}
+}
diff --git a/resource/plugin/server/service/enter.go.tpl b/resource/plugin/server/service/enter.go.tpl
new file mode 100644
index 0000000..034facb
--- /dev/null
+++ b/resource/plugin/server/service/enter.go.tpl
@@ -0,0 +1,7 @@
+package service
+
+var Service = new(service)
+
+type service struct {
+}
+
diff --git a/resource/plugin/server/service/service.go.tpl b/resource/plugin/server/service/service.go.tpl
new file mode 100644
index 0000000..9743602
--- /dev/null
+++ b/resource/plugin/server/service/service.go.tpl
@@ -0,0 +1,211 @@
+{{- $db := "" }}
+{{- if eq .BusinessDB "" }}
+ {{- $db = "global.GVA_DB" }}
+{{- else}}
+ {{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }}
+{{- end}}
+
+{{- if .IsAdd}}
+
+// Get{{.StructName}}InfoList 新增搜索语句
+
+ {{ GenerateSearchConditions .Fields }}
+
+// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容
+ {{- range .Fields}}
+ {{- if .Sort}}
+orderMap["{{.ColumnName}}"] = true
+ {{- end}}
+ {{- end}}
+
+
+{{- if .HasDataSource }}
+// Get{{.StructName}}DataSource()方法新增关联语句
+ {{range $key, $value := .DataSourceMap}}
+{{$key}} := make([]map[string]any, 0)
+{{$db}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
+res["{{$key}}"] = {{$key}}
+ {{- end }}
+{{- end }}
+{{- else}}
+package service
+
+import (
+{{- if not .OnlyTemplate }}
+ "context"
+ "{{.Module}}/global"
+ "{{.Module}}/plugin/{{.Package}}/model"
+ {{- if not .IsTree }}
+ "{{.Module}}/plugin/{{.Package}}/model/request"
+ {{- else }}
+ "errors"
+ {{- end }}
+ {{- if .AutoCreateResource }}
+ "gorm.io/gorm"
+ {{- end}}
+{{- if .IsTree }}
+ "{{.Module}}/utils"
+{{- end }}
+{{- end }}
+)
+
+var {{.StructName}} = new({{.Abbreviation}})
+
+type {{.Abbreviation}} struct {}
+
+{{- $db := "" }}
+{{- if eq .BusinessDB "" }}
+ {{- $db = "global.GVA_DB" }}
+{{- else}}
+ {{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }}
+{{- end}}
+{{- if not .OnlyTemplate }}
+// Create{{.StructName}} 创建{{.Description}}记录
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Create{{.StructName}}(ctx context.Context, {{.Abbreviation}} *model.{{.StructName}}) (err error) {
+ err = {{$db}}.Create({{.Abbreviation}}).Error
+ return err
+}
+
+// Delete{{.StructName}} 删除{{.Description}}记录
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Delete{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string{{- if .AutoCreateResource -}},userID uint{{- end -}}) (err error) {
+
+ {{- if .IsTree }}
+ var count int64
+ err = {{$db}}.Find(&model.{{.StructName}}{},"parent_id = ?",{{.PrimaryField.FieldJson}}).Count(&count).Error
+ if count > 0 {
+ return errors.New("此节点存在子节点不允许删除")
+ }
+ if err != nil {
+ return err
+ }
+ {{- end }}
+
+ {{- if .AutoCreateResource }}
+ err = {{$db}}.Transaction(func(tx *gorm.DB) error {
+ if err := tx.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).Update("deleted_by", userID).Error; err != nil {
+ return err
+ }
+ if err = tx.Delete(&model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error; err != nil {
+ return err
+ }
+ return nil
+ })
+ {{- else }}
+ err = {{$db}}.Delete(&model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error
+ {{- end }}
+ return err
+}
+
+// Delete{{.StructName}}ByIds 批量删除{{.Description}}记录
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Delete{{.StructName}}ByIds(ctx context.Context, {{.PrimaryField.FieldJson}}s []string {{- if .AutoCreateResource }},deleted_by uint{{- end}}) (err error) {
+ {{- if .AutoCreateResource }}
+ err = {{$db}}.Transaction(func(tx *gorm.DB) error {
+ if err := tx.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Update("deleted_by", deleted_by).Error; err != nil {
+ return err
+ }
+ if err := tx.Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Delete(&model.{{.StructName}}{}).Error; err != nil {
+ return err
+ }
+ return nil
+ })
+ {{- else}}
+ err = {{$db}}.Delete(&[]model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} in ?",{{.PrimaryField.FieldJson}}s).Error
+ {{- end}}
+ return err
+}
+
+// Update{{.StructName}} 更新{{.Description}}记录
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Update{{.StructName}}(ctx context.Context, {{.Abbreviation}} model.{{.StructName}}) (err error) {
+ err = {{$db}}.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?",{{.Abbreviation}}.{{.PrimaryField.FieldName}}).Updates(&{{.Abbreviation}}).Error
+ return err
+}
+
+// Get{{.StructName}} 根据{{.PrimaryField.FieldJson}}获取{{.Description}}记录
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Get{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string) ({{.Abbreviation}} model.{{.StructName}}, err error) {
+ err = {{$db}}.Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).First(&{{.Abbreviation}}).Error
+ return
+}
+
+
+{{- if .IsTree }}
+// Get{{.StructName}}InfoList 分页获取{{.Description}}记录,Tree模式下不添加分页和搜索
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context) (list []*model.{{.StructName}},err error) {
+ // 创建db
+ db := {{$db}}.Model(&model.{{.StructName}}{})
+ var {{.Abbreviation}}s []*model.{{.StructName}}
+
+ err = db.Find(&{{.Abbreviation}}s).Error
+
+ return utils.BuildTree({{.Abbreviation}}s), err
+}
+{{- else }}
+// Get{{.StructName}}InfoList 分页获取{{.Description}}记录
+// Author [yourname](https://github.com/yourname)
+func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info request.{{.StructName}}Search) (list []model.{{.StructName}}, total int64, err error) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := {{$db}}.Model(&model.{{.StructName}}{})
+ var {{.Abbreviation}}s []model.{{.StructName}}
+ // 如果有条件搜索 下方会自动创建搜索语句
+{{- if .GvaModel }}
+ if len(info.CreatedAtRange) == 2 {
+ db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
+ }
+{{- end }}
+ {{ GenerateSearchConditions .Fields }}
+ err = db.Count(&total).Error
+ if err!=nil {
+ return
+ }
+ {{- if .NeedSort}}
+ var OrderStr string
+ orderMap := make(map[string]bool)
+ {{- if .GvaModel }}
+ orderMap["id"] = true
+ orderMap["created_at"] = true
+ {{- end }}
+ {{- range .Fields}}
+ {{- if .Sort}}
+ orderMap["{{.ColumnName}}"] = true
+ {{- end}}
+ {{- end}}
+ if orderMap[info.Sort] {
+ OrderStr = info.Sort
+ if info.Order == "descending" {
+ OrderStr = OrderStr + " desc"
+ }
+ db = db.Order(OrderStr)
+ }
+ {{- end}}
+
+ if limit != 0 {
+ db = db.Limit(limit).Offset(offset)
+ }
+ err = db.Find(&{{.Abbreviation}}s).Error
+ return {{.Abbreviation}}s, total, err
+}
+{{- end }}
+{{- if .HasDataSource }}
+func (s *{{.Abbreviation}})Get{{.StructName}}DataSource(ctx context.Context) (res map[string][]map[string]any, err error) {
+ res = make(map[string][]map[string]any)
+ {{range $key, $value := .DataSourceMap}}
+ {{$key}} := make([]map[string]any, 0)
+ {{$db}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
+ res["{{$key}}"] = {{$key}}
+ {{- end }}
+ return
+}
+{{- end }}
+{{- end }}
+
+func (s *{{.Abbreviation}})Get{{.StructName}}Public(ctx context.Context) {
+
+}
+{{- end }}
diff --git a/resource/plugin/web/api/api.js.tpl b/resource/plugin/web/api/api.js.tpl
new file mode 100644
index 0000000..0462fde
--- /dev/null
+++ b/resource/plugin/web/api/api.js.tpl
@@ -0,0 +1,127 @@
+import service from '@/utils/request'
+{{- if not .OnlyTemplate}}
+// @Tags {{.StructName}}
+// @Summary 创建{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "创建{{.Description}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
+// @Router /{{.Abbreviation}}/create{{.StructName}} [post]
+export const create{{.StructName}} = (data) => {
+ return service({
+ url: '/{{.Abbreviation}}/create{{.StructName}}',
+ method: 'post',
+ data
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 删除{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "删除{{.Description}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
+export const delete{{.StructName}} = (params) => {
+ return service({
+ url: '/{{.Abbreviation}}/delete{{.StructName}}',
+ method: 'delete',
+ params
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 批量删除{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body request.IdsReq true "批量删除{{.Description}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
+export const delete{{.StructName}}ByIds = (params) => {
+ return service({
+ url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds',
+ method: 'delete',
+ params
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 更新{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "更新{{.Description}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /{{.Abbreviation}}/update{{.StructName}} [put]
+export const update{{.StructName}} = (data) => {
+ return service({
+ url: '/{{.Abbreviation}}/update{{.StructName}}',
+ method: 'put',
+ data
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 用id查询{{.Description}}
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data query model.{{.StructName}} true "用id查询{{.Description}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
+// @Router /{{.Abbreviation}}/find{{.StructName}} [get]
+export const find{{.StructName}} = (params) => {
+ return service({
+ url: '/{{.Abbreviation}}/find{{.StructName}}',
+ method: 'get',
+ params
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 分页获取{{.Description}}列表
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Param data query request.PageInfo true "分页获取{{.Description}}列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
+export const get{{.StructName}}List = (params) => {
+ return service({
+ url: '/{{.Abbreviation}}/get{{.StructName}}List',
+ method: 'get',
+ params
+ })
+}
+
+{{- if .HasDataSource}}
+// @Tags {{.StructName}}
+// @Summary 获取数据源
+// @Security ApiKeyAuth
+// @Accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
+// @Router /{{.Abbreviation}}/find{{.StructName}}DataSource [get]
+export const get{{.StructName}}DataSource = () => {
+ return service({
+ url: '/{{.Abbreviation}}/get{{.StructName}}DataSource',
+ method: 'get',
+ })
+}
+{{- end}}
+{{- end}}
+// @Tags {{.StructName}}
+// @Summary 不需要鉴权的{{.Description}}接口
+// @Accept application/json
+// @Produce application/json
+// @Param data query request.{{.StructName}}Search true "分页获取{{.Description}}列表"
+// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
+// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]
+export const get{{.StructName}}Public = () => {
+ return service({
+ url: '/{{.Abbreviation}}/get{{.StructName}}Public',
+ method: 'get',
+ })
+}
diff --git a/resource/plugin/web/form/form.vue.tpl b/resource/plugin/web/form/form.vue.tpl
new file mode 100644
index 0000000..7d3406a
--- /dev/null
+++ b/resource/plugin/web/form/form.vue.tpl
@@ -0,0 +1,464 @@
+{{- if .IsAdd }}
+// 新增表单中增加如下代码
+{{- range .Fields}}
+ {{- if .Form}}
+
+ {{- if .CheckDataSource}}
+
+
+
+ {{- else }}
+ {{- if eq .FieldType "bool" }}
+
+ {{- end }}
+ {{- if eq .FieldType "string" }}
+ {{- if .DictType}}
+
+
+
+ {{- else }}
+
+ {{- end }}
+ {{- end }}
+ {{- if eq .FieldType "richtext" }}
+
+ {{- end }}
+ {{- if eq .FieldType "json" }}
+ // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
+ {{"{{"}} formData.{{.FieldJson}} {{"}}"}}
+ {{- end }}
+ {{- if eq .FieldType "array" }}
+
+ {{- end }}
+ {{- if eq .FieldType "int" }}
+
+ {{- end }}
+ {{- if eq .FieldType "time.Time" }}
+
+ {{- end }}
+ {{- if eq .FieldType "float64" }}
+
+ {{- end }}
+ {{- if eq .FieldType "enum" }}
+
+
+
+ {{- end }}
+ {{- if eq .FieldType "picture" }}
+
+ {{- end }}
+ {{- if eq .FieldType "pictures" }}
+
+ {{- end }}
+ {{- if eq .FieldType "video" }}
+
+ {{- end }}
+ {{- if eq .FieldType "file" }}
+
+ {{- end }}
+ {{- end }}
+
+ {{- end }}
+ {{- end }}
+
+// 字典增加如下代码
+ {{- range $index, $element := .DictTypes}}
+const {{ $element }}Options = ref([])
+ {{- end }}
+
+// init方法中增加如下调用
+
+{{- range $index, $element := .DictTypes }}
+ {{ $element }}Options.value = await getDictFunc('{{$element}}')
+{{- end }}
+
+// 基础formData结构增加如下字段
+{{- range .Fields}}
+ {{- if .Form}}
+ {{- if eq .FieldType "bool" }}
+{{.FieldJson}}: false,
+ {{- end }}
+ {{- if eq .FieldType "string" }}
+{{.FieldJson}}: '',
+ {{- end }}
+ {{- if eq .FieldType "richtext" }}
+{{.FieldJson}}: '',
+ {{- end }}
+ {{- if eq .FieldType "int" }}
+{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }},
+ {{- end }}
+ {{- if eq .FieldType "time.Time" }}
+{{.FieldJson}}: new Date(),
+ {{- end }}
+ {{- if eq .FieldType "float64" }}
+{{.FieldJson}}: 0,
+ {{- end }}
+ {{- if eq .FieldType "picture" }}
+{{.FieldJson}}: "",
+ {{- end }}
+ {{- if eq .FieldType "video" }}
+{{.FieldJson}}: "",
+ {{- end }}
+ {{- if eq .FieldType "pictures" }}
+{{.FieldJson}}: [],
+ {{- end }}
+ {{- if eq .FieldType "file" }}
+{{.FieldJson}}: [],
+ {{- end }}
+ {{- if eq .FieldType "json" }}
+{{.FieldJson}}: {},
+ {{- end }}
+ {{- if eq .FieldType "array" }}
+{{.FieldJson}}: [],
+ {{- end }}
+ {{- end }}
+ {{- end }}
+// 验证规则中增加如下字段
+
+{{- range .Fields }}
+ {{- if .Form }}
+ {{- if eq .Require true }}
+{{.FieldJson }} : [{
+ required: true,
+ message: '{{ .ErrorText }}',
+ trigger: ['input','blur'],
+},
+ {{- if eq .FieldType "string" }}
+{
+ whitespace: true,
+ message: '不能只输入空格',
+ trigger: ['input', 'blur'],
+}
+ {{- end }}
+],
+ {{- end }}
+ {{- end }}
+ {{- end }}
+
+{{- if .HasDataSource }}
+// 请引用
+get{{.StructName}}DataSource,
+
+// 获取数据源
+const dataSource = ref([])
+const getDataSourceFunc = async()=>{
+ const res = await get{{.StructName}}DataSource()
+ if (res.code === 0) {
+ dataSource.value = res.data
+ }
+}
+getDataSourceFunc()
+{{- end }}
+{{- else }}
+{{- if not .OnlyTemplate }}
+
+
+
+
+
+
+
+{{- else }}
+
+form
+
+
+
+{{- end }}
+{{- end }}
diff --git a/resource/plugin/web/view/view.vue.tpl b/resource/plugin/web/view/view.vue.tpl
new file mode 100644
index 0000000..f5b8547
--- /dev/null
+++ b/resource/plugin/web/view/view.vue.tpl
@@ -0,0 +1,689 @@
+{{- $global := . }}
+{{- $templateID := printf "%s_%s" .Package .StructName }}
+{{- if .IsAdd }}
+// 请在搜索条件中增加如下代码
+{{- range .Fields}}
+ {{- if .FieldSearchType}}
+{{ GenerateSearchFormItem .}}
+ {{ end }}
+{{ end }}
+
+
+// 表格增加如下列代码
+
+{{- range .Fields}}
+ {{- if .Table}}
+ {{ GenerateTableColumn . }}
+ {{- end }}
+{{- end }}
+
+// 新增表单中增加如下代码
+{{- range .Fields}}
+ {{- if .Form}}
+ {{ GenerateFormItem . }}
+ {{- end }}
+{{- end }}
+
+// 查看抽屉中增加如下代码
+
+{{- range .Fields}}
+ {{- if .Desc }}
+ {{ GenerateDescriptionItem . }}
+ {{- end }}
+ {{- end }}
+
+// 字典增加如下代码
+ {{- range $index, $element := .DictTypes}}
+const {{ $element }}Options = ref([])
+ {{- end }}
+
+// setOptions方法中增加如下调用
+
+{{- range $index, $element := .DictTypes }}
+ {{ $element }}Options.value = await getDictFunc('{{$element}}')
+{{- end }}
+
+// 基础formData结构(变量处和关闭表单处)增加如下字段
+{{- range .Fields}}
+ {{- if .Form}}
+ {{ GenerateDefaultFormValue . }}
+ {{- end }}
+ {{- end }}
+// 验证规则中增加如下字段
+
+{{- range .Fields }}
+ {{- if .Form }}
+ {{- if eq .Require true }}
+{{.FieldJson }} : [{
+ required: true,
+ message: '{{ .ErrorText }}',
+ trigger: ['input','blur'],
+},
+ {{- if eq .FieldType "string" }}
+{
+ whitespace: true,
+ message: '不能只输入空格',
+ trigger: ['input', 'blur'],
+}
+ {{- end }}
+],
+ {{- end }}
+ {{- end }}
+ {{- end }}
+
+
+
+{{- if .HasDataSource }}
+// 请引用
+get{{.StructName}}DataSource,
+
+// 获取数据源
+const dataSource = ref({})
+const getDataSourceFunc = async()=>{
+ const res = await get{{.StructName}}DataSource()
+ if (res.code === 0) {
+ dataSource.value = res.data || []
+ }
+}
+getDataSourceFunc()
+{{- end }}
+
+{{- else }}
+
+{{- if not .OnlyTemplate}}
+
+
+ {{- if not .IsTree }}
+
+
+ {{- if .GvaModel }}
+
+
+
+ 创建日期
+
+
+
+
+
+
+
+ {{ end -}}
+ {{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}
+ {{ GenerateSearchFormItem .}}
+ {{ end }}{{ end }}{{ end }}
+
+
+ {{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }}
+ {{ GenerateSearchFormItem .}}
+ {{ end }}{{ end }}{{ end }}
+
+
+
+ 查询
+ 重置
+ 展开
+ 收起
+
+
+
+ {{- end }}
+
+
+ 新增
+ 删除
+ {{ if .HasExcel -}}
+
+
+
+ {{- end }}
+
+
+
+ {{ if .GvaModel }}
+
+ {{ "{{ formatDate(scope.row.CreatedAt) }}" }}
+
+ {{ end }}
+ {{- range .Fields}}
+ {{- if .Table}}
+ {{ GenerateTableColumn . }}
+ {{- end }}
+ {{- end }}
+
+
+ {{- if .IsTree }}
+ 新增子节点
+ {{- end }}
+ 查看
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
{{"{{"}}type==='create'?'新增':'编辑'{{"}}"}}
+
+ 确 定
+ 取 消
+
+
+
+
+
+ {{- if .IsTree }}
+
+
+
+ {{- end }}
+ {{- range .Fields}}
+ {{- if .Form}}
+ {{ GenerateFormItem . }}
+ {{- end }}
+ {{- end }}
+
+
+
+
+
+ {{- if .IsTree }}
+
+
+
+ {{- end }}
+ {{- range .Fields}}
+ {{- if .Desc }}
+ {{ GenerateDescriptionItem . }}
+ {{- end }}
+ {{- end }}
+
+
+
+
+
+
+
+
+
+{{- else}}
+
+form
+
+
+
+{{- end }}
+
+{{- end }}
diff --git a/router/system/enter.go b/router/system/enter.go
index c652d10..9d65c9c 100644
--- a/router/system/enter.go
+++ b/router/system/enter.go
@@ -19,6 +19,7 @@ type RouterGroup struct {
AuthorityBtnRouter
SysExportTemplateRouter
SysParamsRouter
+ SysVersionRouter
}
var (
@@ -41,4 +42,5 @@ var (
dictionaryDetailApi = api.ApiGroupApp.SystemApiGroup.DictionaryDetailApi
autoCodeTemplateApi = api.ApiGroupApp.SystemApiGroup.AutoCodeTemplateApi
exportTemplateApi = api.ApiGroupApp.SystemApiGroup.SysExportTemplateApi
+ sysVersionApi = api.ApiGroupApp.SystemApiGroup.SysVersionApi
)
diff --git a/router/system/sys_auto_code.go b/router/system/sys_auto_code.go
index e25e1ce..ef89245 100644
--- a/router/system/sys_auto_code.go
+++ b/router/system/sys_auto_code.go
@@ -19,6 +19,11 @@ func (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPubli
autoCodeRouter.POST("createTemp", autoCodeTemplateApi.Create) // 创建自动化代码
autoCodeRouter.POST("addFunc", autoCodeTemplateApi.AddFunc) // 为代码插入方法
}
+ {
+ autoCodeRouter.POST("mcp", autoCodeTemplateApi.MCP) // 自动创建Mcp Tool模板
+ autoCodeRouter.POST("mcpList", autoCodeTemplateApi.MCPList) // 获取MCP ToolList
+ autoCodeRouter.POST("mcpTest", autoCodeTemplateApi.MCPTest) // MCP 工具测试
+ }
{
autoCodeRouter.POST("getPackage", autoCodePackageApi.All) // 获取package包
autoCodeRouter.POST("delPackage", autoCodePackageApi.Delete) // 删除package包
diff --git a/router/system/sys_operation_record.go b/router/system/sys_operation_record.go
index 11b841d..d158d5e 100644
--- a/router/system/sys_operation_record.go
+++ b/router/system/sys_operation_record.go
@@ -9,7 +9,6 @@ type OperationRecordRouter struct{}
func (s *OperationRecordRouter) InitSysOperationRecordRouter(Router *gin.RouterGroup) {
operationRecordRouter := Router.Group("sysOperationRecord")
{
- operationRecordRouter.POST("createSysOperationRecord", operationRecordApi.CreateSysOperationRecord) // 新建SysOperationRecord
operationRecordRouter.DELETE("deleteSysOperationRecord", operationRecordApi.DeleteSysOperationRecord) // 删除SysOperationRecord
operationRecordRouter.DELETE("deleteSysOperationRecordByIds", operationRecordApi.DeleteSysOperationRecordByIds) // 批量删除SysOperationRecord
operationRecordRouter.GET("findSysOperationRecord", operationRecordApi.FindSysOperationRecord) // 根据ID获取SysOperationRecord
diff --git a/router/system/sys_user.go b/router/system/sys_user.go
index 32e0130..cb3187a 100644
--- a/router/system/sys_user.go
+++ b/router/system/sys_user.go
@@ -18,7 +18,7 @@ func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup) {
userRouter.PUT("setUserInfo", baseApi.SetUserInfo) // 设置用户信息
userRouter.PUT("setSelfInfo", baseApi.SetSelfInfo) // 设置自身信息
userRouter.POST("setUserAuthorities", baseApi.SetUserAuthorities) // 设置用户权限组
- userRouter.POST("resetPassword", baseApi.ResetPassword) // 设置用户权限组
+ userRouter.POST("resetPassword", baseApi.ResetPassword) // 重置用户密码
userRouter.PUT("setSelfSetting", baseApi.SetSelfSetting) // 用户界面配置
}
{
diff --git a/router/system/sys_version.go b/router/system/sys_version.go
new file mode 100644
index 0000000..69fef7d
--- /dev/null
+++ b/router/system/sys_version.go
@@ -0,0 +1,25 @@
+package system
+
+import (
+ "git.echol.cn/loser/lckt/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+type SysVersionRouter struct{}
+
+// InitSysVersionRouter 初始化 版本管理 路由信息
+func (s *SysVersionRouter) InitSysVersionRouter(Router *gin.RouterGroup) {
+ sysVersionRouter := Router.Group("sysVersion").Use(middleware.OperationRecord())
+ sysVersionRouterWithoutRecord := Router.Group("sysVersion")
+ {
+ sysVersionRouter.DELETE("deleteSysVersion", sysVersionApi.DeleteSysVersion) // 删除版本管理
+ sysVersionRouter.DELETE("deleteSysVersionByIds", sysVersionApi.DeleteSysVersionByIds) // 批量删除版本管理
+ sysVersionRouter.POST("exportVersion", sysVersionApi.ExportVersion) // 导出版本数据
+ sysVersionRouter.POST("importVersion", sysVersionApi.ImportVersion) // 导入版本数据
+ }
+ {
+ sysVersionRouterWithoutRecord.GET("findSysVersion", sysVersionApi.FindSysVersion) // 根据ID获取版本管理
+ sysVersionRouterWithoutRecord.GET("getSysVersionList", sysVersionApi.GetSysVersionList) // 获取版本管理列表
+ sysVersionRouterWithoutRecord.GET("downloadVersionJson", sysVersionApi.DownloadVersionJson) // 下载版本JSON数据
+ }
+}
diff --git a/service/system/auto_code_mcp.go b/service/system/auto_code_mcp.go
new file mode 100644
index 0000000..cbfbfd8
--- /dev/null
+++ b/service/system/auto_code_mcp.go
@@ -0,0 +1,45 @@
+package system
+
+import (
+ "context"
+ "git.echol.cn/loser/lckt/global"
+ "git.echol.cn/loser/lckt/model/system/request"
+ "git.echol.cn/loser/lckt/utils"
+ "git.echol.cn/loser/lckt/utils/autocode"
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+func (s *autoCodeTemplate) CreateMcp(ctx context.Context, info request.AutoMcpTool) (toolFilePath string, err error) {
+ mcpTemplatePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", "mcp", "tools.tpl")
+ mcpToolPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "mcp")
+
+ var files *template.Template
+
+ templateName := filepath.Base(mcpTemplatePath)
+
+ files, err = template.New(templateName).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(mcpTemplatePath)
+ if err != nil {
+ return
+ }
+
+ fileName := utils.HumpToUnderscore(info.Name)
+
+ toolFilePath = filepath.Join(mcpToolPath, fileName+".go")
+
+ f, err := os.Create(toolFilePath)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+
+ // 执行模板,将内容写入文件
+ err = files.Execute(f, info)
+ if err != nil {
+ return
+ }
+
+ return
+
+}
diff --git a/service/system/auto_code_package.go b/service/system/auto_code_package.go
index 8a99fab..477036f 100644
--- a/service/system/auto_code_package.go
+++ b/service/system/auto_code_package.go
@@ -3,20 +3,20 @@ package system
import (
"context"
"fmt"
- "go/token"
- "os"
- "path/filepath"
- "strings"
- "text/template"
-
"git.echol.cn/loser/lckt/global"
common "git.echol.cn/loser/lckt/model/common/request"
model "git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/model/system/request"
"git.echol.cn/loser/lckt/utils"
"git.echol.cn/loser/lckt/utils/ast"
+ "git.echol.cn/loser/lckt/utils/autocode"
"github.com/pkg/errors"
+ "go/token"
"gorm.io/gorm"
+ "os"
+ "path/filepath"
+ "strings"
+ "text/template"
)
var AutoCodePackage = new(autoCodePackage)
@@ -59,7 +59,7 @@ func (s *autoCodePackage) Create(ctx context.Context, info *request.SysAutoCodeP
}
for key, value := range creates { // key 为 模版绝对路径
var files *template.Template
- files, err = template.ParseFiles(key)
+ files, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", key)
}
@@ -114,6 +114,20 @@ func (s *autoCodePackage) Delete(ctx context.Context, info common.GetById) error
return nil
}
+// DeleteByNames
+// @author: [piexlmax](https://github.com/piexlmax)
+// @author: [SliverHorn](https://github.com/SliverHorn)
+func (s *autoCodePackage) DeleteByNames(ctx context.Context, names []string) error {
+ if len(names) == 0 {
+ return nil
+ }
+ err := global.GVA_DB.WithContext(ctx).Where("package_name IN ?", names).Delete(&model.SysAutoCodePackage{}).Error
+ if err != nil {
+ return errors.Wrap(err, "删除失败!")
+ }
+ return nil
+}
+
// All 获取所有包
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
@@ -233,6 +247,9 @@ func (s *autoCodePackage) Templates(ctx context.Context) ([]string, error) {
if entries[i].Name() == "preview" {
continue
} // preview 为预览代码生成器的代码
+ if entries[i].Name() == "mcp" {
+ continue
+ } // preview 为mcp生成器的代码
templates = append(templates, entries[i].Name())
}
}
@@ -267,7 +284,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
three := filepath.Join(second, secondDirs[j].Name())
if !secondDirs[j].IsDir() {
ext := filepath.Ext(secondDirs[j].Name())
- if ext != ".template" && ext != ".tpl" {
+ if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", three)
}
name := strings.TrimSuffix(secondDirs[j].Name(), ext)
@@ -301,7 +318,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
- if ext != ".template" && ext != ".tpl" {
+ if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
@@ -473,7 +490,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
- if ext != ".template" && ext != ".tpl" {
+ if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
gen := strings.Index(threeDirs[k].Name(), "gen")
@@ -557,7 +574,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", five)
}
ext := filepath.Ext(five)
- if ext != ".template" && ext != ".tpl" {
+ if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", five)
}
hasRequest := strings.Index(fourDirs[l].Name(), "request")
@@ -573,7 +590,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
continue
}
ext := filepath.Ext(threeDirs[k].Name())
- if ext != ".template" && ext != ".tpl" {
+ if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
hasModel := strings.Index(threeDirs[k].Name(), "model")
@@ -634,7 +651,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
- if ext != ".template" && ext != ".tpl" {
+ if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
diff --git a/service/system/auto_code_package_test.go b/service/system/auto_code_package_test.go
index 3956b58..57fbe37 100644
--- a/service/system/auto_code_package_test.go
+++ b/service/system/auto_code_package_test.go
@@ -2,10 +2,11 @@ package system
import (
"context"
- model "git.echol.cn/loser/lckt/model/system"
- "git.echol.cn/loser/lckt/model/system/request"
"reflect"
"testing"
+
+ model "git.echol.cn/loser/lckt/model/system"
+ "git.echol.cn/loser/lckt/model/system/request"
)
func Test_autoCodePackage_Create(t *testing.T) {
@@ -53,9 +54,10 @@ func Test_autoCodePackage_Create(t *testing.T) {
func Test_autoCodePackage_templates(t *testing.T) {
type args struct {
- ctx context.Context
- entity model.SysAutoCodePackage
- info request.AutoCode
+ ctx context.Context
+ entity model.SysAutoCodePackage
+ info request.AutoCode
+ isPackage bool
}
tests := []struct {
name string
@@ -78,6 +80,7 @@ func Test_autoCodePackage_templates(t *testing.T) {
Abbreviation: "user",
HumpPackageName: "user",
},
+ isPackage: false,
},
wantErr: false,
},
@@ -85,7 +88,7 @@ func Test_autoCodePackage_templates(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &autoCodePackage{}
- gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info)
+ gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info, tt.args.isPackage)
if (err != nil) != tt.wantErr {
t.Errorf("templates() error = %v, wantErr %v", err, tt.wantErr)
return
diff --git a/service/system/auto_code_plugin.go b/service/system/auto_code_plugin.go
index 64c5a79..fb5f99d 100644
--- a/service/system/auto_code_plugin.go
+++ b/service/system/auto_code_plugin.go
@@ -9,7 +9,7 @@ import (
"git.echol.cn/loser/lckt/model/system/request"
"git.echol.cn/loser/lckt/utils"
"git.echol.cn/loser/lckt/utils/ast"
- "github.com/mholt/archiver/v4"
+ "github.com/mholt/archives"
cp "github.com/otiai10/copy"
"github.com/pkg/errors"
"go.uber.org/zap"
@@ -154,7 +154,7 @@ func (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) {
fileName := plugName + ".zip"
// 创建一个新的zip文件
- files, err := archiver.FilesFromDisk(nil, map[string]string{
+ files, err := archives.FilesFromDisk(context.Background(), nil, map[string]string{
webPath: plugName + "/web/plugin/" + plugName,
serverPath: plugName + "/server/plugin/" + plugName,
})
@@ -168,8 +168,9 @@ func (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) {
// we can use the CompressedArchive type to gzip a tarball
// (compression is not required; you could use Tar directly)
- format := archiver.Archive{
- Archival: archiver.Zip{},
+ format := archives.CompressedArchive{
+ //Compression: archives.Gz{},
+ Archival: archives.Zip{},
}
// create the archive
@@ -207,7 +208,8 @@ func (s *autoCodePlugin) InitMenu(menuInfo request.InitMenu) (err error) {
},
}
- err = global.GVA_DB.Find(&menus, "id in (?)", menuInfo.Menus).Error
+ // 查询菜单及其关联的参数和按钮
+ err = global.GVA_DB.Preload("Parameters").Preload("MenuBtn").Find(&menus, "id in (?)", menuInfo.Menus).Error
if err != nil {
return err
}
diff --git a/service/system/auto_code_template.go b/service/system/auto_code_template.go
index b02a6b3..711de74 100644
--- a/service/system/auto_code_template.go
+++ b/service/system/auto_code_template.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "git.echol.cn/loser/lckt/utils/autocode"
"go/ast"
"go/format"
"go/parser"
@@ -224,7 +225,7 @@ func (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode,
code := make(map[string]strings.Builder)
for key, create := range templates {
var files *template.Template
- files, err = template.ParseFiles(key)
+ files, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]读取模版文件失败!", key)
}
@@ -322,7 +323,7 @@ func (s *autoCodeTemplate) GetApiAndServer(info request.AutoFunc) (map[string]st
func (s *autoCodeTemplate) getTemplateStr(t string, info request.AutoFunc) (string, error) {
tempPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", "function", t+".tpl")
- files, err := template.ParseFiles(tempPath)
+ files, err := template.New(filepath.Base(tempPath)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(tempPath)
if err != nil {
return "", errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", tempPath)
}
diff --git a/service/system/enter.go b/service/system/enter.go
index 634cd00..d91f279 100644
--- a/service/system/enter.go
+++ b/service/system/enter.go
@@ -17,6 +17,7 @@ type ServiceGroup struct {
AuthorityBtnService
SysExportTemplateService
SysParamsService
+ SysVersionService
AutoCodePlugin autoCodePlugin
AutoCodePackage autoCodePackage
AutoCodeHistory autoCodeHistory
diff --git a/service/system/jwt_black_list.go b/service/system/jwt_black_list.go
index 7b0094e..95242f0 100644
--- a/service/system/jwt_black_list.go
+++ b/service/system/jwt_black_list.go
@@ -7,7 +7,6 @@ import (
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
- "git.echol.cn/loser/lckt/utils"
)
type JwtService struct{}
@@ -29,20 +28,6 @@ func (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err
return
}
-//@author: [piexlmax](https://github.com/piexlmax)
-//@function: IsBlacklist
-//@description: 判断JWT是否在黑名单内部
-//@param: jwt string
-//@return: bool
-
-func (jwtService *JwtService) IsBlacklist(jwt string) bool {
- _, ok := global.BlackCache.Get(jwt)
- return ok
- // err := global.GVA_DB.Where("jwt = ?", jwt).First(&system.JwtBlacklist{}).Error
- // isNotFound := errors.Is(err, gorm.ErrRecordNotFound)
- // return !isNotFound
-}
-
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetRedisJWT
//@description: 从redis取jwt
@@ -54,23 +39,6 @@ func (jwtService *JwtService) GetRedisJWT(userName string) (redisJWT string, err
return redisJWT, err
}
-//@author: [piexlmax](https://github.com/piexlmax)
-//@function: SetRedisJWT
-//@description: jwt存入redis并设置过期时间
-//@param: jwt string, userName string
-//@return: err error
-
-func (jwtService *JwtService) SetRedisJWT(jwt string, userName string) (err error) {
- // 此处过期时间等于jwt过期时间
- dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
- if err != nil {
- return err
- }
- timer := dr
- err = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err()
- return err
-}
-
func LoadAll() {
var data []string
err := global.GVA_DB.Model(&system.JwtBlacklist{}).Select("jwt").Find(&data).Error
diff --git a/service/system/sys_authority.go b/service/system/sys_authority.go
index 5ef1a0b..d751428 100644
--- a/service/system/sys_authority.go
+++ b/service/system/sys_authority.go
@@ -326,5 +326,8 @@ func (authorityService *AuthorityService) findChildrenAuthority(authority *syste
func (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint) (parentID uint, err error) {
var authority system.SysAuthority
err = global.GVA_DB.Where("authority_id = ?", authorityID).First(&authority).Error
- return *authority.ParentId, err
+ if err != nil {
+ return
+ }
+ return *authority.ParentId, nil
}
diff --git a/service/system/sys_auto_code_oracle.go b/service/system/sys_auto_code_oracle.go
index a8abb86..1c4254f 100644
--- a/service/system/sys_auto_code_oracle.go
+++ b/service/system/sys_auto_code_oracle.go
@@ -64,7 +64,7 @@ WHERE
lower(a.table_name) = ?
AND lower(a.OWNER) = ?
ORDER BY
- a.COLUMN_ID;
+ a.COLUMN_ID
`
err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error
diff --git a/service/system/sys_base_menu.go b/service/system/sys_base_menu.go
index f51870e..bbe72db 100644
--- a/service/system/sys_base_menu.go
+++ b/service/system/sys_base_menu.go
@@ -72,6 +72,7 @@ func (baseMenuService *BaseMenuService) UpdateBaseMenu(menu system.SysBaseMenu)
var oldMenu system.SysBaseMenu
upDateMap := make(map[string]interface{})
upDateMap["keep_alive"] = menu.KeepAlive
+ upDateMap["transition_type"] = menu.TransitionType
upDateMap["close_tab"] = menu.CloseTab
upDateMap["default_menu"] = menu.DefaultMenu
upDateMap["parent_id"] = menu.ParentId
diff --git a/service/system/sys_casbin.go b/service/system/sys_casbin.go
index 7aa8b93..2e3cb67 100644
--- a/service/system/sys_casbin.go
+++ b/service/system/sys_casbin.go
@@ -3,17 +3,14 @@ package system
import (
"errors"
"strconv"
- "sync"
"gorm.io/gorm"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system/request"
- "github.com/casbin/casbin/v2"
- "github.com/casbin/casbin/v2/model"
+ "git.echol.cn/loser/lckt/utils"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
- "go.uber.org/zap"
)
//@author: [piexlmax](https://github.com/piexlmax)
@@ -68,7 +65,7 @@ func (casbinService *CasbinService) UpdateCasbin(adminAuthorityID, AuthorityID u
if len(rules) == 0 {
return nil
} // 设置空权限无需调用 AddPolicies 方法
- e := casbinService.Casbin()
+ e := utils.GetCasbin()
success, _ := e.AddPolicies(rules)
if !success {
return errors.New("存在相同api,添加失败,请联系管理员")
@@ -91,7 +88,7 @@ func (casbinService *CasbinService) UpdateCasbinApi(oldPath string, newPath stri
return err
}
- e := casbinService.Casbin()
+ e := utils.GetCasbin()
return e.LoadPolicy()
}
@@ -102,7 +99,7 @@ func (casbinService *CasbinService) UpdateCasbinApi(oldPath string, newPath stri
//@return: pathMaps []request.CasbinInfo
func (casbinService *CasbinService) GetPolicyPathByAuthorityId(AuthorityID uint) (pathMaps []request.CasbinInfo) {
- e := casbinService.Casbin()
+ e := utils.GetCasbin()
authorityId := strconv.Itoa(int(AuthorityID))
list, _ := e.GetFilteredPolicy(0, authorityId)
for _, v := range list {
@@ -121,7 +118,7 @@ func (casbinService *CasbinService) GetPolicyPathByAuthorityId(AuthorityID uint)
//@return: bool
func (casbinService *CasbinService) ClearCasbin(v int, p ...string) bool {
- e := casbinService.Casbin()
+ e := utils.GetCasbin()
success, _ := e.RemoveFilteredPolicy(v, p...)
return success
}
@@ -170,52 +167,7 @@ func (casbinService *CasbinService) AddPolicies(db *gorm.DB, rules [][]string) e
}
func (casbinService *CasbinService) FreshCasbin() (err error) {
- e := casbinService.Casbin()
+ e := utils.GetCasbin()
err = e.LoadPolicy()
return err
}
-
-//@author: [piexlmax](https://github.com/piexlmax)
-//@function: Casbin
-//@description: 持久化到数据库 引入自定义规则
-//@return: *casbin.Enforcer
-
-var (
- syncedCachedEnforcer *casbin.SyncedCachedEnforcer
- once sync.Once
-)
-
-func (casbinService *CasbinService) Casbin() *casbin.SyncedCachedEnforcer {
- once.Do(func() {
- a, err := gormadapter.NewAdapterByDB(global.GVA_DB)
- if err != nil {
- zap.L().Error("适配数据库失败请检查casbin表是否为InnoDB引擎!", zap.Error(err))
- return
- }
- text := `
- [request_definition]
- r = sub, obj, act
-
- [policy_definition]
- p = sub, obj, act
-
- [role_definition]
- g = _, _
-
- [policy_effect]
- e = some(where (p.eft == allow))
-
- [matchers]
- m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
- `
- m, err := model.NewModelFromString(text)
- if err != nil {
- zap.L().Error("字符串加载模型失败!", zap.Error(err))
- return
- }
- syncedCachedEnforcer, _ = casbin.NewSyncedCachedEnforcer(m, a)
- syncedCachedEnforcer.SetExpireTime(60 * 60)
- _ = syncedCachedEnforcer.LoadPolicy()
- })
- return syncedCachedEnforcer
-}
diff --git a/service/system/sys_export_template.go b/service/system/sys_export_template.go
index 158c250..886a2d7 100644
--- a/service/system/sys_export_template.go
+++ b/service/system/sys_export_template.go
@@ -3,6 +3,7 @@ package system
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"mime/multipart"
"net/url"
@@ -127,6 +128,11 @@ func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateIn
// ExportExcel 导出Excel
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) {
+ var params = values.Get("params")
+ paramsValues, err := url.ParseQuery(params)
+ if err != nil {
+ return nil, "", fmt.Errorf("解析 params 参数失败: %v", err)
+ }
var template system.SysExportTemplate
err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error
if err != nil {
@@ -175,10 +181,38 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
db = db.Select(selects).Table(template.TableName)
+ filterDeleted := false
+
+ filterParam := paramsValues.Get("filterDeleted")
+ if filterParam == "true" {
+ filterDeleted = true
+ }
+
+ if filterDeleted {
+ // 自动过滤主表的软删除
+ db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
+
+ // 过滤关联表的软删除(如果有)
+ if len(template.JoinTemplate) > 0 {
+ for _, join := range template.JoinTemplate {
+ // 检查关联表是否有deleted_at字段
+ hasDeletedAt := sysExportTemplateService.hasDeletedAtColumn(join.Table)
+ if hasDeletedAt {
+ db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
+ }
+ }
+ }
+ }
+
if len(template.Conditions) > 0 {
for _, condition := range template.Conditions {
sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
- value := values.Get(condition.From)
+ value := paramsValues.Get(condition.From)
+
+ if condition.Operator == "IN" || condition.Operator == "NOT IN" {
+ sql = fmt.Sprintf("%s %s (?)", condition.Column, condition.Operator)
+ }
+
if value != "" {
if condition.Operator == "LIKE" {
value = "%" + value + "%"
@@ -188,7 +222,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
}
}
// 通过参数传入limit
- limit := values.Get("limit")
+ limit := paramsValues.Get("limit")
if limit != "" {
l, e := strconv.Atoi(limit)
if e == nil {
@@ -201,7 +235,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
}
// 通过参数传入offset
- offset := values.Get("offset")
+ offset := paramsValues.Get("offset")
if offset != "" {
o, e := strconv.Atoi(offset)
if e == nil {
@@ -224,7 +258,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
}
// 通过参数传入order
- order := values.Get("order")
+ order := paramsValues.Get("order")
if order == "" && template.Order != "" {
// 如果没有order入参,这里会使用模板的默认排序
@@ -281,7 +315,17 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
}
for i, row := range rows {
for j, colCell := range row {
- sErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(j+1), i+1), colCell)
+ cell := fmt.Sprintf("%s%d", getColumnName(j+1), i+1)
+
+ var sErr error
+ if v, err := strconv.ParseFloat(colCell, 64); err == nil {
+ sErr = f.SetCellValue("Sheet1", cell, v)
+ } else if v, err := strconv.ParseInt(colCell, 10, 64); err == nil {
+ sErr = f.SetCellValue("Sheet1", cell, v)
+ } else {
+ sErr = f.SetCellValue("Sheet1", cell, colCell)
+ }
+
if sErr != nil {
return nil, "", sErr
}
@@ -344,6 +388,13 @@ func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templat
return file, template.Name, nil
}
+// 辅助函数:检查表是否有deleted_at列
+func (s *SysExportTemplateService) hasDeletedAtColumn(tableName string) bool {
+ var count int64
+ global.GVA_DB.Raw("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = 'deleted_at'", tableName).Count(&count)
+ return count > 0
+}
+
// ImportExcel 导入Excel
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID string, file *multipart.FileHeader) (err error) {
@@ -368,6 +419,9 @@ func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID
if err != nil {
return err
}
+ if len(rows) < 2 {
+ return errors.New("Excel data is not enough.\nIt should contain title row and data")
+ }
var templateInfoMap = make(map[string]string)
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
@@ -387,11 +441,17 @@ func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID
return db.Transaction(func(tx *gorm.DB) error {
excelTitle := rows[0]
+ for i, str := range excelTitle {
+ excelTitle[i] = strings.TrimSpace(str)
+ }
values := rows[1:]
items := make([]map[string]interface{}, 0, len(values))
for _, row := range values {
var item = make(map[string]interface{})
for ii, value := range row {
+ if _, ok := titleKeyMap[excelTitle[ii]]; !ok {
+ continue // excel中多余的标题,在模板信息中没有对应的字段,因此key为空,必须跳过
+ }
key := titleKeyMap[excelTitle[ii]]
item[key] = value
}
diff --git a/service/system/sys_menu.go b/service/system/sys_menu.go
index cfaaed8..3d86e77 100644
--- a/service/system/sys_menu.go
+++ b/service/system/sys_menu.go
@@ -134,10 +134,52 @@ func (menuService *MenuService) getBaseChildrenList(menu *system.SysBaseMenu, tr
//@return: error
func (menuService *MenuService) AddBaseMenu(menu system.SysBaseMenu) error {
- if !errors.Is(global.GVA_DB.Where("name = ?", menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
- return errors.New("存在重复name,请修改name")
- }
- return global.GVA_DB.Create(&menu).Error
+ return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+ // 检查name是否重复
+ if !errors.Is(tx.Where("name = ?", menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
+ return errors.New("存在重复name,请修改name")
+ }
+
+ if menu.ParentId != 0 {
+ // 检查父菜单是否存在
+ var parentMenu system.SysBaseMenu
+ if err := tx.First(&parentMenu, menu.ParentId).Error; err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return errors.New("父菜单不存在")
+ }
+ return err
+ }
+
+ // 检查父菜单下现有子菜单数量
+ var existingChildrenCount int64
+ err := tx.Model(&system.SysBaseMenu{}).Where("parent_id = ?", menu.ParentId).Count(&existingChildrenCount).Error
+ if err != nil {
+ return err
+ }
+
+ // 如果父菜单原本是叶子菜单(没有子菜单),现在要变成枝干菜单,需要清空其权限分配
+ if existingChildrenCount == 0 {
+ // 检查父菜单是否被其他角色设置为首页
+ var defaultRouterCount int64
+ err := tx.Model(&system.SysAuthority{}).Where("default_router = ?", parentMenu.Name).Count(&defaultRouterCount).Error
+ if err != nil {
+ return err
+ }
+ if defaultRouterCount > 0 {
+ return errors.New("父菜单已被其他角色的首页占用,请先释放父菜单的首页权限")
+ }
+
+ // 清空父菜单的所有权限分配
+ err = tx.Where("sys_base_menu_id = ?", menu.ParentId).Delete(&system.SysAuthorityMenu{}).Error
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ // 创建菜单
+ return tx.Create(&menu).Error
+ })
}
//@author: [piexlmax](https://github.com/piexlmax)
diff --git a/service/system/sys_operation_record.go b/service/system/sys_operation_record.go
index c96ba68..589711d 100644
--- a/service/system/sys_operation_record.go
+++ b/service/system/sys_operation_record.go
@@ -17,11 +17,6 @@ type OperationRecordService struct{}
var OperationRecordServiceApp = new(OperationRecordService)
-func (operationRecordService *OperationRecordService) CreateSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) {
- err = global.GVA_DB.Create(&sysOperationRecord).Error
- return err
-}
-
//@author: [granty1](https://github.com/granty1)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteSysOperationRecordByIds
diff --git a/service/system/sys_user.go b/service/system/sys_user.go
index 0790a03..ab3e0e1 100644
--- a/service/system/sys_user.go
+++ b/service/system/sys_user.go
@@ -3,9 +3,10 @@ package system
import (
"errors"
"fmt"
+ "time"
+
"git.echol.cn/loser/lckt/model/common"
systemReq "git.echol.cn/loser/lckt/model/system/request"
- "time"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
@@ -63,20 +64,20 @@ func (userService *UserService) Login(u *system.SysUser) (userInter *system.SysU
//@function: ChangePassword
//@description: 修改用户密码
//@param: u *model.SysUser, newPassword string
-//@return: userInter *model.SysUser,err error
+//@return: err error
-func (userService *UserService) ChangePassword(u *system.SysUser, newPassword string) (userInter *system.SysUser, err error) {
+func (userService *UserService) ChangePassword(u *system.SysUser, newPassword string) (err error) {
var user system.SysUser
- if err = global.GVA_DB.Where("id = ?", u.ID).First(&user).Error; err != nil {
- return nil, err
+ err = global.GVA_DB.Select("id, password").Where("id = ?", u.ID).First(&user).Error
+ if err != nil {
+ return err
}
if ok := utils.BcryptCheck(u.Password, user.Password); !ok {
- return nil, errors.New("原密码错误")
+ return errors.New("原密码错误")
}
- user.Password = utils.BcryptHash(newPassword)
- err = global.GVA_DB.Save(&user).Error
- return &user, err
-
+ pwd := utils.BcryptHash(newPassword)
+ err = global.GVA_DB.Model(&user).Update("password", pwd).Error
+ return err
}
//@author: [piexlmax](https://github.com/piexlmax)
@@ -311,7 +312,7 @@ func (userService *UserService) FindUserByUuid(uuid string) (user *system.SysUse
//@param: ID uint
//@return: err error
-func (userService *UserService) ResetPassword(ID uint) (err error) {
- err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", ID).Update("password", utils.BcryptHash("123456")).Error
+func (userService *UserService) ResetPassword(ID uint, password string) (err error) {
+ err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", ID).Update("password", utils.BcryptHash(password)).Error
return err
}
diff --git a/service/system/sys_version.go b/service/system/sys_version.go
new file mode 100644
index 0000000..2cf8d87
--- /dev/null
+++ b/service/system/sys_version.go
@@ -0,0 +1,230 @@
+package system
+
+import (
+ "context"
+ "git.echol.cn/loser/lckt/global"
+ "git.echol.cn/loser/lckt/model/system"
+ systemReq "git.echol.cn/loser/lckt/model/system/request"
+ "gorm.io/gorm"
+)
+
+type SysVersionService struct{}
+
+// CreateSysVersion 创建版本管理记录
+// Author [yourname](https://github.com/yourname)
+func (sysVersionService *SysVersionService) CreateSysVersion(ctx context.Context, sysVersion *system.SysVersion) (err error) {
+ err = global.GVA_DB.Create(sysVersion).Error
+ return err
+}
+
+// DeleteSysVersion 删除版本管理记录
+// Author [yourname](https://github.com/yourname)
+func (sysVersionService *SysVersionService) DeleteSysVersion(ctx context.Context, ID string) (err error) {
+ err = global.GVA_DB.Delete(&system.SysVersion{}, "id = ?", ID).Error
+ return err
+}
+
+// DeleteSysVersionByIds 批量删除版本管理记录
+// Author [yourname](https://github.com/yourname)
+func (sysVersionService *SysVersionService) DeleteSysVersionByIds(ctx context.Context, IDs []string) (err error) {
+ err = global.GVA_DB.Where("id in ?", IDs).Delete(&system.SysVersion{}).Error
+ return err
+}
+
+// GetSysVersion 根据ID获取版本管理记录
+// Author [yourname](https://github.com/yourname)
+func (sysVersionService *SysVersionService) GetSysVersion(ctx context.Context, ID string) (sysVersion system.SysVersion, err error) {
+ err = global.GVA_DB.Where("id = ?", ID).First(&sysVersion).Error
+ return
+}
+
+// GetSysVersionInfoList 分页获取版本管理记录
+// Author [yourname](https://github.com/yourname)
+func (sysVersionService *SysVersionService) GetSysVersionInfoList(ctx context.Context, info systemReq.SysVersionSearch) (list []system.SysVersion, total int64, err error) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.GVA_DB.Model(&system.SysVersion{})
+ var sysVersions []system.SysVersion
+ // 如果有条件搜索 下方会自动创建搜索语句
+ if len(info.CreatedAtRange) == 2 {
+ db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
+ }
+
+ if info.VersionName != nil && *info.VersionName != "" {
+ db = db.Where("version_name LIKE ?", "%"+*info.VersionName+"%")
+ }
+ if info.VersionCode != nil && *info.VersionCode != "" {
+ db = db.Where("version_code = ?", *info.VersionCode)
+ }
+ err = db.Count(&total).Error
+ if err != nil {
+ return
+ }
+
+ if limit != 0 {
+ db = db.Limit(limit).Offset(offset)
+ }
+
+ err = db.Find(&sysVersions).Error
+ return sysVersions, total, err
+}
+func (sysVersionService *SysVersionService) GetSysVersionPublic(ctx context.Context) {
+ // 此方法为获取数据源定义的数据
+ // 请自行实现
+}
+
+// GetMenusByIds 根据ID列表获取菜单数据
+func (sysVersionService *SysVersionService) GetMenusByIds(ctx context.Context, ids []uint) (menus []system.SysBaseMenu, err error) {
+ err = global.GVA_DB.Where("id in ?", ids).Preload("Parameters").Preload("MenuBtn").Find(&menus).Error
+ return
+}
+
+// GetApisByIds 根据ID列表获取API数据
+func (sysVersionService *SysVersionService) GetApisByIds(ctx context.Context, ids []uint) (apis []system.SysApi, err error) {
+ err = global.GVA_DB.Where("id in ?", ids).Find(&apis).Error
+ return
+}
+
+// GetDictionariesByIds 根据ID列表获取字典数据
+func (sysVersionService *SysVersionService) GetDictionariesByIds(ctx context.Context, ids []uint) (dictionaries []system.SysDictionary, err error) {
+ err = global.GVA_DB.Where("id in ?", ids).Preload("SysDictionaryDetails").Find(&dictionaries).Error
+ return
+}
+
+// ImportMenus 导入菜单数据
+func (sysVersionService *SysVersionService) ImportMenus(ctx context.Context, menus []system.SysBaseMenu) error {
+ return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+ // 递归创建菜单
+ return sysVersionService.createMenusRecursively(tx, menus, 0)
+ })
+}
+
+// createMenusRecursively 递归创建菜单
+func (sysVersionService *SysVersionService) createMenusRecursively(tx *gorm.DB, menus []system.SysBaseMenu, parentId uint) error {
+ for _, menu := range menus {
+ // 检查菜单是否已存在
+ var existingMenu system.SysBaseMenu
+ if err := tx.Where("name = ? AND path = ?", menu.Name, menu.Path).First(&existingMenu).Error; err == nil {
+ // 菜单已存在,使用现有菜单ID继续处理子菜单
+ if len(menu.Children) > 0 {
+ if err := sysVersionService.createMenusRecursively(tx, menu.Children, existingMenu.ID); err != nil {
+ return err
+ }
+ }
+ continue
+ }
+
+ // 保存参数和按钮数据,稍后处理
+ parameters := menu.Parameters
+ menuBtns := menu.MenuBtn
+ children := menu.Children
+
+ // 创建新菜单(不包含关联数据)
+ newMenu := system.SysBaseMenu{
+ ParentId: parentId,
+ Path: menu.Path,
+ Name: menu.Name,
+ Hidden: menu.Hidden,
+ Component: menu.Component,
+ Sort: menu.Sort,
+ Meta: menu.Meta,
+ }
+
+ if err := tx.Create(&newMenu).Error; err != nil {
+ return err
+ }
+
+ // 创建参数
+ if len(parameters) > 0 {
+ for _, param := range parameters {
+ newParam := system.SysBaseMenuParameter{
+ SysBaseMenuID: newMenu.ID,
+ Type: param.Type,
+ Key: param.Key,
+ Value: param.Value,
+ }
+ if err := tx.Create(&newParam).Error; err != nil {
+ return err
+ }
+ }
+ }
+
+ // 创建菜单按钮
+ if len(menuBtns) > 0 {
+ for _, btn := range menuBtns {
+ newBtn := system.SysBaseMenuBtn{
+ SysBaseMenuID: newMenu.ID,
+ Name: btn.Name,
+ Desc: btn.Desc,
+ }
+ if err := tx.Create(&newBtn).Error; err != nil {
+ return err
+ }
+ }
+ }
+
+ // 递归处理子菜单
+ if len(children) > 0 {
+ if err := sysVersionService.createMenusRecursively(tx, children, newMenu.ID); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// ImportApis 导入API数据
+func (sysVersionService *SysVersionService) ImportApis(apis []system.SysApi) error {
+ return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+ for _, api := range apis {
+ // 检查API是否已存在
+ var existingApi system.SysApi
+ if err := tx.Where("path = ? AND method = ?", api.Path, api.Method).First(&existingApi).Error; err == nil {
+ // API已存在,跳过
+ continue
+ }
+
+ // 创建新API
+ newApi := system.SysApi{
+ Path: api.Path,
+ Description: api.Description,
+ ApiGroup: api.ApiGroup,
+ Method: api.Method,
+ }
+
+ if err := tx.Create(&newApi).Error; err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
+
+// ImportDictionaries 导入字典数据
+func (sysVersionService *SysVersionService) ImportDictionaries(dictionaries []system.SysDictionary) error {
+ return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+ for _, dict := range dictionaries {
+ // 检查字典是否已存在
+ var existingDict system.SysDictionary
+ if err := tx.Where("type = ?", dict.Type).First(&existingDict).Error; err == nil {
+ // 字典已存在,跳过
+ continue
+ }
+
+ // 创建新字典
+ newDict := system.SysDictionary{
+ Name: dict.Name,
+ Type: dict.Type,
+ Status: dict.Status,
+ Desc: dict.Desc,
+ SysDictionaryDetails: dict.SysDictionaryDetails,
+ }
+
+ if err := tx.Create(&newDict).Error; err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
diff --git a/source/system/api.go b/source/system/api.go
index fe042fe..c2c8c5c 100644
--- a/source/system/api.go
+++ b/source/system/api.go
@@ -2,6 +2,7 @@ package system
import (
"context"
+
sysModel "git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service/system"
"github.com/pkg/errors"
@@ -117,6 +118,9 @@ 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/mcp", Description: "自动生成 MCP Tool 模板"},
+ {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcpTest", Description: "MCP Tool 测试"},
+ {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcpList", Description: "获取 MCP ToolList"},
{ApiGroup: "模板配置", Method: "POST", Path: "/autoCode/createPackage", Description: "配置模板"},
{ApiGroup: "模板配置", Method: "GET", Path: "/autoCode/getTemplates", Description: "获取模板文件"},
@@ -185,6 +189,14 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {
{ApiGroup: "媒体库分类", Method: "GET", Path: "/attachmentCategory/getCategoryList", Description: "分类列表"},
{ApiGroup: "媒体库分类", Method: "POST", Path: "/attachmentCategory/addCategory", Description: "添加/编辑分类"},
{ApiGroup: "媒体库分类", Method: "POST", Path: "/attachmentCategory/deleteCategory", Description: "删除分类"},
+
+ {ApiGroup: "版本控制", Method: "GET", Path: "/sysVersion/findSysVersion", Description: "获取单一版本"},
+ {ApiGroup: "版本控制", Method: "GET", Path: "/sysVersion/getSysVersionList", Description: "获取版本列表"},
+ {ApiGroup: "版本控制", Method: "GET", Path: "/sysVersion/downloadVersionJson", Description: "下载版本json"},
+ {ApiGroup: "版本控制", Method: "POST", Path: "/sysVersion/exportVersion", Description: "创建版本"},
+ {ApiGroup: "版本控制", Method: "POST", Path: "/sysVersion/importVersion", Description: "同步版本"},
+ {ApiGroup: "版本控制", Method: "DELETE", Path: "/sysVersion/deleteSysVersion", Description: "删除版本"},
+ {ApiGroup: "版本控制", Method: "DELETE", Path: "/sysVersion/deleteSysVersionByIds", Description: "批量删除版本"},
}
if err := db.Create(&entities).Error; err != nil {
return ctx, errors.Wrap(err, sysModel.SysApi{}.TableName()+"表数据初始化失败!")
diff --git a/source/system/authorities_menus.go b/source/system/authorities_menus.go
index 93a9eef..9192280 100644
--- a/source/system/authorities_menus.go
+++ b/source/system/authorities_menus.go
@@ -35,35 +35,72 @@ func (i *initMenuAuthority) InitializeData(ctx context.Context) (next context.Co
if !ok {
return ctx, system.ErrMissingDBContext
}
+
initAuth := &initAuthority{}
authorities, ok := ctx.Value(initAuth.InitializerName()).([]sysModel.SysAuthority)
if !ok {
return ctx, errors.Wrap(system.ErrMissingDependentContext, "创建 [菜单-权限] 关联失败, 未找到权限表初始化数据")
}
- menus, ok := ctx.Value(new(initMenu).InitializerName()).([]sysModel.SysBaseMenu)
+
+ allMenus, ok := ctx.Value(new(initMenu).InitializerName()).([]sysModel.SysBaseMenu)
if !ok {
return next, errors.Wrap(errors.New(""), "创建 [菜单-权限] 关联失败, 未找到菜单表初始化数据")
}
next = ctx
- // 888
- if err = db.Model(&authorities[0]).Association("SysBaseMenus").Replace(menus); err != nil {
- return next, err
+
+ // 构建菜单ID映射,方便快速查找
+ menuMap := make(map[uint]sysModel.SysBaseMenu)
+ for _, menu := range allMenus {
+ menuMap[menu.ID] = menu
+ }
+
+ // 为不同角色分配不同权限
+ // 1. 超级管理员角色(888) - 拥有所有菜单权限
+ if err = db.Model(&authorities[0]).Association("SysBaseMenus").Replace(allMenus); err != nil {
+ return next, errors.Wrap(err, "为超级管理员分配菜单失败")
+ }
+
+ // 2. 普通用户角色(8881) - 仅拥有基础功能菜单
+ // 仅选择部分父级菜单及其子菜单
+ var menu8881 []sysModel.SysBaseMenu
+
+ // 添加仪表盘、关于我们和个人信息菜单
+ for _, menu := range allMenus {
+ if menu.ParentId == 0 && (menu.Name == "dashboard" || menu.Name == "about" || menu.Name == "person" || menu.Name == "state") {
+ menu8881 = append(menu8881, menu)
+ }
}
- // 8881
- menu8881 := menus[:2]
- menu8881 = append(menu8881, menus[7])
if err = db.Model(&authorities[1]).Association("SysBaseMenus").Replace(menu8881); err != nil {
- return next, err
+ return next, errors.Wrap(err, "为普通用户分配菜单失败")
}
- // 9528
- if err = db.Model(&authorities[2]).Association("SysBaseMenus").Replace(menus[:11]); err != nil {
- return next, err
+ // 3. 测试角色(9528) - 拥有部分菜单权限
+ var menu9528 []sysModel.SysBaseMenu
+
+ // 添加所有父级菜单
+ for _, menu := range allMenus {
+ if menu.ParentId == 0 {
+ menu9528 = append(menu9528, menu)
+ }
}
- if err = db.Model(&authorities[2]).Association("SysBaseMenus").Append(menus[12:17]); err != nil {
- return next, err
+
+ // 添加部分子菜单 - 系统工具、示例文件等模块的子菜单
+ for _, menu := range allMenus {
+ parentName := ""
+ if menu.ParentId > 0 && menuMap[menu.ParentId].Name != "" {
+ parentName = menuMap[menu.ParentId].Name
+ }
+
+ if menu.ParentId > 0 && (parentName == "systemTools" || parentName == "example") {
+ menu9528 = append(menu9528, menu)
+ }
}
+
+ if err = db.Model(&authorities[2]).Association("SysBaseMenus").Replace(menu9528); err != nil {
+ return next, errors.Wrap(err, "为测试角色分配菜单失败")
+ }
+
return next, nil
}
diff --git a/source/system/casbin.go b/source/system/casbin.go
index f12b4f5..ea8730a 100644
--- a/source/system/casbin.go
+++ b/source/system/casbin.go
@@ -130,6 +130,9 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
{Ptype: "p", V0: "888", V1: "/autoCode/installPlugin", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/autoCode/pubPlug", V2: "POST"},
{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"},
+ {Ptype: "p", V0: "888", V1: "/autoCode/mcpList", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/findSysDictionaryDetail", V2: "GET"},
{Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/updateSysDictionaryDetail", V2: "PUT"},
@@ -189,6 +192,14 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
{Ptype: "p", V0: "888", V1: "/attachmentCategory/addCategory", V2: "POST"},
{Ptype: "p", V0: "888", V1: "/attachmentCategory/deleteCategory", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/findSysVersion", V2: "GET"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/getSysVersionList", V2: "GET"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/downloadVersionJson", V2: "GET"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/exportVersion", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/importVersion", V2: "POST"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/deleteSysVersion", V2: "DELETE"},
+ {Ptype: "p", V0: "888", V1: "/sysVersion/deleteSysVersionByIds", V2: "DELETE"},
+
{Ptype: "p", V0: "8881", V1: "/user/admin_register", V2: "POST"},
{Ptype: "p", V0: "8881", V1: "/api/createApi", V2: "POST"},
{Ptype: "p", V0: "8881", V1: "/api/getApiList", V2: "POST"},
diff --git a/source/system/dictionary_detail.go b/source/system/dictionary_detail.go
index ace9bde..98c7dae 100644
--- a/source/system/dictionary_detail.go
+++ b/source/system/dictionary_detail.go
@@ -66,7 +66,7 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
}
dicts[2].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
- {Label: "date", Status: &True},
+ {Label: "date", Value: "0", Status: &True, Extend: "mysql", Sort: 0},
{Label: "time", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "year", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "datetime", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
@@ -74,7 +74,7 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
{Label: "timestamptz", Value: "6", Status: &True, Extend: "pgsql", Sort: 5},
}
dicts[3].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
- {Label: "float", Status: &True},
+ {Label: "float", Value: "0", Status: &True, Extend: "mysql", Sort: 0},
{Label: "double", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "decimal", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "numeric", Value: "3", Status: &True, Extend: "pgsql", Sort: 3},
@@ -82,7 +82,7 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
}
dicts[4].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
- {Label: "char", Status: &True},
+ {Label: "char", Value: "0", Status: &True, Extend: "mysql", Sort: 0},
{Label: "varchar", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "tinyblob", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "tinytext", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
diff --git a/source/system/menu.go b/source/system/menu.go
index cde0895..b982801 100644
--- a/source/system/menu.go
+++ b/source/system/menu.go
@@ -50,43 +50,75 @@ func (i *initMenu) InitializeData(ctx context.Context) (next context.Context, er
if !ok {
return ctx, system.ErrMissingDBContext
}
- entities := []SysBaseMenu{
+
+ // 定义所有菜单
+ allMenus := []SysBaseMenu{
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Sort: 1, Meta: Meta{Title: "仪表盘", Icon: "odometer"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "about", Name: "about", Component: "view/about/index.vue", Sort: 9, Meta: Meta{Title: "关于我们", Icon: "info-filled"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "admin", Name: "superAdmin", Component: "view/superAdmin/index.vue", Sort: 3, Meta: Meta{Title: "超级管理员", Icon: "user"}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "authority", Name: "authority", Component: "view/superAdmin/authority/authority.vue", Sort: 1, Meta: Meta{Title: "角色管理", Icon: "avatar"}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "menu", Name: "menu", Component: "view/superAdmin/menu/menu.vue", Sort: 2, Meta: Meta{Title: "菜单管理", Icon: "tickets", KeepAlive: true}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "api", Name: "api", Component: "view/superAdmin/api/api.vue", Sort: 3, Meta: Meta{Title: "api管理", Icon: "platform", KeepAlive: true}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "user", Name: "user", Component: "view/superAdmin/user/user.vue", Sort: 4, Meta: Meta{Title: "用户管理", Icon: "coordinate"}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "dictionary", Name: "dictionary", Component: "view/superAdmin/dictionary/sysDictionary.vue", Sort: 5, Meta: Meta{Title: "字典管理", Icon: "notebook"}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "operation", Name: "operation", Component: "view/superAdmin/operation/sysOperationRecord.vue", Sort: 6, Meta: Meta{Title: "操作历史", Icon: "pie-chart"}},
{MenuLevel: 0, Hidden: true, ParentId: 0, Path: "person", Name: "person", Component: "view/person/person.vue", Sort: 4, Meta: Meta{Title: "个人信息", Icon: "message"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "example", Name: "example", Component: "view/example/index.vue", Sort: 7, Meta: Meta{Title: "示例文件", Icon: "management"}},
- {MenuLevel: 0, Hidden: false, ParentId: 11, Path: "upload", Name: "upload", Component: "view/example/upload/upload.vue", Sort: 5, Meta: Meta{Title: "媒体库(上传下载)", Icon: "upload"}},
- {MenuLevel: 0, Hidden: false, ParentId: 11, Path: "breakpoint", Name: "breakpoint", Component: "view/example/breakpoint/breakpoint.vue", Sort: 6, Meta: Meta{Title: "断点续传", Icon: "upload-filled"}},
- {MenuLevel: 0, Hidden: false, ParentId: 11, Path: "customer", Name: "customer", Component: "view/example/customer/customer.vue", Sort: 7, Meta: Meta{Title: "客户列表(资源示例)", Icon: "avatar"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "systemTools", Name: "systemTools", Component: "view/systemTools/index.vue", Sort: 5, Meta: Meta{Title: "系统工具", Icon: "tools"}},
- {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "autoCode", Name: "autoCode", Component: "view/systemTools/autoCode/index.vue", Sort: 1, Meta: Meta{Title: "代码生成器", Icon: "cpu", KeepAlive: true}},
- {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "formCreate", Name: "formCreate", Component: "view/systemTools/formCreate/index.vue", Sort: 3, Meta: Meta{Title: "表单生成器", Icon: "magic-stick", KeepAlive: true}},
- {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "system", Name: "system", Component: "view/systemTools/system/system.vue", Sort: 4, Meta: Meta{Title: "系统配置", Icon: "operation"}},
- {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "autoCodeAdmin", Name: "autoCodeAdmin", Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 2, Meta: Meta{Title: "自动化代码管理", Icon: "magic-stick"}},
- {MenuLevel: 0, Hidden: true, ParentId: 15, Path: "autoCodeEdit/:id", Name: "autoCodeEdit", Component: "view/systemTools/autoCode/index.vue", Sort: 0, Meta: Meta{Title: "自动化代码-${id}", Icon: "magic-stick"}},
- {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "autoPkg", Name: "autoPkg", Component: "view/systemTools/autoPkg/autoPkg.vue", Sort: 0, Meta: Meta{Title: "模板配置", Icon: "folder"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "https://www.gin-vue-admin.com", Name: "https://www.gin-vue-admin.com", Component: "/", Sort: 0, Meta: Meta{Title: "官方网站", Icon: "customer-gva"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "state", Name: "state", Component: "view/system/state.vue", Sort: 8, Meta: Meta{Title: "服务器状态", Icon: "cloudy"}},
{MenuLevel: 0, Hidden: false, ParentId: 0, Path: "plugin", Name: "plugin", Component: "view/routerHolder.vue", Sort: 6, Meta: Meta{Title: "插件系统", Icon: "cherry"}},
- {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "https://plugin.gin-vue-admin.com/", Name: "https://plugin.gin-vue-admin.com/", Component: "https://plugin.gin-vue-admin.com/", Sort: 0, Meta: Meta{Title: "插件市场", Icon: "shop"}},
- {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "installPlugin", Name: "installPlugin", Component: "view/systemTools/installPlugin/index.vue", Sort: 1, Meta: Meta{Title: "插件安装", Icon: "box"}},
- {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "pubPlug", Name: "pubPlug", Component: "view/systemTools/pubPlug/pubPlug.vue", Sort: 3, Meta: Meta{Title: "打包插件", Icon: "files"}},
- {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "plugin-email", Name: "plugin-email", Component: "plugin/email/view/index.vue", Sort: 4, Meta: Meta{Title: "邮件插件", Icon: "message"}},
- {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "exportTemplate", Name: "exportTemplate", Component: "view/systemTools/exportTemplate/exportTemplate.vue", Sort: 5, Meta: Meta{Title: "导出模板", Icon: "reading"}},
- {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "anInfo", Name: "anInfo", Component: "plugin/announcement/view/info.vue", Sort: 5, Meta: Meta{Title: "公告管理[示例]", Icon: "scaleToOriginal"}},
- {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "sysParams", Name: "sysParams", Component: "view/superAdmin/params/sysParams.vue", Sort: 7, Meta: Meta{Title: "参数管理", Icon: "compass"}},
}
- if err = db.Create(&entities).Error; err != nil {
- return ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+"表数据初始化失败!")
+
+ // 先创建父级菜单(ParentId = 0 的菜单)
+ if err = db.Create(&allMenus).Error; err != nil {
+ return ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+"父级菜单初始化失败!")
}
- next = context.WithValue(ctx, i.InitializerName(), entities)
+
+ // 建立菜单映射 - 通过Name查找已创建的菜单及其ID
+ menuNameMap := make(map[string]uint)
+ for _, menu := range allMenus {
+ menuNameMap[menu.Name] = menu.ID
+ }
+
+ // 定义子菜单,并设置正确的ParentId
+ childMenus := []SysBaseMenu{
+ // superAdmin子菜单
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "authority", Name: "authority", Component: "view/superAdmin/authority/authority.vue", Sort: 1, Meta: Meta{Title: "角色管理", Icon: "avatar"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "menu", Name: "menu", Component: "view/superAdmin/menu/menu.vue", Sort: 2, Meta: Meta{Title: "菜单管理", Icon: "tickets", KeepAlive: true}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "api", Name: "api", Component: "view/superAdmin/api/api.vue", Sort: 3, Meta: Meta{Title: "api管理", Icon: "platform", KeepAlive: true}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "user", Name: "user", Component: "view/superAdmin/user/user.vue", Sort: 4, Meta: Meta{Title: "用户管理", Icon: "coordinate"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "dictionary", Name: "dictionary", Component: "view/superAdmin/dictionary/sysDictionary.vue", Sort: 5, Meta: Meta{Title: "字典管理", Icon: "notebook"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "operation", Name: "operation", Component: "view/superAdmin/operation/sysOperationRecord.vue", Sort: 6, Meta: Meta{Title: "操作历史", Icon: "pie-chart"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["superAdmin"], Path: "sysParams", Name: "sysParams", Component: "view/superAdmin/params/sysParams.vue", Sort: 7, Meta: Meta{Title: "参数管理", Icon: "compass"}},
+
+ // example子菜单
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["example"], Path: "upload", Name: "upload", Component: "view/example/upload/upload.vue", Sort: 5, Meta: Meta{Title: "媒体库(上传下载)", Icon: "upload"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["example"], Path: "breakpoint", Name: "breakpoint", Component: "view/example/breakpoint/breakpoint.vue", Sort: 6, Meta: Meta{Title: "断点续传", Icon: "upload-filled"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["example"], Path: "customer", Name: "customer", Component: "view/example/customer/customer.vue", Sort: 7, Meta: Meta{Title: "客户列表(资源示例)", Icon: "avatar"}},
+
+ // systemTools子菜单
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "autoCode", Name: "autoCode", Component: "view/systemTools/autoCode/index.vue", Sort: 1, Meta: Meta{Title: "代码生成器", Icon: "cpu", KeepAlive: true}},
+ {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: 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: "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"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "sysVersion", Name: "sysVersion", Component: "view/systemTools/version/version.vue", Sort: 8, Meta: Meta{Title: "版本管理", Icon: "server"}},
+
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "https://plugin.gin-vue-admin.com/", Name: "https://plugin.gin-vue-admin.com/", Component: "https://plugin.gin-vue-admin.com/", Sort: 0, Meta: Meta{Title: "插件市场", Icon: "shop"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "installPlugin", Name: "installPlugin", Component: "view/systemTools/installPlugin/index.vue", Sort: 1, Meta: Meta{Title: "插件安装", Icon: "box"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "pubPlug", Name: "pubPlug", Component: "view/systemTools/pubPlug/pubPlug.vue", Sort: 3, Meta: Meta{Title: "打包插件", Icon: "files"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "plugin-email", Name: "plugin-email", Component: "plugin/email/view/index.vue", Sort: 4, Meta: Meta{Title: "邮件插件", Icon: "message"}},
+ {MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "anInfo", Name: "anInfo", Component: "plugin/announcement/view/info.vue", Sort: 5, Meta: Meta{Title: "公告管理[示例]", Icon: "scaleToOriginal"}},
+ }
+
+ // 创建子菜单
+ if err = db.Create(&childMenus).Error; err != nil {
+ return ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+"子菜单初始化失败!")
+ }
+
+ // 组合所有菜单作为返回结果
+ allEntities := append(allMenus, childMenus...)
+ next = context.WithValue(ctx, i.InitializerName(), allEntities)
return next, nil
}
diff --git a/utils/ast/ast.go b/utils/ast/ast.go
index 4ed7a30..34cba37 100644
--- a/utils/ast/ast.go
+++ b/utils/ast/ast.go
@@ -121,6 +121,81 @@ func CreateMenuStructAst(menus []system.SysBaseMenu) *[]ast.Expr {
},
},
}
+
+ // 添加菜单参数
+ if len(menus[i].Parameters) > 0 {
+ var paramElts []ast.Expr
+ for _, param := range menus[i].Parameters {
+ paramElts = append(paramElts, &ast.CompositeLit{
+ Type: &ast.SelectorExpr{
+ X: &ast.Ident{Name: "model"},
+ Sel: &ast.Ident{Name: "SysBaseMenuParameter"},
+ },
+ Elts: []ast.Expr{
+ &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "Type"},
+ Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", param.Type)},
+ },
+ &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "Key"},
+ Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", param.Key)},
+ },
+ &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "Value"},
+ Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", param.Value)},
+ },
+ },
+ })
+ }
+ elts = append(elts, &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "Parameters"},
+ Value: &ast.CompositeLit{
+ Type: &ast.ArrayType{
+ Elt: &ast.SelectorExpr{
+ X: &ast.Ident{Name: "model"},
+ Sel: &ast.Ident{Name: "SysBaseMenuParameter"},
+ },
+ },
+ Elts: paramElts,
+ },
+ })
+ }
+
+ // 添加菜单按钮
+ if len(menus[i].MenuBtn) > 0 {
+ var btnElts []ast.Expr
+ for _, btn := range menus[i].MenuBtn {
+ btnElts = append(btnElts, &ast.CompositeLit{
+ Type: &ast.SelectorExpr{
+ X: &ast.Ident{Name: "model"},
+ Sel: &ast.Ident{Name: "SysBaseMenuBtn"},
+ },
+ Elts: []ast.Expr{
+ &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "Name"},
+ Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", btn.Name)},
+ },
+ &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "Desc"},
+ Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", btn.Desc)},
+ },
+ },
+ })
+ }
+ elts = append(elts, &ast.KeyValueExpr{
+ Key: &ast.Ident{Name: "MenuBtn"},
+ Value: &ast.CompositeLit{
+ Type: &ast.ArrayType{
+ Elt: &ast.SelectorExpr{
+ X: &ast.Ident{Name: "model"},
+ Sel: &ast.Ident{Name: "SysBaseMenuBtn"},
+ },
+ },
+ Elts: btnElts,
+ },
+ })
+ }
+
menuElts = append(menuElts, &ast.CompositeLit{
Type: nil,
Elts: elts,
diff --git a/utils/autocode/template_funcs.go b/utils/autocode/template_funcs.go
new file mode 100644
index 0000000..8935948
--- /dev/null
+++ b/utils/autocode/template_funcs.go
@@ -0,0 +1,723 @@
+package autocode
+
+import (
+ "fmt"
+ systemReq "git.echol.cn/loser/lckt/model/system/request"
+ "slices"
+ "strings"
+ "text/template"
+)
+
+// GetTemplateFuncMap 返回模板函数映射,用于在模板中使用
+func GetTemplateFuncMap() template.FuncMap {
+ return template.FuncMap{
+ "title": strings.Title,
+ "GenerateField": GenerateField,
+ "GenerateSearchField": GenerateSearchField,
+ "GenerateSearchConditions": GenerateSearchConditions,
+ "GenerateSearchFormItem": GenerateSearchFormItem,
+ "GenerateTableColumn": GenerateTableColumn,
+ "GenerateFormItem": GenerateFormItem,
+ "GenerateDescriptionItem": GenerateDescriptionItem,
+ "GenerateDefaultFormValue": GenerateDefaultFormValue,
+ }
+}
+
+// 渲染Model中的字段
+func GenerateField(field systemReq.AutoCodeField) string {
+ // 构建gorm标签
+ gormTag := ``
+
+ if field.FieldIndexType != "" {
+ gormTag += field.FieldIndexType + ";"
+ }
+
+ if field.PrimaryKey {
+ gormTag += "primarykey;"
+ }
+
+ if field.DefaultValue != "" {
+ gormTag += fmt.Sprintf("default:%s;", field.DefaultValue)
+ }
+
+ if field.Comment != "" {
+ gormTag += fmt.Sprintf("comment:%s;", field.Comment)
+ }
+
+ gormTag += "column:" + field.ColumnName + ";"
+
+ // 对于int类型,根据DataTypeLong决定具体的Go类型,不使用size标签
+ if field.DataTypeLong != "" && field.FieldType != "enum" && field.FieldType != "int" {
+ gormTag += fmt.Sprintf("size:%s;", field.DataTypeLong)
+ }
+
+ requireTag := ` binding:"required"` + "`"
+
+ // 根据字段类型构建不同的字段定义
+ var result string
+ switch field.FieldType {
+ case "enum":
+ result = fmt.Sprintf(`%s string `+"`"+`json:"%s" form:"%s" gorm:"%stype:enum(%s);"`+"`",
+ field.FieldName, field.FieldJson, field.FieldJson, gormTag, field.DataTypeLong)
+ case "picture", "video":
+ tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
+ field.FieldJson, field.FieldJson, gormTag)
+
+ result = fmt.Sprintf(`%s string `+"`"+`%s`+"`"+``, field.FieldName, tagContent)
+ case "file", "pictures", "array":
+ tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
+ field.FieldJson, field.FieldJson, gormTag)
+
+ result = fmt.Sprintf(`%s datatypes.JSON `+"`"+`%s swaggertype:"array,object"`+"`"+``,
+ field.FieldName, tagContent)
+ case "richtext":
+ tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s`,
+ field.FieldJson, field.FieldJson, gormTag)
+
+ result = fmt.Sprintf(`%s *string `+"`"+`%stype:text;"`+"`"+``,
+ field.FieldName, tagContent)
+ case "json":
+ tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
+ field.FieldJson, field.FieldJson, gormTag)
+
+ result = fmt.Sprintf(`%s datatypes.JSON `+"`"+`%s swaggertype:"object"`+"`"+``,
+ field.FieldName, tagContent)
+ default:
+ tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
+ field.FieldJson, field.FieldJson, gormTag)
+
+ // 对于int类型,根据DataTypeLong决定具体的Go类型
+ var fieldType string
+ if field.FieldType == "int" {
+ switch field.DataTypeLong {
+ case "1", "2", "3":
+ fieldType = "int8"
+ case "4", "5":
+ fieldType = "int16"
+ case "6", "7", "8", "9", "10":
+ fieldType = "int32"
+ case "11", "12", "13", "14", "15", "16", "17", "18", "19", "20":
+ fieldType = "int64"
+ default:
+ fieldType = "int64"
+ }
+ } else {
+ fieldType = field.FieldType
+ }
+
+ result = fmt.Sprintf(`%s *%s `+"`"+`%s`+"`"+``,
+ field.FieldName, fieldType, tagContent)
+ }
+
+ if field.Require {
+ result = result[0:len(result)-1] + requireTag
+ }
+
+ // 添加字段描述
+ if field.FieldDesc != "" {
+ result += fmt.Sprintf(" //%s", field.FieldDesc)
+ }
+
+ return result
+}
+
+// 格式化搜索条件语句
+func GenerateSearchConditions(fields []*systemReq.AutoCodeField) string {
+ var conditions []string
+
+ for _, field := range fields {
+ if field.FieldSearchType == "" {
+ continue
+ }
+
+ var condition string
+
+ if slices.Contains([]string{"enum", "pictures", "picture", "video", "json", "richtext", "array"}, field.FieldType) {
+ if field.FieldType == "enum" {
+ if field.FieldSearchType == "LIKE" {
+ condition = fmt.Sprintf(`
+ if info.%s != "" {
+ db = db.Where("%s LIKE ?", "%%"+ info.%s+"%%")
+ }`,
+ field.FieldName, field.ColumnName, field.FieldName)
+ } else {
+ condition = fmt.Sprintf(`
+ if info.%s != "" {
+ db = db.Where("%s %s ?", info.%s)
+ }`,
+ field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName)
+ }
+ } else {
+ condition = fmt.Sprintf(`
+ if info.%s != "" {
+ // TODO 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务
+ }`, field.FieldName)
+ }
+
+ } else if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
+ if field.FieldType == "time.Time" {
+ condition = fmt.Sprintf(`
+ if len(info.%sRange) == 2 {
+ db = db.Where("%s %s ? AND ? ", info.%sRange[0], info.%sRange[1])
+ }`,
+ field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName, field.FieldName)
+ } else {
+ condition = fmt.Sprintf(`
+ if info.Start%s != nil && info.End%s != nil {
+ db = db.Where("%s %s ? AND ? ", *info.Start%s, *info.End%s)
+ }`,
+ field.FieldName, field.FieldName, field.ColumnName,
+ field.FieldSearchType, field.FieldName, field.FieldName)
+ }
+ } else {
+ nullCheck := "info." + field.FieldName + " != nil"
+ if field.FieldType == "string" {
+ condition = fmt.Sprintf(`
+ if %s && *info.%s != "" {`, nullCheck, field.FieldName)
+ } else {
+ condition = fmt.Sprintf(`
+ if %s {`, nullCheck)
+ }
+
+ if field.FieldSearchType == "LIKE" {
+ condition += fmt.Sprintf(`
+ db = db.Where("%s LIKE ?", "%%"+ *info.%s+"%%")
+ }`,
+ field.ColumnName, field.FieldName)
+ } else {
+ condition += fmt.Sprintf(`
+ db = db.Where("%s %s ?", *info.%s)
+ }`,
+ field.ColumnName, field.FieldSearchType, field.FieldName)
+ }
+ }
+
+ conditions = append(conditions, condition)
+ }
+
+ return strings.Join(conditions, "")
+}
+
+// 格式化前端搜索条件
+func GenerateSearchFormItem(field systemReq.AutoCodeField) string {
+ // 开始构建表单项
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+
+ // 根据字段属性生成不同的输入类型
+ if field.FieldType == "bool" {
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += `
+`
+ } else if field.DictType != "" {
+ multipleAttr := ""
+ if field.FieldType == "array" {
+ multipleAttr = "multiple "
+ }
+ result += fmt.Sprintf(` {searchInfo.%s=undefined}">
+`,
+ multipleAttr, field.FieldJson, field.FieldJson)
+ result += fmt.Sprintf(`
+`,
+ field.DictType)
+ result += `
+`
+ } else if field.CheckDataSource {
+ multipleAttr := ""
+ if field.DataSource.Association == 2 {
+ multipleAttr = "multiple "
+ }
+ result += fmt.Sprintf(`
+`,
+ multipleAttr, field.FieldJson, field.FieldDesc, field.Clearable)
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson)
+ result += `
+`
+ } else if field.FieldType == "float64" || field.FieldType == "int" {
+ if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
+ result += fmt.Sprintf(`
+`, field.FieldName)
+ result += ` —
+`
+ result += fmt.Sprintf(`
+`, field.FieldName)
+ } else {
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ }
+ } else if field.FieldType == "time.Time" {
+ if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
+ result += `
+`
+ result += `
+`
+ result += fmt.Sprintf(` %s
+`, field.FieldDesc)
+ result += `
+`
+ result += `
+`
+ result += `
+`
+ result += `
+`
+ result += `
+`
+ result += fmt.Sprintf(``, field.FieldJson)
+ } else {
+ result += fmt.Sprintf(``, field.FieldJson)
+ }
+ } else {
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ }
+
+ // 关闭表单项
+ result += ``
+
+ return result
+}
+
+// GenerateTableColumn generates HTML for table column based on field properties
+func GenerateTableColumn(field systemReq.AutoCodeField) string {
+ // Add sortable attribute if needed
+ sortAttr := ""
+ if field.Sort {
+ sortAttr = " sortable"
+ }
+
+ // Handle different field types
+ if field.CheckDataSource {
+ result := fmt.Sprintf(`
+`,
+ sortAttr, field.FieldDesc, field.FieldJson)
+ result += `
+`
+
+ if field.DataSource.Association == 2 {
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldJson)
+ result += ` {{ item }}
+`
+ result += `
+`
+ } else {
+ result += fmt.Sprintf(` {{ filterDataSource(dataSource.%s,scope.row.%s) }}
+`,
+ field.FieldJson, field.FieldJson)
+ }
+
+ result += `
+`
+ result += ``
+ return result
+ } else if field.DictType != "" {
+ result := fmt.Sprintf(`
+`,
+ sortAttr, field.FieldDesc, field.FieldJson)
+ result += `
+`
+
+ if field.FieldType == "array" {
+ result += fmt.Sprintf(` {{ filterDict(item,%sOptions) }}
+`,
+ field.FieldJson, field.DictType)
+ } else {
+ result += fmt.Sprintf(` {{ filterDict(scope.row.%s,%sOptions) }}
+`,
+ field.FieldJson, field.DictType)
+ }
+
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "bool" {
+ result := fmt.Sprintf(`
+`,
+ sortAttr, field.FieldDesc, field.FieldJson)
+ result += fmt.Sprintf(` {{ formatBoolean(scope.row.%s) }}
+`, field.FieldJson)
+ result += ``
+ return result
+ } else if field.FieldType == "time.Time" {
+ result := fmt.Sprintf(`
+`,
+ sortAttr, field.FieldDesc, field.FieldJson)
+ result += fmt.Sprintf(` {{ formatDate(scope.row.%s) }}
+`, field.FieldJson)
+ result += ``
+ return result
+ } else if field.FieldType == "picture" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "pictures" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "video" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "richtext" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += ` [富文本内容]
+`
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "file" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += fmt.Sprintf(` {{ file.name }}
+`, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "json" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += ` [JSON]
+`
+ result += `
+`
+ result += ``
+ return result
+ } else if field.FieldType == "array" {
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+ result += `
+`
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ result += `
+`
+ result += ``
+ return result
+ } else {
+ return fmt.Sprintf(`
+`,
+ sortAttr, field.FieldDesc, field.FieldJson)
+ }
+}
+
+func GenerateFormItem(field systemReq.AutoCodeField) string {
+ // 开始构建表单项
+ result := fmt.Sprintf(`
+`, field.FieldDesc, field.FieldJson)
+
+ // 处理不同字段类型
+ if field.CheckDataSource {
+ multipleAttr := ""
+ if field.DataSource.Association == 2 {
+ multipleAttr = " multiple"
+ }
+ result += fmt.Sprintf(`
+`,
+ multipleAttr, field.FieldJson, field.FieldDesc, field.Clearable)
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson)
+ result += `
+`
+ } else {
+ switch field.FieldType {
+ case "bool":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson)
+
+ case "string":
+ if field.DictType != "" {
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldDesc, field.Clearable)
+ result += fmt.Sprintf(`
+`,
+ field.DictType)
+ result += `
+`
+ } else {
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.Clearable, field.FieldDesc)
+ }
+
+ case "richtext":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+
+ case "json":
+ result += fmt.Sprintf(` // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.%s 后端会按照json的类型进行存取
+`, field.FieldJson)
+ result += fmt.Sprintf(` {{ formData.%s }}
+`, field.FieldJson)
+
+ case "array":
+ if field.DictType != "" {
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldDesc, field.Clearable)
+ result += fmt.Sprintf(`
+`,
+ field.DictType)
+ result += `
+`
+ } else {
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ }
+
+ case "int":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.Clearable, field.FieldDesc)
+
+ case "time.Time":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.Clearable)
+
+ case "float64":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.Clearable)
+
+ case "enum":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldDesc, field.Clearable)
+ result += fmt.Sprintf(`
+`,
+ field.DataTypeLong)
+ result += `
+`
+
+ case "picture":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+
+ case "pictures":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+
+ case "video":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+
+ case "file":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ }
+ }
+
+ // 关闭表单项
+ result += ``
+
+ return result
+}
+
+func GenerateDescriptionItem(field systemReq.AutoCodeField) string {
+ // 开始构建描述项
+ result := fmt.Sprintf(`
+`, field.FieldDesc)
+
+ if field.CheckDataSource {
+ result += `
+`
+ if field.DataSource.Association == 2 {
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldJson)
+ result += ` {{ item }}
+`
+ result += `
+`
+ } else {
+ result += fmt.Sprintf(` {{ filterDataSource(dataSource.%s,detailForm.%s) }}
+`,
+ field.FieldJson, field.FieldJson)
+ }
+ result += `
+`
+ } else if field.FieldType != "picture" && field.FieldType != "pictures" &&
+ field.FieldType != "file" && field.FieldType != "array" &&
+ field.FieldType != "richtext" {
+ result += fmt.Sprintf(` {{ detailForm.%s }}
+`, field.FieldJson)
+ } else {
+ switch field.FieldType {
+ case "picture":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldJson)
+ case "array":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ case "pictures":
+ result += fmt.Sprintf(`
+`,
+ field.FieldJson, field.FieldJson)
+ case "richtext":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ case "file":
+ result += fmt.Sprintf(`
+`, field.FieldJson)
+ result += `
+`
+ result += `
+`
+ result += ` {{ item.name }}
+`
+ result += `
+`
+ result += `
+`
+ }
+ }
+
+ // 关闭描述项
+ result += ``
+
+ return result
+}
+
+func GenerateDefaultFormValue(field systemReq.AutoCodeField) string {
+ // 根据字段类型确定默认值
+ var defaultValue string
+
+ switch field.FieldType {
+ case "bool":
+ defaultValue = "false"
+ case "string", "richtext":
+ defaultValue = "''"
+ case "int":
+ if field.DataSource != nil { // 检查数据源是否存在
+ defaultValue = "undefined"
+ } else {
+ defaultValue = "0"
+ }
+ case "time.Time":
+ defaultValue = "new Date()"
+ case "float64":
+ defaultValue = "0"
+ case "picture", "video":
+ defaultValue = "\"\""
+ case "pictures", "file", "array":
+ defaultValue = "[]"
+ case "json":
+ defaultValue = "{}"
+ default:
+ defaultValue = "null"
+ }
+
+ // 返回格式化后的默认值字符串
+ return fmt.Sprintf(`%s: %s,`, field.FieldJson, defaultValue)
+}
+
+// GenerateSearchField 根据字段属性生成搜索结构体中的字段定义
+func GenerateSearchField(field systemReq.AutoCodeField) string {
+ var result string
+
+ if field.FieldSearchType == "" {
+ return "" // 如果没有搜索类型,返回空字符串
+ }
+
+ if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
+ // 生成范围搜索字段
+ // time 的情况
+ if field.FieldType == "time.Time" {
+ result = fmt.Sprintf("%sRange []time.Time `json:\"%sRange\" form:\"%sRange[]\"`",
+ field.FieldName, field.FieldJson, field.FieldJson)
+ } else {
+ startField := fmt.Sprintf("Start%s *%s `json:\"start%s\" form:\"start%s\"`",
+ field.FieldName, field.FieldType, field.FieldName, field.FieldName)
+ endField := fmt.Sprintf("End%s *%s `json:\"end%s\" form:\"end%s\"`",
+ field.FieldName, field.FieldType, field.FieldName, field.FieldName)
+ result = startField + "\n" + endField
+ }
+ } else {
+ // 生成普通搜索字段
+ if field.FieldType == "enum" || field.FieldType == "picture" ||
+ field.FieldType == "pictures" || field.FieldType == "video" ||
+ field.FieldType == "json" || field.FieldType == "richtext" || field.FieldType == "array" {
+ result = fmt.Sprintf("%s string `json:\"%s\" form:\"%s\"` ",
+ field.FieldName, field.FieldJson, field.FieldJson)
+ } else {
+ result = fmt.Sprintf("%s *%s `json:\"%s\" form:\"%s\"` ",
+ field.FieldName, field.FieldType, field.FieldJson, field.FieldJson)
+ }
+ }
+
+ return result
+}
diff --git a/utils/captcha/redis.go b/utils/captcha/redis.go
index 5802047..5170aae 100644
--- a/utils/captcha/redis.go
+++ b/utils/captcha/redis.go
@@ -5,7 +5,6 @@ import (
"time"
"git.echol.cn/loser/lckt/global"
- "github.com/mojocn/base64Captcha"
"go.uber.org/zap"
)
@@ -23,8 +22,10 @@ type RedisStore struct {
Context context.Context
}
-func (rs *RedisStore) UseWithCtx(ctx context.Context) base64Captcha.Store {
- rs.Context = ctx
+func (rs *RedisStore) UseWithCtx(ctx context.Context) *RedisStore {
+ if ctx == nil {
+ rs.Context = ctx
+ }
return rs
}
diff --git a/utils/casbin_util.go b/utils/casbin_util.go
new file mode 100644
index 0000000..c6a2024
--- /dev/null
+++ b/utils/casbin_util.go
@@ -0,0 +1,52 @@
+package utils
+
+import (
+ "sync"
+
+ "git.echol.cn/loser/lckt/global"
+ "github.com/casbin/casbin/v2"
+ "github.com/casbin/casbin/v2/model"
+ gormadapter "github.com/casbin/gorm-adapter/v3"
+ "go.uber.org/zap"
+)
+
+var (
+ syncedCachedEnforcer *casbin.SyncedCachedEnforcer
+ once sync.Once
+)
+
+// GetCasbin 获取casbin实例
+func GetCasbin() *casbin.SyncedCachedEnforcer {
+ once.Do(func() {
+ a, err := gormadapter.NewAdapterByDB(global.GVA_DB)
+ if err != nil {
+ zap.L().Error("适配数据库失败请检查casbin表是否为InnoDB引擎!", zap.Error(err))
+ return
+ }
+ text := `
+ [request_definition]
+ r = sub, obj, act
+
+ [policy_definition]
+ p = sub, obj, act
+
+ [role_definition]
+ g = _, _
+
+ [policy_effect]
+ e = some(where (p.eft == allow))
+
+ [matchers]
+ m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
+ `
+ m, err := model.NewModelFromString(text)
+ if err != nil {
+ zap.L().Error("字符串加载模型失败!", zap.Error(err))
+ return
+ }
+ syncedCachedEnforcer, _ = casbin.NewSyncedCachedEnforcer(m, a)
+ syncedCachedEnforcer.SetExpireTime(60 * 60)
+ _ = syncedCachedEnforcer.LoadPolicy()
+ })
+ return syncedCachedEnforcer
+}
diff --git a/utils/fmt_plus.go b/utils/fmt_plus.go
index d849d18..4a613fa 100644
--- a/utils/fmt_plus.go
+++ b/utils/fmt_plus.go
@@ -68,6 +68,23 @@ func MaheHump(s string) string {
return strings.Join(words, "")
}
+// HumpToUnderscore 将驼峰命名转换为下划线分割模式
+func HumpToUnderscore(s string) string {
+ var result strings.Builder
+
+ for i, char := range s {
+ if i > 0 && char >= 'A' && char <= 'Z' {
+ // 在大写字母前添加下划线
+ result.WriteRune('_')
+ result.WriteRune(char - 'A' + 'a') // 转小写
+ } else {
+ result.WriteRune(char)
+ }
+ }
+
+ return strings.ToLower(result.String())
+}
+
// RandomString 随机字符串
func RandomString(n int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
diff --git a/utils/jwt.go b/utils/jwt.go
index 096e2d1..c4e45d5 100644
--- a/utils/jwt.go
+++ b/utils/jwt.go
@@ -1,6 +1,7 @@
package utils
import (
+ "context"
"errors"
"time"
@@ -85,3 +86,20 @@ func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
}
return nil, TokenValid
}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: SetRedisJWT
+//@description: jwt存入redis并设置过期时间
+//@param: jwt string, userName string
+//@return: err error
+
+func SetRedisJWT(jwt string, userName string) (err error) {
+ // 此处过期时间等于jwt过期时间
+ dr, err := ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
+ if err != nil {
+ return err
+ }
+ timer := dr
+ err = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err()
+ return err
+}
diff --git a/utils/system_events.go b/utils/system_events.go
new file mode 100644
index 0000000..126d85b
--- /dev/null
+++ b/utils/system_events.go
@@ -0,0 +1,34 @@
+package utils
+
+import (
+ "sync"
+)
+
+// SystemEvents 定义系统级事件处理
+type SystemEvents struct {
+ reloadHandlers []func() error
+ mu sync.RWMutex
+}
+
+// 全局事件管理器
+var GlobalSystemEvents = &SystemEvents{}
+
+// RegisterReloadHandler 注册系统重载处理函数
+func (e *SystemEvents) RegisterReloadHandler(handler func() error) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.reloadHandlers = append(e.reloadHandlers, handler)
+}
+
+// TriggerReload 触发所有注册的重载处理函数
+func (e *SystemEvents) TriggerReload() error {
+ e.mu.RLock()
+ defer e.mu.RUnlock()
+
+ for _, handler := range e.reloadHandlers {
+ if err := handler(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/utils/verify.go b/utils/verify.go
index 43a8672..cc2cb78 100644
--- a/utils/verify.go
+++ b/utils/verify.go
@@ -5,7 +5,7 @@ var (
ApiVerify = Rules{"Path": {NotEmpty()}, "Description": {NotEmpty()}, "ApiGroup": {NotEmpty()}, "Method": {NotEmpty()}}
MenuVerify = Rules{"Path": {NotEmpty()}, "Name": {NotEmpty()}, "Component": {NotEmpty()}, "Sort": {Ge("0")}}
MenuMetaVerify = Rules{"Title": {NotEmpty()}}
- LoginVerify = Rules{"CaptchaId": {NotEmpty()}, "Username": {NotEmpty()}, "Password": {NotEmpty()}}
+ LoginVerify = Rules{"Username": {NotEmpty()}, "Password": {NotEmpty()}}
RegisterVerify = Rules{"Username": {NotEmpty()}, "NickName": {NotEmpty()}, "Password": {NotEmpty()}, "AuthorityId": {NotEmpty()}}
PageInfoVerify = Rules{"Page": {NotEmpty()}, "PageSize": {NotEmpty()}}
CustomerVerify = Rules{"CustomerName": {NotEmpty()}, "CustomerPhoneData": {NotEmpty()}}