小飞侠接口调通测试
This commit is contained in:
@@ -29,7 +29,7 @@ export class CourierConfigService {
|
||||
provider,
|
||||
xiaofeixia: {
|
||||
apiUrl:
|
||||
this.config.get<string>('XIAOFEIXIA_API_URL') ??
|
||||
this.config.get<string>('XIAOFEIXIA_API_URL')?.trim() ||
|
||||
'https://beta.51xiaoju.cn/app/api/interface.do',
|
||||
appId: this.config.get<string>('XIAOFEIXIA_APP_ID') || undefined,
|
||||
mchId: this.config.get<string>('XIAOFEIXIA_MCH_ID') ?? '',
|
||||
|
||||
@@ -66,7 +66,22 @@ export class XiaofeixiaClient {
|
||||
);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as XiaofeixiaApiResponse<T>;
|
||||
const rawText = await response.text();
|
||||
let payload: XiaofeixiaApiResponse<T>;
|
||||
try {
|
||||
payload = rawText ? (JSON.parse(rawText) as XiaofeixiaApiResponse<T>) : (null as unknown as XiaofeixiaApiResponse<T>);
|
||||
} catch {
|
||||
throw new CourierApiError(
|
||||
`小飞侠响应非 JSON(HTTP ${response.status}): ${rawText.slice(0, 200) || '(空)'}`,
|
||||
'200000',
|
||||
'XIAOFEIXIA',
|
||||
rawText,
|
||||
);
|
||||
}
|
||||
|
||||
if (!payload) {
|
||||
throw new CourierApiError('小飞侠返回空响应', '200000', 'XIAOFEIXIA');
|
||||
}
|
||||
|
||||
if (payload.code !== XIAOFEIXIA_SUCCESS_CODE) {
|
||||
throw new CourierApiError(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Delete, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
|
||||
import { AdminUsersService } from './admin-users.service';
|
||||
import { AdminUsersQueryDto } from './dto/admin-query.dto';
|
||||
|
||||
@@ -17,4 +18,10 @@ export class AdminUsersController {
|
||||
detail(@Param('id') id: string) {
|
||||
return this.usersService.detail(BigInt(id));
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(SuperAdminGuard)
|
||||
remove(@Param('id') id: string) {
|
||||
return this.usersService.deleteUser(BigInt(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,37 @@ import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import type { AdminUsersQueryDto } from './dto/admin-query.dto';
|
||||
|
||||
function mapAdminUserRow(u: {
|
||||
id: bigint;
|
||||
userNo: string;
|
||||
deviceKey: string | null;
|
||||
phone: string | null;
|
||||
phoneVerifiedAt: Date | null;
|
||||
mergedIntoUserId: bigint | null;
|
||||
wxOpenId: string | null;
|
||||
nickname: string | null;
|
||||
status: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: { orders: number };
|
||||
}) {
|
||||
return {
|
||||
id: u.id,
|
||||
userNo: u.userNo,
|
||||
deviceKey: u.deviceKey,
|
||||
phone: u.phone,
|
||||
phoneVerifiedAt: u.phoneVerifiedAt,
|
||||
mergedIntoUserId: u.mergedIntoUserId,
|
||||
wxOpenId: u.wxOpenId,
|
||||
wechatVerified: !!u.wxOpenId,
|
||||
nickname: u.nickname,
|
||||
status: u.status,
|
||||
createdAt: u.createdAt,
|
||||
updatedAt: u.updatedAt,
|
||||
orderCount: u._count.orders,
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminUsersService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
@@ -33,6 +64,7 @@ export class AdminUsersService {
|
||||
phone: true,
|
||||
phoneVerifiedAt: true,
|
||||
mergedIntoUserId: true,
|
||||
wxOpenId: true,
|
||||
nickname: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
@@ -44,11 +76,7 @@ export class AdminUsersService {
|
||||
]);
|
||||
|
||||
return serializeBigInt({
|
||||
items: items.map((u) => ({
|
||||
...u,
|
||||
orderCount: u._count.orders,
|
||||
_count: undefined,
|
||||
})),
|
||||
items: items.map((u) => mapAdminUserRow(u)),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -63,12 +91,12 @@ export class AdminUsersService {
|
||||
mergedInto: { select: { id: true, userNo: true, phone: true, nickname: true } },
|
||||
orders: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 5,
|
||||
select: {
|
||||
id: true,
|
||||
orderNo: true,
|
||||
status: true,
|
||||
payAmount: true,
|
||||
payStatus: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
@@ -79,10 +107,59 @@ export class AdminUsersService {
|
||||
|
||||
return serializeBigInt({
|
||||
...user,
|
||||
wechatVerified: !!user.wxOpenId,
|
||||
mergedFromCount: user._count.mergedFrom,
|
||||
orderCount: user._count.orders,
|
||||
addressCount: user._count.addresses,
|
||||
_count: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteUser(id: bigint) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id } });
|
||||
if (!user) throw new NotFoundException('用户不存在');
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
const orderIds = (
|
||||
await tx.order.findMany({ where: { userId: id }, select: { id: true } })
|
||||
).map((o) => o.id);
|
||||
|
||||
const redeemIds = (
|
||||
await tx.redeemRecord.findMany({ where: { userId: id }, select: { id: true } })
|
||||
).map((r) => r.id);
|
||||
|
||||
if (redeemIds.length) {
|
||||
await tx.storePayout.deleteMany({ where: { redeemRecordId: { in: redeemIds } } });
|
||||
await tx.storeRating.deleteMany({ where: { redeemRecordId: { in: redeemIds } } });
|
||||
await tx.redeemRecord.deleteMany({ where: { id: { in: redeemIds } } });
|
||||
}
|
||||
|
||||
await tx.benefitCoupon.deleteMany({ where: { userId: id } });
|
||||
|
||||
if (orderIds.length) {
|
||||
await tx.order.updateMany({
|
||||
where: { originOrderId: { in: orderIds } },
|
||||
data: { originOrderId: null },
|
||||
});
|
||||
await tx.commonTicket.deleteMany({
|
||||
where: { refType: 'ORDER', refId: { in: orderIds } },
|
||||
});
|
||||
await tx.order.deleteMany({ where: { id: { in: orderIds } } });
|
||||
}
|
||||
|
||||
if (user.avatarResourceId) {
|
||||
await tx.commonResource.updateMany({
|
||||
where: { id: user.avatarResourceId, ownerType: 'USER', ownerId: id },
|
||||
data: { status: 'DELETED' },
|
||||
});
|
||||
}
|
||||
|
||||
await tx.user.delete({ where: { id } });
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
message: '用户及关联业务数据已删除,行为日志已保留',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,12 @@ export class AdminXiaofeixiaService {
|
||||
raw: err.raw,
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return {
|
||||
ok: false,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
error: message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
IsString,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class XiaofeixiaEstimateFreightDto {
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.01)
|
||||
weight: number;
|
||||
|
||||
Reference in New Issue
Block a user