diff --git a/backend/src/views/apps/organization/components/PositionPage.vue b/backend/src/views/apps/organization/components/PositionPage.vue index 4028d0d..0c19bae 100644 --- a/backend/src/views/apps/organization/components/PositionPage.vue +++ b/backend/src/views/apps/organization/components/PositionPage.vue @@ -46,7 +46,7 @@ - + diff --git a/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue b/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue index 4eabd5c..6e2f147 100644 --- a/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue +++ b/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue @@ -1,301 +1,328 @@ - - - - - + + + + + diff --git a/backend/src/views/system/loginLog/index.vue b/backend/src/views/system/loginLog/index.vue index 2078f58..06dda70 100644 --- a/backend/src/views/system/loginLog/index.vue +++ b/backend/src/views/system/loginLog/index.vue @@ -80,8 +80,8 @@ v-loading="loading" @selection-change="handleSelectionChange" > - - + - - + + 0 { + tid = uint64(v) + } + return tid, nil +} + type erpContactDTO struct { ID uint64 `json:"id"` Tid uint64 `json:"tid"` @@ -46,7 +82,11 @@ type erpContactDTO struct { // List 获取通讯录列表(支持分页、搜索、按组织筛选) // GET /backend/erp/contact/list func (c *BackendErpContactController) List() { - tid, _ := c.GetInt64("tid") + tid, err := c.contactTenantID() + if err != nil { + c.contactJsonError(401, "未登录或无权访问") + return + } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("page_size", 20) keyword := strings.TrimSpace(c.GetString("keyword")) @@ -62,10 +102,8 @@ func (c *BackendErpContactController) List() { qs := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("delete_time__isnull", true). - Exclude("status", 0) - if tid > 0 { - qs = qs.Filter("tid", tid) - } + Exclude("status", 0). + Filter("tid", tid) if orgID > 0 { qs = qs.Filter("org_id", orgID) } @@ -398,17 +436,19 @@ func (c *BackendErpContactController) SyncAllContacts() { // GetContactOrgTree 获取通讯录组织树(带各部门联系人数量) // GET /backend/erp/contact/orgTree func (c *BackendErpContactController) GetContactOrgTree() { - tid, _ := c.GetInt64("tid") + tid, err := c.contactTenantID() + if err != nil { + c.contactJsonError(401, "未登录或无权访问") + return + } qs := models.Orm.QueryTable(new(models.BackendOrganization)). Filter("delete_time__isnull", true). - Exclude("status", 0) - if tid > 0 { - qs = qs.Filter("tid", tid) - } + Exclude("status", 0). + Filter("tid", tid) var orgs []models.BackendOrganization - _, err := qs.OrderBy("sort", "id").All(&orgs) + _, err = qs.OrderBy("sort", "id").All(&orgs) if err != nil { c.contactJsonError(500, "查询组织架构失败: "+err.Error()) return @@ -418,10 +458,8 @@ func (c *BackendErpContactController) GetContactOrgTree() { cqs := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("delete_time__isnull", true). Exclude("status", 0). - Filter("contact_type", 1) - if tid > 0 { - cqs = cqs.Filter("tid", tid) - } + Filter("contact_type", 1). + Filter("tid", tid) type orgCount struct { OrgID uint64 `orm:"column(org_id)"` @@ -437,6 +475,7 @@ func (c *BackendErpContactController) GetContactOrgTree() { for _, org := range orgs { cnt, _ := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("org_id", org.ID). + Filter("tid", tid). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("contact_type", 1). diff --git a/go/controllers/backend_operation_log.go b/go/controllers/backend_operation_log.go index 906940b..56a619c 100644 --- a/go/controllers/backend_operation_log.go +++ b/go/controllers/backend_operation_log.go @@ -48,10 +48,13 @@ func (c *BackendOperationLogController) jsonErr(httpStatus, bizCode int, msg str // List GET /backend/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= func (c *BackendOperationLogController) List() { - if _, err := c.backendClaims(); err != nil { + claims, err := c.backendClaims() + if err != nil { c.jsonErr(401, 401, err.Error()) return } + uid := uint64(claims.UserID) + tid := uint64(claims.TenantId) page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 20) @@ -72,7 +75,10 @@ func (c *BackendOperationLogController) List() { startTimeStr := strings.TrimSpace(c.GetString("startTime")) endTimeStr := strings.TrimSpace(c.GetString("endTime")) - qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true) + qs := models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("tid", tid). + Filter("user_id", uid) // 条件拼装 cond := orm.NewCondition() @@ -174,10 +180,13 @@ func (c *BackendOperationLogController) List() { // Detail GET /backend/operationLogs/:id func (c *BackendOperationLogController) Detail() { - if _, err := c.backendClaims(); err != nil { + claims, err := c.backendClaims() + if err != nil { c.jsonErr(401, 401, err.Error()) return } + uid := uint64(claims.UserID) + tid := uint64(claims.TenantId) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { @@ -187,6 +196,8 @@ func (c *BackendOperationLogController) Detail() { var row models.SystemOperationLog err = models.Orm.QueryTable(new(models.SystemOperationLog)). Filter("id", id). + Filter("tid", tid). + Filter("user_id", uid). Filter("delete_time__isnull", true). One(&row) if err != nil { @@ -219,10 +230,13 @@ func (c *BackendOperationLogController) Detail() { // Delete DELETE /backend/operationLogs/:id func (c *BackendOperationLogController) Delete() { - if _, err := c.backendClaims(); err != nil { + claims, err := c.backendClaims() + if err != nil { c.jsonErr(401, 401, err.Error()) return } + uid := uint64(claims.UserID) + tid := uint64(claims.TenantId) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { @@ -232,6 +246,8 @@ func (c *BackendOperationLogController) Delete() { now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemOperationLog)). Filter("id", id). + Filter("tid", tid). + Filter("user_id", uid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now}) if err != nil { @@ -252,10 +268,13 @@ type backendBatchDeletePayload struct { // BatchDelete POST /backend/operationLogs/batchDelete func (c *BackendOperationLogController) BatchDelete() { - if _, err := c.backendClaims(); err != nil { + claims, err := c.backendClaims() + if err != nil { c.jsonErr(401, 401, err.Error()) return } + uid := uint64(claims.UserID) + tid := uint64(claims.TenantId) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") @@ -273,6 +292,8 @@ func (c *BackendOperationLogController) BatchDelete() { now := time.Now() _, err = models.Orm.QueryTable(new(models.SystemOperationLog)). Filter("id__in", p.IDs). + Filter("tid", tid). + Filter("user_id", uid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now}) if err != nil { @@ -286,14 +307,19 @@ func (c *BackendOperationLogController) BatchDelete() { // Statistics GET /backend/operationLogs/statistics // 供前端筛选项:modules/actions func (c *BackendOperationLogController) Statistics() { - if _, err := c.backendClaims(); err != nil { + claims, err := c.backendClaims() + if err != nil { c.jsonErr(401, 401, err.Error()) return } + uid := uint64(claims.UserID) + tid := uint64(claims.TenantId) var moduleRows []models.SystemOperationLog _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). Filter("delete_time__isnull", true). + Filter("tid", tid). + Filter("user_id", uid). Filter("module__isnull", false). Limit(1000). All(&moduleRows, "Module") @@ -312,6 +338,8 @@ func (c *BackendOperationLogController) Statistics() { var actionRows []models.SystemOperationLog _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). Filter("delete_time__isnull", true). + Filter("tid", tid). + Filter("user_id", uid). Filter("action__isnull", false). Limit(1000). All(&actionRows, "Action") diff --git a/go/controllers/backend_site_settings.go b/go/controllers/backend_site_settings.go index 860568c..ea1871b 100644 --- a/go/controllers/backend_site_settings.go +++ b/go/controllers/backend_site_settings.go @@ -1,607 +1,632 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// BackendSiteSettingsController 租户站点设置(站点基本信息) -// 对应前端 normalSettings.vue 的: -// - GET /backend/normalInfos -// - POST /backend/saveNormalInfos -// - GET /platform/normalInfos -// - POST /platform/saveNormalInfos -type BackendSiteSettingsController struct { - beego.Controller -} - -func (c *BackendSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *BackendSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - - path := strings.ToLower(c.Ctx.Request.URL.Path) - if strings.HasPrefix(path, "/platform/") { - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - } else if strings.HasPrefix(path, "/backend/") { - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - } - - return claims, nil -} - -func parseBackendUint64Flexible(v interface{}) uint64 { - if v == nil { - return 0 - } - switch x := v.(type) { - case float64: - if x <= 0 { - return 0 - } - return uint64(x) - case string: - s := strings.TrimSpace(x) - if s == "" { - return 0 - } - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || n == 0 { - return 0 - } - return n - default: - return 0 - } -} - -type backendNormalInfosOutput struct { - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` -} - -// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos -func (c *BackendSiteSettingsController) GetNormalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 - tid := uint64(claims.TenantId) - if tid == 0 { - tidStr := strings.TrimSpace(c.GetString("tid")) - if tidStr != "" { - if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { - tid = n - } - } - } - - out := backendNormalInfosOutput{ - Sitename: "", - Companyintroduction: "", - Description: "", - Copyright: "", - Companyname: "", - Icp: "", - Logo: "", - Logow: "", - Ico: "", - } - - // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - var rows []models.TenantSiteSetting - _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Limit(1). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - if len(rows) > 0 { - r := rows[0] - out.Sitename = r.Sitename - out.Companyintroduction = r.Companyintroduction - out.Logo = r.Logo - out.Logow = r.Logow - out.Ico = r.Ico - out.Description = r.Description - out.Copyright = r.Copyright - out.Companyname = r.Companyname - out.Icp = r.Icp - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type backendNormalInfosPayload struct { - // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) - Tid interface{} `json:"tid"` - - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` -} - -// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos -func (c *BackendSiteSettingsController) SaveNormalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - var p backendNormalInfosPayload - if uerr := json.Unmarshal(raw, &p); uerr != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := uint64(claims.TenantId) - if tid == 0 { - tid = parseBackendUint64Flexible(p.Tid) - } - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - sitename := strings.TrimSpace(p.Sitename) - if sitename == "" { - c.jsonErr(400, 400, "站点名称不能为空") - return - } - - now := time.Now() - - up := map[string]interface{}{ - "tid": tid, - "sitename": sitename, - "companyintroduction": strings.TrimSpace(p.Companyintroduction), - "logo": strings.TrimSpace(p.Logo), - "logow": strings.TrimSpace(p.Logow), - "ico": strings.TrimSpace(p.Ico), - "description": strings.TrimSpace(p.Description), - "copyright": strings.TrimSpace(p.Copyright), - "companyname": strings.TrimSpace(p.Companyname), - "icp": strings.TrimSpace(p.Icp), - "update_time": now, - } - - cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Count() - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - if cnt == 0 { - row := &models.TenantSiteSetting{ - Tid: tid, - Sitename: sitename, - Companyintroduction: strings.TrimSpace(p.Companyintroduction), - Logo: strings.TrimSpace(p.Logo), - Logow: strings.TrimSpace(p.Logow), - Ico: strings.TrimSpace(p.Ico), - Description: strings.TrimSpace(p.Description), - Copyright: strings.TrimSpace(p.Copyright), - Companyname: strings.TrimSpace(p.Companyname), - Icp: strings.TrimSpace(p.Icp), - CreateTime: now, - UpdateTime: &now, - } - _, err = models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } else { - _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -func (c *BackendSiteSettingsController) resolveBackendTenantID(claims *jwtutil.Claims, payloadTid interface{}) uint64 { - tid := uint64(claims.TenantId) - if tid == 0 { - tid = parseBackendUint64Flexible(payloadTid) - } - if tid == 0 { - tidStr := strings.TrimSpace(c.GetString("tid")) - if tidStr != "" { - if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { - tid = n - } - } - } - return tid -} - -func (c *BackendSiteSettingsController) ensureBackendSettingItemsTable() error { - _, err := models.Orm.Raw(` -CREATE TABLE IF NOT EXISTS yz_system_tenant_setting_items ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - tid BIGINT UNSIGNED NOT NULL DEFAULT 0, - setting_key VARCHAR(64) NOT NULL DEFAULT '', - setting_value LONGTEXT NULL, - create_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP, - update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - delete_time DATETIME NULL DEFAULT NULL, - PRIMARY KEY (id), - UNIQUE KEY uk_tid_key (tid, setting_key), - KEY idx_tid (tid), - KEY idx_delete_time (delete_time) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户站点扩展设置'; -`).Exec() - return err -} - -func (c *BackendSiteSettingsController) getBackendSettingItems(tid uint64, keys []string) (map[string]string, error) { - out := make(map[string]string, len(keys)) - for _, key := range keys { - out[key] = "" - } - - if err := c.ensureBackendSettingItemsTable(); err != nil { - return out, err - } - - type rowItem struct { - SettingKey string - SettingValue string - } - var rows []rowItem - _, err := models.Orm.Raw( - "SELECT setting_key, IFNULL(setting_value, '') AS setting_value FROM yz_system_tenant_setting_items WHERE tid = ? AND setting_key IN ('"+strings.Join(keys, "','")+"') AND delete_time IS NULL", - tid, - ).QueryRows(&rows) - if err != nil { - return out, err - } - - for _, row := range rows { - out[row.SettingKey] = row.SettingValue - } - return out, nil -} - -func (c *BackendSiteSettingsController) saveBackendSettingItems(tid uint64, values map[string]string) error { - if err := c.ensureBackendSettingItemsTable(); err != nil { - return err - } - for key, value := range values { - _, err := models.Orm.Raw(` -INSERT INTO yz_system_tenant_setting_items (tid, setting_key, setting_value, create_time, update_time) -VALUES (?, ?, ?, NOW(), NOW()) -ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), update_time = NOW(), delete_time = NULL -`, tid, key, value).Exec() - if err != nil { - return err - } - } - return nil -} - -// GetLegalInfos GET /backend/legalInfos -func (c *BackendSiteSettingsController) GetLegalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - tid := c.resolveBackendTenantID(claims, nil) - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ - {"label": "legalNotice", "value": ""}, - {"label": "privacyTerms", "value": ""}, - }} - _ = c.ServeJSON() - return - } - - values, err := c.getBackendSettingItems(tid, []string{"legalNotice", "privacyTerms"}) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ - {"label": "legalNotice", "value": values["legalNotice"]}, - {"label": "privacyTerms", "value": values["privacyTerms"]}, - }} - _ = c.ServeJSON() -} - -type backendLegalInfosPayload struct { - Tid interface{} `json:"tid"` - LegalNotice string `json:"legalNotice"` - PrivacyTerms string `json:"privacyTerms"` -} - -// SaveLegalInfos POST /backend/saveLegalInfos -func (c *BackendSiteSettingsController) SaveLegalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendLegalInfosPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := c.resolveBackendTenantID(claims, p.Tid) - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - err = c.saveBackendSettingItems(tid, map[string]string{ - "legalNotice": strings.TrimSpace(p.LegalNotice), - "privacyTerms": strings.TrimSpace(p.PrivacyTerms), - }) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -// GetCompanyInfos GET /backend/companyInfos -func (c *BackendSiteSettingsController) GetCompanyInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - tid := c.resolveBackendTenantID(claims, nil) - out := map[string]interface{}{ - "contact_phone": "", - "contact_email": "", - "address": "", - "worktime": "", - } - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - var row models.SystemTenant - err = models.Orm.QueryTable(new(models.SystemTenant)). - Filter("id", tid). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - if row.ContactPhone != nil { - out["contact_phone"] = *row.ContactPhone - } - if row.ContactEmail != nil { - out["contact_email"] = *row.ContactEmail - } - if row.Address != nil { - out["address"] = *row.Address - } - if row.Worktime != nil { - out["worktime"] = *row.Worktime - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type backendCompanyInfosPayload struct { - Tid interface{} `json:"tid"` - ContactPhone string `json:"contact_phone"` - ContactEmail string `json:"contact_email"` - Address string `json:"address"` - Worktime string `json:"worktime"` -} - -// SaveCompanyInfos POST /backend/saveCompanyInfos -func (c *BackendSiteSettingsController) SaveCompanyInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendCompanyInfosPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := c.resolveBackendTenantID(claims, p.Tid) - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - _, err = models.Orm.QueryTable(new(models.SystemTenant)). - Filter("id", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{ - "contact_phone": strings.TrimSpace(p.ContactPhone), - "contact_email": strings.TrimSpace(p.ContactEmail), - "address": strings.TrimSpace(p.Address), - "worktime": strings.TrimSpace(p.Worktime), - "update_time": time.Now(), - }) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -// GetCompanySeo GET /backend/companySeo -func (c *BackendSiteSettingsController) GetCompanySeo() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - tid := c.resolveBackendTenantID(claims, nil) - out := map[string]string{ - "seoTitle": "", - "seoKeywords": "", - "seoDescription": "", - } - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - values, err := c.getBackendSettingItems(tid, []string{"seoTitle", "seoKeywords", "seoDescription"}) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - out["seoTitle"] = values["seoTitle"] - out["seoKeywords"] = values["seoKeywords"] - out["seoDescription"] = values["seoDescription"] - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type backendCompanySeoPayload struct { - Tid interface{} `json:"tid"` - SeoTitle string `json:"seoTitle"` - SeoKeywords string `json:"seoKeywords"` - SeoDescription string `json:"seoDescription"` -} - -// SaveCompanySeo POST /backend/saveCompanySeo -func (c *BackendSiteSettingsController) SaveCompanySeo() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendCompanySeoPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := c.resolveBackendTenantID(claims, p.Tid) - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - err = c.saveBackendSettingItems(tid, map[string]string{ - "seoTitle": strings.TrimSpace(p.SeoTitle), - "seoKeywords": strings.TrimSpace(p.SeoKeywords), - "seoDescription": strings.TrimSpace(p.SeoDescription), - }) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendSiteSettingsController 租户站点设置(站点基本信息) +// 对应前端 normalSettings.vue 的: +// - GET /backend/normalInfos +// - POST /backend/saveNormalInfos +// - GET /platform/normalInfos +// - POST /platform/saveNormalInfos +type BackendSiteSettingsController struct { + beego.Controller +} + +func (c *BackendSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + + path := strings.ToLower(c.Ctx.Request.URL.Path) + if strings.HasPrefix(path, "/platform/") { + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + } else if strings.HasPrefix(path, "/backend/") { + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + } + + return claims, nil +} + +func parseBackendUint64Flexible(v interface{}) uint64 { + if v == nil { + return 0 + } + switch x := v.(type) { + case float64: + if x <= 0 { + return 0 + } + return uint64(x) + case string: + s := strings.TrimSpace(x) + if s == "" { + return 0 + } + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || n == 0 { + return 0 + } + return n + default: + return 0 + } +} + +type backendNormalInfosOutput struct { + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + CompanyShortName string `json:"company_short_name"` + CompanyEnglishName string `json:"company_english_name"` + CompanyEnglishShort string `json:"company_english_short_name"` + Icp string `json:"icp"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` +} + +// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos +func (c *BackendSiteSettingsController) GetNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 + tid := uint64(claims.TenantId) + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + + out := backendNormalInfosOutput{ + Sitename: "", + Companyintroduction: "", + Description: "", + Copyright: "", + Companyname: "", + Icp: "", + Logo: "", + Logow: "", + Ico: "", + } + + // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var rows []models.TenantSiteSetting + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Limit(1). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + if len(rows) > 0 { + r := rows[0] + out.Sitename = r.Sitename + out.Companyintroduction = r.Companyintroduction + out.Logo = r.Logo + out.Logow = r.Logow + out.Ico = r.Ico + out.Description = r.Description + out.Copyright = r.Copyright + out.Companyname = r.Companyname + out.Icp = r.Icp + } + + // 扩展字段(企业简称 / 企业英文名称 / 企业英文简称)存放在 yz_system_tenant_setting_items + if ext, eerr := c.getBackendSettingItems(tid, []string{ + "company_short_name", "company_english_name", "company_english_short_name", + }); eerr == nil { + out.CompanyShortName = ext["company_short_name"] + out.CompanyEnglishName = ext["company_english_name"] + out.CompanyEnglishShort = ext["company_english_short_name"] + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendNormalInfosPayload struct { + // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) + Tid interface{} `json:"tid"` + + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + CompanyShortName string `json:"company_short_name"` + CompanyEnglishName string `json:"company_english_name"` + CompanyEnglishShort string `json:"company_english_short_name"` + Icp string `json:"icp"` +} + +// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos +func (c *BackendSiteSettingsController) SaveNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + var p backendNormalInfosPayload + if uerr := json.Unmarshal(raw, &p); uerr != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseBackendUint64Flexible(p.Tid) + } + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + sitename := strings.TrimSpace(p.Sitename) + if sitename == "" { + c.jsonErr(400, 400, "站点名称不能为空") + return + } + + now := time.Now() + + up := map[string]interface{}{ + "tid": tid, + "sitename": sitename, + "companyintroduction": strings.TrimSpace(p.Companyintroduction), + "logo": strings.TrimSpace(p.Logo), + "logow": strings.TrimSpace(p.Logow), + "ico": strings.TrimSpace(p.Ico), + "description": strings.TrimSpace(p.Description), + "copyright": strings.TrimSpace(p.Copyright), + "companyname": strings.TrimSpace(p.Companyname), + "icp": strings.TrimSpace(p.Icp), + "update_time": now, + } + + cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + if cnt == 0 { + row := &models.TenantSiteSetting{ + Tid: tid, + Sitename: sitename, + Companyintroduction: strings.TrimSpace(p.Companyintroduction), + Logo: strings.TrimSpace(p.Logo), + Logow: strings.TrimSpace(p.Logow), + Ico: strings.TrimSpace(p.Ico), + Description: strings.TrimSpace(p.Description), + Copyright: strings.TrimSpace(p.Copyright), + Companyname: strings.TrimSpace(p.Companyname), + Icp: strings.TrimSpace(p.Icp), + CreateTime: now, + UpdateTime: &now, + } + _, err = models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + + // 扩展字段(企业简称 / 企业英文名称 / 企业英文简称) + if err = c.saveBackendSettingItems(tid, map[string]string{ + "company_short_name": strings.TrimSpace(p.CompanyShortName), + "company_english_name": strings.TrimSpace(p.CompanyEnglishName), + "company_english_short_name": strings.TrimSpace(p.CompanyEnglishShort), + }); err != nil { + c.jsonErr(500, 500, "保存扩展信息失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +func (c *BackendSiteSettingsController) resolveBackendTenantID(claims *jwtutil.Claims, payloadTid interface{}) uint64 { + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseBackendUint64Flexible(payloadTid) + } + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + return tid +} + +func (c *BackendSiteSettingsController) ensureBackendSettingItemsTable() error { + _, err := models.Orm.Raw(` +CREATE TABLE IF NOT EXISTS yz_system_tenant_setting_items ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + tid BIGINT UNSIGNED NOT NULL DEFAULT 0, + setting_key VARCHAR(64) NOT NULL DEFAULT '', + setting_value LONGTEXT NULL, + create_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + delete_time DATETIME NULL DEFAULT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_tid_key (tid, setting_key), + KEY idx_tid (tid), + KEY idx_delete_time (delete_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户站点扩展设置'; +`).Exec() + return err +} + +func (c *BackendSiteSettingsController) getBackendSettingItems(tid uint64, keys []string) (map[string]string, error) { + out := make(map[string]string, len(keys)) + for _, key := range keys { + out[key] = "" + } + + if err := c.ensureBackendSettingItemsTable(); err != nil { + return out, err + } + + type rowItem struct { + SettingKey string + SettingValue string + } + var rows []rowItem + _, err := models.Orm.Raw( + "SELECT setting_key, IFNULL(setting_value, '') AS setting_value FROM yz_system_tenant_setting_items WHERE tid = ? AND setting_key IN ('"+strings.Join(keys, "','")+"') AND delete_time IS NULL", + tid, + ).QueryRows(&rows) + if err != nil { + return out, err + } + + for _, row := range rows { + out[row.SettingKey] = row.SettingValue + } + return out, nil +} + +func (c *BackendSiteSettingsController) saveBackendSettingItems(tid uint64, values map[string]string) error { + if err := c.ensureBackendSettingItemsTable(); err != nil { + return err + } + for key, value := range values { + _, err := models.Orm.Raw(` +INSERT INTO yz_system_tenant_setting_items (tid, setting_key, setting_value, create_time, update_time) +VALUES (?, ?, ?, NOW(), NOW()) +ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), update_time = NOW(), delete_time = NULL +`, tid, key, value).Exec() + if err != nil { + return err + } + } + return nil +} + +// GetLegalInfos GET /backend/legalInfos +func (c *BackendSiteSettingsController) GetLegalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ + {"label": "legalNotice", "value": ""}, + {"label": "privacyTerms", "value": ""}, + }} + _ = c.ServeJSON() + return + } + + values, err := c.getBackendSettingItems(tid, []string{"legalNotice", "privacyTerms"}) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ + {"label": "legalNotice", "value": values["legalNotice"]}, + {"label": "privacyTerms", "value": values["privacyTerms"]}, + }} + _ = c.ServeJSON() +} + +type backendLegalInfosPayload struct { + Tid interface{} `json:"tid"` + LegalNotice string `json:"legalNotice"` + PrivacyTerms string `json:"privacyTerms"` +} + +// SaveLegalInfos POST /backend/saveLegalInfos +func (c *BackendSiteSettingsController) SaveLegalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendLegalInfosPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + err = c.saveBackendSettingItems(tid, map[string]string{ + "legalNotice": strings.TrimSpace(p.LegalNotice), + "privacyTerms": strings.TrimSpace(p.PrivacyTerms), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// GetCompanyInfos GET /backend/companyInfos +func (c *BackendSiteSettingsController) GetCompanyInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + out := map[string]interface{}{ + "contact_phone": "", + "contact_email": "", + "address": "", + "worktime": "", + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var row models.SystemTenant + err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", tid). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + if row.ContactPhone != nil { + out["contact_phone"] = *row.ContactPhone + } + if row.ContactEmail != nil { + out["contact_email"] = *row.ContactEmail + } + if row.Address != nil { + out["address"] = *row.Address + } + if row.Worktime != nil { + out["worktime"] = *row.Worktime + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendCompanyInfosPayload struct { + Tid interface{} `json:"tid"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` +} + +// SaveCompanyInfos POST /backend/saveCompanyInfos +func (c *BackendSiteSettingsController) SaveCompanyInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendCompanyInfosPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "contact_phone": strings.TrimSpace(p.ContactPhone), + "contact_email": strings.TrimSpace(p.ContactEmail), + "address": strings.TrimSpace(p.Address), + "worktime": strings.TrimSpace(p.Worktime), + "update_time": time.Now(), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// GetCompanySeo GET /backend/companySeo +func (c *BackendSiteSettingsController) GetCompanySeo() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + out := map[string]string{ + "seoTitle": "", + "seoKeywords": "", + "seoDescription": "", + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + values, err := c.getBackendSettingItems(tid, []string{"seoTitle", "seoKeywords", "seoDescription"}) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + out["seoTitle"] = values["seoTitle"] + out["seoKeywords"] = values["seoKeywords"] + out["seoDescription"] = values["seoDescription"] + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendCompanySeoPayload struct { + Tid interface{} `json:"tid"` + SeoTitle string `json:"seoTitle"` + SeoKeywords string `json:"seoKeywords"` + SeoDescription string `json:"seoDescription"` +} + +// SaveCompanySeo POST /backend/saveCompanySeo +func (c *BackendSiteSettingsController) SaveCompanySeo() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendCompanySeoPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + err = c.saveBackendSettingItems(tid, map[string]string{ + "seoTitle": strings.TrimSpace(p.SeoTitle), + "seoKeywords": strings.TrimSpace(p.SeoKeywords), + "seoDescription": strings.TrimSpace(p.SeoDescription), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} diff --git a/go/main.exe b/go/main.exe index 2622e01..a764f41 100644 Binary files a/go/main.exe and b/go/main.exe differ