feat(contact): 添加联系信息提交功能
Build docker and publish / build (20.15.1) (push) Successful in 5m55s

实现联系信息提交功能,包括:
1. 新增ContactRequest类型定义
2. 添加POST /contact路由
3. 实现联系信息提交处理逻辑
4. 通过Telegram发送联系信息通知
5. 在Telegram配置中添加GroupChatID字段
This commit is contained in:
2025-12-21 19:32:23 -08:00
parent 2fdc9c8127
commit 74f4a12422
8 changed files with 207 additions and 12 deletions
+70
View File
@@ -0,0 +1,70 @@
package common
import (
"context"
"fmt"
"strconv"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type ContactLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewContactLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ContactLogic {
return &ContactLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ContactLogic) SubmitContact(req *types.ContactRequest) error {
if l.svcCtx.TelegramBot == nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "telegram bot not initialized")
}
chatIDStr := l.svcCtx.Config.Telegram.GroupChatID
if chatIDStr == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "telegram group chat id not configured")
}
chatID, err := strconv.ParseInt(chatIDStr, 10, 64)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "invalid group chat id: %v", err.Error())
}
name := escapeMarkdown(req.Name)
email := escapeMarkdown(req.Email)
other := req.OtherContact
if strings.TrimSpace(other) == "" {
other = "无"
}
other = escapeMarkdown(other)
notes := req.Notes
if strings.TrimSpace(notes) == "" {
notes = "无"
}
notes = escapeMarkdown(notes)
text := fmt.Sprintf("新的联系/合作信息\n称呼:%s\n邮箱:%s\n其他联系方式:%s\n优势/备注:%s", name, email, other, notes)
msg := tgbotapi.NewMessage(chatID, text)
msg.ParseMode = "markdown"
_, err = l.svcCtx.TelegramBot.Send(msg)
if err != nil {
l.Errorw("send telegram message failed", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "send telegram message failed: %v", err.Error())
}
return nil
}
func escapeMarkdown(s string) string {
return strings.ReplaceAll(s, "_", "\\_")
}
+80 -6
View File
@@ -2,9 +2,14 @@ package notify
import (
"context"
"encoding/json"
"strconv"
"strings"
iapmodel "github.com/perfect-panel/server/internal/model/iap/apple"
"github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
iapapple "github.com/perfect-panel/server/pkg/iap/apple"
"github.com/perfect-panel/server/pkg/logger"
"gorm.io/gorm"
@@ -82,10 +87,79 @@ func (l *AppleIAPNotifyLogic) Handle(signedPayload string) error {
}
}
}
pm, _ := iapapple.ParseProductMap(l.svcCtx.Config.Site.CustomData)
m := pm.Items[txPayload.ProductId]
// 若产品映射缺失,记录警告日志(不影响事务入库)
if m.DurationDays == 0 {
var days int64
{
pid := strings.ToLower(txPayload.ProductId)
parts := strings.Split(pid, ".")
for i := len(parts) - 1; i >= 0; i-- {
p := parts[i]
var unit string
if strings.HasPrefix(p, "day") {
unit = "Day"
p = p[len("day"):]
} else if strings.HasPrefix(p, "month") {
unit = "Month"
p = p[len("month"):]
} else if strings.HasPrefix(p, "year") {
unit = "Year"
p = p[len("year"):]
}
if unit != "" {
digits := p
for j := 0; j < len(digits); j++ {
if digits[j] < '0' || digits[j] > '9' {
digits = digits[:j]
break
}
}
if q, e := strconv.ParseInt(digits, 10, 64); e == nil && q > 0 {
switch unit {
case "Day":
days = q
case "Month":
days = q * 30
case "Year":
days = q * 365
}
break
}
}
}
}
if days == 0 {
_, subs, e := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{
Page: 1,
Size: 9999,
Show: true,
Sell: true,
DefaultLanguage: true,
})
if e == nil && len(subs) > 0 {
for _, item := range subs {
var discounts []types.SubscribeDiscount
if item.Discount != "" {
_ = json.Unmarshal([]byte(item.Discount), &discounts)
}
for _, d := range discounts {
if strings.Contains(strings.ToLower(txPayload.ProductId), strings.ToLower(item.UnitTime)) && d.Quantity > 0 {
// fallback not strict
if item.UnitTime == "Day" {
days = int64(d.Quantity)
} else if item.UnitTime == "Month" {
days = int64(d.Quantity) * 30
} else if item.UnitTime == "Year" {
days = int64(d.Quantity) * 365
}
break
}
}
if days > 0 {
break
}
}
}
}
if days == 0 {
l.Errorw("iap notify product mapping missing", logger.Field("productId", txPayload.ProductId))
}
token := "iap:" + txPayload.OriginalTransactionId
@@ -97,9 +171,9 @@ func (l *AppleIAPNotifyLogic) Handle(signedPayload string) error {
t := *txPayload.RevocationDate
sub.FinishedAt = &t
sub.ExpireTime = t
} else if m.DurationDays > 0 {
} else if days > 0 {
// 正常:根据映射天数续期
exp := iapapple.CalcExpire(txPayload.PurchaseDate, m.DurationDays)
exp := iapapple.CalcExpire(txPayload.PurchaseDate, days)
sub.ExpireTime = exp
sub.Status = 1
}