refactor(device): 设备平台只记 iOS/Android
去掉系统版本、机型与版本号上报,表结构收敛为 device_id + platform。
This commit is contained in:
1 parent
2dadca6288
commit
229f1d0198
10 files changed
+39
-80
No files matched your search
+1
-5
@@ -75,14 +75,10 @@ public class FavoriteAdminService {
|
||||
vo.setAnonymous(row.getUserId() == null);
|
||||
if (profile != null) {
|
||||
vo.setPlatform(profile.getPlatform());
|
||||
vo.setOsVersion(profile.getOsVersion());
|
||||
vo.setDeviceModel(profile.getDeviceModel());
|
||||
}
|
||||
// 已登录用户的互动行不带 device_id,设备画像查不到时退回最近一次登录会话
|
||||
// 已登录用户的互动行不带 device_id,设备平台查不到时退回最近一次登录会话
|
||||
if (!StringUtils.hasText(vo.getPlatform()) && session != null) {
|
||||
vo.setPlatform(session.getPlatform());
|
||||
vo.setOsVersion(session.getOsVersion());
|
||||
vo.setDeviceModel(session.getDeviceModel());
|
||||
}
|
||||
vo.setDramaId(row.getDramaId());
|
||||
vo.setCreatedAt(row.getCreatedAt());
|
||||
|
||||
+1
-5
@@ -149,16 +149,12 @@ public class LikeAdminService {
|
||||
AppDeviceProfile profile = row.getDeviceId() == null ? null : profiles.get(row.getDeviceId());
|
||||
if (profile != null) {
|
||||
vo.setPlatform(profile.getPlatform());
|
||||
vo.setOsVersion(profile.getOsVersion());
|
||||
vo.setDeviceModel(profile.getDeviceModel());
|
||||
}
|
||||
// 已登录用户的互动行不带 device_id,设备画像查不到时退回最近一次登录会话
|
||||
// 已登录用户的互动行不带 device_id,设备平台查不到时退回最近一次登录会话
|
||||
if (!StringUtils.hasText(vo.getPlatform()) && row.getUserId() != null) {
|
||||
AppUserSession session = sessions.get(row.getUserId());
|
||||
if (session != null) {
|
||||
vo.setPlatform(session.getPlatform());
|
||||
vo.setOsVersion(session.getOsVersion());
|
||||
vo.setDeviceModel(session.getDeviceModel());
|
||||
}
|
||||
}
|
||||
vo.setTargetType(row.getTargetType());
|
||||
|
||||
+2
-4
@@ -182,11 +182,9 @@ public class WatchHistoryAdminService {
|
||||
vo.setSource(row.getSource());
|
||||
vo.setDeviceId(row.getDeviceId());
|
||||
vo.setWatchedAt(row.getWatchedAt());
|
||||
// 设备画像不依赖登录态,优先;没有画像(老数据)再回退到登录会话
|
||||
if (profile != null && (StringUtils.hasText(profile.getPlatform())
|
||||
|| StringUtils.hasText(profile.getOsVersion()))) {
|
||||
// 设备平台不依赖登录态,优先;没有记录(老数据)再回退到登录会话
|
||||
if (profile != null && StringUtils.hasText(profile.getPlatform())) {
|
||||
vo.setPlatform(profile.getPlatform());
|
||||
vo.setOsVersion(profile.getOsVersion());
|
||||
} else if (session != null) {
|
||||
vo.setPlatform(session.getPlatform());
|
||||
vo.setOsVersion(session.getOsVersion());
|
||||
|
||||
@@ -13,10 +13,8 @@ public class AdminFavoriteVO {
|
||||
private String uid;
|
||||
private String nickname;
|
||||
private String deviceId;
|
||||
/** 客户端系统:ANDROID / IOS / WEB,来自设备画像,游客同样有值。 */
|
||||
/** 客户端平台:ANDROID / IOS / WEB,来自设备平台记录,游客同样有值。 */
|
||||
private String platform;
|
||||
private String osVersion;
|
||||
private String deviceModel;
|
||||
private Boolean anonymous;
|
||||
private Long dramaId;
|
||||
private String dramaTitle;
|
||||
|
||||
@@ -13,10 +13,8 @@ public class AdminLikeVO {
|
||||
private String uid;
|
||||
private String nickname;
|
||||
private String deviceId;
|
||||
/** 客户端系统:ANDROID / IOS / WEB,来自设备画像,游客同样有值。 */
|
||||
/** 客户端平台:ANDROID / IOS / WEB,来自设备平台记录,游客同样有值。 */
|
||||
private String platform;
|
||||
private String osVersion;
|
||||
private String deviceModel;
|
||||
private Boolean anonymous;
|
||||
private Long dramaId;
|
||||
private String dramaTitle;
|
||||
|
||||
@@ -18,10 +18,10 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 收集客户端上报的设备画像(系统 / 机型 / 版本)。
|
||||
* 收集客户端上报的平台(iOS / Android)。
|
||||
*
|
||||
* <p>客户端在请求头带 `X-Client-Platform`、`X-Client-Os`、`X-Client-Model`、`X-App-Version`,
|
||||
* 这里按 device_id 建档,后台列表据此展示系统;游客请求同样入库,这是与旧会话方案的关键差异。
|
||||
* <p>客户端在请求头带 `X-Client-Platform`,这里按 device_id 建档,
|
||||
* 后台列表据此区分两端;游客请求同样入库,这是与旧会话方案的关键差异。
|
||||
* 同一设备 60 秒内只写一次,避免每个请求都打库。
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -61,10 +61,6 @@ public class DeviceProfileFilter extends OncePerRequestFilter {
|
||||
lastWriteAt.clear();
|
||||
}
|
||||
lastWriteAt.put(deviceId, now);
|
||||
deviceProfileService.touch(deviceId,
|
||||
request.getHeader("X-Client-Platform"),
|
||||
request.getHeader("X-Client-Os"),
|
||||
request.getHeader("X-Client-Model"),
|
||||
request.getHeader("X-App-Version"));
|
||||
deviceProfileService.touch(deviceId, request.getHeader("X-Client-Platform"));
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -11,7 +11,7 @@ import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 设备画像:按 device_id 记录客户端系统与机型。
|
||||
* 设备平台:按 device_id 记录客户端是 iOS 还是 Android。
|
||||
* 与会话不同,它不依赖用户身份,游客请求也会建档。
|
||||
*/
|
||||
@Data
|
||||
@@ -22,9 +22,6 @@ public class AppDeviceProfile {
|
||||
private Long id;
|
||||
private String deviceId;
|
||||
private String platform;
|
||||
private String osVersion;
|
||||
private String deviceModel;
|
||||
private String appVersion;
|
||||
private LocalDateTime firstSeenAt;
|
||||
private LocalDateTime lastSeenAt;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
+4
-10
@@ -10,23 +10,17 @@ import org.apache.ibatis.annotations.Param;
|
||||
public interface AppDeviceProfileMapper extends BaseMapper<AppDeviceProfile> {
|
||||
|
||||
/**
|
||||
* 按 device_id 建档或刷新。
|
||||
* 空字段不覆盖已有值:老版本客户端只带 deviceId,不能把已记录的系统抹掉。
|
||||
* 按 device_id 建档或刷新平台。
|
||||
* 空平台不覆盖已有值:老版本客户端不带平台头,不能把已记录的值抹掉。
|
||||
*/
|
||||
@Insert("""
|
||||
INSERT INTO app_device_profile (id, device_id, platform, os_version, device_model, app_version,
|
||||
INSERT INTO app_device_profile (id, device_id, platform,
|
||||
first_seen_at, last_seen_at, created_at, updated_at, deleted)
|
||||
VALUES (#{p.id}, #{p.deviceId}, #{p.platform}, #{p.osVersion}, #{p.deviceModel}, #{p.appVersion},
|
||||
VALUES (#{p.id}, #{p.deviceId}, #{p.platform},
|
||||
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0)
|
||||
ON CONFLICT (device_id) DO UPDATE SET
|
||||
platform = CASE WHEN EXCLUDED.platform <> '' THEN EXCLUDED.platform
|
||||
ELSE app_device_profile.platform END,
|
||||
os_version = CASE WHEN EXCLUDED.os_version <> '' THEN EXCLUDED.os_version
|
||||
ELSE app_device_profile.os_version END,
|
||||
device_model = CASE WHEN EXCLUDED.device_model <> '' THEN EXCLUDED.device_model
|
||||
ELSE app_device_profile.device_model END,
|
||||
app_version = CASE WHEN EXCLUDED.app_version <> '' THEN EXCLUDED.app_version
|
||||
ELSE app_device_profile.app_version END,
|
||||
last_seen_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
""")
|
||||
|
||||
+9
-22
@@ -10,7 +10,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/** 设备画像写入:按 device_id 建档,匿名请求也记录。 */
|
||||
import java.util.Locale;
|
||||
|
||||
/** 设备平台写入:按 device_id 建档,匿名请求也记录。 */
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -18,39 +20,24 @@ public class DeviceProfileService {
|
||||
|
||||
private static final int DEVICE_ID_MAX = 128;
|
||||
private static final int PLATFORM_MAX = 16;
|
||||
private static final int OS_VERSION_MAX = 64;
|
||||
private static final int DEVICE_MODEL_MAX = 128;
|
||||
private static final int APP_VERSION_MAX = 32;
|
||||
|
||||
private final AppDeviceProfileMapper mapper;
|
||||
|
||||
/**
|
||||
* 记录或刷新设备画像。header 全部为空时直接跳过,
|
||||
* 避免老版本客户端只带 deviceId 时产生无意义的空记录。
|
||||
*/
|
||||
public void touch(String deviceId, String platform, String osVersion, String deviceModel, String appVersion) {
|
||||
if (!StringUtils.hasText(deviceId)) {
|
||||
return;
|
||||
}
|
||||
String cleanPlatform = sanitize(platform, PLATFORM_MAX).toUpperCase();
|
||||
String cleanOs = sanitize(osVersion, OS_VERSION_MAX);
|
||||
String cleanModel = sanitize(deviceModel, DEVICE_MODEL_MAX);
|
||||
String cleanApp = sanitize(appVersion, APP_VERSION_MAX);
|
||||
if (cleanPlatform.isEmpty() && cleanOs.isEmpty() && cleanModel.isEmpty() && cleanApp.isEmpty()) {
|
||||
/** 记录或刷新设备平台;平台为空时直接跳过,避免产生无意义记录。 */
|
||||
public void touch(String deviceId, String platform) {
|
||||
String cleanPlatform = sanitize(platform, PLATFORM_MAX).toUpperCase(Locale.ROOT);
|
||||
if (!StringUtils.hasText(deviceId) || cleanPlatform.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AppDeviceProfile profile = new AppDeviceProfile();
|
||||
profile.setId(IdWorker.getId());
|
||||
profile.setDeviceId(sanitize(deviceId, DEVICE_ID_MAX));
|
||||
profile.setPlatform(cleanPlatform);
|
||||
profile.setOsVersion(cleanOs);
|
||||
profile.setDeviceModel(cleanModel);
|
||||
profile.setAppVersion(cleanApp);
|
||||
try {
|
||||
mapper.upsert(profile);
|
||||
} catch (RuntimeException ex) {
|
||||
// 画像只用于后台展示,写失败不能影响正常业务请求
|
||||
log.warn("device-profile upsert failed deviceId={} reason={}", profile.getDeviceId(), ex.getMessage());
|
||||
// 平台只用于后台展示,写失败不能影响正常业务请求
|
||||
log.warn("device-platform upsert failed deviceId={} reason={}", profile.getDeviceId(), ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- K111 设备画像:记录每个 device_id 的客户端系统与机型。
|
||||
-- 之前的系统信息只存在于 app_user_session / app_user_device,两者都依赖已登录账号,
|
||||
-- 游客行为完全拿不到系统;改成按 device_id 独立建档,匿名请求也能落库。
|
||||
-- K111 设备平台:记录每个 device_id 是 iOS 还是 Android。
|
||||
-- 会话/设备表都依赖已登录账号,游客行为拿不到平台;这里按 device_id 独立建档,
|
||||
-- 只存平台,系统版本与机型不上报。
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_device_profile (
|
||||
id BIGINT NOT NULL,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
platform VARCHAR(16) NOT NULL DEFAULT '',
|
||||
os_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
device_model VARCHAR(128) NOT NULL DEFAULT '',
|
||||
app_version VARCHAR(32) NOT NULL DEFAULT '',
|
||||
DROP TABLE IF EXISTS app_device_profile;
|
||||
|
||||
CREATE TABLE app_device_profile (
|
||||
id BIGINT NOT NULL,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
platform VARCHAR(16) NOT NULL DEFAULT '',
|
||||
first_seen_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_seen_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted SMALLINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted SMALLINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
-- 不带 deleted 谓词:写入用的 ON CONFLICT (device_id) 需要能推断到唯一索引;
|
||||
-- 设备画像不做软删,重复建档走更新。
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_app_device_profile_device
|
||||
-- 设备平台不做软删,重复建档走更新。
|
||||
CREATE UNIQUE INDEX uk_app_device_profile_device
|
||||
ON app_device_profile (device_id);
|
||||
|
||||
COMMENT ON TABLE app_device_profile IS '设备画像:device_id 维度的客户端系统与机型,匿名请求同样记录';
|
||||
COMMENT ON TABLE app_device_profile IS '设备平台:device_id 维度区分 iOS/Android,匿名请求同样记录';
|
||||
|
||||
COMMIT;
|
||||
Reference in new issue
Block a user