更新
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -36,6 +37,10 @@ type planView struct {
|
||||
Level int `json:"level"`
|
||||
DurationDays int `json:"durationDays"`
|
||||
DailyActiveChatLimit int `json:"dailyActiveChatLimit"`
|
||||
DailyLikeLimit int `json:"dailyLikeLimit"`
|
||||
CanViewVisitors bool `json:"canViewVisitors"`
|
||||
CanInvisibleVisit bool `json:"canInvisibleVisit"`
|
||||
RecommendationWeight int `json:"recommendationWeight"`
|
||||
PriceCent int `json:"priceCent"`
|
||||
OriginalPriceCent int `json:"originalPriceCent"`
|
||||
Status int `json:"status"`
|
||||
@@ -43,7 +48,7 @@ type planView struct {
|
||||
}
|
||||
|
||||
func (a *App) membershipPlans(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT id,code,name,level,duration_days,daily_active_chat_limit,price_cent,original_price_cent,status,sort_order FROM membership_plans WHERE status=1 AND deleted_at IS NULL ORDER BY sort_order`)
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT id,code,name,level,duration_days,daily_active_chat_limit,daily_like_limit,can_view_visitors,can_invisible_visit,recommendation_weight,price_cent,original_price_cent,status,sort_order FROM membership_plans WHERE status=1 AND deleted_at IS NULL ORDER BY sort_order`)
|
||||
if err != nil {
|
||||
fail(w, 500, 50001, "查询套餐失败")
|
||||
return
|
||||
@@ -52,7 +57,7 @@ func (a *App) membershipPlans(w http.ResponseWriter, r *http.Request) {
|
||||
items := []planView{}
|
||||
for rows.Next() {
|
||||
var item planView
|
||||
_ = rows.Scan(&item.ID, &item.Code, &item.Name, &item.Level, &item.DurationDays, &item.DailyActiveChatLimit, &item.PriceCent, &item.OriginalPriceCent, &item.Status, &item.SortOrder)
|
||||
_ = rows.Scan(&item.ID, &item.Code, &item.Name, &item.Level, &item.DurationDays, &item.DailyActiveChatLimit, &item.DailyLikeLimit, &item.CanViewVisitors, &item.CanInvisibleVisit, &item.RecommendationWeight, &item.PriceCent, &item.OriginalPriceCent, &item.Status, &item.SortOrder)
|
||||
items = append(items, item)
|
||||
}
|
||||
reply(w, map[string]any{"items": items})
|
||||
@@ -60,15 +65,26 @@ func (a *App) membershipPlans(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (a *App) membershipStatus(w http.ResponseWriter, r *http.Request) {
|
||||
quota := a.dailyActiveChatQuota(r.Context(), current(r).ID)
|
||||
entitlements := a.resolveMembershipEntitlements(r.Context(), current(r).ID)
|
||||
var likeUsed int
|
||||
_ = a.db.QueryRowContext(r.Context(), `SELECT used_count FROM user_daily_like_usage WHERE user_id=? AND usage_date=CURRENT_DATE()`, current(r).ID).Scan(&likeUsed)
|
||||
likeRemaining := -1
|
||||
if entitlements.DailyLikeLimit > 0 {
|
||||
likeRemaining = entitlements.DailyLikeLimit - likeUsed
|
||||
if likeRemaining < 0 {
|
||||
likeRemaining = 0
|
||||
}
|
||||
}
|
||||
entitlementView := map[string]any{"dailyActiveChatLimit": entitlements.DailyActiveChatLimit, "dailyLikeLimit": entitlements.DailyLikeLimit, "dailyLikeUsed": likeUsed, "dailyLikeRemaining": likeRemaining, "canViewVisitors": entitlements.CanViewVisitors, "canInvisibleVisit": entitlements.CanInvisibleVisit, "recommendationWeight": entitlements.RecommendationWeight}
|
||||
var planName string
|
||||
var level int
|
||||
var expires time.Time
|
||||
err := a.db.QueryRowContext(r.Context(), `SELECT p.name,p.level,s.expires_at FROM subscriptions s JOIN membership_plans p ON p.id=s.plan_id WHERE s.user_id=? AND s.status=1 AND s.started_at<=NOW(3) AND s.expires_at>NOW(3) ORDER BY p.level DESC,s.expires_at DESC LIMIT 1`, current(r).ID).Scan(&planName, &level, &expires)
|
||||
if err != nil {
|
||||
reply(w, map[string]any{"active": false, "dailyActiveChat": quota, "level": 0, "name": "普通用户"})
|
||||
reply(w, map[string]any{"active": false, "dailyActiveChat": quota, "entitlements": entitlementView, "level": 0, "name": "普通用户"})
|
||||
return
|
||||
}
|
||||
reply(w, map[string]any{"active": true, "dailyActiveChat": quota, "level": level, "name": planName, "expiresAt": expires})
|
||||
reply(w, map[string]any{"active": true, "dailyActiveChat": quota, "entitlements": entitlementView, "level": level, "name": planName, "expiresAt": expires})
|
||||
}
|
||||
|
||||
func (a *App) createOrder(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -210,14 +226,14 @@ func (a *App) orderStatus(w http.ResponseWriter, r *http.Request) {
|
||||
fail(w, http.StatusBadRequest, 20001, "订单编号无效")
|
||||
return
|
||||
}
|
||||
var orderNo, status, channel string
|
||||
var orderNo, status, channel, productName, refundReason string
|
||||
var amountCent int
|
||||
var paidAt sql.NullTime
|
||||
if err = a.db.QueryRowContext(r.Context(), `SELECT order_no,amount_cent,status,channel,paid_at FROM orders WHERE id=? AND user_id=? AND deleted_at IS NULL`, id, current(r).ID).Scan(&orderNo, &amountCent, &status, &channel, &paidAt); err != nil {
|
||||
var paidAt, refundRequestedAt sql.NullTime
|
||||
if err = a.db.QueryRowContext(r.Context(), `SELECT o.order_no,o.amount_cent,o.status,o.channel,o.paid_at,COALESCE(p.name,'已删除套餐'),o.refund_reason,o.refund_requested_at FROM orders o LEFT JOIN membership_plans p ON p.id=o.product_id WHERE o.id=? AND o.user_id=? AND o.deleted_at IS NULL`, id, current(r).ID).Scan(&orderNo, &amountCent, &status, &channel, &paidAt, &productName, &refundReason, &refundRequestedAt); err != nil {
|
||||
fail(w, http.StatusNotFound, 30001, "订单不存在")
|
||||
return
|
||||
}
|
||||
result := map[string]any{"id": id, "orderNo": orderNo, "amountCent": amountCent, "status": status, "channel": channel}
|
||||
result := map[string]any{"id": id, "orderNo": orderNo, "amountCent": amountCent, "status": status, "channel": channel, "productName": productName, "refundReason": refundReason, "refundRequestedAt": nullableTime(refundRequestedAt)}
|
||||
if paidAt.Valid {
|
||||
result["paidAt"] = paidAt.Time
|
||||
}
|
||||
@@ -225,7 +241,13 @@ func (a *App) orderStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *App) myOrders(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT o.id,o.order_no,o.amount_cent,o.status,o.channel,o.created_at,COALESCE(p.name,'已删除套餐') FROM orders o LEFT JOIN membership_plans p ON p.id=o.product_id WHERE o.user_id=? AND o.deleted_at IS NULL ORDER BY o.created_at DESC`, current(r).ID)
|
||||
page, pageSize, offset := pageOptions(r)
|
||||
var total int
|
||||
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM orders WHERE user_id=? AND deleted_at IS NULL`, current(r).ID).Scan(&total); err != nil {
|
||||
fail(w, 500, 50001, "查询失败")
|
||||
return
|
||||
}
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT o.id,o.order_no,o.amount_cent,o.status,o.channel,o.created_at,COALESCE(p.name,'已删除套餐') FROM orders o LEFT JOIN membership_plans p ON p.id=o.product_id WHERE o.user_id=? AND o.deleted_at IS NULL ORDER BY o.created_at DESC LIMIT ? OFFSET ?`, current(r).ID, pageSize, offset)
|
||||
if err != nil {
|
||||
fail(w, 500, 50001, "查询失败")
|
||||
return
|
||||
@@ -240,11 +262,20 @@ func (a *App) myOrders(w http.ResponseWriter, r *http.Request) {
|
||||
_ = rows.Scan(&id, &orderNo, &amount, &status, &channel, &created, &name)
|
||||
items = append(items, map[string]any{"id": id, "orderNo": orderNo, "amountCent": amount, "status": status, "channel": channel, "createdAt": created, "productName": name})
|
||||
}
|
||||
reply(w, map[string]any{"items": items})
|
||||
reply(w, map[string]any{"items": items, "page": page, "pageSize": pageSize, "total": total, "hasMore": offset+len(items) < total})
|
||||
}
|
||||
|
||||
func (a *App) notifications(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT id,type,title,content,biz_type,biz_id,read_at,created_at FROM notifications WHERE user_id=? ORDER BY created_at DESC LIMIT 100`, current(r).ID)
|
||||
page, pageSize, offset := pageOptions(r)
|
||||
notificationType := strings.TrimSpace(r.URL.Query().Get("type"))
|
||||
where := ` WHERE user_id=?`
|
||||
args := []any{current(r).ID}
|
||||
if notificationType != "" {
|
||||
where += ` AND type=?`
|
||||
args = append(args, notificationType)
|
||||
}
|
||||
args = append(args, pageSize, offset)
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT id,type,title,content,biz_type,biz_id,read_at,created_at FROM notifications`+where+` ORDER BY created_at DESC LIMIT ? OFFSET ?`, args...)
|
||||
if err != nil {
|
||||
fail(w, 500, 50001, "查询失败")
|
||||
return
|
||||
@@ -260,7 +291,21 @@ func (a *App) notifications(w http.ResponseWriter, r *http.Request) {
|
||||
_ = rows.Scan(&id, &typ, &title, &content, &bizType, &bizID, &readAt, &created)
|
||||
items = append(items, map[string]any{"id": id, "type": typ, "title": title, "content": content, "bizType": bizType, "bizId": bizID, "readAt": readAt, "createdAt": created})
|
||||
}
|
||||
reply(w, map[string]any{"items": items})
|
||||
var unread, total int
|
||||
_ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM notifications WHERE user_id=? AND read_at IS NULL`, current(r).ID).Scan(&unread)
|
||||
_ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM notifications`+where, args[:len(args)-2]...).Scan(&total)
|
||||
rowsByType, _ := a.db.QueryContext(r.Context(), `SELECT type,COUNT(*) FROM notifications WHERE user_id=? AND read_at IS NULL GROUP BY type`, current(r).ID)
|
||||
unreadByType := map[string]int{}
|
||||
if rowsByType != nil {
|
||||
defer rowsByType.Close()
|
||||
for rowsByType.Next() {
|
||||
var typ string
|
||||
var count int
|
||||
_ = rowsByType.Scan(&typ, &count)
|
||||
unreadByType[typ] = count
|
||||
}
|
||||
}
|
||||
reply(w, map[string]any{"items": items, "page": page, "pageSize": pageSize, "total": total, "unread": unread, "unreadByType": unreadByType, "hasMore": offset+len(items) < total})
|
||||
}
|
||||
func (a *App) readAllNotifications(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = a.db.ExecContext(r.Context(), `UPDATE notifications SET read_at=NOW(3) WHERE user_id=? AND read_at IS NULL`, current(r).ID)
|
||||
@@ -278,5 +323,22 @@ func (a *App) appConfig(w http.ResponseWriter, r *http.Request) {
|
||||
configs[key] = value
|
||||
}
|
||||
}
|
||||
reply(w, map[string]any{"configs": configs, "features": map[string]bool{"nearby": true, "feed": true, "membership": true, "im": true}, "minVersion": "1.0.0"})
|
||||
platform := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("platform")))
|
||||
if platform != "android" && platform != "ios" && platform != "h5" {
|
||||
platform = "h5"
|
||||
}
|
||||
latest := map[string]any{}
|
||||
var version, downloadURL, notes string
|
||||
var build int
|
||||
var force bool
|
||||
if a.db.QueryRowContext(r.Context(), `SELECT version,build_number,force_update,download_url,release_notes FROM app_versions WHERE platform=? AND status=1 ORDER BY build_number DESC LIMIT 1`, platform).Scan(&version, &build, &force, &downloadURL, ¬es) == nil {
|
||||
latest = map[string]any{"version": version, "buildNumber": build, "forceUpdate": force, "downloadUrl": downloadURL, "releaseNotes": notes}
|
||||
}
|
||||
features := map[string]bool{
|
||||
"nearby": a.configBool(r.Context(), "app.features.nearby", true),
|
||||
"feed": a.configBool(r.Context(), "app.features.feed", true),
|
||||
"membership": a.configBool(r.Context(), "app.features.membership", true),
|
||||
"im": a.configBool(r.Context(), "app.features.im", true),
|
||||
}
|
||||
reply(w, map[string]any{"configs": configs, "platform": platform, "features": features, "maintenance": map[string]any{"enabled": a.configBool(r.Context(), "app.maintenance.enabled", false), "message": a.configPlain(r.Context(), "app.maintenance.message", "系统维护中,请稍后再试")}, "legal": map[string]string{"userAgreementVersion": a.configPlain(r.Context(), "legal.user_agreement_version", "1.0"), "privacyPolicyVersion": a.configPlain(r.Context(), "legal.privacy_policy_version", "1.0"), "operatorName": a.configPlain(r.Context(), "legal.operator_name", ""), "contact": a.configPlain(r.Context(), "legal.contact", ""), "effectiveDate": a.configPlain(r.Context(), "legal.effective_date", ""), "userAgreementUrl": a.configPlain(r.Context(), "legal.user_agreement_url", ""), "privacyPolicyUrl": a.configPlain(r.Context(), "legal.privacy_policy_url", "")}, "minVersion": a.configPlain(r.Context(), "app.min_version."+platform, "1.0.0"), "latest": latest})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user