feat: 全局客户名称脱敏(首尾保留+中间三个*)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 2-3字: 首字+*** (徐***) - 4-6字: 首2字+***+末1字 (嘉兴***司) - 7字+: 首4字+***+末2字 (嘉兴市乍***公司) - 覆盖所有接口: monitoring, targets, vehicles, weekly-detail Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,26 @@
|
|||||||
import type { AuthUser } from './types.js';
|
import type { AuthUser } from './types.js';
|
||||||
|
|
||||||
|
/** 客户名称脱敏 */
|
||||||
|
export function maskCustomerName(name: string | null): string | null {
|
||||||
|
if (!name) return name;
|
||||||
|
const len = name.length;
|
||||||
|
if (len <= 1) return '*';
|
||||||
|
if (len <= 3) return name[0] + '***';
|
||||||
|
if (len <= 6) return name.slice(0, 2) + '***' + name.slice(-1);
|
||||||
|
return name.slice(0, 4) + '***' + name.slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 对数据列表中的客户名称进行脱敏 */
|
||||||
|
export function maskCustomerNames<T>(items: T[]): T[] {
|
||||||
|
return items.map(v => {
|
||||||
|
const obj = v as any;
|
||||||
|
const copy = { ...obj };
|
||||||
|
if ('customer' in copy && copy.customer) copy.customer = maskCustomerName(copy.customer);
|
||||||
|
if ('customerName' in copy && copy.customerName) copy.customerName = maskCustomerName(copy.customerName);
|
||||||
|
return copy as T;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用权限过滤函数
|
* 通用权限过滤函数
|
||||||
* 适配 CachedVehicle(department, manager, managerId)和 Vehicle(departmentName, customerManager, managerId)
|
* 适配 CachedVehicle(department, manager, managerId)和 Vehicle(departmentName, customerManager, managerId)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { getCache, queryDateMileage, buildDateFilters } from './cache.js';
|
import { getCache, queryDateMileage, buildDateFilters } from './cache.js';
|
||||||
import { filterByPermission } from '../../auth/permissions.js';
|
import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js';
|
||||||
import type { AuthUser } from '../../auth/types.js';
|
import type { AuthUser } from '../../auth/types.js';
|
||||||
import type { CachedVehicle, MonitoringFilters, MonitoringResponse } from './types.js';
|
import type { CachedVehicle, MonitoringFilters, MonitoringResponse } from './types.js';
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ app.get('/', async (c) => {
|
|||||||
const total = filtered.length;
|
const total = filtered.length;
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
vehicles: paged,
|
vehicles: maskCustomerNames(paged),
|
||||||
stats,
|
stats,
|
||||||
filters,
|
filters,
|
||||||
total,
|
total,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import pool from '../../db.js';
|
|||||||
import mileagePool from '../../mileage-db.js';
|
import mileagePool from '../../mileage-db.js';
|
||||||
import { getCache } from './cache.js';
|
import { getCache } from './cache.js';
|
||||||
import { fetchVehicleInfoByPlates } from './vehicle-info.js';
|
import { fetchVehicleInfoByPlates } from './vehicle-info.js';
|
||||||
import { filterByPermission } from '../../auth/permissions.js';
|
import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js';
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ app.get('/:id/vehicles', async (c) => {
|
|||||||
|
|
||||||
const user = (c as any).get('user') as import('../../auth/types.js').AuthUser | undefined;
|
const user = (c as any).get('user') as import('../../auth/types.js').AuthUser | undefined;
|
||||||
const filtered = user ? filterByPermission(result, user) : result;
|
const filtered = user ? filterByPermission(result, user) : result;
|
||||||
return c.json(filtered);
|
return c.json(maskCustomerNames(filtered));
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
console.error('target vehicles error:', e);
|
console.error('target vehicles error:', e);
|
||||||
return c.json([], 500);
|
return c.json([], 500);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type {
|
|||||||
BatchGroup,
|
BatchGroup,
|
||||||
InventoryTypeSummary,
|
InventoryTypeSummary,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { filterByPermission } from '../auth/permissions.js';
|
import { filterByPermission, maskCustomerNames, maskCustomerName } from '../auth/permissions.js';
|
||||||
import type { AuthUser } from '../auth/types.js';
|
import type { AuthUser } from '../auth/types.js';
|
||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
|
|
||||||
@@ -312,15 +312,12 @@ async function getVehicles(): Promise<Vehicle[]> {
|
|||||||
|
|
||||||
async function getVehiclesForUser(c: Context): Promise<Vehicle[]> {
|
async function getVehiclesForUser(c: Context): Promise<Vehicle[]> {
|
||||||
const all = await getVehicles();
|
const all = await getVehicles();
|
||||||
// Hono 子路由 context 可能丢失变量,尝试多种方式获取
|
|
||||||
const user = ((c as any).get?.('user') || (c as any).var?.user) as AuthUser | undefined;
|
const user = ((c as any).get?.('user') || (c as any).var?.user) as AuthUser | undefined;
|
||||||
if (user) {
|
if (user) {
|
||||||
const filtered = filterByPermission(all, user);
|
const filtered = filterByPermission(all, user);
|
||||||
console.log(`[vehicles] permission: ${user.permissionLevel}, user: ${user.userName}, before: ${all.length}, after: ${filtered.length}`);
|
return maskCustomerNames(filtered);
|
||||||
return filtered;
|
|
||||||
}
|
}
|
||||||
console.log('[vehicles] WARNING: no user in context, returning all');
|
return maskCustomerNames(all);
|
||||||
return all;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRegionCounts(vehicles: Vehicle[], regions: readonly string[]): Record<string, number> {
|
function getRegionCounts(vehicles: Vehicle[], regions: readonly string[]): Record<string, number> {
|
||||||
@@ -1009,7 +1006,8 @@ app.get('/weekly-detail', async (c) => {
|
|||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
const [rows] = await pool.query<any[]>(sql);
|
const [rows] = await pool.query<any[]>(sql);
|
||||||
return c.json(rows);
|
const masked = (rows as any[]).map(r => ({ ...r, customer_name: maskCustomerName(r.customer_name) }));
|
||||||
|
return c.json(masked);
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/refresh — force cache refresh
|
// GET /api/vehicles/refresh — force cache refresh
|
||||||
|
|||||||
Reference in New Issue
Block a user