Files
yunzerwebsiteallinone/go/controllers/tenant_site.go
T
2026-08-19 23:34:28 +08:00

191 lines
5.7 KiB
Go

package controllers
import (
"fmt"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"server/models"
"server/pkg/tagengine"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// TenantSiteController 租户官网前台渲染(Go 直出 HTML,无需登录)
// 路由:GET / | /news | /news/:id | /page/:path
type TenantSiteController struct {
beego.Controller
}
// siteNotFoundPage 未匹配域名的提示页
const siteNotFoundPage = `<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>站点不存在</title>
<style>body{font-family:"Microsoft YaHei",sans-serif;background:#f5f6f8;color:#333;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
.box{background:#fff;padding:48px 64px;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,.06);text-align:center}
h1{font-size:22px;margin:0 0 12px}p{color:#888;font-size:14px;margin:0}</style></head>
<body><div class="box"><h1>站点不存在</h1><p>当前域名未绑定任何租户官网,请检查域名配置。</p></div></body>
</html>`
// renderErrorPage 渲染失败的兜底页
const renderErrorPage = `<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>页面异常</title>
<style>body{font-family:"Microsoft YaHei",sans-serif;background:#f5f6f8;color:#333;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
.box{background:#fff;padding:48px 64px;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,.06);text-align:center}
h1{font-size:22px;margin:0 0 12px}p{color:#888;font-size:14px;margin:0}</style></head>
<body><div class="box"><h1>页面暂时无法访问</h1><p>%s</p></div></body>
</html>`
// resolveTid 按访问 Host 识别租户 ID。
// 规则:dev 环境支持 X-Tenant-ID 头切换 → localhost/127.0.0.1 默认租户 1
// → 平台主域名池命中返回租户 1(平台站点)→ 租户域名表命中返回对应 tid
// → 全部未命中返回 0,false(不再默认回落租户 1)
func (c *TenantSiteController) resolveTid() (uint64, bool) {
runmode, _ := beego.AppConfig.String("runmode")
if runmode == "dev" {
if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-ID")); h != "" {
if decoded, err := url.QueryUnescape(h); err == nil {
h = decoded
}
if n, err := strconv.ParseUint(h, 10, 64); err == nil && n > 0 {
return n, true
}
var tenant models.SystemTenant
cond := orm.NewCondition().Or("tenant_name", h).Or("tenant_short_name", h)
if err := models.Orm.QueryTable(new(models.SystemTenant)).SetCond(cond).One(&tenant); err == nil {
return tenant.ID, true
}
}
}
host := c.Ctx.Request.Host
if idx := strings.Index(host, ":"); idx > 0 {
host = host[:idx]
}
if host == "localhost" || host == "127.0.0.1" {
return 1, true
}
// 平台主域名池命中 → 平台站点(tid 1 + 默认模板)
poolCnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)).
Filter("main_domain", host).
Filter("status", 1).
Filter("delete_time__isnull", true).
Count()
if poolCnt > 0 {
return 1, true
}
var td models.SystemTenantDomain
err := models.Orm.QueryTable(new(models.SystemTenantDomain)).
Filter("full_domain", host).
Filter("status", 1).
Filter("delete_time__isnull", true).
One(&td)
if err == nil && td.Tid != nil && *td.Tid > 0 {
return *td.Tid, true
}
return 0, false
}
// resolveTemplateCode 解析租户选用模板编码,缺失/停用/目录不存在均回退 default
func (c *TenantSiteController) resolveTemplateCode(tid uint64) string {
code := strings.TrimSpace(cmsTenantTemplateCode(tid))
if code != "" && code != "default" {
cnt, _ := models.Orm.QueryTable(new(models.CmsFrontendTemplate)).
Filter("code", code).
Filter("status", 1).
Filter("delete_time__isnull", true).
Count()
if cnt == 0 {
code = ""
}
}
if code == "" {
code = "default"
}
if !cmsTemplateDirExists(code) {
code = "default"
}
return code
}
// writeHTML 直出 HTML
func (c *TenantSiteController) writeHTML(status int, html string) {
c.Ctx.Output.SetStatus(status)
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
c.Ctx.WriteString(html)
}
// render 解析并输出模板文件
func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.RenderCtx)) {
tid, ok := c.resolveTid()
if !ok {
c.writeHTML(404, siteNotFoundPage)
return
}
code := c.resolveTemplateCode(tid)
dir := filepath.Join(cmsThemesRoot(), code)
if _, err := os.Stat(filepath.Join(dir, file)); err != nil {
c.writeHTML(404, fmt.Sprintf(renderErrorPage, "模板页面不存在"))
return
}
ctx := &tagengine.RenderCtx{Tid: tid, Page: 1, PageSize: 10}
if mod != nil {
mod(ctx)
}
out, err := tagengine.Render(dir, file, ctx)
if err != nil {
c.writeHTML(500, fmt.Sprintf(renderErrorPage, "模板渲染失败"))
return
}
c.writeHTML(200, out)
}
// Index GET / 首页
func (c *TenantSiteController) Index() {
c.render("index.html", nil)
}
// News GET /news 新闻列表页(?page=N 分页)
func (c *TenantSiteController) News() {
page, _ := c.GetInt("page", 1)
if page < 1 {
page = 1
}
c.render("news.html", func(ctx *tagengine.RenderCtx) {
ctx.Page = page
})
}
// NewsDetail GET /news/:id 文章详情页
func (c *TenantSiteController) NewsDetail() {
id, _ := c.GetUint64(":id")
if id == 0 {
c.writeHTML(404, fmt.Sprintf(renderErrorPage, "文章不存在"))
return
}
c.render("news_detail.html", func(ctx *tagengine.RenderCtx) {
ctx.ArticleID = id
})
}
// Page GET /page/:path 单页
func (c *TenantSiteController) Page() {
path := strings.TrimSpace(c.GetString(":path"))
if path == "" || strings.ContainsAny(path, "/\\.") {
c.writeHTML(404, fmt.Sprintf(renderErrorPage, "页面不存在"))
return
}
c.render("page.html", func(ctx *tagengine.RenderCtx) {
ctx.PagePath = path
})
}