From 229f1d0198b7313cef8002f16873c4cb67245ffc Mon Sep 17 00:00:00 2001 From: jianghaifeng Date: Tue, 27 Oct 2026 14:00:12 +0800 Subject: [PATCH] =?UTF-8?q?refactor(device):=20=E8=AE=BE=E5=A4=87=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=8F=AA=E8=AE=B0=20iOS/Android?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 去掉系统版本、机型与版本号上报,表结构收敛为 device_id + platform。 --- .../admin/service/FavoriteAdminService.java | 6 +--- .../admin/service/LikeAdminService.java | 6 +--- .../service/WatchHistoryAdminService.java | 6 ++-- .../fruitshorts/admin/vo/AdminFavoriteVO.java | 4 +-- .../com/fruitshorts/admin/vo/AdminLikeVO.java | 4 +-- .../app/web/DeviceProfileFilter.java | 12 +++---- .../user/dal/entity/AppDeviceProfile.java | 5 +-- .../dal/mapper/AppDeviceProfileMapper.java | 14 +++------ .../user/service/DeviceProfileService.java | 31 ++++++------------- sql/migrate-k111-device-profile.sql | 31 +++++++++---------- 10 files changed, 39 insertions(+), 80 deletions(-) diff --git a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/FavoriteAdminService.java b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/FavoriteAdminService.java index 0116f5d..8193480 100644 --- a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/FavoriteAdminService.java +++ b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/FavoriteAdminService.java @@ -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()); diff --git a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/LikeAdminService.java b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/LikeAdminService.java index 56281d9..4520750 100644 --- a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/LikeAdminService.java +++ b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/LikeAdminService.java @@ -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()); diff --git a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/WatchHistoryAdminService.java b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/WatchHistoryAdminService.java index 288c917..8ec7f45 100644 --- a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/WatchHistoryAdminService.java +++ b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/service/WatchHistoryAdminService.java @@ -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()); diff --git a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminFavoriteVO.java b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminFavoriteVO.java index d2fcbff..809135a 100644 --- a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminFavoriteVO.java +++ b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminFavoriteVO.java @@ -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; diff --git a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminLikeVO.java b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminLikeVO.java index ba55b72..717254e 100644 --- a/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminLikeVO.java +++ b/fruitshorts-admin-api/src/main/java/com/fruitshorts/admin/vo/AdminLikeVO.java @@ -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; diff --git a/fruitshorts-app-api/src/main/java/com/fruitshorts/app/web/DeviceProfileFilter.java b/fruitshorts-app-api/src/main/java/com/fruitshorts/app/web/DeviceProfileFilter.java index e1adfca..1f9057c 100644 --- a/fruitshorts-app-api/src/main/java/com/fruitshorts/app/web/DeviceProfileFilter.java +++ b/fruitshorts-app-api/src/main/java/com/fruitshorts/app/web/DeviceProfileFilter.java @@ -18,10 +18,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** - * 收集客户端上报的设备画像(系统 / 机型 / 版本)。 + * 收集客户端上报的平台(iOS / Android)。 * - *

客户端在请求头带 `X-Client-Platform`、`X-Client-Os`、`X-Client-Model`、`X-App-Version`, - * 这里按 device_id 建档,后台列表据此展示系统;游客请求同样入库,这是与旧会话方案的关键差异。 + *

客户端在请求头带 `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")); } } diff --git a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/entity/AppDeviceProfile.java b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/entity/AppDeviceProfile.java index 3db5861..c380694 100644 --- a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/entity/AppDeviceProfile.java +++ b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/entity/AppDeviceProfile.java @@ -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; diff --git a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/mapper/AppDeviceProfileMapper.java b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/mapper/AppDeviceProfileMapper.java index 7490d0d..06db4b7 100644 --- a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/mapper/AppDeviceProfileMapper.java +++ b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/dal/mapper/AppDeviceProfileMapper.java @@ -10,23 +10,17 @@ import org.apache.ibatis.annotations.Param; public interface AppDeviceProfileMapper extends BaseMapper { /** - * 按 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 """) diff --git a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/DeviceProfileService.java b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/DeviceProfileService.java index 40ab409..72f574d 100644 --- a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/DeviceProfileService.java +++ b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/DeviceProfileService.java @@ -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()); } } diff --git a/sql/migrate-k111-device-profile.sql b/sql/migrate-k111-device-profile.sql index 6fca24c..73157f9 100644 --- a/sql/migrate-k111-device-profile.sql +++ b/sql/migrate-k111-device-profile.sql @@ -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;