Files
ai_site/platform/internal/handler/platform.go
whm 3c9fe46359 feat: allow admins to bind member phone numbers
Members page previously only displayed phone status; add bind/replace UI and admin member update phone support for login and sync identity.
2026-08-05 14:41:27 +08:00

342 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"aijianzhan/platform/internal/authx"
"aijianzhan/platform/internal/logic/applogic"
"aijianzhan/platform/internal/svc"
"aijianzhan/platform/internal/userstore"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
)
func platformListTenantsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
list, err := l.ListTenants()
if err != nil {
authx.WriteError(w, http.StatusForbidden, err.Error())
return
}
httpx.OkJson(w, map[string]any{"items": list})
}
}
func platformCreateTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body struct {
Name string `json:"name"`
Slug string `json:"slug"`
AdminPhone string `json:"admin_phone"`
WithAdminInvite *bool `json:"with_admin_invite"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
withInvite := false
if body.WithAdminInvite != nil {
withInvite = *body.WithAdminInvite
}
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
res, err := l.CreateTenant(body.Name, body.Slug, withInvite, body.AdminPhone)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, res)
}
}
func platformUpdateTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
var body struct {
Name string `json:"name"`
Slug string `json:"slug"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
t, err := l.UpdateTenant(id, body.Name, body.Slug)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, t)
}
}
func platformAdminInviteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
inv, err := l.IssueAdminInvite(id)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, inv)
}
}
func platformAdminAccountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
var body struct {
Phone string `json:"phone"`
}
_ = json.NewDecoder(r.Body).Decode(&body)
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
acc, err := l.IssueAdminAccount(id, body.Phone)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
_ = svcCtx.Audit.Log(r.Context(), id, authx.UserID(r.Context()), "platform.admin_account", acc.Username)
httpx.OkJson(w, map[string]any{"admin_account": acc})
}
}
func platformListAdminsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
items, err := l.ListCompanyAdmins(id)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, map[string]any{"items": items})
}
}
func platformUpdateAdminHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tid, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
uid, _ := strconv.ParseInt(pathvar.Vars(r)["uid"], 10, 64)
var body struct {
ResetPassword *bool `json:"reset_password"`
Password string `json:"password"`
Phone *string `json:"phone"`
DisableUsernameLogin *bool `json:"disable_username_login"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
reset := body.ResetPassword != nil && *body.ResetPassword
if !reset && body.Phone == nil && body.DisableUsernameLogin == nil {
authx.WriteError(w, http.StatusBadRequest, "请指定重置密码、更新手机号或禁用用户名登录")
return
}
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
acc, err := l.UpdateCompanyAdmin(tid, uid, reset, body.Password, body.Phone, body.DisableUsernameLogin)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
_ = svcCtx.Audit.Log(r.Context(), tid, authx.UserID(r.Context()), "platform.update_admin", acc.Username)
httpx.OkJson(w, map[string]any{"admin_account": acc})
}
}
func platformEnterTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
resp, err := l.EnterTenant(id)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
_ = svcCtx.Audit.Log(r.Context(), id, resp.UserID, "platform.enter_tenant", resp.Message)
httpx.OkJson(w, resp)
}
}
func platformExitTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
resp, err := l.ExitTenant()
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
_ = svcCtx.Audit.Log(r.Context(), 0, resp.UserID, "platform.exit_tenant", resp.Message)
httpx.OkJson(w, resp)
}
}
func platformPermModulesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !authx.IsPlatformAdmin(authx.Role(r.Context())) {
authx.WriteError(w, http.StatusForbidden, "需要超级管理员")
return
}
httpx.OkJson(w, map[string]any{
"modules": authx.PermModules(),
"catalog": authx.CompanyPermCatalog(),
"platform": []string{authx.Perm管理租户},
})
}
}
func platformGetTenantPermsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
perms, err := l.GetTenantPerms(id)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, map[string]any{"tenant_id": id, "permissions": perms, "total": len(authx.CompanyPermCatalog())})
}
}
func platformSetTenantPermsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
var body struct {
Permissions []string `json:"permissions"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
perms, err := l.SetTenantPerms(id, body.Permissions)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, map[string]any{"tenant_id": id, "permissions": perms, "total": len(authx.CompanyPermCatalog())})
}
}
func companyEntitlementsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := applogic.NewAuthLogic(r.Context(), svcCtx)
perms, modules, err := l.CompanyEntitlements()
if err != nil {
authx.WriteError(w, http.StatusForbidden, err.Error())
return
}
httpx.OkJson(w, map[string]any{"permissions": perms, "modules": modules})
}
}
func memberListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := applogic.NewMemberLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
list, err := l.List()
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
items := make([]map[string]any, 0, len(list))
for _, u := range list {
items = append(items, map[string]any{
"user_id": u.UserID,
"username": u.Username,
"phone": u.Phone,
"display_name": u.DisplayName,
"role": u.Role,
"org_unit_id": u.OrgUnitID,
"status": u.Status,
"created_at": u.CreatedAt,
})
}
httpx.OkJson(w, map[string]any{"items": items})
}
}
func memberCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body struct {
Password string `json:"password"`
DisplayName string `json:"display_name"`
Role string `json:"role"`
OrgUnitID int64 `json:"org_unit_id"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
l := applogic.NewMemberLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
u, plain, err := l.Create(body.Password, body.DisplayName, body.Role, body.OrgUnitID)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
_ = svcCtx.Audit.Log(r.Context(), authx.TenantID(r.Context()), authx.UserID(r.Context()),
"member.create", "创建成员 "+u.Username)
httpx.OkJson(w, map[string]any{
"user_id": u.UserID,
"username": u.Username,
"display_name": u.DisplayName,
"role": u.Role,
"org_unit_id": u.OrgUnitID,
"status": u.Status,
"password": plain,
})
}
}
func memberUpdateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64)
var body struct {
Role string `json:"role"`
OrgUnitID int64 `json:"org_unit_id"`
Status string `json:"status"`
Phone *string `json:"phone"` // 传则绑定/更换;传 "" 解绑
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
l := applogic.NewMemberLogic(applogic.NewAuthLogic(r.Context(), svcCtx))
var u *userstore.User
var err error
if body.Phone != nil {
u, err = l.SetPhone(id, *body.Phone)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
_ = svcCtx.Audit.Log(r.Context(), authx.TenantID(r.Context()), authx.UserID(r.Context()),
"member.bind_phone", fmt.Sprintf("成员 #%d phone=%s", id, u.Phone))
}
// 仅改手机时不必强制带 role/status若其它字段有值则再 Update
if body.Role != "" || body.Status != "" || body.OrgUnitID != 0 || body.Phone == nil {
u, err = l.Update(id, body.Role, body.OrgUnitID, body.Status)
if err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
}
if u == nil {
authx.WriteError(w, http.StatusBadRequest, "nothing to update")
return
}
httpx.OkJson(w, map[string]any{
"user_id": u.UserID,
"username": u.Username,
"phone": u.Phone,
"display_name": u.DisplayName,
"role": u.Role,
"org_unit_id": u.OrgUnitID,
"status": u.Status,
})
}
}