权限相关
This commit is contained in:
parent
7744232add
commit
152bdf232e
@ -122,6 +122,13 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
limitSQL := "LIMIT ? OFFSET ?"
|
||||||
|
queryArgs := append(args, query.PageSize, offset(query.Page, query.PageSize))
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
// 工资钱包余额来自 wallet-service 读模型,不能只排序 SQL 当前页;先取出所有匹配身份,补齐余额后再分页。
|
||||||
|
limitSQL = ""
|
||||||
|
queryArgs = args
|
||||||
|
}
|
||||||
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
||||||
SELECT bp.user_id, bp.role, `+userCountryRegionIDSQL("user_region")+`, COALESCE(bp.parent_leader_user_id, 0),
|
SELECT bp.user_id, bp.role, `+userCountryRegionIDSQL("user_region")+`, COALESCE(bp.parent_leader_user_id, 0),
|
||||||
bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms,
|
bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms,
|
||||||
@ -140,8 +147,8 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin
|
|||||||
)
|
)
|
||||||
%s
|
%s
|
||||||
ORDER BY bp.created_at_ms DESC, bp.user_id DESC
|
ORDER BY bp.created_at_ms DESC, bp.user_id DESC
|
||||||
LIMIT ? OFFSET ?
|
%s
|
||||||
`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...)
|
`, whereSQL, limitSQL), queryArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@ -189,6 +196,10 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin
|
|||||||
if err := r.fillBDProfileSalaryWallets(ctx, role, items); err != nil {
|
if err := r.fillBDProfileSalaryWallets(ctx, role, items); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
sortBDProfilesBySalaryWallet(items, query.SortDirection)
|
||||||
|
items = paginateBDProfiles(items, query.Page, query.PageSize)
|
||||||
|
}
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,6 +236,13 @@ func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]*
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
limitSQL := "LIMIT ? OFFSET ?"
|
||||||
|
queryArgs := append(args, query.PageSize, offset(query.Page, query.PageSize))
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
// BD Leader 工资余额同样在 wallet-service;排序必须等 Admin 工资钱包资产填充完成后再裁剪当前页。
|
||||||
|
limitSQL = ""
|
||||||
|
queryArgs = args
|
||||||
|
}
|
||||||
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
||||||
SELECT bp.user_id, 'bd_leader', `+userCountryRegionIDSQL("user_region")+`, 0,
|
SELECT bp.user_id, 'bd_leader', `+userCountryRegionIDSQL("user_region")+`, 0,
|
||||||
bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms,
|
bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms,
|
||||||
@ -242,8 +260,8 @@ func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]*
|
|||||||
)
|
)
|
||||||
%s
|
%s
|
||||||
ORDER BY bp.created_at_ms DESC, bp.user_id DESC
|
ORDER BY bp.created_at_ms DESC, bp.user_id DESC
|
||||||
LIMIT ? OFFSET ?
|
%s
|
||||||
`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...)
|
`, whereSQL, limitSQL), queryArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@ -289,6 +307,10 @@ func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]*
|
|||||||
if err := r.fillBDProfileSalaryWallets(ctx, salaryWalletRoleBDLeader, items); err != nil {
|
if err := r.fillBDProfileSalaryWallets(ctx, salaryWalletRoleBDLeader, items); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
sortBDProfilesBySalaryWallet(items, query.SortDirection)
|
||||||
|
items = paginateBDProfiles(items, query.Page, query.PageSize)
|
||||||
|
}
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,6 +601,13 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
limitSQL := "LIMIT ? OFFSET ?"
|
||||||
|
queryArgs := append(args, query.PageSize, offset(query.Page, query.PageSize))
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
// Agency 展示的是 owner 的 Agency 工资钱包;先补余额再排序,保证跨页顺序和真实余额一致。
|
||||||
|
limitSQL = ""
|
||||||
|
queryArgs = args
|
||||||
|
}
|
||||||
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
||||||
SELECT a.agency_id, a.owner_user_id, `+userCountryRegionIDSQL("owner_region")+`, a.parent_bd_user_id,
|
SELECT a.agency_id, a.owner_user_id, `+userCountryRegionIDSQL("owner_region")+`, a.parent_bd_user_id,
|
||||||
a.name, a.status, a.join_enabled, a.max_hosts,
|
a.name, a.status, a.join_enabled, a.max_hosts,
|
||||||
@ -589,8 +618,8 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie
|
|||||||
COALESCE(parent_bd.avatar, ''), `+userCountryRegionNameSQL("owner_region")+`
|
COALESCE(parent_bd.avatar, ''), `+userCountryRegionNameSQL("owner_region")+`
|
||||||
%s
|
%s
|
||||||
ORDER BY a.created_at_ms DESC, a.agency_id DESC
|
ORDER BY a.created_at_ms DESC, a.agency_id DESC
|
||||||
LIMIT ? OFFSET ?
|
%s
|
||||||
`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...)
|
`, whereSQL, limitSQL), queryArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@ -629,6 +658,10 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie
|
|||||||
if err := r.fillAgencySalaryWallets(ctx, items); err != nil {
|
if err := r.fillAgencySalaryWallets(ctx, items); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
sortAgenciesBySalaryWallet(items, query.SortDirection)
|
||||||
|
items = paginateAgencies(items, query.Page, query.PageSize)
|
||||||
|
}
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,7 +709,8 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
|
|||||||
}
|
}
|
||||||
limitSQL := "LIMIT ? OFFSET ?"
|
limitSQL := "LIMIT ? OFFSET ?"
|
||||||
queryArgs := append(args, query.PageSize, offset(query.Page, query.PageSize))
|
queryArgs := append(args, query.PageSize, offset(query.Page, query.PageSize))
|
||||||
if query.SortBy == "diamond" {
|
if query.SortBy == sortByDiamond || query.SortBy == sortBySalaryWallet {
|
||||||
|
// 主播钻石和工资余额都是列表外部聚合值;排序时取全量匹配集,聚合后稳定排序再分页。
|
||||||
limitSQL = ""
|
limitSQL = ""
|
||||||
queryArgs = args
|
queryArgs = args
|
||||||
}
|
}
|
||||||
@ -739,10 +773,14 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
|
|||||||
if err := r.fillHostSalaryWallets(ctx, items); err != nil {
|
if err := r.fillHostSalaryWallets(ctx, items); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
if query.SortBy == "diamond" {
|
if query.SortBy == sortByDiamond {
|
||||||
sortHostProfilesByDiamond(items, query.SortDirection)
|
sortHostProfilesByDiamond(items, query.SortDirection)
|
||||||
items = paginateHostProfiles(items, query.Page, query.PageSize)
|
items = paginateHostProfiles(items, query.Page, query.PageSize)
|
||||||
}
|
}
|
||||||
|
if query.SortBy == sortBySalaryWallet {
|
||||||
|
sortHostProfilesBySalaryWallet(items, query.SortDirection)
|
||||||
|
items = paginateHostProfiles(items, query.Page, query.PageSize)
|
||||||
|
}
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1131,6 +1169,50 @@ func coinSellerSortValue(item *CoinSellerListItem, sortBy string) int64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortBDProfilesBySalaryWallet 按当前身份的工资钱包可用余额排序;余额相同时保留 SQL 默认创建时间顺序,避免同值行跳动。
|
||||||
|
func sortBDProfilesBySalaryWallet(items []*userclient.BDProfile, direction string) {
|
||||||
|
sort.SliceStable(items, func(i, j int) bool {
|
||||||
|
left := bdProfileSalaryWalletValue(items[i])
|
||||||
|
right := bdProfileSalaryWalletValue(items[j])
|
||||||
|
if left == right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if direction == "asc" {
|
||||||
|
return left < right
|
||||||
|
}
|
||||||
|
return left > right
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func bdProfileSalaryWalletValue(item *userclient.BDProfile) int64 {
|
||||||
|
if item == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return item.SalaryWallet.AvailableAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortAgenciesBySalaryWallet 按 Agency owner 对应的 Agency 工资钱包余额排序,不混用 owner 的其他身份钱包。
|
||||||
|
func sortAgenciesBySalaryWallet(items []*userclient.Agency, direction string) {
|
||||||
|
sort.SliceStable(items, func(i, j int) bool {
|
||||||
|
left := agencySalaryWalletValue(items[i])
|
||||||
|
right := agencySalaryWalletValue(items[j])
|
||||||
|
if left == right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if direction == "asc" {
|
||||||
|
return left < right
|
||||||
|
}
|
||||||
|
return left > right
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func agencySalaryWalletValue(item *userclient.Agency) int64 {
|
||||||
|
if item == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return item.SalaryWallet.AvailableAmount
|
||||||
|
}
|
||||||
|
|
||||||
func sortHostProfilesByDiamond(items []*userclient.HostProfile, direction string) {
|
func sortHostProfilesByDiamond(items []*userclient.HostProfile, direction string) {
|
||||||
sort.SliceStable(items, func(i, j int) bool {
|
sort.SliceStable(items, func(i, j int) bool {
|
||||||
if items[i].Diamond == items[j].Diamond {
|
if items[i].Diamond == items[j].Diamond {
|
||||||
@ -1143,6 +1225,28 @@ func sortHostProfilesByDiamond(items []*userclient.HostProfile, direction string
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortHostProfilesBySalaryWallet 按主播工资钱包可用余额排序,和钻石排序一样在分页前处理跨页顺序。
|
||||||
|
func sortHostProfilesBySalaryWallet(items []*userclient.HostProfile, direction string) {
|
||||||
|
sort.SliceStable(items, func(i, j int) bool {
|
||||||
|
left := hostProfileSalaryWalletValue(items[i])
|
||||||
|
right := hostProfileSalaryWalletValue(items[j])
|
||||||
|
if left == right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if direction == "asc" {
|
||||||
|
return left < right
|
||||||
|
}
|
||||||
|
return left > right
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostProfileSalaryWalletValue(item *userclient.HostProfile) int64 {
|
||||||
|
if item == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return item.SalaryWallet.AvailableAmount
|
||||||
|
}
|
||||||
|
|
||||||
func paginateCoinSellerListItems(items []*CoinSellerListItem, page int, pageSize int) []*CoinSellerListItem {
|
func paginateCoinSellerListItems(items []*CoinSellerListItem, page int, pageSize int) []*CoinSellerListItem {
|
||||||
if page < 1 {
|
if page < 1 {
|
||||||
page = 1
|
page = 1
|
||||||
@ -1161,6 +1265,42 @@ func paginateCoinSellerListItems(items []*CoinSellerListItem, page int, pageSize
|
|||||||
return items[start:end]
|
return items[start:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func paginateBDProfiles(items []*userclient.BDProfile, page int, pageSize int) []*userclient.BDProfile {
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
start := (page - 1) * pageSize
|
||||||
|
if start >= len(items) {
|
||||||
|
return []*userclient.BDProfile{}
|
||||||
|
}
|
||||||
|
end := start + pageSize
|
||||||
|
if end > len(items) {
|
||||||
|
end = len(items)
|
||||||
|
}
|
||||||
|
return items[start:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
func paginateAgencies(items []*userclient.Agency, page int, pageSize int) []*userclient.Agency {
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
start := (page - 1) * pageSize
|
||||||
|
if start >= len(items) {
|
||||||
|
return []*userclient.Agency{}
|
||||||
|
}
|
||||||
|
end := start + pageSize
|
||||||
|
if end > len(items) {
|
||||||
|
end = len(items)
|
||||||
|
}
|
||||||
|
return items[start:end]
|
||||||
|
}
|
||||||
|
|
||||||
func paginateHostProfiles(items []*userclient.HostProfile, page int, pageSize int) []*userclient.HostProfile {
|
func paginateHostProfiles(items []*userclient.HostProfile, page int, pageSize int) []*userclient.HostProfile {
|
||||||
if page < 1 {
|
if page < 1 {
|
||||||
page = 1
|
page = 1
|
||||||
|
|||||||
@ -20,6 +20,7 @@ const (
|
|||||||
paidCurrencyUSDT = "USDT"
|
paidCurrencyUSDT = "USDT"
|
||||||
sortByDiamond = "diamond"
|
sortByDiamond = "diamond"
|
||||||
sortByMerchantBalance = "merchant_balance"
|
sortByMerchantBalance = "merchant_balance"
|
||||||
|
sortBySalaryWallet = "salary_wallet"
|
||||||
sortByTotalRechargeUSDT = "total_recharge_usdt"
|
sortByTotalRechargeUSDT = "total_recharge_usdt"
|
||||||
bdLeaderPositionAliasMaxRunes = 64
|
bdLeaderPositionAliasMaxRunes = 64
|
||||||
)
|
)
|
||||||
@ -589,7 +590,7 @@ func normalizeListQuery(query listQuery) listQuery {
|
|||||||
query.Status = strings.ToLower(strings.TrimSpace(query.Status))
|
query.Status = strings.ToLower(strings.TrimSpace(query.Status))
|
||||||
query.SortBy = strings.ToLower(strings.TrimSpace(query.SortBy))
|
query.SortBy = strings.ToLower(strings.TrimSpace(query.SortBy))
|
||||||
switch query.SortBy {
|
switch query.SortBy {
|
||||||
case sortByDiamond, sortByMerchantBalance, sortByTotalRechargeUSDT:
|
case sortByDiamond, sortByMerchantBalance, sortBySalaryWallet, sortByTotalRechargeUSDT:
|
||||||
default:
|
default:
|
||||||
query.SortBy = ""
|
query.SortBy = ""
|
||||||
query.SortDirection = ""
|
query.SortDirection = ""
|
||||||
|
|||||||
@ -3,11 +3,13 @@ package hostorg
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/integration/userclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestNormalizeListQueryAllowsCoinSellerComputedSorts 锁定币商余额和累充 USDT 排序字段会被后台列表查询接受。
|
// TestNormalizeListQueryAllowsComputedSorts 锁定后台列表支持的计算排序字段不会在查询归一化时被清掉。
|
||||||
func TestNormalizeListQueryAllowsCoinSellerComputedSorts(t *testing.T) {
|
func TestNormalizeListQueryAllowsComputedSorts(t *testing.T) {
|
||||||
for _, sortBy := range []string{sortByMerchantBalance, sortByTotalRechargeUSDT} {
|
for _, sortBy := range []string{sortByMerchantBalance, sortBySalaryWallet, sortByTotalRechargeUSDT} {
|
||||||
query := normalizeListQuery(listQuery{Page: 1, PageSize: 50, SortBy: sortBy, SortDirection: "asc"})
|
query := normalizeListQuery(listQuery{Page: 1, PageSize: 50, SortBy: sortBy, SortDirection: "asc"})
|
||||||
if query.SortBy != sortBy || query.SortDirection != "asc" {
|
if query.SortBy != sortBy || query.SortDirection != "asc" {
|
||||||
t.Fatalf("expected sort %s asc to be kept, got sort_by=%q direction=%q", sortBy, query.SortBy, query.SortDirection)
|
t.Fatalf("expected sort %s asc to be kept, got sort_by=%q direction=%q", sortBy, query.SortBy, query.SortDirection)
|
||||||
@ -96,3 +98,45 @@ func TestSortCoinSellerListItemsComputedFields(t *testing.T) {
|
|||||||
t.Fatalf("total recharge usdt asc order mismatch: %v", got)
|
t.Fatalf("total recharge usdt asc order mismatch: %v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSortSalaryWalletComputedFields 覆盖四个组织列表的工资钱包余额排序;这些余额来自 wallet-service,必须在分页前排序。
|
||||||
|
func TestSortSalaryWalletComputedFields(t *testing.T) {
|
||||||
|
bds := []*userclient.BDProfile{
|
||||||
|
{UserID: 1, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 100}},
|
||||||
|
{UserID: 2, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 300}},
|
||||||
|
{UserID: 3, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 200}},
|
||||||
|
}
|
||||||
|
sortBDProfilesBySalaryWallet(bds, "desc")
|
||||||
|
if got := []int64{bds[0].UserID, bds[1].UserID, bds[2].UserID}; got[0] != 2 || got[1] != 3 || got[2] != 1 {
|
||||||
|
t.Fatalf("bd salary wallet desc order mismatch: %v", got)
|
||||||
|
}
|
||||||
|
if page := paginateBDProfiles(bds, 2, 2); len(page) != 1 || page[0].UserID != 1 {
|
||||||
|
t.Fatalf("bd salary wallet pagination mismatch: %+v", page)
|
||||||
|
}
|
||||||
|
|
||||||
|
agencies := []*userclient.Agency{
|
||||||
|
{AgencyID: 10, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 100}},
|
||||||
|
{AgencyID: 20, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 300}},
|
||||||
|
{AgencyID: 30, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 200}},
|
||||||
|
}
|
||||||
|
sortAgenciesBySalaryWallet(agencies, "asc")
|
||||||
|
if got := []int64{agencies[0].AgencyID, agencies[1].AgencyID, agencies[2].AgencyID}; got[0] != 10 || got[1] != 30 || got[2] != 20 {
|
||||||
|
t.Fatalf("agency salary wallet asc order mismatch: %v", got)
|
||||||
|
}
|
||||||
|
if page := paginateAgencies(agencies, 2, 2); len(page) != 1 || page[0].AgencyID != 20 {
|
||||||
|
t.Fatalf("agency salary wallet pagination mismatch: %+v", page)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts := []*userclient.HostProfile{
|
||||||
|
{UserID: 1, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 100}},
|
||||||
|
{UserID: 2, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 300}},
|
||||||
|
{UserID: 3, SalaryWallet: userclient.SalaryWallet{AvailableAmount: 200}},
|
||||||
|
}
|
||||||
|
sortHostProfilesBySalaryWallet(hosts, "desc")
|
||||||
|
if got := []int64{hosts[0].UserID, hosts[1].UserID, hosts[2].UserID}; got[0] != 2 || got[1] != 3 || got[2] != 1 {
|
||||||
|
t.Fatalf("host salary wallet desc order mismatch: %v", got)
|
||||||
|
}
|
||||||
|
if page := paginateHostProfiles(hosts, 2, 2); len(page) != 1 || page[0].UserID != 1 {
|
||||||
|
t.Fatalf("host salary wallet pagination mismatch: %+v", page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||||||
|
|
||||||
|
-- 线上生产环境关闭 bootstrap 和 AutoMigrate,新按钮权限必须通过 migration 落库,
|
||||||
|
-- 否则前端即使已经上线,也会因为登录权限列表缺少该 code 而隐藏“工资兑换比例”按钮。
|
||||||
|
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
|
||||||
|
('币商工资兑换比例', 'coin-seller:exchange-rate', 'button', '', @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
kind = VALUES(kind),
|
||||||
|
description = VALUES(description),
|
||||||
|
updated_at_ms = @now_ms;
|
||||||
|
|
||||||
|
-- 平台管理员应拥有全部后台能力;运维管理员也需要维护币商工资转金币比例。
|
||||||
|
-- INSERT IGNORE 保持幂等,重复执行不会产生重复绑定。
|
||||||
|
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
|
||||||
|
SELECT admin_role.id, admin_permission.id
|
||||||
|
FROM admin_roles admin_role
|
||||||
|
JOIN admin_permissions admin_permission
|
||||||
|
WHERE admin_role.code IN ('platform-admin', 'ops-admin')
|
||||||
|
AND admin_permission.code = 'coin-seller:exchange-rate';
|
||||||
Loading…
x
Reference in New Issue
Block a user