增加字典

This commit is contained in:
2025-11-07 17:38:59 +08:00
parent b159d71a77
commit 8b8b0c54a1
10 changed files with 1892 additions and 97 deletions
+511
View File
@@ -0,0 +1,511 @@
package controllers
import (
"encoding/json"
"server/models"
"server/services"
"strconv"
"github.com/beego/beego/v2/server/web"
)
// DictController 字典管理控制器
type DictController struct {
web.Controller
}
// GetDictTypes 获取字典类型列表
func (c *DictController) GetDictTypes() {
// 获取租户ID
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
parentId, _ := c.GetInt("parent_id", -1)
statusStr := c.GetString("status")
var status *int8
if statusStr != "" {
s, _ := strconv.ParseInt(statusStr, 10, 8)
statusVal := int8(s)
status = &statusVal
}
dictTypes, err := services.GetDictTypes(tenantId, parentId, status)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "查询字典类型失败: " + err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": dictTypes,
}
c.ServeJSON()
}
// GetDictTypeById 根据ID获取字典类型
func (c *DictController) GetDictTypeById() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
dictType, err := services.GetDictTypeById(id)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "字典类型不存在",
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": dictType,
}
c.ServeJSON()
}
// AddDictType 添加字典类型
func (c *DictController) AddDictType() {
var dictType models.DictType
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &dictType); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数解析失败: " + err.Error(),
}
c.ServeJSON()
return
}
// 获取租户ID和用户名
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
usernameData := c.Ctx.Input.GetData("username")
username := ""
if usernameData != nil {
if u, ok := usernameData.(string); ok {
username = u
}
}
// 设置租户ID和创建人
dictType.TenantId = tenantId
dictType.CreateBy = username
if dictType.Status == 0 {
dictType.Status = 1 // 默认启用
}
id, err := services.AddDictType(&dictType)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "添加成功",
"data": map[string]interface{}{"id": id},
}
c.ServeJSON()
}
// UpdateDictType 更新字典类型
func (c *DictController) UpdateDictType() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
var dictType models.DictType
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &dictType); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数解析失败: " + err.Error(),
}
c.ServeJSON()
return
}
// 获取用户名
usernameData := c.Ctx.Input.GetData("username")
username := ""
if usernameData != nil {
if u, ok := usernameData.(string); ok {
username = u
}
}
dictType.Id = id
dictType.UpdateBy = username
err = services.UpdateDictType(&dictType)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "更新成功",
}
c.ServeJSON()
}
// DeleteDictType 删除字典类型
func (c *DictController) DeleteDictType() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
// 获取租户ID
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
err = services.DeleteDictType(id, tenantId)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "删除成功",
}
c.ServeJSON()
}
// GetDictItems 获取字典项列表
func (c *DictController) GetDictItems() {
dictTypeId, _ := c.GetInt("dict_type_id", 0)
if dictTypeId <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误:dict_type_id 必填",
}
c.ServeJSON()
return
}
parentIdStr := c.GetString("parent_id")
statusStr := c.GetString("status")
var parentId *int
if parentIdStr != "" {
pid, _ := strconv.Atoi(parentIdStr)
parentId = &pid
}
var status *int8
if statusStr != "" {
s, _ := strconv.ParseInt(statusStr, 10, 8)
statusVal := int8(s)
status = &statusVal
}
dictItems, err := services.GetDictItems(dictTypeId, parentId, status)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "查询字典项失败: " + err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": dictItems,
}
c.ServeJSON()
}
// GetDictItemById 根据ID获取字典项
func (c *DictController) GetDictItemById() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
dictItem, err := services.GetDictItemById(id)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "字典项不存在",
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": dictItem,
}
c.ServeJSON()
}
// AddDictItem 添加字典项
func (c *DictController) AddDictItem() {
var dictItem models.DictItem
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &dictItem); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数解析失败: " + err.Error(),
}
c.ServeJSON()
return
}
if dictItem.DictTypeId <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误:dict_type_id 必填",
}
c.ServeJSON()
return
}
// 获取用户名
usernameData := c.Ctx.Input.GetData("username")
username := ""
if usernameData != nil {
if u, ok := usernameData.(string); ok {
username = u
}
}
// 设置创建人
dictItem.CreateBy = username
if dictItem.Status == 0 {
dictItem.Status = 1 // 默认启用
}
id, err := services.AddDictItem(&dictItem)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "添加成功",
"data": map[string]interface{}{"id": id},
}
c.ServeJSON()
}
// UpdateDictItem 更新字典项
func (c *DictController) UpdateDictItem() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
var dictItem models.DictItem
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &dictItem); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数解析失败: " + err.Error(),
}
c.ServeJSON()
return
}
// 获取用户名
usernameData := c.Ctx.Input.GetData("username")
username := ""
if usernameData != nil {
if u, ok := usernameData.(string); ok {
username = u
}
}
dictItem.Id = id
dictItem.UpdateBy = username
err = services.UpdateDictItem(&dictItem)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "更新成功",
}
c.ServeJSON()
}
// DeleteDictItem 删除字典项
func (c *DictController) DeleteDictItem() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
err = services.DeleteDictItem(id)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "删除成功",
}
c.ServeJSON()
}
// GetDictItemsByCode 根据字典编码获取字典项(用于业务查询)
func (c *DictController) GetDictItemsByCode() {
dictCode := c.Ctx.Input.Param(":code")
if dictCode == "" {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误:code 必填",
}
c.ServeJSON()
return
}
// 获取租户ID
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
includeDisabled := c.GetString("include_disabled") == "1"
dictItems, err := services.GetDictItemsByCode(dictCode, tenantId, includeDisabled)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "查询字典项失败: " + err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": dictItems,
}
c.ServeJSON()
}
// BatchUpdateDictItemSort 批量更新字典项排序
func (c *DictController) BatchUpdateDictItemSort() {
var items []struct {
Id int `json:"id"`
Sort int `json:"sort"`
}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &items); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数解析失败: " + err.Error(),
}
c.ServeJSON()
return
}
err := services.BatchUpdateDictItemSort(items)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "更新成功",
}
c.ServeJSON()
}
+58
View File
@@ -0,0 +1,58 @@
package models
import (
"time"
"github.com/beego/beego/v2/client/orm"
)
// DictType 字典类型模型
type DictType struct {
Id int `orm:"auto" json:"id"`
TenantId int `orm:"column(tenant_id);default(0)" json:"tenant_id"` // 0表示平台字典,>0表示租户字典
DictCode string `orm:"column(dict_code);size(50)" json:"dict_code"` // 字典编码(唯一)
DictName string `orm:"column(dict_name);size(100)" json:"dict_name"` // 字典名称
ParentId int `orm:"column(parent_id);default(0)" json:"parent_id"` // 父级字典ID(支持多级)
Status int8 `orm:"column(status);default(1)" json:"status"` // 0-禁用,1-启用
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序号
Remark string `orm:"column(remark);size(500);null" json:"remark"` // 备注
CreateBy string `orm:"column(create_by);size(50);null" json:"create_by"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateBy string `orm:"column(update_by);size(50);null" json:"update_by"`
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"` // 0-未删,1-已删
}
// TableName 设置表名
func (d *DictType) TableName() string {
return "sys_dict_type"
}
// DictItem 字典项模型
type DictItem struct {
Id int `orm:"auto" json:"id"`
DictTypeId int `orm:"column(dict_type_id)" json:"dict_type_id"` // 关联字典类型ID
DictLabel string `orm:"column(dict_label);size(100)" json:"dict_label"` // 字典标签(显示值)
DictValue string `orm:"column(dict_value);size(100)" json:"dict_value"` // 字典值(存储值)
ParentId int `orm:"column(parent_id);default(0)" json:"parent_id"` // 父级字典项ID(支持多级)
Status int8 `orm:"column(status);default(1)" json:"status"` // 0-禁用,1-启用
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序号
Color string `orm:"column(color);size(20);null" json:"color"` // 颜色标记
Icon string `orm:"column(icon);size(50);null" json:"icon"` // 图标
Remark string `orm:"column(remark);size(500);null" json:"remark"` // 备注
CreateBy string `orm:"column(create_by);size(50);null" json:"create_by"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateBy string `orm:"column(update_by);size(50);null" json:"update_by"`
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"` // 0-未删,1-已删
}
// TableName 设置表名
func (d *DictItem) TableName() string {
return "sys_dict_item"
}
func init() {
orm.RegisterModel(new(DictType))
orm.RegisterModel(new(DictItem))
}
+47 -5
View File
@@ -324,7 +324,6 @@ func GetAllMenuPermissionsForUser(userId int, userType string, roleId int) ([]*M
// 分配角色权限,更新role菜单ID
func AssignRolePermissions(roleId int, menuIds []int, createBy string) error {
o := orm.NewOrm()
var jsonData []byte
var err error
if len(menuIds) == 0 {
@@ -335,11 +334,54 @@ func AssignRolePermissions(roleId int, menuIds []int, createBy string) error {
return fmt.Errorf("序列化菜单ID失败: %v", err)
}
}
_, err = o.Raw("UPDATE yz_roles SET menu_ids = ?, update_by = ?, update_time = NOW() WHERE role_id = ?", string(jsonData), createBy, roleId).Exec()
if err != nil {
return fmt.Errorf("更新角色权限失败: %v", err)
// 使用重试机制处理连接失效问题
maxRetries := 3
var lastErr error
for i := 0; i < maxRetries; i++ {
o := orm.NewOrm()
// 在执行前检查连接有效性
db, dbErr := orm.GetDB("default")
if dbErr == nil {
// 尝试 Ping 检查连接
if pingErr := db.Ping(); pingErr != nil {
// 连接失效,等待后重试
if i < maxRetries-1 {
time.Sleep(200 * time.Millisecond)
lastErr = fmt.Errorf("数据库连接失效: %v", pingErr)
continue
}
}
}
// 尝试执行更新
_, err = o.Raw("UPDATE yz_roles SET menu_ids = ?, update_by = ?, update_time = NOW() WHERE role_id = ?", string(jsonData), createBy, roleId).Exec()
if err == nil {
// 成功,返回
return nil
}
// 检查是否是连接错误
errStr := err.Error()
if strings.Contains(errStr, "invalid connection") ||
strings.Contains(errStr, "driver: bad connection") ||
strings.Contains(errStr, "connection reset") {
lastErr = err
// 如果是连接错误且不是最后一次重试,等待后继续
if i < maxRetries-1 {
time.Sleep(200 * time.Millisecond)
continue
}
} else {
// 非连接错误,直接返回
return fmt.Errorf("更新角色权限失败: %v", err)
}
}
return nil
// 所有重试都失败了
return fmt.Errorf("更新角色权限失败(已重试%d次): %v", maxRetries, lastErr)
}
// 获取用户所有权限(从所属角色)
+9 -3
View File
@@ -44,6 +44,8 @@ func Init(version string) {
orm.RegisterModel(new(Knowledge))
orm.RegisterModel(new(KnowledgeCategory))
orm.RegisterModel(new(KnowledgeTag))
orm.RegisterModel(new(DictType))
orm.RegisterModel(new(DictItem))
ormConfig, err := beego.AppConfig.String("orm")
if err != nil {
@@ -60,6 +62,7 @@ func Init(version string) {
}
// 构建连接字符串,添加连接池和性能优化参数
// Go MySQL驱动会自动通过连接池管理连接,不需要额外的reconnect参数
dsn := user + ":" + pass + "@tcp(" + urls + ")/" + dbName + "?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=30s"
fmt.Println("数据库连接字符串:", dsn)
@@ -76,9 +79,12 @@ func Init(version string) {
}
// 设置连接池参数
dbConn.SetMaxIdleConns(10) // 设置空闲连接池中连接的最大数量
dbConn.SetMaxOpenConns(100) // 设置打开数据库连接的最大数量
dbConn.SetConnMaxLifetime(time.Hour) // 设置连接可复用的最大时间
dbConn.SetMaxIdleConns(10) // 设置空闲连接池中连接的最大数量
dbConn.SetMaxOpenConns(100) // 设置打开数据库连接的最大数量
dbConn.SetConnMaxLifetime(30 * time.Minute) // 设置连接可复用的最大时间30分钟,避免连接过期)
// 注意:SetConnMaxIdleTime 在 Go 1.15+ 和 database/sql 1.4+ 中可用
// 如果版本不支持,可以移除这一行
// dbConn.SetConnMaxIdleTime(10 * time.Minute) // 设置空闲连接的最大空闲时间
fmt.Println("数据库连接成功!")
fmt.Printf("当前项目版本: %s\n", version)
+8
View File
@@ -315,6 +315,14 @@ func init() {
beego.Router("/api/dashboard/platform-stats", &controllers.DashboardController{}, "get:GetPlatformStats")
beego.Router("/api/dashboard/tenant-stats", &controllers.DashboardController{}, "get:GetTenantStats")
// 字典管理路由
beego.Router("/api/dict/types", &controllers.DictController{}, "get:GetDictTypes;post:AddDictType")
beego.Router("/api/dict/types/:id", &controllers.DictController{}, "get:GetDictTypeById;put:UpdateDictType;delete:DeleteDictType")
beego.Router("/api/dict/items", &controllers.DictController{}, "get:GetDictItems;post:AddDictItem")
beego.Router("/api/dict/items/:id", &controllers.DictController{}, "get:GetDictItemById;put:UpdateDictItem;delete:DeleteDictItem")
beego.Router("/api/dict/items/code/:code", &controllers.DictController{}, "get:GetDictItemsByCode")
beego.Router("/api/dict/items/sort", &controllers.DictController{}, "put:BatchUpdateDictItemSort")
// 手动配置特殊路由(无法通过自动路由处理的)
beego.Router("/api/allmenu", &controllers.MenuController{}, "get:GetAllMenus")
beego.Router("/api/program-categories/public", &controllers.ProgramCategoryController{}, "get:GetProgramCategoriesPublic")
+373
View File
@@ -0,0 +1,373 @@
package services
import (
"fmt"
"regexp"
"server/models"
"time"
"github.com/beego/beego/v2/client/orm"
)
// ========== 字典类型相关服务 ==========
// AddDictType 添加字典类型
func AddDictType(dictType *models.DictType) (int64, error) {
// 校验字典编码格式(只能包含字母、数字、下划线)
matched, err := regexp.MatchString(`^[A-Za-z0-9_]+$`, dictType.DictCode)
if err != nil || !matched {
return 0, fmt.Errorf("字典编码格式错误,只能包含字母、数字、下划线")
}
o := orm.NewOrm()
// 检查字典编码是否已存在(同一租户下唯一)
existType := &models.DictType{}
err = o.Raw("SELECT id FROM sys_dict_type WHERE dict_code = ? AND tenant_id = ? AND is_deleted = 0",
dictType.DictCode, dictType.TenantId).QueryRow(existType)
if err == nil {
return 0, fmt.Errorf("字典编码 %s 已存在", dictType.DictCode)
}
// 插入新字典类型
id, err := o.Insert(dictType)
if err != nil {
return 0, fmt.Errorf("添加字典类型失败: %v", err)
}
// 清除相关缓存
clearDictCache(dictType.TenantId, dictType.DictCode)
return id, nil
}
// UpdateDictType 更新字典类型
func UpdateDictType(dictType *models.DictType) error {
o := orm.NewOrm()
// 获取原字典类型信息
oldType := &models.DictType{Id: dictType.Id}
err := o.Read(oldType)
if err != nil {
return fmt.Errorf("字典类型不存在: %v", err)
}
// 不允许修改 dict_code(避免业务引用失效)
if oldType.DictCode != dictType.DictCode {
return fmt.Errorf("不允许修改字典编码")
}
// 更新字段(排除 dict_code
_, err = o.Update(dictType, "dict_name", "parent_id", "status", "sort", "remark", "update_by", "update_time")
if err != nil {
return fmt.Errorf("更新字典类型失败: %v", err)
}
// 清除相关缓存
clearDictCache(dictType.TenantId, dictType.DictCode)
return nil
}
// DeleteDictType 删除字典类型(逻辑删除)
func DeleteDictType(id int, tenantId int) error {
o := orm.NewOrm()
// 检查是否存在
dictType := &models.DictType{Id: id}
err := o.Read(dictType)
if err != nil {
return fmt.Errorf("字典类型不存在: %v", err)
}
// 检查租户权限
if dictType.TenantId != tenantId {
return fmt.Errorf("无权操作该字典类型")
}
// 检查是否关联字典项
var count int
err = o.Raw("SELECT COUNT(*) FROM sys_dict_item WHERE dict_type_id = ? AND is_deleted = 0", id).QueryRow(&count)
if err == nil && count > 0 {
return fmt.Errorf("该字典类型下存在字典项,请先删除字典项")
}
// 逻辑删除
dictType.IsDeleted = 1
dictType.UpdateTime = time.Now()
_, err = o.Update(dictType, "is_deleted", "update_time")
if err != nil {
return fmt.Errorf("删除字典类型失败: %v", err)
}
// 清除相关缓存
clearDictCache(dictType.TenantId, dictType.DictCode)
return nil
}
// GetDictTypeById 根据ID获取字典类型
func GetDictTypeById(id int) (*models.DictType, error) {
o := orm.NewOrm()
dictType := &models.DictType{Id: id}
err := o.Read(dictType)
if err != nil {
return nil, err
}
if dictType.IsDeleted == 1 {
return nil, fmt.Errorf("字典类型已删除")
}
return dictType, nil
}
// GetDictTypes 获取字典类型列表
func GetDictTypes(tenantId int, parentId int, status *int8) ([]*models.DictType, error) {
o := orm.NewOrm()
qs := o.QueryTable("sys_dict_type").Filter("is_deleted", 0)
// 租户过滤:0表示平台字典,>0表示租户字典
if tenantId > 0 {
// 租户用户只能看到自己的字典和平台字典
qs = qs.Filter("tenant_id__in", 0, tenantId)
} else {
// 平台用户只能看到平台字典
qs = qs.Filter("tenant_id", 0)
}
// 父级过滤
if parentId >= 0 {
qs = qs.Filter("parent_id", parentId)
}
// 状态过滤
if status != nil {
qs = qs.Filter("status", *status)
}
var dictTypes []*models.DictType
_, err := qs.OrderBy("sort", "create_time").All(&dictTypes)
return dictTypes, err
}
// ========== 字典项相关服务 ==========
// AddDictItem 添加字典项
func AddDictItem(dictItem *models.DictItem) (int64, error) {
o := orm.NewOrm()
// 检查字典类型是否存在
dictType := &models.DictType{Id: dictItem.DictTypeId}
err := o.Read(dictType)
if err != nil {
return 0, fmt.Errorf("字典类型不存在: %v", err)
}
if dictType.IsDeleted == 1 || dictType.Status == 0 {
return 0, fmt.Errorf("字典类型已删除或已禁用")
}
// 检查同一字典类型下 dict_value 是否已存在
existItem := &models.DictItem{}
err = o.Raw("SELECT id FROM sys_dict_item WHERE dict_type_id = ? AND dict_value = ? AND is_deleted = 0",
dictItem.DictTypeId, dictItem.DictValue).QueryRow(existItem)
if err == nil {
return 0, fmt.Errorf("字典值 %s 已存在", dictItem.DictValue)
}
// 插入新字典项
id, err := o.Insert(dictItem)
if err != nil {
return 0, fmt.Errorf("添加字典项失败: %v", err)
}
// 清除相关缓存
clearDictCacheByTypeId(dictItem.DictTypeId)
return id, nil
}
// UpdateDictItem 更新字典项
func UpdateDictItem(dictItem *models.DictItem) error {
o := orm.NewOrm()
// 获取原字典项信息
oldItem := &models.DictItem{Id: dictItem.Id}
err := o.Read(oldItem)
if err != nil {
return fmt.Errorf("字典项不存在: %v", err)
}
// 不允许修改 dict_value(避免业务存储值与字典不匹配)
if oldItem.DictValue != dictItem.DictValue {
return fmt.Errorf("不允许修改字典值")
}
// 如果修改了 dict_type_id,需要检查新类型是否存在
if oldItem.DictTypeId != dictItem.DictTypeId {
dictType := &models.DictType{Id: dictItem.DictTypeId}
err = o.Read(dictType)
if err != nil {
return fmt.Errorf("字典类型不存在: %v", err)
}
}
// 更新字段(排除 dict_value
_, err = o.Update(dictItem, "dict_label", "dict_type_id", "parent_id", "status", "sort", "color", "icon", "remark", "update_by", "update_time")
if err != nil {
return fmt.Errorf("更新字典项失败: %v", err)
}
// 清除相关缓存
clearDictCacheByTypeId(oldItem.DictTypeId)
if oldItem.DictTypeId != dictItem.DictTypeId {
clearDictCacheByTypeId(dictItem.DictTypeId)
}
return nil
}
// DeleteDictItem 删除字典项(逻辑删除)
func DeleteDictItem(id int) error {
o := orm.NewOrm()
// 检查是否存在
dictItem := &models.DictItem{Id: id}
err := o.Read(dictItem)
if err != nil {
return fmt.Errorf("字典项不存在: %v", err)
}
// 检查是否有子项
var count int
err = o.Raw("SELECT COUNT(*) FROM sys_dict_item WHERE parent_id = ? AND is_deleted = 0", id).QueryRow(&count)
if err == nil && count > 0 {
return fmt.Errorf("该字典项下存在子项,请先删除子项")
}
// 逻辑删除
dictItem.IsDeleted = 1
dictItem.UpdateTime = time.Now()
_, err = o.Update(dictItem, "is_deleted", "update_time")
if err != nil {
return fmt.Errorf("删除字典项失败: %v", err)
}
// 清除相关缓存
clearDictCacheByTypeId(dictItem.DictTypeId)
return nil
}
// GetDictItemById 根据ID获取字典项
func GetDictItemById(id int) (*models.DictItem, error) {
o := orm.NewOrm()
dictItem := &models.DictItem{Id: id}
err := o.Read(dictItem)
if err != nil {
return nil, err
}
if dictItem.IsDeleted == 1 {
return nil, fmt.Errorf("字典项已删除")
}
return dictItem, nil
}
// GetDictItems 获取字典项列表
func GetDictItems(dictTypeId int, parentId *int, status *int8) ([]*models.DictItem, error) {
o := orm.NewOrm()
qs := o.QueryTable("sys_dict_item").Filter("is_deleted", 0).Filter("dict_type_id", dictTypeId)
// 父级过滤
if parentId != nil {
qs = qs.Filter("parent_id", *parentId)
}
// 状态过滤
if status != nil {
qs = qs.Filter("status", *status)
}
var dictItems []*models.DictItem
_, err := qs.OrderBy("sort", "create_time").All(&dictItems)
return dictItems, err
}
// GetDictItemsByCode 根据字典编码获取字典项列表(支持缓存)
func GetDictItemsByCode(dictCode string, tenantId int, includeDisabled bool) ([]*models.DictItem, error) {
o := orm.NewOrm()
// 先查询字典类型
dictType := &models.DictType{}
err := o.Raw("SELECT * FROM sys_dict_type WHERE dict_code = ? AND tenant_id = ? AND is_deleted = 0 AND status = 1",
dictCode, tenantId).QueryRow(dictType)
if err != nil {
// 如果租户字典不存在,尝试查询平台字典
if tenantId > 0 {
err = o.Raw("SELECT * FROM sys_dict_type WHERE dict_code = ? AND tenant_id = 0 AND is_deleted = 0 AND status = 1",
dictCode).QueryRow(dictType)
if err != nil {
return []*models.DictItem{}, nil // 返回空列表而不是错误
}
} else {
return []*models.DictItem{}, nil
}
}
// 查询字典项
qs := o.QueryTable("sys_dict_item").Filter("dict_type_id", dictType.Id).Filter("is_deleted", 0)
if !includeDisabled {
qs = qs.Filter("status", 1)
}
var dictItems []*models.DictItem
_, err = qs.OrderBy("sort", "create_time").All(&dictItems)
if err != nil {
return nil, err
}
return dictItems, nil
}
// ========== 缓存相关(简化版,实际应使用Redis) ==========
// 清除字典缓存(简化实现,实际应使用Redis)
func clearDictCache(tenantId int, dictCode string) {
// TODO: 实现Redis缓存清除
// 格式:SYS_DICT:ITEM:{tenantId}:{dictCode}
}
// 根据字典类型ID清除缓存
func clearDictCacheByTypeId(dictTypeId int) {
o := orm.NewOrm()
dictType := &models.DictType{Id: dictTypeId}
err := o.Read(dictType)
if err == nil {
clearDictCache(dictType.TenantId, dictType.DictCode)
}
}
// ========== 批量操作 ==========
// BatchUpdateDictItemSort 批量更新字典项排序
func BatchUpdateDictItemSort(items []struct {
Id int `json:"id"`
Sort int `json:"sort"`
}) error {
o := orm.NewOrm()
for _, item := range items {
dictItem := &models.DictItem{Id: item.Id}
err := o.Read(dictItem)
if err != nil {
continue
}
dictItem.Sort = item.Sort
dictItem.UpdateTime = time.Now()
_, err = o.Update(dictItem, "sort", "update_time")
if err != nil {
return fmt.Errorf("更新字典项排序失败: %v", err)
}
}
return nil
}
+45
View File
@@ -0,0 +1,45 @@
-- 字典类型表
CREATE TABLE IF NOT EXISTS `sys_dict_type` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID(0表示平台字典,>0表示租户字典)',
`dict_code` varchar(50) NOT NULL COMMENT '字典编码(唯一,如 USER_STATUS',
`dict_name` varchar(100) NOT NULL COMMENT '字典名称(如 用户状态)',
`parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父级字典ID0表示一级字典)',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0-禁用,1-启用)',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_dict_code_tenant` (`dict_code`, `is_deleted`),
KEY `idx_parent_id` (`parent_id`, `is_deleted`),
KEY `idx_status` (`status`, `is_deleted`),
KEY `idx_tenant_id` (`tenant_id`, `is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表';
-- 字典项表
CREATE TABLE IF NOT EXISTS `sys_dict_item` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`dict_type_id` bigint(20) NOT NULL COMMENT '字典类型ID',
`dict_label` varchar(100) NOT NULL COMMENT '字典标签(显示值,如 正常)',
`dict_value` varchar(100) NOT NULL COMMENT '字典值(存储值,如 1',
`parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父级字典项ID0表示一级项)',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0-禁用,1-启用)',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号',
`color` varchar(20) DEFAULT NULL COMMENT '颜色标记(如 #1890ff',
`icon` varchar(50) DEFAULT NULL COMMENT '图标(如 el-icon-success',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)',
PRIMARY KEY (`id`),
KEY `idx_dict_type_parent_status` (`dict_type_id`, `parent_id`, `status`, `is_deleted`),
UNIQUE KEY `uk_dict_type_value` (`dict_type_id`, `dict_value`, `is_deleted`),
KEY `idx_parent_id` (`parent_id`, `status`, `is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字典项表';