feat(contact): 添加联系信息提交功能
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 5m55s
All checks were successful
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:
parent
2fdc9c8127
commit
74f4a12422
@ -87,6 +87,12 @@ type (
|
||||
Total int64 `json:"total"`
|
||||
List []SubscribeClient `json:"list"`
|
||||
}
|
||||
ContactRequest {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
OtherContact string `json:"other_contact,optional"`
|
||||
Notes string `json:"notes,optional"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
@ -99,6 +105,10 @@ service ppanel {
|
||||
@handler GetGlobalConfig
|
||||
get /site/config returns (GetGlobalConfigResponse)
|
||||
|
||||
@doc "Submit contact info"
|
||||
@handler SubmitContact
|
||||
post /contact (ContactRequest)
|
||||
|
||||
@doc "Get Tos Content"
|
||||
@handler GetTos
|
||||
get /site/tos returns (GetTosResponse)
|
||||
@ -131,4 +141,3 @@ service ppanel {
|
||||
@handler GetClient
|
||||
get /client returns (GetSubscribeClientResponse)
|
||||
}
|
||||
|
||||
|
||||
@ -29,12 +29,16 @@ func Telegram(svc *svc.ServiceContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if tgConfig.BotToken == "" {
|
||||
usedToken := tgConfig.BotToken
|
||||
if usedToken == "" {
|
||||
usedToken = svc.Config.Telegram.BotToken
|
||||
if usedToken == "" {
|
||||
logger.Debug("[Init Telegram Config] Telegram Token is empty")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(tg.BotToken)
|
||||
bot, err := tgbotapi.NewBotAPI(usedToken)
|
||||
if err != nil {
|
||||
logger.Error("[Init Telegram Config] New Bot API Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
@ -55,7 +59,7 @@ func Telegram(svc *svc.ServiceContext) {
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
wh, err := tgbotapi.NewWebhook(fmt.Sprintf("%s/v1/telegram/webhook?secret=%s", tgConfig.WebHookDomain, tool.Md5Encode(tgConfig.BotToken, false)))
|
||||
wh, err := tgbotapi.NewWebhook(fmt.Sprintf("%s/v1/telegram/webhook?secret=%s", tgConfig.WebHookDomain, tool.Md5Encode(usedToken, false)))
|
||||
if err != nil {
|
||||
logger.Errorf("[Init Telegram Config] New Webhook Error: %s", err.Error())
|
||||
return
|
||||
@ -74,6 +78,7 @@ func Telegram(svc *svc.ServiceContext) {
|
||||
}
|
||||
svc.Config.Telegram.BotID = user.ID
|
||||
svc.Config.Telegram.BotName = user.UserName
|
||||
svc.Config.Telegram.BotToken = usedToken
|
||||
svc.Config.Telegram.EnableNotify = tg.EnableNotify
|
||||
svc.Config.Telegram.WebHookDomain = tg.WebHookDomain
|
||||
svc.TelegramBot = bot
|
||||
|
||||
@ -212,6 +212,7 @@ type Telegram struct {
|
||||
BotID int64 `yaml:"BotID" default:""`
|
||||
BotName string `yaml:"BotName" default:""`
|
||||
BotToken string `yaml:"BotToken" default:""`
|
||||
GroupChatID string `yaml:"GroupChatID" default:""`
|
||||
EnableNotify bool `yaml:"EnableNotify" default:"false"`
|
||||
WebHookDomain string `yaml:"WebHookDomain" default:""`
|
||||
}
|
||||
|
||||
25
internal/handler/common/contactHandler.go
Normal file
25
internal/handler/common/contactHandler.go
Normal file
@ -0,0 +1,25 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
)
|
||||
|
||||
func SubmitContactHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
var req types.ContactRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
validateErr := svcCtx.Validate(&req)
|
||||
if validateErr != nil {
|
||||
result.ParamErrorResult(c, validateErr)
|
||||
return
|
||||
}
|
||||
l := common.NewContactLogic(c.Request.Context(), svcCtx)
|
||||
err := l.SubmitContact(&req)
|
||||
result.HttpResult(c, nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,6 +642,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
||||
// Get Client
|
||||
commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx))
|
||||
|
||||
// Submit contact info
|
||||
commonGroupRouter.POST("/contact", common.SubmitContactHandler(serverCtx))
|
||||
|
||||
// Get verification code
|
||||
commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx))
|
||||
|
||||
|
||||
70
internal/logic/common/contactLogic.go
Normal file
70
internal/logic/common/contactLogic.go
Normal 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, "_", "\\_")
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
8
internal/types/contact.go
Normal file
8
internal/types/contact.go
Normal file
@ -0,0 +1,8 @@
|
||||
package types
|
||||
|
||||
type ContactRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
OtherContact string `json:"other_contact,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user