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:
@@ -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.items;Java 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.items;Java 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 data;Java 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user