package com.yau.digitalrmb.training.application; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @Service public class TrainingTaskProgressService { private static final String GENERATION = "PBOC_CURRENCY_GENERATION"; private static final String ISSUANCE = "PBOC_CURRENCY_ISSUANCE"; private static final String WALLET = "WALLET_OPENING"; private static final String EXCHANGE = "CURRENCY_EXCHANGE"; private static final String PAYMENT = "CURRENCY_PAYMENT"; private final JdbcTemplate jdbc; private final Map definitions; public TrainingTaskProgressService(JdbcTemplate jdbc) { this.jdbc = jdbc; this.definitions = definitions(); } public List all(String userId) { List result = new ArrayList<>(); for (String moduleCode : definitions.keySet()) result.add(one(userId, moduleCode)); return result; } public TrainingTaskView one(String userId, String moduleCode) { ModuleDefinition module = definitions.get(moduleCode); if (module == null) throw new IllegalArgumentException("不支持的实训模块:" + moduleCode); List completed = jdbc.queryForList("SELECT step_code FROM training_task_step_progress WHERE user_id = ? AND module_code = ?", String.class, userId, moduleCode); return view(module, completed); } /** 真实业务步骤成功后调用;同步补齐此前步骤,避免历史业务数据导致进度断层。 */ public void completeThrough(String userId, String moduleCode, String stepCode) { ModuleDefinition module = definitions.get(moduleCode); if (module == null || module.unavailable) return; int index = module.indexOf(stepCode); if (index < 0) return; Instant now = Instant.now(); for (int i = 0; i <= index; i++) { String currentStep = module.steps.get(i).code; jdbc.update("INSERT INTO training_task_step_progress (user_id, module_code, step_code, completed_at) VALUES (?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE completed_at = completed_at", userId, moduleCode, currentStep, now); } } public void reset(String userId, String moduleCode) { jdbc.update("DELETE FROM training_task_step_progress WHERE user_id = ? AND module_code = ?", userId, moduleCode); } private TrainingTaskView view(ModuleDefinition module, List completed) { List steps = new ArrayList<>(); boolean prefixCompleted = true; int completedCount = 0; String currentStepCode = null; for (StepDefinition step : module.steps) { boolean done = prefixCompleted && completed.contains(step.code); String status; boolean actionAvailable = false; if (done) { status = "COMPLETED"; completedCount++; } else if (module.unavailable && completedCount == 0) { status = "NOT_STARTED"; prefixCompleted = false; } else if (prefixCompleted) { status = "CURRENT"; actionAvailable = true; currentStepCode = step.code; prefixCompleted = false; } else { status = "LOCKED"; } steps.add(new TrainingStepView(step.code, step.name, step.sortNo, status, actionAvailable)); } String status = completedCount == module.steps.size() ? "COMPLETED" : completedCount == 0 ? "NOT_STARTED" : "IN_PROGRESS"; return new TrainingTaskView(module.code, module.name, status, module.steps.size(), completedCount, currentStepCode, module.unavailable, module.unavailableReason, steps); } private Map definitions() { Map result = new LinkedHashMap<>(); result.put(GENERATION, module(GENERATION, "货币生成", false, null, "机构身份信息与密钥准备", "生成数字货币请求", "验证货币生成请求", "生成交易信息标识", "生成额度控制位请求", "验证额度控制位请求", "控制系统签名", "生成额度控制位", "生成标准面额币串")); result.put(ISSUANCE, module(ISSUANCE, "货币发行", false, null, "查看货币需求", "生成发行申请计划", "发行计划数字签名", "发送标准报文", "中央银行验证与业务核查", "准备金通知与ACS扣减", "发行并确权数字货币")); result.put(WALLET, module(WALLET, "开立数字钱包", true, "钱包开立真实业务接口尚未实现", "开立数字钱包")); result.put(EXCHANGE, module(EXCHANGE, "货币兑换", false, null, "确认银行卡、钱包和商业银行库存", "生成取币请求报文", "钱包私钥签名", "商业银行验签并扣款", "央行确认币串权属")); result.put(PAYMENT, module(PAYMENT, "数字货币支付", false, null, "初始化付款方和收款方钱包", "签名支付请求", "付款行预处理", "中央银行结算", "收款行入账", "生成支付回执与到账通知")); return Collections.unmodifiableMap(result); } private ModuleDefinition module(String code, String name, boolean unavailable, String reason, String... stepNames) { List steps = new ArrayList<>(); for (int i = 0; i < stepNames.length; i++) steps.add(new StepDefinition(String.format("%02d", i + 1), stepNames[i], (i + 1) * 10)); return new ModuleDefinition(code, name, unavailable, reason, steps); } @Getter @AllArgsConstructor public static class TrainingTaskView { private final String moduleCode; private final String moduleName; private final String status; private final int totalSteps; private final int completedSteps; private final String currentStepCode; private final boolean actionUnavailable; private final String unavailableReason; private final List steps; } @Getter @AllArgsConstructor public static class TrainingStepView { private final String stepCode; private final String stepName; private final int sortNo; private final String status; private final boolean actionAvailable; } private static class ModuleDefinition { private final String code, name; private final boolean unavailable; private final String unavailableReason; private final List steps; private ModuleDefinition(String code, String name, boolean unavailable, String unavailableReason, List steps) { this.code = code; this.name = name; this.unavailable = unavailable; this.unavailableReason = unavailableReason; this.steps = steps; } private int indexOf(String code) { for (int i = 0; i < steps.size(); i++) if (steps.get(i).code.equals(code)) return i; return -1; } } private static class StepDefinition { private final String code, name; private final int sortNo; private StepDefinition(String code, String name, int sortNo) { this.code = code; this.name = name; this.sortNo = sortNo; } } }