chore: 新增 .gitignore 排除开发文档,同步代码与构建产物

Made-with: Cursor
This commit is contained in:
卡若
2026-03-16 09:21:39 +08:00
parent fa9903d235
commit 85ce2422d1
40 changed files with 2315 additions and 947 deletions

View File

@@ -161,7 +161,7 @@ func Load() (*Config, error) {
version := os.Getenv("APP_VERSION")
if version == "" {
version = "0.0.0"
version = "dev"
}
// 微信配置

View File

@@ -2,6 +2,7 @@ package handler
import (
"net/http"
"strconv"
"soul-api/internal/database"
"soul-api/internal/model"
@@ -11,9 +12,29 @@ import (
// AdminChaptersList GET /api/admin/chapters 从 chapters 表组树part -> chapters -> sections
func AdminChaptersList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
if pageSize < 1 {
pageSize = 20
}
if pageSize > 200 {
pageSize = 200
}
var list []model.Chapter
if err := database.DB().Order("sort_order ASC, id ASC").Find(&list).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"structure": []interface{}{}, "stats": nil}})
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{
"structure": []interface{}{},
"records": []interface{}{},
"stats": nil,
"page": page,
"pageSize": pageSize,
"totalPages": 0,
"total": 0,
}})
return
}
type section struct {
@@ -25,6 +46,19 @@ func AdminChaptersList(c *gin.Context) {
EditionStandard *bool `json:"editionStandard,omitempty"`
EditionPremium *bool `json:"editionPremium,omitempty"`
}
type sectionRecord struct {
ID string `json:"id"`
PartID string `json:"partId"`
PartTitle string `json:"partTitle"`
ChapterID string `json:"chapterId"`
ChapterTitle string `json:"chapterTitle"`
Title string `json:"title"`
Price float64 `json:"price"`
IsFree bool `json:"isFree"`
Status string `json:"status"`
EditionStandard *bool `json:"editionStandard,omitempty"`
EditionPremium *bool `json:"editionPremium,omitempty"`
}
type chapter struct {
ID string `json:"id"`
Title string `json:"title"`
@@ -38,6 +72,7 @@ func AdminChaptersList(c *gin.Context) {
}
partMap := make(map[string]*part)
chapterMap := make(map[string]map[string]*chapter)
records := make([]sectionRecord, 0, len(list))
for _, row := range list {
if partMap[row.PartID] == nil {
partMap[row.PartID] = &part{ID: row.PartID, Title: row.PartTitle, Type: "part", Chapters: []chapter{}}
@@ -66,6 +101,19 @@ func AdminChaptersList(c *gin.Context) {
ID: row.ID, Title: row.SectionTitle, Price: price, IsFree: isFree, Status: st,
EditionStandard: row.EditionStandard, EditionPremium: row.EditionPremium,
})
records = append(records, sectionRecord{
ID: row.ID,
PartID: row.PartID,
PartTitle: row.PartTitle,
ChapterID: row.ChapterID,
ChapterTitle: row.ChapterTitle,
Title: row.SectionTitle,
Price: price,
IsFree: isFree,
Status: st,
EditionStandard: row.EditionStandard,
EditionPremium: row.EditionPremium,
})
}
structure := make([]part, 0, len(partMap))
for _, p := range partMap {
@@ -73,9 +121,29 @@ func AdminChaptersList(c *gin.Context) {
}
var total int64
database.DB().Model(&model.Chapter{}).Count(&total)
totalPages := 0
if pageSize > 0 {
totalPages = (int(total) + pageSize - 1) / pageSize
}
start := (page - 1) * pageSize
if start > len(records) {
start = len(records)
}
end := start + pageSize
if end > len(records) {
end = len(records)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{"structure": structure, "stats": gin.H{"totalSections": total}},
"data": gin.H{
"structure": structure,
"records": records[start:end],
"stats": gin.H{"totalSections": total},
"page": page,
"pageSize": pageSize,
"totalPages": totalPages,
"total": total,
},
})
}

View File

@@ -14,6 +14,11 @@ import (
"gorm.io/gorm"
)
// AdminAppUsersList GET /api/admin/users 普通用户列表兼容入口(转发到 DBUsersList
func AdminAppUsersList(c *gin.Context) {
DBUsersList(c)
}
// AdminUsersList GET /api/admin/users 管理员用户列表(仅 super_admin
func AdminUsersList(c *gin.Context) {
claims := middleware.GetAdminClaims(c)

View File

@@ -125,8 +125,9 @@ func BalanceRechargeConfirm(c *gin.Context) {
// POST /api/miniprogram/balance/gift 小程序-代付解锁(用余额帮他人解锁章节)
func BalanceGift(c *gin.Context) {
var body struct {
GiverID string `json:"giverId" binding:"required"`
SectionID string `json:"sectionId" binding:"required"`
GiverID string `json:"giverId" binding:"required"`
SectionID string `json:"sectionId" binding:"required"`
PaidViaWechat bool `json:"paidViaWechat"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "参数错误"})
@@ -150,46 +151,56 @@ func BalanceGift(c *gin.Context) {
}
var giftCode string
err := db.Transaction(func(tx *gorm.DB) error {
var bal model.UserBalance
if err := tx.Where("user_id = ?", body.GiverID).First(&bal).Error; err != nil || bal.Balance < price {
return fmt.Errorf("余额不足,当前 ¥%.2f,需要 ¥%.2f", bal.Balance, price)
}
code := make([]byte, 16)
rand.Read(code)
giftCode = hex.EncodeToString(code)
if err := tx.Model(&bal).Updates(map[string]interface{}{
"balance": gorm.Expr("balance - ?", price),
"total_gifted": gorm.Expr("total_gifted + ?", price),
}).Error; err != nil {
return err
}
code := make([]byte, 16)
rand.Read(code)
giftCode = hex.EncodeToString(code)
tx.Create(&model.GiftUnlock{
if body.PaidViaWechat {
db.Create(&model.GiftUnlock{
GiftCode: giftCode,
GiverID: body.GiverID,
SectionID: body.SectionID,
Amount: price,
Status: "pending",
})
} else {
err := db.Transaction(func(tx *gorm.DB) error {
var bal model.UserBalance
if err := tx.Where("user_id = ?", body.GiverID).First(&bal).Error; err != nil || bal.Balance < price {
return fmt.Errorf("余额不足,当前 ¥%.2f,需要 ¥%.2f", bal.Balance, price)
}
tx.Create(&model.BalanceTransaction{
UserID: body.GiverID,
Type: "gift",
Amount: -price,
BalanceAfter: bal.Balance - price,
SectionID: &body.SectionID,
Description: fmt.Sprintf("代付章节 %s (¥%.2f)", body.SectionID, price),
if err := tx.Model(&bal).Updates(map[string]interface{}{
"balance": gorm.Expr("balance - ?", price),
"total_gifted": gorm.Expr("total_gifted + ?", price),
}).Error; err != nil {
return err
}
tx.Create(&model.GiftUnlock{
GiftCode: giftCode,
GiverID: body.GiverID,
SectionID: body.SectionID,
Amount: price,
Status: "pending",
})
tx.Create(&model.BalanceTransaction{
UserID: body.GiverID,
Type: "gift",
Amount: -price,
BalanceAfter: bal.Balance - price,
SectionID: &body.SectionID,
Description: fmt.Sprintf("代付章节 %s (¥%.2f)", body.SectionID, price),
})
return nil
})
return nil
})
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()})
return
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()})
return
}
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{
@@ -418,5 +429,47 @@ func BalanceGiftInfo(c *gin.Context) {
"amount": gift.Amount,
"status": gift.Status,
"giverId": gift.GiverID,
"mid": chapter.MID,
}})
}
// GET /api/miniprogram/balance/gifts?userId=xxx 我的代付列表
func BalanceGiftList(c *gin.Context) {
userId := c.Query("userId")
if userId == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 userId"})
return
}
db := database.DB()
var gifts []model.GiftUnlock
db.Where("giver_id = ?", userId).Order("created_at DESC").Limit(50).Find(&gifts)
type giftItem struct {
GiftCode string `json:"giftCode"`
SectionID string `json:"sectionId"`
SectionTitle string `json:"sectionTitle"`
Amount float64 `json:"amount"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
}
var result []giftItem
for _, g := range gifts {
var ch model.Chapter
title := g.SectionID
if db.Where("id = ?", g.SectionID).First(&ch).Error == nil && ch.SectionTitle != "" {
title = ch.SectionTitle
}
result = append(result, giftItem{
GiftCode: g.GiftCode,
SectionID: g.SectionID,
SectionTitle: title,
Amount: g.Amount,
Status: g.Status,
CreatedAt: g.CreatedAt.Format("2006-01-02 15:04"),
})
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"gifts": result}})
}

View File

@@ -36,7 +36,7 @@ func BookAllChapters(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}})
return
}
freeIDs := getFreeChapterIDs(db)
freeIDs := getEffectiveFreeChapterIDs(db)
for i := range list {
if freeIDs[list[i].ID] {
t := true
@@ -112,6 +112,24 @@ func getFreeChapterIDs(db *gorm.DB) map[string]bool {
return ids
}
func getEffectiveFreeChapterIDs(db *gorm.DB) map[string]bool {
ids := getFreeChapterIDs(db)
var rows []struct {
ID string `gorm:"column:id"`
}
if err := db.Model(&model.Chapter{}).
Select("id").
Where("is_free = ? OR price = 0", true).
Find(&rows).Error; err == nil {
for _, row := range rows {
if row.ID != "" {
ids[row.ID] = true
}
}
}
return ids
}
// checkUserChapterAccess 判断 userId 是否有权读取 chapterIDVIP / 全书购买 / 单章购买)
// isPremium=true 表示增值版fullbook 买断不含增值版
func checkUserChapterAccess(db *gorm.DB, userID, chapterID string, isPremium bool) bool {
@@ -587,8 +605,7 @@ func BookStats(c *gin.Context) {
db := database.DB()
var total int64
db.Model(&model.Chapter{}).Count(&total)
var freeCount int64
db.Model(&model.Chapter{}).Where("is_free = ?", true).Count(&freeCount)
freeCount := len(getEffectiveFreeChapterIDs(db))
var totalWords struct{ S int64 }
db.Model(&model.Chapter{}).Select("COALESCE(SUM(word_count),0) as s").Scan(&totalWords)
var userCount int64

View File

@@ -340,24 +340,13 @@ func CKBIndexLead(c *gin.Context) {
// 首页固定使用全局密钥system_config > .env > 代码内置
leadKey := getCkbLeadApiKey()
// 去重限频2 分钟内同一用户/手机/微信只能提交一次
var cond []string
var args []interface{}
if body.UserID != "" {
cond = append(cond, "user_id = ?")
args = append(args, body.UserID)
}
cond = append(cond, "phone = ?")
args = append(args, phone)
cutoff := time.Now().Add(-2 * time.Minute)
var recentCount int64
if db.Model(&model.CkbLeadRecord{}).Where(strings.Join(cond, " OR "), args...).Where("created_at > ?", cutoff).Count(&recentCount) == nil && recentCount > 0 {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "您操作太频繁请2分钟后再试"})
return
}
// 去重:同一用户只记录一次(首页链接卡若)
repeatedSubmit := false
var existCount int64
repeatedSubmit = db.Model(&model.CkbLeadRecord{}).Where(strings.Join(cond, " OR "), args...).Count(&existCount) == nil && existCount > 0
if body.UserID != "" {
var existCount int64
db.Model(&model.CkbLeadRecord{}).Where("user_id = ? AND source = ?", body.UserID, "index_link_button").Count(&existCount)
repeatedSubmit = existCount > 0
}
source := "index_link_button"
paramsJSON, _ := json.Marshal(map[string]interface{}{
@@ -478,33 +467,12 @@ func CKBLead(c *gin.Context) {
}
}
// 去重限频2 分钟内同一用户/手机/微信只能提交一次
var cond []string
var args []interface{}
if body.UserID != "" {
cond = append(cond, "user_id = ?")
args = append(args, body.UserID)
}
if phone != "" {
cond = append(cond, "phone = ?")
args = append(args, phone)
}
if wechatId != "" {
cond = append(cond, "wechat_id = ?")
args = append(args, wechatId)
}
if len(cond) > 0 {
cutoff := time.Now().Add(-2 * time.Minute)
var recentCount int64
if db.Model(&model.CkbLeadRecord{}).Where(strings.Join(cond, " OR "), args...).Where("created_at > ?", cutoff).Count(&recentCount) == nil && recentCount > 0 {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "您操作太频繁请2分钟后再试"})
return
}
}
// 去重:同一用户对同一目标人物只记录一次(不再限制时间间隔,允许对不同人物立即提交)
repeatedSubmit := false
if len(cond) > 0 {
if body.UserID != "" && body.TargetUserID != "" {
var existCount int64
repeatedSubmit = db.Model(&model.CkbLeadRecord{}).Where(strings.Join(cond, " OR "), args...).Count(&existCount) == nil && existCount > 0
db.Model(&model.CkbLeadRecord{}).Where("user_id = ? AND target_person_id = ?", body.UserID, body.TargetUserID).Count(&existCount)
repeatedSubmit = existCount > 0
}
source := strings.TrimSpace(body.Source)

View File

@@ -323,7 +323,7 @@ func AdminCKBPlans(c *gin.Context) {
if keyword != "" {
values.Set("keyword", keyword)
}
planURL := ckbOpenBaseURL + "/v1/plans?" + values.Encode()
planURL := ckbOpenBaseURL + "/v1/plan/list?" + values.Encode()
req, err := http.NewRequest(http.MethodGet, planURL, nil)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "构造请求失败"})
@@ -344,6 +344,16 @@ func AdminCKBPlans(c *gin.Context) {
return
}
code, _ := parsed["code"].(float64)
if int(code) != 200 {
msg, _ := parsed["msg"].(string)
if msg == "" {
msg = "存客宝返回异常"
}
c.JSON(http.StatusOK, gin.H{"success": false, "error": msg})
return
}
var listAny interface{}
if dataVal, ok := parsed["data"].(map[string]interface{}); ok {
listAny = dataVal["list"]
@@ -359,42 +369,113 @@ func AdminCKBPlans(c *gin.Context) {
plans := make([]map[string]interface{}, 0)
if arr, ok := listAny.([]interface{}); ok {
for _, item := range arr {
if m, ok := item.(map[string]interface{}); ok {
plans = append(plans, map[string]interface{}{
"id": m["id"],
"name": m["name"],
"apiKey": m["apiKey"],
"sceneId": m["sceneId"],
"scenario": m["scenario"],
"enabled": m["enabled"],
"greeting": m["greeting"],
"tips": m["tips"],
"remarkType": m["remarkType"],
"remarkFormat": m["remarkFormat"],
"addInterval": m["addInterval"],
"startTime": m["startTime"],
"endTime": m["endTime"],
"deviceGroups": m["deviceGroups"],
})
m, ok := item.(map[string]interface{})
if !ok {
continue
}
reqConf, _ := m["reqConf"].(map[string]interface{})
sceneConf, _ := m["sceneConf"].(map[string]interface{})
enabled := false
if sceneConf != nil {
if v, ok := sceneConf["enabled"].(bool); ok {
enabled = v
}
}
if m["status"] != nil {
if s, ok := m["status"].(float64); ok {
enabled = int(s) == 1
}
}
greeting, _ := mapStr(reqConf, "greeting")
tips, _ := mapStr(sceneConf, "tips")
remarkType, _ := mapStr(reqConf, "remarkType")
remarkFormat, _ := mapStr(reqConf, "remarkFormat")
startTime, _ := mapStr(reqConf, "startTime")
endTime, _ := mapStr(reqConf, "endTime")
addInterval := mapFloat(reqConf, "addFriendInterval")
if addInterval == 0 {
addInterval = mapFloat(sceneConf, "addInterval")
}
var deviceGroups interface{}
if reqConf != nil {
deviceGroups = reqConf["device"]
}
if deviceGroups == nil && sceneConf != nil {
deviceGroups = sceneConf["deviceGroups"]
}
plans = append(plans, map[string]interface{}{
"id": m["id"],
"name": m["name"],
"apiKey": m["apiKey"],
"sceneId": m["sceneId"],
"scenario": m["sceneId"],
"enabled": enabled,
"greeting": greeting,
"tips": tips,
"remarkType": remarkType,
"remarkFormat": remarkFormat,
"addInterval": addInterval,
"startTime": startTime,
"endTime": endTime,
"deviceGroups": deviceGroups,
})
}
}
total := 0
switch tv := parsed["total"].(type) {
case float64:
total = int(tv)
case int:
total = tv
case string:
if n, err := strconv.Atoi(tv); err == nil {
total = n
if dataVal, ok := parsed["data"].(map[string]interface{}); ok {
switch tv := dataVal["total"].(type) {
case float64:
total = int(tv)
case int:
total = tv
case string:
if n, err := strconv.Atoi(tv); err == nil {
total = n
}
}
}
c.JSON(http.StatusOK, gin.H{"success": true, "plans": plans, "total": total})
}
func mapStr(m map[string]interface{}, key string) (string, bool) {
if m == nil {
return "", false
}
v, ok := m[key]
if !ok || v == nil {
return "", false
}
s, ok := v.(string)
return s, ok
}
func mapFloat(m map[string]interface{}, key string) float64 {
if m == nil {
return 0
}
v, ok := m[key]
if !ok || v == nil {
return 0
}
switch val := v.(type) {
case float64:
return val
case int:
return float64(val)
case string:
if n, err := strconv.ParseFloat(val, 64); err == nil {
return n
}
}
return 0
}
// AdminCKBPlanDetail GET /api/admin/ckb/plan-detail?planId=xxx 管理端-存客宝获客计划详情
func AdminCKBPlanDetail(c *gin.Context) {
planIDStr := c.Query("planId")

View File

@@ -13,6 +13,7 @@ import (
"soul-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// GetPublicDBConfig GET /api/miniprogram/config 公开接口,供小程序获取完整配置(与 next-project 对齐)
@@ -228,7 +229,7 @@ func AdminSettingsGet(c *gin.Context) {
}
case "oss_config":
if m, ok := val.(map[string]interface{}); ok && len(m) > 0 {
out["ossConfig"] = m
out["ossConfig"] = sanitizeOSSConfig(m)
}
}
}
@@ -284,6 +285,7 @@ func AdminSettingsPost(c *gin.Context) {
}
}
if body.OssConfig != nil {
body.OssConfig = mergeOSSConfigSecret(db, body.OssConfig)
if err := saveKey("oss_config", "阿里云 OSS 配置", body.OssConfig); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存 OSS 配置失败: " + err.Error()})
return
@@ -1206,6 +1208,49 @@ func DBConfigDelete(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
}
func sanitizeOSSConfig(cfg map[string]interface{}) gin.H {
out := gin.H{}
for k, v := range cfg {
out[k] = v
}
if secret, ok := out["accessKeySecret"].(string); ok && secret != "" {
out["accessKeySecret"] = "****"
}
return out
}
func mergeOSSConfigSecret(db *gorm.DB, incoming map[string]interface{}) map[string]interface{} {
if incoming == nil {
return incoming
}
secret, _ := incoming["accessKeySecret"].(string)
if secret != "" && secret != "****" {
return incoming
}
var row model.SystemConfig
if err := db.Where("config_key = ?", "oss_config").First(&row).Error; err != nil {
return incoming
}
var existing map[string]interface{}
if err := json.Unmarshal(row.ConfigValue, &existing); err != nil {
return incoming
}
existingSecret, _ := existing["accessKeySecret"].(string)
if existingSecret == "" {
return incoming
}
merged := map[string]interface{}{}
for k, v := range incoming {
merged[k] = v
}
merged["accessKeySecret"] = existingSecret
return merged
}
// DBInitGet GET /api/db/init
func DBInitGet(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"message": "ok"}})

View File

@@ -74,7 +74,9 @@ func DBPersonSave(c *gin.Context) {
existing.Name = body.Name
existing.Aliases = body.Aliases
existing.Label = body.Label
existing.CkbApiKey = body.CkbApiKey
if strings.TrimSpace(body.CkbApiKey) != "" {
existing.CkbApiKey = body.CkbApiKey
}
existing.Greeting = body.Greeting
existing.Tips = body.Tips
existing.RemarkType = body.RemarkType
@@ -99,27 +101,20 @@ func DBPersonSave(c *gin.Context) {
} else {
existing.DeviceGroups = ""
}
db.Save(&existing)
if err := db.Save(&existing).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "person": existing})
return
}
// 新增:创建本地 Person 记录前,先在存客宝创建获客计划并获取 planId + apiKey
// 新增:创建本地人物记录,存客宝同步失败不阻断 @人物 与内容管理主链路
tok, err := genPersonToken()
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "生成 token 失败"})
return
}
// 1. 获取开放 API token
openToken, err := ckbOpenGetToken()
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
// 2. 构造创建计划请求体
// 参考 Cunkebao createPlanname, sceneId, scenario, remarkType, greeting, addInterval, startTime, endTime, enabled, tips, deviceGroups
name := fmt.Sprintf("SOUL链接人与事-%s", body.Name)
addInterval := 1
if body.AddFriendInterval != nil && *body.AddFriendInterval > 0 {
@@ -139,52 +134,18 @@ func DBPersonSave(c *gin.Context) {
deviceIDs = append(deviceIDs, id)
}
}
planPayload := map[string]interface{}{
"name": name,
"sceneId": 11,
"scenario": 11,
"remarkType": body.RemarkType,
"greeting": body.Greeting,
"addInterval": addInterval,
"startTime": startTime,
"endTime": endTime,
"enabled": true,
"tips": body.Tips,
"distributionEnabled": false,
}
if len(deviceIDs) > 0 {
planPayload["deviceGroups"] = deviceIDs
}
planID, ckbCreateData, ckbResponse, err := ckbOpenCreatePlan(openToken, planPayload)
if err != nil {
out := gin.H{"success": false, "error": "创建存客宝计划失败: " + err.Error()}
if ckbResponse != nil {
out["ckbResponse"] = ckbResponse
}
c.JSON(http.StatusOK, out)
return
}
// 3. 用 planId 拉计划详情,获取 apiKey
apiKey, err := ckbOpenGetPlanDetail(openToken, planID)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "创建成功但获取计划密钥失败: " + err.Error()})
return
}
newPerson := model.Person{
PersonID: body.PersonID,
Token: tok,
Name: body.Name,
Aliases: body.Aliases,
Label: body.Label,
CkbApiKey: apiKey,
CkbPlanID: planID,
Greeting: body.Greeting,
Tips: body.Tips,
RemarkType: body.RemarkType,
RemarkFormat: body.RemarkFormat,
PersonID: body.PersonID,
Token: tok,
Name: body.Name,
Aliases: body.Aliases,
Label: body.Label,
CkbApiKey: strings.TrimSpace(body.CkbApiKey),
Greeting: body.Greeting,
Tips: body.Tips,
RemarkType: body.RemarkType,
RemarkFormat: body.RemarkFormat,
AddFriendInterval: addInterval,
StartTime: startTime,
EndTime: endTime,
@@ -201,7 +162,67 @@ func DBPersonSave(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
resp := gin.H{"success": true, "person": newPerson}
planPayload := map[string]interface{}{
"name": name,
"sceneId": 11,
"scenario": 11,
"remarkType": body.RemarkType,
"greeting": body.Greeting,
"addInterval": addInterval,
"startTime": startTime,
"endTime": endTime,
"enabled": true,
"tips": body.Tips,
"distributionEnabled": false,
}
if len(deviceIDs) > 0 {
planPayload["deviceGroups"] = deviceIDs
}
openToken, tokenErr := ckbOpenGetToken()
if tokenErr != nil {
resp["ckbSyncError"] = tokenErr.Error()
resp["message"] = "人物已保存,存客宝同步失败,可稍后补同步"
c.JSON(http.StatusOK, resp)
return
}
planID, ckbCreateData, ckbResponse, planErr := ckbOpenCreatePlan(openToken, planPayload)
if planErr != nil {
resp["ckbSyncError"] = "创建存客宝计划失败: " + planErr.Error()
if ckbResponse != nil {
resp["ckbResponse"] = ckbResponse
}
resp["message"] = "人物已保存,存客宝同步失败,可稍后补同步"
c.JSON(http.StatusOK, resp)
return
}
apiKey, detailErr := ckbOpenGetPlanDetail(openToken, planID)
if detailErr != nil {
db.Model(&model.Person{}).Where("person_id = ?", newPerson.PersonID).Update("ckb_plan_id", planID)
newPerson.CkbPlanID = planID
resp["person"] = newPerson
resp["ckbSyncError"] = "创建成功但获取计划密钥失败: " + detailErr.Error()
resp["message"] = "人物已保存,存客宝部分同步成功"
if len(ckbCreateData) > 0 {
resp["ckbCreateResult"] = ckbCreateData
}
c.JSON(http.StatusOK, resp)
return
}
newPerson.CkbPlanID = planID
newPerson.CkbApiKey = apiKey
if err := db.Model(&model.Person{}).Where("person_id = ?", newPerson.PersonID).Updates(map[string]interface{}{
"ckb_api_key": apiKey,
"ckb_plan_id": planID,
}).Error; err == nil {
resp["person"] = newPerson
}
if len(ckbCreateData) > 0 {
resp["ckbCreateResult"] = ckbCreateData
}

View File

@@ -83,10 +83,11 @@ func Setup(cfg *config.Config) *gin.Engine {
admin.POST("/author-settings", handler.AdminAuthorSettingsPost)
admin.PUT("/orders/refund", handler.AdminOrderRefund)
admin.POST("/content/upload", handler.AdminContentUpload)
admin.GET("/users", handler.AdminUsersList)
admin.POST("/users", handler.AdminUsersAction)
admin.PUT("/users", handler.AdminUsersAction)
admin.DELETE("/users", handler.AdminUsersAction)
admin.GET("/users", handler.AdminAppUsersList)
admin.GET("/admin-users", handler.AdminUsersList)
admin.POST("/admin-users", handler.AdminUsersAction)
admin.PUT("/admin-users", handler.AdminUsersAction)
admin.DELETE("/admin-users", handler.AdminUsersAction)
admin.GET("/orders", handler.OrdersList)
admin.GET("/balance/summary", handler.BalanceSummary)
admin.GET("/shensheshou/query", handler.AdminShensheShouQuery)
@@ -341,6 +342,7 @@ func Setup(cfg *config.Config) *gin.Engine {
miniprogram.POST("/balance/gift", handler.BalanceGift)
miniprogram.POST("/balance/gift/redeem", handler.BalanceGiftRedeem)
miniprogram.GET("/balance/gift/info", handler.BalanceGiftInfo)
miniprogram.GET("/balance/gifts", handler.BalanceGiftList)
miniprogram.POST("/balance/refund", handler.BalanceRefund)
miniprogram.GET("/balance/transactions", handler.BalanceTransactions)
}