feat: 优化web

This commit is contained in:
2026-04-23 18:58:13 +08:00
commit 544a2f3428
160 changed files with 27327 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
package handler
import (
"net/http"
"giter.top/smart/internal/iam/service"
"github.com/gin-gonic/gin"
)
type DeptHandler struct {
svc service.DeptService
}
func NewDeptHandler(svc service.DeptService) *DeptHandler {
return &DeptHandler{svc: svc}
}
func (h *DeptHandler) Tree(c *gin.Context) {
tid := headerTenantID(c)
keyword := c.Query("keyword")
var leaderID *string
if s := c.Query("leader_id"); s != "" {
leaderID = &s
}
tree, err := h.svc.Tree(c.Request.Context(), tid, keyword, leaderID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, tree)
}
func (h *DeptHandler) Create(c *gin.Context) {
tid := headerTenantID(c)
var req service.CreateDeptRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
d, err := h.svc.Create(c.Request.Context(), tid, &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, d)
}
func (h *DeptHandler) Update(c *gin.Context) {
tid := headerTenantID(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var req service.UpdateDeptRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
d, err := h.svc.Update(c.Request.Context(), tid, id, &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, d)
}
func (h *DeptHandler) Delete(c *gin.Context) {
tid := headerTenantID(c)
var ids []string
if err := c.ShouldBindJSON(&ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.Delete(c.Request.Context(), tid, ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
func (h *DeptHandler) Get(c *gin.Context) {
tid := headerTenantID(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
d, err := h.svc.Get(c.Request.Context(), tid, id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, d)
}
+53
View File
@@ -0,0 +1,53 @@
package handler
import (
"strconv"
authmw "giter.top/smart/internal/auth/middleware"
"giter.top/smart/internal/iam/entity"
"github.com/gin-gonic/gin"
)
func atoiDef(s string, def int) int {
if s == "" {
return def
}
v, err := strconv.Atoi(s)
if err != nil {
return def
}
return v
}
// headerTenantID 当前租户:优先 OAuth2 Bearer 解析结果,其次 X-Tenant-ID,缺省平台租户。
func headerTenantID(c *gin.Context) string {
if v, ok := c.Get(authmw.CtxTenantID); ok {
if s, ok2 := v.(string); ok2 && s != "" {
return s
}
}
s := c.GetHeader("X-Tenant-ID")
if s == "" {
return entity.PlatformTenantID
}
return s
}
// headerUserID 当前用户:优先 OAuth2 opaque access_token 对应用户,其次 X-User-ID。
func headerUserID(c *gin.Context) string {
if v, ok := c.Get(authmw.CtxUserID); ok {
if s, ok2 := v.(string); ok2 && s != "" {
return s
}
}
return c.GetHeader("X-User-ID")
}
// headerGrantorUserID 请求头 X-Grantor-User-ID(授权人,用于防越权校验)
func headerGrantorUserID(c *gin.Context) *string {
s := c.GetHeader("X-Grantor-User-ID")
if s == "" {
return nil
}
return &s
}
+141
View File
@@ -0,0 +1,141 @@
package handler
import (
"errors"
"net/http"
"strconv"
"giter.top/smart/internal/iam/entity"
"giter.top/smart/internal/iam/repository"
"giter.top/smart/internal/iam/service"
"github.com/gin-gonic/gin"
)
type MenuHandler struct {
svc service.MenuService
}
func NewMenuHandler(svc service.MenuService) *MenuHandler {
return &MenuHandler{svc: svc}
}
func isPlatformAdmin(c *gin.Context) bool {
return headerTenantID(c) == entity.PlatformTenantID
}
func (h *MenuHandler) Create(c *gin.Context) {
var req service.CreateMenuRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
m, err := h.svc.Create(c.Request.Context(), &req, isPlatformAdmin(c))
if err != nil {
if errors.Is(err, repository.ErrForbidden) {
c.JSON(http.StatusForbidden, gin.H{"error": "仅平台管理员可维护菜单"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, m)
}
func (h *MenuHandler) Update(c *gin.Context) {
mid := c.Param("id")
if mid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var req service.UpdateMenuRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
m, err := h.svc.Update(c.Request.Context(), mid, &req, isPlatformAdmin(c))
if err != nil {
if errors.Is(err, repository.ErrForbidden) {
c.JSON(http.StatusForbidden, gin.H{"error": "仅平台管理员可维护菜单"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, m)
}
func (h *MenuHandler) Delete(c *gin.Context) {
var ids []string
if err := c.ShouldBindJSON(&ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.Delete(c.Request.Context(), ids, isPlatformAdmin(c)); err != nil {
if errors.Is(err, repository.ErrForbidden) {
c.JSON(http.StatusForbidden, gin.H{"error": "仅平台管理员可维护菜单"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
func (h *MenuHandler) Get(c *gin.Context) {
mid := c.Param("id")
if mid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
m, err := h.svc.Get(c.Request.Context(), mid)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, m)
}
func (h *MenuHandler) Tree(c *gin.Context) {
var mt *int16
if s := c.Query("menu_type"); s != "" {
v64, err := strconv.ParseInt(s, 10, 16)
if err == nil {
v := int16(v64)
mt = &v
}
}
tree, err := h.svc.Tree(c.Request.Context(), mt)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, tree)
}
func (h *MenuHandler) Nav(c *gin.Context) {
uid := headerUserID(c)
if uid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "需要 X-User-ID"})
return
}
tree, err := h.svc.NavForUser(c.Request.Context(), uid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, tree)
}
func (h *MenuHandler) Perms(c *gin.Context) {
uid := headerUserID(c)
if uid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "需要 X-User-ID"})
return
}
perms, err := h.svc.PermsForUser(c.Request.Context(), uid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"perms": perms})
}
+117
View File
@@ -0,0 +1,117 @@
package handler
import (
"net/http"
"giter.top/smart/internal/iam/service"
"github.com/gin-gonic/gin"
)
type RoleHandler struct {
svc service.RoleService
}
func NewRoleHandler(svc service.RoleService) *RoleHandler {
return &RoleHandler{svc: svc}
}
func (h *RoleHandler) Create(c *gin.Context) {
tid := headerTenantID(c)
var req service.CreateRoleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
role, err := h.svc.Create(c.Request.Context(), tid, &req, headerGrantorUserID(c))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, role)
}
func (h *RoleHandler) Update(c *gin.Context) {
tid := headerTenantID(c)
rid := c.Param("id")
if rid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var req service.UpdateRoleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
r, err := h.svc.Update(c.Request.Context(), tid, rid, &req, headerGrantorUserID(c))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, r)
}
func (h *RoleHandler) Delete(c *gin.Context) {
tid := headerTenantID(c)
var ids []string
if err := c.ShouldBindJSON(&ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.Delete(c.Request.Context(), tid, ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
func (h *RoleHandler) Get(c *gin.Context) {
tid := headerTenantID(c)
rid := c.Param("id")
if rid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
r, err := h.svc.Get(c.Request.Context(), tid, rid)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, r)
}
func (h *RoleHandler) List(c *gin.Context) {
tid := headerTenantID(c)
name := c.Query("name")
code := c.Query("code")
page := atoiDef(c.Query("page"), 1)
pageSize := atoiDef(c.Query("page_size"), 10)
resp, err := h.svc.List(c.Request.Context(), tid, name, code, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
type assignMenusBody struct {
MenuIDs []string `json:"menu_ids"`
}
func (h *RoleHandler) AssignMenus(c *gin.Context) {
tid := headerTenantID(c)
rid := c.Param("id")
if rid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var body assignMenusBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.AssignMenus(c.Request.Context(), tid, rid, body.MenuIDs, headerGrantorUserID(c)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
+98
View File
@@ -0,0 +1,98 @@
package handler
import (
"net/http"
"strconv"
"giter.top/smart/internal/iam/service"
"github.com/gin-gonic/gin"
)
type TenantHandler struct {
svc service.TenantService
}
func NewTenantHandler(svc service.TenantService) *TenantHandler {
return &TenantHandler{svc: svc}
}
func (h *TenantHandler) Create(c *gin.Context) {
var req service.CreateTenantRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
t, err := h.svc.Create(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, t)
}
func (h *TenantHandler) Update(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var req service.UpdateTenantRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
t, err := h.svc.Update(c.Request.Context(), id, &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, t)
}
func (h *TenantHandler) Delete(c *gin.Context) {
var ids []string
if err := c.ShouldBindJSON(&ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.Delete(c.Request.Context(), ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
func (h *TenantHandler) Get(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
t, err := h.svc.Get(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, t)
}
func (h *TenantHandler) List(c *gin.Context) {
name := c.Query("name")
code := c.Query("code")
var status *int16
if s := c.Query("status"); s != "" {
v64, err := strconv.ParseInt(s, 10, 16)
if err == nil {
v := int16(v64)
status = &v
}
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
resp, err := h.svc.List(c.Request.Context(), name, code, status, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
+123
View File
@@ -0,0 +1,123 @@
package handler
import (
"net/http"
"strconv"
"giter.top/smart/internal/iam/service"
"github.com/gin-gonic/gin"
)
type UserHandler struct {
svc service.UserService
}
func NewUserHandler(svc service.UserService) *UserHandler {
return &UserHandler{svc: svc}
}
func (h *UserHandler) Create(c *gin.Context) {
tid := headerTenantID(c)
var req service.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u, err := h.svc.Create(c.Request.Context(), tid, &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, u)
}
func (h *UserHandler) Update(c *gin.Context) {
tid := headerTenantID(c)
uid := c.Param("id")
if uid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var req service.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u, err := h.svc.Update(c.Request.Context(), tid, uid, &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, u)
}
func (h *UserHandler) Delete(c *gin.Context) {
tid := headerTenantID(c)
var ids []string
if err := c.ShouldBindJSON(&ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.Delete(c.Request.Context(), tid, ids); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
func (h *UserHandler) Get(c *gin.Context) {
tid := headerTenantID(c)
uid := c.Param("id")
if uid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
u, err := h.svc.Get(c.Request.Context(), tid, uid)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, u)
}
func (h *UserHandler) List(c *gin.Context) {
tid := headerTenantID(c)
q := &service.UserListQuery{
Keyword: c.Query("keyword"),
Page: atoiDef(c.Query("page"), 1),
PageSize: atoiDef(c.Query("page_size"), 10),
}
if s := c.Query("dept_id"); s != "" {
q.DeptID = &s
}
if s := c.Query("role_id"); s != "" {
q.RoleID = &s
}
if s := c.Query("status"); s != "" {
v64, err := strconv.ParseInt(s, 10, 16)
if err == nil {
v := int16(v64)
q.Status = &v
}
}
resp, err := h.svc.List(c.Request.Context(), tid, q)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
func (h *UserHandler) DataScope(c *gin.Context) {
uid := headerUserID(c)
if uid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "需要 X-User-ID"})
return
}
ds, err := h.svc.DataScopeForUser(c.Request.Context(), uid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data_scope": ds})
}