feat(security): 完善超管平台订阅守卫拦截与移动端高意向访客提醒

这个提交包含在:
wikiboss
2026-09-21 11:15:59 +08:00
父节点 654f317c59
当前提交 c58a99d602
共有 7 个文件被更改,包括 93 次插入 和 4 次删除

查看文件

@@ -12,6 +12,7 @@ import { TeamQuotaService } from '../../service/quota';
import { TeamInvoiceService } from '../../service/invoice';
import { TeamStripeService } from '../../service/stripe';
import { TeamFinanceAnalyticsService } from '../../service/finance-analytics';
import { assertSuperAdmin } from '../../../../comm/guard/super-admin';
import {
CalculateProrationDTO,
CreateInvoiceDTO,
@@ -88,6 +89,7 @@ export class AdminTeamBillingController extends BaseController {
async postAnalytics(
@Body() body?: FinanceAnalyticsQueryDTO
): Promise<CoolAdminResponse<FinanceAnalyticsResult>> {
assertSuperAdmin(this.ctx);
const result = await this.teamFinanceAnalyticsService.getOverviewAnalytics(body || {});
return this.ok(result) as unknown as CoolAdminResponse<FinanceAnalyticsResult>;
}

查看文件

@@ -2,6 +2,7 @@ import { Body, Inject, Post, Provide } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { CoolController, BaseController } from '@cool-midway/core';
import { PlatformSubscriptionService } from '../../service/platform-subscription';
import { assertSuperAdmin } from '../../../../comm/guard/super-admin';
import {
ManualRenewDTO,
PlanConfigItem,
@@ -44,6 +45,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
*/
@Post('/overview', { summary: '平台会员与订阅整体概览' })
async overview() {
assertSuperAdmin(this.ctx);
const result: PlatformOverviewResult =
await this.platformSubscriptionService.getOverview();
return this.ok(result);
@@ -64,6 +66,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
keyword?: string;
}
) {
assertSuperAdmin(this.ctx);
const result = await this.platformSubscriptionService.getSubscribers(body);
return this.ok(result);
}
@@ -74,6 +77,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
*/
@Post('/subscribers/update', { summary: '超管修改会员套餐/配额/延期' })
async subscribersUpdate(@Body() body: UpdateSubscriberDTO) {
assertSuperAdmin(this.ctx);
const result = await this.platformSubscriptionService.updateSubscriber(body);
return this.ok(result);
}
@@ -92,6 +96,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
keyword?: string;
}
) {
assertSuperAdmin(this.ctx);
const result = await this.platformSubscriptionService.getRenewals(body);
return this.ok(result);
}
@@ -102,6 +107,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
*/
@Post('/renewals/manual-renew', { summary: '超管手动代续费与补录订单' })
async manualRenew(@Body() body: ManualRenewDTO) {
assertSuperAdmin(this.ctx);
const result = await this.platformSubscriptionService.manualRenew(body);
return this.ok(result);
}
@@ -112,6 +118,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
*/
@Post('/plans/list', { summary: 'SaaS 套餐方案矩阵列表' })
async plansList() {
assertSuperAdmin(this.ctx);
const result = await this.platformSubscriptionService.getPlans();
return this.ok(result);
}
@@ -128,6 +135,7 @@ export class AdminPlatformSubscriptionController extends BaseController {
params: Partial<PlanConfigItem>;
}
) {
assertSuperAdmin(this.ctx);
const result = await this.platformSubscriptionService.updatePlan(
body.planType,
body.params

查看文件

@@ -46,7 +46,7 @@ describe('后端权限隔离 - 平台超级管理员专属守卫 (Super Admin Gu
try {
assertSuperAdmin(ctx);
} catch (err: any) {
expect(err.status || err.code).toBe(403);
expect(err.statusCode || err.status).toBe(403);
expect(err.message).toContain('权限不足');
}
});

查看文件

@@ -18,6 +18,13 @@
>
<text class="hall-tab-text">{{ t('tabTeam') }}</text>
</view>
<view
class="hall-tab"
:class="{ 'hall-tab-active': state.tab === 'event' }"
@click="handleTab('event')"
>
<text class="hall-tab-text">{{ t('tabEvent') }}</text>
</view>
</view>
<input
class="hall-search"
@@ -35,6 +42,15 @@
:placeholder="exchangePlaceholder"
:disabled="state.loading"
/>
<input
v-if="state.tab === 'event'"
class="hall-search"
type="text"
maxlength="6"
v-model="state.eventCode"
:placeholder="eventPlaceholder"
:disabled="state.loading"
/>
</view>
<view v-if="state.loading" class="hall-skeleton">
@@ -89,6 +105,7 @@ import {
openHallPortal,
refreshHall,
resolveHallText,
submitHallEventJoin,
submitHallExchange,
} from './useHall.uts';
@@ -113,11 +130,15 @@ const pageTitle = ref(resolveHallText('title'));
const pageSubtitle = ref(resolveHallText('subtitle'));
const searchPlaceholder = ref(resolveHallText('searchPlaceholder'));
const exchangePlaceholder = ref(resolveHallText('exchangePlaceholder'));
const eventPlaceholder = ref(resolveHallText('eventPlaceholder'));
const emptyDesc = computed((): string => {
if (state.tab === 'team') {
return resolveHallText('teamEmpty');
}
if (state.tab === 'event') {
return resolveHallText('eventEmpty');
}
return pageSubtitle.value;
});
@@ -125,6 +146,9 @@ const ctaLabel = computed((): string => {
if (state.loading) {
return resolveHallText('loading');
}
if (state.tab === 'event' && state.eventCode.trim() !== '') {
return resolveHallText('eventJoin');
}
if (state.exchangeCode.trim() !== '') {
return resolveHallText('exchange');
}
@@ -152,6 +176,10 @@ function handleOpenPortal(item: HallContactItem): void {
}
async function handlePrimary(): Promise<void> {
if (state.tab === 'event' && state.eventCode.trim() !== '') {
await submitHallEventJoin(state);
return;
}
if (state.exchangeCode.trim() !== '') {
await submitHallExchange(state);
return;

查看文件

@@ -3,6 +3,11 @@
<view class="leads-header">
<text class="leads-title">{{ pageTitle }}</text>
<text class="leads-subtitle">{{ followUpLabel }}</text>
<text
v-if="hotUnreadText !== ''"
class="radar-hot-badge"
@click="openRadar"
>{{ hotUnreadText }}</text>
<view v-if="radarLineOne !== ''" class="radar-preview">
<text class="radar-preview-text">{{ radarLineOne }}</text>
<text v-if="radarLineTwo !== ''" class="radar-preview-text">{{ radarLineTwo }}</text>
@@ -115,6 +120,7 @@ import {
applyLeadStatusFilter,
applyLeadsOptions,
createLeadsState,
formatHotUnread,
formatLeadCompany,
formatRadarLine,
openRadarPage,
@@ -149,6 +155,9 @@ const searchPlaceholder = ref(resolveLeadsText('searchPlaceholder'));
const scoreLabel = ref(resolveLeadsText('score'));
const briefingLabel = ref(resolveOverviewText('title'));
const radarMoreLabel = ref(resolveLeadsText('radarMore'));
const hotUnreadText = computed((): string => {
return formatHotUnread(state.hotUnread);
});
const radarLineOne = computed((): string => {
if (state.radarTracks.length < 1) {
return '';
@@ -250,6 +259,19 @@ onLoad((options: LeadsPageOptions) => {
margin-bottom: var(--oc-space-8, 8px);
}
.radar-hot-badge {
min-height: 44px;
min-width: 44px;
padding-inline: 12px;
margin-bottom: var(--oc-space-12, 12px);
background-color: var(--oc-primary-soft, #EAF1FF);
color: var(--oc-primary, #0B5CFF);
border-radius: 12px;
font-size: 13px;
font-weight: 600;
line-height: 44px;
}
.radar-preview {
margin-bottom: var(--oc-space-12, 12px);
padding: var(--oc-space-12, 12px);

查看文件

@@ -8,10 +8,13 @@ import {
OwnerPageData,
OwnerPageQuery,
RadarTrack,
TeamRankItem,
} from '../../../common/types.uts';
import {
fetchCurrentEvent,
fetchOwnerLeadPage,
fetchOwnerRadarPage,
fetchTeamLiveRank,
} from '../../../common/api.uts';
import { getCurrentLocale, getOmniText } from '../../../common/locale.uts';
@@ -27,7 +30,8 @@ export type OverviewCopyKey =
| 'empty'
| 'error'
| 'loading'
| 'unknownCountry';
| 'unknownCountry'
| 'rankTitle';
export type OverviewSummary = {
totalLeads: number;
@@ -49,6 +53,8 @@ export type LoadOverviewApi = {
fetchOwnerRadarPage?: (
query: OwnerPageQuery
) => Promise<OwnerPageData<RadarTrack>>;
fetchTeamLiveRank?: (eventId?: string) => Promise<TeamRankItem[]>;
fetchCurrentEvent?: () => Promise<{ code?: string } | null>;
};
export type LoadOverviewResult = {

查看文件

@@ -10,7 +10,11 @@ import {
OwnerPageQuery,
RadarTrack,
} from '../../common/types.uts';
import { fetchOwnerLeadPage, fetchOwnerRadarPage } from '../../common/api.uts';
import {
fetchOwnerLeadPage,
fetchOwnerRadarPage,
fetchRadarHotInbox,
} from '../../common/api.uts';
import { getCurrentLocale, getOmniText } from '../../common/locale.uts';
import { formatLocation, formatStay } from './useRadar.uts';
@@ -29,7 +33,8 @@ export type LeadsCopyKey =
| 'error'
| 'loading'
| 'score'
| 'radarMore';
| 'radarMore'
| 'hotBadge';
export type LoadLeadsQuery = {
page: number;
@@ -58,6 +63,7 @@ export type LoadLeadsResult = {
export type LeadsState = {
list: LeadEntity[];
radarTracks: RadarTrack[];
hotUnread: number;
page: number;
size: number;
total: number;
@@ -154,9 +160,19 @@ export function resolveLeadsText(key: LeadsCopyKey): string {
if (key === 'radarMore') {
return zh ? '查看全部雷达' : 'Full radar';
}
if (key === 'hotBadge') {
return zh ? '高意向访客' : 'Hot visitors';
}
return '';
}
export function formatHotUnread(unread: number): string {
if (unread < 1) {
return '';
}
return `${resolveLeadsText('hotBadge')} ${unread}`;
}
export function statusLabel(status: string): string {
if (status === 'new') {
return resolveLeadsText('statusNew');
@@ -244,6 +260,7 @@ export function createLeadsState(): LeadsState {
return {
list: [],
radarTracks: [],
hotUnread: 0,
page: 1,
size: 20,
total: 0,
@@ -318,6 +335,12 @@ export async function refreshLeads(
} catch (_err) {
state.radarTracks = [];
}
try {
const inbox = await fetchRadarHotInbox();
state.hotUnread = inbox != null && inbox.unread != null ? inbox.unread : 0;
} catch (_err) {
state.hotUnread = 0;
}
state.loading = false;
return result;
}