Files
soul-yongping/soul-api/internal/handler/admin_linked_mp.go
Alex-larget db4b4b8b87 Add linked mini program functionality and enhance link tag handling
- Introduced `navigateToMiniProgramAppIdList` in app.json for mini program navigation.
- Updated link tag handling in the read page to support mini program keys and app IDs.
- Enhanced content parsing to include app ID and mini program key in link tags.
- Added linked mini programs management in the admin panel with API endpoints for CRUD operations.
- Improved UI for selecting linked mini programs in the content creation page.
2026-03-12 16:51:12 +08:00

213 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"net/http"
"strings"
"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"`
ID string `json:"id,omitempty"` // 兼容旧数据,新数据 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{}
}
// 兼容旧数据:无 key 时用 id 作为 key
for i := range list {
if list[i].Key == "" && list[i].ID != "" {
list[i].Key = list[i].ID
}
}
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
}
}
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].Key == "" && list[i].ID == body.Key) {
list[i].Name = body.Name
list[i].AppID = body.AppID
list[i].Path = body.Path
list[i].Sort = body.Sort
if list[i].Key == "" {
list[i].Key = list[i].ID
}
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
}
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 && (item.Key != "" || item.ID != 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
}
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
}