|
|
@@ -11,6 +11,7 @@ import org.springframework.stereotype.Service;
|
|
|
import javax.annotation.Resource;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.security.MessageDigest;
|
|
|
+import java.sql.Timestamp;
|
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
@@ -21,6 +22,9 @@ import java.util.Map;
|
|
|
* <p>配置存于 {@code sys_config.onboarding_guide_config};已读状态按账号存于
|
|
|
* {@code user_onboarding_guide}(user_id + position 唯一)。是否展示由服务端判定:
|
|
|
* 新账号无记录必弹;同一账号在引导内容(版本号 + 实际展示端步骤文案)变化后重弹。</p>
|
|
|
+ *
|
|
|
+ * <p>老账号兼容:首次上线本逻辑时,{@code onboarding_guide_baseline_at} 记录基线时间,
|
|
|
+ * 基线之前注册的账号视为已读并静默写入记录,避免全体老用户重弹;基线之后注册的新账号照常弹。</p>
|
|
|
*/
|
|
|
@Service
|
|
|
public class GuideConfigService {
|
|
|
@@ -28,6 +32,7 @@ public class GuideConfigService {
|
|
|
private static final Logger logger = LoggerFactory.getLogger(GuideConfigService.class);
|
|
|
|
|
|
private static final String CONFIG_KEY = "onboarding_guide_config";
|
|
|
+ private static final String BASELINE_KEY = "onboarding_guide_baseline_at";
|
|
|
private static final String POSITION_HOME = "home";
|
|
|
private static final String POSITION_CHILD = "child";
|
|
|
private static final String ROLE_PARENT = "parent";
|
|
|
@@ -60,7 +65,7 @@ public class GuideConfigService {
|
|
|
String pos = normalizePosition(position);
|
|
|
Map<String, Object> config = getConfig();
|
|
|
Map<String, Object> data = config == null ? new LinkedHashMap<>() : new LinkedHashMap<>(config);
|
|
|
- String version = config == null ? "1.0" : String.valueOf(config.getOrDefault("version", "1.0"));
|
|
|
+ String version = versionOf(config);
|
|
|
String hash = contentHash(config, pos, role);
|
|
|
data.put("version", version);
|
|
|
data.put("contentHash", hash);
|
|
|
@@ -88,7 +93,8 @@ public class GuideConfigService {
|
|
|
"SELECT content_hash FROM user_onboarding_guide WHERE user_id=? AND position=?",
|
|
|
userId, position);
|
|
|
if (rows.isEmpty()) {
|
|
|
- return true;
|
|
|
+ // 老账号基线迁移:静默记为已读,避免上线后全体老用户重弹
|
|
|
+ return !isLegacyAccount(userId, position, config, hash);
|
|
|
}
|
|
|
Object stored = rows.get(0).get("content_hash");
|
|
|
return stored == null || !hash.equals(String.valueOf(stored));
|
|
|
@@ -103,17 +109,11 @@ public class GuideConfigService {
|
|
|
}
|
|
|
String pos = normalizePosition(position);
|
|
|
Map<String, Object> config = getConfig();
|
|
|
- String ver = (version == null || version.isEmpty())
|
|
|
- ? (config == null ? "1.0" : String.valueOf(config.getOrDefault("version", "1.0")))
|
|
|
- : version;
|
|
|
+ String ver = (version == null || version.isEmpty()) ? versionOf(config) : version;
|
|
|
String hash = (contentHash == null || contentHash.isEmpty())
|
|
|
? contentHash(config, pos, role)
|
|
|
: contentHash;
|
|
|
- jdbcTemplate.update(
|
|
|
- "INSERT INTO user_onboarding_guide (user_id, position, version, content_hash, done_at) "
|
|
|
- + "VALUES (?, ?, ?, ?, NOW()) "
|
|
|
- + "ON DUPLICATE KEY UPDATE version=VALUES(version), content_hash=VALUES(content_hash), done_at=NOW()",
|
|
|
- userId, pos, ver, hash);
|
|
|
+ upsertSeen(userId, pos, ver, hash);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -123,11 +123,10 @@ public class GuideConfigService {
|
|
|
* role 缺省时按 position 推断。</p>
|
|
|
*/
|
|
|
public String contentHash(Map<String, Object> config, String position, String role) {
|
|
|
- String version = config == null ? "1.0" : String.valueOf(config.getOrDefault("version", "1.0"));
|
|
|
String end = resolveEnd(position, role);
|
|
|
Object rawSteps = config == null ? null : config.get(end);
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("version=").append(version).append('|');
|
|
|
+ sb.append("version=").append(versionOf(config)).append('|');
|
|
|
sb.append("end=").append(end).append('|');
|
|
|
if (rawSteps instanceof List) {
|
|
|
for (Object step : (List<?>) rawSteps) {
|
|
|
@@ -144,6 +143,48 @@ public class GuideConfigService {
|
|
|
return sha256Hex(sb.toString());
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 基线之前注册的账号:静默写入当前内容已读记录并返回 true。
|
|
|
+ * 基线未就绪或账号创建时间晚于基线时返回 false(按新账号处理)。
|
|
|
+ */
|
|
|
+ private boolean isLegacyAccount(Long userId, String position, Map<String, Object> config, String hash) {
|
|
|
+ String baselineValue = sysConfigService.getValue(BASELINE_KEY);
|
|
|
+ if (baselineValue == null || baselineValue.trim().isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ long baseline;
|
|
|
+ try {
|
|
|
+ baseline = Long.parseLong(baselineValue.trim());
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ logger.warn("首页引导基线时间格式异常: {}", baselineValue);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Timestamp createdAt;
|
|
|
+ try {
|
|
|
+ createdAt = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT created_at FROM users WHERE id=?", Timestamp.class, userId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (createdAt == null || createdAt.getTime() > baseline) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ upsertSeen(userId, position, versionOf(config), hash);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void upsertSeen(Long userId, String position, String version, String hash) {
|
|
|
+ jdbcTemplate.update(
|
|
|
+ "INSERT INTO user_onboarding_guide (user_id, position, version, content_hash, done_at) "
|
|
|
+ + "VALUES (?, ?, ?, ?, NOW()) "
|
|
|
+ + "ON DUPLICATE KEY UPDATE version=VALUES(version), content_hash=VALUES(content_hash), done_at=NOW()",
|
|
|
+ userId, position, version, hash);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String versionOf(Map<String, Object> config) {
|
|
|
+ return config == null ? "1.0" : String.valueOf(config.getOrDefault("version", "1.0"));
|
|
|
+ }
|
|
|
+
|
|
|
private String resolveEnd(String position, String role) {
|
|
|
if (ROLE_CHILD.equals(role)) {
|
|
|
return ROLE_CHILD;
|