This commit is contained in:
2025-12-27 18:12:05 +08:00
commit 40cee46125
573 changed files with 57724 additions and 0 deletions

View File

@@ -0,0 +1,255 @@
package admin_agent
import (
"context"
"sort"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentRankingLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentRankingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentRankingLogic {
return &AdminGetAgentRankingLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentRankingLogic) AdminGetAgentRanking(req *types.AdminGetAgentRankingReq) (resp *types.AdminGetAgentRankingResp, err error) {
// Set default limit if not provided
limit := req.Limit
if limit <= 0 {
limit = 10
}
// Validate type parameter
if req.Type != "commission" && req.Type != "orders" {
return nil, errors.New("invalid type parameter, must be 'commission' or 'orders'")
}
var items []types.AgentRankingItem
if req.Type == "commission" {
// Rank by sum of commission amounts
items, err = l.queryCommissionRanking(int(limit))
if err != nil {
return nil, err
}
} else {
// Rank by count of orders
items, err = l.queryOrdersRanking(int(limit))
if err != nil {
return nil, err
}
}
resp = &types.AdminGetAgentRankingResp{
Items: items,
}
return resp, nil
}
// queryCommissionRanking queries top agents by total commission amount
func (l *AdminGetAgentRankingLogic) queryCommissionRanking(limit int) ([]types.AgentRankingItem, error) {
// 1. Query all commissions
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, err
}
// 2. Group by agent_id and sum amounts
agentCommissionMap := make(map[string]float64)
for _, commission := range commissions {
agentCommissionMap[commission.AgentId] += commission.Amount
}
// 3. Build agent stats
type AgentStat struct {
AgentID string
Amount float64
}
stats := make([]AgentStat, 0, len(agentCommissionMap))
for agentID, amount := range agentCommissionMap {
stats = append(stats, AgentStat{
AgentID: agentID,
Amount: amount,
})
}
// 4. Sort by amount descending
sort.Slice(stats, func(i, j int) bool {
return stats[i].Amount > stats[j].Amount
})
// 5. Apply limit
if limit > len(stats) {
limit = len(stats)
}
stats = stats[:limit]
// 6. Query agents and build response
agentIDs := make([]string, len(stats))
for i, stat := range stats {
agentIDs[i] = stat.AgentID
}
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
l.svcCtx.AgentModel.SelectBuilder().Where("del_state = ?", globalkey.DelStateNo), "")
if err != nil {
return nil, err
}
// Build agent map
agentMap := make(map[string]*struct {
Mobile string
Region string
})
for _, agent := range agents {
region := "未知"
if agent.Region.Valid && agent.Region.String != "" {
region = agent.Region.String
}
agentMap[agent.Id] = &struct {
Mobile string
Region string
}{
Mobile: agent.Mobile,
Region: region,
}
}
// 7. Build items
items := make([]types.AgentRankingItem, len(stats))
for i, stat := range stats {
agentInfo := agentMap[stat.AgentID]
// Decrypt mobile
decryptedMobile, err := crypto.DecryptMobile(agentInfo.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
l.Errorf("Failed to decrypt mobile for agent %s: %v", stat.AgentID, err)
decryptedMobile = ""
}
items[i] = types.AgentRankingItem{
AgentId: stat.AgentID,
AgentMobile: decryptedMobile,
Region: agentInfo.Region,
Value: stat.Amount,
}
}
return items, nil
}
// queryOrdersRanking queries top agents by order count
func (l *AdminGetAgentRankingLogic) queryOrdersRanking(limit int) ([]types.AgentRankingItem, error) {
// 1. Query all orders
builder := l.svcCtx.AgentOrderModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
orders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, err
}
// 2. Group by agent_id and count
agentOrderCountMap := make(map[string]int64)
for _, order := range orders {
agentOrderCountMap[order.AgentId]++
}
// 3. Build agent stats
type AgentStat struct {
AgentID string
Count int64
}
stats := make([]AgentStat, 0, len(agentOrderCountMap))
for agentID, count := range agentOrderCountMap {
stats = append(stats, AgentStat{
AgentID: agentID,
Count: count,
})
}
// 4. Sort by count descending
sort.Slice(stats, func(i, j int) bool {
return stats[i].Count > stats[j].Count
})
// 5. Apply limit
if limit > len(stats) {
limit = len(stats)
}
stats = stats[:limit]
// 6. Query agents
agentIDs := make([]string, len(stats))
for i, stat := range stats {
agentIDs[i] = stat.AgentID
}
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
l.svcCtx.AgentModel.SelectBuilder().Where("del_state = ?", globalkey.DelStateNo), "")
if err != nil {
return nil, err
}
// Build agent map
agentMap := make(map[string]*struct {
Mobile string
Region string
})
for _, agent := range agents {
region := "未知"
if agent.Region.Valid && agent.Region.String != "" {
region = agent.Region.String
}
agentMap[agent.Id] = &struct {
Mobile string
Region string
}{
Mobile: agent.Mobile,
Region: region,
}
}
// 7. Build items
items := make([]types.AgentRankingItem, len(stats))
for i, stat := range stats {
agentInfo := agentMap[stat.AgentID]
// Decrypt mobile
decryptedMobile, err := crypto.DecryptMobile(agentInfo.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
l.Errorf("Failed to decrypt mobile for agent %s: %v", stat.AgentID, err)
decryptedMobile = ""
}
items[i] = types.AgentRankingItem{
AgentId: stat.AgentID,
AgentMobile: decryptedMobile,
Region: agentInfo.Region,
Value: float64(stat.Count),
}
}
return items, nil
}

View File

@@ -0,0 +1,99 @@
package admin_agent
import (
"context"
"fmt"
"sort"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentTrendsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentTrendsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentTrendsLogic {
return &AdminGetAgentTrendsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentTrendsLogic) AdminGetAgentTrends(req *types.AdminGetAgentTrendsReq) (resp *types.AdminGetAgentTrendsResp, err error) {
// 1. Parse date parameters with default to last 30 days
var startDate, endDate time.Time
if req.StartDate != "" {
startDate, err = time.Parse("2006-01-02", req.StartDate)
if err != nil {
return nil, fmt.Errorf("invalid start_date format: %v", err)
}
} else {
// Default to 30 days ago
startDate = time.Now().AddDate(0, 0, -30)
}
if req.EndDate != "" {
endDate, err = time.Parse("2006-01-02", req.EndDate)
if err != nil {
return nil, fmt.Errorf("invalid end_date format: %v", err)
}
// Set end date to end of day
endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 0, time.Local)
} else {
// Default to today
endDate = time.Now()
}
// Ensure start date is before end date
if startDate.After(endDate) {
return nil, fmt.Errorf("start_date must be before end_date")
}
// 2. Query all agents in date range
builder := l.svcCtx.AgentModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
builder = builder.Where("create_time >= ?", startDate)
builder = builder.Where("create_time <= ?", endDate)
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, fmt.Errorf("failed to query agents: %v", err)
}
// 3. Group by date in Go
dateMap := make(map[string]int64)
for _, agent := range agents {
date := agent.CreateTime.Format("2006-01-02")
dateMap[date]++
}
// 4. Build sorted response
dates := make([]string, 0, len(dateMap))
counts := make([]int64, 0, len(dateMap))
for date := range dateMap {
dates = append(dates, date)
}
// Sort dates
sort.Strings(dates)
for _, date := range dates {
counts = append(counts, dateMap[date])
}
return &types.AdminGetAgentTrendsResp{
Dates: dates,
Counts: counts,
}, nil
}

View File

@@ -0,0 +1,120 @@
package admin_agent
import (
"context"
"fmt"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetOrderTrendsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetOrderTrendsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderTrendsLogic {
return &AdminGetOrderTrendsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetOrderTrendsLogic) AdminGetOrderTrends(req *types.AdminGetOrderTrendsReq) (resp *types.AdminGetOrderTrendsResp, err error) {
// 1. Parse date parameters with default to last 30 days
var startDate, endDate time.Time
if req.StartDate != "" {
startDate, err = time.Parse("2006-01-02", req.StartDate)
if err != nil {
return nil, fmt.Errorf("invalid start_date format: %v", err)
}
} else {
// Default to 30 days ago
startDate = time.Now().AddDate(0, 0, -30)
}
if req.EndDate != "" {
endDate, err = time.Parse("2006-01-02", req.EndDate)
if err != nil {
return nil, fmt.Errorf("invalid end_date format: %v", err)
}
// Set end date to end of day
endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 0, time.Local)
} else {
// Default to today
endDate = time.Now()
}
// Ensure start date is before end date
if startDate.After(endDate) {
return nil, fmt.Errorf("start_date must be before end_date")
}
// 2. Query all commissions in date range
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
builder = builder.Where("create_time >= ?", startDate)
builder = builder.Where("create_time <= ?", endDate)
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, fmt.Errorf("failed to query commissions: %v", err)
}
// 3. Group by date in Go
dateMap := make(map[string]*struct {
Amount float64
Count int64
})
for _, commission := range commissions {
date := commission.CreateTime.Format("2006-01-02")
if _, exists := dateMap[date]; !exists {
dateMap[date] = &struct {
Amount float64
Count int64
}{}
}
dateMap[date].Amount += commission.Amount
dateMap[date].Count++
}
// 4. Build sorted response
dates := make([]string, 0, len(dateMap))
amounts := make([]float64, 0, len(dateMap))
counts := make([]int64, 0, len(dateMap))
// Sort dates
sortedDates := make([]string, 0, len(dateMap))
for date := range dateMap {
sortedDates = append(sortedDates, date)
}
// Simple bubble sort (can be optimized with sort package)
for i := 0; i < len(sortedDates); i++ {
for j := i + 1; j < len(sortedDates); j++ {
if sortedDates[i] > sortedDates[j] {
sortedDates[i], sortedDates[j] = sortedDates[j], sortedDates[i]
}
}
}
for _, date := range sortedDates {
dates = append(dates, date)
amounts = append(amounts, dateMap[date].Amount)
counts = append(counts, dateMap[date].Count)
}
return &types.AdminGetOrderTrendsResp{
Dates: dates,
Amounts: amounts,
Counts: counts,
}, nil
}

View File

@@ -0,0 +1,123 @@
package admin_agent
import (
"context"
"sort"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetProductDistributionLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetProductDistributionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductDistributionLogic {
return &AdminGetProductDistributionLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetProductDistributionLogic) AdminGetProductDistribution() (resp *types.AdminGetProductDistributionResp, err error) {
// 1. Query all commissions
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, err
}
// 2. Collect all product IDs
productIds := make([]string, 0, len(commissions))
productIDSet := make(map[string]bool)
for _, commission := range commissions {
if !productIDSet[commission.ProductId] {
productIDSet[commission.ProductId] = true
productIds = append(productIds, commission.ProductId)
}
}
// 3. Query products
var products []*model.Product
if len(productIds) > 0 {
products, err = l.svcCtx.ProductModel.FindAll(l.ctx,
l.svcCtx.ProductModel.SelectBuilder().Where("del_state = ?", globalkey.DelStateNo), "")
if err != nil {
return nil, err
}
}
// 4. Build product name map
productNameMap := make(map[string]string)
for _, product := range products {
productNameMap[product.Id] = product.ProductName
}
// 5. Group by product in Go
productStats := make(map[string]*struct {
Count int64
Amount float64
})
for _, commission := range commissions {
productName := productNameMap[commission.ProductId]
if productName == "" {
productName = "未知产品"
}
if _, exists := productStats[productName]; !exists {
productStats[productName] = &struct {
Count int64
Amount float64
}{}
}
productStats[productName].Count++
productStats[productName].Amount += commission.Amount
}
// 6. Sort by amount descending
type ProductStat struct {
Name string
Count int64
Amount float64
}
stats := make([]ProductStat, 0, len(productStats))
for name, stat := range productStats {
stats = append(stats, ProductStat{
Name: name,
Count: stat.Count,
Amount: stat.Amount,
})
}
sort.Slice(stats, func(i, j int) bool {
return stats[i].Amount > stats[j].Amount
})
// 7. Build response
productNames := make([]string, len(stats))
counts := make([]int64, len(stats))
amounts := make([]float64, len(stats))
for i, stat := range stats {
productNames[i] = stat.Name
counts[i] = stat.Count
amounts[i] = stat.Amount
}
return &types.AdminGetProductDistributionResp{
Products: productNames,
Counts: counts,
Amounts: amounts,
}, nil
}

View File

@@ -0,0 +1,80 @@
package admin_agent
import (
"context"
"sort"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetRegionDistributionLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetRegionDistributionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetRegionDistributionLogic {
return &AdminGetRegionDistributionLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetRegionDistributionLogic) AdminGetRegionDistribution() (resp *types.AdminGetRegionDistributionResp, err error) {
// 1. Query all agents
builder := l.svcCtx.AgentModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, err
}
// 2. Group by region in Go
regionMap := make(map[string]int64)
for _, agent := range agents {
region := "未知"
if agent.Region.Valid && agent.Region.String != "" {
region = agent.Region.String
}
regionMap[region]++
}
// 3. Sort by count descending
type RegionStat struct {
Region string
Count int64
}
stats := make([]RegionStat, 0, len(regionMap))
for region, count := range regionMap {
stats = append(stats, RegionStat{
Region: region,
Count: count,
})
}
sort.Slice(stats, func(i, j int) bool {
return stats[i].Count > stats[j].Count
})
// 4. Build response
regions := make([]string, len(stats))
counts := make([]int64, len(stats))
for i, stat := range stats {
regions[i] = stat.Region
counts[i] = stat.Count
}
return &types.AdminGetRegionDistributionResp{
Regions: regions,
Counts: counts,
}, nil
}

View File

@@ -0,0 +1,132 @@
package admin_agent
import (
"context"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/Masterminds/squirrel"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetStatisticsOverviewLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetStatisticsOverviewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetStatisticsOverviewLogic {
return &AdminGetStatisticsOverviewLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetStatisticsOverviewLogic) AdminGetStatisticsOverview() (resp *types.AdminGetStatisticsOverviewResp, err error) {
// Get current time for date filtering
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 1. Total agents - count from agent table
totalAgents, err := l.svcCtx.AgentModel.FindCount(l.ctx, l.svcCtx.AgentModel.SelectBuilder(), "id")
if err != nil {
l.Errorf("Failed to count total agents: %v", err)
return nil, err
}
// 2. Today new agents - count agents created today
todayAgentsBuilder := l.svcCtx.AgentModel.SelectBuilder().Where(squirrel.GtOrEq{"create_time": todayStart})
todayNewAgents, err := l.svcCtx.AgentModel.FindCount(l.ctx, todayAgentsBuilder, "id")
if err != nil {
l.Errorf("Failed to count today new agents: %v", err)
return nil, err
}
// 3. Total orders - count from agent_order table
totalOrders, err := l.svcCtx.AgentOrderModel.FindCount(l.ctx, l.svcCtx.AgentOrderModel.SelectBuilder(), "id")
if err != nil {
l.Errorf("Failed to count total orders: %v", err)
return nil, err
}
// 4. Today orders - count orders created today
todayOrdersBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().Where(squirrel.GtOrEq{"create_time": todayStart})
todayOrders, err := l.svcCtx.AgentOrderModel.FindCount(l.ctx, todayOrdersBuilder, "id")
if err != nil {
l.Errorf("Failed to count today orders: %v", err)
return nil, err
}
// 5. Total order amount - sum from agent_order table
totalOrderAmount, err := l.svcCtx.AgentOrderModel.FindSum(l.ctx, l.svcCtx.AgentOrderModel.SelectBuilder(), "order_amount")
if err != nil {
l.Errorf("Failed to sum total order amount: %v", err)
return nil, err
}
// 6. Today order amount - sum orders created today
todayOrderAmount, err := l.svcCtx.AgentOrderModel.FindSum(l.ctx, todayOrdersBuilder, "order_amount")
if err != nil {
l.Errorf("Failed to sum today order amount: %v", err)
return nil, err
}
// 7. Total commission - sum of all commission amounts
totalCommission, err := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, l.svcCtx.AgentCommissionModel.SelectBuilder(), "amount")
if err != nil {
l.Errorf("Failed to sum total commission: %v", err)
return nil, err
}
// 8. Today commission - sum of today's commission
todayCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.GtOrEq{"create_time": todayStart})
todayCommission, err := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, todayCommissionBuilder, "amount")
if err != nil {
l.Errorf("Failed to sum today commission: %v", err)
return nil, err
}
// 9. Pending withdraw - sum of withdraw records with status=0
pendingWithdrawBuilder := l.svcCtx.AgentWithdrawModel.SelectBuilder().Where(squirrel.Eq{"status": 0})
pendingWithdraw, err := l.svcCtx.AgentWithdrawModel.FindSum(l.ctx, pendingWithdrawBuilder, "withdraw_amount")
if err != nil {
l.Errorf("Failed to sum pending withdraw: %v", err)
return nil, err
}
// 10. Month order amount - sum of orders in current month
monthOrderAmountBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().Where(squirrel.GtOrEq{"create_time": monthStart})
monthOrderAmount, err := l.svcCtx.AgentOrderModel.FindSum(l.ctx, monthOrderAmountBuilder, "order_amount")
if err != nil {
l.Errorf("Failed to sum month order amount: %v", err)
return nil, err
}
// 11. Month commission - sum of commission in current month
monthCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.GtOrEq{"create_time": monthStart})
monthCommission, err := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, monthCommissionBuilder, "amount")
if err != nil {
l.Errorf("Failed to sum month commission: %v", err)
return nil, err
}
resp = &types.AdminGetStatisticsOverviewResp{
TotalAgents: totalAgents,
TodayNewAgents: todayNewAgents,
TotalOrders: totalOrders,
TodayOrders: todayOrders,
TotalOrderAmount: totalOrderAmount,
TodayOrderAmount: todayOrderAmount,
TotalCommission: totalCommission,
TodayCommission: todayCommission,
PendingWithdraw: pendingWithdraw,
MonthOrderAmount: monthOrderAmount,
MonthCommission: monthCommission,
}
return resp, nil
}

View File

@@ -0,0 +1,94 @@
package admin_agent
import (
"context"
"fmt"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
dysmsapi "github.com/alibabacloud-go/dysmsapi-20170525/v3/client"
"github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminSendAgentComplaintNotifyLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminSendAgentComplaintNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminSendAgentComplaintNotifyLogic {
return &AdminSendAgentComplaintNotifyLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminSendAgentComplaintNotifyLogic) AdminSendAgentComplaintNotify(req *types.AdminSendAgentComplaintNotifyReq) (resp *types.AdminSendAgentComplaintNotifyResp, err error) {
// 1. 查询代理信息
agent, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.AgentId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理信息失败: %v", err)
}
// 2. 解密手机号
mobile, err := crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密手机号失败: %v", err)
}
// 3. 发送短信
smsResp, err := l.sendSmsRequest(mobile, req.UserName)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "发送短信失败: %v", err)
}
// 4. 检查发送结果
if *smsResp.Body.Code != "OK" {
return &types.AdminSendAgentComplaintNotifyResp{
Success: false,
Message: fmt.Sprintf("短信发送失败: %s", *smsResp.Body.Message),
}, nil
}
resp = &types.AdminSendAgentComplaintNotifyResp{
Success: true,
Message: "投诉通知发送成功",
}
return
}
// CreateClient 创建阿里云短信客户端
func (l *AdminSendAgentComplaintNotifyLogic) CreateClient() (*dysmsapi.Client, error) {
config := &openapi.Config{
AccessKeyId: &l.svcCtx.Config.VerifyCode.AccessKeyID,
AccessKeySecret: &l.svcCtx.Config.VerifyCode.AccessKeySecret,
}
config.Endpoint = tea.String(l.svcCtx.Config.VerifyCode.EndpointURL)
return dysmsapi.NewClient(config)
}
// sendSmsRequest 发送投诉通知短信请求
func (l *AdminSendAgentComplaintNotifyLogic) sendSmsRequest(mobile, userName string) (*dysmsapi.SendSmsResponse, error) {
// 初始化阿里云短信客户端
cli, err := l.CreateClient()
if err != nil {
return nil, err
}
request := &dysmsapi.SendSmsRequest{
SignName: tea.String(l.svcCtx.Config.VerifyCode.SignName),
TemplateCode: tea.String(l.svcCtx.Config.VerifyCode.ComplaintTemplate),
PhoneNumbers: tea.String(mobile),
TemplateParam: tea.String(fmt.Sprintf("{\"name\":\"%s\"}", userName)),
}
runtime := &service.RuntimeOptions{}
return cli.SendSmsWithOptions(request, runtime)
}

View File

@@ -0,0 +1,30 @@
package admin_agent
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminAuditAgentLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminAuditAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminAuditAgentLogic {
return &AdminAuditAgentLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminAuditAgentLogic) AdminAuditAgent(req *types.AdminAuditAgentReq) (resp *types.AdminAuditAgentResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,185 @@
package admin_agent
import (
"context"
"strconv"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminAuditAgentWithdrawLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminAuditAgentWithdrawLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminAuditAgentWithdrawLogic {
return &AdminAuditAgentWithdrawLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminAuditAgentWithdrawLogic) AdminAuditAgentWithdraw(req *types.AdminAuditAgentWithdrawReq) (resp *types.AdminAuditAgentWithdrawResp, err error) {
// 1. 获取当前审核人ID管理员ID
adminUserId, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取审核人信息失败: %v", err)
}
// 2. 验证审核状态
if req.Status != 1 && req.Status != 2 {
return nil, errors.Wrap(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "审核状态无效,只能通过(1)或拒绝(2)")
}
// 3. 如果是拒绝,必须填写审核原因
if req.Status == 2 && req.AuditRemark == "" {
return nil, errors.Wrap(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "拒绝提现时必须填写审核原因")
}
// 4. 查询提现记录
withdrawRecord, err := l.svcCtx.AgentWithdrawModel.FindOne(l.ctx, req.WithdrawId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录失败: %v", err)
}
// 5. 验证提现记录状态
if withdrawRecord.Status != 0 {
return nil, errors.Wrap(xerr.NewErrCode(xerr.CUSTOM_ERROR), "该提现记录已被审核,请勿重复操作")
}
// 6. 如果是审核通过,从配置表读取税率并计算税费
var taxAmount float64
var actualAmount float64
if req.Status == 1 {
// 从配置表读取税率
taxRateConfig, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, "tax_rate")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取税率配置失败: %v", err)
}
taxRate, _ := strconv.ParseFloat(taxRateConfig.ConfigValue, 64)
// 计算税费和实际到账金额
taxAmount = withdrawRecord.WithdrawAmount * taxRate
actualAmount = withdrawRecord.WithdrawAmount - taxAmount
logx.Infof("提现审核通过计算税费:提现金额=%.2f,税率=%.4f,税费=%.2f,实际到账=%.2f",
withdrawRecord.WithdrawAmount, taxRate, taxAmount, actualAmount)
}
// 7. 查询代理钱包
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, withdrawRecord.AgentId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理钱包失败: %v", err)
}
// 8. 开启事务处理审核逻辑
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 7.0 在事务中再次查询提现记录和钱包,确保数据是最新的且未被修改
withdrawRecord, err = l.svcCtx.AgentWithdrawModel.FindOne(ctx, req.WithdrawId)
if err != nil {
return errors.Wrapf(err, "查询提现记录失败")
}
// 验证状态未被修改
if withdrawRecord.Status != 0 {
return errors.New("该提现记录已被审核,请勿重复操作")
}
wallet, err = l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, withdrawRecord.AgentId)
if err != nil {
return errors.Wrapf(err, "查询代理钱包失败")
}
// 验证冻结金额是否匹配提现记录
if wallet.FrozenAmount < withdrawRecord.FrozenAmount {
return errors.Errorf("钱包冻结金额不足,当前冻结: %.2f,需要: %.2f", wallet.FrozenAmount, withdrawRecord.FrozenAmount)
}
if req.Status == 1 {
// ============ 审核通过 ============
// 8.1 更新钱包:扣除冻结金额,增加累计提现金额
wallet.FrozenAmount -= withdrawRecord.FrozenAmount
wallet.TotalWithdraw += actualAmount // 累计实际到账金额
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
if err != nil {
return errors.Wrapf(err, "更新钱包信息失败")
}
// 8.2 更新提现记录状态
updateSQL := `
UPDATE agent_withdraw
SET status = 1,
tax_amount = ?,
actual_amount = ?,
audit_user_id = ?,
audit_time = NOW(),
audit_remark = ?,
version = version + 1
WHERE id = ? AND version = ?
`
_, err = session.Exec(updateSQL, taxAmount, actualAmount, adminUserId, req.AuditRemark, req.WithdrawId, withdrawRecord.Version)
if err != nil {
return errors.Wrapf(err, "更新提现记录失败")
}
logx.Infof("管理员 %s 审核通过提现记录 %s代理ID: %s提现金额: %.2f,实际到账: %.2f",
adminUserId, req.WithdrawId, withdrawRecord.AgentId, withdrawRecord.WithdrawAmount, actualAmount)
} else {
// ============ 审核拒绝 ============
// 7.1 更新钱包:解冻金额,返回余额
wallet.FrozenAmount -= withdrawRecord.FrozenAmount
wallet.Balance += withdrawRecord.FrozenAmount
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
if err != nil {
return errors.Wrapf(err, "更新钱包信息失败")
}
// 7.2 更新提现记录状态
updateSQL := `
UPDATE agent_withdraw
SET status = 2,
audit_user_id = ?,
audit_time = NOW(),
audit_remark = ?,
version = version + 1
WHERE id = ? AND version = ?
`
_, err = session.Exec(updateSQL, adminUserId, req.AuditRemark, req.WithdrawId, withdrawRecord.Version)
if err != nil {
return errors.Wrapf(err, "更新提现记录失败")
}
logx.Infof("管理员 %s 拒绝提现记录 %s代理ID: %s提现金额: %.2f,原因: %s",
adminUserId, req.WithdrawId, withdrawRecord.AgentId, withdrawRecord.WithdrawAmount, req.AuditRemark)
}
return nil
})
if err != nil {
return nil, errors.Wrap(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), err.Error())
}
// 8. 返回成功响应
message := "审核通过"
if req.Status == 2 {
message = "审核拒绝"
}
return &types.AdminAuditAgentWithdrawResp{
Success: true,
Message: message,
}, nil
}

View File

@@ -0,0 +1,74 @@
package admin_agent
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentCommissionListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentCommissionListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentCommissionListLogic {
return &AdminGetAgentCommissionListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *types.AdminGetAgentCommissionListReq) (resp *types.AdminGetAgentCommissionListResp, err error) {
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
if req.AgentId != nil {
builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId})
}
if req.Status != nil {
builder = builder.Where(squirrel.Eq{"status": *req.Status})
}
// 产品名称筛选功能已移除如需可按product_id筛选
list, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, err
}
// 批量查product_name
productIds := make(map[string]struct{})
for _, v := range list {
productIds[v.ProductId] = struct{}{}
}
productNameMap := make(map[string]string)
if len(productIds) > 0 {
ids := make([]string, 0, len(productIds))
for id := range productIds {
ids = append(ids, id)
}
builder := l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": ids})
products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, builder, "")
for _, p := range products {
productNameMap[p.Id] = p.ProductName
}
}
items := make([]types.AgentCommissionListItem, 0, len(list))
for _, v := range list {
item := types.AgentCommissionListItem{}
_ = copier.Copy(&item, v)
item.ProductName = productNameMap[v.ProductId]
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
items = append(items, item)
}
resp = &types.AdminGetAgentCommissionListResp{
Total: total,
Items: items,
}
return
}

View File

@@ -0,0 +1,62 @@
package admin_agent
import (
"context"
"strconv"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentConfigLogic {
return &AdminGetAgentConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAgentConfigResp, err error) {
// 获取配置值的辅助函数
getConfigFloat := func(key string) float64 {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, key)
if err != nil {
return 0
}
value, _ := strconv.ParseFloat(config.ConfigValue, 64)
return value
}
getConfigInt := func(key string) int64 {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, key)
if err != nil {
return 0
}
value, _ := strconv.ParseInt(config.ConfigValue, 10, 64)
return value
}
// 系统简化:移除等级加成、升级费用、升级返佣、直接上级返佣配置、免税额度
// 只保留佣金冻结配置和税率配置
// 获取佣金冻结配置
commissionFreezeRatio := getConfigFloat("commission_freeze_ratio")
commissionFreezeThreshold := getConfigFloat("commission_freeze_threshold")
commissionFreezeDays := getConfigInt("commission_freeze_days")
return &types.AdminGetAgentConfigResp{
CommissionFreeze: types.CommissionFreezeConfig{
Ratio: commissionFreezeRatio,
Threshold: commissionFreezeThreshold,
Days: commissionFreezeDays,
},
TaxRate: getConfigFloat("tax_rate"),
}, nil
}

View File

@@ -0,0 +1,100 @@
package admin_agent
import (
"context"
"fmt"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/Masterminds/squirrel"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentLinkListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentLinkListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentLinkListLogic {
return &AdminGetAgentLinkListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentLinkListLogic) AdminGetAgentLinkList(req *types.AdminGetAgentLinkListReq) (resp *types.AdminGetAgentLinkListResp, err error) {
builder := l.svcCtx.AgentLinkModel.SelectBuilder()
if req.AgentId != nil {
builder = builder.Where("agent_id = ?", *req.AgentId)
}
if req.LinkIdentifier != nil && *req.LinkIdentifier != "" {
builder = builder.Where("link_identifier = ?", *req.LinkIdentifier)
}
// 如果传入ProductId添加筛选条件
if req.ProductId != nil {
builder = builder.Where("product_id = ?", *req.ProductId)
}
links, total, err := l.svcCtx.AgentLinkModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return nil, err
}
// 批量查product_id->name避免N+1
productIdSet := make(map[string]struct{})
linkIdList := make([]string, 0, len(links))
for _, link := range links {
productIdSet[link.ProductId] = struct{}{}
linkIdList = append(linkIdList, link.Id)
}
productIdList := make([]string, 0, len(productIdSet))
for id := range productIdSet {
productIdList = append(productIdList, id)
}
productNameMap := make(map[string]string)
if len(productIdList) > 0 {
products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": productIdList}), "")
for _, p := range products {
productNameMap[p.Id] = p.ProductName
}
}
// 批量查询短链信息
shortLinkMap := make(map[string]string) // link_id -> short_link
if len(linkIdList) > 0 {
shortLinks, _ := l.svcCtx.AgentShortLinkModel.FindAll(l.ctx, l.svcCtx.AgentShortLinkModel.SelectBuilder().
Where(squirrel.Eq{"link_id": linkIdList}).
Where(squirrel.Eq{"type": 1}). // 推广报告类型
Where(squirrel.Eq{"del_state": 0}), "")
for _, sl := range shortLinks {
if sl.LinkId.Valid && sl.ShortCode != "" && sl.PromotionDomain != "" {
// 构建短链链接: {promotion_domain}/s/{shortCode}
// promotion_domain 已经包含协议http:// 或 https://
shortLinkMap[sl.LinkId.String] = fmt.Sprintf("%s/s/%s", sl.PromotionDomain, sl.ShortCode)
}
}
}
items := make([]types.AgentLinkListItem, 0, len(links))
for _, link := range links {
items = append(items, types.AgentLinkListItem{
Id: link.Id,
AgentId: link.AgentId,
ProductId: link.ProductId,
ProductName: productNameMap[link.ProductId],
SetPrice: link.SetPrice,
ShortLink: shortLinkMap[link.Id], // 短链链接
CreateTime: link.CreateTime.Format("2006-01-02 15:04:05"),
})
}
resp = &types.AdminGetAgentLinkListResp{
Total: total,
Items: items,
}
return
}

View File

@@ -0,0 +1,105 @@
package admin_agent
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentListLogic {
return &AdminGetAgentListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListReq) (resp *types.AdminGetAgentListResp, err error) {
builder := l.svcCtx.AgentModel.SelectBuilder()
// 系统简化:移除 TeamLeaderId, Level 筛选条件
// 只保留 Mobile 和 Region 筛选
if req.Mobile != nil && *req.Mobile != "" {
// 加密手机号进行查询
encryptedMobile, err := crypto.EncryptMobile(*req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
builder = builder.Where(squirrel.Eq{"mobile": encryptedMobile})
}
if req.Region != nil && *req.Region != "" {
// 注意region字段现在是可选的查询时需要处理NULL值
// 如果region字段是NULL使用IS NULL查询否则使用等值查询
// 这里简化处理直接使用等值查询如果数据库中有NULL值需要特殊处理
builder = builder.Where(squirrel.Eq{"region": *req.Region})
}
agents, total, err := l.svcCtx.AgentModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return nil, err
}
items := make([]types.AgentListItem, 0, len(agents))
for _, agent := range agents {
agent.Mobile, err = crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err)
}
// 查询钱包信息
wallet, _ := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
wechatId := ""
if agent.WechatId.Valid {
wechatId = agent.WechatId.String
}
// 获取区域
region := ""
if agent.Region.Valid {
region = agent.Region.String
}
// 系统简化:移除 Level, LevelName, TeamLeaderId, WithdrawnAmount, IsRealName 字段
item := types.AgentListItem{
Id: agent.Id,
UserId: agent.UserId,
Region: region,
Mobile: agent.Mobile,
WechatId: wechatId,
AgentCode: agent.AgentCode,
Balance: 0,
FrozenAmount: 0,
TotalEarnings: 0,
CreateTime: agent.CreateTime.Format("2006-01-02 15:04:05"),
}
if wallet != nil {
item.Balance = wallet.Balance
item.FrozenAmount = wallet.FrozenAmount
item.TotalEarnings = wallet.TotalEarnings
}
items = append(items, item)
}
resp = &types.AdminGetAgentListResp{
Total: total,
Items: items,
}
return
}

View File

@@ -0,0 +1,100 @@
package admin_agent
import (
"context"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentOrderListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentOrderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentOrderListLogic {
return &AdminGetAgentOrderListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGetAgentOrderListReq) (resp *types.AdminGetAgentOrderListResp, err error) {
builder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
if req.AgentId != nil {
builder = builder.Where("agent_id = ?", *req.AgentId)
}
if req.OrderId != nil {
builder = builder.Where("order_id = ?", *req.OrderId)
}
if req.ProcessStatus != nil {
builder = builder.Where("process_status = ?", *req.ProcessStatus)
}
// 分页查询
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
orders, total, err := l.svcCtx.AgentOrderModel.FindPageListByPageWithTotal(l.ctx, builder, page, pageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理订单列表失败, %v", err)
}
// 批量查询产品名称
productIdSet := make(map[string]struct{})
for _, order := range orders {
productIdSet[order.ProductId] = struct{}{}
}
productIdList := make([]string, 0, len(productIdSet))
for id := range productIdSet {
productIdList = append(productIdList, id)
}
productNameMap := make(map[string]string)
if len(productIdList) > 0 {
products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": productIdList}), "")
for _, p := range products {
productNameMap[p.Id] = p.ProductName
}
}
// 组装响应
items := make([]types.AgentOrderListItem, 0, len(orders))
for _, order := range orders {
items = append(items, types.AgentOrderListItem{
Id: order.Id,
AgentId: order.AgentId,
OrderId: order.OrderId,
ProductId: order.ProductId,
ProductName: productNameMap[order.ProductId],
OrderAmount: order.OrderAmount,
SetPrice: order.SetPrice,
ActualBasePrice: order.ActualBasePrice,
PriceCost: order.PriceCost,
AgentProfit: order.AgentProfit,
ProcessStatus: order.ProcessStatus,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return &types.AdminGetAgentOrderListResp{
Total: total,
Items: items,
}, nil
}

View File

@@ -0,0 +1,526 @@
package admin_agent
import (
"context"
"fmt"
"strings"
"time"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentOrdersListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentOrdersListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentOrdersListLogic {
return &AdminGetAgentOrdersListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentOrdersListLogic) AdminGetAgentOrdersList(req *types.AdminGetAgentOrdersListReq) (resp *types.AdminGetAgentOrdersListResp, err error) {
// 1. 查询订单列表(基础查询)
orders, total, err := l.queryOrders(req)
if err != nil {
return nil, err
}
if len(orders) == 0 {
return &types.AdminGetAgentOrdersListResp{
Total: 0,
Items: []types.AgentOrdersListItem{},
}, nil
}
// 2. 收集所有需要的ID
orderIds := make([]string, len(orders))
userIds := make([]string, 0, len(orders))
productIds := make([]string, 0, len(orders))
for i, order := range orders {
orderIds[i] = order.Id
userIds = append(userIds, order.UserId)
productIds = append(productIds, order.ProductId)
}
// 3. 批量查询佣金信息
commissionsMap := l.queryCommissionsByOrderIds(orderIds)
// 4. 批量查询代理信息
agentIds := make([]string, 0)
for _, commission := range commissionsMap {
agentIds = append(agentIds, commission.AgentId)
}
agentsMap := l.queryAgentsByAgentIds(agentIds)
// 5. 批量查询用户信息
usersMap := l.queryUsersByUserIds(userIds)
// 6. 批量查询产品信息
productsMap := l.queryProductsByProductIds(productIds)
// 7. 组装数据
list := l.assembleOrderList(orders, commissionsMap, agentsMap, usersMap, productsMap)
// 8. 应用后端筛选条件(代理、用户、产品、佣金状态)
filteredList := l.applyFilters(list, req, agentsMap, usersMap, productsMap, commissionsMap)
// 9. 应用分页(如果使用了后端筛选)
if l.needsBackendFiltering(req) {
total = int64(len(filteredList))
// 手动分页
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
start := (page - 1) * pageSize
end := start + pageSize
if start > int64(len(filteredList)) {
return &types.AdminGetAgentOrdersListResp{
Total: total,
Items: []types.AgentOrdersListItem{},
}, nil
}
if end > int64(len(filteredList)) {
end = int64(len(filteredList))
}
return &types.AdminGetAgentOrdersListResp{
Total: total,
Items: filteredList[start:end],
}, nil
}
return &types.AdminGetAgentOrdersListResp{
Total: total,
Items: filteredList,
}, nil
}
// needsBackendFiltering 检查是否需要后端筛选
func (l *AdminGetAgentOrdersListLogic) needsBackendFiltering(req *types.AdminGetAgentOrdersListReq) bool {
return (req.AgentId != nil && *req.AgentId != "") ||
(req.AgentMobile != nil && *req.AgentMobile != "") ||
(req.UserMobile != nil && *req.UserMobile != "") ||
(req.ProductName != nil && *req.ProductName != "") ||
(req.CommissionStatus != nil)
}
// applyFilters 应用后端筛选条件
func (l *AdminGetAgentOrdersListLogic) applyFilters(
list []types.AgentOrdersListItem,
req *types.AdminGetAgentOrdersListReq,
agentsMap map[string]*model.Agent,
usersMap map[string]*model.User,
productsMap map[string]*model.Product,
commissionsMap map[string]*model.AgentCommission,
) []types.AgentOrdersListItem {
var result []types.AgentOrdersListItem
for _, item := range list {
// 代理ID筛选
if req.AgentId != nil && *req.AgentId != "" {
if item.AgentId != *req.AgentId {
continue
}
}
// 代理手机号筛选
if req.AgentMobile != nil && *req.AgentMobile != "" {
if !contains(item.AgentMobile, *req.AgentMobile) {
continue
}
}
// 用户手机号筛选
if req.UserMobile != nil && *req.UserMobile != "" {
if item.UserMobile == nil || !contains(*item.UserMobile, *req.UserMobile) {
continue
}
}
// 产品名称筛选
if req.ProductName != nil && *req.ProductName != "" {
if !contains(item.ProductName, *req.ProductName) {
continue
}
}
// 佣金状态筛选
if req.CommissionStatus != nil {
if item.CommissionStatus != *req.CommissionStatus {
continue
}
}
result = append(result, item)
}
return result
}
// contains 检查字符串是否包含子字符串(不区分大小写)
func contains(str, substr string) bool {
return strings.Contains(strings.ToLower(str), strings.ToLower(substr))
}
// queryOrders 查询订单列表
func (l *AdminGetAgentOrdersListLogic) queryOrders(req *types.AdminGetAgentOrdersListReq) ([]*model.Order, int64, error) {
builder := l.svcCtx.OrderModel.SelectBuilder().
Where("`del_state` = ?", globalkey.DelStateNo)
// 订单筛选
if req.OrderNo != nil && *req.OrderNo != "" {
builder = builder.Where("`order_no` LIKE ?", "%"+*req.OrderNo+"%")
}
if req.PlatformOrderId != nil && *req.PlatformOrderId != "" {
builder = builder.Where("`platform_order_id` LIKE ?", "%"+*req.PlatformOrderId+"%")
}
if req.PaymentPlatform != nil && *req.PaymentPlatform != "" {
builder = builder.Where("`payment_platform` = ?", *req.PaymentPlatform)
}
if req.PaymentScene != nil && *req.PaymentScene != "" {
builder = builder.Where("`payment_scene` = ?", *req.PaymentScene)
}
if req.OrderStatus != nil && *req.OrderStatus != "" {
builder = builder.Where("`status` = ?", *req.OrderStatus)
}
// 时间筛选
if req.CreateTimeStart != nil && *req.CreateTimeStart != "" {
createTimeStart, err := time.Parse("2006-01-02 15:04:05", *req.CreateTimeStart)
if err == nil {
builder = builder.Where("`create_time` >= ?", createTimeStart)
}
}
if req.CreateTimeEnd != nil && *req.CreateTimeEnd != "" {
createTimeEnd, err := time.Parse("2006-01-02 15:04:05", *req.CreateTimeEnd)
if err == nil {
builder = builder.Where("`create_time` <= ?", createTimeEnd)
}
}
if req.PayTimeStart != nil && *req.PayTimeStart != "" {
payTimeStart, err := time.Parse("2006-01-02 15:04:05", *req.PayTimeStart)
if err == nil {
builder = builder.Where("`pay_time` >= ?", payTimeStart)
}
}
if req.PayTimeEnd != nil && *req.PayTimeEnd != "" {
payTimeEnd, err := time.Parse("2006-01-02 15:04:05", *req.PayTimeEnd)
if err == nil {
builder = builder.Where("`pay_time` <= ?", payTimeEnd)
}
}
// 排序
orderBy := "`create_time` DESC"
if req.OrderBy != nil && *req.OrderBy != "" {
orderType := "DESC"
if req.OrderType != nil && strings.ToUpper(*req.OrderType) == "ASC" {
orderType = "ASC"
}
orderBy = fmt.Sprintf("`%s` %s", *req.OrderBy, orderType)
}
// 分页
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
// 查询总数和列表
orders, total, err := l.svcCtx.OrderModel.FindPageListByPageWithTotal(l.ctx, builder, page, pageSize, orderBy)
if err != nil {
return nil, 0, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单列表失败, %v", err)
}
return orders, total, nil
}
// queryCommissionsByOrderIds 批量查询佣金信息
func (l *AdminGetAgentOrdersListLogic) queryCommissionsByOrderIds(orderIds []string) map[string]*model.AgentCommission {
if len(orderIds) == 0 {
return make(map[string]*model.AgentCommission)
}
// 构建IN查询
inClause := strings.Repeat("?,", len(orderIds))
inClause = inClause[:len(inClause)-1]
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx,
l.svcCtx.AgentCommissionModel.SelectBuilder().
Where(fmt.Sprintf("`order_id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(orderIds), globalkey.DelStateNo)...),
"")
if err != nil {
logx.Errorf("查询佣金信息失败: %v", err)
return make(map[string]*model.AgentCommission)
}
// 构建map: orderId -> commission
result := make(map[string]*model.AgentCommission)
for _, commission := range commissions {
result[commission.OrderId] = commission
}
return result
}
// queryAgentsByAgentIds 批量查询代理信息
func (l *AdminGetAgentOrdersListLogic) queryAgentsByAgentIds(agentIds []string) map[string]*model.Agent {
if len(agentIds) == 0 {
return make(map[string]*model.Agent)
}
// 去重
uniqueAgentIds := make(map[string]bool)
for _, id := range agentIds {
uniqueAgentIds[id] = true
}
ids := make([]string, 0, len(uniqueAgentIds))
for id := range uniqueAgentIds {
ids = append(ids, id)
}
if len(ids) == 0 {
return make(map[string]*model.Agent)
}
// 构建IN查询
inClause := strings.Repeat("?,", len(ids))
inClause = inClause[:len(inClause)-1]
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
l.svcCtx.AgentModel.SelectBuilder().
Where(fmt.Sprintf("`id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(ids), globalkey.DelStateNo)...),
"")
if err != nil {
logx.Errorf("查询代理信息失败: %v", err)
return make(map[string]*model.Agent)
}
// 构建map: agentId -> agent
result := make(map[string]*model.Agent)
for _, agent := range agents {
result[agent.Id] = agent
}
return result
}
// queryUsersByUserIds 批量查询用户信息
func (l *AdminGetAgentOrdersListLogic) queryUsersByUserIds(userIds []string) map[string]*model.User {
if len(userIds) == 0 {
return make(map[string]*model.User)
}
// 去重
uniqueUserIds := make(map[string]bool)
for _, id := range userIds {
uniqueUserIds[id] = true
}
ids := make([]string, 0, len(uniqueUserIds))
for id := range uniqueUserIds {
ids = append(ids, id)
}
if len(ids) == 0 {
return make(map[string]*model.User)
}
// 构建IN查询
inClause := strings.Repeat("?,", len(ids))
inClause = inClause[:len(inClause)-1]
users, err := l.svcCtx.UserModel.FindAll(l.ctx,
l.svcCtx.UserModel.SelectBuilder().
Where(fmt.Sprintf("`id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(ids), globalkey.DelStateNo)...),
"")
if err != nil {
logx.Errorf("查询用户信息失败: %v", err)
return make(map[string]*model.User)
}
// 构建map: userId -> user
result := make(map[string]*model.User)
for _, user := range users {
result[user.Id] = user
}
return result
}
// queryProductsByProductIds 批量查询产品信息
func (l *AdminGetAgentOrdersListLogic) queryProductsByProductIds(productIds []string) map[string]*model.Product {
if len(productIds) == 0 {
return make(map[string]*model.Product)
}
// 去重
uniqueProductIds := make(map[string]bool)
for _, id := range productIds {
uniqueProductIds[id] = true
}
ids := make([]string, 0, len(uniqueProductIds))
for id := range uniqueProductIds {
ids = append(ids, id)
}
if len(ids) == 0 {
return make(map[string]*model.Product)
}
// 构建IN查询
inClause := strings.Repeat("?,", len(ids))
inClause = inClause[:len(inClause)-1]
products, err := l.svcCtx.ProductModel.FindAll(l.ctx,
l.svcCtx.ProductModel.SelectBuilder().
Where(fmt.Sprintf("`id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(ids), globalkey.DelStateNo)...),
"")
if err != nil {
logx.Errorf("查询产品信息失败: %v", err)
return make(map[string]*model.Product)
}
// 构建map: productId -> product
result := make(map[string]*model.Product)
for _, product := range products {
result[product.Id] = product
}
return result
}
// assembleOrderList 组装订单列表数据
func (l *AdminGetAgentOrdersListLogic) assembleOrderList(
orders []*model.Order,
commissionsMap map[string]*model.AgentCommission,
agentsMap map[string]*model.Agent,
usersMap map[string]*model.User,
productsMap map[string]*model.Product,
) []types.AgentOrdersListItem {
var list []types.AgentOrdersListItem
for _, order := range orders {
// 获取佣金信息
var commissionAmount float64
var commissionStatus int64 = 2 // 默认已取消
var agentId string
var agentMobile string
if commission, ok := commissionsMap[order.Id]; ok {
commissionAmount = commission.Amount
commissionStatus = commission.Status
agentId = commission.AgentId
// 获取代理信息并解密手机号
if agent, ok := agentsMap[commission.AgentId]; ok {
if agent.Mobile != "" {
// 解密代理手机号
mobile, err := crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
logx.Errorf("解密代理手机号失败: %v", err)
agentMobile = ""
}
agentMobile = mobile
}
}
} else {
// 没有佣金记录使用订单ID作为代理ID
agentId = order.Id
}
// 获取用户手机号并解密
var userMobile *string
if user, ok := usersMap[order.UserId]; ok && user.Mobile.Valid {
// 解密用户手机号
mobile, err := crypto.DecryptMobile(user.Mobile.String, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
logx.Errorf("解密用户手机号失败: %v", err)
userMobile = nil
} else {
userMobile = &mobile
}
}
// 获取产品名称
productName := "未知产品"
if product, ok := productsMap[order.ProductId]; ok {
productName = product.ProductName
}
// 获取平台订单号
platformOrderId := ""
if order.PlatformOrderId.Valid {
platformOrderId = order.PlatformOrderId.String
}
// 获取支付时间
var payTime *string
if order.PayTime.Valid {
pt := order.PayTime.Time.Format("2006-01-02 15:04:05")
payTime = &pt
}
list = append(list, types.AgentOrdersListItem{
Id: order.Id,
OrderNo: order.OrderNo,
PlatformOrderId: platformOrderId,
AgentId: agentId,
AgentMobile: agentMobile,
UserId: order.UserId,
UserMobile: userMobile,
ProductId: order.ProductId,
ProductName: productName,
OrderAmount: order.Amount,
CommissionAmount: commissionAmount,
PaymentPlatform: order.PaymentPlatform,
PaymentScene: order.PaymentScene,
OrderStatus: order.Status,
CommissionStatus: commissionStatus,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
PayTime: payTime,
})
}
return list
}
// stringsToInterfaces 将字符串数组转换为interface数组
func stringsToInterfaces(strs []string) []interface{} {
result := make([]interface{}, len(strs))
for i, str := range strs {
result[i] = str
}
return result
}

View File

@@ -0,0 +1,99 @@
package admin_agent
import (
"context"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentProductConfigListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentProductConfigListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentProductConfigListLogic {
return &AdminGetAgentProductConfigListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req *types.AdminGetAgentProductConfigListReq) (resp *types.AdminGetAgentProductConfigListResp, err error) {
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
// 如果提供了产品ID直接过滤
if req.ProductId != nil {
builder = builder.Where("product_id = ?", *req.ProductId)
}
// 如果提供了产品名称,通过关联查询 product 表过滤
if req.ProductName != nil && *req.ProductName != "" {
builder = builder.Where("product_id IN (SELECT id FROM product WHERE product_name LIKE ? AND del_state = ?)", "%"+*req.ProductName+"%", globalkey.DelStateNo)
}
// 分页查询
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
configs, total, err := l.svcCtx.AgentProductConfigModel.FindPageListByPageWithTotal(l.ctx, builder, page, pageSize, "id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置列表失败, %v", err)
}
// 组装响应(通过 product_id 查询产品名称)
items := make([]types.AgentProductConfigItem, 0, len(configs))
for _, config := range configs {
// 通过 product_id 查询产品信息获取产品名称
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, config.ProductId)
productName := ""
if err == nil {
productName = product.ProductName
} else {
// 如果产品不存在,记录日志但不影响主流程
l.Infof("查询产品信息失败, productId: %d, err: %v", config.ProductId, err)
}
priceThreshold := 0.0
if config.PriceThreshold.Valid {
priceThreshold = config.PriceThreshold.Float64
}
priceFeeRate := 0.0
if config.PriceFeeRate.Valid {
priceFeeRate = config.PriceFeeRate.Float64
}
items = append(items, types.AgentProductConfigItem{
Id: config.Id,
ProductId: config.ProductId,
ProductName: productName,
BasePrice: config.BasePrice,
PriceRangeMin: config.BasePrice, // 最低定价等于基础底价
PriceRangeMax: config.SystemMaxPrice,
PriceThreshold: priceThreshold,
PriceFeeRate: priceFeeRate,
CreateTime: config.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return &types.AdminGetAgentProductConfigListResp{
Total: total,
Items: items,
}, nil
}

View File

@@ -0,0 +1,158 @@
package admin_agent
import (
"context"
"fmt"
"strings"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAgentWithdrawListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAgentWithdrawListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentWithdrawListLogic {
return &AdminGetAgentWithdrawListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAgentWithdrawListLogic) AdminGetAgentWithdrawList(req *types.AdminGetAgentWithdrawListReq) (resp *types.AdminGetAgentWithdrawListResp, err error) {
// 1. 构建查询条件
whereConditions := []string{fmt.Sprintf("del_state = %d", globalkey.DelStateNo)}
args := []interface{}{}
// 可选过滤条件
if req.AgentId != nil && *req.AgentId != "" {
whereConditions = append(whereConditions, "agent_id = ?")
args = append(args, *req.AgentId)
}
if req.Status != nil {
whereConditions = append(whereConditions, "status = ?")
args = append(args, *req.Status)
}
whereClause := strings.Join(whereConditions, " AND ")
// 2. 构建查询builder
builder := l.svcCtx.AgentWithdrawModel.SelectBuilder().
Where(whereClause, args...).
OrderBy("create_time DESC")
// 3. 分页参数
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentWithdrawModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录总数失败: %v", err)
}
if total == 0 {
return &types.AdminGetAgentWithdrawListResp{
Total: 0,
Items: []types.AgentWithdrawListItem{},
}, nil
}
// 5. 查询列表
builder = builder.Limit(uint64(pageSize)).Offset(uint64(offset))
withdrawals, err := l.svcCtx.AgentWithdrawModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录列表失败: %v", err)
}
// 6. 组装响应
var items []types.AgentWithdrawListItem
for _, withdrawal := range withdrawals {
// 处理代理ID
agentId := withdrawal.AgentId
// 处理代理手机号
agentMobile := ""
if withdrawal.AgentMobile.Valid {
agentMobile = withdrawal.AgentMobile.String
}
// 处理代理编码
var agentCode int64 = 0
if withdrawal.AgentCode.Valid {
agentCode = withdrawal.AgentCode.Int64
}
// 银行卡号脱敏只显示前4位和后4位
bankCardNumber := withdrawal.BankCardNumber
if len(bankCardNumber) > 8 {
bankCardNumber = fmt.Sprintf("%s****%s", bankCardNumber[:4], bankCardNumber[len(bankCardNumber)-4:])
}
// 处理开户支行
bankBranch := ""
if withdrawal.BankBranch.Valid {
bankBranch = withdrawal.BankBranch.String
}
// 处理审核人ID
auditUserId := ""
if withdrawal.AuditUserId.Valid {
auditUserId = withdrawal.AuditUserId.String
}
// 处理审核时间
auditTime := ""
if withdrawal.AuditTime.Valid {
auditTime = withdrawal.AuditTime.Time.Format("2006-01-02 15:04:05")
}
// 处理审核备注
auditRemark := ""
if withdrawal.AuditRemark.Valid {
auditRemark = withdrawal.AuditRemark.String
}
items = append(items, types.AgentWithdrawListItem{
Id: withdrawal.Id,
AgentId: agentId,
AgentMobile: agentMobile,
AgentCode: agentCode,
WithdrawAmount: withdrawal.WithdrawAmount,
TaxAmount: withdrawal.TaxAmount,
ActualAmount: withdrawal.ActualAmount,
FrozenAmount: withdrawal.FrozenAmount,
AccountName: withdrawal.AccountName,
BankCardNumber: bankCardNumber, // 脱敏显示
BankCardNumberFull: withdrawal.BankCardNumber, // 完整卡号用于审核
BankBranch: bankBranch,
Status: withdrawal.Status,
AuditUserId: auditUserId,
AuditTime: auditTime,
AuditRemark: auditRemark,
CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return &types.AdminGetAgentWithdrawListResp{
Total: total,
Items: items,
}, nil
}

View File

@@ -0,0 +1,30 @@
package admin_agent
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminRefundAgentOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminRefundAgentOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRefundAgentOrderLogic {
return &AdminRefundAgentOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminRefundAgentOrderLogic) AdminRefundAgentOrder(req *types.AdminRefundAgentOrderReq) (resp *types.AdminRefundAgentOrderResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,126 @@
package admin_agent
import (
"context"
"database/sql"
"strconv"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUpdateAgentConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateAgentConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateAgentConfigLogic {
return &AdminUpdateAgentConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateAgentConfigLogic) AdminUpdateAgentConfig(req *types.AdminUpdateAgentConfigReq) (resp *types.AdminUpdateAgentConfigResp, err error) {
// 系统简化:移除等级加成、升级费用、升级返佣、直接上级返佣配置、免税额度
// 只保留佣金冻结配置和税率配置
configTypeForKey := func(key string) string {
switch key {
case "commission_freeze_ratio", "commission_freeze_threshold", "commission_freeze_days":
return "commission_freeze"
case "tax_rate":
return "tax"
default:
return "other"
}
}
updateConfig := func(key string, value *float64) error {
if value == nil {
return nil
}
valStr := strconv.FormatFloat(*value, 'f', -1, 64)
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, key)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
cfg := &model.AgentConfig{
ConfigKey: key,
ConfigValue: valStr,
ConfigType: configTypeForKey(key),
Description: sql.NullString{},
DeleteTime: sql.NullTime{},
Version: 0,
}
_, insErr := l.svcCtx.AgentConfigModel.Insert(l.ctx, nil, cfg)
if insErr != nil {
return errors.Wrapf(insErr, "创建配置失败, key: %s", key)
}
return nil
}
return errors.Wrapf(err, "查询配置失败, key: %s", key)
}
config.ConfigValue = valStr
if uErr := l.svcCtx.AgentConfigModel.UpdateWithVersion(l.ctx, nil, config); uErr != nil {
if errors.Is(uErr, model.ErrNoRowsUpdate) {
latestByKey, reErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, key)
if reErr != nil {
if errors.Is(reErr, model.ErrNotFound) {
cfg := &model.AgentConfig{
ConfigKey: key,
ConfigValue: valStr,
ConfigType: configTypeForKey(key),
Description: sql.NullString{},
DeleteTime: sql.NullTime{},
Version: 0,
}
_, insErr := l.svcCtx.AgentConfigModel.Insert(l.ctx, nil, cfg)
if insErr != nil {
return errors.Wrapf(insErr, "创建配置失败, key: %s", key)
}
return nil
}
return errors.Wrapf(reErr, "查询最新配置失败, key: %s", key)
}
latestByKey.ConfigValue = valStr
return l.svcCtx.AgentConfigModel.UpdateWithVersion(l.ctx, nil, latestByKey)
}
return uErr
}
return nil
}
// 更新佣金冻结配置
if req.CommissionFreeze != nil {
if err := updateConfig("commission_freeze_ratio", &req.CommissionFreeze.Ratio); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结比例失败, %v", err)
}
if err := updateConfig("commission_freeze_threshold", &req.CommissionFreeze.Threshold); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结阈值失败, %v", err)
}
// 更新解冻天数(整数类型)
if req.CommissionFreeze.Days > 0 {
daysFloat := float64(req.CommissionFreeze.Days)
if err := updateConfig("commission_freeze_days", &daysFloat); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结解冻天数失败, %v", err)
}
}
}
// 更新税费配置
if err := updateConfig("tax_rate", req.TaxRate); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新税率失败, %v", err)
}
return &types.AdminUpdateAgentConfigResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,63 @@
package admin_agent
import (
"context"
"database/sql"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUpdateAgentProductConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateAgentProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateAgentProductConfigLogic {
return &AdminUpdateAgentProductConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateAgentProductConfigLogic) AdminUpdateAgentProductConfig(req *types.AdminUpdateAgentProductConfigReq) (resp *types.AdminUpdateAgentProductConfigResp, err error) {
// 查询配置
config, err := l.svcCtx.AgentProductConfigModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置失败, %v", err)
}
// 更新配置字段
config.BasePrice = req.BasePrice
config.SystemMaxPrice = req.PriceRangeMax
// 价格阈值(可选)
if req.PriceThreshold != nil {
config.PriceThreshold = sql.NullFloat64{Float64: *req.PriceThreshold, Valid: true}
} else {
config.PriceThreshold = sql.NullFloat64{Valid: false}
}
// 提价手续费比例(可选)
if req.PriceFeeRate != nil {
config.PriceFeeRate = sql.NullFloat64{Float64: *req.PriceFeeRate, Valid: true}
} else {
config.PriceFeeRate = sql.NullFloat64{Valid: false}
}
// 更新配置
if err := l.svcCtx.AgentProductConfigModel.UpdateWithVersion(l.ctx, nil, config); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新产品配置失败, %v", err)
}
return &types.AdminUpdateAgentProductConfigResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,70 @@
package admin_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminBatchUpdateApiStatusLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminBatchUpdateApiStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminBatchUpdateApiStatusLogic {
return &AdminBatchUpdateApiStatusLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminBatchUpdateApiStatusLogic) AdminBatchUpdateApiStatus(req *types.AdminBatchUpdateApiStatusReq) (resp *types.AdminBatchUpdateApiStatusResp, err error) {
// 1. 参数验证
if len(req.Ids) == 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API ID列表不能为空")
}
if req.Status != 0 && req.Status != 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"状态值无效, status: %d", req.Status)
}
// 2. 批量更新API状态
successCount := 0
for _, id := range req.Ids {
if id == "" {
continue
}
// 查询API是否存在
api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
continue // 跳过不存在的API
}
logx.Errorf("查询API失败, err: %v, id: %s", err, id)
continue
}
// 更新状态
api.Status = req.Status
_, err = l.svcCtx.AdminApiModel.Update(l.ctx, nil, api)
if err != nil {
logx.Errorf("更新API状态失败, err: %v, id: %d", err, id)
continue
}
successCount++
}
// 3. 返回结果
return &types.AdminBatchUpdateApiStatusResp{Success: true}, nil
}

View File

@@ -0,0 +1,79 @@
package admin_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminCreateApiLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreateApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateApiLogic {
return &AdminCreateApiLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreateApiLogic) AdminCreateApi(req *types.AdminCreateApiReq) (resp *types.AdminCreateApiResp, err error) {
// 1. 参数验证
if req.ApiName == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API名称不能为空")
}
if req.ApiCode == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API编码不能为空")
}
if req.Method == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"请求方法不能为空")
}
if req.Url == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API路径不能为空")
}
// 2. 检查API编码是否已存在
existing, err := l.svcCtx.AdminApiModel.FindOneByApiCode(l.ctx, req.ApiCode)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API失败, err: %v, apiCode: %s", err, req.ApiCode)
}
if existing != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API编码已存在: %s", req.ApiCode)
}
// 3. 创建API记录
apiData := &model.AdminApi{
Id: uuid.NewString(),
ApiName: req.ApiName,
ApiCode: req.ApiCode,
Method: req.Method,
Url: req.Url,
Status: req.Status,
Description: req.Description,
}
result, err := l.svcCtx.AdminApiModel.Insert(l.ctx, nil, apiData)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建API失败, err: %v", err)
}
// 4. 返回结果
_ = result
return &types.AdminCreateApiResp{Id: apiData.Id}, nil
}

View File

@@ -0,0 +1,68 @@
package admin_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminDeleteApiLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeleteApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteApiLogic {
return &AdminDeleteApiLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeleteApiLogic) AdminDeleteApi(req *types.AdminDeleteApiReq) (resp *types.AdminDeleteApiResp, err error) {
// 1. 参数验证
if req.Id == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API ID不能为空, id: %s", req.Id)
}
// 2. 查询API是否存在
api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, req.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"API不存在, id: %d", req.Id)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API失败, err: %v, id: %d", err, req.Id)
}
// 3. 检查是否有角色关联该API
roleApiBuilder := l.svcCtx.AdminRoleApiModel.SelectBuilder().Where("api_id = ?", req.Id)
roleApis, err := l.svcCtx.AdminRoleApiModel.FindAll(l.ctx, roleApiBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色API关联失败, err: %v, apiId: %d", err, req.Id)
}
if len(roleApis) > 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"该API已被角色使用无法删除, apiId: %d", req.Id)
}
// 4. 执行软删除
err = l.svcCtx.AdminApiModel.DeleteSoft(l.ctx, nil, api)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除API失败, err: %v, id: %d", err, req.Id)
}
// 5. 返回结果
return &types.AdminDeleteApiResp{Success: true}, nil
}

View File

@@ -0,0 +1,61 @@
package admin_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetApiDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetApiDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetApiDetailLogic {
return &AdminGetApiDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetApiDetailLogic) AdminGetApiDetail(req *types.AdminGetApiDetailReq) (resp *types.AdminGetApiDetailResp, err error) {
// 1. 参数验证
if req.Id == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API ID不能为空, id: %s", req.Id)
}
// 2. 查询API详情
api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, req.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"API不存在, id: %d", req.Id)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API详情失败, err: %v, id: %d", err, req.Id)
}
// 3. 返回结果
return &types.AdminGetApiDetailResp{
AdminApiInfo: types.AdminApiInfo{
Id: api.Id,
ApiName: api.ApiName,
ApiCode: api.ApiCode,
Method: api.Method,
Url: api.Url,
Status: api.Status,
Description: api.Description,
CreateTime: api.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: api.UpdateTime.Format("2006-01-02 15:04:05"),
},
}, nil
}

View File

@@ -0,0 +1,89 @@
package admin_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetApiListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetApiListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetApiListLogic {
return &AdminGetApiListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetApiListLogic) AdminGetApiList(req *types.AdminGetApiListReq) (resp *types.AdminGetApiListResp, err error) {
// 1. 参数验证
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 20
}
if req.PageSize > 100 {
req.PageSize = 100
}
// 2. 构建查询条件
builder := l.svcCtx.AdminApiModel.SelectBuilder()
// 添加搜索条件
if req.ApiName != "" {
builder = builder.Where("api_name LIKE ?", "%"+req.ApiName+"%")
}
if req.Method != "" {
builder = builder.Where("method = ?", req.Method)
}
if req.Status > 0 {
builder = builder.Where("status = ?", req.Status)
}
// 3. 查询总数
total, err := l.svcCtx.AdminApiModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API总数失败, err: %v", err)
}
// 4. 查询列表
apis, err := l.svcCtx.AdminApiModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API列表失败, err: %v", err)
}
// 5. 转换数据格式
var apiList []types.AdminApiInfo
for _, api := range apis {
apiList = append(apiList, types.AdminApiInfo{
Id: api.Id,
ApiName: api.ApiName,
ApiCode: api.ApiCode,
Method: api.Method,
Url: api.Url,
Status: api.Status,
Description: api.Description,
CreateTime: api.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: api.UpdateTime.Format("2006-01-02 15:04:05"),
})
}
// 6. 返回结果
return &types.AdminGetApiListResp{
Items: apiList,
Total: total,
}, nil
}

View File

@@ -0,0 +1,92 @@
package admin_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUpdateApiLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateApiLogic {
return &AdminUpdateApiLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateApiLogic) AdminUpdateApi(req *types.AdminUpdateApiReq) (resp *types.AdminUpdateApiResp, err error) {
// 1. 参数验证
if req.Id == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API ID不能为空, id: %s", req.Id)
}
if req.ApiName == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API名称不能为空")
}
if req.ApiCode == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API编码不能为空")
}
if req.Method == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"请求方法不能为空")
}
if req.Url == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API路径不能为空")
}
// 2. 查询API是否存在
api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, req.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"API不存在, id: %d", req.Id)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API失败, err: %v, id: %d", err, req.Id)
}
// 3. 检查API编码是否被其他记录使用
if api.ApiCode != req.ApiCode {
existing, err := l.svcCtx.AdminApiModel.FindOneByApiCode(l.ctx, req.ApiCode)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API失败, err: %v, apiCode: %s", err, req.ApiCode)
}
if existing != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API编码已存在: %s", req.ApiCode)
}
}
// 4. 更新API信息
api.ApiName = req.ApiName
api.ApiCode = req.ApiCode
api.Method = req.Method
api.Url = req.Url
api.Status = req.Status
api.Description = req.Description
_, err = l.svcCtx.AdminApiModel.Update(l.ctx, nil, api)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"更新API失败, err: %v, id: %d", err, req.Id)
}
// 5. 返回结果
return &types.AdminUpdateApiResp{Success: true}, nil
}

View File

@@ -0,0 +1,96 @@
package admin_auth
import (
"context"
"os"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
jwtx "jnc-server/common/jwt"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminLoginLogic {
return &AdminLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.AdminLoginResp, err error) {
// 1. 验证验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
if !req.Captcha {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha)
}
}
// 2. 验证用户名和密码
user, err := l.svcCtx.AdminUserModel.FindOneByUsername(l.ctx, req.Username)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("用户名或密码错误"), "用户登录, 用户名或密码错误, 用户名: %s", req.Username)
}
// 3. 验证密码
if !crypto.PasswordVerify(req.Password, user.Password) {
return nil, errors.Wrapf(xerr.NewErrMsg("用户名或密码错误"), "用户登录, 用户名或密码错误, 用户名: %s", req.Username)
}
// 4. 获取权限
adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": user.Id})
permissions, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, adminUserRoleBuilder, "role_id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("获取权限失败"), "用户登录, 获取权限失败, 用户名: %s", req.Username)
}
// 获取角色ID数组
roleIds := make([]string, 0)
for _, permission := range permissions {
roleIds = append(roleIds, permission.RoleId)
}
// 获取角色名称
roles := make([]string, 0)
for _, roleId := range roleIds {
role, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, roleId)
if err != nil {
continue
}
roles = append(roles, role.RoleCode)
}
// 5. 生成token
refreshToken := l.svcCtx.Config.AdminConfig.RefreshAfter
expiresAt := l.svcCtx.Config.AdminConfig.AccessExpire
claims := jwtx.JwtClaims{
UserId: user.Id,
AgentId: "",
Platform: model.PlatformAdmin,
UserType: model.UserTypeAdmin,
IsAgent: model.AgentStatusNo,
}
token, err := jwtx.GenerateJwtToken(claims, l.svcCtx.Config.AdminConfig.AccessSecret, expiresAt)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("生成token失败"), "用户登录, 生成token失败, 用户名: %s", req.Username)
}
return &types.AdminLoginResp{
AccessToken: token,
AccessExpire: expiresAt,
RefreshAfter: refreshToken,
Roles: roles,
}, nil
}

View File

@@ -0,0 +1,94 @@
package admin_feature
import (
"context"
"encoding/hex"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/google/uuid"
)
type AdminConfigFeatureExampleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminConfigFeatureExampleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminConfigFeatureExampleLogic {
return &AdminConfigFeatureExampleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminConfigFeatureExampleLogic) AdminConfigFeatureExample(req *types.AdminConfigFeatureExampleReq) (resp *types.AdminConfigFeatureExampleResp, err error) {
// 1. 验证功能是否存在
feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.FeatureId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询功能失败, featureId: %d, err: %v", req.FeatureId, err)
}
if feature == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR),
"功能不存在, featureId: %d", req.FeatureId)
}
// 2. 检查是否已存在示例数据
existingExample, err := l.svcCtx.ExampleModel.FindOneByFeatureId(l.ctx, req.FeatureId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询示例数据失败, featureId: %d, err: %v", req.FeatureId, err)
}
// 3. 加密示例数据
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"获取AES密钥失败: %v", decodeErr)
}
encryptedData, aesEncryptErr := crypto.AesEncrypt([]byte(req.Data), key)
if aesEncryptErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"加密示例数据失败: %v", aesEncryptErr)
}
// 4. 准备示例数据
exampleData := &model.Example{
Id: uuid.NewString(),
ApiId: feature.ApiId,
FeatureId: req.FeatureId,
Content: encryptedData,
}
// 4. 根据是否存在决定新增或更新
if existingExample == nil {
// 新增示例数据
_, err = l.svcCtx.ExampleModel.Insert(l.ctx, nil, exampleData)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建示例数据失败, featureId: %d, err: %v", req.FeatureId, err)
}
} else {
// 更新示例数据
exampleData.Id = existingExample.Id
exampleData.Version = existingExample.Version
_, err = l.svcCtx.ExampleModel.Update(l.ctx, nil, exampleData)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"更新示例数据失败, featureId: %d, err: %v", req.FeatureId, err)
}
}
// 5. 返回成功结果
return &types.AdminConfigFeatureExampleResp{Success: true}, nil
}

View File

@@ -0,0 +1,48 @@
package admin_feature
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminCreateFeatureLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreateFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateFeatureLogic {
return &AdminCreateFeatureLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreateFeatureLogic) AdminCreateFeature(req *types.AdminCreateFeatureReq) (resp *types.AdminCreateFeatureResp, err error) {
// 1. 数据转换
data := &model.Feature{
Id: uuid.NewString(),
ApiId: req.ApiId,
Name: req.Name,
}
// 2. 数据库操作
result, err := l.svcCtx.FeatureModel.Insert(l.ctx, nil, data)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建功能失败, err: %v, req: %+v", err, req)
}
// 3. 返回结果
_ = result
return &types.AdminCreateFeatureResp{Id: data.Id}, nil
}

View File

@@ -0,0 +1,45 @@
package admin_feature
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminDeleteFeatureLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeleteFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteFeatureLogic {
return &AdminDeleteFeatureLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeleteFeatureLogic) AdminDeleteFeature(req *types.AdminDeleteFeatureReq) (resp *types.AdminDeleteFeatureResp, err error) {
// 1. 查询记录是否存在
record, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找功能失败, err: %v, id: %d", err, req.Id)
}
// 2. 执行软删除
err = l.svcCtx.FeatureModel.DeleteSoft(l.ctx, nil, record)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除功能失败, err: %v, id: %d", err, req.Id)
}
// 3. 返回结果
return &types.AdminDeleteFeatureResp{Success: true}, nil
}

View File

@@ -0,0 +1,46 @@
package admin_feature
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetFeatureDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetFeatureDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetFeatureDetailLogic {
return &AdminGetFeatureDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetFeatureDetailLogic) AdminGetFeatureDetail(req *types.AdminGetFeatureDetailReq) (resp *types.AdminGetFeatureDetailResp, err error) {
// 1. 查询记录
record, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找功能失败, err: %v, id: %d", err, req.Id)
}
// 2. 构建响应
resp = &types.AdminGetFeatureDetailResp{
Id: record.Id,
ApiId: record.ApiId,
Name: record.Name,
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
}
return resp, nil
}

View File

@@ -0,0 +1,82 @@
package admin_feature
import (
"context"
"encoding/hex"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetFeatureExampleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetFeatureExampleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetFeatureExampleLogic {
return &AdminGetFeatureExampleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetFeatureExampleLogic) AdminGetFeatureExample(req *types.AdminGetFeatureExampleReq) (resp *types.AdminGetFeatureExampleResp, err error) {
// 1. 查询示例数据
example, err := l.svcCtx.ExampleModel.FindOneByFeatureId(l.ctx, req.FeatureId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 示例数据不存在,返回空数据
return &types.AdminGetFeatureExampleResp{
Id: "",
FeatureId: req.FeatureId,
ApiId: "",
Data: "",
CreateTime: "",
UpdateTime: "",
}, nil
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询示例数据失败, featureId: %d, err: %v", req.FeatureId, err)
}
// 2. 获取解密密钥
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"获取AES密钥失败: %v", decodeErr)
}
// 3. 解密示例数据
var decryptedData string
if example.Content == "000" {
// 特殊值,直接返回
decryptedData = example.Content
} else {
// 解密数据
decryptedBytes, decryptErr := crypto.AesDecrypt(example.Content, key)
if decryptErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"解密示例数据失败: %v", decryptErr)
}
decryptedData = string(decryptedBytes)
}
// 4. 返回解密后的数据
return &types.AdminGetFeatureExampleResp{
Id: example.Id,
FeatureId: example.FeatureId,
ApiId: example.ApiId,
Data: decryptedData,
CreateTime: example.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: example.UpdateTime.Format("2006-01-02 15:04:05"),
}, nil
}

View File

@@ -0,0 +1,66 @@
package admin_feature
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetFeatureListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetFeatureListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetFeatureListLogic {
return &AdminGetFeatureListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetFeatureListLogic) AdminGetFeatureList(req *types.AdminGetFeatureListReq) (resp *types.AdminGetFeatureListResp, err error) {
// 1. 构建查询条件
builder := l.svcCtx.FeatureModel.SelectBuilder()
// 2. 添加查询条件
if req.ApiId != nil && *req.ApiId != "" {
builder = builder.Where("api_id LIKE ?", "%"+*req.ApiId+"%")
}
if req.Name != nil && *req.Name != "" {
builder = builder.Where("name LIKE ?", "%"+*req.Name+"%")
}
// 3. 执行分页查询
list, total, err := l.svcCtx.FeatureModel.FindPageListByPageWithTotal(
l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询功能列表失败, err: %v, req: %+v", err, req)
}
// 4. 构建响应列表
items := make([]types.FeatureListItem, 0, len(list))
for _, item := range list {
listItem := types.FeatureListItem{
Id: item.Id,
ApiId: item.ApiId,
Name: item.Name,
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
}
items = append(items, listItem)
}
// 5. 返回结果
return &types.AdminGetFeatureListResp{
Total: total,
Items: items,
}, nil
}

View File

@@ -0,0 +1,59 @@
package admin_feature
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUpdateFeatureLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateFeatureLogic {
return &AdminUpdateFeatureLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateFeatureLogic) AdminUpdateFeature(req *types.AdminUpdateFeatureReq) (resp *types.AdminUpdateFeatureResp, err error) {
// 1. 参数验证
if req.Id == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"功能ID不能为空, id: %s", req.Id)
}
// 2. 查询记录是否存在
record, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找功能失败, err: %v, id: %d", err, req.Id)
}
// 3. 直接更新record的字段只更新非空字段
if req.ApiId != nil && *req.ApiId != "" {
record.ApiId = *req.ApiId
}
if req.Name != nil && *req.Name != "" {
record.Name = *req.Name
}
// 4. 执行更新操作
err = l.svcCtx.FeatureModel.UpdateWithVersion(l.ctx, nil, record)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"更新功能失败, err: %v, id: %d", err, req.Id)
}
// 5. 返回成功结果
return &types.AdminUpdateFeatureResp{Success: true}, nil
}

View File

@@ -0,0 +1,99 @@
package admin_menu
import (
"context"
"database/sql"
"encoding/json"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/google/uuid"
)
type CreateMenuLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateMenuLogic {
return &CreateMenuLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateMenuLogic) CreateMenu(req *types.CreateMenuReq) (resp *types.CreateMenuResp, err error) {
// 1. 参数验证
if req.Name == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称不能为空"), "菜单名称不能为空")
}
if req.Type == "menu" && req.Component == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("组件路径不能为空"), "组件路径不能为空")
}
// 2. 检查名称和路径是否重复
exists, err := l.svcCtx.AdminMenuModel.FindOneByNamePath(l.ctx, req.Name, req.Path)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err)
}
if exists != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称或路径已存在"), "菜单名称或路径已存在")
}
// 3. 检查父菜单是否存在(如果不是根菜单)
if req.Pid != "" {
parentMenu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Pid)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询父菜单失败, id: %s, err: %v", req.Pid, err)
}
if parentMenu == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "父菜单不存在, id: %s", req.Pid)
}
}
// 4. 将类型标签转换为值
typeValue, err := l.svcCtx.DictService.GetDictValue(l.ctx, "admin_menu_type", req.Type)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单类型无效: %v", err)
}
// 5. 创建菜单记录
menu := &model.AdminMenu{
Id: uuid.NewString(),
Pid: sql.NullString{String: req.Pid, Valid: req.Pid != ""},
Name: req.Name,
Path: req.Path,
Component: req.Component,
Redirect: sql.NullString{String: req.Redirect, Valid: req.Redirect != ""},
Status: req.Status,
Type: typeValue,
Sort: req.Sort,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
// 将Meta转换为JSON字符串
metaJson, err := json.Marshal(req.Meta)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Meta数据格式错误: %v", err)
}
menu.Meta = string(metaJson)
// 6. 保存到数据库
_, err = l.svcCtx.AdminMenuModel.Insert(l.ctx, nil, menu)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建菜单失败, err: %v", err)
}
return &types.CreateMenuResp{
Id: menu.Id,
}, nil
}

View File

@@ -0,0 +1,75 @@
package admin_menu
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteMenuLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteMenuLogic {
return &DeleteMenuLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteMenuLogic) DeleteMenu(req *types.DeleteMenuReq) (resp *types.DeleteMenuResp, err error) {
// 1. 参数验证
if req.Id == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"菜单ID不能为空, id: %s", req.Id)
}
// 2. 查询菜单是否存在
menu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找菜单失败, err: %v, id: %s", err, req.Id)
}
// 3. 检查是否有子菜单
childMenuBuilder := l.svcCtx.AdminMenuModel.SelectBuilder().Where("pid = ?", req.Id)
childMenus, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, childMenuBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询子菜单失败, err: %v, parent_id: %d", err, req.Id)
}
if len(childMenus) > 0 {
return nil, errors.Wrapf(xerr.NewErrMsg("该菜单下还有子菜单,无法删除"),
"该菜单下还有子菜单,无法删除, id: %d", req.Id)
}
// 4. 检查是否有角色关联该菜单
roleMenuBuilder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().Where("menu_id = ?", req.Id)
roleMenus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, roleMenuBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色菜单关联失败, err: %v, menu_id: %d", err, req.Id)
}
if len(roleMenus) > 0 {
return nil, errors.Wrapf(xerr.NewErrMsg("该菜单已被角色使用,无法删除"),
"该菜单已被角色使用,无法删除, id: %d", req.Id)
}
// 5. 执行软删除
err = l.svcCtx.AdminMenuModel.DeleteSoft(l.ctx, nil, menu)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除菜单失败, err: %v, id: %d", err, req.Id)
}
// 6. 返回成功结果
return &types.DeleteMenuResp{Success: true}, nil
}

View File

@@ -0,0 +1,251 @@
package admin_menu
import (
"context"
"database/sql"
"sort"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/bytedance/sonic"
"github.com/pkg/errors"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetMenuAllLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMenuAllLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuAllLogic {
return &GetMenuAllLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMenuAllLogic) GetMenuAll(req *types.GetMenuAllReq) (resp *[]types.GetMenuAllResp, err error) {
userId, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %+v", err)
}
// 使用MapReduceVoid并发获取用户角色UUID 字符串)
var roleIds []string
var permissions []*struct {
RoleId string
}
type UserRoleResult struct {
RoleId string
}
err = mr.MapReduceVoid(
func(source chan<- interface{}) {
adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": userId})
source <- adminUserRoleBuilder
},
func(item interface{}, writer mr.Writer[*UserRoleResult], cancel func(error)) {
builder := item.(squirrel.SelectBuilder)
result, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "role_id DESC")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户角色信息失败, %+v", err))
return
}
for _, r := range result {
writer.Write(&UserRoleResult{RoleId: r.RoleId})
}
},
func(pipe <-chan *UserRoleResult, cancel func(error)) {
for item := range pipe {
permissions = append(permissions, &struct{ RoleId string }{RoleId: item.RoleId})
}
},
)
if err != nil {
return nil, err
}
for _, permission := range permissions {
roleIds = append(roleIds, permission.RoleId)
}
// 使用MapReduceVoid并发获取角色菜单UUID 字符串)
var menuIds []string
var roleMenus []*struct {
MenuId string
}
type RoleMenuResult struct {
MenuId string
}
err = mr.MapReduceVoid(
func(source chan<- interface{}) {
getRoleMenuBuilder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().Where(squirrel.Eq{"role_id": roleIds})
source <- getRoleMenuBuilder
},
func(item interface{}, writer mr.Writer[*RoleMenuResult], cancel func(error)) {
builder := item.(squirrel.SelectBuilder)
result, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id DESC")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取角色菜单信息失败, %+v", err))
return
}
for _, r := range result {
writer.Write(&RoleMenuResult{MenuId: r.MenuId})
}
},
func(pipe <-chan *RoleMenuResult, cancel func(error)) {
for item := range pipe {
roleMenus = append(roleMenus, &struct{ MenuId string }{MenuId: item.MenuId})
}
},
)
if err != nil {
return nil, err
}
for _, roleMenu := range roleMenus {
menuIds = append(menuIds, roleMenu.MenuId)
}
// 使用MapReduceVoid并发获取菜单
type AdminMenuStruct struct {
Id string
Pid sql.NullString
Name string
Path string
Component string
Redirect struct {
String string
Valid bool
}
Meta string
Sort int64
Type int64
Status int64
}
var menus []*AdminMenuStruct
err = mr.MapReduceVoid(
func(source chan<- interface{}) {
adminMenuBuilder := l.svcCtx.AdminMenuModel.SelectBuilder().Where(squirrel.Eq{"id": menuIds})
source <- adminMenuBuilder
},
func(item interface{}, writer mr.Writer[*AdminMenuStruct], cancel func(error)) {
builder := item.(squirrel.SelectBuilder)
result, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, builder, "sort ASC")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取菜单信息失败, %+v", err))
return
}
for _, r := range result {
menu := &AdminMenuStruct{
Id: r.Id,
Pid: r.Pid,
Name: r.Name,
Path: r.Path,
Component: r.Component,
Redirect: r.Redirect,
Meta: r.Meta,
Sort: r.Sort,
Type: r.Type,
Status: r.Status,
}
writer.Write(menu)
}
},
func(pipe <-chan *AdminMenuStruct, cancel func(error)) {
for item := range pipe {
menus = append(menus, item)
}
},
)
if err != nil {
return nil, err
}
// 转换为types.Menu结构并存储到映射表
menuMap := make(map[string]types.GetMenuAllResp)
for _, menu := range menus {
// 只处理状态正常的菜单
if menu.Status != 1 {
continue
}
meta := make(map[string]interface{})
err = sonic.Unmarshal([]byte(menu.Meta), &meta)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析菜单Meta信息失败, %+v", err)
}
redirect := func() string {
if menu.Redirect.Valid {
return menu.Redirect.String
}
return ""
}()
menuMap[menu.Id] = types.GetMenuAllResp{
Name: menu.Name,
Path: menu.Path,
Redirect: redirect,
Component: menu.Component,
Sort: menu.Sort,
Meta: meta,
Children: make([]types.GetMenuAllResp, 0),
}
}
// 按ParentId将菜单分组字符串键
menuGroups := lo.GroupBy(menus, func(item *AdminMenuStruct) string {
if item.Pid.Valid {
return item.Pid.String
}
return "0"
})
// 递归构建菜单树(字符串键)
var buildMenuTree func(parentId string) []types.GetMenuAllResp
buildMenuTree = func(parentId string) []types.GetMenuAllResp {
children := make([]types.GetMenuAllResp, 0)
childMenus, ok := menuGroups[parentId]
if !ok {
return children
}
// 按Sort排序
sort.Slice(childMenus, func(i, j int) bool {
return childMenus[i].Sort < childMenus[j].Sort
})
for _, childMenu := range childMenus {
if menu, exists := menuMap[childMenu.Id]; exists && childMenu.Status == 1 {
// 递归构建子菜单
menu.Children = buildMenuTree(childMenu.Id)
children = append(children, menu)
}
}
return children
}
// 从根菜单开始构建ParentId为"0"的是根菜单)
menuTree := buildMenuTree("0")
return &menuTree, nil
}

View File

@@ -0,0 +1,30 @@
package admin_menu
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetMenuDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMenuDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuDetailLogic {
return &GetMenuDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMenuDetailLogic) GetMenuDetail(req *types.GetMenuDetailReq) (resp *types.GetMenuDetailResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,109 @@
package admin_menu
import (
"context"
"encoding/json"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetMenuListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMenuListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuListLogic {
return &GetMenuListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMenuListLogic) GetMenuList(req *types.GetMenuListReq) (resp []types.MenuListItem, err error) {
// 构建查询条件
builder := l.svcCtx.AdminMenuModel.SelectBuilder()
// 添加筛选条件
if len(req.Name) > 0 {
builder = builder.Where("name LIKE ?", "%"+req.Name+"%")
}
if len(req.Path) > 0 {
builder = builder.Where("path LIKE ?", "%"+req.Path+"%")
}
if req.Status != -1 {
builder = builder.Where("status = ?", req.Status)
}
if req.Type != "" {
builder = builder.Where("type = ?", req.Type)
}
// 排序但不分页,获取所有符合条件的菜单
builder = builder.OrderBy("sort ASC")
// 获取所有菜单
menus, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err)
}
// 将菜单按ID存入map
menuMap := make(map[string]types.MenuListItem)
for _, menu := range menus {
var meta map[string]interface{}
err := json.Unmarshal([]byte(menu.Meta), &meta)
if err != nil {
logx.Errorf("解析Meta字段失败: %v", err)
meta = make(map[string]interface{})
}
menuType, err := l.svcCtx.DictService.GetDictLabel(l.ctx, "admin_menu_type", menu.Type)
if err != nil {
logx.Errorf("获取菜单类型失败: %v", err)
menuType = ""
}
item := types.MenuListItem{
Id: menu.Id,
Pid: menu.Pid.String,
Name: menu.Name,
Path: menu.Path,
Component: menu.Component,
Redirect: menu.Redirect.String,
Meta: meta,
Status: menu.Status,
Type: menuType,
Sort: menu.Sort,
CreateTime: menu.CreateTime.Format("2006-01-02 15:04:05"),
Children: make([]types.MenuListItem, 0),
}
menuMap[menu.Id] = item
}
// 构建父子关系
for _, menu := range menus {
if menu.Pid.Valid && menu.Pid.String != "0" {
// 找到父菜单
if parent, exists := menuMap[menu.Pid.String]; exists {
// 添加当前菜单到父菜单的子菜单列表
children := append(parent.Children, menuMap[menu.Id])
parent.Children = children
menuMap[menu.Pid.String] = parent
}
}
}
// 提取顶级菜单ParentId为0到响应列表
result := make([]types.MenuListItem, 0)
for _, menu := range menus {
if menu.Pid.Valid && menu.Pid.String == "0" {
result = append(result, menuMap[menu.Id])
}
}
return result, nil
}

View File

@@ -0,0 +1,96 @@
package admin_menu
import (
"context"
"database/sql"
"encoding/json"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateMenuLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateMenuLogic {
return &UpdateMenuLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateMenuLogic) UpdateMenu(req *types.UpdateMenuReq) (resp *types.UpdateMenuResp, err error) {
// 1. 检查菜单是否存在
menu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, id: %d, err: %v", req.Id, err)
}
if menu == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单不存在, id: %d", req.Id)
}
// 2. 将类型标签转换为值
typeValue, err := l.svcCtx.DictService.GetDictValue(l.ctx, "admin_menu_type", req.Type)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单类型无效: %v", err)
}
// 3. 检查父菜单是否存在(如果不是根菜单)
if req.Pid != nil && *req.Pid != "0" {
parentMenu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, *req.Pid)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询父菜单失败, id: %s, err: %v", *req.Pid, err)
}
if parentMenu == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "父菜单不存在, id: %s", *req.Pid)
}
}
// 4. 检查名称和路径是否重复
if req.Name != menu.Name || req.Path != menu.Path {
exists, err := l.svcCtx.AdminMenuModel.FindOneByNamePath(l.ctx, req.Name, req.Path)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err)
}
if exists != nil && exists.Id != req.Id {
return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称或路径已存在"), "菜单名称或路径已存在")
}
}
// 5. 更新菜单信息
menu.Pid = sql.NullString{String: *req.Pid, Valid: req.Pid != nil}
menu.Name = req.Name
menu.Path = req.Path
menu.Component = req.Component
menu.Redirect = sql.NullString{String: req.Redirect, Valid: req.Redirect != ""}
menu.Status = req.Status
menu.Type = typeValue
menu.Sort = req.Sort
// 将Meta转换为JSON字符串
metaJson, err := json.Marshal(req.Meta)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Meta数据格式错误: %v", err)
}
menu.Meta = string(metaJson)
// 6. 保存更新
_, err = l.svcCtx.AdminMenuModel.Update(l.ctx, nil, menu)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新菜单失败, err: %v", err)
}
return &types.UpdateMenuResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,52 @@
package admin_notification
import (
"context"
"database/sql"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminCreateNotificationLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreateNotificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateNotificationLogic {
return &AdminCreateNotificationLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreateNotificationLogic) AdminCreateNotification(req *types.AdminCreateNotificationReq) (resp *types.AdminCreateNotificationResp, err error) {
startDate, _ := time.Parse("2006-01-02", req.StartDate)
endDate, _ := time.Parse("2006-01-02", req.EndDate)
data := &model.GlobalNotifications{
Id: uuid.NewString(),
Title: req.Title,
Content: req.Content,
NotificationPage: req.NotificationPage,
StartDate: sql.NullTime{Time: startDate, Valid: req.StartDate != ""},
EndDate: sql.NullTime{Time: endDate, Valid: req.EndDate != ""},
StartTime: req.StartTime,
EndTime: req.EndTime,
Status: req.Status,
}
result, err := l.svcCtx.GlobalNotificationsModel.Insert(l.ctx, nil, data)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建通知失败, err: %v, req: %+v", err, req)
}
_ = result
return &types.AdminCreateNotificationResp{Id: data.Id}, nil
}

View File

@@ -0,0 +1,38 @@
package admin_notification
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminDeleteNotificationLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeleteNotificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteNotificationLogic {
return &AdminDeleteNotificationLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeleteNotificationLogic) AdminDeleteNotification(req *types.AdminDeleteNotificationReq) (resp *types.AdminDeleteNotificationResp, err error) {
notification, err := l.svcCtx.GlobalNotificationsModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id)
}
err = l.svcCtx.GlobalNotificationsModel.DeleteSoft(l.ctx, nil, notification)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除通知失败, err: %v, id: %d", err, req.Id)
}
return &types.AdminDeleteNotificationResp{Success: true}, nil
}

View File

@@ -0,0 +1,53 @@
package admin_notification
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetNotificationDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetNotificationDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetNotificationDetailLogic {
return &AdminGetNotificationDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetNotificationDetailLogic) AdminGetNotificationDetail(req *types.AdminGetNotificationDetailReq) (resp *types.AdminGetNotificationDetailResp, err error) {
notification, err := l.svcCtx.GlobalNotificationsModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id)
}
resp = &types.AdminGetNotificationDetailResp{
Id: notification.Id,
Title: notification.Title,
Content: notification.Content,
NotificationPage: notification.NotificationPage,
StartDate: "",
StartTime: notification.StartTime,
EndDate: "",
EndTime: notification.EndTime,
Status: notification.Status,
CreateTime: notification.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: notification.UpdateTime.Format("2006-01-02 15:04:05"),
}
if notification.StartDate.Valid {
resp.StartDate = notification.StartDate.Time.Format("2006-01-02")
}
if notification.EndDate.Valid {
resp.EndDate = notification.EndDate.Time.Format("2006-01-02")
}
return resp, nil
}

View File

@@ -0,0 +1,82 @@
package admin_notification
import (
"context"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetNotificationListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetNotificationListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetNotificationListLogic {
return &AdminGetNotificationListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetNotificationListLogic) AdminGetNotificationList(req *types.AdminGetNotificationListReq) (resp *types.AdminGetNotificationListResp, err error) {
builder := l.svcCtx.GlobalNotificationsModel.SelectBuilder()
if req.Title != nil {
builder = builder.Where("title LIKE ?", "%"+*req.Title+"%")
}
if req.NotificationPage != nil {
builder = builder.Where("notification_page = ?", *req.NotificationPage)
}
if req.Status != nil {
builder = builder.Where("status = ?", *req.Status)
}
if req.StartDate != nil {
if t, err := time.Parse("2006-01-02", *req.StartDate); err == nil {
builder = builder.Where("start_date >= ?", t)
}
}
if req.EndDate != nil {
if t, err := time.Parse("2006-01-02", *req.EndDate); err == nil {
builder = builder.Where("end_date <= ?", t)
}
}
list, total, err := l.svcCtx.GlobalNotificationsModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询通知列表失败, err: %v, req: %+v", err, req)
}
items := make([]types.NotificationListItem, 0, len(list))
for _, n := range list {
item := types.NotificationListItem{
Id: n.Id,
Title: n.Title,
NotificationPage: n.NotificationPage,
Content: n.Content,
StartDate: "",
StartTime: n.StartTime,
EndDate: "",
EndTime: n.EndTime,
Status: n.Status,
CreateTime: n.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: n.UpdateTime.Format("2006-01-02 15:04:05"),
}
if n.StartDate.Valid {
item.StartDate = n.StartDate.Time.Format("2006-01-02")
}
if n.EndDate.Valid {
item.EndDate = n.EndDate.Time.Format("2006-01-02")
}
items = append(items, item)
}
resp = &types.AdminGetNotificationListResp{
Total: total,
Items: items,
}
return resp, nil
}

View File

@@ -0,0 +1,66 @@
package admin_notification
import (
"context"
"database/sql"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUpdateNotificationLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateNotificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateNotificationLogic {
return &AdminUpdateNotificationLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateNotificationLogic) AdminUpdateNotification(req *types.AdminUpdateNotificationReq) (resp *types.AdminUpdateNotificationResp, err error) {
notification, err := l.svcCtx.GlobalNotificationsModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id)
}
if req.StartDate != nil {
startDate, _ := time.Parse("2006-01-02", *req.StartDate)
notification.StartDate = sql.NullTime{Time: startDate, Valid: true}
}
if req.EndDate != nil {
endDate, _ := time.Parse("2006-01-02", *req.EndDate)
notification.EndDate = sql.NullTime{Time: endDate, Valid: true}
}
if req.Title != nil {
notification.Title = *req.Title
}
if req.Content != nil {
notification.Content = *req.Content
}
if req.NotificationPage != nil {
notification.NotificationPage = *req.NotificationPage
}
if req.StartTime != nil {
notification.StartTime = *req.StartTime
}
if req.EndTime != nil {
notification.EndTime = *req.EndTime
}
if req.Status != nil {
notification.Status = *req.Status
}
_, err = l.svcCtx.GlobalNotificationsModel.Update(l.ctx, nil, notification)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新通知失败, err: %v, req: %+v", err, req)
}
return &types.AdminUpdateNotificationResp{Success: true}, nil
}

View File

@@ -0,0 +1,82 @@
package admin_order
import (
"context"
"database/sql"
"fmt"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminCreateOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateOrderLogic {
return &AdminCreateOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreateOrderLogic) AdminCreateOrder(req *types.AdminCreateOrderReq) (resp *types.AdminCreateOrderResp, err error) {
// 生成订单号
orderNo := fmt.Sprintf("%dADMIN", time.Now().UnixNano())
// 根据产品名称查询产品ID
builder := l.svcCtx.ProductModel.SelectBuilder()
builder = builder.Where("product_name = ? AND del_state = ?", req.ProductName, 0)
products, err := l.svcCtx.ProductModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 查询产品失败 err: %v", err)
}
if len(products) == 0 {
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("产品不存在: %s", req.ProductName)), "AdminCreateOrder, 查询产品失败 err: %v", err)
}
product := products[0]
// 创建订单对象
order := &model.Order{
Id: uuid.NewString(),
OrderNo: orderNo,
PlatformOrderId: sql.NullString{String: req.PlatformOrderId, Valid: req.PlatformOrderId != ""},
ProductId: product.Id,
PaymentPlatform: req.PaymentPlatform,
PaymentScene: req.PaymentScene,
Amount: req.Amount,
Status: req.Status,
}
// 使用事务处理订单创建
var orderId string
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 插入订单
_, err := l.svcCtx.OrderModel.Insert(ctx, session, order)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 创建订单失败 err: %v", err)
}
orderId = order.Id
return nil
})
if err != nil {
return nil, err
}
return &types.AdminCreateOrderResp{
Id: orderId,
}, nil
}

View File

@@ -0,0 +1,54 @@
package admin_order
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminDeleteOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeleteOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteOrderLogic {
return &AdminDeleteOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeleteOrderLogic) AdminDeleteOrder(req *types.AdminDeleteOrderReq) (resp *types.AdminDeleteOrderResp, err error) {
// 获取订单信息
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 查询订单失败 err: %v", err)
}
// 使用事务删除订单
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 软删除订单
err := l.svcCtx.OrderModel.DeleteSoft(ctx, session, order)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 删除订单失败 err: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
return &types.AdminDeleteOrderResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,128 @@
package admin_order
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetOrderDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetOrderDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderDetailLogic {
return &AdminGetOrderDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderDetailReq) (resp *types.AdminGetOrderDetailResp, err error) {
// 获取订单信息
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询订单失败 err: %v", err)
}
// 获取产品信息
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, order.ProductId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询产品失败 err: %v", err)
}
// 判断是否为代理订单并获取代理处理状态
var isAgentOrder bool
var agentProcessStatus string
agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, order.Id)
if err == nil && agentOrder != nil {
isAgentOrder = true
// 查询代理佣金记录
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx,
l.svcCtx.AgentCommissionModel.SelectBuilder().Where("order_id = ?", order.Id), "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询代理佣金失败 err: %v", err)
}
if len(commissions) > 0 {
agentProcessStatus = "success"
} else {
// 检查订单状态,如果是已支付但无佣金记录,则为待处理或失败
if order.Status == "paid" {
agentProcessStatus = "pending"
} else {
agentProcessStatus = "failed"
}
}
} else {
isAgentOrder = false
agentProcessStatus = "not_agent"
}
// 获取查询状态
var queryState string
builder := l.svcCtx.QueryModel.SelectBuilder().Where("order_id = ?", order.Id).Columns("query_state")
queries, err := l.svcCtx.QueryModel.FindAll(l.ctx, builder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询查询状态失败 err: %v", err)
}
if len(queries) > 0 {
queryState = queries[0].QueryState
} else {
// 查询清理日志
cleanupBuilder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder().
Where("order_id = ?", order.Id).
Where("del_state = ?", globalkey.DelStateNo).
OrderBy("create_time DESC").
Limit(1)
cleanupDetails, err := l.svcCtx.QueryCleanupDetailModel.FindAll(l.ctx, cleanupBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询清理日志失败 err: %v", err)
}
if len(cleanupDetails) > 0 {
queryState = model.QueryStateCleaned
} else {
queryState = ""
}
}
// 构建响应
resp = &types.AdminGetOrderDetailResp{
Id: order.Id,
OrderNo: order.OrderNo,
PlatformOrderId: order.PlatformOrderId.String,
ProductName: product.ProductName,
PaymentPlatform: order.PaymentPlatform,
PaymentScene: order.PaymentScene,
Amount: order.Amount,
Status: order.Status,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"),
QueryState: queryState,
IsAgentOrder: isAgentOrder,
AgentProcessStatus: agentProcessStatus,
}
// 处理可选字段
if order.PayTime.Valid {
resp.PayTime = order.PayTime.Time.Format("2006-01-02 15:04:05")
}
if order.RefundTime.Valid {
resp.RefundTime = order.RefundTime.Time.Format("2006-01-02 15:04:05")
}
return resp, nil
}

View File

@@ -0,0 +1,287 @@
package admin_order
import (
"context"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type AdminGetOrderListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetOrderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderListLogic {
return &AdminGetOrderListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListReq) (resp *types.AdminGetOrderListResp, err error) {
// 构建查询条件
builder := l.svcCtx.OrderModel.SelectBuilder()
if req.OrderNo != "" {
builder = builder.Where("order_no = ?", req.OrderNo)
}
if req.PlatformOrderId != "" {
builder = builder.Where("platform_order_id = ?", req.PlatformOrderId)
}
if req.ProductName != "" {
builder = builder.Where("product_id IN (SELECT id FROM product WHERE product_name LIKE ?)", "%"+req.ProductName+"%")
}
if req.PaymentPlatform != "" {
builder = builder.Where("payment_platform = ?", req.PaymentPlatform)
}
if req.PaymentScene != "" {
builder = builder.Where("payment_scene = ?", req.PaymentScene)
}
if req.Amount > 0 {
builder = builder.Where("amount = ?", req.Amount)
}
if req.Status != "" {
builder = builder.Where("status = ?", req.Status)
}
// 时间范围查询
if req.CreateTimeStart != "" {
builder = builder.Where("create_time >= ?", req.CreateTimeStart)
}
if req.CreateTimeEnd != "" {
builder = builder.Where("create_time <= ?", req.CreateTimeEnd)
}
if req.PayTimeStart != "" {
builder = builder.Where("pay_time >= ?", req.PayTimeStart)
}
if req.PayTimeEnd != "" {
builder = builder.Where("pay_time <= ?", req.PayTimeEnd)
}
if req.RefundTimeStart != "" {
builder = builder.Where("refund_time >= ?", req.RefundTimeStart)
}
if req.RefundTimeEnd != "" {
builder = builder.Where("refund_time <= ?", req.RefundTimeEnd)
}
// 并发获取总数和列表
var total int64
var orders []*model.Order
err = mr.Finish(func() error {
var err error
total, err = l.svcCtx.OrderModel.FindCount(l.ctx, builder, "id")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单总数失败 err: %v", err)
}
return nil
}, func() error {
var err error
orders, err = l.svcCtx.OrderModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单列表失败 err: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
// 并发获取产品信息和查询状态
productMap := make(map[string]string)
queryStateMap := make(map[string]string)
agentOrderMap := make(map[string]bool) // 代理订单映射
agentProcessStatusMap := make(map[string]string) // 代理处理状态映射
var mu sync.Mutex
// 批量获取查询状态
if len(orders) > 0 {
orderIds := make([]string, 0, len(orders))
for _, order := range orders {
orderIds = append(orderIds, order.Id)
}
// 1. 先查询当前查询状态
builder := l.svcCtx.QueryModel.SelectBuilder().
Where(squirrel.Eq{"order_id": orderIds}).
Columns("order_id", "query_state")
queries, err := l.svcCtx.QueryModel.FindAll(l.ctx, builder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询查询状态失败 err: %v", err)
}
// 2. 记录已找到查询状态的订单ID
foundOrderIds := make(map[string]bool)
for _, query := range queries {
queryStateMap[query.OrderId] = query.QueryState
foundOrderIds[query.OrderId] = true
}
// 3. 查找未找到查询状态的订单是否在清理日志中
notFoundOrderIds := make([]string, 0)
for _, orderId := range orderIds {
if !foundOrderIds[orderId] {
notFoundOrderIds = append(notFoundOrderIds, orderId)
}
}
if len(notFoundOrderIds) > 0 {
// 查询清理日志
cleanupBuilder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder().
Where(squirrel.Eq{"order_id": notFoundOrderIds}).
Where("del_state = ?", globalkey.DelStateNo).
OrderBy("create_time DESC")
cleanupDetails, err := l.svcCtx.QueryCleanupDetailModel.FindAll(l.ctx, cleanupBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询清理日志失败 err: %v", err)
}
// 记录已清理的订单状态
for _, detail := range cleanupDetails {
if _, exists := queryStateMap[detail.OrderId]; !exists {
queryStateMap[detail.OrderId] = model.QueryStateCleaned // 使用常量标记为已清除状态
}
}
// 对于既没有查询状态也没有清理记录的订单,不设置状态(保持为空字符串)
for _, orderId := range notFoundOrderIds {
if _, exists := queryStateMap[orderId]; !exists {
queryStateMap[orderId] = "" // 未知状态保持为空字符串
}
}
}
// 批量获取代理订单状态
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx,
l.svcCtx.AgentOrderModel.SelectBuilder().Where(squirrel.Eq{"order_id": orderIds}), "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理订单失败 err: %v", err)
}
// 记录代理订单
for _, agentOrder := range agentOrders {
agentOrderMap[agentOrder.OrderId] = true
}
// 对于代理订单,查询代理处理状态
if len(agentOrders) > 0 {
agentOrderIds := make([]string, 0, len(agentOrders))
for _, agentOrder := range agentOrders {
agentOrderIds = append(agentOrderIds, agentOrder.OrderId)
}
// 查询代理佣金记录
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx,
l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{"order_id": agentOrderIds}), "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理佣金失败 err: %v", err)
}
// 记录有佣金记录的订单为处理成功
processedOrderIds := make(map[string]bool)
for _, commission := range commissions {
processedOrderIds[commission.OrderId] = true
}
// 创建订单状态映射,避免重复查找
orderStatusMap := make(map[string]string)
for _, order := range orders {
orderStatusMap[order.Id] = order.Status
}
// 设置代理处理状态
for _, agentOrder := range agentOrders {
orderId := agentOrder.OrderId
if processedOrderIds[orderId] {
agentProcessStatusMap[orderId] = "success"
} else {
// 检查订单状态,如果是已支付但无佣金记录,则为待处理或失败
if orderStatusMap[orderId] == "paid" {
agentProcessStatusMap[orderId] = "pending"
} else {
agentProcessStatusMap[orderId] = "failed"
}
}
}
}
}
// 并发获取产品信息
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for _, order := range orders {
source <- order
}
}, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) {
order := item.(*model.Order)
// 获取产品信息
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, order.ProductId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询产品信息失败 err: %v", err))
return
}
mu.Lock()
if product != nil {
productMap[product.Id] = product.ProductName
} else {
productMap[order.ProductId] = "" // 产品不存在时设置为空字符串
}
mu.Unlock()
writer.Write(struct{}{})
}, func(pipe <-chan struct{}, cancel func(error)) {
for range pipe {
}
})
if err != nil {
return nil, err
}
// 构建响应
resp = &types.AdminGetOrderListResp{
Total: total,
Items: make([]types.OrderListItem, 0, len(orders)),
}
for _, order := range orders {
item := types.OrderListItem{
Id: order.Id,
OrderNo: order.OrderNo,
PlatformOrderId: order.PlatformOrderId.String,
ProductName: productMap[order.ProductId],
PaymentPlatform: order.PaymentPlatform,
PaymentScene: order.PaymentScene,
Amount: order.Amount,
Status: order.Status,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
QueryState: queryStateMap[order.Id],
}
if order.PayTime.Valid {
item.PayTime = order.PayTime.Time.Format("2006-01-02 15:04:05")
}
if order.RefundTime.Valid {
item.RefundTime = order.RefundTime.Time.Format("2006-01-02 15:04:05")
}
// 设置代理订单相关字段
if agentOrderMap[order.Id] {
item.IsAgentOrder = true
item.AgentProcessStatus = agentProcessStatusMap[order.Id]
} else {
item.IsAgentOrder = false
item.AgentProcessStatus = "not_agent"
}
resp.Items = append(resp.Items, item)
}
return resp, nil
}

View File

@@ -0,0 +1,196 @@
package admin_order
import (
"context"
"database/sql"
"fmt"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/google/uuid"
)
const (
PaymentPlatformAlipay = "alipay"
PaymentPlatformWechat = "wechat"
OrderStatusPaid = "paid"
RefundNoPrefix = "refund-"
)
type AdminRefundOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminRefundOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRefundOrderLogic {
return &AdminRefundOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq) (resp *types.AdminRefundOrderResp, err error) {
// 获取并验证订单
order, err := l.getAndValidateOrder(req.Id, req.RefundAmount)
if err != nil {
return nil, err
}
// 根据支付平台处理退款
switch order.PaymentPlatform {
case PaymentPlatformAlipay:
return l.handleAlipayRefund(order, req)
case PaymentPlatformWechat:
return l.handleWechatRefund(order, req)
default:
return nil, errors.Wrapf(xerr.NewErrMsg("不支持的支付平台"), "AdminRefundOrder, 不支持的支付平台: %s", order.PaymentPlatform)
}
}
// getAndValidateOrder 获取并验证订单信息
func (l *AdminRefundOrderLogic) getAndValidateOrder(orderId string, refundAmount float64) (*model.Order, error) {
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, orderId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminRefundOrder, 查询订单失败 err: %v", err)
}
// 检查订单状态
if order.Status != OrderStatusPaid {
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态: %s", order.Status)
}
// 检查退款金额
if refundAmount > order.Amount {
return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额: %f, 订单金额: %f", refundAmount, order.Amount)
}
return order, nil
}
// handleAlipayRefund 处理支付宝退款
func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用支付宝退款接口
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err)
}
refundNo := l.generateRefundNo(order.OrderNo)
if refundResp.IsSuccess() {
// 支付宝退款成功,创建成功记录
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess)
if err != nil {
return nil, err
}
return &types.AdminRefundOrderResp{
Status: model.OrderStatusRefunded,
RefundNo: refundNo,
Amount: req.RefundAmount,
}, nil
} else {
// 支付宝退款失败,创建失败记录但不更新订单状态
err = l.createRefundRecordOnly(order, req, refundNo, refundResp.TradeNo, model.OrderRefundStatusFailed)
if err != nil {
logx.Errorf("创建退款失败记录时出错: %v", err)
}
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败: %v", refundResp.Msg)), "AdminRefundOrder, 支付宝退款失败")
}
}
// handleWechatRefund 处理微信退款
func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用微信退款接口
err := l.svcCtx.WechatPayService.WeChatRefund(l.ctx, order.OrderNo, req.RefundAmount, order.Amount)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 微信退款失败 err: %v", err)
}
// 微信退款是异步的创建pending状态的退款记录
refundNo := l.generateRefundNo(order.OrderNo)
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, "", model.OrderStatusRefunding, model.OrderRefundStatusPending)
if err != nil {
return nil, err
}
return &types.AdminRefundOrderResp{
Status: model.OrderRefundStatusPending,
RefundNo: refundNo,
Amount: req.RefundAmount,
}, nil
}
// createRefundRecordAndUpdateOrder 创建退款记录并更新订单状态
func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, orderStatus, refundStatus string) error {
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建退款记录
refund := &model.OrderRefund{
Id: uuid.NewString(),
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, // 使用传入的状态,不再硬编码
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
// 更新订单状态
order.Status = orderStatus
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
}
// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况)
func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error {
refund := &model.OrderRefund{
Id: uuid.NewString(),
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
_, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund)
if err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
return nil
}
// generateRefundNo 生成退款单号
func (l *AdminRefundOrderLogic) generateRefundNo(orderNo string) string {
return fmt.Sprintf("%s%s", RefundNoPrefix, orderNo)
}
// createNullString 创建 sql.NullString
func (l *AdminRefundOrderLogic) createNullString(value string) sql.NullString {
return sql.NullString{
String: value,
Valid: value != "",
}
}

View File

@@ -0,0 +1,63 @@
package admin_order
import (
"context"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminRetryAgentProcessLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminRetryAgentProcessLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRetryAgentProcessLogic {
return &AdminRetryAgentProcessLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminRetryAgentProcessLogic) AdminRetryAgentProcess(req *types.AdminRetryAgentProcessReq) (resp *types.AdminRetryAgentProcessResp, err error) {
// 新系统查询订单并调用AgentProcess重新处理
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
if err != nil {
return &types.AdminRetryAgentProcessResp{
Status: "failed",
Message: "订单不存在",
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
err = l.svcCtx.AgentService.AgentProcess(l.ctx, order)
if err != nil {
// 检查是否是"已经处理"的错误
if err.Error() == "代理处理已经成功,无需重新执行" {
return &types.AdminRetryAgentProcessResp{
Status: "already_processed",
Message: "代理处理已经成功,无需重新执行",
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
// 其他错误
logx.Errorf("重新执行代理处理失败订单ID: %d, 错误: %v", req.Id, err)
return &types.AdminRetryAgentProcessResp{
Status: "failed",
Message: err.Error(),
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
// 执行成功
return &types.AdminRetryAgentProcessResp{
Status: "success",
Message: "代理处理重新执行成功",
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}

View File

@@ -0,0 +1,88 @@
package admin_order
import (
"context"
"database/sql"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminUpdateOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateOrderLogic {
return &AdminUpdateOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateOrderLogic) AdminUpdateOrder(req *types.AdminUpdateOrderReq) (resp *types.AdminUpdateOrderResp, err error) {
// 获取原订单信息
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 查询订单失败 err: %v", err)
}
// 更新订单字段
if req.OrderNo != nil {
order.OrderNo = *req.OrderNo
}
if req.PlatformOrderId != nil {
order.PlatformOrderId = sql.NullString{String: *req.PlatformOrderId, Valid: true}
}
if req.PaymentPlatform != nil {
order.PaymentPlatform = *req.PaymentPlatform
}
if req.PaymentScene != nil {
order.PaymentScene = *req.PaymentScene
}
if req.Amount != nil {
order.Amount = *req.Amount
}
if req.Status != nil {
order.Status = *req.Status
}
if req.PayTime != nil {
payTime, err := time.Parse("2006-01-02 15:04:05", *req.PayTime)
if err == nil {
order.PayTime = sql.NullTime{Time: payTime, Valid: true}
}
}
if req.RefundTime != nil {
refundTime, err := time.Parse("2006-01-02 15:04:05", *req.RefundTime)
if err == nil {
order.RefundTime = sql.NullTime{Time: refundTime, Valid: true}
}
}
// 使用事务更新订单
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 更新订单
_, err := l.svcCtx.OrderModel.Update(ctx, session, order)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 更新订单失败 err: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
return &types.AdminUpdateOrderResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,56 @@
package admin_platform_user
import (
"context"
"database/sql"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/google/uuid"
)
type AdminCreatePlatformUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreatePlatformUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreatePlatformUserLogic {
return &AdminCreatePlatformUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreatePlatformUserLogic) AdminCreatePlatformUser(req *types.AdminCreatePlatformUserReq) (resp *types.AdminCreatePlatformUserResp, err error) {
// 校验手机号唯一性
_, err = l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: req.Mobile, Valid: req.Mobile != ""})
if err == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机号已存在: %s", req.Mobile)
}
if err != model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询手机号失败: %v", err)
}
user := &model.User{
Id: uuid.NewString(),
Mobile: sql.NullString{String: req.Mobile, Valid: req.Mobile != ""},
Password: sql.NullString{String: req.Password, Valid: req.Password != ""},
Nickname: sql.NullString{String: req.Nickname, Valid: req.Nickname != ""},
Info: req.Info,
Inside: req.Inside,
}
result, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
}
_ = result
resp = &types.AdminCreatePlatformUserResp{Id: user.Id}
return resp, nil
}

View File

@@ -0,0 +1,43 @@
package admin_platform_user
import (
"context"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminDeletePlatformUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeletePlatformUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeletePlatformUserLogic {
return &AdminDeletePlatformUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeletePlatformUserLogic) AdminDeletePlatformUser(req *types.AdminDeletePlatformUserReq) (resp *types.AdminDeletePlatformUserResp, err error) {
user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %d, err: %v", req.Id, err)
}
user.DelState = 1
user.DeleteTime.Time = time.Now()
user.DeleteTime.Valid = true
err = l.svcCtx.UserModel.DeleteSoft(l.ctx, nil, user)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "软删除用户失败: %v", err)
}
resp = &types.AdminDeletePlatformUserResp{Success: true}
return resp, nil
}

View File

@@ -0,0 +1,53 @@
package admin_platform_user
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetPlatformUserDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetPlatformUserDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetPlatformUserDetailLogic {
return &AdminGetPlatformUserDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetPlatformUserDetailLogic) AdminGetPlatformUserDetail(req *types.AdminGetPlatformUserDetailReq) (resp *types.AdminGetPlatformUserDetailResp, err error) {
user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %d, err: %v", req.Id, err)
}
key := l.svcCtx.Config.Encrypt.SecretKey
DecryptMobile, err := crypto.DecryptMobile(user.Mobile.String, key)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密手机号失败: %v", err)
}
// 查询平台类型取第一个user_auth
resp = &types.AdminGetPlatformUserDetailResp{
Id: user.Id,
Mobile: DecryptMobile,
Nickname: "",
Info: user.Info,
Inside: user.Inside,
CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"),
}
if user.Nickname.Valid {
resp.Nickname = user.Nickname.String
}
return resp, nil
}

View File

@@ -0,0 +1,88 @@
package admin_platform_user
import (
"context"
"database/sql"
"fmt"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetPlatformUserListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetPlatformUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetPlatformUserListLogic {
return &AdminGetPlatformUserListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) {
builder := l.svcCtx.UserModel.SelectBuilder()
if req.Mobile != "" {
builder = builder.Where("mobile = ?", req.Mobile)
}
if req.Nickname != "" {
builder = builder.Where("nickname = ?", req.Nickname)
}
if req.Inside != 0 {
builder = builder.Where("inside = ?", req.Inside)
}
if req.CreateTimeStart != "" {
builder = builder.Where("create_time >= ?", req.CreateTimeStart)
}
if req.CreateTimeEnd != "" {
builder = builder.Where("create_time <= ?", req.CreateTimeEnd)
}
orderBy := "id DESC"
if req.OrderBy != "" && req.OrderType != "" {
orderBy = fmt.Sprintf("%s %s", req.OrderBy, req.OrderType)
}
users, total, err := l.svcCtx.UserModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, orderBy)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户分页失败: %v", err)
}
var items []types.PlatformUserListItem
secretKey := l.svcCtx.Config.Encrypt.SecretKey
for _, user := range users {
mobile := user.Mobile
if mobile.Valid {
encryptedMobile, err := crypto.DecryptMobile(mobile.String, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 解密手机号失败: %+v", err)
}
mobile = sql.NullString{String: encryptedMobile, Valid: true}
}
itemData := types.PlatformUserListItem{
Id: user.Id,
Mobile: mobile.String,
Nickname: "",
Info: user.Info,
Inside: user.Inside,
CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"),
}
if user.Nickname.Valid {
itemData.Nickname = user.Nickname.String
}
items = append(items, itemData)
}
resp = &types.AdminGetPlatformUserListResp{
Total: total,
Items: items,
}
return resp, nil
}

View File

@@ -0,0 +1,64 @@
package admin_platform_user
import (
"context"
"database/sql"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUpdatePlatformUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdatePlatformUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdatePlatformUserLogic {
return &AdminUpdatePlatformUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdatePlatformUserLogic) AdminUpdatePlatformUser(req *types.AdminUpdatePlatformUserReq) (resp *types.AdminUpdatePlatformUserResp, err error) {
user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %d, err: %v", req.Id, err)
}
if req.Mobile != nil {
key := l.svcCtx.Config.Encrypt.SecretKey
EncryptMobile, err := crypto.EncryptMobile(*req.Mobile, key)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
user.Mobile = sql.NullString{String: EncryptMobile, Valid: true}
}
if req.Nickname != nil {
user.Nickname = sql.NullString{String: *req.Nickname, Valid: *req.Nickname != ""}
}
if req.Info != nil {
user.Info = *req.Info
}
if req.Inside != nil {
if *req.Inside != 1 && *req.Inside != 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "内部用户状态错误: %d", *req.Inside)
}
user.Inside = *req.Inside
}
if req.Password != nil {
user.Password = sql.NullString{String: *req.Password, Valid: *req.Password != ""}
}
_, err = l.svcCtx.UserModel.Update(l.ctx, nil, user)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新用户失败: %v", err)
}
resp = &types.AdminUpdatePlatformUserResp{Success: true}
return resp, nil
}

View File

@@ -0,0 +1,79 @@
package admin_product
import (
"context"
"database/sql"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminCreateProductLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateProductLogic {
return &AdminCreateProductLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProductReq) (resp *types.AdminCreateProductResp, err error) {
// 1. 数据转换
data := &model.Product{
Id: uuid.NewString(),
ProductName: req.ProductName,
ProductEn: req.ProductEn,
Description: req.Description,
Notes: sql.NullString{String: req.Notes, Valid: req.Notes != ""},
CostPrice: req.CostPrice,
SellPrice: req.SellPrice,
}
// 2. 数据库操作(使用事务确保产品表和代理产品配置表同步)
var productId string
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 2.1 插入产品
_, err := l.svcCtx.ProductModel.Insert(ctx, session, data)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建产品失败, err: %v, req: %+v", err, req)
}
productId = data.Id
// 2.2 同步创建代理产品配置(使用默认值)
agentProductConfig := &model.AgentProductConfig{
Id: uuid.NewString(),
ProductId: productId,
BasePrice: 0.00, // 默认基础底价
SystemMaxPrice: 9999.99, // 默认系统价格上限
PriceThreshold: sql.NullFloat64{Valid: false}, // 默认不设阈值
PriceFeeRate: sql.NullFloat64{Valid: false}, // 默认不收费
}
_, err = l.svcCtx.AgentProductConfigModel.Insert(ctx, session, agentProductConfig)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建代理产品配置失败, err: %v, productId: %s", err, productId)
}
return nil
})
if err != nil {
return nil, err
}
// 3. 返回结果
return &types.AdminCreateProductResp{Id: productId}, nil
}

View File

@@ -0,0 +1,67 @@
package admin_product
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminDeleteProductLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteProductLogic {
return &AdminDeleteProductLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProductReq) (resp *types.AdminDeleteProductResp, err error) {
// 1. 查询记录是否存在
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找产品失败, err: %v, id: %d", err, req.Id)
}
// 2. 执行软删除(使用事务确保产品表和代理产品配置表同步)
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 2.1 软删除产品
err = l.svcCtx.ProductModel.DeleteSoft(ctx, session, record)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除产品失败, err: %v, id: %d", err, req.Id)
}
// 2.2 同步软删除代理产品配置
agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, req.Id)
if err != nil {
// 如果代理产品配置不存在,记录日志但不影响主流程
l.Infof("同步删除代理产品配置失败:代理产品配置不存在, productId: %d, err: %v", req.Id, err)
} else {
err = l.svcCtx.AgentProductConfigModel.DeleteSoft(ctx, session, agentProductConfig)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"同步删除代理产品配置失败, err: %v, productId: %d", err, req.Id)
}
}
return nil
})
if err != nil {
return nil, err
}
// 3. 返回结果
return &types.AdminDeleteProductResp{Success: true}, nil
}

View File

@@ -0,0 +1,49 @@
package admin_product
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetProductDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetProductDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductDetailLogic {
return &AdminGetProductDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetProductDetailLogic) AdminGetProductDetail(req *types.AdminGetProductDetailReq) (resp *types.AdminGetProductDetailResp, err error) {
// 1. 查询记录
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找产品失败, err: %v, id: %d", err, req.Id)
}
// 2. 构建响应
resp = &types.AdminGetProductDetailResp{
Id: record.Id,
ProductName: record.ProductName,
ProductEn: record.ProductEn,
Description: record.Description,
Notes: record.Notes.String,
CostPrice: record.CostPrice,
SellPrice: record.SellPrice,
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
}
return resp, nil
}

View File

@@ -0,0 +1,119 @@
package admin_product
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type AdminGetProductFeatureListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetProductFeatureListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductFeatureListLogic {
return &AdminGetProductFeatureListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetProductFeatureListLogic) AdminGetProductFeatureList(req *types.AdminGetProductFeatureListReq) (resp *[]types.AdminGetProductFeatureListResp, err error) {
// 1. 构建查询条件
builder := l.svcCtx.ProductFeatureModel.SelectBuilder().
Where("product_id = ?", req.ProductId)
// 2. 执行查询
list, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "sort ASC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询产品功能列表失败, err: %v, product_id: %d", err, req.ProductId)
}
// 3. 获取所有功能ID
featureIds := make([]string, 0, len(list))
for _, item := range list {
featureIds = append(featureIds, item.FeatureId)
}
// 4. 并发查询功能详情
type featureResult struct {
feature *model.Feature
err error
}
results := make([]featureResult, len(featureIds))
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for i, id := range featureIds {
source <- struct {
index int
id string
}{i, id}
}
}, func(item interface{}, writer mr.Writer[featureResult], cancel func(error)) {
data := item.(struct {
index int
id string
})
feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, data.id)
writer.Write(featureResult{
feature: feature,
err: err,
})
}, func(pipe <-chan featureResult, cancel func(error)) {
for result := range pipe {
if result.err != nil {
l.Logger.Errorf("查询功能详情失败, feature_id: %s, err: %v", result.feature.Id, result.err)
continue
}
results = append(results, result)
}
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"并发查询功能详情失败, err: %v", err)
}
// 5. 构建功能ID到详情的映射
featureMap := make(map[string]*model.Feature)
for _, result := range results {
if result.feature != nil {
featureMap[result.feature.Id] = result.feature
}
}
// 6. 构建响应列表
items := make([]types.AdminGetProductFeatureListResp, 0, len(list))
for _, item := range list {
feature, exists := featureMap[item.FeatureId]
if !exists {
continue // 跳过不存在的功能
}
listItem := types.AdminGetProductFeatureListResp{
Id: item.Id,
ProductId: item.ProductId,
FeatureId: item.FeatureId,
ApiId: feature.ApiId,
Name: feature.Name,
Sort: item.Sort,
Enable: item.Enable,
IsImportant: item.IsImportant,
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
}
items = append(items, listItem)
}
// 7. 返回结果
return &items, nil
}

View File

@@ -0,0 +1,69 @@
package admin_product
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetProductListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetProductListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductListLogic {
return &AdminGetProductListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetProductListLogic) AdminGetProductList(req *types.AdminGetProductListReq) (resp *types.AdminGetProductListResp, err error) {
// 1. 构建查询条件
builder := l.svcCtx.ProductModel.SelectBuilder()
// 2. 添加查询条件
if req.ProductName != nil && *req.ProductName != "" {
builder = builder.Where("product_name LIKE ?", "%"+*req.ProductName+"%")
}
if req.ProductEn != nil && *req.ProductEn != "" {
builder = builder.Where("product_en LIKE ?", "%"+*req.ProductEn+"%")
}
// 3. 执行分页查询
list, total, err := l.svcCtx.ProductModel.FindPageListByPageWithTotal(
l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询产品列表失败, err: %v, req: %+v", err, req)
}
// 4. 构建响应列表
items := make([]types.ProductListItem, 0, len(list))
for _, item := range list {
listItem := types.ProductListItem{
Id: item.Id,
ProductName: item.ProductName,
ProductEn: item.ProductEn,
Description: item.Description,
Notes: item.Notes.String,
CostPrice: item.CostPrice,
SellPrice: item.SellPrice,
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
}
items = append(items, listItem)
}
// 5. 返回结果
return &types.AdminGetProductListResp{
Total: total,
Items: items,
}, nil
}

View File

@@ -0,0 +1,161 @@
package admin_product
import (
"context"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/google/uuid"
)
type AdminUpdateProductFeaturesLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateProductFeaturesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateProductFeaturesLogic {
return &AdminUpdateProductFeaturesLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateProductFeaturesLogic) AdminUpdateProductFeatures(req *types.AdminUpdateProductFeaturesReq) (resp *types.AdminUpdateProductFeaturesResp, err error) {
// 1. 查询现有关联
builder := l.svcCtx.ProductFeatureModel.SelectBuilder().
Where("product_id = ?", req.ProductId)
existingList, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询现有产品功能关联失败, err: %v, product_id: %d", err, req.ProductId)
}
// 2. 构建现有关联的映射
existingMap := make(map[string]*model.ProductFeature)
for _, item := range existingList {
existingMap[item.FeatureId] = item
}
// 3. 构建新关联的映射
newMap := make(map[string]*types.ProductFeatureItem)
for _, item := range req.Features {
newMap[item.FeatureId] = &item
}
// 4. 在事务中执行更新操作
err = l.svcCtx.ProductFeatureModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 4.1 处理需要删除的关联
var mu sync.Mutex
var deleteIds []string
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for featureId, existing := range existingMap {
if _, exists := newMap[featureId]; !exists {
source <- existing.Id
}
}
}, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) {
id := item.(string)
mu.Lock()
deleteIds = append(deleteIds, id)
mu.Unlock()
}, func(pipe <-chan struct{}, cancel func(error)) {
// 等待所有ID收集完成
})
if err != nil {
return errors.Wrapf(err, "收集待删除ID失败")
}
// 批量删除
if len(deleteIds) > 0 {
for _, id := range deleteIds {
err = l.svcCtx.ProductFeatureModel.Delete(ctx, session, id)
if err != nil {
return errors.Wrapf(err, "删除产品功能关联失败, product_id: %d, id: %d",
req.ProductId, id)
}
}
}
// 4.2 并发处理需要新增或更新的关联
var updateErr error
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for featureId, newItem := range newMap {
source <- struct {
featureId string
newItem *types.ProductFeatureItem
existing *model.ProductFeature
}{
featureId: featureId,
newItem: newItem,
existing: existingMap[featureId],
}
}
}, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) {
data := item.(struct {
featureId string
newItem *types.ProductFeatureItem
existing *model.ProductFeature
})
if data.existing != nil {
// 更新现有关联
data.existing.Sort = data.newItem.Sort
data.existing.Enable = data.newItem.Enable
data.existing.IsImportant = data.newItem.IsImportant
_, err = l.svcCtx.ProductFeatureModel.Update(ctx, session, data.existing)
if err != nil {
updateErr = errors.Wrapf(err, "更新产品功能关联失败, product_id: %d, feature_id: %d",
req.ProductId, data.featureId)
cancel(updateErr)
return
}
} else {
// 新增关联
newFeature := &model.ProductFeature{
Id: uuid.NewString(),
ProductId: req.ProductId,
FeatureId: data.featureId,
Sort: data.newItem.Sort,
Enable: data.newItem.Enable,
IsImportant: data.newItem.IsImportant,
}
_, err = l.svcCtx.ProductFeatureModel.Insert(ctx, session, newFeature)
if err != nil {
updateErr = errors.Wrapf(err, "新增产品功能关联失败, product_id: %d, feature_id: %d",
req.ProductId, data.featureId)
cancel(updateErr)
return
}
}
}, func(pipe <-chan struct{}, cancel func(error)) {
// 等待所有更新完成
})
if err != nil {
return errors.Wrapf(err, "并发更新产品功能关联失败")
}
if updateErr != nil {
return updateErr
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"更新产品功能关联失败, err: %v, req: %+v", err, req)
}
// 5. 返回结果
return &types.AdminUpdateProductFeaturesResp{Success: true}, nil
}

View File

@@ -0,0 +1,75 @@
package admin_product
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"database/sql"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminUpdateProductLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateProductLogic {
return &AdminUpdateProductLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateProductLogic) AdminUpdateProduct(req *types.AdminUpdateProductReq) (resp *types.AdminUpdateProductResp, err error) {
// 1. 查询记录是否存在
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找产品失败, err: %v, id: %d", err, req.Id)
}
// 2. 更新字段
if req.ProductName != nil {
record.ProductName = *req.ProductName
}
if req.ProductEn != nil {
record.ProductEn = *req.ProductEn
}
if req.Description != nil {
record.Description = *req.Description
}
if req.Notes != nil {
record.Notes = sql.NullString{String: *req.Notes, Valid: *req.Notes != ""}
}
if req.CostPrice != nil {
record.CostPrice = *req.CostPrice
}
if req.SellPrice != nil {
record.SellPrice = *req.SellPrice
}
// 3. 执行更新操作(使用事务确保产品表和代理产品配置表同步)
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 3.1 更新产品
_, err = l.svcCtx.ProductModel.Update(ctx, session, record)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"更新产品失败, err: %v, req: %+v", err, req)
}
return nil
})
if err != nil {
return nil, err
}
// 4. 返回结果
return &types.AdminUpdateProductResp{Success: true}, nil
}

View File

@@ -0,0 +1,62 @@
package admin_query
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetQueryCleanupConfigListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetQueryCleanupConfigListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryCleanupConfigListLogic {
return &AdminGetQueryCleanupConfigListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetQueryCleanupConfigListLogic) AdminGetQueryCleanupConfigList(req *types.AdminGetQueryCleanupConfigListReq) (resp *types.AdminGetQueryCleanupConfigListResp, err error) {
// 构建查询条件
builder := l.svcCtx.QueryCleanupConfigModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
if req.Status > 0 {
builder = builder.Where("status = ?", req.Status)
}
// 查询配置列表
configs, err := l.svcCtx.QueryCleanupConfigModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理配置列表失败 err: %v", err)
}
// 构建响应
resp = &types.AdminGetQueryCleanupConfigListResp{
Items: make([]types.QueryCleanupConfigItem, 0, len(configs)),
}
for _, config := range configs {
item := types.QueryCleanupConfigItem{
Id: config.Id,
ConfigKey: config.ConfigKey,
ConfigValue: config.ConfigValue,
ConfigDesc: config.ConfigDesc,
Status: config.Status,
CreateTime: config.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: config.UpdateTime.Format("2006-01-02 15:04:05"),
}
resp.Items = append(resp.Items, item)
}
return resp, nil
}

View File

@@ -0,0 +1,126 @@
package admin_query
import (
"context"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type AdminGetQueryCleanupDetailListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetQueryCleanupDetailListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryCleanupDetailListLogic {
return &AdminGetQueryCleanupDetailListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetQueryCleanupDetailListLogic) AdminGetQueryCleanupDetailList(req *types.AdminGetQueryCleanupDetailListReq) (resp *types.AdminGetQueryCleanupDetailListResp, err error) {
// 1. 验证清理日志是否存在
_, err = l.svcCtx.QueryCleanupLogModel.FindOne(l.ctx, req.LogId)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "清理日志不存在, log_id: %d", req.LogId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理日志失败, log_id: %d, err: %v", req.LogId, err)
}
// 2. 构建查询条件
builder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder().
Where("cleanup_log_id = ?", req.LogId).
Where("del_state = ?", globalkey.DelStateNo)
// 3. 并发获取总数和列表
var total int64
var details []*model.QueryCleanupDetail
err = mr.Finish(func() error {
var err error
total, err = l.svcCtx.QueryCleanupDetailModel.FindCount(l.ctx, builder, "id")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理详情总数失败 err: %v", err)
}
return nil
}, func() error {
var err error
details, err = l.svcCtx.QueryCleanupDetailModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理详情列表失败 err: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
// 4. 获取所有产品ID
productIds := make([]string, 0, len(details))
for _, detail := range details {
productIds = append(productIds, detail.ProductId)
}
// 5. 并发获取产品信息
productMap := make(map[string]string)
var mu sync.Mutex
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for _, productId := range productIds {
source <- productId
}
}, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) {
productId := item.(string)
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, productId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品信息失败, product_id: %s, err: %v", productId, err))
return
}
mu.Lock()
if product != nil {
productMap[productId] = product.ProductName
} else {
productMap[productId] = "" // 产品不存在时设置为空字符串
}
mu.Unlock()
writer.Write(struct{}{})
}, func(pipe <-chan struct{}, cancel func(error)) {
for range pipe {
}
})
if err != nil {
return nil, err
}
// 6. 构建响应
resp = &types.AdminGetQueryCleanupDetailListResp{
Total: total,
Items: make([]types.QueryCleanupDetailItem, 0, len(details)),
}
for _, detail := range details {
item := types.QueryCleanupDetailItem{
Id: detail.Id,
CleanupLogId: detail.CleanupLogId,
QueryId: detail.QueryId,
OrderId: detail.OrderId,
UserId: detail.UserId,
ProductName: productMap[detail.ProductId],
QueryState: detail.QueryState,
CreateTimeOld: detail.CreateTimeOld.Format("2006-01-02 15:04:05"),
CreateTime: detail.CreateTime.Format("2006-01-02 15:04:05"),
}
resp.Items = append(resp.Items, item)
}
return resp, nil
}

View File

@@ -0,0 +1,88 @@
package admin_query
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type AdminGetQueryCleanupLogListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetQueryCleanupLogListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryCleanupLogListLogic {
return &AdminGetQueryCleanupLogListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetQueryCleanupLogListLogic) AdminGetQueryCleanupLogList(req *types.AdminGetQueryCleanupLogListReq) (resp *types.AdminGetQueryCleanupLogListResp, err error) {
// 构建查询条件
builder := l.svcCtx.QueryCleanupLogModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
if req.Status > 0 {
builder = builder.Where("status = ?", req.Status)
}
if req.StartTime != "" {
builder = builder.Where("cleanup_time >= ?", req.StartTime)
}
if req.EndTime != "" {
builder = builder.Where("cleanup_time <= ?", req.EndTime)
}
// 并发获取总数和列表
var total int64
var logs []*model.QueryCleanupLog
err = mr.Finish(func() error {
var err error
total, err = l.svcCtx.QueryCleanupLogModel.FindCount(l.ctx, builder, "id")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理日志总数失败 err: %v", err)
}
return nil
}, func() error {
var err error
logs, err = l.svcCtx.QueryCleanupLogModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理日志列表失败 err: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
// 构建响应
resp = &types.AdminGetQueryCleanupLogListResp{
Total: total,
Items: make([]types.QueryCleanupLogItem, 0, len(logs)),
}
for _, log := range logs {
item := types.QueryCleanupLogItem{
Id: log.Id,
CleanupTime: log.CleanupTime.Format("2006-01-02 15:04:05"),
CleanupBefore: log.CleanupBefore.Format("2006-01-02 15:04:05"),
Status: log.Status,
AffectedRows: log.AffectedRows,
ErrorMsg: log.ErrorMsg.String,
Remark: log.Remark.String,
CreateTime: log.CreateTime.Format("2006-01-02 15:04:05"),
}
resp.Items = append(resp.Items, item)
}
return resp, nil
}

View File

@@ -0,0 +1,189 @@
package admin_query
import (
"context"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"jnc-server/pkg/lzkit/lzUtils"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetQueryDetailByOrderIdLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetQueryDetailByOrderIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryDetailByOrderIdLogic {
return &AdminGetQueryDetailByOrderIdLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetQueryDetailByOrderIdLogic) AdminGetQueryDetailByOrderId(req *types.AdminGetQueryDetailByOrderIdReq) (resp *types.AdminGetQueryDetailByOrderIdResp, err error) {
// 获取报告信息
queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
}
var query types.AdminGetQueryDetailByOrderIdResp
query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05")
query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05")
// 解密查询数据
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %+v", err)
}
processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key)
if processParamsErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr)
}
processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key)
if processErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr)
}
updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData)
if updateFeatureAndProductFeatureErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr)
}
// 复制报告数据
err = copier.Copy(&query, queryModel)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err)
}
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
}
query.ProductName = product.ProductName
return &types.AdminGetQueryDetailByOrderIdResp{
Id: query.Id,
OrderId: query.OrderId,
UserId: query.UserId,
ProductName: query.ProductName,
QueryParams: query.QueryParams,
QueryData: query.QueryData,
CreateTime: query.CreateTime,
UpdateTime: query.UpdateTime,
QueryState: query.QueryState,
}, nil
}
// ProcessQueryData 解密和反序列化 QueryData
func ProcessQueryData(queryData sql.NullString, target *[]types.AdminQueryItem, key []byte) error {
queryDataStr := lzUtils.NullStringToString(queryData)
if queryDataStr == "" {
return nil
}
// 解密数据
decryptedData, decryptErr := crypto.AesDecrypt(queryDataStr, key)
if decryptErr != nil {
return decryptErr
}
// 解析 JSON 数组
var decryptedArray []map[string]interface{}
unmarshalErr := json.Unmarshal(decryptedData, &decryptedArray)
if unmarshalErr != nil {
return unmarshalErr
}
// 确保 target 具有正确的长度
if len(*target) == 0 {
*target = make([]types.AdminQueryItem, len(decryptedArray))
}
// 填充解密后的数据到 target
for i := 0; i < len(decryptedArray); i++ {
// 直接填充解密数据到 Data 字段
(*target)[i].Data = decryptedArray[i]
}
return nil
}
// ProcessQueryParams解密和反序列化 QueryParams
func ProcessQueryParams(QueryParams string, target *map[string]interface{}, key []byte) error {
// 解密 QueryParams
decryptedData, decryptErr := crypto.AesDecrypt(QueryParams, key)
if decryptErr != nil {
return decryptErr
}
// 反序列化解密后的数据
unmarshalErr := json.Unmarshal(decryptedData, target)
if unmarshalErr != nil {
return unmarshalErr
}
return nil
}
func (l *AdminGetQueryDetailByOrderIdLogic) UpdateFeatureAndProductFeature(productID string, target *[]types.AdminQueryItem) error {
// 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引
for i := len(*target) - 1; i >= 0; i-- {
queryItem := &(*target)[i]
// 确保 Data 为 map 类型
data, ok := queryItem.Data.(map[string]interface{})
if !ok {
return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型")
}
// 从 Data 中获取 apiID
apiID, ok := data["apiID"].(string)
if !ok {
return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型")
}
// 查询 Feature
feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID)
if err != nil {
// 如果 Feature 查不到,也要删除当前 QueryItem
*target = append((*target)[:i], (*target)[i+1:]...)
continue
}
// 查询 ProductFeatureModel
builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID)
productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "")
if err != nil {
return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err)
}
// 遍历 productFeatures找到与 feature.ID 关联且 enable == 1 的项
var featureData map[string]interface{}
sort := 0
for _, pf := range productFeatures {
if pf.FeatureId == feature.Id { // 确保和 Feature 关联
sort = int(pf.Sort)
break // 找到第一个符合条件的就退出循环
}
}
featureData = map[string]interface{}{
"featureName": feature.Name,
"sort": sort,
}
// 更新 queryItem 的 Feature 字段(不是数组)
queryItem.Feature = featureData
}
return nil
}

View File

@@ -0,0 +1,63 @@
package admin_query
import (
"context"
"time"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminUpdateQueryCleanupConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateQueryCleanupConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateQueryCleanupConfigLogic {
return &AdminUpdateQueryCleanupConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateQueryCleanupConfigLogic) AdminUpdateQueryCleanupConfig(req *types.AdminUpdateQueryCleanupConfigReq) (resp *types.AdminUpdateQueryCleanupConfigResp, err error) {
// 使用事务处理更新操作
err = l.svcCtx.QueryCleanupConfigModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 1. 查询配置是否存在
config, err := l.svcCtx.QueryCleanupConfigModel.FindOne(ctx, req.Id)
if err != nil {
if err == model.ErrNotFound {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "配置不存在, id: %d", req.Id)
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询配置失败, id: %d, err: %v", req.Id, err)
}
// 2. 更新配置
config.ConfigValue = req.ConfigValue
config.Status = req.Status
config.UpdateTime = time.Now()
_, err = l.svcCtx.QueryCleanupConfigModel.Update(ctx, session, config)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新配置失败, id: %d, err: %v", req.Id, err)
}
return nil
})
if err != nil {
return nil, err
}
return &types.AdminUpdateQueryCleanupConfigResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,83 @@
package admin_role
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/google/uuid"
)
type CreateRoleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateRoleLogic {
return &CreateRoleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateRoleLogic) CreateRole(req *types.CreateRoleReq) (resp *types.CreateRoleResp, err error) {
// 检查角色编码是否已存在
roleModel, err := l.svcCtx.AdminRoleModel.FindOneByRoleCode(l.ctx, req.RoleCode)
if err != nil && err != model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建角色失败: %v", err)
}
if roleModel != nil && roleModel.RoleName == req.RoleName {
return nil, errors.Wrapf(xerr.NewErrMsg("角色名称已存在"), "创建角色失败, 角色名称已存在: %v", err)
}
// 创建角色
role := &model.AdminRole{
Id: uuid.NewString(),
RoleName: req.RoleName,
RoleCode: req.RoleCode,
Description: req.Description,
Status: req.Status,
Sort: req.Sort,
}
var roleId string
// 使用事务创建角色和关联菜单
err = l.svcCtx.AdminRoleModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建角色
_, err := l.svcCtx.AdminRoleModel.Insert(ctx, session, role)
if err != nil {
return errors.New("插入新角色失败")
}
roleId = role.Id
// 创建角色菜单关联
if len(req.MenuIds) > 0 {
for _, menuId := range req.MenuIds {
roleMenu := &model.AdminRoleMenu{
Id: uuid.NewString(),
RoleId: roleId,
MenuId: menuId,
}
_, err = l.svcCtx.AdminRoleMenuModel.Insert(ctx, session, roleMenu)
if err != nil {
return errors.New("插入角色菜单关联失败")
}
}
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建角色失败: %v", err)
}
return &types.CreateRoleResp{
Id: roleId,
}, nil
}

View File

@@ -0,0 +1,84 @@
package admin_role
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type DeleteRoleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteRoleLogic {
return &DeleteRoleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteRoleLogic) DeleteRole(req *types.DeleteRoleReq) (resp *types.DeleteRoleResp, err error) {
// 检查角色是否存在
_, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("角色不存在"), "删除角色失败, 角色不存在err: %v", err)
}
return nil, err
}
// 使用事务删除角色和关联数据
err = l.svcCtx.AdminRoleModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 删除角色菜单关联
builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().
Where("role_id = ?", req.Id)
menus, err := l.svcCtx.AdminRoleMenuModel.FindAll(ctx, builder, "id ASC")
if err != nil {
return err
}
for _, menu := range menus {
err = l.svcCtx.AdminRoleMenuModel.Delete(ctx, session, menu.Id)
if err != nil {
return err
}
}
// 删除角色用户关联
builder = l.svcCtx.AdminUserRoleModel.SelectBuilder().
Where("role_id = ?", req.Id)
users, err := l.svcCtx.AdminUserRoleModel.FindAll(ctx, builder, "id ASC")
if err != nil {
return err
}
for _, user := range users {
err = l.svcCtx.AdminUserRoleModel.Delete(ctx, session, user.Id)
if err != nil {
return err
}
}
// 删除角色
err = l.svcCtx.AdminRoleModel.Delete(ctx, session, req.Id)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除角色失败err: %v", err)
}
return &types.DeleteRoleResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,91 @@
package admin_role
import (
"context"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetRoleDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetRoleDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRoleDetailLogic {
return &GetRoleDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetRoleDetailLogic) GetRoleDetail(req *types.GetRoleDetailReq) (resp *types.GetRoleDetailResp, err error) {
// 使用MapReduceVoid并发获取角色信息和菜单ID
var role *model.AdminRole
var menuIds []string
var mutex sync.Mutex
var wg sync.WaitGroup
mr.MapReduceVoid(func(source chan<- interface{}) {
source <- 1 // 获取角色信息
source <- 2 // 获取菜单ID
}, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) {
taskType := item.(int)
wg.Add(1)
defer wg.Done()
if taskType == 1 {
result, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.Id)
if err != nil {
cancel(err)
return
}
mutex.Lock()
role = result
mutex.Unlock()
} else if taskType == 2 {
builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().
Where("role_id = ?", req.Id)
menus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
cancel(err)
return
}
mutex.Lock()
menuIds = lo.Map(menus, func(item *model.AdminRoleMenu, _ int) string {
return item.MenuId
})
mutex.Unlock()
}
}, func(pipe <-chan interface{}, cancel func(error)) {
// 不需要处理pipe中的数据
})
wg.Wait()
if role == nil {
return nil, errors.Wrapf(xerr.NewErrMsg("角色不存在"), "获取角色详情失败, 角色不存在err: %v", err)
}
return &types.GetRoleDetailResp{
Id: role.Id,
RoleName: role.RoleName,
RoleCode: role.RoleCode,
Description: role.Description,
Status: role.Status,
Sort: role.Sort,
CreateTime: role.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: role.UpdateTime.Format("2006-01-02 15:04:05"),
MenuIds: menuIds,
}, nil
}

View File

@@ -0,0 +1,148 @@
package admin_role
import (
"context"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetRoleListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetRoleListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRoleListLogic {
return &GetRoleListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetRoleListLogic) GetRoleList(req *types.GetRoleListReq) (resp *types.GetRoleListResp, err error) {
resp = &types.GetRoleListResp{
Items: make([]types.RoleListItem, 0),
Total: 0,
}
// 构建查询条件
builder := l.svcCtx.AdminRoleModel.SelectBuilder()
if len(req.Name) > 0 {
builder = builder.Where("role_name LIKE ?", "%"+req.Name+"%")
}
if len(req.Code) > 0 {
builder = builder.Where("role_code LIKE ?", "%"+req.Code+"%")
}
if req.Status != -1 {
builder = builder.Where("status = ?", req.Status)
}
// 设置分页
offset := (req.Page - 1) * req.PageSize
builder = builder.OrderBy("sort ASC").Limit(uint64(req.PageSize)).Offset(uint64(offset))
// 使用MapReduceVoid并发获取总数和列表数据
var roles []*model.AdminRole
var total int64
var mutex sync.Mutex
var wg sync.WaitGroup
mr.MapReduceVoid(func(source chan<- interface{}) {
source <- 1 // 获取角色列表
source <- 2 // 获取总数
}, func(item interface{}, writer mr.Writer[*model.AdminRole], cancel func(error)) {
taskType := item.(int)
wg.Add(1)
defer wg.Done()
if taskType == 1 {
result, err := l.svcCtx.AdminRoleModel.FindAll(l.ctx, builder, "id DESC")
if err != nil {
cancel(err)
return
}
mutex.Lock()
roles = result
mutex.Unlock()
} else if taskType == 2 {
countBuilder := l.svcCtx.AdminRoleModel.SelectBuilder()
if len(req.Name) > 0 {
countBuilder = countBuilder.Where("role_name LIKE ?", "%"+req.Name+"%")
}
if len(req.Code) > 0 {
countBuilder = countBuilder.Where("role_code LIKE ?", "%"+req.Code+"%")
}
if req.Status != -1 {
countBuilder = countBuilder.Where("status = ?", req.Status)
}
count, err := l.svcCtx.AdminRoleModel.FindCount(l.ctx, countBuilder, "id")
if err != nil {
cancel(err)
return
}
mutex.Lock()
total = count
mutex.Unlock()
}
}, func(pipe <-chan *model.AdminRole, cancel func(error)) {
// 不需要处理pipe中的数据
})
wg.Wait()
// 并发获取每个角色的菜单ID
var roleItems []types.RoleListItem
var roleItemsMutex sync.Mutex
mr.MapReduceVoid(func(source chan<- interface{}) {
for _, role := range roles {
source <- role
}
}, func(item interface{}, writer mr.Writer[[]string], cancel func(error)) {
role := item.(*model.AdminRole)
// 获取角色关联的菜单ID
builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().
Where("role_id = ?", role.Id)
menus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
cancel(err)
return
}
menuIds := lo.Map(menus, func(item *model.AdminRoleMenu, _ int) string {
return item.MenuId
})
writer.Write(menuIds)
}, func(pipe <-chan []string, cancel func(error)) {
for _, role := range roles {
menuIds := <-pipe
item := types.RoleListItem{
Id: role.Id,
RoleName: role.RoleName,
RoleCode: role.RoleCode,
Description: role.Description,
Status: role.Status,
Sort: role.Sort,
CreateTime: role.CreateTime.Format("2006-01-02 15:04:05"),
MenuIds: menuIds,
}
roleItemsMutex.Lock()
roleItems = append(roleItems, item)
roleItemsMutex.Unlock()
}
})
resp.Items = roleItems
resp.Total = total
return resp, nil
}

View File

@@ -0,0 +1,150 @@
package admin_role
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/google/uuid"
)
type UpdateRoleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateRoleLogic {
return &UpdateRoleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateRoleLogic) UpdateRole(req *types.UpdateRoleReq) (resp *types.UpdateRoleResp, err error) {
// 检查角色是否存在
role, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("角色不存在"), "更新角色失败, 角色不存在err: %v", err)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新角色失败err: %v", err)
}
// 检查角色编码是否重复
if req.RoleCode != nil && *req.RoleCode != role.RoleCode {
roleModel, err := l.svcCtx.AdminRoleModel.FindOneByRoleCode(l.ctx, *req.RoleCode)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新角色失败err: %v", err)
}
if roleModel != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("角色编码已存在"), "更新角色失败, 角色编码已存在err: %v", err)
}
}
// 更新角色信息
if req.RoleName != nil {
role.RoleName = *req.RoleName
}
if req.RoleCode != nil {
role.RoleCode = *req.RoleCode
}
if req.Description != nil {
role.Description = *req.Description
}
if req.Status != nil {
role.Status = *req.Status
}
if req.Sort != nil {
role.Sort = *req.Sort
}
// 使用事务更新角色和关联菜单
err = l.svcCtx.AdminRoleModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 更新角色
_, err = l.svcCtx.AdminRoleModel.Update(ctx, session, role)
if err != nil {
return err
}
if req.MenuIds != nil {
// 1. 获取当前关联的菜单ID
builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().
Where("role_id = ?", req.Id)
currentMenus, err := l.svcCtx.AdminRoleMenuModel.FindAll(ctx, builder, "id ASC")
if err != nil {
return err
}
// 2. 转换为map便于查找
currentMenuMap := make(map[string]*model.AdminRoleMenu)
for _, menu := range currentMenus {
currentMenuMap[menu.MenuId] = menu
}
// 3. 检查新的菜单ID是否存在
for _, menuId := range req.MenuIds {
exists, err := l.svcCtx.AdminMenuModel.FindOne(ctx, menuId)
if err != nil || exists == nil {
return errors.Wrapf(xerr.NewErrMsg("菜单不存在"), "菜单ID: %s", menuId)
}
}
// 4. 找出需要删除和新增的关联
var toDelete []*model.AdminRoleMenu
var toInsert []string
// 需要删除的:当前存在但新列表中没有的
for menuId, roleMenu := range currentMenuMap {
if !lo.Contains(req.MenuIds, menuId) {
toDelete = append(toDelete, roleMenu)
}
}
// 需要新增的:新列表中有但当前不存在的
for _, menuId := range req.MenuIds {
if _, exists := currentMenuMap[menuId]; !exists {
toInsert = append(toInsert, menuId)
}
}
// 5. 删除需要移除的关联
for _, roleMenu := range toDelete {
err = l.svcCtx.AdminRoleMenuModel.Delete(ctx, session, roleMenu.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除角色菜单关联失败: %v", err)
}
}
// 6. 添加新的关联
for _, menuId := range toInsert {
roleMenu := &model.AdminRoleMenu{
Id: uuid.NewString(),
RoleId: req.Id,
MenuId: menuId,
}
_, err = l.svcCtx.AdminRoleMenuModel.Insert(ctx, session, roleMenu)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "添加角色菜单关联失败: %v", err)
}
}
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新角色失败err: %v", err)
}
return &types.UpdateRoleResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,98 @@
package admin_role_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/google/uuid"
)
type AdminAssignRoleApiLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminAssignRoleApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminAssignRoleApiLogic {
return &AdminAssignRoleApiLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminAssignRoleApiLogic) AdminAssignRoleApi(req *types.AdminAssignRoleApiReq) (resp *types.AdminAssignRoleApiResp, err error) {
// 1. 参数验证
if req.RoleId == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"角色ID不能为空, roleId: %s", req.RoleId)
}
if len(req.ApiIds) == 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API ID列表不能为空")
}
// 2. 查询角色是否存在
_, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"角色不存在, roleId: %d", req.RoleId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色失败, err: %v, roleId: %d", err, req.RoleId)
}
// 3. 批量分配API权限
successCount := 0
for _, apiId := range req.ApiIds {
if apiId == "" {
continue
}
// 检查API是否存在
_, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, apiId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
logx.Errorf("API不存在, apiId: %s", apiId)
continue
}
logx.Errorf("查询API失败, err: %v, apiId: %s", err, apiId)
continue
}
// 检查是否已存在关联
existing, err := l.svcCtx.AdminRoleApiModel.FindOneByRoleIdApiId(l.ctx, req.RoleId, apiId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
logx.Errorf("查询角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId)
continue
}
if existing != nil {
continue // 已存在,跳过
}
// 创建关联
roleApiData := &model.AdminRoleApi{
Id: uuid.NewString(),
RoleId: req.RoleId,
ApiId: apiId,
}
_, err = l.svcCtx.AdminRoleApiModel.Insert(l.ctx, nil, roleApiData)
if err != nil {
logx.Errorf("创建角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId)
continue
}
successCount++
}
// 4. 返回结果
return &types.AdminAssignRoleApiResp{Success: true}, nil
}

View File

@@ -0,0 +1,64 @@
package admin_role_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetAllApiListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetAllApiListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAllApiListLogic {
return &AdminGetAllApiListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetAllApiListLogic) AdminGetAllApiList(req *types.AdminGetAllApiListReq) (resp *types.AdminGetAllApiListResp, err error) {
// 1. 构建查询条件
builder := l.svcCtx.AdminApiModel.SelectBuilder()
// 添加状态过滤
if req.Status > 0 {
builder = builder.Where("status = ?", req.Status)
}
// 2. 查询所有API列表
apis, err := l.svcCtx.AdminApiModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询API列表失败, err: %v", err)
}
// 3. 转换数据格式
var apiList []types.AdminRoleApiInfo
for _, api := range apis {
apiList = append(apiList, types.AdminRoleApiInfo{
Id: api.Id,
RoleId: "",
ApiId: api.Id,
ApiName: api.ApiName,
ApiCode: api.ApiCode,
Method: api.Method,
Url: api.Url,
Status: api.Status,
Description: api.Description,
})
}
// 4. 返回结果
return &types.AdminGetAllApiListResp{
Items: apiList,
}, nil
}

View File

@@ -0,0 +1,84 @@
package admin_role_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminGetRoleApiListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetRoleApiListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetRoleApiListLogic {
return &AdminGetRoleApiListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetRoleApiListLogic) AdminGetRoleApiList(req *types.AdminGetRoleApiListReq) (resp *types.AdminGetRoleApiListResp, err error) {
// 1. 参数验证
if req.RoleId == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"角色ID不能为空, roleId: %s", req.RoleId)
}
// 2. 查询角色是否存在
_, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"角色不存在, roleId: %s", req.RoleId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色失败, err: %v, roleId: %d", err, req.RoleId)
}
// 3. 查询角色API权限列表
builder := l.svcCtx.AdminRoleApiModel.SelectBuilder().
Where("role_id = ?", req.RoleId)
roleApis, err := l.svcCtx.AdminRoleApiModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色API权限失败, err: %v, roleId: %d", err, req.RoleId)
}
// 4. 转换数据格式
var apiList []types.AdminRoleApiInfo
for _, roleApi := range roleApis {
// 查询API详情
api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, roleApi.ApiId)
if err != nil {
logx.Errorf("查询API详情失败, err: %v, apiId: %d", err, roleApi.ApiId)
continue
}
apiList = append(apiList, types.AdminRoleApiInfo{
Id: roleApi.Id,
RoleId: roleApi.RoleId,
ApiId: roleApi.ApiId,
ApiName: api.ApiName,
ApiCode: api.ApiCode,
Method: api.Method,
Url: api.Url,
Status: api.Status,
Description: api.Description,
})
}
// 5. 返回结果
return &types.AdminGetRoleApiListResp{
Items: apiList,
}, nil
}

View File

@@ -0,0 +1,80 @@
package admin_role_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminRemoveRoleApiLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminRemoveRoleApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRemoveRoleApiLogic {
return &AdminRemoveRoleApiLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminRemoveRoleApiLogic) AdminRemoveRoleApi(req *types.AdminRemoveRoleApiReq) (resp *types.AdminRemoveRoleApiResp, err error) {
// 1. 参数验证
if req.RoleId == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"角色ID不能为空, roleId: %s", req.RoleId)
}
if len(req.ApiIds) == 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"API ID列表不能为空")
}
// 2. 查询角色是否存在
_, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"角色不存在, roleId: %d", req.RoleId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色失败, err: %v, roleId: %d", err, req.RoleId)
}
// 3. 批量移除API权限
successCount := 0
for _, apiId := range req.ApiIds {
if apiId == "" {
continue
}
// 查询关联记录
roleApi, err := l.svcCtx.AdminRoleApiModel.FindOneByRoleIdApiId(l.ctx, req.RoleId, apiId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
continue // 不存在,跳过
}
logx.Errorf("查询角色API关联失败, err: %v, roleId: %s, apiId: %s", err, req.RoleId, apiId)
continue
}
// 删除关联
err = l.svcCtx.AdminRoleApiModel.DeleteSoft(l.ctx, nil, roleApi)
if err != nil {
logx.Errorf("删除角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId)
continue
}
successCount++
}
// 4. 返回结果
return &types.AdminRemoveRoleApiResp{Success: true}, nil
}

View File

@@ -0,0 +1,98 @@
package admin_role_api
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/google/uuid"
)
type AdminUpdateRoleApiLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateRoleApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateRoleApiLogic {
return &AdminUpdateRoleApiLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateRoleApiLogic) AdminUpdateRoleApi(req *types.AdminUpdateRoleApiReq) (resp *types.AdminUpdateRoleApiResp, err error) {
// 1. 参数验证
if req.RoleId == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"角色ID不能为空, roleId: %s", req.RoleId)
}
// 2. 查询角色是否存在
_, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"角色不存在, roleId: %s", req.RoleId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色失败, err: %v, roleId: %s", err, req.RoleId)
}
// 3. 删除该角色的所有API权限
builder := l.svcCtx.AdminRoleApiModel.SelectBuilder().Where("role_id = ?", req.RoleId)
roleApis, err := l.svcCtx.AdminRoleApiModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色API权限失败, err: %v, roleId: %d", err, req.RoleId)
}
// 删除现有权限
for _, roleApi := range roleApis {
err = l.svcCtx.AdminRoleApiModel.DeleteSoft(l.ctx, nil, roleApi)
if err != nil {
logx.Errorf("删除角色API权限失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, roleApi.ApiId)
}
}
// 4. 添加新的API权限
if len(req.ApiIds) > 0 {
for _, apiId := range req.ApiIds {
if apiId == "" {
continue
}
// 检查API是否存在
_, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, apiId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
logx.Errorf("API不存在, apiId: %s", apiId)
continue
}
logx.Errorf("查询API失败, err: %v, apiId: %s", err, apiId)
continue
}
// 创建新的关联
roleApiData := &model.AdminRoleApi{
Id: uuid.NewString(),
RoleId: req.RoleId,
ApiId: apiId,
}
_, err = l.svcCtx.AdminRoleApiModel.Insert(l.ctx, nil, roleApiData)
if err != nil {
logx.Errorf("创建角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId)
}
}
}
// 5. 返回结果
return &types.AdminUpdateRoleApiResp{Success: true}, nil
}

View File

@@ -0,0 +1,86 @@
package admin_user
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminCreateUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateUserLogic {
return &AdminCreateUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminCreateUserLogic) AdminCreateUser(req *types.AdminCreateUserReq) (resp *types.AdminCreateUserResp, err error) {
// 检查用户名是否已存在
exists, err := l.svcCtx.AdminUserModel.FindOneByUsername(l.ctx, req.Username)
if err != nil && err != model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
}
if exists != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("用户名已存在"), "创建用户失败")
}
password, err := crypto.PasswordHash("123456")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建用户失败, 加密密码失败: %v", err)
}
// 创建用户
user := &model.AdminUser{
Id: uuid.NewString(),
Username: req.Username,
Password: password,
RealName: req.RealName,
Status: req.Status,
}
// 使用事务创建用户和关联角色
err = l.svcCtx.AdminUserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建用户
_, err := l.svcCtx.AdminUserModel.Insert(ctx, session, user)
if err != nil {
return err
}
// 创建用户角色关联
if len(req.RoleIds) > 0 {
for _, roleId := range req.RoleIds {
userRole := &model.AdminUserRole{
Id: uuid.NewString(),
UserId: user.Id,
RoleId: roleId,
}
_, err = l.svcCtx.AdminUserRoleModel.Insert(ctx, session, userRole)
if err != nil {
return err
}
}
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
}
return &types.AdminCreateUserResp{
Id: user.Id,
}, nil
}

View File

@@ -0,0 +1,68 @@
package admin_user
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminDeleteUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminDeleteUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteUserLogic {
return &AdminDeleteUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminDeleteUserLogic) AdminDeleteUser(req *types.AdminDeleteUserReq) (resp *types.AdminDeleteUserResp, err error) {
// 检查用户是否存在
_, err = l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %v", err)
}
// 使用事务删除用户和关联数据
err = l.svcCtx.AdminUserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 删除用户角色关联
builder := l.svcCtx.AdminUserRoleModel.SelectBuilder().
Where("user_id = ?", req.Id)
roles, err := l.svcCtx.AdminUserRoleModel.FindAll(ctx, builder, "id ASC")
if err != nil {
return err
}
for _, role := range roles {
err = l.svcCtx.AdminUserRoleModel.Delete(ctx, session, role.Id)
if err != nil {
return err
}
}
// 删除用户
err = l.svcCtx.AdminUserModel.Delete(ctx, session, req.Id)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除用户失败: %v", err)
}
return &types.AdminDeleteUserResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,88 @@
package admin_user
import (
"context"
"errors"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type AdminGetUserDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetUserDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetUserDetailLogic {
return &AdminGetUserDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetUserDetailLogic) AdminGetUserDetail(req *types.AdminGetUserDetailReq) (resp *types.AdminGetUserDetailResp, err error) {
// 使用MapReduceVoid并发获取用户信息和角色ID
var user *model.AdminUser
var roleIds []string
var mutex sync.Mutex
var wg sync.WaitGroup
mr.MapReduceVoid(func(source chan<- interface{}) {
source <- 1 // 获取用户信息
source <- 2 // 获取角色ID
}, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) {
taskType := item.(int)
wg.Add(1)
defer wg.Done()
if taskType == 1 {
result, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id)
if err != nil {
cancel(err)
return
}
mutex.Lock()
user = result
mutex.Unlock()
} else if taskType == 2 {
builder := l.svcCtx.AdminUserRoleModel.SelectBuilder().
Where("user_id = ?", req.Id)
roles, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
cancel(err)
return
}
mutex.Lock()
roleIds = lo.Map(roles, func(item *model.AdminUserRole, _ int) string {
return item.RoleId
})
mutex.Unlock()
}
}, func(pipe <-chan interface{}, cancel func(error)) {
// 不需要处理pipe中的数据
})
wg.Wait()
if user == nil {
return nil, errors.New("用户不存在")
}
return &types.AdminGetUserDetailResp{
Id: user.Id,
Username: user.Username,
RealName: user.RealName,
Status: user.Status,
CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"),
RoleIds: roleIds,
}, nil
}

View File

@@ -0,0 +1,149 @@
package admin_user
import (
"context"
"sync"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type AdminGetUserListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminGetUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetUserListLogic {
return &AdminGetUserListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminGetUserListLogic) AdminGetUserList(req *types.AdminGetUserListReq) (resp *types.AdminGetUserListResp, err error) {
resp = &types.AdminGetUserListResp{
Items: make([]types.AdminUserListItem, 0),
Total: 0,
}
// 构建查询条件
builder := l.svcCtx.AdminUserModel.SelectBuilder().
Where("del_state = ?", 0)
if len(req.Username) > 0 {
builder = builder.Where("username LIKE ?", "%"+req.Username+"%")
}
if len(req.RealName) > 0 {
builder = builder.Where("real_name LIKE ?", "%"+req.RealName+"%")
}
if req.Status != -1 {
builder = builder.Where("status = ?", req.Status)
}
// 设置分页
offset := (req.Page - 1) * req.PageSize
builder = builder.OrderBy("id DESC").Limit(uint64(req.PageSize)).Offset(uint64(offset))
// 使用MapReduceVoid并发获取总数和列表数据
var users []*model.AdminUser
var total int64
var mutex sync.Mutex
var wg sync.WaitGroup
mr.MapReduceVoid(func(source chan<- interface{}) {
source <- 1 // 获取用户列表
source <- 2 // 获取总数
}, func(item interface{}, writer mr.Writer[*model.AdminUser], cancel func(error)) {
taskType := item.(int)
wg.Add(1)
defer wg.Done()
if taskType == 1 {
result, err := l.svcCtx.AdminUserModel.FindAll(l.ctx, builder, "id DESC")
if err != nil {
cancel(err)
return
}
mutex.Lock()
users = result
mutex.Unlock()
} else if taskType == 2 {
countBuilder := l.svcCtx.AdminUserModel.SelectBuilder().
Where("del_state = ?", 0)
if len(req.Username) > 0 {
countBuilder = countBuilder.Where("username LIKE ?", "%"+req.Username+"%")
}
if len(req.RealName) > 0 {
countBuilder = countBuilder.Where("real_name LIKE ?", "%"+req.RealName+"%")
}
if req.Status != -1 {
countBuilder = countBuilder.Where("status = ?", req.Status)
}
count, err := l.svcCtx.AdminUserModel.FindCount(l.ctx, countBuilder, "id")
if err != nil {
cancel(err)
return
}
mutex.Lock()
total = count
mutex.Unlock()
}
}, func(pipe <-chan *model.AdminUser, cancel func(error)) {
// 不需要处理pipe中的数据
})
wg.Wait()
// 并发获取每个用户的角色ID
var userItems []types.AdminUserListItem
var userItemsMutex sync.Mutex
mr.MapReduceVoid(func(source chan<- interface{}) {
for _, user := range users {
source <- user
}
}, func(item interface{}, writer mr.Writer[[]string], cancel func(error)) {
user := item.(*model.AdminUser)
// 获取用户关联的角色ID
builder := l.svcCtx.AdminUserRoleModel.SelectBuilder().
Where("user_id = ?", user.Id)
roles, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
cancel(err)
return
}
roleIds := lo.Map(roles, func(item *model.AdminUserRole, _ int) string {
return item.RoleId
})
writer.Write(roleIds)
}, func(pipe <-chan []string, cancel func(error)) {
for _, user := range users {
roleIds := <-pipe
item := types.AdminUserListItem{
Id: user.Id,
Username: user.Username,
RealName: user.RealName,
Status: user.Status,
CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"),
RoleIds: roleIds,
}
userItemsMutex.Lock()
userItems = append(userItems, item)
userItemsMutex.Unlock()
}
})
resp.Items = userItems
resp.Total = total
return resp, nil
}

View File

@@ -0,0 +1,63 @@
package admin_user
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminResetPasswordLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminResetPasswordLogic {
return &AdminResetPasswordLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminResetPasswordLogic) AdminResetPassword(req *types.AdminResetPasswordReq) (resp *types.AdminResetPasswordResp, err error) {
// 检查用户是否存在
user, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrMsg("用户不存在"), "用户ID: %d", req.Id)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err)
}
// 检查用户状态
if user.Status != 1 {
return nil, errors.Wrapf(xerr.NewErrMsg("用户已被禁用,无法重置密码"), "用户ID: %d", req.Id)
}
// 对密码进行加密
hashedPassword, err := crypto.PasswordHash(req.Password)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "密码加密失败: %v", err)
}
// 更新用户密码
user.Password = hashedPassword
_, err = l.svcCtx.AdminUserModel.Update(l.ctx, nil, user)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新密码失败: %v", err)
}
l.Infof("管理员密码重置成功用户ID: %d, 用户名: %s", req.Id, user.Username)
return &types.AdminResetPasswordResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,143 @@
package admin_user
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/google/uuid"
)
type AdminUpdateUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUpdateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateUserLogic {
return &AdminUpdateUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUpdateUserLogic) AdminUpdateUser(req *types.AdminUpdateUserReq) (resp *types.AdminUpdateUserResp, err error) {
// 检查用户是否存在
user, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %v", err)
}
// 检查用户名是否重复
if req.Username != nil && *req.Username != user.Username {
exists, err := l.svcCtx.AdminUserModel.FindOneByUsername(l.ctx, *req.Username)
if err != nil && err != model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新用户失败: %v", err)
}
if exists != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("用户名已存在"), "更新用户失败")
}
}
// 更新用户信息
if req.Username != nil {
user.Username = *req.Username
}
if req.RealName != nil {
user.RealName = *req.RealName
}
if req.Status != nil {
user.Status = *req.Status
}
// 使用事务更新用户和关联角色
err = l.svcCtx.AdminUserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 更新用户
_, err = l.svcCtx.AdminUserModel.Update(ctx, session, user)
if err != nil {
return err
}
// 只有当RoleIds不为nil时才更新角色关联
if req.RoleIds != nil {
// 1. 获取当前关联的角色ID
builder := l.svcCtx.AdminUserRoleModel.SelectBuilder().
Where("user_id = ?", req.Id)
currentRoles, err := l.svcCtx.AdminUserRoleModel.FindAll(ctx, builder, "id ASC")
if err != nil {
return err
}
// 2. 转换为map便于查找
currentRoleMap := make(map[string]*model.AdminUserRole)
for _, role := range currentRoles {
currentRoleMap[role.RoleId] = role
}
// 3. 检查新的角色ID是否存在
for _, roleId := range req.RoleIds {
exists, err := l.svcCtx.AdminRoleModel.FindOne(ctx, roleId)
if err != nil || exists == nil {
return errors.Wrapf(xerr.NewErrMsg("角色不存在"), "角色ID: %s", roleId)
}
}
// 4. 找出需要删除和新增的关联
var toDelete []*model.AdminUserRole
var toInsert []string
// 需要删除的:当前存在但新列表中没有的
for roleId, userRole := range currentRoleMap {
if !lo.Contains(req.RoleIds, roleId) {
toDelete = append(toDelete, userRole)
}
}
// 需要新增的:新列表中有但当前不存在的
for _, roleId := range req.RoleIds {
if _, exists := currentRoleMap[roleId]; !exists {
toInsert = append(toInsert, roleId)
}
}
// 5. 删除需要移除的关联
for _, userRole := range toDelete {
err = l.svcCtx.AdminUserRoleModel.Delete(ctx, session, userRole.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除用户角色关联失败: %v", err)
}
}
// 6. 添加新的关联
for _, roleId := range toInsert {
userRole := &model.AdminUserRole{
Id: uuid.NewString(),
UserId: req.Id,
RoleId: roleId,
}
_, err = l.svcCtx.AdminUserRoleModel.Insert(ctx, session, userRole)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "添加用户角色关联失败: %v", err)
}
}
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新用户失败: %v", err)
}
return &types.AdminUpdateUserResp{
Success: true,
}, nil
}

View File

@@ -0,0 +1,67 @@
package admin_user
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminUserInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUserInfoLogic {
return &AdminUserInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminUserInfoLogic) AdminUserInfo(req *types.AdminUserInfoReq) (resp *types.AdminUserInfoResp, err error) {
userId, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %+v", err)
}
user, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, userId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID信息失败, %+v", err)
}
// 获取权限
adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": user.Id})
permissions, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, adminUserRoleBuilder, "role_id DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("获取权限失败"), "用户登录, 获取权限失败, 用户名: %s", user.Username)
}
// 获取角色ID数组
roleIds := make([]string, 0)
for _, permission := range permissions {
roleIds = append(roleIds, permission.RoleId)
}
// 获取角色名称
roles := make([]string, 0)
for _, roleId := range roleIds {
role, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, roleId)
if err != nil {
continue
}
roles = append(roles, role.RoleCode)
}
return &types.AdminUserInfoResp{
Username: user.Username,
RealName: user.RealName,
Roles: roles,
}, nil
}

View File

@@ -0,0 +1,190 @@
package agent
import (
"context"
"database/sql"
stderrors "errors"
"fmt"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"os"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ApplyForAgentLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewApplyForAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyForAgentLogic {
return &ApplyForAgentLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *types.AgentApplyResp, err error) {
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err != nil && !stderrors.Is(err, ctxdata.ErrNoInCtx) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请失败, %v", err)
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
// 系统简化:无需邀请码,直接申请成为代理
if req.Referrer == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
}
// 2. 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if stderrors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
}
}
var userID string
transErr := l.svcCtx.AgentModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 1. 处理用户注册/绑定
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
if findUserErr != nil && !stderrors.Is(findUserErr, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败, %v", findUserErr)
}
if user == nil {
// 用户不存在,注册新用户
if claims != nil && claims.UserType == model.UserTypeNormal {
return errors.Wrapf(xerr.NewErrMsg("当前用户已注册,请输入注册的手机号"), "")
}
userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "注册用户失败: %v", err)
}
} else {
// 用户已存在
if claims != nil && claims.UserType == model.UserTypeTemp {
// 临时用户,检查手机号是否已绑定其他微信号
userAuth, findUserAuthErr := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, user.Id, claims.AuthType)
if findUserAuthErr != nil && !stderrors.Is(findUserAuthErr, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户认证失败, %v", findUserAuthErr)
}
if userAuth != nil && userAuth.AuthKey != claims.AuthKey {
return errors.Wrapf(xerr.NewErrMsg("该手机号已绑定其他微信号"), "")
}
// 临时用户,转为正式用户
err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定用户失败: %v", err)
}
}
userID = user.Id
}
// 3. 检查是否已是代理
existingAgent, err := l.svcCtx.AgentModel.FindOneByUserId(transCtx, userID)
if err != nil && !stderrors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
if existingAgent != nil {
return errors.Wrapf(xerr.NewErrMsg("您已经是代理"), "")
}
// 系统简化:移除邀请码验证、等级设置、团队关系建立等复杂逻辑
// 4. 分配代理编码
agentCode, err := l.allocateAgentCode(transCtx, session)
if err != nil {
return errors.Wrapf(err, "分配代理编码失败")
}
// 5. 创建代理记录(简化版:无等级、无团队首领)
newAgent := &model.Agent{
Id: uuid.NewString(),
UserId: userID,
AgentCode: agentCode,
Mobile: encryptedMobile,
}
if req.Region != "" {
newAgent.Region = sql.NullString{String: req.Region, Valid: true}
}
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
// 6. 初始化钱包(简化版:移除冻结余额、提现金额字段)
wallet := &model.AgentWallet{
Id: uuid.NewString(),
AgentId: newAgent.Id,
}
if _, err := l.svcCtx.AgentWalletModel.Insert(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "初始化钱包失败")
}
return nil
})
if transErr != nil {
return nil, transErr
}
// 7. 生成并返回token
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成token失败: %v", err)
}
now := time.Now().Unix()
agent, _ := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
var code int64
if agent != nil {
code = agent.AgentCode
}
return &types.AgentApplyResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
AgentCode: code,
}, nil
}
// allocateAgentCode 分配代理编码
func (l *ApplyForAgentLogic) allocateAgentCode(ctx context.Context, session sqlx.Session) (int64, error) {
// 系统简化:不需要事务,因为调用方已经在事务中
builder := l.svcCtx.AgentModel.SelectBuilder().OrderBy("agent_code DESC").Limit(1)
rows, err := l.svcCtx.AgentModel.FindAll(ctx, builder, "")
if err != nil {
return 0, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理编码失败, %v", err)
}
var next int64 = 16800
if len(rows) > 0 && rows[0].AgentCode > 0 {
next = rows[0].AgentCode + 1
}
return next, nil
}

View File

@@ -0,0 +1,139 @@
package agent
import (
"context"
"database/sql"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type CreateWithdrawLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateWithdrawLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateWithdrawLogic {
return &CreateWithdrawLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateWithdrawLogic) CreateWithdraw(req *types.CreateWithdrawReq) (resp *types.CreateWithdrawResp, err error) {
// 1. 获取当前用户ID代理ID
userId, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err)
}
// 2. 查询代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrap(xerr.NewErrCode(xerr.CUSTOM_ERROR), "您还不是代理,无法申请提现")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理信息失败: %v", err)
}
// 3. 查询钱包信息
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询钱包信息失败: %v", err)
}
// 4. 验证提现金额
if req.WithdrawAmount <= 0 {
return nil, errors.Wrap(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "提现金额必须大于0")
}
minWithdraw := 100.00 // 最低提现金额100元
if req.WithdrawAmount < minWithdraw {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "最低提现金额为%.2f元", minWithdraw)
}
if wallet.Balance < req.WithdrawAmount {
return nil, errors.Wrap(xerr.NewErrCode(xerr.CUSTOM_ERROR), "可用余额不足")
}
// 5. 加密银行卡号
encryptedCardNumber, err := crypto.AesEncrypt([]byte(req.BankCardNumber), []byte(l.svcCtx.Config.Encrypt.SecretKey))
if err != nil {
logx.Errorf("加密银行卡号失败: %v", err)
return nil, errors.Wrap(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密银行卡号失败")
}
// 6. 生成提现记录ID
withdrawId := uuid.NewString()
// 7. 解密代理手机号(用于冗余存储)
agentMobile, err := crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
logx.Errorf("解密代理手机号失败: %v", err)
agentMobile = ""
}
// 8. 开启事务处理
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 8.1 冻结提现金额
wallet.Balance -= req.WithdrawAmount
wallet.FrozenAmount += req.WithdrawAmount
// 使用版本号乐观锁更新
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
if err != nil {
return errors.Wrapf(err, "冻结金额失败")
}
// 8.2 创建提现记录
withdrawRecord := &model.AgentWithdraw{
Id: withdrawId,
AgentId: agent.Id,
WithdrawAmount: req.WithdrawAmount,
TaxAmount: 0.00,
ActualAmount: 0.00,
FrozenAmount: req.WithdrawAmount,
AccountName: req.AccountName,
BankCardNumber: req.BankCardNumber,
BankCardNumberEncrypted: sql.NullString{String: string(encryptedCardNumber), Valid: true},
BankBranch: sql.NullString{String: req.BankBranch, Valid: req.BankBranch != ""},
Status: 0,
}
// 冗余存储代理手机号和编码
if agentMobile != "" {
withdrawRecord.AgentMobile = sql.NullString{String: agentMobile, Valid: true}
}
if agent.AgentCode > 0 {
withdrawRecord.AgentCode = sql.NullInt64{Int64: agent.AgentCode, Valid: true}
}
_, err = l.svcCtx.AgentWithdrawModel.Insert(ctx, session, withdrawRecord)
if err != nil {
return errors.Wrapf(err, "创建提现记录失败")
}
return nil
})
if err != nil {
return nil, errors.Wrap(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), err.Error())
}
logx.Infof("代理 %s 申请提现成功,提现金额: %.2f", agent.Id, req.WithdrawAmount)
return &types.CreateWithdrawResp{
WithdrawId: withdrawId,
}, nil
}

View File

@@ -0,0 +1,279 @@
package agent
import (
"context"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"strings"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/globalkey"
"jnc-server/common/tool"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/google/uuid"
"github.com/zeromicro/go-zero/core/logx"
)
type GeneratingLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGeneratingLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GeneratingLinkLogic {
return &GeneratingLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广链接失败, %v", err)
}
// 1. 获取代理信息
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 获取产品配置(必须存在)
productConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, req.ProductId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品配置不存在, productId: %d请先在后台配置产品价格参数", req.ProductId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置失败, productId: %d, %v", req.ProductId, err)
}
// 系统简化:移除等级加成和等级上调金额,直接使用底价和系统最高价
actualBasePrice := productConfig.BasePrice
systemMaxPrice := productConfig.SystemMaxPrice
if req.SetPrice < actualBasePrice || req.SetPrice > systemMaxPrice {
return nil, errors.Wrapf(xerr.NewErrMsg("设定价格必须在 %.2f 到 %.2f 之间"), "设定价格必须在 %.2f 到 %.2f 之间", actualBasePrice, systemMaxPrice)
}
// 检查是否已存在相同的链接(同一代理、同一产品、同一价格)
builder := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{
squirrel.Eq{"agent_id": agentModel.Id},
squirrel.Eq{"product_id": req.ProductId},
squirrel.Eq{"set_price": req.SetPrice},
squirrel.Eq{"del_state": globalkey.DelStateNo},
})
existingLinks, err := l.svcCtx.AgentLinkModel.FindAll(l.ctx, builder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询推广链接失败, %v", err)
}
if len(existingLinks) > 0 {
// 已存在,检查是否有短链,如果没有则生成
targetPath := req.TargetPath
if targetPath == "" {
targetPath = "/agent/promotionInquire/"
}
shortLink, err := l.getOrCreateShortLink(1, existingLinks[0].Id, "", existingLinks[0].LinkIdentifier, "", targetPath)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取或创建短链失败, %v", err)
}
return &types.AgentGeneratingLinkResp{
LinkIdentifier: existingLinks[0].LinkIdentifier,
FullLink: shortLink,
}, nil
}
// 7. 生成推广链接标识
var agentIdentifier types.AgentIdentifier
agentIdentifier.AgentID = agentModel.Id
agentIdentifier.ProductID = req.ProductId
agentIdentifier.SetPrice = req.SetPrice
agentIdentifierByte, err := json.Marshal(agentIdentifier)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化标识失败, %v", err)
}
// 8. 加密链接标识
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取AES密钥失败: %+v", decodeErr)
}
encrypted, err := crypto.AesEncryptURL(agentIdentifierByte, key)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密链接标识失败, %v", err)
}
// 9. 保存推广链接
agentLink := &model.AgentLink{
// Id: uuid.NewString(),
AgentId: agentModel.Id,
UserId: userID,
ProductId: req.ProductId,
LinkIdentifier: encrypted,
SetPrice: req.SetPrice,
ActualBasePrice: actualBasePrice,
}
_, err = l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, agentLink)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "保存推广链接失败, %v", err)
}
linkId := agentLink.Id
// 使用默认target_path如果未提供
targetPath := req.TargetPath
if targetPath == "" {
targetPath = "/agent/promotionInquire/"
}
// 生成短链类型1=推广报告)
shortLink, err := l.createShortLink(1, linkId, "", encrypted, "", targetPath)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成短链失败, %v", err)
}
return &types.AgentGeneratingLinkResp{
LinkIdentifier: encrypted,
FullLink: shortLink,
}, nil
}
// getOrCreateShortLink 获取或创建短链
// type: 1=推广报告(promotion), 2=邀请好友(invite)
// linkId: 推广链接ID仅推广报告使用
// inviteCodeId: 邀请码ID仅邀请好友使用
// linkIdentifier: 推广链接标识(仅推广报告使用)
// inviteCode: 邀请码(仅邀请好友使用)
// targetPath: 目标地址(前端传入)
func (l *GeneratingLinkLogic) getOrCreateShortLink(linkType int64, linkId, inviteCodeId string, linkIdentifier, inviteCode, targetPath string) (string, error) {
// 先查询是否已存在短链
var existingShortLink *model.AgentShortLink
var err error
if linkType == 1 {
// 推广报告类型使用link_id查询
if linkId != "" {
existingShortLink, err = l.svcCtx.AgentShortLinkModel.FindOneByLinkIdTypeDelState(l.ctx, sql.NullString{String: linkId, Valid: true}, linkType, globalkey.DelStateNo)
}
} else {
// 邀请好友类型使用invite_code_id查询
if inviteCodeId != "" {
existingShortLink, err = l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeIdTypeDelState(l.ctx, sql.NullString{String: inviteCodeId, Valid: true}, linkType, globalkey.DelStateNo)
}
}
if err == nil && existingShortLink != nil {
// 已存在短链,直接返回
return l.buildShortLinkURL(existingShortLink.ShortCode), nil
}
if err != nil && !errors.Is(err, model.ErrNotFound) {
return "", errors.Wrapf(err, "查询短链失败")
}
// 不存在,创建新的短链
return l.createShortLink(linkType, linkId, inviteCodeId, linkIdentifier, inviteCode, targetPath)
}
// createShortLink 创建短链
// type: 1=推广报告(promotion), 2=邀请好友(invite)
func (l *GeneratingLinkLogic) createShortLink(linkType int64, linkId, inviteCodeId string, linkIdentifier, inviteCode, targetPath string) (string, error) {
promotionConfig := l.svcCtx.Config.Promotion
// 如果没有配置推广域名,返回空字符串(保持向后兼容)
if promotionConfig.PromotionDomain == "" {
l.Errorf("推广域名未配置,返回空链接")
return "", nil
}
// 验证target_path
if targetPath == "" {
return "", errors.Wrapf(xerr.NewErrMsg("目标地址不能为空"), "")
}
// 对于推广报告类型,将 linkIdentifier 拼接到 target_path
if linkType == 1 && linkIdentifier != "" {
// 如果 target_path 以 / 结尾,直接拼接 linkIdentifier
if strings.HasSuffix(targetPath, "/") {
targetPath = targetPath + url.QueryEscape(linkIdentifier)
} else {
// 否则在末尾添加 / 再拼接
targetPath = targetPath + "/" + url.QueryEscape(linkIdentifier)
}
}
// 生成短链标识6位随机字符串大小写字母+数字)
var shortCode string
maxRetries := 10 // 最大重试次数
for retry := 0; retry < maxRetries; retry++ {
shortCode = tool.Krand(6, tool.KC_RAND_KIND_ALL)
// 检查短链标识是否已存在
_, err := l.svcCtx.AgentShortLinkModel.FindOneByShortCodeDelState(l.ctx, shortCode, globalkey.DelStateNo)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 短链标识不存在,可以使用
break
}
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查短链标识失败, %v", err)
}
// 短链标识已存在,继续生成
if retry == maxRetries-1 {
return "", errors.Wrapf(xerr.NewErrMsg("生成短链失败,请重试"), "")
}
}
// 创建短链记录
shortLink := &model.AgentShortLink{
Id: uuid.NewString(),
Type: linkType,
ShortCode: shortCode,
TargetPath: targetPath,
PromotionDomain: promotionConfig.PromotionDomain,
}
// 根据类型设置对应字段
if linkType == 1 {
// 推广报告类型
shortLink.LinkId = sql.NullString{String: linkId, Valid: linkId != ""}
if linkIdentifier != "" {
shortLink.LinkIdentifier = sql.NullString{String: linkIdentifier, Valid: true}
}
} else if linkType == 2 {
// 邀请好友类型
shortLink.InviteCodeId = sql.NullString{String: inviteCodeId, Valid: inviteCodeId != ""}
if inviteCode != "" {
shortLink.InviteCode = sql.NullString{String: inviteCode, Valid: true}
}
}
_, err := l.svcCtx.AgentShortLinkModel.Insert(l.ctx, nil, shortLink)
if err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "保存短链失败, %v", err)
}
return l.buildShortLinkURL(shortCode), nil
}
// buildShortLinkURL 构建短链URL
func (l *GeneratingLinkLogic) buildShortLinkURL(shortCode string) string {
promotionConfig := l.svcCtx.Config.Promotion
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, shortCode)
}

View File

@@ -0,0 +1,157 @@
package agent
import (
"context"
"fmt"
"time"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentDashboardLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentDashboardLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentDashboardLogic {
return &GetAgentDashboardLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentDashboardLogic) GetAgentDashboard() (resp *types.AgentDashboardResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 查询代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您还不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 查询钱包信息
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包信息失败, %v", err)
}
if wallet == nil {
// 如果没有钱包,创建一个空的
wallet = &model.AgentWallet{
Balance: 0,
TotalEarnings: 0,
}
}
// 获取今日统计数据(使用佣金表)
today := time.Now().Format("2006-01-02")
todayStart, _ := time.Parse("2006-01-02", today)
todayEnd := todayStart.Add(24 * time.Hour)
// 今日订单数和佣金
todayOrders, todayEarnings, err := l.getCommissionStats(l.ctx, agent.Id, todayStart, todayEnd)
if err != nil {
logx.Errorf("获取今日佣金统计失败: %v", err)
todayOrders = 0
todayEarnings = 0
}
// 本月订单数和佣金
monthStart := time.Now().AddDate(0, 0, -time.Now().Day()+1) // 本月1号
monthStart = time.Date(monthStart.Year(), monthStart.Month(), monthStart.Day(), 0, 0, 0, 0, monthStart.Location())
_, monthEarnings, err := l.getCommissionStats(l.ctx, agent.Id, monthStart, time.Now())
if err != nil {
logx.Errorf("获取本月佣金统计失败: %v", err)
monthEarnings = 0
}
// 总订单数和佣金
totalOrders, totalEarnings, err := l.getCommissionStats(l.ctx, agent.Id, time.Time{}, time.Now())
if err != nil {
logx.Errorf("获取总佣金统计失败: %v", err)
totalOrders = 0
totalEarnings = 0
}
// 构建推广链接
domain := l.svcCtx.Config.Promotion.OfficialDomain
promotionLink := fmt.Sprintf("%s/pages/query/index?agentCode=%d", domain, agent.AgentCode)
// 获取佣金率从配置中读取默认为20%
commissionRate := l.getCommissionRate()
return &types.AgentDashboardResp{
Statistics: types.DashboardStatistics{
TodayOrders: todayOrders,
TodayEarnings: fmt.Sprintf("%.2f", todayEarnings),
MonthEarnings: fmt.Sprintf("%.2f", monthEarnings),
TotalOrders: totalOrders,
TotalEarnings: fmt.Sprintf("%.2f", totalEarnings),
CommissionRate: commissionRate,
CurrentBalance: fmt.Sprintf("%.2f", wallet.Balance),
FrozenBalance: fmt.Sprintf("%.2f", wallet.FrozenAmount),
},
PromotionLink: promotionLink,
Product: types.DashboardProduct{
Name: "个人大数据",
Price: "29.90",
Description: "全面个人数据风险报告",
},
}, nil
}
// getCommissionStats 获取指定时间段的佣金统计
func (l *GetAgentDashboardLogic) getCommissionStats(ctx context.Context, agentId string, startTime, endTime time.Time) (int64, float64, error) {
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
// 添加代理ID条件
builder = builder.Where("agent_id = ?", agentId)
// 添加时间范围条件
if !startTime.IsZero() {
builder = builder.Where("create_time >= ?", startTime)
}
if !endTime.IsZero() {
builder = builder.Where("create_time < ?", endTime)
}
// 只统计已发放的佣金
builder = builder.Where("status = ?", 1)
// 获取数量
count, err := l.svcCtx.AgentCommissionModel.FindCount(ctx, builder, "id")
if err != nil {
return 0, 0, err
}
// 获取总额
sum, err := l.svcCtx.AgentCommissionModel.FindSum(ctx, builder, "amount")
if err != nil {
return count, 0, err
}
return count, sum, nil
}
// getCommissionRate 获取佣金率
func (l *GetAgentDashboardLogic) getCommissionRate() string {
// TODO: 从配置或数据库中读取佣金率
// 目前返回固定值20%
return "20%"
}

View File

@@ -0,0 +1,79 @@
package agent
import (
"context"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentInfoLogic {
return &GetAgentInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentInfoLogic) GetAgentInfo() (resp *types.AgentInfoResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 不是代理,返回空信息
return &types.AgentInfoResp{
AgentId: "",
Region: "",
Mobile: "",
WechatId: "",
AgentCode: 0,
}, nil
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 解密手机号
mobile, err := crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密手机号失败: %v", err)
}
// 系统简化:移除实名认证查询
// 获取微信号
wechatId := ""
if agent.WechatId.Valid {
wechatId = agent.WechatId.String
}
// 获取区域
region := ""
if agent.Region.Valid {
region = agent.Region.String
}
return &types.AgentInfoResp{
AgentId: agent.Id,
Region: region,
Mobile: mobile,
WechatId: wechatId,
AgentCode: agent.AgentCode,
}, nil
}

View File

@@ -0,0 +1,99 @@
package agent
import (
"context"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentProductConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentProductConfigLogic {
return &GetAgentProductConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentProductConfigResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 验证用户是否为代理
_, err = l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 系统简化:移除等级加成,直接使用基础配置
// 2. 查询所有产品配置
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder()
productConfigs, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置失败, %v", err)
}
// 3. 组装响应数据(通过 product_id 查询产品名称和英文标识)
var respList []types.ProductConfigItem
for _, productConfig := range productConfigs {
// 通过 product_id 查询产品信息获取产品名称和英文标识
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, productConfig.ProductId)
productName := ""
productEn := ""
if err == nil {
productName = product.ProductName
productEn = product.ProductEn
} else {
// 如果产品不存在,记录日志但不影响主流程
l.Infof("查询产品信息失败, productId: %d, err: %v", productConfig.ProductId, err)
}
// 系统简化:直接使用基础底价和系统最高价
actualBasePrice := productConfig.BasePrice
priceRangeMin := actualBasePrice
priceRangeMax := productConfig.SystemMaxPrice
// 使用产品配置的提价阈值和手续费比例如果为NULL则使用0
productPriceThreshold := 0.0
if productConfig.PriceThreshold.Valid {
productPriceThreshold = productConfig.PriceThreshold.Float64
}
productPriceFeeRate := 0.0
if productConfig.PriceFeeRate.Valid {
productPriceFeeRate = productConfig.PriceFeeRate.Float64
}
respList = append(respList, types.ProductConfigItem{
ProductId: productConfig.ProductId,
ProductName: productName,
ProductEn: productEn,
ActualBasePrice: actualBasePrice,
PriceRangeMin: priceRangeMin,
PriceRangeMax: priceRangeMax,
PriceThreshold: productPriceThreshold,
PriceFeeRate: productPriceFeeRate,
})
}
return &types.AgentProductConfigResp{
List: respList,
}, nil
}

View File

@@ -0,0 +1,112 @@
package agent
import (
"context"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetCommissionListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetCommissionListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCommissionListLogic {
return &GetCommissionListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetCommissionListLogic) GetCommissionList(req *types.GetCommissionListReq) (resp *types.GetCommissionListResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 构建查询条件
builder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo).
OrderBy("create_time DESC")
// 3. 分页查询
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentCommissionModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询佣金总数失败, %v", err)
}
// 5. 查询列表
builder = builder.Limit(uint64(pageSize)).Offset(uint64(offset))
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询佣金列表失败, %v", err)
}
// 6. 组装响应
var list []types.CommissionItem
for _, commission := range commissions {
// 查询产品名称
productName := ""
if commission.ProductId != "" {
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, commission.ProductId)
if err == nil {
productName = product.ProductName
}
}
// 查询订单号
orderNo := ""
if commission.OrderId != "" {
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, commission.OrderId)
if err == nil {
orderNo = order.OrderNo
}
}
list = append(list, types.CommissionItem{
Id: commission.Id,
OrderId: commission.OrderId,
OrderNo: orderNo,
ProductName: productName,
Amount: commission.Amount,
Status: commission.Status,
CreateTime: commission.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return &types.GetCommissionListResp{
Total: total,
List: list,
}, nil
}

View File

@@ -0,0 +1,102 @@
package agent
import (
"context"
"sort"
"jnc-server/app/main/model"
"jnc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/mr"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetLinkDataLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetLinkDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLinkDataLogic {
return &GetLinkDataLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetLinkDataLogic) GetLinkData(req *types.GetLinkDataReq) (resp *types.GetLinkDataResp, err error) {
agentLinkModel, err := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, req.LinkIdentifier)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据失败, %v", err)
}
productModel, err := l.svcCtx.ProductModel.FindOne(l.ctx, agentLinkModel.ProductId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取产品信息失败, %v", err)
}
// 获取产品功能列表
build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{
"product_id": productModel.Id,
})
productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取产品功能列表失败, %v", err)
}
// 创建featureId到sort的映射用于后续排序
featureSortMap := make(map[string]int64)
for _, productFeature := range productFeatureAll {
featureSortMap[productFeature.FeatureId] = productFeature.Sort
}
var features []types.Feature
mr.MapReduceVoid(func(source chan<- interface{}) {
for _, productFeature := range productFeatureAll {
source <- productFeature.FeatureId
}
}, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) {
id := item.(string)
feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id)
if findFeatureErr != nil {
logx.WithContext(l.ctx).Errorf("获取产品功能失败: %s, err:%v", id, findFeatureErr)
return
}
if feature != nil && feature.Id != "" {
writer.Write(feature)
}
}, func(pipe <-chan *model.Feature, cancel func(error)) {
for item := range pipe {
var feature types.Feature
_ = copier.Copy(&feature, item)
features = append(features, feature)
}
})
// 按照productFeature.Sort字段对features进行排序
sort.Slice(features, func(i, j int) bool {
sortI := featureSortMap[features[i].ID]
sortJ := featureSortMap[features[j].ID]
return sortI < sortJ
})
return &types.GetLinkDataResp{
AgentId: agentLinkModel.AgentId,
ProductId: agentLinkModel.ProductId,
SetPrice: agentLinkModel.SetPrice,
ActualBasePrice: agentLinkModel.ActualBasePrice,
ProductName: productModel.ProductName,
ProductEn: productModel.ProductEn,
SellPrice: agentLinkModel.SetPrice, // 使用代理设定价格作为销售价格
Description: productModel.Description,
Features: features,
}, nil
}

View File

@@ -0,0 +1,125 @@
package agent
import (
"context"
"encoding/hex"
"encoding/json"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
)
type GetPromotionQueryListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetPromotionQueryListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionQueryListLogic {
return &GetPromotionQueryListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetPromotionQueryListLogic) GetPromotionQueryList(req *types.GetPromotionQueryListReq) (resp *types.GetPromotionQueryListResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 查询当前代理的代理订单,按创建时间倒序分页
builder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo)
orders, _, err := l.svcCtx.AgentOrderModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理订单失败, %v", err)
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"获取AES密钥失败: %v", decodeErr)
}
// 组装查询报告列表(只展示已创建的查询)
list := make([]types.PromotionQueryItem, 0, len(orders))
for _, ao := range orders {
// 查询对应的报告
q, qErr := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, ao.OrderId)
if qErr != nil {
if errors.Is(qErr, model.ErrNotFound) {
// 订单对应的查询尚未创建,跳过展示
continue
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询报告失败, %v", qErr)
}
// 查询订单信息获取order_no
order, oErr := l.svcCtx.OrderModel.FindOne(l.ctx, ao.OrderId)
if oErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单信息失败, %v", oErr)
}
// 获取产品名称
product, pErr := l.svcCtx.ProductModel.FindOne(l.ctx, ao.ProductId)
if pErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品信息失败, %v", pErr)
}
item := types.PromotionQueryItem{}
_ = copier.Copy(&item, q)
item.Id = q.Id
item.OrderId = q.OrderId
item.OrderNo = order.OrderNo
item.ProductName = product.ProductName
item.OrderAmount = order.Amount // 订单金额
item.Amount = ao.AgentProfit // 推广收益
item.CreateTime = q.CreateTime.Format("2006-01-02 15:04:05")
item.QueryState = q.QueryState
// 解析query_params获取查询人信息
if q.QueryParams != "" {
queryParamsByte, err := crypto.AesDecrypt(q.QueryParams, key)
var queryParams map[string]interface{}
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密查询参数失败, %v", err)
}
if err := json.Unmarshal(queryParamsByte, &queryParams); err == nil {
// 获取姓名
if name, ok := queryParams["name"].(string); ok {
item.QueryName = name
}
// 获取手机号
if mobile, ok := queryParams["mobile"].(string); ok {
item.QueryMobile = mobile
}
}
}
list = append(list, item)
}
return &types.GetPromotionQueryListResp{
Total: int64(len(list)), // 前端仅展示已创建查询条目
List: list,
}, nil
}

View File

@@ -0,0 +1,84 @@
package agent
import (
"context"
"time"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetRevenueInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetRevenueInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRevenueInfoLogic {
return &GetRevenueInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetRevenueInfoLogic) GetRevenueInfo() (resp *types.GetRevenueInfoResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 获取钱包信息
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包信息失败, %v", err)
}
// 获取当前时间
now := time.Now()
// 今日开始时间00:00:00
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
// 本月开始时间1号 00:00:00
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 3. 统计佣金总额(从 agent_commission 表统计)
commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo)
commissionTotal, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionBuilder, "amount")
// 3.1 统计佣金今日收益
commissionTodayBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, todayStart)
commissionToday, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionTodayBuilder, "amount")
// 3.2 统计佣金本月收益
commissionMonthBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, monthStart)
commissionMonth, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionMonthBuilder, "amount")
// 系统简化:移除返佣功能(推广返佣和升级返佣)
return &types.GetRevenueInfoResp{
Balance: wallet.Balance,
TotalEarnings: wallet.TotalEarnings,
CommissionTotal: commissionTotal, // 佣金累计总收益
CommissionToday: commissionToday, // 佣金今日收益
CommissionMonth: commissionMonth, // 佣金本月收益
}, nil
}

View File

@@ -0,0 +1,127 @@
package agent
import (
"context"
"fmt"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/ctxdata"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetWithdrawListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetWithdrawListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWithdrawListLogic {
return &GetWithdrawListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetWithdrawListLogic) GetWithdrawList(req *types.GetWithdrawListReq) (resp *types.GetWithdrawListResp, err error) {
// 1. 获取当前用户ID代理ID
userId, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err)
}
// 2. 查询代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 不是代理,返回空列表
return &types.GetWithdrawListResp{
Total: 0,
List: []types.WithdrawItem{},
}, nil
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理信息失败: %v", err)
}
// 3. 构建查询条件
builder := l.svcCtx.AgentWithdrawModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo).
OrderBy("create_time DESC")
// 4. 分页参数
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
// 5. 查询总数
total, err := l.svcCtx.AgentWithdrawModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录总数失败: %v", err)
}
if total == 0 {
return &types.GetWithdrawListResp{
Total: 0,
List: []types.WithdrawItem{},
}, nil
}
// 6. 查询列表
builder = builder.Limit(uint64(pageSize)).Offset(uint64(offset))
withdrawals, err := l.svcCtx.AgentWithdrawModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录列表失败: %v", err)
}
// 7. 组装响应
var list []types.WithdrawItem
for _, withdrawal := range withdrawals {
// 处理银行卡号脱敏只显示前4位和后4位
bankCardNumber := withdrawal.BankCardNumber
if len(bankCardNumber) > 8 {
bankCardNumber = fmt.Sprintf("%s****%s", bankCardNumber[:4], bankCardNumber[len(bankCardNumber)-4:])
}
// 处理审核备注
auditRemark := ""
if withdrawal.AuditRemark.Valid {
auditRemark = withdrawal.AuditRemark.String
}
// 处理审核时间
auditTime := ""
if withdrawal.AuditTime.Valid {
auditTime = withdrawal.AuditTime.Time.Format("2006-01-02 15:04:05")
}
list = append(list, types.WithdrawItem{
Id: withdrawal.Id,
WithdrawAmount: fmt.Sprintf("%.2f", withdrawal.WithdrawAmount),
TaxAmount: fmt.Sprintf("%.2f", withdrawal.TaxAmount),
ActualAmount: fmt.Sprintf("%.2f", withdrawal.ActualAmount),
AccountName: withdrawal.AccountName,
BankCardNumber: bankCardNumber,
Status: withdrawal.Status,
AuditRemark: auditRemark,
CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"),
AuditTime: auditTime,
})
}
return &types.GetWithdrawListResp{
Total: total,
List: list,
}, nil
}

View File

@@ -0,0 +1,102 @@
package agent
import (
"context"
"fmt"
"net/http"
"strings"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"jnc-server/app/main/api/internal/svc"
)
type ShortLinkRedirectLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewShortLinkRedirectLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ShortLinkRedirectLogic {
return &ShortLinkRedirectLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// ShortLinkRedirect 短链重定向
// 从短链重定向到推广页面
func (l *ShortLinkRedirectLogic) ShortLinkRedirect(shortCode string, r *http.Request, w http.ResponseWriter) error {
// 1. 验证短链标识
if shortCode == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "缺少短链标识")
}
// 2. 查询短链记录
shortLink, err := l.svcCtx.AgentShortLinkModel.FindOneByShortCodeDelState(l.ctx, shortCode, globalkey.DelStateNo)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "短链不存在或已失效")
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询短链失败, %v", err)
}
// 3. 根据类型验证链接有效性
if shortLink.Type == 1 {
// 推广报告类型验证linkIdentifier是否存在
if shortLink.LinkIdentifier.Valid && shortLink.LinkIdentifier.String != "" {
_, err = l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, shortLink.LinkIdentifier.String)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "推广链接不存在或已失效")
}
l.Errorf("查询推广链接失败: %v", err)
// 即使查询失败,也继续重定向,避免影响用户体验
}
}
}
// 系统简化:移除邀请码验证功能
// 4. 构建重定向URL
redirectURL := shortLink.TargetPath
if redirectURL == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短链目标地址为空")
}
// 如果 target_path 是相对路径,需要拼接正式域名
// 如果 target_path 已经是完整URL则直接使用
if !strings.HasPrefix(redirectURL, "http://") && !strings.HasPrefix(redirectURL, "https://") {
// 相对路径,需要拼接正式域名
officialDomain := l.svcCtx.Config.Promotion.OfficialDomain
if officialDomain == "" {
// 如果没有配置正式域名,使用当前请求的域名(向后兼容)
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
officialDomain = fmt.Sprintf("%s://%s", scheme, r.Host)
}
// 确保正式域名不以 / 结尾
officialDomain = strings.TrimSuffix(officialDomain, "/")
// 确保 target_path 以 / 开头
if !strings.HasPrefix(redirectURL, "/") {
redirectURL = "/" + redirectURL
}
redirectURL = officialDomain + redirectURL
}
// 5. 执行重定向302临时重定向
linkIdentifierStr := ""
if shortLink.LinkIdentifier.Valid {
linkIdentifierStr = shortLink.LinkIdentifier.String
}
l.Infof("短链重定向: shortCode=%s, type=%d, linkIdentifier=%s, redirectURL=%s", shortCode, shortLink.Type, linkIdentifierStr, redirectURL)
http.Redirect(w, r, redirectURL, http.StatusFound)
return nil
}

View File

@@ -0,0 +1,43 @@
package app
import (
"context"
"strconv"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAppConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAppConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAppConfigLogic {
return &GetAppConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAppConfigLogic) GetAppConfig() (resp *types.GetAppConfigResp, err error) {
retentionDays := int64(0)
cfg, cfgErr := l.svcCtx.QueryCleanupConfigModel.FindOneByConfigKey(l.ctx, "retention_days")
if cfgErr == nil && cfg.Status == 1 {
if v, parseErr := strconv.ParseInt(cfg.ConfigValue, 10, 64); parseErr == nil && v >= 0 {
retentionDays = v
}
} else if cfgErr != nil && cfgErr != model.ErrNotFound {
l.Errorf("获取清理配置失败: %v", cfgErr)
}
return &types.GetAppConfigResp{
QueryRetentionDays: retentionDays,
WechatH5LoginEnabled: l.svcCtx.Config.WechatH5.Enabled,
}, nil
}

View File

@@ -0,0 +1,31 @@
package app
import (
"context"
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAppVersionLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAppVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAppVersionLogic {
return &GetAppVersionLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAppVersionLogic) GetAppVersion() (resp *types.GetAppVersionResp, err error) {
return &types.GetAppVersionResp{
Version: "1.0.0",
WgtUrl: "https://www.dsjcq168.cn/app_version/jnc_1.0.0.wgt",
}, nil
}

Some files were not shown because too many files have changed in this diff Show More