298 lines
8.5 KiB
Go
298 lines
8.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"past/internal/httpapi"
|
|
"past/internal/store"
|
|
"past/internal/upload"
|
|
)
|
|
|
|
// ========== 通用上传 / 背景图 ==========
|
|
|
|
func registerBg(r *httpapi.Router) {
|
|
r.Handle("upload", uploadGeneric)
|
|
r.Handle("get_bg_config", getBgConfig)
|
|
r.Handle("set_bg_image", setBgImage)
|
|
r.Handle("delete_bg_image", deleteBgImage)
|
|
}
|
|
|
|
// uploadResult 统一处理 handleUpload 结果的响应(成功/违规/失败)
|
|
func uploadResult(c *httpapi.Ctx, res upload.Result, onSuccess func(path string) error) error {
|
|
if res.Path != "" {
|
|
return onSuccess(res.Path)
|
|
}
|
|
if strings.Contains(res.Error, "违规") {
|
|
return c.ErrCode(400, res.Error, upload.ErrHandleFailed)
|
|
}
|
|
return c.ErrCode(400, "上传失败: "+orDefault(res.Error, "未知错误"), upload.ErrHandleFailed)
|
|
}
|
|
|
|
func uploadGeneric(c *httpapi.Ctx) error {
|
|
if c.Method != "POST" {
|
|
return c.ErrCode(405, "方法不允许", upload.ErrMethodNotAllowed)
|
|
}
|
|
subDir := "photos"
|
|
if c.FormHas("sub_dir") {
|
|
subDir = c.Form("sub_dir")
|
|
}
|
|
if strings.ContainsAny(subDir, "/\\.") || subDir == "" {
|
|
return c.Err(400, "无效的上传目录")
|
|
}
|
|
uid := c.UID()
|
|
if uid == 0 {
|
|
return c.ErrCode(401, "请先登录", upload.ErrNotLoggedIn)
|
|
}
|
|
prefix := upload.UserFilePath(uid, subDir)
|
|
var res *upload.Result
|
|
for _, f := range []string{"file", "avatar", "photo", "image"} {
|
|
if fh := c.File(f); fh != nil {
|
|
r := up.Handle(fh, "", prefix)
|
|
res = &r
|
|
break
|
|
}
|
|
}
|
|
if res == nil {
|
|
// 兼容某些 Android 端:原始请求体
|
|
if raw := c.Body(); len(raw) > 100 {
|
|
r := up.HandleBytes(raw, "upload_"+time.Now().Format("20060102150405")+".jpg", "", prefix)
|
|
res = &r
|
|
}
|
|
}
|
|
if res == nil {
|
|
return c.ErrCode(400, "未接收到文件", upload.ErrNoFileReceived)
|
|
}
|
|
return uploadResult(c, *res, func(path string) error {
|
|
return c.OK(M{"success": true, "path": path})
|
|
})
|
|
}
|
|
|
|
func settingValue(c *httpapi.Ctx, key string) (string, bool) {
|
|
row, _ := c.DB.QueryOne("SELECT `value` FROM settings WHERE `key` = ?", key)
|
|
if row == nil {
|
|
return "", false
|
|
}
|
|
return row.Str("value"), true
|
|
}
|
|
|
|
func setSetting(c *httpapi.Ctx, key string, value any) error {
|
|
_, err := c.DB.Exec("INSERT INTO settings (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?", key, value, value)
|
|
return err
|
|
}
|
|
|
|
func bgKeys(cid int64) (string, string) {
|
|
if cid != 0 {
|
|
return "bg_" + store.ToStr(cid), "bgver_" + store.ToStr(cid)
|
|
}
|
|
return "bg_global", "bgver_global"
|
|
}
|
|
|
|
func getBgConfig(c *httpapi.Ctx) error {
|
|
bgKey, verKey := bgKeys(c.CoupleID())
|
|
img, _ := settingValue(c, bgKey)
|
|
ver, _ := settingValue(c, verKey)
|
|
return c.OK(M{"image": img, "version": store.ToInt(ver)})
|
|
}
|
|
|
|
func setBgImage(c *httpapi.Ctx) error {
|
|
if c.Method != "POST" {
|
|
return c.ErrCode(405, "方法不允许", upload.ErrMethodNotAllowed)
|
|
}
|
|
cid := c.CoupleID()
|
|
if cid == 0 {
|
|
return c.ErrCode(400, "请先绑定情侣", upload.ErrNotBound)
|
|
}
|
|
couplePath := upload.CouplePath(coupleSpaceID(c, cid), "covers")
|
|
bgKey, verKey := bgKeys(cid)
|
|
nextVersion := func() int64 {
|
|
if old, ok := settingValue(c, verKey); ok {
|
|
return store.ToInt(old) + 1
|
|
}
|
|
return 1
|
|
}
|
|
saveBg := func(path string, version int64) error {
|
|
if err := setSetting(c, bgKey, path); err != nil {
|
|
return err
|
|
}
|
|
if err := setSetting(c, verKey, version); err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true, "path": path, "version": version})
|
|
}
|
|
fh := c.File("bg")
|
|
if fh == nil {
|
|
fh = c.File("file")
|
|
}
|
|
if fh != nil {
|
|
version := nextVersion()
|
|
return uploadResult(c, up.Handle(fh, "", couplePath), func(path string) error { return saveBg(path, version) })
|
|
}
|
|
if raw := c.Body(); len(raw) > 100 {
|
|
res := up.SaveRaw(raw, "bg_", "jpg", "", couplePath)
|
|
if res.Path != "" {
|
|
return saveBg(res.Path, nextVersion())
|
|
}
|
|
if strings.Contains(res.Error, "违规") {
|
|
return c.ErrCode(400, res.Error, upload.ErrHandleFailed)
|
|
}
|
|
return c.ErrCode(400, "原始数据保存失败", upload.ErrRawBodySave)
|
|
}
|
|
return c.ErrCode(400, "未接收到文件", upload.ErrNoFileReceived)
|
|
}
|
|
|
|
func deleteBgImage(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
cid, err := requireCoupleID(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bgKey, verKey := bgKeys(cid)
|
|
if v, ok := settingValue(c, bgKey); ok && v != "" {
|
|
up.Remove(v)
|
|
}
|
|
if _, err := c.DB.Exec("DELETE FROM settings WHERE `key` = ? OR `key` = ?", bgKey, verKey); err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true})
|
|
}
|
|
|
|
// ========== 站点公开 ==========
|
|
|
|
func registerSite(r *httpapi.Router) {
|
|
r.Handle("site_info", siteInfo)
|
|
r.Handle("site_stats", siteStats)
|
|
}
|
|
|
|
func siteInfo(c *httpapi.Ctx) error {
|
|
rs, err := c.DB.Query("SELECT `key`, `value` FROM settings")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rs) == 0 {
|
|
return c.OK([]any{}) // PHP 空数组 → []
|
|
}
|
|
out := M{}
|
|
for _, r := range rs {
|
|
out[r.Str("key")] = r["value"]
|
|
}
|
|
return c.OK(out)
|
|
}
|
|
|
|
func siteStats(c *httpapi.Ctx) error {
|
|
raw := func(t string) any { return countRaw(c, "cnt", "SELECT COUNT(*) as cnt FROM "+t) }
|
|
cnt := func(t string) int64 { return countOf(c, "SELECT COUNT(*) as cnt FROM "+t) }
|
|
couples, users := raw("couples"), raw("users")
|
|
memories := cnt("timeline") + cnt("photos") + cnt("diaries")
|
|
var days any = 0
|
|
if e, _ := c.DB.QueryOne("SELECT MIN(created_at) as d FROM users"); e != nil && e.Str("d") != "" {
|
|
if t, err := parseDate(e.Str("d")); err == nil {
|
|
d := math.Floor(time.Since(t).Seconds() / 86400)
|
|
if d < 1 {
|
|
d = 1
|
|
}
|
|
days = d
|
|
}
|
|
}
|
|
return c.OK(M{"couples": couples, "users": users, "memories": memories, "days": days})
|
|
}
|
|
|
|
// ========== 操作记录 / 订阅 ==========
|
|
|
|
func registerActivity(r *httpapi.Router) {
|
|
r.Handle("activity_log_list", activityLogList)
|
|
}
|
|
|
|
func registerSubscribe(r *httpapi.Router) {
|
|
r.Handle("subscribe_template", subscribeTemplate)
|
|
r.Handle("unsubscribe_template", unsubscribeTemplate)
|
|
r.Handle("my_subscribe_templates", mySubscribeTemplates)
|
|
}
|
|
|
|
// pageParams 对应 max(1, intval(page)) 与 min(maxSize, max(1, intval(page_size)))
|
|
func pageParams(c *httpapi.Ctx, defSize, maxSize int64) (page, pageSize, offset int64) {
|
|
page = 1
|
|
if c.QHas("page") {
|
|
page = c.QInt("page")
|
|
}
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
pageSize = defSize
|
|
if c.QHas("page_size") {
|
|
pageSize = c.QInt("page_size")
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 1
|
|
}
|
|
if pageSize > maxSize {
|
|
pageSize = maxSize
|
|
}
|
|
return page, pageSize, (page - 1) * pageSize
|
|
}
|
|
|
|
func activityLogList(c *httpapi.Ctx) error {
|
|
ids := coupleUserIds(c, 0)
|
|
if len(ids) == 0 {
|
|
return c.OK([]any{})
|
|
}
|
|
page, pageSize, offset := pageParams(c, 6, 100)
|
|
totalRaw := countRaw(c, "cnt", "SELECT COUNT(*) as cnt FROM activity_log WHERE user_id IN (?, ?)", ids[0], ids[1])
|
|
total := store.ToInt(totalRaw)
|
|
items, err := c.DB.Query("SELECT al.*, u.nickname, u.avatar, u.gender FROM activity_log al JOIN users u ON al.user_id = u.id WHERE al.user_id IN (?, ?) ORDER BY al.created_at DESC LIMIT ? OFFSET ?", ids[0], ids[1], pageSize, offset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"items": rows(items), "total": totalRaw, "page": page, "page_size": pageSize, "total_pages": math.Ceil(float64(total) / float64(pageSize))})
|
|
}
|
|
|
|
func subscribeTemplate(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
templateType, templateID := c.InTrim("template_type"), c.InTrim("template_id")
|
|
if !store.Truthy(templateType) || !store.Truthy(templateID) {
|
|
return c.Err(400, "缺少参数")
|
|
}
|
|
if _, ok := c.App.Wechat.Templates()[templateType]; !ok {
|
|
return c.Err(400, "不支持的模板类型")
|
|
}
|
|
if _, err := c.DB.Exec("INSERT INTO subscribe_templates (user_id, template_id, template_type) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE template_id = VALUES(template_id)", uid, templateID, templateType); err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true})
|
|
}
|
|
|
|
func unsubscribeTemplate(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
templateType := c.InTrim("template_type")
|
|
if !store.Truthy(templateType) {
|
|
return c.Err(400, "缺少参数")
|
|
}
|
|
if _, err := c.DB.Exec("DELETE FROM subscribe_templates WHERE user_id = ? AND template_type = ?", c.UID(), templateType); err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true})
|
|
}
|
|
|
|
func mySubscribeTemplates(c *httpapi.Ctx) error {
|
|
rs, err := c.DB.Query("SELECT template_type, template_id FROM subscribe_templates WHERE user_id = ?", c.UID())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rs) == 0 {
|
|
return c.OK(M{"templates": []any{}}) // PHP 空数组 → []
|
|
}
|
|
out := M{}
|
|
for _, r := range rs {
|
|
out[r.Str("template_type")] = r["template_id"]
|
|
}
|
|
return c.OK(M{"templates": out})
|
|
}
|