| | |
| | | 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.credentials.LocalCredentials; |
| | | import com.battery.device.api.model.AdapterCapabilities; |
| | | import com.battery.device.api.registry.AdapterInstanceConfig; |
| | | import com.battery.device.api.registry.DeviceAdapterFactory; |
| | |
| | | /** 现场已知的全权限账号;写进交付配置就等于把误写风险带进现场。 */ |
| | | private static final List<String> FULL_PRIVILEGE_USERS = List.of("btsserver", "root"); |
| | | /** 凭据文件相对进程工作目录定位,使开发(仓库根)与交付(应用目录)用同一个位置。 */ |
| | | static final Path CREDENTIALS_FILE = Path.of("config", "vendor-credentials.properties"); |
| | | static final Path CREDENTIALS_FILE = LocalCredentials.DEFAULT_FILE; |
| | | |
| | | @Override |
| | | public String adapterType() |
| | |
| | | String user = readOnlyUser(config.sourceConfig()); |
| | | String key = option(config.sourceConfig(), PASSWORD_ENV_OPTION, |
| | | NewareMysqlAdapter.DEFAULT_PASSWORD_ENV); |
| | | String password = credential(key, CREDENTIALS_FILE.toAbsolutePath(), System.getenv(), |
| | | String password = LocalCredentials.resolve(key, CREDENTIALS_FILE.toAbsolutePath(), System.getenv(), |
| | | System::getProperty); |
| | | SingleConnectionVendorDataSource source = new SingleConnectionVendorDataSource( |
| | | jdbcUrl(config.host(), config.port(), database), user, password); |
| | |
| | | if (FULL_PRIVILEGE_USERS.contains(user.toLowerCase(Locale.ROOT))) |
| | | throw new DeviceAdapterException("厂商数据源只允许只读账号,拒绝全权限账号:" + user); |
| | | return user; |
| | | } |
| | | |
| | | /** |
| | | * 按「本地凭据文件 → 环境变量 → 启动参数」取值;键名由实例配置的 {@code passwordEnv} 指定。 |
| | | * 文件优先,是为了让部署只做"复制模板填一行"这一件事。 |
| | | */ |
| | | static String credential(String key, Path credentialsFile, Map<String, String> environment, |
| | | Function<String, String> startupParameters) |
| | | { |
| | | 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(); |
| | | } |
| | | |
| | | /** |