task-81(店铺中心): 定义店铺和店铺密钥 DTO

新增 src/pages/shop/shop-dto.ts 纯逻辑 DTO:ShopKeyItem/ShopSummary/
ShopCredential/SensitiveString 等类型边界与分页归一/查询序列化(店铺接口
camelCase);凭证明文与紫鸟令牌按敏感字段建模,禁止进入通用日志。

TDD: task-81.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
2026-09-05 16:32:08 +08:00
parent 7ccabf859f
commit 93033e94db
2 changed files with 282 additions and 0 deletions
@@ -0,0 +1,164 @@
/** 店铺中心共享 DTO(任务 81):店铺密钥/店铺摘要/明文凭据/分组与分页类型;
* 纯逻辑、无框架依赖,与 Java shopkey 模块的 camelCase 接口契约对齐。 */
export const SHOP_PAGE_DEFAULT_SIZE = 15
export const SHOP_PAGE_MIN_PAGE = 1
export const SHOP_PAGE_MAX_SIZE = 100
/** 敏感字符串品牌:紫鸟令牌、店铺密码等明文只允许经 toSensitiveString 标记,禁止直接进入通用日志/上报。 */
export type SensitiveString = string & { readonly __sensitiveBrand: unique symbol }
export function toSensitiveString(value: string): SensitiveString {
return value as SensitiveString
}
/** 紫鸟 IP 白名单状态。 */
export type IpWhitelistStatus = 'UNKNOWN' | 'ALLOWED' | 'BLOCKED'
/** 店铺密钥行(GET /api/admin/shop-keys data.itemsJava ShopKeyItemVo)。 */
export interface ShopKeyItem {
id: number
remarkName: string
ziniaoAccountName: string
/** 紫鸟令牌:敏感字段,列表展示必须掩码。 */
ziniaoToken: SensitiveString
ipWhitelistStatus: IpWhitelistStatus
ipWhitelistCheckedAt?: string
ipWhitelistMessage?: string
createdAt?: string
updatedAt?: string
}
export interface ShopKeyPageResult {
items: ShopKeyItem[]
total: number
page: number
pageSize: number
}
/** 店铺行(GET /api/admin/shop-manages data.itemsJava ShopManageItemVo,密码只下发掩码)。 */
export interface ShopSummary {
id: number
groupId: number | null
groupName: string
shopName: string
mallName: string
znUsername: string
account: string
passwordMasked?: string
createdAt?: string
updatedAt?: string
}
export interface ShopPageResult {
items: ShopSummary[]
total: number
page: number
pageSize: number
}
/** 店铺明文凭据(GET /api/admin/shop-manages/{id}/credential dataJava ShopManageCredentialVo)。 */
export interface ShopCredential {
id: number
groupId: number | null
groupName: string
shopName: string
mallName: string
znUsername: string
account: string
password: SensitiveString
}
/** 店铺分组下拉项(GET /api/admin/shop-manages/groups data)。 */
export interface ShopGroupOption {
id: number
groupName: string
memberCount?: number
leaderUsername?: string
}
/** 店铺密钥列表分页参数(该接口只支持分页)。 */
export interface ShopKeyListParams {
page: number
pageSize: number
}
/** 店铺列表分页/筛选参数。 */
export interface ShopManageListParams {
page: number
pageSize: number
groupId?: number | null
shopName?: string
}
export interface ShopKeyPageQuery {
page: number
pageSize: number
}
export interface ShopManagePageQuery {
page: number
pageSize: number
groupId?: number
shopName?: string
}
function finiteInt(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null
}
function positiveIdOrNull(value: unknown): number | null {
const id = finiteInt(value)
return id === null || id < 1 ? null : id
}
function trimOrUndefined(value: unknown): string | undefined {
return typeof value === 'string' && value.trim() ? value.trim() : undefined
}
/** 归一化分页:页码下限 1、页大小钳制在 1..100(与 Java 服务端一致)。 */
function normalizePageCore(raw: { page?: unknown; pageSize?: unknown }): { page: number; pageSize: number } {
const page = Math.max(finiteInt(raw.page) ?? SHOP_PAGE_MIN_PAGE, SHOP_PAGE_MIN_PAGE)
const rawSize = finiteInt(raw.pageSize)
const pageSize =
rawSize === null || rawSize < SHOP_PAGE_MIN_PAGE
? SHOP_PAGE_DEFAULT_SIZE
: Math.min(rawSize, SHOP_PAGE_MAX_SIZE)
return { page, pageSize }
}
/** 店铺密钥列表分页归一。 */
export function normalizeKeyListParams(raw: Partial<ShopKeyListParams>): ShopKeyListParams {
return normalizePageCore(raw)
}
/** 店铺列表分页/筛选归一:负/缺分组 id 视为无筛选,空白店铺名视为无过滤。 */
export function normalizeShopListParams(raw: Partial<ShopManageListParams>): ShopManageListParams {
return {
...normalizePageCore(raw),
groupId: positiveIdOrNull(raw.groupId),
shopName: trimOrUndefined(raw.shopName),
}
}
export function toKeyPageQuery(params: ShopKeyListParams): ShopKeyPageQuery {
return { page: params.page, pageSize: params.pageSize }
}
/** 前端状态 → Java 查询参数(camelCase);仅下发非空可选字段。 */
export function toShopManagePageQuery(params: ShopManageListParams): ShopManagePageQuery {
const query: ShopManagePageQuery = { page: params.page, pageSize: params.pageSize }
if (params.groupId != null) query.groupId = params.groupId
if (params.shopName) query.shopName = params.shopName
return query
}
/** 无数据密钥分页缺省值(首屏/加载前占位)。 */
export function emptyShopKeyPageResult(): ShopKeyPageResult {
return { items: [], total: 0, page: SHOP_PAGE_MIN_PAGE, pageSize: SHOP_PAGE_DEFAULT_SIZE }
}
/** 无数据店铺分页缺省值(首屏/加载前占位)。 */
export function emptyShopPageResult(): ShopPageResult {
return { items: [], total: 0, page: SHOP_PAGE_MIN_PAGE, pageSize: SHOP_PAGE_DEFAULT_SIZE }
}