如何在不修改OpenAPI自动生成类的前提下,日志打印对象时屏蔽或脱敏指定字段?
太懂这种痛点了——OpenAPI自动生成的类碰都不敢改,打日志又怕把敏感/不必要的字段漏出去,手动写个toString又要写一堆嵌套逻辑,太麻烦。给你几个不用动生成代码的简洁方案:
1. 用Jackson的Mixin实现自定义序列化(推荐长期使用)
很多Kotlin/Java项目都会用Jackson做序列化,它的Mixin机制完美解决“不能修改原类但要自定义序列化规则”的问题——你只需要写一个抽象类/接口,定义你想要的序列化逻辑,再把它绑定到生成的类上就行,完全不用碰OpenAPI生成的代码。
比如针对你的Color和RGB类,我们可以这么写Mixin:
// 定义Color的序列化规则:忽略Opacity,脱敏RGB的r字段 abstract class ColorMixin { @JsonIgnore // 直接忽略Opacity字段,序列化时不输出 abstract val opacity: Opacity? // 自定义RGB字段的序列化逻辑,脱敏r值 @JsonProperty("rgb") fun serializeRgb(rgb: RGB): Map<String, String> { val maskedR = rgb.r.takeLast(1).let { "xx$it" } // 保留最后一位,前面替换成xx return mapOf( "r" to maskedR, "g" to rgb.g, "b" to rgb.b ) } }
然后配置Jackson的ObjectMapper,把Mixin绑定到生成的Color类:
val objectMapper = ObjectMapper().apply { registerModule(KotlinModule()) // 别忘了加Kotlin支持模块 addMixIn(Color::class.java, ColorMixin::class.java) }
之后打日志的时候,直接用objectMapper.writeValueAsString(color)就能得到你想要的脱敏后的JSON字符串,完全不用改原类。
2. 给日志框架加自定义转换器(适合专门的日志场景)
如果你的项目用Logback或者Log4j这类日志框架,可以写个自定义转换器,专门处理这些生成类的日志输出逻辑,不用改序列化逻辑,只在日志打印时生效。
比如用Logback写一个ColorConverter:
class ColorConverter : ClassicConverter() { override fun convert(event: ILoggingEvent): String { // 处理参数化日志的情况,比如logger.info("当前颜色: {}", color) event.parameterArray?.forEach { param -> if (param is Color) { val rgb = param.rgb val maskedR = rgb.r.takeLast(1).let { "xx$it" } return "Color(RGB=(r=\"$maskedR\", g=\"${rgb.g}\", b=\"${rgb.b}\"))" } } // 如果不是Color对象,就返回原日志内容 return event.message } }
然后在logback.xml里注册这个转换器:
<conversionRule conversionWord="maskedColor" converterClass="com.yourpackage.ColorConverter"/>
之后在日志格式里用%maskedColor,或者直接在打印时用参数化日志,就能自动输出脱敏后的Color对象了。
3. 反射工具类(适合临时调试)
如果只是临时调试用,不想动框架配置,可以写个简单的反射工具函数,遍历对象字段,跳过不需要的、脱敏需要的:
fun maskDebugObject(obj: Any): String { val className = obj::class.simpleName ?: "UnknownObject" val fields = obj::class.memberProperties.filterIsInstance<KProperty1<Any, *>>() val maskedFields = fields.mapNotNull { prop -> when (prop.name) { "opacity" -> null // 直接跳过这个字段 "rgb" -> { val rgb = prop.get(obj) as RGB val maskedR = rgb.r.takeLast(1).let { "xx$it" } "RGB=(r=\"$maskedR\", g=\"${rgb.g}\", b=\"${rgb.b}\")" } else -> "${prop.name}=\"${prop.get(obj)}\"" } }.joinToString(", ") return "$className($maskedFields)" }
调试的时候直接调用maskDebugObject(color),就能得到你想要的格式化字符串,缺点是反射性能一般,适合临时用,不推荐长期在生产环境高频调用。
总的来说,优先推荐Jackson Mixin方案,通用性强,适合长期维护;日志转换器适合专门针对日志场景做定制;反射工具类则是临时调试的快速方案。
内容来源于stack exchange

