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

@@ -0,0 +1,206 @@
package handler
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"net/http"
"strings"
"soul-api/internal/cache"
"soul-api/internal/database"
"soul-api/internal/model"
"github.com/gin-gonic/gin"
)
const linkedMpConfigKey = "linked_miniprograms"
// LinkedMpItem 关联小程序项key 为 32 位密钥,链接标签存 key小程序端用 key 查 appId
type LinkedMpItem struct {
Key string `json:"key"`
Name string `json:"name"`
AppID string `json:"appId"`
Path string `json:"path,omitempty"`
Sort int `json:"sort"`
}
// AdminLinkedMpList GET /api/admin/linked-miniprograms 管理端-关联小程序列表
func AdminLinkedMpList(c *gin.Context) {
db := database.DB()
var row model.SystemConfig
if err := db.Where("config_key = ?", linkedMpConfigKey).First(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "data": []LinkedMpItem{}})
return
}
var list []LinkedMpItem
if err := json.Unmarshal(row.ConfigValue, &list); err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "data": []LinkedMpItem{}})
return
}
if list == nil {
list = []LinkedMpItem{}
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
}
// AdminLinkedMpCreate POST /api/admin/linked-miniprograms 管理端-新增关联小程序
func AdminLinkedMpCreate(c *gin.Context) {
var body struct {
Name string `json:"name" binding:"required"`
AppID string `json:"appId" binding:"required"`
Path string `json:"path"`
Sort int `json:"sort"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请填写小程序名称和 AppID"})
return
}
body.Name = trimSpace(body.Name)
body.AppID = trimSpace(body.AppID)
if body.Name == "" || body.AppID == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "小程序名称和 AppID 不能为空"})
return
}
key, err := genMpKey()
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "生成密钥失败"})
return
}
item := LinkedMpItem{Key: key, Name: body.Name, AppID: body.AppID, Path: body.Path, Sort: body.Sort}
db := database.DB()
var row model.SystemConfig
var list []LinkedMpItem
if err := db.Where("config_key = ?", linkedMpConfigKey).First(&row).Error; err != nil {
list = []LinkedMpItem{}
} else {
_ = json.Unmarshal(row.ConfigValue, &list)
if list == nil {
list = []LinkedMpItem{}
}
}
list = append(list, item)
valBytes, _ := json.Marshal(list)
desc := "关联小程序列表,用于 wx.navigateToMiniProgram 跳转"
if row.ConfigKey == "" {
row = model.SystemConfig{ConfigKey: linkedMpConfigKey, ConfigValue: valBytes, Description: &desc}
if err := db.Create(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存失败: " + err.Error()})
return
}
} else {
row.ConfigValue = valBytes
if err := db.Save(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存失败: " + err.Error()})
return
}
}
cache.InvalidateConfig()
c.JSON(http.StatusOK, gin.H{"success": true, "data": item})
}
// AdminLinkedMpUpdate PUT /api/admin/linked-miniprograms 管理端-编辑关联小程序
func AdminLinkedMpUpdate(c *gin.Context) {
var body struct {
Key string `json:"key" binding:"required"`
Name string `json:"name" binding:"required"`
AppID string `json:"appId" binding:"required"`
Path string `json:"path"`
Sort int `json:"sort"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数无效"})
return
}
body.Name = trimSpace(body.Name)
body.AppID = trimSpace(body.AppID)
if body.Name == "" || body.AppID == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "小程序名称和 AppID 不能为空"})
return
}
db := database.DB()
var row model.SystemConfig
if err := db.Where("config_key = ?", linkedMpConfigKey).First(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "未找到该记录"})
return
}
var list []LinkedMpItem
if err := json.Unmarshal(row.ConfigValue, &list); err != nil || list == nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "数据格式错误"})
return
}
found := false
for i := range list {
if list[i].Key == body.Key {
list[i].Name = body.Name
list[i].AppID = body.AppID
list[i].Path = body.Path
list[i].Sort = body.Sort
found = true
break
}
}
if !found {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "未找到该记录"})
return
}
valBytes, _ := json.Marshal(list)
row.ConfigValue = valBytes
if err := db.Save(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存失败: " + err.Error()})
return
}
cache.InvalidateConfig()
c.JSON(http.StatusOK, gin.H{"success": true})
}
// AdminLinkedMpDelete DELETE /api/admin/linked-miniprograms/:id 管理端-删除(:id 实际传 key
func AdminLinkedMpDelete(c *gin.Context) {
key := c.Param("id")
if key == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少密钥"})
return
}
db := database.DB()
var row model.SystemConfig
if err := db.Where("config_key = ?", linkedMpConfigKey).First(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "未找到该记录"})
return
}
var list []LinkedMpItem
if err := json.Unmarshal(row.ConfigValue, &list); err != nil || list == nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "数据格式错误"})
return
}
newList := make([]LinkedMpItem, 0, len(list))
for _, item := range list {
if item.Key != key {
newList = append(newList, item)
}
}
valBytes, _ := json.Marshal(newList)
row.ConfigValue = valBytes
if err := db.Save(&row).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "删除失败: " + err.Error()})
return
}
cache.InvalidateConfig()
c.JSON(http.StatusOK, gin.H{"success": true})
}
// genMpKey 生成 32 位英文+数字密钥,供链接标签引用
func genMpKey() (string, error) {
b := make([]byte, 24)
if _, err := rand.Read(b); err != nil {
return "", err
}
// base64 编码后取 32 位,去掉 +/= 仅保留字母数字
s := base64.URLEncoding.EncodeToString(b)
s = strings.ReplaceAll(s, "+", "")
s = strings.ReplaceAll(s, "/", "")
s = strings.ReplaceAll(s, "=", "")
if len(s) >= 32 {
return s[:32], nil
}
return s + "0123456789abcdefghijklmnopqrstuv"[:(32-len(s))], nil
}