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