chore: 清理敏感与开发文档,仅同步代码

- 永久忽略并从仓库移除 开发文档/
- 移除并忽略 .env 与小程序私有配置
- 同步小程序/管理端/API与脚本改动

Made-with: Cursor
This commit is contained in:
卡若
2026-03-17 17:50:12 +08:00
parent 868b0a10d9
commit 76965adb23
443 changed files with 24175 additions and 64154 deletions

View File

@@ -1,6 +1,7 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -8,6 +9,7 @@ import (
"strings"
"time"
"soul-api/internal/cache"
"soul-api/internal/config"
"soul-api/internal/database"
"soul-api/internal/model"
@@ -16,10 +18,15 @@ import (
)
// GetPublicDBConfig GET /api/miniprogram/config 公开接口,供小程序获取完整配置(与 next-project 对齐)
// 从 system_config 读取 chapter_config、feature_config、mp_config合并后返回免费以章节 is_free/price 为准)
// Redis 缓存 10min配置变更时失效
func GetPublicDBConfig(c *gin.Context) {
var cached map[string]interface{}
if cache.Get(context.Background(), cache.KeyConfigMiniprogram, &cached) && len(cached) > 0 {
c.JSON(http.StatusOK, cached)
return
}
defaultPrices := gin.H{"section": float64(1), "fullbook": 9.9}
defaultFeatures := gin.H{"matchEnabled": true, "referralEnabled": true, "searchEnabled": true, "aboutEnabled": true}
defaultFeatures := gin.H{"matchEnabled": true, "referralEnabled": true, "searchEnabled": true}
apiDomain := "https://soulapi.quwanzhi.com"
if cfg := config.Get(); cfg != nil && cfg.BaseURL != "" {
apiDomain = cfg.BaseURL
@@ -101,6 +108,33 @@ func GetPublicDBConfig(c *gin.Context) {
if _, has := out["userDiscount"]; !has {
out["userDiscount"] = float64(5)
}
// 链接标签列表(小程序 onLinkTagTap 需要 typeminiprogram 类型存 mpKey用 key 查 linkedMiniprograms 得 appId
var linkTagRows []model.LinkTag
if err := db.Order("label ASC").Find(&linkTagRows).Error; err == nil {
tags := make([]gin.H, 0, len(linkTagRows))
for _, t := range linkTagRows {
h := gin.H{"tagId": t.TagID, "label": t.Label, "url": t.URL, "type": t.Type, "pagePath": t.PagePath}
if t.Type == "miniprogram" {
h["mpKey"] = t.AppID // miniprogram 类型时 AppID 列存的是密钥
} else {
h["appId"] = t.AppID
}
tags = append(tags, h)
}
out["linkTags"] = tags
}
// 关联小程序列表key 为 32 位密钥,小程序用 key 查 appId 后 wx.navigateToMiniProgram
var linkedMpRow model.SystemConfig
if err := db.Where("config_key = ?", "linked_miniprograms").First(&linkedMpRow).Error; err == nil && len(linkedMpRow.ConfigValue) > 0 {
var linkedList []gin.H
if err := json.Unmarshal(linkedMpRow.ConfigValue, &linkedList); err == nil && len(linkedList) > 0 {
out["linkedMiniprograms"] = linkedList
}
}
if _, has := out["linkedMiniprograms"]; !has {
out["linkedMiniprograms"] = []gin.H{}
}
cache.Set(context.Background(), cache.KeyConfigMiniprogram, out, cache.ConfigTTL)
c.JSON(http.StatusOK, out)
}
@@ -148,11 +182,12 @@ func AdminSettingsGet(c *gin.Context) {
}
out := gin.H{
"success": true,
"featureConfig": gin.H{"matchEnabled": true, "referralEnabled": true, "searchEnabled": true, "aboutEnabled": true},
"featureConfig": gin.H{"matchEnabled": true, "referralEnabled": true, "searchEnabled": true},
"siteSettings": gin.H{"sectionPrice": float64(1), "baseBookPrice": 9.9, "distributorShare": float64(90), "authorInfo": gin.H{}},
"mpConfig": defaultMp,
"ossConfig": gin.H{},
}
keys := []string{"feature_config", "site_settings", "mp_config"}
keys := []string{"feature_config", "site_settings", "mp_config", "oss_config"}
for _, k := range keys {
var row model.SystemConfig
if err := db.Where("config_key = ?", k).First(&row).Error; err != nil {
@@ -182,6 +217,10 @@ func AdminSettingsGet(c *gin.Context) {
}
out["mpConfig"] = merged
}
case "oss_config":
if m, ok := val.(map[string]interface{}); ok {
out["ossConfig"] = m
}
}
}
c.JSON(http.StatusOK, out)
@@ -193,6 +232,7 @@ func AdminSettingsPost(c *gin.Context) {
FeatureConfig map[string]interface{} `json:"featureConfig"`
SiteSettings map[string]interface{} `json:"siteSettings"`
MpConfig map[string]interface{} `json:"mpConfig"`
OssConfig map[string]interface{} `json:"ossConfig"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
@@ -234,6 +274,13 @@ func AdminSettingsPost(c *gin.Context) {
return
}
}
if body.OssConfig != nil {
if err := saveKey("oss_config", "阿里云 OSS 配置", body.OssConfig); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存 OSS 配置失败: " + err.Error()})
return
}
}
cache.InvalidateConfig()
c.JSON(http.StatusOK, gin.H{"success": true, "message": "设置已保存"})
}
@@ -317,6 +364,7 @@ func AdminReferralSettingsPost(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
cache.InvalidateConfig()
c.JSON(http.StatusOK, gin.H{"success": true, "message": "推广设置已保存"})
}
@@ -488,6 +536,7 @@ func DBConfigPost(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
cache.InvalidateConfig()
c.JSON(http.StatusOK, gin.H{"success": true, "message": "配置保存成功"})
}
@@ -514,14 +563,12 @@ func DBUsersList(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "user": nil})
return
}
// 填充 hasFullBookis_vip 或 orders
// 填充 hasFullBookorders、is_vip、手动设置的 has_full_book
var cnt int64
db.Model(&model.Order{}).Where("user_id = ? AND (status = ? OR status = ?) AND (product_type = ? OR product_type = ?)",
id, "paid", "completed", "fullbook", "vip").Count(&cnt)
user.HasFullBook = ptrBool(cnt > 0)
if user.IsVip != nil && *user.IsVip {
user.HasFullBook = ptrBool(true)
}
hasFull := cnt > 0 || (user.IsVip != nil && *user.IsVip) || (user.HasFullBook != nil && *user.HasFullBook)
user.HasFullBook = ptrBool(hasFull)
c.JSON(http.StatusOK, gin.H{"success": true, "user": user})
return
}
@@ -644,11 +691,14 @@ func DBUsersList(c *gin.Context) {
// 填充每个用户的实时计算字段
for i := range users {
uid := users[i].ID
// 购买状态(含手动设置的 VIPis_vip=1 且 vip_expire_date>NOW
// 购买状态(含订单、is_vip、手动设置的 has_full_book
hasFull := hasFullBookMap[uid]
if users[i].IsVip != nil && *users[i].IsVip && users[i].VipExpireDate != nil && users[i].VipExpireDate.After(time.Now()) {
hasFull = true
}
if users[i].HasFullBook != nil && *users[i].HasFullBook {
hasFull = true
}
users[i].HasFullBook = ptrBool(hasFull)
users[i].PurchasedSectionCount = sectionCountMap[uid]
// 分销收益
@@ -712,13 +762,14 @@ func DBUsersAction(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "user": u, "isNew": true, "message": "用户创建成功"})
return
}
// PUT 更新(含 VIP 手动设置is_vip、vip_expire_date、vip_name、vip_avatar、vip_project、vip_contact、vip_bio
// PUT 更新(含 VIP 手动设置is_vip、vip_expire_date、vip_name、vip_avatar、vip_project、vip_contact、vip_biotags 存 ckb_tags
var body struct {
ID string `json:"id"`
Nickname *string `json:"nickname"`
Phone *string `json:"phone"`
WechatID *string `json:"wechatId"`
Avatar *string `json:"avatar"`
Tags *string `json:"tags"` // JSON 数组字符串,如 ["创业者","电商"],存 ckb_tags
HasFullBook *bool `json:"hasFullBook"`
IsAdmin *bool `json:"isAdmin"`
Earnings *float64 `json:"earnings"`
@@ -763,6 +814,9 @@ func DBUsersAction(c *gin.Context) {
if body.Avatar != nil {
updates["avatar"] = *body.Avatar
}
if body.Tags != nil {
updates["ckb_tags"] = *body.Tags
}
if body.HasFullBook != nil {
updates["has_full_book"] = *body.HasFullBook
}
@@ -855,7 +909,26 @@ func DBUsersDelete(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "用户ID不能为空"})
return
}
if err := database.DB().Where("id = ?", id).Delete(&model.User{}).Error; err != nil {
db := database.DB()
cleanupTables := []struct{ table, col string }{
{"match_records", "user_id"},
{"reading_progress", "user_id"},
{"user_tracks", "user_id"},
{"referral_bindings", "referrer_id"},
{"referral_bindings", "referee_id"},
{"referral_visits", "visitor_id"},
{"ckb_submit_records", "user_id"},
{"ckb_lead_records", "user_id"},
{"user_addresses", "user_id"},
{"user_balances", "user_id"},
{"balance_transactions", "user_id"},
{"withdrawals", "user_id"},
{"orders", "user_id"},
}
for _, t := range cleanupTables {
db.Exec("DELETE FROM "+t.table+" WHERE "+t.col+" = ?", id)
}
if err := db.Where("id = ?", id).Delete(&model.User{}).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}