diff --git a/controllers/admin_menu.go b/controllers/admin_menu.go new file mode 100644 index 0000000..30b7537 --- /dev/null +++ b/controllers/admin_menu.go @@ -0,0 +1,335 @@ +package controllers + +import ( + "encoding/json" + "io" + "server/models" + "strconv" + "strings" + + beego "github.com/beego/beego/v2/server/web" +) + +// AdminMenuController 后台菜单控制器 +type AdminMenuController struct { + beego.Controller +} + +type menuPayload struct { + Pid *int64 `json:"pid"` + Title *string `json:"title"` + Path *string `json:"path"` + ComponentPath *string `json:"component_path"` + Icon *string `json:"icon"` + Sort *int64 `json:"sort"` + Status *int8 `json:"status"` + IsVisible *int8 `json:"is_visible"` + IsPlatform *int8 `json:"is_platform"` + Type *int8 `json:"type"` + Permission *string `json:"permission"` +} + +// GetMenu 获取指定用户可见的菜单列表(简化版:当前先忽略用户权限,返回全部启用且平台端菜单) +// 路由示例:GET /platform/menu/1 +func (c *AdminMenuController) GetMenu() { + // 从路由参数中解析用户 ID,占位保留,方便后续按用户权限过滤 + _ = c.Ctx.Input.Param(":id") + + // 查询所有启用且标记为平台端的菜单 + var menus []models.SystemMenu + qs := models.Orm. + QueryTable(new(models.SystemMenu)). + Filter("status", 1). + Filter("is_platform", 1) + _, err := qs.All(&menus) + if err != nil { + c.Data["json"] = map[string]interface{}{ + "code": 500, + "msg": "获取菜单失败: " + err.Error(), + "data": nil, + } + _ = c.ServeJSON() + return + } + + // 将平铺的菜单列表构建为树形结构 + menuTree := buildMenuTree(menus, 0) + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": menuTree, + } + _ = c.ServeJSON() +} + +// GetAllMenus 获取平台端全部菜单(用于菜单管理界面) +// 路由:GET /platform/allmenu +func (c *AdminMenuController) GetAllMenus() { + var menus []models.SystemMenu + cid, _ := c.GetInt("cid") + + qs := models.Orm.QueryTable(new(models.SystemMenu)) + // 菜单管理默认返回全量菜单;仅在明确传 cid 时按分类筛选 + // cid: 1平台角色 -> 平台菜单;2租户角色 -> 租户菜单 + if cid == 1 { + qs = qs.Filter("is_platform", 1) + } else if cid == 2 { + qs = qs.Filter("is_platform", 0) + } + _, err := qs.All(&menus) + if err != nil { + c.Data["json"] = map[string]interface{}{ + "code": 500, + "msg": "获取菜单失败: " + err.Error(), + "data": nil, + } + _ = c.ServeJSON() + return + } + + tree := buildMenuTree(menus, 0) + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": tree, + } + _ = c.ServeJSON() +} + +// menuNode 用于 JSON 返回的菜单结构 +type menuNode struct { + ID uint64 `json:"id"` + Pid int64 `json:"pid"` + Title string `json:"title"` + Path string `json:"path,omitempty"` + ComponentPath string `json:"component_path,omitempty"` + Icon string `json:"icon,omitempty"` + Sort int64 `json:"sort"` + Status int8 `json:"status"` + IsVisible *int8 `json:"is_visible,omitempty"` + IsPlatform *int8 `json:"is_platform,omitempty"` + Type int8 `json:"type"` + Permission string `json:"permission,omitempty"` + Children []*menuNode `json:"children,omitempty"` +} + +// buildMenuTree 将菜单列表构建成树结构 +func buildMenuTree(menus []models.SystemMenu, pid int64) []*menuNode { + var tree []*menuNode + for _, m := range menus { + if m.Pid == pid { + node := &menuNode{ + ID: m.ID, + Pid: m.Pid, + Title: m.Title, + Sort: m.Sort, + Status: m.Status, + IsVisible: m.IsVisible, + IsPlatform: m.IsPlatform, + Type: m.Type, + } + if m.Path != nil { + node.Path = *m.Path + } + if m.ComponentPath != nil { + node.ComponentPath = *m.ComponentPath + } + if m.Icon != nil { + node.Icon = *m.Icon + } + if m.Permission != nil { + node.Permission = *m.Permission + } + + // 递归查找子菜单 + children := buildMenuTree(menus, int64(m.ID)) + if len(children) > 0 { + node.Children = children + } + tree = append(tree, node) + } + } + return tree +} + +// UpdateMenuStatus 更新菜单状态 +// 路由:PATCH /platform/menu/status/:id +func (c *AdminMenuController) UpdateMenuStatus() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} + _ = c.ServeJSON() + return + } + + var body struct { + Status *int8 `json:"status"` + } + rawBody, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(rawBody, &body); err != nil || body.Status == nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.SystemMenu)). + Filter("id", id). + Update(map[string]interface{}{"status": *body.Status}) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "success": true} + _ = c.ServeJSON() +} + +// CreateMenu 创建菜单 +// 路由:POST /platform/createmenu +func (c *AdminMenuController) CreateMenu() { + payload, ok := c.parseMenuPayload(true) + if !ok { + return + } + + menu := models.SystemMenu{ + Pid: valueInt64(payload.Pid, 0), + Title: strings.TrimSpace(valueString(payload.Title, "")), + Sort: valueInt64(payload.Sort, 0), + Status: valueInt8(payload.Status, 1), + IsVisible: ptrInt8(valueInt8(payload.IsVisible, 1)), + IsPlatform: ptrInt8(valueInt8(payload.IsPlatform, 1)), + Type: valueInt8(payload.Type, 1), + } + + menu.Path = ptrString(valueString(payload.Path, "")) + menu.ComponentPath = ptrString(valueString(payload.ComponentPath, "")) + menu.Icon = ptrString(valueString(payload.Icon, "")) + menu.Permission = ptrString(valueString(payload.Permission, "")) + + id, err := models.Orm.Insert(&menu) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "创建成功", + "data": map[string]interface{}{"id": id}, + } + _ = c.ServeJSON() +} + +// UpdateMenu 更新菜单 +// 路由:PUT /platform/updatemenu/:id +func (c *AdminMenuController) UpdateMenu() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} + _ = c.ServeJSON() + return + } + + payload, ok := c.parseMenuPayload(false) + if !ok { + return + } + + update := map[string]interface{}{ + "pid": valueInt64(payload.Pid, 0), + "title": strings.TrimSpace(valueString(payload.Title, "")), + "path": valueString(payload.Path, ""), + "component_path": valueString(payload.ComponentPath, ""), + "icon": valueString(payload.Icon, ""), + "sort": valueInt64(payload.Sort, 0), + "status": valueInt8(payload.Status, 1), + "is_visible": valueInt8(payload.IsVisible, 1), + "is_platform": valueInt8(payload.IsPlatform, 1), + "type": valueInt8(payload.Type, 1), + "permission": valueString(payload.Permission, ""), + } + + _, err = models.Orm.QueryTable(new(models.SystemMenu)). + Filter("id", id). + Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// DeleteMenu 删除菜单 +// 路由:DELETE /platform/deletemenu/:id +func (c *AdminMenuController) DeleteMenu() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Delete() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功", "success": true} + _ = c.ServeJSON() +} + +func (c *AdminMenuController) parseMenuPayload(needTitle bool) (*menuPayload, bool) { + rawBody, _ := io.ReadAll(c.Ctx.Request.Body) + var payload menuPayload + if err := json.Unmarshal(rawBody, &payload); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return nil, false + } + if needTitle && strings.TrimSpace(valueString(payload.Title, "")) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "菜单名称不能为空"} + _ = c.ServeJSON() + return nil, false + } + return &payload, true +} + +func valueString(v *string, def string) string { + if v == nil { + return def + } + return *v +} + +func valueInt8(v *int8, def int8) int8 { + if v == nil { + return def + } + return *v +} + +func valueInt64(v *int64, def int64) int64 { + if v == nil { + return def + } + return *v +} + +func ptrString(v string) *string { + return &v +} + +func ptrInt8(v int8) *int8 { + return &v +} + diff --git a/controllers/platform_admin_user.go b/controllers/platform_admin_user.go new file mode 100644 index 0000000..1ab0a79 --- /dev/null +++ b/controllers/platform_admin_user.go @@ -0,0 +1,303 @@ +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformAdminUserController 平台管理员用户管理(yz_admin_user) +type PlatformAdminUserController struct { + beego.Controller +} + +type adminUserDTO struct { + ID uint64 `json:"id"` + Account string `json:"account"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Qq *string `json:"qq"` + Sex uint8 `json:"sex"` + Avatar *string `json:"avatar"` + GroupID uint64 `json:"group_id"` + LoginCount uint64 `json:"login_count"` + LastLoginIP *string `json:"last_login_ip"` + Status uint8 `json:"status"` + CreateTime string `json:"create_time"` + UpdateTime *string `json:"update_time"` +} + +func toAdminUserDTO(u models.AdminUser) adminUserDTO { + var updateTime *string + if u.UpdateTime != nil { + s := u.UpdateTime.Format("2006-01-02 15:04:05") + updateTime = &s + } + return adminUserDTO{ + ID: u.ID, + Account: u.Account, + Name: u.Name, + Phone: u.Phone, + Email: u.Email, + Qq: u.Qq, + Sex: u.Sex, + Avatar: u.Avatar, + GroupID: u.RoleID, + LoginCount: u.LoginCount, + LastLoginIP: u.LastLoginIP, + Status: u.Status, + CreateTime: u.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: updateTime, + } +} + +// GetAllUsers 获取全部平台管理员用户 +// GET /platform/getAllUsers +func (c *PlatformAdminUserController) GetAllUsers() { + rows, total, err := models.ListAdminUsers() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"} + _ = c.ServeJSON() + return + } + list := make([]adminUserDTO, 0, len(rows)) + for _, u := range rows { + list = append(list, toAdminUserDTO(u)) + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": list, "total": total}, + } + _ = c.ServeJSON() +} + +// GetUserInfo 获取用户详情 +// GET /platform/getUserInfo/:id +func (c *PlatformAdminUserController) GetUserInfo() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + u, err := models.GetAdminUserByID(id) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": toAdminUserDTO(*u), + } + _ = c.ServeJSON() +} + +type adminAddUserPayload struct { + Account string `json:"account"` + Password string `json:"password"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Qq *string `json:"qq"` + Sex *uint8 `json:"sex"` + Avatar *string `json:"avatar"` + GroupID *uint64 `json:"group_id"` + Status *uint8 `json:"status"` +} + +// AddUser 添加平台管理员用户(仅写 yz_admin_user,不处理 tid) +// POST /platform/addUser +func (c *PlatformAdminUserController) AddUser() { + var p adminAddUserPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + p.Account = strings.TrimSpace(p.Account) + p.Password = strings.TrimSpace(p.Password) + if p.Account == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + if p.Password == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + + status := uint8(1) + if p.Status != nil { + status = *p.Status + } + sex := uint8(0) + if p.Sex != nil { + sex = *p.Sex + } + groupID := uint64(1) + if p.GroupID != nil && *p.GroupID != 0 { + groupID = *p.GroupID + } + + id, err := models.CreateAdminUser(p.Account, p.Password, p.Name, p.Phone, p.Email, p.Qq, p.Avatar, sex, groupID, status) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"id": id}, + } + _ = c.ServeJSON() +} + +type editUserPayload struct { + Account *string `json:"account"` + Password *string `json:"password"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Qq *string `json:"qq"` + Sex *uint8 `json:"sex"` + Avatar *string `json:"avatar"` + GroupID *uint64 `json:"group_id"` + Status *uint8 `json:"status"` +} + +// EditUser 编辑用户信息(password 可选,存在则修改) +// POST /platform/editUser/:id +func (c *PlatformAdminUserController) EditUser() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + var p editUserPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + fields := map[string]interface{}{} + if p.Account != nil { + acc := strings.TrimSpace(*p.Account) + if acc != "" { + fields["account"] = acc + } + } + if p.Name != nil { + fields["name"] = *p.Name + } + if p.Phone != nil { + fields["phone"] = *p.Phone + } + if p.Email != nil { + fields["email"] = *p.Email + } + if p.Qq != nil { + fields["qq"] = *p.Qq + } + if p.Sex != nil { + fields["sex"] = *p.Sex + } + if p.Avatar != nil { + fields["avatar"] = *p.Avatar + } + if p.GroupID != nil && *p.GroupID != 0 { + fields["role_id"] = *p.GroupID + } + if p.Status != nil { + fields["status"] = *p.Status + } + + if len(fields) > 0 { + if err := models.UpdateAdminUser(id, fields); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "编辑失败"} + _ = c.ServeJSON() + return + } + } + if p.Password != nil && strings.TrimSpace(*p.Password) != "" { + if err := models.ChangeAdminUserPassword(id, *p.Password); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "密码修改失败"} + _ = c.ServeJSON() + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteUser 删除用户 +// DELETE /platform/deleteUser/:id +func (c *PlatformAdminUserController) DeleteUser() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + if err := models.DeleteAdminUser(id); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +type changePasswordPayload struct { + ID uint64 `json:"id"` + Password string `json:"password"` +} + +// ChangePassword 修改密码 +// POST /platform/changePassword +func (c *PlatformAdminUserController) ChangePassword() { + var p changePasswordPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + if p.ID == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + if strings.TrimSpace(p.Password) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + if err := models.ChangeAdminUserPassword(p.ID, p.Password); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "修改失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} + _ = c.ServeJSON() +} + diff --git a/controllers/platform_auth.go b/controllers/platform_auth.go index c7d933e..cbc3fdf 100644 --- a/controllers/platform_auth.go +++ b/controllers/platform_auth.go @@ -53,7 +53,7 @@ func (c *PlatformAuthController) Login() { } // 控制器只做 HTTP 解析与响应编排,业务逻辑放 services 层 - token, err := services.PlatformLogin(req.Account, req.Password) + token, loginUser, err := services.PlatformLogin(req.Account, req.Password) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 401, @@ -68,14 +68,12 @@ func (c *PlatformAuthController) Login() { "msg": "登录成功", "data": map[string]interface{}{ "token": token, - // user 结构用于前端 authStore 兼容旧格式 "user": map[string]interface{}{ - "id": 1, - "account": req.Account, - "name": "平台管理员", - "group_id": "", - "tid": "", - "avatar": "", + "id": loginUser.ID, + "account": loginUser.Account, + "name": loginUser.Name, + "rid": loginUser.Rid, + "avatar": loginUser.Avatar, }, }, } diff --git a/controllers/platform_role.go b/controllers/platform_role.go new file mode 100644 index 0000000..8a40e23 --- /dev/null +++ b/controllers/platform_role.go @@ -0,0 +1,202 @@ +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformRoleController 平台角色管理(yz_admin_role) +type PlatformRoleController struct { + beego.Controller +} + +type rolePayload struct { + Cid *uint8 `json:"cid"` + Name string `json:"name"` + Status *uint8 `json:"status"` + Rights interface{} `json:"rights"` +} + +func normalizeRights(v interface{}) *string { + if v == nil { + return nil + } + switch t := v.(type) { + case string: + s := strings.TrimSpace(t) + if s == "" { + return nil + } + return &s + default: + b, err := json.Marshal(v) + if err != nil { + return nil + } + s := string(b) + return &s + } +} + +// GetAllRoles 获取角色列表 +// GET /platform/allRoles +func (c *PlatformRoleController) GetAllRoles() { + var rows []models.AdminRole + _, err := models.Orm.QueryTable(new(models.AdminRole)). + OrderBy("-id"). + All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} + _ = c.ServeJSON() +} + +// GetRoleByID 获取角色详情 +// GET /platform/roles/:id +func (c *PlatformRoleController) GetRoleByID() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + role := models.AdminRole{ID: id} + if err := models.Orm.Read(&role); err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "角色不存在"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": role} + _ = c.ServeJSON() +} + +// CreateRole 创建角色 +// POST /platform/roles +func (c *PlatformRoleController) CreateRole() { + var p rolePayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + p.Name = strings.TrimSpace(p.Name) + if p.Name == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "name 不能为空"} + _ = c.ServeJSON() + return + } + + status := uint8(1) + if p.Status != nil { + status = *p.Status + } + cid := uint8(1) + if p.Cid != nil { + cid = *p.Cid + } + if cid != 1 && cid != 2 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "cid 仅支持 1/2"} + _ = c.ServeJSON() + return + } + rights := normalizeRights(p.Rights) + role := &models.AdminRole{ + Cid: cid, + Name: p.Name, + Status: status, + Rights: rights, + } + id, err := models.Orm.Insert(role) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +// UpdateRole 更新角色 +// PUT /platform/roles/:id +func (c *PlatformRoleController) UpdateRole() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + + var p rolePayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + update := map[string]interface{}{} + if strings.TrimSpace(p.Name) != "" { + update["name"] = strings.TrimSpace(p.Name) + } + if p.Status != nil { + update["status"] = *p.Status + } + if p.Cid != nil { + if *p.Cid != 1 && *p.Cid != 2 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "cid 仅支持 1/2"} + _ = c.ServeJSON() + return + } + update["cid"] = *p.Cid + } + if p.Rights != nil { + update["rights"] = normalizeRights(p.Rights) + } + if len(update) == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} + _ = c.ServeJSON() + return + } + + _, err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", id).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteRole 删除角色 +// DELETE /platform/roles/:id +func (c *PlatformRoleController) DeleteRole() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + _, err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", id).Delete() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + diff --git a/controllers/platform_tenant.go b/controllers/platform_tenant.go new file mode 100644 index 0000000..ef57026 --- /dev/null +++ b/controllers/platform_tenant.go @@ -0,0 +1,333 @@ +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + "time" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformTenantController 平台端租户管理 +type PlatformTenantController struct { + beego.Controller +} + +type tenantDTO struct { + ID uint64 `json:"id"` + TenantCode string `json:"tenant_code"` + TenantName string `json:"tenant_name"` + ContactPerson string `json:"contact_person"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` + Status int8 `json:"status"` + Remark string `json:"remark"` + CreateTime *time.Time `json:"create_time,omitempty"` + UpdateTime *time.Time `json:"update_time,omitempty"` + DeleteTime *time.Time `json:"delete_time,omitempty"` +} + +func toTenantDTO(t models.Tenant) tenantDTO { + ct := t.CreateTime + ut := t.UpdateTime + return tenantDTO{ + ID: t.ID, + TenantCode: t.TenantCode, + TenantName: t.TenantName, + ContactPerson: t.ContactPerson, + ContactPhone: t.ContactPhone, + ContactEmail: t.ContactEmail, + Address: t.Address, + Worktime: t.Worktime, + Status: t.Status, + Remark: t.Remark, + CreateTime: &ct, + UpdateTime: &ut, + DeleteTime: t.DeleteTime, + } +} + +// GetTenant 获取租户列表 +// GET /platform/tenant/getTenant?page=1&pageSize=10&tenant_name=...&tenant_code=...&contact_person=...&contact_phone=... +func (c *PlatformTenantController) GetTenant() { + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + + tenantName := strings.TrimSpace(c.GetString("tenant_name")) + tenantCode := strings.TrimSpace(c.GetString("tenant_code")) + contactPerson := strings.TrimSpace(c.GetString("contact_person")) + contactPhone := strings.TrimSpace(c.GetString("contact_phone")) + + qs := models.Orm.QueryTable(new(models.Tenant)) + if tenantName != "" { + qs = qs.Filter("tenant_name__icontains", tenantName) + } + if tenantCode != "" { + qs = qs.Filter("tenant_code__icontains", tenantCode) + } + if contactPerson != "" { + qs = qs.Filter("contact_person__icontains", contactPerson) + } + if contactPhone != "" { + qs = qs.Filter("contact_phone__icontains", contactPhone) + } + + total, err := qs.Count() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取租户失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + var rows []models.Tenant + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取租户失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + list := make([]tenantDTO, 0, len(rows)) + for _, t := range rows { + list = append(list, toTenantDTO(t)) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// GetTenantDetail 获取租户详情 +// GET /platform/tenant/getTenantDetail/:id +func (c *PlatformTenantController) GetTenantDetail() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + var t models.Tenant + err = models.Orm.QueryTable(new(models.Tenant)).Filter("id", id).One(&t) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "租户不存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": toTenantDTO(t), + } + _ = c.ServeJSON() +} + +type tenantPayload struct { + TenantCode string `json:"tenant_code"` + TenantName string `json:"tenant_name"` + ContactPerson string `json:"contact_person"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` + Status *int8 `json:"status"` + Remark string `json:"remark"` +} + +func (c *PlatformTenantController) parseTenantPayload() (tenantPayload, error) { + // 优先从表单读取(createTenant 使用 multipart/form-data) + p := tenantPayload{ + TenantCode: strings.TrimSpace(c.GetString("tenant_code")), + TenantName: strings.TrimSpace(c.GetString("tenant_name")), + ContactPerson: strings.TrimSpace(c.GetString("contact_person")), + ContactPhone: strings.TrimSpace(c.GetString("contact_phone")), + ContactEmail: strings.TrimSpace(c.GetString("contact_email")), + Address: strings.TrimSpace(c.GetString("address")), + Worktime: strings.TrimSpace(c.GetString("worktime")), + Remark: strings.TrimSpace(c.GetString("remark")), + } + if s := strings.TrimSpace(c.GetString("status")); s != "" { + if v, err := strconv.ParseInt(s, 10, 8); err == nil { + tmp := int8(v) + p.Status = &tmp + } + } + + // 如果关键字段为空,尝试从 JSON body 解析(editTenant 默认 JSON) + if p.TenantName == "" && p.TenantCode == "" { + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if len(raw) > 0 { + _ = json.Unmarshal(raw, &p) + } + } + return p, nil +} + +// CreateTenant 创建租户 +// POST /platform/tenant/createTenant +func (c *PlatformTenantController) CreateTenant() { + p, _ := c.parseTenantPayload() + if strings.TrimSpace(p.TenantName) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称不能为空"} + _ = c.ServeJSON() + return + } + if strings.TrimSpace(p.TenantCode) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户编码不能为空"} + _ = c.ServeJSON() + return + } + + // 校验编码唯一 + cnt, err := models.Orm.QueryTable(new(models.Tenant)).Filter("tenant_code", p.TenantCode).Count() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + if cnt > 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户编码已存在"} + _ = c.ServeJSON() + return + } + + status := int8(1) + if p.Status != nil { + status = *p.Status + } + + t := models.Tenant{ + TenantCode: p.TenantCode, + TenantName: p.TenantName, + ContactPerson: p.ContactPerson, + ContactPhone: p.ContactPhone, + ContactEmail: p.ContactEmail, + Address: p.Address, + Worktime: p.Worktime, + Status: status, + Remark: p.Remark, + } + + id, err := models.Orm.Insert(&t) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"id": id}, + } + _ = c.ServeJSON() +} + +// EditTenant 编辑租户 +// POST /platform/tenant/editTenant/:id +func (c *PlatformTenantController) EditTenant() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + p, _ := c.parseTenantPayload() + if strings.TrimSpace(p.TenantName) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称不能为空"} + _ = c.ServeJSON() + return + } + + update := map[string]interface{}{ + "tenant_name": p.TenantName, + "contact_person": p.ContactPerson, + "contact_phone": p.ContactPhone, + "contact_email": p.ContactEmail, + "address": p.Address, + "worktime": p.Worktime, + "remark": p.Remark, + } + if p.Status != nil { + update["status"] = *p.Status + } + + _, err = models.Orm.QueryTable(new(models.Tenant)).Filter("id", id).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteTenant 删除租户 +// DELETE /platform/tenant/deleteTenant/:id +func (c *PlatformTenantController) DeleteTenant() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.Tenant)).Filter("id", id).Delete() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// FindTenantCode 校验租户编码是否重复 +// GET /platform/tenant/findTenantCode?tenant_code=xxxxxx +// 返回 code=200 表示可用;非200表示重复/不可用(前端会自动重新生成) +func (c *PlatformTenantController) FindTenantCode() { + code := strings.TrimSpace(c.GetString("tenant_code")) + if code == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tenant_code 不能为空"} + _ = c.ServeJSON() + return + } + + cnt, err := models.Orm.QueryTable(new(models.Tenant)).Filter("tenant_code", code).Count() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "校验失败: " + err.Error()} + _ = c.ServeJSON() + return + } + if cnt > 0 { + c.Data["json"] = map[string]interface{}{"code": 409, "msg": "租户编码已存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "ok"} + _ = c.ServeJSON() +} + diff --git a/controllers/platform_tenant_user.go b/controllers/platform_tenant_user.go new file mode 100644 index 0000000..25f7c9b --- /dev/null +++ b/controllers/platform_tenant_user.go @@ -0,0 +1,289 @@ +package controllers + +import ( + "encoding/json" + "errors" + "io" + "math/rand" + "strconv" + "strings" + "time" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformTenantUserController 平台端租户-用户绑定管理 +type PlatformTenantUserController struct { + beego.Controller +} + +type tenantUserPayload struct { + Tid uint64 `json:"tid"` + Uid uint64 `json:"uid"` + Account *string `json:"account"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Password *string `json:"password"` + IsDefault *int8 `json:"is_default"` + Status *int8 `json:"status"` + Remark *string `json:"remark"` +} + +// GetTenantUserList 获取绑定列表(支持按 tid / uid 过滤) +// GET /platform/tenantUser/list?tid=1&uid=2 +func (c *PlatformTenantUserController) GetTenantUserList() { + tid, _ := c.GetUint64("tid") + uid, _ := c.GetUint64("uid") + + qs := models.Orm.QueryTable(new(models.TenantUser)) + if tid > 0 { + qs = qs.Filter("tid", tid) + } + if uid > 0 { + qs = qs.Filter("uid", uid) + } + + var rows []models.TenantUser + _, err := qs.OrderBy("-is_default", "-id").All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": rows, + "total": len(rows), + }, + } + _ = c.ServeJSON() +} + +// GetTenantUsersByTid 兼容路径参数方式获取租户用户列表 +// GET /platform/getTenantUsers/:tid +func (c *PlatformTenantUserController) GetTenantUsersByTid() { + tidStr := c.Ctx.Input.Param(":tid") + tid, _ := strconv.ParseUint(tidStr, 10, 64) + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + var rows []models.TenantUser + _, err := models.Orm.QueryTable(new(models.TenantUser)). + Filter("tid", tid). + OrderBy("-is_default", "-id"). + All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": rows, "total": len(rows)}, + } + _ = c.ServeJSON() +} + +// GetTenantUserDetail 获取绑定详情 +// GET /platform/tenantUser/detail/:id +func (c *PlatformTenantUserController) GetTenantUserDetail() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + var row models.TenantUser + err = models.Orm.QueryTable(new(models.TenantUser)).Filter("id", id).One(&row) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "记录不存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} + _ = c.ServeJSON() +} + +// CreateTenantUser 创建绑定 +// POST /platform/tenantUser/create +func (c *PlatformTenantUserController) CreateTenantUser() { + p, ok := c.parsePayload() + if !ok { + return + } + if p.Tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + if p.Account == nil || strings.TrimSpace(*p.Account) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + if p.Password == nil || strings.TrimSpace(*p.Password) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + if p.Uid == 0 { + uid, err := generateTenantUID(p.Tid) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "生成租户用户ID失败"} + _ = c.ServeJSON() + return + } + p.Uid = uid + } + + isDefault := int8(0) + status := int8(1) + if p.IsDefault != nil { + isDefault = *p.IsDefault + } + if p.Status != nil { + status = *p.Status + } + + id, err := models.BindTenantUser(p.Tid, p.Uid, p.Account, p.Name, p.Phone, p.Email, p.Password, isDefault, status, p.Remark) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + if isDefault == 1 { + _ = models.SetDefaultTenant(p.Uid, p.Tid) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +// EditTenantUser 编辑绑定 +// POST /platform/tenantUser/edit/:id +func (c *PlatformTenantUserController) EditTenantUser() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + p, ok := c.parsePayload() + if !ok { + return + } + + update := map[string]interface{}{} + if p.Tid > 0 { + update["tid"] = p.Tid + } + if p.Uid > 0 { + update["uid"] = p.Uid + } + if p.Account != nil { + update["account"] = p.Account + } + if p.Name != nil { + update["name"] = p.Name + } + if p.Phone != nil { + update["phone"] = p.Phone + } + if p.Email != nil { + update["email"] = p.Email + } + if p.Password != nil { + update["password"] = p.Password + } + if p.IsDefault != nil { + update["is_default"] = *p.IsDefault + } + if p.Status != nil { + update["status"] = *p.Status + } + if p.Remark != nil { + update["remark"] = p.Remark + } + + if len(update) == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.TenantUser)).Filter("id", id).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + if p.IsDefault != nil && *p.IsDefault == 1 && p.Uid > 0 && p.Tid > 0 { + _ = models.SetDefaultTenant(p.Uid, p.Tid) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteTenantUser 删除绑定 +// DELETE /platform/tenantUser/delete/:id +func (c *PlatformTenantUserController) DeleteTenantUser() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + if err := models.UnbindTenantUser(id); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +func (c *PlatformTenantUserController) parsePayload() (tenantUserPayload, bool) { + var p tenantUserPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return tenantUserPayload{}, false + } + return p, true +} + +func generateTenantUID(tid uint64) (uint64, error) { + rand.Seed(time.Now().UnixNano()) + for i := 0; i < 8; i++ { + uid := uint64(10000000 + rand.Intn(90000000)) + cnt, err := models.Orm.QueryTable(new(models.TenantUser)). + Filter("tid", tid). + Filter("uid", uid). + Count() + if err != nil { + return 0, err + } + if cnt == 0 { + return uid, nil + } + } + return 0, errors.New("uid collision") +} + diff --git a/controllers/platform_user.go b/controllers/platform_user.go new file mode 100644 index 0000000..df2ea4e --- /dev/null +++ b/controllers/platform_user.go @@ -0,0 +1,100 @@ +package controllers + +import ( + "encoding/json" + "io" + "math/rand" + "strings" + "time" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformUserController 平台端用户相关(简化:当前用户信息落在 yz_tenant_user) +type PlatformUserController struct { + beego.Controller +} + +type addUserPayload struct { + Tid uint64 `json:"tid"` + Account string `json:"account"` + Password string `json:"password"` + Name string `json:"name"` + Phone string `json:"phone"` + Email string `json:"email"` + Status *int8 `json:"status"` + Remark *string `json:"remark"` +} + +// AddUser 添加用户(绑定到租户) +// POST /platform/addUser +func (c *PlatformUserController) AddUser() { + var p addUserPayload + + // 兼容 JSON body + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + p.Account = strings.TrimSpace(p.Account) + p.Password = strings.TrimSpace(p.Password) + p.Name = strings.TrimSpace(p.Name) + p.Phone = strings.TrimSpace(p.Phone) + p.Email = strings.TrimSpace(p.Email) + + if p.Tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + if p.Account == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + if p.Password == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + + status := int8(1) + if p.Status != nil { + status = *p.Status + } + + // 生成 uid:8位数字即可(10000000~99999999) + rand.Seed(time.Now().UnixNano()) + var uid uint64 + for i := 0; i < 5; i++ { + uid = uint64(10000000 + rand.Intn(90000000)) + // 尝试写入(若冲突由唯一索引兜底,外层再重试) + account := &p.Account + name := &p.Name + phone := &p.Phone + email := &p.Email + password := &p.Password + + _, err := models.BindTenantUser(p.Tid, uid, account, name, phone, email, password, 0, status, p.Remark) + if err == nil { + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"tid": p.Tid, "uid": uid}, + } + _ = c.ServeJSON() + return + } + // 轻量重试 + time.Sleep(5 * time.Millisecond) + } + + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败,请重试"} + _ = c.ServeJSON() +} + diff --git a/models/admin_role.go b/models/admin_role.go new file mode 100644 index 0000000..06b0466 --- /dev/null +++ b/models/admin_role.go @@ -0,0 +1,20 @@ +package models + +import "time" + +// AdminRole 平台角色表 yz_admin_role +type AdminRole struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Cid uint8 `orm:"column(cid);default(1)" json:"cid"` // 1平台角色 2租户角色 + Name string `orm:"column(name);size(32)" json:"name"` + Status uint8 `orm:"column(status);default(1)" json:"status"` + Rights *string `orm:"column(rights);type(text);null" json:"rights"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *AdminRole) TableName() string { + return "yz_admin_role" +} + diff --git a/models/admin_user.go b/models/admin_user.go new file mode 100644 index 0000000..ba30a05 --- /dev/null +++ b/models/admin_user.go @@ -0,0 +1,96 @@ +package models + +import ( + "crypto/md5" + "encoding/hex" + "strings" + "time" +) + +// AdminUser 平台管理员信息表 yz_admin_user +type AdminUser struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Account string `orm:"column(account);size(64)" json:"account"` + Password string `orm:"column(password);size(32)" json:"-"` + Name *string `orm:"column(name);size(32);null" json:"name"` + Phone *string `orm:"column(phone);size(18);null" json:"phone"` + Email *string `orm:"column(email);size(255);null" json:"email"` + Qq *string `orm:"column(qq);size(16);null" json:"qq"` + Sex uint8 `orm:"column(sex);default(0)" json:"sex"` + Avatar *string `orm:"column(avatar);size(255);null" json:"avatar"` + RoleID uint64 `orm:"column(role_id)" json:"group_id"` + LoginCount uint64 `orm:"column(login_count);default(0)" json:"login_count"` + LastLoginIP *string `orm:"column(last_login_ip);size(255);null" json:"last_login_ip"` + Status uint8 `orm:"column(status);default(1)" json:"status"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *AdminUser) TableName() string { + return "yz_admin_user" +} + +func md5Hex(s string) string { + sum := md5.Sum([]byte(s)) + return hex.EncodeToString(sum[:]) +} + +func NormalizeAccount(s string) string { + return strings.TrimSpace(s) +} + +// CreateAdminUser 创建平台管理员用户(password 会被 md5) +func CreateAdminUser(account, password string, name, phone, email, qq, avatar *string, sex uint8, roleID uint64, status uint8) (uint64, error) { + u := &AdminUser{ + Account: NormalizeAccount(account), + Password: md5Hex(strings.TrimSpace(password)), + Name: name, + Phone: phone, + Email: email, + Qq: qq, + Avatar: avatar, + Sex: sex, + RoleID: roleID, + Status: status, + } + id, err := Orm.Insert(u) + return uint64(id), err +} + +func GetAdminUserByID(id uint64) (*AdminUser, error) { + u := &AdminUser{ID: id} + if err := Orm.Read(u); err != nil { + return nil, err + } + return u, nil +} + +// UpdateAdminUser 更新用户基础信息(不含 password) +func UpdateAdminUser(id uint64, fields map[string]interface{}) error { + _, err := Orm.QueryTable(new(AdminUser)).Filter("id", id).Update(fields) + return err +} + +func DeleteAdminUser(id uint64) error { + _, err := Orm.QueryTable(new(AdminUser)).Filter("id", id).Delete() + return err +} + +func ChangeAdminUserPassword(id uint64, newPassword string) error { + _, err := Orm.QueryTable(new(AdminUser)).Filter("id", id).Update(map[string]interface{}{ + "password": md5Hex(strings.TrimSpace(newPassword)), + }) + return err +} + +func ListAdminUsers() ([]AdminUser, int64, error) { + var rows []AdminUser + total, err := Orm.QueryTable(new(AdminUser)).Count() + if err != nil { + return nil, 0, err + } + _, err = Orm.QueryTable(new(AdminUser)).OrderBy("-id").All(&rows) + return rows, total, err +} + diff --git a/models/init.go b/models/init.go index b97c24a..436e15c 100644 --- a/models/init.go +++ b/models/init.go @@ -1,8 +1,46 @@ package models -// Init 初始化模型层资源(如:数据库连接、模型注册)。 -// 目前仅做占位以支持当前结构编译通过;后续按实际数据库/模型重写。 +import ( + "fmt" + + beego "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/client/orm" + _ "github.com/go-sql-driver/mysql" +) + +// Orm 全局 ORM 对象,供业务层和控制器使用 +var Orm orm.Ormer + +// Init 初始化模型层资源(数据库连接、模型注册等)。 func Init(_ string) { - // TODO: 初始化数据库连接等 + // 从配置读取数据库连接信息 + user, _ := beego.AppConfig.String("mysqluser") + pass, _ := beego.AppConfig.String("mysqlpass") + urls, _ := beego.AppConfig.String("mysqlurls") + dbname, _ := beego.AppConfig.String("mysqldb") + + if user == "" || urls == "" || dbname == "" { + panic("数据库配置(mysqluser/mysqlurls/mysqldb) 未正确设置") + } + + // 组装 DSN:user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, pass, urls, dbname) + + // 注册默认数据库 + if err := orm.RegisterDataBase("default", "mysql", dsn); err != nil { + panic("注册数据库失败: " + err.Error()) + } + + // 注册模型 + orm.RegisterModel( + new(Tenant), + new(TenantUser), + new(SystemMenu), + new(AdminUser), + new(AdminRole), + ) + + // 创建全局 Ormer + Orm = orm.NewOrm() } diff --git a/models/system_menu.go b/models/system_menu.go new file mode 100644 index 0000000..ba04500 --- /dev/null +++ b/models/system_menu.go @@ -0,0 +1,30 @@ +package models + +import "time" + +// SystemMenu 系统菜单表 yz_system_menu +type SystemMenu struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` // 菜单ID + Pid int64 `orm:"column(pid);default(0)" json:"pid"` // 上级菜单ID + Title string `orm:"column(title);size(50)" json:"title"` // 菜单名称 + Path *string `orm:"column(path);size(200);null" json:"path"` // 路由路径 + ComponentPath *string `orm:"column(component_path);size(255);null" json:"componentPath"` // 组件路径 + Icon *string `orm:"column(icon);size(100);null" json:"icon"` // 菜单图标 + Sort int64 `orm:"column(sort);default(0)" json:"sort"` // 排序号 + Status int8 `orm:"column(status);default(0)" json:"status"` // 状态:1-启用,0-禁用 + IsVisible *int8 `orm:"column(is_visible);null" json:"isVisible"` // 是否显示:1-显示 0-不显示 + IsPlatform *int8 `orm:"column(is_platform);null" json:"isPlatform"` // 是否平台:1-是 0-否 + Type int8 `orm:"column(type)" json:"type"` // 菜单类型:1-目录,2-页面,3-接口 + Permission *string `orm:"column(permission);size(100);null" json:"permission"` // 权限标识(按钮类型时填写) + Creater *string `orm:"column(creater);size(50);null" json:"creater"` // 创建者 + Remark *string `orm:"column(remark);size(500);null" json:"remark"` // 备注 + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` // 创建时间 + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` // 更新时间 + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` // 删除时间 +} + +// TableName 自定义表名 +func (m *SystemMenu) TableName() string { + return "yz_system_menu" +} + diff --git a/models/tenant_user.go b/models/tenant_user.go new file mode 100644 index 0000000..fc1a0cd --- /dev/null +++ b/models/tenant_user.go @@ -0,0 +1,106 @@ +package models + +import "time" + +// TenantUser 租户用户绑定关系表 yz_tenant_user +type TenantUser struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid)" json:"tid"` // 租户ID + Uid uint64 `orm:"column(uid)" json:"uid"` // 用户ID + Account *string `orm:"column(account);size(64);null" json:"account"` // 用户账号(冗余) + Name *string `orm:"column(name);size(64);null" json:"name"` // 用户名称(冗余) + Phone *string `orm:"column(phone);size(20);null" json:"phone"` // 手机号(冗余) + Email *string `orm:"column(email);size(128);null" json:"email"` // 邮箱(冗余) + Password *string `orm:"column(password);size(255);null" json:"password"` // 密码(冗余/可选) + IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` // 是否默认租户 + Status int8 `orm:"column(status);default(1)" json:"status"` // 状态:1启用,0禁用 + Remark *string `orm:"column(remark);size(255);null" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +// TableName 自定义表名 +func (m *TenantUser) TableName() string { + return "yz_tenant_user" +} + +// BindTenantUser 绑定用户到租户(若已存在则更新状态/默认值) +func BindTenantUser(tid, uid uint64, account, name, phone, email, password *string, isDefault, status int8, remark *string) (uint64, error) { + var existed TenantUser + err := Orm.QueryTable(new(TenantUser)). + Filter("tid", tid). + Filter("uid", uid). + One(&existed) + if err == nil { + update := map[string]interface{}{ + "account": account, + "name": name, + "phone": phone, + "email": email, + "password": password, + "status": status, + "is_default": isDefault, + "remark": remark, + } + _, uErr := Orm.QueryTable(new(TenantUser)).Filter("id", existed.ID).Update(update) + return existed.ID, uErr + } + + m := &TenantUser{ + Tid: tid, + Uid: uid, + Account: account, + Name: name, + Phone: phone, + Email: email, + Password: password, + IsDefault: isDefault, + Status: status, + Remark: remark, + } + id, iErr := Orm.Insert(m) + return uint64(id), iErr +} + +// UnbindTenantUser 删除绑定关系 +func UnbindTenantUser(id uint64) error { + _, err := Orm.QueryTable(new(TenantUser)).Filter("id", id).Delete() + return err +} + +// ListTenantUsersByTid 根据租户ID查询绑定关系 +func ListTenantUsersByTid(tid uint64) ([]TenantUser, error) { + var rows []TenantUser + _, err := Orm.QueryTable(new(TenantUser)). + Filter("tid", tid). + OrderBy("-is_default", "-id"). + All(&rows) + return rows, err +} + +// ListTenantBindingsByUid 根据用户ID查询绑定关系 +func ListTenantBindingsByUid(uid uint64) ([]TenantUser, error) { + var rows []TenantUser + _, err := Orm.QueryTable(new(TenantUser)). + Filter("uid", uid). + OrderBy("-is_default", "-id"). + All(&rows) + return rows, err +} + +// SetDefaultTenant 设置用户默认租户(同一用户仅一个默认) +func SetDefaultTenant(uid, tid uint64) error { + _, err := Orm.QueryTable(new(TenantUser)).Filter("uid", uid).Update(map[string]interface{}{ + "is_default": 0, + }) + if err != nil { + return err + } + _, err = Orm.QueryTable(new(TenantUser)). + Filter("uid", uid). + Filter("tid", tid). + Update(map[string]interface{}{"is_default": 1}) + return err +} + diff --git a/routers/backend/backend.go b/routers/backend/backend.go index 57539c0..cf28839 100644 --- a/routers/backend/backend.go +++ b/routers/backend/backend.go @@ -1,7 +1,7 @@ package backend -// Register 注册租户管理端(backend)路由。 -// 目前仅占位,后续按 /backend/* 规则补充具体接口。 +// Register 注册租户端(backend)路由。 +// 该端不包含平台菜单配置接口。 func Register() { } diff --git a/routers/platform/platform.go b/routers/platform/platform.go index 61f15e2..3670384 100644 --- a/routers/platform/platform.go +++ b/routers/platform/platform.go @@ -22,5 +22,44 @@ func Register() { // 找回密码相关 beego.Router("/platform/resetPassword", &controllers.PlatformAuthController{}, "post:ResetPassword") beego.Router("/platform/sendResetCode", &controllers.PlatformAuthController{}, "post:SendResetCode") + + // 平台菜单配置相关 + beego.Router("/platform/menu/:id", &controllers.AdminMenuController{}, "get:GetMenu") + beego.Router("/platform/allmenu", &controllers.AdminMenuController{}, "get:GetAllMenus") + beego.Router("/platform/menu/status/:id", &controllers.AdminMenuController{}, "patch:UpdateMenuStatus") + beego.Router("/platform/createmenu", &controllers.AdminMenuController{}, "post:CreateMenu") + beego.Router("/platform/updatemenu/:id", &controllers.AdminMenuController{}, "put:UpdateMenu") + beego.Router("/platform/deletemenu/:id", &controllers.AdminMenuController{}, "delete:DeleteMenu") + + // 平台租户管理相关 + beego.Router("/platform/tenant/getTenant", &controllers.PlatformTenantController{}, "get:GetTenant") + beego.Router("/platform/tenant/getTenantDetail/:id", &controllers.PlatformTenantController{}, "get:GetTenantDetail") + beego.Router("/platform/tenant/createTenant", &controllers.PlatformTenantController{}, "post:CreateTenant") + beego.Router("/platform/tenant/editTenant/:id", &controllers.PlatformTenantController{}, "post:EditTenant") + beego.Router("/platform/tenant/deleteTenant/:id", &controllers.PlatformTenantController{}, "delete:DeleteTenant") + beego.Router("/platform/tenant/findTenantCode", &controllers.PlatformTenantController{}, "get:FindTenantCode") + + // 平台租户用户绑定相关 + beego.Router("/platform/getTenantUsers/:tid", &controllers.PlatformTenantUserController{}, "get:GetTenantUsersByTid") + beego.Router("/platform/tenantUser/list", &controllers.PlatformTenantUserController{}, "get:GetTenantUserList") + beego.Router("/platform/tenantUser/detail/:id", &controllers.PlatformTenantUserController{}, "get:GetTenantUserDetail") + beego.Router("/platform/tenantUser/create", &controllers.PlatformTenantUserController{}, "post:CreateTenantUser") + beego.Router("/platform/tenantUser/edit/:id", &controllers.PlatformTenantUserController{}, "post:EditTenantUser") + beego.Router("/platform/tenantUser/delete/:id", &controllers.PlatformTenantUserController{}, "delete:DeleteTenantUser") + + // 平台管理员用户管理(yz_admin_user) + beego.Router("/platform/getAllUsers", &controllers.PlatformAdminUserController{}, "get:GetAllUsers") + beego.Router("/platform/getUserInfo/:id", &controllers.PlatformAdminUserController{}, "get:GetUserInfo") + beego.Router("/platform/addUser", &controllers.PlatformAdminUserController{}, "post:AddUser") + beego.Router("/platform/editUser/:id", &controllers.PlatformAdminUserController{}, "post:EditUser") + beego.Router("/platform/deleteUser/:id", &controllers.PlatformAdminUserController{}, "delete:DeleteUser") + beego.Router("/platform/changePassword", &controllers.PlatformAdminUserController{}, "post:ChangePassword") + + // 平台角色管理(yz_admin_role) + beego.Router("/platform/allRoles", &controllers.PlatformRoleController{}, "get:GetAllRoles") + beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "get:GetRoleByID") + beego.Router("/platform/roles", &controllers.PlatformRoleController{}, "post:CreateRole") + beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "put:UpdateRole") + beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "delete:DeleteRole") } diff --git a/services/platform_auth.go b/services/platform_auth.go index 195fe69..0aa663c 100644 --- a/services/platform_auth.go +++ b/services/platform_auth.go @@ -1,37 +1,72 @@ package services import ( + "crypto/md5" + "encoding/hex" "errors" + "strings" + "server/models" "server/pkg/jwtutil" ) -// PlatformLogin 平台登录业务 -// TODO: 后续接真实用户/租户表,这里先做最小可用实现。 -func PlatformLogin(username, password string) (string, error) { - // 临时简单校验:用户名和密码非空 - if username == "" || password == "" { - return "", errors.New("用户名或密码不能为空") - } - - // 测试账号:admin / admin123 - if username != "admin" || password != "admin123" { - return "", errors.New("用户名或密码错误") - } - - // 这里后续应: - // 1. 从平台用户表查询用户 - // 2. 校验密码(含加盐加密) - // 3. 绑定平台/租户信息 - // 目前先返回一个平台用户的 JWT 占位 token - const fakeUserID = 1 - const fakeTenantID = 0 - const userType = "platform" - - token, err := jwtutil.GenerateToken(fakeUserID, username, fakeTenantID, userType) - if err != nil { - return "", err - } - return token, nil +type PlatformLoginUser struct { + ID uint64 + Account string + Name string + Rid uint64 + Avatar string +} + +func md5Hex(s string) string { + sum := md5.Sum([]byte(s)) + return hex.EncodeToString(sum[:]) +} + +// PlatformLogin 平台登录业务(仅允许平台用户 yz_admin_user 登录) +func PlatformLogin(account, password string) (string, *PlatformLoginUser, error) { + account = strings.TrimSpace(account) + password = strings.TrimSpace(password) + if account == "" || password == "" { + return "", nil, errors.New("用户名或密码不能为空") + } + + var user models.AdminUser + err := models.Orm.QueryTable(new(models.AdminUser)). + Filter("account", account). + One(&user) + if err != nil { + return "", nil, errors.New("用户名或密码错误") + } + if user.Password != md5Hex(password) { + return "", nil, errors.New("用户名或密码错误") + } + + if user.Status == 0 { + return "", nil, errors.New("账号已禁用") + } + const fakeTenantID = 0 + const userType = "platform" + token, err := jwtutil.GenerateToken(int(user.ID), user.Account, fakeTenantID, userType) + if err != nil { + return "", nil, err + } + + name := "" + if user.Name != nil { + name = *user.Name + } + avatar := "" + if user.Avatar != nil { + avatar = *user.Avatar + } + loginUser := &PlatformLoginUser{ + ID: user.ID, + Account: user.Account, + Name: name, + Rid: user.RoleID, + Avatar: avatar, + } + return token, loginUser, nil }