优化若干功能

This commit is contained in:
2026-09-09 17:02:29 +08:00
parent fef0233cc4
commit 2f238875e1
16 changed files with 609 additions and 68 deletions
+94
View File
@@ -195,6 +195,100 @@ func (c *BackendAuthController) GetCurrentUser() {
})
}
// GetCurrentUserProfile 当前登录用户的角色 / 部门 / 职位
// GET /backend/getCurrentUserProfile
// 返回 department / position(来自员工档案,按登录账号 + 租户匹配);
// 若均无,则回退到角色名(role_name)。
func (c *BackendAuthController) GetCurrentUserProfile() {
authHeader := c.Ctx.Request.Header.Get("Authorization")
if authHeader == "" {
c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"})
return
}
authParts := strings.SplitN(authHeader, " ", 2)
if len(authParts) != 2 || authParts[0] != "Bearer" {
c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"})
return
}
claims, err := jwtutil.ParseToken(authParts[1])
if err != nil {
c.serveJSON(map[string]interface{}{"code": 401, "msg": "无效的token"})
return
}
if claims.UserType != "backend" && claims.UserType != "app" {
c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"})
return
}
var tenantUser models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("uid", claims.UserID).
Filter("tid", claims.TenantId).
One(&tenantUser); err != nil {
c.serveJSON(map[string]interface{}{"code": 401, "msg": "用户不存在"})
return
}
account := ""
if tenantUser.Account != nil {
account = strings.TrimSpace(*tenantUser.Account)
}
name := ""
if tenantUser.Name != nil {
name = strings.TrimSpace(*tenantUser.Name)
}
// 角色名:按 group_id 查询本租户下的租户角色(cid=2)
roleName := ""
if tenantUser.GroupID > 0 {
var role models.AdminRole
if err := models.Orm.QueryTable(new(models.AdminRole)).
Filter("id", tenantUser.GroupID).
Filter("cid", 2).
Filter("tenant_id", claims.TenantId).
One(&role); err == nil {
roleName = role.Name
}
}
// 部门 / 职位:来自员工档案,按账号 + 租户匹配
department := ""
position := ""
if account != "" {
var emp models.BackendEmployee
qs := models.Orm.QueryTable(new(models.BackendEmployee)).Filter("account", account)
if claims.TenantId > 0 {
qs = qs.Filter("tid", int(claims.TenantId))
}
if err := qs.One(&emp); err == nil {
if emp.Department != nil {
department = strings.TrimSpace(*emp.Department)
}
if emp.Position != nil {
position = strings.TrimSpace(*emp.Position)
}
}
}
// 员工档案的 department 字段存的是组织ID,需转换为组织名称展示
if department != "" {
orgNames := (&BackendEmployeeFileController{}).efOrgNameMap(int(claims.TenantId))
department = efOrgNameByID(orgNames, department)
}
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"account": account,
"name": name,
"role_name": roleName,
"department": department,
"position": position,
},
})
}
// SendLoginCode 发送 backend 登录验证码
func (c *BackendAuthController) SendLoginCode() {
var req struct {
+43
View File
@@ -232,6 +232,49 @@ func (c *BackendMenuController) GetAllMenus() {
_ = c.ServeJSON()
}
// filterAssignableMenus 仅保留「已启用且已显示」的菜单。
// 停用或隐藏的菜单不参与角色权限分配,直接在服务端屏蔽,不依赖前端过滤。
func filterAssignableMenus(menus []models.SystemMenu) []models.SystemMenu {
out := make([]models.SystemMenu, 0, len(menus))
for _, m := range menus {
if m.Status != 1 {
continue
}
// is_visible 为空视为显示
if m.IsVisible != nil && *m.IsVisible == 0 {
continue
}
out = append(out, m)
}
return out
}
// GetAssignableMenus 获取可分配给角色的菜单树
// 过滤规则(均在服务端完成):
// 1. cid:按菜单所属端过滤(1=平台端 2=租户端,不传则不过滤)
// 2. status=1:仅已启用的菜单
// 3. is_visible != 0:仅已显示的菜单(为空视为显示)
//
// 父级菜单被过滤时,其子菜单一并不可分配(菜单树按 pid 构建,父级缺失则整条分支不会返回)。
// GET /backend/assignableMenus?cid=2
func (c *BackendMenuController) GetAssignableMenus() {
var menus []models.SystemMenu
cid, _ := c.GetInt("cid")
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil}
_ = c.ServeJSON()
return
}
if cid == 1 || cid == 2 {
menus = filterMenusByView(menus, cid)
}
menus = filterAssignableMenus(menus)
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)}
_ = c.ServeJSON()
}
func (c *BackendMenuController) GetAllBackendMenus() {
var menus []models.SystemMenu
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil {
+48
View File
@@ -702,6 +702,54 @@ func (c *BackendOaScheduleController) SetImages() {
c.oasOk(map[string]interface{}{"images": parseOaScheduleImages(imagesJSON)})
}
// UpdateContent POST /backend/oa/schedule/content/:id 仅更新备注内容。
// 与 SetImages 类似,只改写 Content 字段,不影响标题、日期、提醒等其他字段,
// 因此即便日程已完成、已绑定提醒,补充备注也不会破坏既有状态。
func (c *BackendOaScheduleController) UpdateContent() {
claims, err := c.oaScheduleClaims()
if err != nil {
c.oasJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.oasJsonErr(400, 400, "无效ID")
return
}
var payload struct {
Content string `json:"content"`
}
raw, _ := io.ReadAll(c.Ctx.Request.Body)
if len(raw) > 0 && json.Unmarshal(raw, &payload) != nil {
c.oasJsonErr(400, 400, "参数错误")
return
}
var item models.OaSchedule
err = models.Orm.QueryTable(new(models.OaSchedule)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&item)
if err != nil {
c.oasJsonErr(404, 404, "日程不存在")
return
}
now := time.Now()
item.Content = payload.Content
item.UpdateTime = &now
if _, err := models.Orm.Update(&item, "Content", "UpdateTime"); err != nil {
c.oasJsonErr(500, 500, "保存失败")
return
}
c.oasOk(map[string]interface{}{"content": item.Content})
}
// Delete DELETE /backend/oa/schedule/delete/:id 软删除日程。
func (c *BackendOaScheduleController) Delete() {
claims, err := c.oaScheduleClaims()
+88 -2
View File
@@ -25,6 +25,92 @@ type backendRolePayload struct {
Rights interface{} `json:"rights"`
}
// parseSubmittedIDs 将前端提交的 rights 解析为菜单 ID 列表(兼容 JSON 数组 / 逗号分隔字符串)。
func parseSubmittedIDs(v interface{}) []uint64 {
ids := make([]uint64, 0)
switch t := v.(type) {
case []interface{}:
for _, item := range t {
switch n := item.(type) {
case float64:
ids = append(ids, uint64(n))
case json.Number:
if i, err := n.Int64(); err == nil {
ids = append(ids, uint64(i))
}
case string:
if i, err := strconv.ParseUint(strings.TrimSpace(n), 10, 64); err == nil {
ids = append(ids, i)
}
}
}
case []uint64:
ids = append(ids, t...)
case string:
var arr []uint64
if err := json.Unmarshal([]byte(t), &arr); err == nil {
ids = append(ids, arr...)
} else {
for _, part := range strings.Split(t, ",") {
if i, err := strconv.ParseUint(strings.TrimSpace(part), 10, 64); err == nil {
ids = append(ids, i)
}
}
}
}
return ids
}
// loadAssignableTenantMenuIDs 返回租户端可分配菜单的 ID 集合:cid=2 且已启用且已显示。
func loadAssignableTenantMenuIDs() map[uint64]bool {
ids := make(map[uint64]bool)
var menus []models.SystemMenu
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil {
return ids
}
menus = filterMenusByView(menus, 2)
menus = filterAssignableMenus(menus)
for _, m := range menus {
ids[m.ID] = true
}
return ids
}
// sanitizeTenantRights 在服务端收敛租户角色权限:
// 仅保留租户端(cid=2)已启用且已显示的菜单 ID,剔除平台菜单、停用菜单、隐藏菜单。
// rights 为 nil 或空串表示全权限(与 filterMenusByRights 保持一致)。
func sanitizeTenantRights(v interface{}) *string {
if v == nil {
return nil
}
if s, ok := v.(string); ok && strings.TrimSpace(s) == "" {
return nil
}
submitted := parseSubmittedIDs(v)
empty := "[]"
if len(submitted) == 0 {
return &empty
}
allowed := loadAssignableTenantMenuIDs()
kept := make([]uint64, 0, len(submitted))
seen := make(map[uint64]bool, len(submitted))
for _, id := range submitted {
if id == 0 || seen[id] || !allowed[id] {
continue
}
seen[id] = true
kept = append(kept, id)
}
if len(kept) == 0 {
return &empty
}
b, _ := json.Marshal(kept)
s := string(b)
return &s
}
// currentTenantID 从 Bearer Token 解析当前登录租户 ID。
// 返回 (tid, true) 表示成功;失败时已写入 401/403 响应并返回 (0, false)。
func (c *BackendRoleController) currentTenantID() (uint64, bool) {
@@ -137,7 +223,7 @@ func (c *BackendRoleController) CreateRole() {
if p.Status != nil {
status = *p.Status
}
rights := normalizeRights(p.Rights)
rights := sanitizeTenantRights(p.Rights)
// 租户端创建的角色一律标记为“自定义角色”,与系统/平台预置角色区分,并按租户隔离
role := &models.AdminRole{
TenantID: tid,
@@ -188,7 +274,7 @@ func (c *BackendRoleController) UpdateRole() {
update["status"] = *p.Status
}
if p.Rights != nil {
update["rights"] = normalizeRights(p.Rights)
update["rights"] = sanitizeTenantRights(p.Rights)
}
if len(update) == 0 {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"}
+5
View File
@@ -42,8 +42,12 @@ func RegisterAuthRoutes() {
// 当前登录用户信息
beego.Router("/backend/getCurrentUser", &controllers.BackendAuthController{}, "get:GetCurrentUser")
// 当前登录用户的角色 / 部门 / 职位
beego.Router("/backend/getCurrentUserProfile", &controllers.BackendAuthController{}, "get:GetCurrentUserProfile")
// 菜单接口
// 可分配给角色的菜单(服务端已过滤停用/隐藏菜单)
beego.Router("/backend/assignableMenus", &controllers.BackendMenuController{}, "get:GetAssignableMenus")
beego.Router("/backend/allmenu", &controllers.BackendMenuController{}, "get:GetAllMenus")
beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu")
// 前端菜单接口
@@ -281,6 +285,7 @@ func RegisterAuthRoutes() {
beego.Router("/backend/oa/schedule/carry/:id", &controllers.BackendOaScheduleController{}, "post:CarrySchedule")
beego.Router("/backend/oa/schedule/carry-pending", &controllers.BackendOaScheduleController{}, "post:CarryPending")
beego.Router("/backend/oa/schedule/images/:id", &controllers.BackendOaScheduleController{}, "post:SetImages")
beego.Router("/backend/oa/schedule/content/:id", &controllers.BackendOaScheduleController{}, "post:UpdateContent")
// OA通知公告
beego.Router("/backend/oa/notice/list", &controllers.BackendOaNoticeController{}, "get:List")
+1
View File
@@ -0,0 +1 @@
exit status 0xffffffff