| | |
| | | package com.battery.device.neware; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.Properties; |
| | | import java.util.function.Function; |
| | | import com.battery.device.api.DeviceAdapter; |
| | | import com.battery.device.api.DeviceAdapterException; |
| | | import com.battery.device.api.model.AdapterCapabilities; |
| | |
| | | /** |
| | | * 按存储形态 {@code NEWARE_MYSQL} 构造直连厂商库的只读适配器实例。 |
| | | * |
| | | * <p>连接口令只从环境变量或启动参数读取(变量名由实例配置的 {@code passwordEnv} 指定, |
| | | * 缺省为 {@link NewareMysqlAdapter#DEFAULT_PASSWORD_ENV}),不进数据库也不进仓库。 |
| | | * <p>连接口令不进数据库、不进仓库,也不打包进 jar,取值顺序见 {@link #credential}: |
| | | * 本地凭据文件 → 同名环境变量 → 同名 JVM 启动参数。三者都没有时装配失败, |
| | | * 错误信息同时点名文件路径与变量名,使"忘了放口令"是可见的失败而不是沉默的缺失。 |
| | | * 账号固定为现场取得的服务端只读账号,全权限账号不进入配置。 |
| | | */ |
| | | public final class NewareMysqlAdapterFactory implements DeviceAdapterFactory |
| | |
| | | private static final String READ_ONLY_USER = "visitor"; |
| | | /** 现场已知的全权限账号;写进交付配置就等于把误写风险带进现场。 */ |
| | | private static final List<String> FULL_PRIVILEGE_USERS = List.of("btsserver", "root"); |
| | | /** 凭据文件相对进程工作目录定位,使开发(仓库根)与交付(应用目录)用同一个位置。 */ |
| | | static final Path CREDENTIALS_FILE = Path.of("config", "vendor-credentials.properties"); |
| | | |
| | | @Override |
| | | public String adapterType() |
| | |
| | | { |
| | | String database = option(config.sourceConfig(), DATABASE_OPTION, DEFAULT_DATABASE); |
| | | String user = readOnlyUser(config.sourceConfig()); |
| | | String password = credential(option(config.sourceConfig(), PASSWORD_ENV_OPTION, |
| | | NewareMysqlAdapter.DEFAULT_PASSWORD_ENV)); |
| | | String key = option(config.sourceConfig(), PASSWORD_ENV_OPTION, |
| | | NewareMysqlAdapter.DEFAULT_PASSWORD_ENV); |
| | | String password = credential(key, CREDENTIALS_FILE.toAbsolutePath(), System.getenv(), |
| | | System::getProperty); |
| | | SingleConnectionVendorDataSource source = new SingleConnectionVendorDataSource( |
| | | jdbcUrl(config.host(), config.port(), database), user, password); |
| | | return NewareMysqlAdapter.create(config, source); |
| | |
| | | return user; |
| | | } |
| | | |
| | | /** 只读账号口令;启动参数优先于环境变量,便于本机排查而不必改环境。 */ |
| | | static String credential(String variableName) |
| | | /** |
| | | * 按「本地凭据文件 → 环境变量 → 启动参数」取值;键名由实例配置的 {@code passwordEnv} 指定。 |
| | | * 文件优先,是为了让部署只做"复制模板填一行"这一件事。 |
| | | */ |
| | | static String credential(String key, Path credentialsFile, Map<String, String> environment, |
| | | Function<String, String> startupParameters) |
| | | { |
| | | if (variableName == null || variableName.isBlank()) |
| | | throw new DeviceAdapterException("未配置厂商库口令的取值变量名"); |
| | | String value = System.getenv(variableName); |
| | | if (value == null || value.isBlank()) value = System.getProperty(variableName); |
| | | if (value == null || value.isBlank()) |
| | | throw new DeviceAdapterException("缺少厂商库连接口令:请设置环境变量 " + variableName); |
| | | return value; |
| | | if (key == null || key.isBlank()) throw new DeviceAdapterException("未配置厂商库口令的取值名"); |
| | | String fromFile = credentialFromFile(key, credentialsFile); |
| | | if (fromFile != null) return fromFile; |
| | | String fromEnvironment = environment == null ? null : environment.get(key); |
| | | if (fromEnvironment != null && !fromEnvironment.isBlank()) return fromEnvironment.trim(); |
| | | String fromParameter = startupParameters == null ? null : startupParameters.apply(key); |
| | | if (fromParameter != null && !fromParameter.isBlank()) return fromParameter.trim(); |
| | | throw new DeviceAdapterException("缺少厂商库连接口令:" + credentialsFile |
| | | + " 中没有键 " + key + ",环境变量与启动参数 -D" + key + " 也都没有"); |
| | | } |
| | | |
| | | /** 凭据文件缺失不是错误:交付形态可能只用环境变量,所以这里只负责"读到了就返回"。 */ |
| | | private static String credentialFromFile(String key, Path credentialsFile) |
| | | { |
| | | if (credentialsFile == null || !Files.isRegularFile(credentialsFile)) return null; |
| | | Properties properties = new Properties(); |
| | | try (InputStream input = Files.newInputStream(credentialsFile)) |
| | | { |
| | | properties.load(input); |
| | | } |
| | | catch (IOException exception) |
| | | { |
| | | throw new DeviceAdapterException("厂商库凭据文件无法读取:" + credentialsFile, exception); |
| | | } |
| | | String value = properties.getProperty(key); |
| | | return value == null || value.isBlank() ? null : value.trim(); |
| | | } |
| | | |
| | | /** |