clearlove2.1
1// Package models 定义核心数据模型与少量通用存取辅助。
2// 大部分 SQL 直接写在 handlers 中以保持内聚,此处仅放置跨模块共享的内容。
3package models
4
5import (
6 "database/sql"
7 "encoding/json"
8 "strings"
9 "sync"
10 "time"
11
12 "clearlove/internal/database"
13)
14
15// User 注册用户
16type User struct {
17 ID int64
18 Username string
19 Email string
20 Password string
21 Avatar string
22 Status int // 1 正常 0 禁用
23 CreatedAt string
24}
25
26// Admin 管理员。Role=super 拥有全部权限;custom 按 Perms 权限组列表判定
27type Admin struct {
28 ID int64
29 Username string
30 Password string
31 Role string
32 Perms string // JSON 数组,如 ["posts","users"]
33 CreatedAt string
34}
35
36// HasPerm 判断管理员是否拥有某权限组
37func (a *Admin) HasPerm(p string) bool {
38 if a == nil || a.Role == "super" {
39 return a != nil
40 }
41 var perms []string
42 _ = json.Unmarshal([]byte(a.Perms), &perms)
43 for _, v := range perms {
44 if v == p {
45 return true
46 }
47 }
48 return false
49}
50
51// 权限组常量(后台权限分配粒度)
52const (
53 PermPosts = "posts" // 帖子管理
54 PermReports = "reports" // 举报管理
55 PermUsers = "users" // 用户管理
56 PermSetting = "settings"
57 PermNotice = "notices" // 公告与话题
58 PermPlugin = "plugins"
59 PermAPI = "api"
60 PermAdmins = "admins"
61)
62
63// Post 帖子
64type Post struct {
65 ID int64
66 UserID int64
67 Nickname string
68 Content string
69 TopicID int64
70 IP string
71 Fingerprint string
72 Status int // 1 可见 0 隐藏(AI/管理员处理)
73 LikeCount int64
74 CommentCount int64
75 IsAdmin int // 1 = 管理员专享昵称发布(已通过后台账号验证)
76 BadgesRaw string // 插件写入的标识列表(JSON 数组字符串)
77 CreatedAt string
78}
79
80// ---------- 帖子标识(内置管理员标识 + 插件 badge 标识) ----------
81
82// BadgesJSON 标识列表转 JSON 字符串(空列表返回空串,便于兼容旧数据)
83func BadgesJSON(list []string) string {
84 if len(list) == 0 {
85 return ""
86 }
87 b, err := json.Marshal(list)
88 if err != nil {
89 return ""
90 }
91 return string(b)
92}
93
94// ParseBadges 解析 JSON 标识列表
95func ParseBadges(s string) []string {
96 s = strings.TrimSpace(s)
97 if s == "" {
98 return nil
99 }
100 var out []string
101 if err := json.Unmarshal([]byte(s), &out); err != nil {
102 return nil
103 }
104 return out
105}
106
107// MergeBadges 合并多个标识列表并去重(保持首次出现的顺序)
108func MergeBadges(lists ...[]string) []string {
109 var out []string
110 seen := map[string]bool{}
111 for _, list := range lists {
112 for _, v := range list {
113 v = strings.TrimSpace(v)
114 if v == "" || seen[v] {
115 continue
116 }
117 seen[v] = true
118 out = append(out, v)
119 }
120 }
121 return out
122}
123
124// DisplayBadges 计算帖子最终展示的标识(管理员标识 + 插件标识)
125func DisplayBadges(isAdmin int64, raw string) []string {
126 var builtin []string
127 if isAdmin == 1 {
128 builtin = append(builtin, "管理员")
129 }
130 return MergeBadges(builtin, ParseBadges(raw))
131}
132
133// AdminNicknames 后台配置的「管理员专享昵称」列表。
134// 支持中英文逗号、分号与换行分隔,用于防止他人冒充官方身份。
135func AdminNicknames() []string {
136 raw := GetSetting("admin_nicknames")
137 if raw == "" {
138 return nil
139 }
140 fields := strings.FieldsFunc(raw, func(r rune) bool {
141 return r == ',' || r == ',' || r == ';' || r == ';' || r == '\n' || r == '\r'
142 })
143 out := make([]string, 0, len(fields))
144 for _, f := range fields {
145 if s := strings.TrimSpace(f); s != "" {
146 out = append(out, s)
147 }
148 }
149 return out
150}
151
152// IsAdminNickname 昵称是否属于管理员专享(忽略大小写与首尾空白)
153func IsAdminNickname(name string) bool {
154 name = strings.TrimSpace(name)
155 if name == "" {
156 return false
157 }
158 for _, n := range AdminNicknames() {
159 if strings.EqualFold(n, name) {
160 return true
161 }
162 }
163 return false
164}
165
166var (
167 setCache sync.Map // 设置项内存缓存
168 setCacheMu sync.Mutex
169)
170
171// Now 返回统一格式的 UTC 时间字符串
172func Now() string { return time.Now().UTC().Format(time.RFC3339) }
173
174// GetSetting 读取设置项(带内存缓存)。安装前数据库未连接时返回空串
175func GetSetting(k string) string {
176 if database.DB == nil {
177 return ""
178 }
179 if v, ok := setCache.Load(k); ok {
180 s, _ := v.(string)
181 return s
182 }
183 setCacheMu.Lock()
184 defer setCacheMu.Unlock()
185 var v string
186 err := database.DB.QueryRow("SELECT v FROM settings WHERE k=?", k).Scan(&v)
187 if err != nil {
188 return ""
189 }
190 setCache.Store(k, v)
191 return v
192}
193
194// SetSetting 写入设置项并刷新缓存(按驱动选择 UPSERT 语法)
195func SetSetting(k, v string) error {
196 if database.DB == nil {
197 return nil
198 }
199 setCacheMu.Lock()
200 defer setCacheMu.Unlock()
201 sql := "INSERT INTO settings(k,v) VALUES(?,?) ON CONFLICT(k) DO UPDATE SET v=excluded.v"
202 if database.Driver == "mysql" {
203 sql = "INSERT INTO settings(k,v) VALUES(?,?) ON DUPLICATE KEY UPDATE v=VALUES(v)"
204 }
205 if _, err := database.DB.Exec(sql, k, v); err != nil {
206 return err
207 }
208 setCache.Store(k, v)
209 return nil
210}
211
212// QueryInt 快捷查询单个整数值
213func QueryInt(q string, args ...any) int64 {
214 if database.DB == nil {
215 return 0
216 }
217 var n int64
218 _ = database.DB.QueryRow(q, args...).Scan(&n)
219 return n
220}
221
222// QueryStr 快捷查询单个字符串值
223func QueryStr(q string, args ...any) string {
224 if database.DB == nil {
225 return ""
226 }
227 var s string
228 _ = database.DB.QueryRow(q, args...).Scan(&s)
229 return s
230}
231
232// GetUserByID 按 ID 查询用户
233func GetUserByID(id int64) *User {
234 if database.DB == nil {
235 return nil
236 }
237 u := &User{}
238 err := database.DB.QueryRow(
239 "SELECT id,username,email,password,IFNULL(avatar,''),status,IFNULL(created_at,'') FROM users WHERE id=?", id,
240 ).Scan(&u.ID, &u.Username, &u.Email, &u.Password, &u.Avatar, &u.Status, &u.CreatedAt)
241 if err != nil {
242 return nil
243 }
244 return u
245}
246
247// GetAdminByID 按 ID 查询管理员
248func GetAdminByID(id int64) *Admin {
249 if database.DB == nil {
250 return nil
251 }
252 a := &Admin{}
253 err := database.DB.QueryRow(
254 "SELECT id,username,password,role,IFNULL(perms,'[]'),IFNULL(created_at,'') FROM admins WHERE id=?", id,
255 ).Scan(&a.ID, &a.Username, &a.Password, &a.Role, &a.Perms, &a.CreatedAt)
256 if err != nil {
257 return nil
258 }
259 return a
260}
261
262// NilCoalesce 兼容空值扫描的辅助
263func NilStr(ns sql.NullString) string {
264 if ns.Valid {
265 return ns.String
266 }
267 return ""
268}