feat: opaque session、安装/发帖 SSR 与最小 Admin 后台

浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings;
落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 05:44:16 +08:00
parent 3f50316ad0
commit fde5f628ec
80 changed files with 4148 additions and 1849 deletions

View File

@@ -338,10 +338,9 @@ func (s *PostService) List(q PostListQuery) ([]models.Post, int64, error) {
}
}
if tag := strings.TrimSpace(q.Tag); tag != "" {
// 整枚标签匹配:逗号/中文逗号分隔,忽略标签两侧空格,大小写不敏感
// 整枚标签匹配:逗号/中文逗号分隔,忽略标签两侧空格,大小写不敏感(跨 sqlite/postgres/mysql
escaped := escapeLikePattern(strings.ToLower(tag))
normalized := "LOWER(',' || REPLACE(REPLACE(REPLACE(IFNULL(tags,''), '', ','), ', ', ','), ' ,', ',') || ',')"
db = db.Where(normalized+" LIKE ? ESCAPE '\\'", "%,"+escaped+",%")
db = db.Where(tagsNormalizedExpr()+" LIKE ? ESCAPE '\\'", "%,"+escaped+",%")
}
var total int64
db.Count(&total)
@@ -390,6 +389,18 @@ func escapeLikePattern(s string) string {
return s
}
// tagsNormalizedExpr 标签列规范化表达式(跨方言)
// 方言sqlite / postgres 用 ||mysql 用 CONCATCOALESCE 三库通用
func tagsNormalizedExpr() string {
inner := "REPLACE(REPLACE(REPLACE(COALESCE(tags,''), '', ','), ', ', ','), ' ,', ',')"
switch models.DialectorName() {
case "mysql":
return "LOWER(CONCAT(',', " + inner + ", ','))"
default:
return "LOWER(',' || " + inner + " || ',')"
}
}
// resolveAuthorUserID 按用户名精确匹配,否则按昵称精确匹配(优先用户名)
func resolveAuthorUserID(author string) (uint, bool) {
author = strings.TrimSpace(author)