Repository Wiki
isHarryh/Ark-Pets

ArkConfig 配置模型与默认配置

ArkConfig 是 ArkPets 桌宠应用的核心配置模型类,以纯公有字段 + fastjson2 注解的方式定义了全部用户可配置项(行为、画布、显示、物理、渲染、窗口样式等),并通过内置默认配置资源 ArkPetsConfigDefault.json 实现"首次运行自动生成外部配置文件"的机制。

Purpose and Scope

本页面覆盖以下内容:

  • ArkConfig 类的完整字段模型、默认值与版本演进(@since 标注)
  • 配置的加载流程(内部默认配置 → 外部自定义配置)与保存流程
  • 内置默认配置文件 assets/ArkPetsConfigDefault.json 的作用与结构
  • 敏感字段(MirrorChyan CDK)的弱加密存取 getMcCdk() / setMcCdk()
  • 值转换辅助方法(缓动函数、颜色、描边模式)与 RenderOutline 枚举
  • 失败模式、边界情况与并发注意事项

以下相关主题由兄弟页面承接,本页不展开:

  • 配置文件路径常量(Const.configExternal / Const.configInternal)与其它路径常量:参见 常量定义与路径约定
  • 桌面启动器中读写配置的 UI 交互(StartupConfig、控制器模块):参见 启动器与桌面端模块
  • 遥测系统对 @PrivacyField 标注字段的处理逻辑:参见 遥测与隐私处理

Overview

ArkPets 需要在跨平台(Windows/macOS/Linux)的桌面环境中持久化用户偏好。设计上采用扁平化的 JSON 配置模型:

  • 单文件单模型:所有配置项收敛在一个 ArkConfig 实例中,对应一个外部 JSON 文件 ArkPetsConfig.json(位于工作目录,见 Const.configExternal)。
  • 默认配置内置于 Jar:通过 Java 资源 /ArkPetsConfigDefault.json(Const.configInternal,实际文件为 assets/ArkPetsConfigDefault.json)随包分发,保证任何环境下都能恢复出厂默认。
  • 双层默认值机制:每个字段通过 fastjson2 的 @JSONField(defaultValue = "...") 声明代码级默认值;同时内置 JSON 文件提供文件级默认值。二者保持一致,前者兜底解析缺失字段,后者作为生成外部文件的模板。
  • 纯公有字段:ArkConfig 使用 snake_case 公有字段而非 getter/setter Bean 风格,由 fastjson2 直接按字段名反序列化,减少样板代码;构造函数为 private,实例只能通过静态工厂方法获得。

这种设计的意图是:用户可以手工编辑 JSON 文件(字段名即文档),而程序在字段缺失、值非法时仍能以安全默认值运行,避免启动崩溃。

Architecture

Loading diagram...

架构要点说明:

  • ArkConfig 是唯一配置入口:它同时持有内部默认资源 URL(configDefault,类加载时通过 Objects.requireNonNull 保证存在)与外部自定义文件 File(configCustom)两个静态定位符。
  • fastjson2 是唯一的序列化引擎:读取用 JSONObject.parseObject(text, ArkConfig.class),写出用 JSON.toJSONString(this, PrettyFormat),保证字段名与 JSON 键完全对应。
  • Const 常量类解耦路径知识:ArkConfig 不硬编码任何路径或正则,全部来自 Const,便于集中维护。
  • 敏感字段旁路序列化:CDK 的读写方法都标注 @JSONField(serialize = false) 或 (deserialize = false),避免加密/解密方法被 fastjson2 当作 Bean 属性序列化。

配置加载与保存的完整控制流

getConfig():主加载入口

java
1public static ArkConfig getConfig() { 2 if (!configCustom.exists()) { 3 // Use the default config if the external config file does not exist. 4 isNewcomer = true; 5 ArkConfig config = getDefaultConfig(); 6 if (config != null) 7 config.save(); 8 return getDefaultConfig(); 9 } else { 10 return getConfig(configCustom); 11 } 12}

Source: ArkConfig.java

关键行为逐行解析:

  1. 外部文件不存在:置静态标志 isNewcomer = true,随后读取内部默认配置、立即调用 save() 把默认值物化为外部 ArkPetsConfig.json(这一步完成"首次运行生成配置文件"),最后再返回一份默认配置实例。
  2. 外部文件存在:走 getConfig(File) 分支解析用户自定义配置。
  3. 注意 isNewcomer 是类级静态布尔而非实例字段——启动器可据此判断是否为首次使用(例如弹出新手引导)。

getConfig(File):解析指定配置文件

java
1public static ArkConfig getConfig(File configFile) { 2 if (configFile.exists()) { 3 // Read and parse the config file. 4 try { 5 return Objects.requireNonNull( 6 JSONObject.parseObject(FileUtil.readString(configFile, charsetDefault), ArkConfig.class), 7 "JSON parsing returns null." 8 ); 9 } catch (IOException | NullPointerException e) { 10 Logger.error("Config", "Failed to get the custom config, details see below.", e); 11 } 12 } 13 return null; 14}

Source: ArkConfig.java

  • 以 charsetDefault(即 UTF-8)读取,规避平台默认编码差异。
  • Objects.requireNonNull(..., "JSON parsing returns null.") 把 fastjson2 可能返回 null 的情况统一转为带消息的 NullPointerException,再被同一 catch 捕获记录——失败不抛出,返回 null 交由调用方决策。
  • 解析时,JSON 中缺失的字段会取 @JSONField(defaultValue = "...") 声明的代码级默认值。

getDefaultConfig():读取内置默认配置

java
1public static ArkConfig getDefaultConfig() { 2 try (InputStream inputStream = configDefault.openStream()) { 3 return Objects.requireNonNull( 4 JSONObject.parseObject(new String(inputStream.readAllBytes(), charsetDefault), ArkConfig.class), 5 "JSON parsing returns null." 6 ); 7 } catch (IOException e) { 8 Logger.error("Config", "Failed to get the default config, details see below.", e); 9 } 10 return null; 11}

Source: ArkConfig.java

使用 try-with-resources 打开类路径资源流,读完即关闭。由于 configDefault URL 在类初始化时已 requireNonNull 校验,此处的 IOException 主要覆盖流读取失败而非资源缺失。

save():持久化到外部文件

java
1@JSONField(serialize = false) 2public void save() { 3 try { 4 FileUtil.writeString(configCustom, charsetDefault, JSON.toJSONString(this, PrettyFormat), false); 5 Logger.debug("Config", "Config saved"); 6 } catch (IOException e) { 7 Logger.error("Config", "Config saving failed, details see below.", e); 8 } 9}

Source: ArkConfig.java

PrettyFormat 让生成的 ArkPetsConfig.json 带缩进、便于用户手工编辑;save() 同样吞掉 IOException 仅记日志,不会中断程序流程——配置保存失败属于可降级错误(下次启动仍可用旧文件或默认值)。

时序图:首次运行 vs 常规运行

Loading diagram...

字段模型与分组

全部字段为公有字段、snake_case 命名、按字母序排列,并通过 Javadoc @since ArkPets X.Y 标注引入版本。以下按功能域分组(字段总数约 47 个,完整清单见源码):

java
1/** @since ArkPets 1.0 */ @JSONField(defaultValue = "4") 2public int behavior_ai_activation; 3/** @since ArkPets 1.0 */ @JSONField(defaultValue = "true") 4public boolean behavior_allow_interact; 5/** @since ArkPets 3.6 */ @JSONField(defaultValue = "false") 6public boolean behavior_allow_sleep; 7/** @since ArkPets 3.9 */ @JSONField(defaultValue = "30.0") 8public float behavior_walk_speed; 9/** @since ArkPets 3.3 */ @JSONField(defaultValue = "#00000000") 10public String canvas_color; 11/** @since ArkPets 2.0 */ @JSONField() 12public String character_asset; 13/** @since ArkPets 3.5 */ @JSONField() 14public JSONObject character_favorites; 15/** @since ArkPets 3.9 */ @JSONField() @PrivacyField() 16public String download_mc_cdk; 17/** @since ArkPets 3.13 */ @JSONField(defaultValue = "true") 18public boolean enable_telemetry; 19/** @since ArkPets 2.2 */ @JSONField(defaultValue = "800.0") 20public float physic_gravity_acc; 21/** @since ArkPets 3.3 */ @JSONField(defaultValue = "#FFFF00FF") 22public String render_outline_color; 23/** @since ArkPets 3.5 */ @JSONField(defaultValue = "EASE_OUT_CUBIC") 24public String transition_type;

Source: ArkConfig.java

设计意图分析:

  • defaultValue 以字符串书写:fastjson2 会按字段类型把字符串转换为对应数值/布尔值,使注解形式统一("4"、"true"、"#FFFF00FF")。
  • 无默认值的字段(character_asset、character_favorites、character_files、character_label、download_mc_cdk、user_announcement_read)都是"用户数据"而非"偏好设置",语义上没有出厂默认,反序列化后由调用方判空处理。
  • @PrivacyField() 标注(download_mc_cdk、user_announcement_read)标记这些字段包含隐私数据,遥测上报时会被过滤(详见遥测相关页面)。
  • JSONObject 类型的字段(character_favorites、character_files、user_announcement_read)用于存储动态键值结构,模型刻意不为其定义强类型子模型,保持 schema 演进弹性。

内置默认配置文件

assets/ArkPetsConfigDefault.json 是打包进 Jar 的默认配置模板,与代码级 defaultValue 完全对齐(节选):

json
1{ 2 "behavior_ai_activation":4, 3 "behavior_allow_interact":true, 4 "behavior_allow_sleep":false, 5 "behavior_walk_speed": 30.0, 6 "canvas_color":"#00000000", 7 "character_asset":"", 8 "character_favorites":{}, 9 "display_fps":60, 10 "display_scale":1.0, 11 "enable_telemetry":true, 12 "logging_level":"INFO", 13 "physic_gravity_acc":800.0, 14 "render_outline_color":"#FFFF00FF", 15 "transition_duration":0.3, 16 "transition_type":"EASE_OUT_CUBIC", 17 "window_style_toolwindow":true, 18 "window_style_topmost":true 19}

Source: ArkPetsConfigDefault.json

它与 Const 中定义的路径常量一一对应:

java
1// Encoding presets 2public static final String charsetDefault = "UTF-8"; 3 4// Paths of static files and internal files 5public static final String configExternal = "ArkPetsConfig.json"; 6public static final String configInternal = "/ArkPetsConfigDefault.json";

Source: Const.java

即:外部文件固定为工作目录下的 ArkPetsConfig.json,内置资源固定为类路径根的 /ArkPetsConfigDefault.json。

值转换与工具方法

由于 JSON 中只能保存原始类型(String / int / float / boolean),运行期所需的枚举与 libGDX 类型由静态转换方法按需转换,转换失败一律降级为安全默认值并告警,而非抛异常:

java
1/** @see EasingFunction */ 2public static EasingFunction getEasingFunctionFrom(String string) { 3 try { 4 return EasingFunction.valueOf(string); 5 } catch (IllegalArgumentException e) { 6 Logger.warn("Config", "Invalid easing function, using linear"); 7 return EasingFunction.LINEAR; 8 } 9} 10 11/** @see Color */ 12public static Color getGdxColorFrom(String string) { 13 Color color; 14 if (hexColorRegex.matcher(string).matches()) { 15 color = Color.valueOf(string); 16 } else { 17 Logger.warn("Config", "Invalid color config, using transparent"); 18 color = Color.CLEAR; 19 } 20 return color; 21} 22 23/** @see RenderOutline */ 24public static RenderOutline getRenderOutlineFrom(int ordinal) { 25 if (ordinal >= RenderOutline.values().length) 26 ordinal = 0; 27 return RenderOutline.values()[ordinal]; 28}

Source: ArkConfig.java

  • getGdxColorFrom 依赖 Const.hexColorRegex(^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$,见 Const.java)校验 #RRGGBB(AA) 形式的颜色串。
  • getRenderOutlineFrom 通过序数(ordinal)映射,越界时回退到 NEVER(序数 0)。注意如果传入负数,values()[ordinal] 会抛出 ArrayIndexOutOfBoundsException,源码只防御了上界——这是一个已知的边界行为。

render_outline 等配置项使用嵌套枚举 RenderOutline 表示描边显示时机:

java
1/** Config options for render outline. */ 2public enum RenderOutline { 3 NEVER, 4 DRAGGING, 5 PRESSING, 6 FOCUSED, 7 _RESERVED, 8 ALWAYS 9}

Source: ArkConfig.java

序数值即 JSON 中存储的整数值(render_outline 默认 1 对应 DRAGGING,render_outline_emphasis 默认 3 对应 FOCUSED)。_RESERVED 是被移除功能的保留占位,用于维持后续枚举序数的向后兼容——这是以 ordinal 做持久化格式时必须保留历史槽位的典型做法。

敏感字段:MirrorChyan CDK 的弱加密存取

download_mc_cdk 存储的是资源站 MirrorChyan 的 CDK(兑换码)。出于"避免明文落盘"的考虑,字段中保存的是经 SecretUtils.WeakEncryptionV0 加密后的密文:

java
1/** Gets the MirrorChyan CDK. 2 * @return The decrypted CDK, or {@code null} if the CDK is not set or decryption failed. 3 */ 4@JSONField(serialize = false) 5public String getMcCdk() { 6 if (download_mc_cdk != null && !download_mc_cdk.isEmpty()) { 7 try { 8 String result = new SecretUtils.WeakEncryptionV0().decrypt(download_mc_cdk); 9 Logger.debug("Config", "Decrypt MirrorChyan CDK okay"); 10 return result; 11 } catch (GeneralSecurityException e) { 12 Logger.error("Config", "Failed to decrypt MirrorChyan CDK, details see below.", e); 13 } 14 } 15 return null; 16} 17 18/** Sets the MirrorChyan CDK. 19 * @param string The CDK to set. If the string is {@code null} or empty, the CDK will be cleared. 20 */ 21@JSONField(deserialize = false) 22public void setMcCdk(String string) throws GeneralSecurityException { 23 if (string != null && !string.isEmpty()) { 24 try { 25 download_mc_cdk = new SecretUtils.WeakEncryptionV0().encrypt(string); 26 Logger.debug("Config", "Encrypt MirrorChyan CDK okay"); 27 return; 28 } catch (GeneralSecurityException e) { 29 Logger.error("Config", "Failed to encrypt MirrorChyan CDK, details see below.", e); 30 throw e; 31 } 32 } 33 download_mc_cdk = ""; 34}

Source: ArkConfig.java

不对称的错误语义值得注意:

  • getMcCdk():解密失败仅记录错误并返回 null(视为"未设置"),不抛出——保证读取路径永不阻塞。
  • setMcCdk():加密失败记录错误后重抛 GeneralSecurityException,让调用方(通常是设置界面的保存流程)感知失败并提示用户。
  • 两个方法分别标注 @JSONField(serialize = false) / (deserialize = false),防止 fastjson2 把 getMcCdk/setMcCdk 识别为 Bean 属性而在序列化时双重处理(否则 JSON 里会出现多余的 mcCdk 键)。字段本身仍以 download_mc_cdk 密文形式正常序列化。
  • 类名 WeakEncryptionV0 自述其安全强度:这是防君子不防小人的混淆级加密(密钥内嵌于开源代码),目的是防止普通用户在共享配置截图/文件时意外泄露 CDK。

配置项参考表

按功能域整理主要配置项(以代码级 defaultValue 与内置 JSON 为准):

配置项类型默认值引入版本说明
behavior_ai_activationint41.0AI 行为激进度
behavior_allow_interactbooleantrue1.0允许点击交互
behavior_allow_sitbooleantrue1.0允许坐下行为
behavior_allow_sleepbooleanfalse3.6允许睡眠行为
behavior_allow_specialbooleantrue3.6允许特殊行为
behavior_allow_walkbooleantrue1.0允许行走行为
behavior_direction_switchingint13.11朝向切换策略
behavior_do_peer_repulsionbooleantrue1.6多桌宠之间互斥
behavior_walk_speedfloat30.03.9行走速度
canvas_colorString#000000003.3采样画布颜色
canvas_coveragefloat0.83.8画布覆盖率
canvas_sampling_intervalint43.8画布采样间隔
character_assetString(无默认)2.0当前角色资源标识
character_favoritesJSONObject(无默认)3.5角色收藏夹
character_filesJSONObject(无默认)2.2角色模型文件映射
character_labelString(无默认)2.0当前角色显示名
display_fpsint601.0渲染帧率
display_margin_bottomint01.0底边距(悬空高度)
display_multi_monitorsbooleantrue2.1允许跨多显示器
display_scalefloat1.01.0缩放比例
download_mc_cdkString(无默认)3.9MirrorChyan CDK(弱加密,@PrivacyField)
eco_modebooleanfalse3.9节能模式
enable_telemetrybooleantrue3.13启用遥测
initial_position_xfloat0.23.2初始横坐标(相对比例)
initial_position_yfloat0.23.2初始纵坐标(相对比例)
launcher_solid_exitbooleantrue3.0启动器退出时结束桌宠进程
logging_levelStringINFO2.0日志级别
opacity_dimfloat0.753.3失焦暗淡不透明度
opacity_normalfloat1.03.3正常不透明度
physic_gravity_accfloat800.02.2重力加速度
physic_air_friction_accfloat100.02.2空气摩擦加速度
physic_static_friction_accfloat500.02.2静摩擦加速度
physic_speed_limit_xfloat1000.02.2水平速度上限
physic_speed_limit_yfloat1000.02.2垂直速度上限
render_animation_mixturefloat0.33.5动画过渡混合度
render_enable_mipmapbooleantrue3.8启用 mipmap
render_outlineint13.3描边时机(RenderOutline 序数)
render_outline_colorString#FFFF00FF3.3描边颜色
render_outline_emphasisint33.9强调描边时机(序数)
render_outline_emphasis_colorString#FFBB00FF3.9强调描边颜色
render_outline_widthfloat2.03.3描边宽度
render_shader_high_qualitybooleantrue3.12高质量着色器
render_shadow_colorString#000000BB3.6阴影颜色
transition_durationfloat0.33.5过渡动画时长(秒)
transition_typeStringEASE_OUT_CUBIC3.5缓动函数名(EasingFunction)
user_announcement_readJSONObject(无默认)3.7已读公告(@PrivacyField)
window_style_toolwindowbooleantrue3.2工具窗口样式(不在任务栏显示)
window_style_topmostbooleantrue3.2置顶显示

API Reference

public static ArkConfig getConfig()

读取默认外部配置文件 ArkPetsConfig.json 并返回 ArkConfig 实例;若文件不存在,则先以内置默认配置生成该文件(isNewcomer = true)。

返回: ArkConfig 实例;任何读取/解析失败时返回 null。

public static ArkConfig getConfig(File configFile)

解析指定配置文件。

参数:

  • configFile (java.io.File):配置文件路径

返回: ArkConfig 实例;文件不存在或读取/解析失败(IOException / NullPointerException)时返回 null,失败详情写入 Logger。

public static ArkConfig getDefaultConfig()

从类路径资源 /ArkPetsConfigDefault.json 读取默认配置。

返回: 默认 ArkConfig 实例;IOException 时返回 null。

public void save()

以 UTF-8 + PrettyFormat 将当前实例序列化并覆盖写入外部文件 ArkPetsConfig.json。

抛出: 无(IOException 被捕获并记录,不外抛)。

public boolean isNewcomer()

返回: 最近一次 getConfig() 调用时外部配置文件是否为新生成。

public String getMcCdk() / public void setMcCdk(String string)

MirrorChyan CDK 的解密读取与加密写入(详见前文)。

setMcCdk 抛出: GeneralSecurityException——加密失败时。

public static EasingFunction getEasingFunctionFrom(String string)

返回: 对应的缓动函数枚举;非法名返回 EasingFunction.LINEAR(仅告警)。

public static Color getGdxColorFrom(String string)

返回: libGDX Color;不匹配 hexColorRegex 时返回 Color.CLEAR(仅告警)。

public static RenderOutline getRenderOutlineFrom(int ordinal)

返回: 按序数取 RenderOutline;ordinal 越上界时回退为序数 0(NEVER)。负数序数未防御,会抛出 ArrayIndexOutOfBoundsException。

失败模式、边界情况与并发

场景行为源码依据
外部配置文件不存在置 isNewcomer = true,用内置默认生成并落盘getConfig() 分支 1
外部 JSON 语法损坏 / 字段类型不符JSONObject.parseObject 抛异常被捕获,记错误日志,返回 nullgetConfig(File)
JSON 中缺失某字段fastjson2 填充 @JSONField(defaultValue)注解声明
内置资源读取失败(极少见,资源缺失会在类加载时 NPE)记错误日志,返回 nullgetDefaultConfig() 与静态初始化 requireNonNull
save() 写盘失败(磁盘满/权限)记错误日志,不抛出,程序继续save()
颜色串非法返回 Color.CLEAR 透明色 + 告警getGdxColorFrom
缓动函数名非法返回 LINEAR + 告警getEasingFunctionFrom
render_outline 序数越上界回退 NEVER;负数未防御会抛 AIOOBEgetRenderOutlineFrom
CDK 解密失败返回 null 视为未设置;设置失败则重抛 GeneralSecurityExceptiongetMcCdk / setMcCdk

并发注意事项:

  • isNewcomer 是静态可变字段且未做同步保护;并发调用 getConfig() 可能产生竞态。当前实际调用场景(应用启动单线程)不会触发,但新增并发加载逻辑时需注意。
  • ArkConfig 实例本身未做任何同步;多线程同时读写字段(尤其 character_favorites 这类 JSONObject)需要调用方自行保证可见性与互斥。
  • save() 是全量覆盖写,无原子写/临时文件交换,写盘中断可能留下截断的 JSON 文件——下次启动将走"解析失败返回 null"路径。

性能与扩展说明

  • 全量内存模型 + 全量序列化:配置整体很小(几十个标量字段 + 3 个 JSONObject),读取即一次 parseObject,保存即一次 toJSONString,无任何缓存层,性能不构成瓶颈。
  • 新增配置项的扩展步骤:在 ArkConfig.java 中按字母序添加公有字段并标注 @since 与 @JSONField(defaultValue = "...");同时同步更新 ArkPetsConfigDefault.json 以保持双层默认值一致。旧配置文件因字段缺失会自动落回默认值,天然向后兼容。
  • 扩展为强类型子模型:character_favorites 等当前用 JSONObject 存动态结构;若需强类型校验,可定义 POJO 替换,fastjson2 反序列化无需其它改动。

Sources

(2 files)
core/src/cn/harryh/arkpets