精华内容
下载资源
问答
  • /** * deviceID的组成为:渠道标志+识别符来源标志+hash后的终端识别符 * * 渠道标志为: * 1,andriod(a) * * 识别符来源标志: * 1, wifi mac地址(wifi); * 2, IMEI(imei); * 3, ...
    1. /**</span></p>  * deviceID的组成为:渠道标志+识别符来源标志+hash后的终端识别符 
    2.   * 
    3.   * 渠道标志为: 
    4.   * 1,andriod(a) 
    5.   * 
    6.   * 识别符来源标志: 
    7.   * 1, wifi mac地址(wifi); 
    8.   * 2, IMEI(imei); 
    9.   * 3, 序列号(sn); 
    10.   * 4, id:随机码。若前面的都取不到时,则随机生成一个随机码,需要缓存。 
    11.   * 
    12.   * @param context 
    13.   * @return 
    14.   */  
    15. public static String getDeviceId(Context context) {  
    16.   StringBuilder deviceId = new StringBuilder();  
    17.   // 渠道标志  
    18.   deviceId.append("a");  
    19.   try {  
    20.     //wifi mac地址  
    21.     WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);  
    22.     WifiInfo info = wifi.getConnectionInfo();  
    23.     String wifiMac = info.getMacAddress();  
    24.     if(!isEmpty(wifiMac)){  
    25.       deviceId.append("wifi");  
    26.       deviceId.append(wifiMac);  
    27.       PALog.e("getDeviceId : ", deviceId.toString());  
    28.       return deviceId.toString();  
    29.     }  
    30.     //IMEI(imei)  
    31.     TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);  
    32.     String imei = tm.getDeviceId();  
    33.     if(!isEmpty(imei)){  
    34.       deviceId.append("imei");  
    35.       deviceId.append(imei);  
    36.       PALog.e("getDeviceId : ", deviceId.toString());  
    37.       return deviceId.toString();  
    38.     }  
    39.     //序列号(sn)  
    40.     String sn = tm.getSimSerialNumber();  
    41.     if(!isEmpty(sn)){  
    42.       deviceId.append("sn");  
    43.       deviceId.append(sn);  
    44.       PALog.e("getDeviceId : ", deviceId.toString());  
    45.       return deviceId.toString();  
    46.     }  
    47.     //如果上面都没有, 则生成一个id:随机码  
    48.     String uuid = getUUID(context);  
    49.     if(!isEmpty(uuid)){  
    50.       deviceId.append("id");  
    51.       deviceId.append(uuid);  
    52.       PALog.e("getDeviceId : ", deviceId.toString());  
    53.       return deviceId.toString();  
    54.     }  
    55.   } catch (Exception e) {  
    56.     e.printStackTrace();  
    57.     deviceId.append("id").append(getUUID(context));  
    58.   }  
    59.   PALog.e("getDeviceId : ", deviceId.toString());  
    60.   return deviceId.toString();  
    61. }  
    62. /** 
    63.  * 得到全局唯一UUID 
    64.  */  
    65. public static String getUUID(Context context){  
    66.   SharedPreferences mShare = getSysShare(context, "sysCacheMap");  
    67.   if(mShare != null){  
    68.     uuid = mShare.getString("uuid""");  
    69.   }  
    70.   if(isEmpty(uuid)){  
    71.     uuid = UUID.randomUUID().toString();  
    72.     saveSysMap(context, "sysCacheMap""uuid", uuid);  
    73.   }  
    74.   PALog.e(tag, "getUUID : " + uuid);  
    75. return uuid;  

    有时需要对用户设备进行标识,所以希望能够得到一个稳定可靠并且唯一的识别码。虽然Android系统中提供了这样设备识别码,但是由于Android系统版本、厂商定制系统中的Bug等限制,稳定性和唯一性并不理想。而通过其他硬件信息标识也因为系统版本、手机硬件等限制存在不同程度的问题。

    下面收集了一些“有能力”或“有一定能力”作为设备标识的串码。

    DEVICE_ID

    这是Android系统为开发者提供的用于标识手机设备的串号,也是各种方法中普适性较高的,可以说几乎所有的设备都可以返回这个串号,并且唯一性良好。

    这个DEVICE_ID可以同通过下面的方法获取:

    1. TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);  
    2. String DEVICE_ID = tm.getDeviceId();   

    假设我们确实需要用到真实设备的标识,可能就需要用到DEVICE_ID。在以前,我们的Android设备是手机,这个DEVICE_ID可以同通过TelephonyManager.getDeviceId()获取,它根据不同的手机设备返回IMEI,MEID或者ESN码,但它在使用的过程中会遇到很多问题:

    • 非手机设备: 如果只带有Wifi的设备或者音乐播放器没有通话的硬件功能的话就没有这个DEVICE_ID
    • 权限: 获取DEVICE_ID需要READ_PHONE_STATE权限,但如果我们只为了获取它,没有用到其他的通话功能,那这个权限有点大才小用
    • bug:在少数的一些手机设备上,该实现有漏洞,会返回垃圾,如:zeros或者asterisks的产品

    MAC ADDRESS

    可以使用手机Wifi或蓝牙的MAC地址作为设备标识,但是并不推荐这么做,原因有以下两点:

    • 硬件限制:并不是所有的设备都有Wifi和蓝牙硬件,硬件不存在自然也就得不到这一信息。
    • 获取的限制:如果Wifi没有打开过,是无法获取其Mac地址的;而蓝牙是只有在打开的时候才能获取到其Mac地址。

    获取Wifi Mac地址:

    获取蓝牙 Mac地址:

    Sim Serial Number

    装有SIM卡的Android 2.3设备,可以通过下面的方法获取到Sim Serial Number:

    1. TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);   
    2. String SimSerialNumber = tm.getSimSerialNumber();   

    注意:对于CDMA设备,返回的是一个空值!

    ANDROID_ID

    在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,当设备被wipe后该值会被重置。可以通过下面的方法获取:

    1. import android.provider.Settings;  
    2. String ANDROID_ID = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID);  
    ANDROID_ID可以作为设备标识,但需要注意:
    • 厂商定制系统的Bug:不同的设备可能会产生相同的ANDROID_ID:9774d56d682e549c。
    • 厂商定制系统的Bug:有些设备返回的值为null。
    • 设备差异:对于CDMA设备,ANDROID_ID和TelephonyManager.getDeviceId() 返回相同的值。
    • 它在Android <=2.1 or Android >=2.3的版本是可靠、稳定的,但在2.2的版本并不是100%可靠的

    Serial Number

    Android系统2.3版本以上可以通过下面的方法得到Serial Number,且非手机设备也可以通过该接口获取。

    1. String SerialNumber = android.os.Build.SERIAL;   

    以上几种方式都或多或少存在一定的局限性或者Bug,如果并不是确实需要对硬件本身进行绑定,使用自己生成的UUID也是一个不错的选择,因为该方法无需访问设备的资源,也跟设备类型无关。

    Installtion ID

    这种方式的原理是在程序安装后第一次运行时生成一个ID,该方式和设备唯一标识不一样,不同的应用程序会产生不同的ID,同一个程序重新安装也会不同。所以这不是设备的唯一ID,但是可以保证每个用户的ID是不同的。可以说是用来标识每一份应用程序的唯一ID(即Installtion ID),可以用来跟踪应用的安装数量等。

    Google Developer Blog提供了这样的一个框架:

    1. public class Installation {  
    2.     private static String sID = null;  
    3.     private static final String INSTALLATION = "INSTALLATION";  
    4.   
    5.     public synchronized static String id(Context context) {  
    6.         if (sID == null) {    
    7.             File installation = new File(context.getFilesDir(), INSTALLATION);  
    8.             try {  
    9.                 if (!installation.exists())  
    10.                     writeInstallationFile(installation);  
    11.                 sID = readInstallationFile(installation);  
    12.             } catch (Exception e) {  
    13.                 throw new RuntimeException(e);  
    14.             }  
    15.         }  
    16.         return sID;  
    17.     }  
    18.   
    19.     private static String readInstallationFile(File installation) throws IOException {  
    20.         RandomAccessFile f = new RandomAccessFile(installation, "r");  
    21.         byte[] bytes = new byte[(int) f.length()];  
    22.         f.readFully(bytes);  
    23.         f.close();  
    24.         return new String(bytes);  
    25.     }  
    26.   
    27.     private static void writeInstallationFile(File installation) throws IOException {  
    28.         FileOutputStream out = new FileOutputStream(installation);  
    29.         String id = UUID.randomUUID().toString();  
    30.         out.write(id.getBytes());  
    31.         out.close();  
    32.     }  
    33. }  

    设备唯一ID

    上文可以看出,Android系统中并没有可以可靠获取所有厂商设备唯一ID的方法,各个方法都有自己的使用范围和局限性,这也是目前流行的Android系统版本过多,设备也是来自不同厂商,且没有统一标准等原因造成的。

    从目前发展来看,Android系统多版本共存还会持续较长的时间,而Android系统也不会被某个设备生产厂商垄断,长远看Android基础系统将会趋于稳定,设备标识也将会作为系统基础部分而标准化,届时这一问题才有望彻底解决。

    目前的解决办法,比较可行的是一一适配,在保证大多数设备方便的前提下,如果获取不到,使用其他备选信息作为标识,即自己再封装一个设备ID出来,通过内部算法保证尽量和设备硬件信息相关,以及标识的唯一性。

    总结

    综合以上所述,为了实现在设备上更通用的获取设备唯一标识,我们可以实现这样的一个类,为每个设备产生唯一的UUID,以ANDROID_ID为基础,在获取失败时以TelephonyManager.getDeviceId()为备选方法,如果再失败,使用UUID的生成策略。

    重申下,以下方法是生成Device ID,在大多数情况下Installtion ID能够满足我们的需求,但是如果确实需要用到Device ID,那可以通过以下方式实现:

    1. import android.content.Context;  
    2. import android.content.SharedPreferences;  
    3. import android.provider.Settings.Secure;  
    4. import android.telephony.TelephonyManager;  
    5. import java.io.UnsupportedEncodingException;  
    6. import java.util.UUID;  
    7.   
    8. public class DeviceUuidFactory {  
    9.     protected static final String PREFS_FILE = "device_id.xml";  
    10.     protected static final String PREFS_DEVICE_ID = "device_id";  
    11.     protected static UUID uuid;  
    12.   
    13.     public DeviceUuidFactory(Context context) {  
    14.         if( uuid ==null ) {  
    15.             synchronized (DeviceUuidFactory.class) {  
    16.                 if( uuid == null) {  
    17.                     final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0);  
    18.                     final String id = prefs.getString(PREFS_DEVICE_ID, null );  
    19.                     if (id != null) {  
    20.                         // Use the ids previously computed and stored in the prefs file  
    21.                         uuid = UUID.fromString(id);  
    22.                     } else {  
    23.                         final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);  
    24.                         // Use the Android ID unless it's broken, in which case fallback on deviceId,  
    25.                         // unless it's not available, then fallback on a random number which we store  
    26.                         // to a prefs file  
    27.                         try {  
    28.                             if (!"9774d56d682e549c".equals(androidId)) {  
    29.                                 uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));  
    30.                             } else {  
    31.                                 final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();  
    32.                                 uuid = deviceId!=null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();  
    33.                             }  
    34.                         } catch (UnsupportedEncodingException e) {  
    35.                             throw new RuntimeException(e);  
    36.                         }  
    37.                         // Write the value out to the prefs file  
    38.                         prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();  
    39.                     }  
    40.                 }  
    41.             }  
    42.         }  
    43.     }  
    44.     /** 
    45.      * Returns a unique UUID for the current android device.  As with all UUIDs, this unique ID is "very highly likely" 
    46.      * to be unique across all Android devices.  Much more so than ANDROID_ID is. 
    47.      * 
    48.      * The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on 
    49.      * TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back 
    50.      * on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a 
    51.      * usable value. 
    52.      * 
    53.      * In some rare circumstances, this ID may change.  In particular, if the device is factory reset a new device ID 
    54.      * may be generated.  In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2 
    55.      * to a newer, non-buggy version of Android, the device ID may change.  Or, if a user uninstalls your app on 
    56.      * a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation. 
    57.      * 
    58.      * Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT 
    59.      * change after a factory reset.  Something to be aware of. 
    60.      * 
    61.      * Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly. 
    62.      * 
    63.      * @see http://code.google.com/p/android/issues/detail?id=10603 
    64.      * 
    65.      * @return a UUID that may be used to uniquely identify your device for most purposes. 
    66.      */  
    67.     public UUID getDeviceUuid() {  
    68.         return uuid;  
    69.     }  
    70. }  
    如何获取Android手机的唯一标识?

    代码: 这里是你在Android里读出 唯一的 IMSI-ID / IMEI-ID 的方法。

    Java: 
    1. String myIMSI = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_IMSI);   
    2. // within my emulator it returns: 310995000000000   
    3.   
    4. String myIMEI = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_IMEI);   
    5. // within my emulator it returns: 000000000000000   
    注:android.os.SystemProperties的标签被打上@hide了,所以sdk中并不会存在。如果需要使用,需要有android的source code支持。



    展开全文
  • 产品运营Oneid的实现-合理标识用户的唯一性 “平生不修善果,只爱杀人放火。忽地顿开金绳,这里扯断玉锁。钱塘江上潮信来,今日方知我是我”,这是鲁智深生前在杭州六合寺对自己的人生总结和一生的感悟。仿佛在此...

    产品运营中Oneid的实现-合理标识用户的唯一性

    “平生不修善果,只爱杀人放火。忽地顿开金绳,这里扯断玉锁。钱塘江上潮信来,今日方知我是我”,这是鲁智深生前在杭州六合寺对自己的人生总结和一生的感悟。仿佛在此之前的鲁智深处于一种非常混沌的状态,不清楚自己是谁,像隐匿了自己身份的一个人。直至到了六合寺听到钱塘江上潮信的瞬间,顿时参透人生真谛,把之前所有的事情关联起来,找到了最真实的自己。

    在这里插入图片描述

    那么,鲁智深的事情与本文要提及的产品运营中Oneid的实现有什么关系呢?我们都知道产品运营的基础是埋点,埋点中对用户进行唯一性标识犹如鲁智深在听到钱塘潮信悟透自己的平生一样,即在某种场景下触发匿名身份的用户和真实身份用户之间的关联,进而把两种状态下用户行为归集为一个用户,完成用户的唯一性标识。

    一、为何要对用户进行标识

    用户唯一标识,是用户唯一的身份ID,相同的身份ID,就会被当做是相同的一个用户。在进行埋点数据采集方案设计时,如何对用户唯一性进行标识对数据准确性影响较大。即如何区分某个用户是此用户,而非彼用户是至关重要的,因为如果做不到对用户的唯一性进行识别,那么凡是涉及到用户的数据将都是不准确的,比如:累计用户量、新增用户量、活跃用户量。因此,选取合适的用户标识对于提高用户行为分析的准确性显得尤为重要,尤其是诸如漏斗、留存、分群等用户相关的分析。

    二、什么是用户标识

    在互联网高速发展的今天,我们都在积极主动的融入这个世界,信息在高速流转,不再像以前那样闭塞。各类互联网产品也在以一种前所未有的开放态度拥抱其用户,用户在触达一款应用时,可以在匿名(非登录)状态下使用应用中的大部分功能,此时用户的匿名ID一般是指设备ID。在支付、充值、积分等一些关键的业务环节,应用会要求用户以某种方式进行登录。目前比较常见的登录方式主要是手机号,金融、政务、医疗、教育相关的应用除手机号以外,还支持身份证号、卡号等登录方式,我们称之为登录ID。(图:常见应用的登录界面)

    在这里插入图片描述
    目前常见的需要做用户唯一性标识进而提升数据准确性的场景大致如下:

    a)一个用户在多个设备上使用同一个应用,如何对该用户的唯一性进行标识,进而把用户的行为归属到同一个用户上。

    比如:用户A在移动设备X上使用中原银行手机银行浏览并购买了我行的中原如意宝这款年化高、可自主选择投资期限、比较符合自己投资意愿的理财产品,购买过程包含匿名访问与登录等操作。一周后,用户A在另外一个移动设备Y上登录我行的手机银行查看了其购买的理财的收益情况。若不对用户进行唯一性标识,埋点数据上报的用户量就是2个,而实际是1个。

    b)与一个用户在多个设备上使用同一个应用相对应的是:多个用户在同一台设备上操作同一个应用。

    详细的场景介绍,这里就不再描述,大家自行脑补。

    在进行埋点方案设计时,为了对用户唯一性进行标识,需要在方案中写明如何采集用户匿名状态下的ID(设备ID)及登录状态下的登录ID,这里分别以first_id、second_id命名,后续文中涉及到的first_id、second_id均指匿名ID、登录ID。基于采集到的与用户相关的first_id与second_id,在行为分析系统后台通过一定的关联规则,生成一个能够唯一标识用户的user_id,在以后的分析过程中,与用户量有关的指标均已user_id为基础进行统计分析。

    在这里插入图片描述

    匿名ID即设备ID,在不同类型的客户端上有所不同,且同一类型的客户端上,设备ID也并非是唯一的。例如 Web 端的 Cookies 有可能被各种安全卫士清空,而 iOS 端的 IDFV 在不同厂商的 App 间是不同的。

    • Android端
      安卓系统经过多次升级,对权限控制越来越严格,唯一识别手机的方法也在不断发生变化。 Android端适合作为设备ID标识符的有OAID、Android_id、UUID、IMEI。IMEI是最适合做设备唯一标识的,但获取IMEI需要授予权限且Android 10以后不再开放IMEI的权限,App 卸载重装 UUID(如:550e8400-e29b-41d4-a716-446655440000)会发生变化。综合起来,Android(如:774d56d682e549c)端比较适合作为标识符的是Android_id,如果 Android_Id 获取不到则获取随机的 UUID。

    • iOS端
      苹果系统,可用于识别唯一设备的标识不像Andriod那样多。综合起来,苹果系统生成设备ID的标识符顺序应该是IDFA -> IDFV ->UDID,即优先获取IDFA(如:1E2DFA89-496A-47FD-9941-DF1FC4E6484A),获取不到在获取IDFV(如:1E2DFA10-236A-47UD-6641-AB1FC4E6483F),获取不到IDFV时,再获取UUID(如:550e8400-e29b-41d4-a716-446655440000)。

    • JavaScript
      默认情况下使用 cookie_id(例如:15ffdb0a3f898-02045d1cb7be78-31126a5d-250125-15ffdb0a3fa40a),存贮在浏览器的 cookie 中。

    • 微信小程序
      默认情况下使用 UUID(例如:1558509239724-9278730-00c1875d5f63f8-41373096),但是删除小程序,UUID 会变。为了保证设备 ID 不变,建议获取并使用 openid(例如:oWDMZ0WHqfsjIz7A9B2XNQOWmN3E)。 如果选择使用 openid 的话,请注意操作暂存,由于获取 openid 是一个异步的操作,但是冷启动事件等会先发生,所以我们会把先发生的操作,暂存起来,等获取到 openid 后才会发送数据。

    三、如何做用户标识

    在介绍用户标识的实现方法前,先带大家了解一下,当前行为数据分析系统数据储存的模型。

    目前主流的数据储存模型是用一张events表存储与用户相关的事件,其中event表中有个distinct_id字段,在事件发生时用户如处于匿名状态,则记录设备ID,登录状态下记录登录ID。用users表用来储存用户的匿名ID(设备ID)、登录ID、基于关联规则生成的user_id等用户属性。这样通过events表和users表,就可以成功的把用户与事件联系在一起。

    • 用户唯一性标识方案一:只使用设备ID进行标识

      适用场景:适合没有用户注册体系,或者极少数用户会进行多设备登录的产品,如工具类产品、搜索引擎、部分电商等。

      场景举例
      在这里插入图片描述
      行为序列说明

      1. 某用户在华为手机上新安装了 App,并进行了一系列操作,events表中distinct_id为设备ID,记为X,分配得到的user_id为1,同时把1、X分别存入users表中的user_id、first_id中。
      2. 该用户进行了注册并登录,设备未变,发送的 distinct_id 仍为 X,user_id仍为1。
      3. 该用户登录之后继续进行一系列操作,发送的 distinct_id 仍 为 X,user_id仍为1。
      4. 该用户把手机送给朋友了,朋友用自己的账号登录设备 X,发送的 distinct_id 仍为 X,user_id仍为1。
      5. 该用户的朋友一直使用自己的账号在设备 X 上进行了一系列操作,由于设备未变,所以user_id仍为1。
      6. 该用户更换了新的小米手机,进行一系列操作,此时设备ID 变为Y,发送的 distinct_id 为 Y,分配的user_id为 2,则将user_id 2、设备ID Y 存入 users 表的 id, first_id 字段。该用户登录之后的后续操作,都会以user_id 2标识,只要不更换设备。

      在上述场景中,仅使用设备 ID 识别用户的好处就是逻辑很简单,当然局限性也很明显:

      1.当用户换手机后,用户换手机前后的行为无法关联上。

      2.当用户把手机送给朋友后,朋友的行为却仍记在该用户下。

    • 用户唯一性标识方案二:关联设备ID与登录ID(又称一对一)

      适用场景:成功关联设备 ID 和登录 ID 之后,用户在该设备 ID 上或该登录 ID 下的行为就会贯通,被认为是一个user_id发生的。在进行事件、漏斗、留存等用户相关分析时也会算作一个用户。所以一般来说,当遇到以下场景时,考虑一对一的关联:

      1.需要贯通一个用户在一个设备上注册前后的行为。

      2.需要贯通一个注册用户在不同设备上登录之后的行为。

      场景举例
      在这里插入图片描述
      行为序列说明

      1. 某用户在小米手机上新安装了 App,并进行了一系列操作,对应的设备ID 为 X,事件表中 distinct_id 为 X,对应分配的user_id为 1,则将user_id 1、设备ID X 存入 users 表的user_id,,first_id 字段。
      2. 该用户进行了注册并登录,其登录ID为A,事件表中 distinct_id 为A,设备ID X 和登录 ID A 关联成功,将登录ID A存入users 表的 second_id 字段,use_id仍为1。
      3. 该用户登录后继续进行一系列操作,事件表中distinct_id为 A,user_id仍为1。
      4. 该用户把手机送给朋友了,朋友用自己的账号登录设备 X,登录ID为B,将设备 ID X与登录ID B进行关联,由于 X 已与 A 关联,所以此次关联会失败,同时会分配一个新的user_id 2来标识此用户,并将登录ID B 同时存入 users 表的 first_id 和 second_id 字段(用户的朋友账号上之前未关联过别的设备,且首次登录设备关联失败,则将登录 ID 同时记录到 first_id 上),此时又称自关联。
      5. 之后,该用户的朋友一直使用账号B在设备 X 上进行了一系列操作,事件表中的 distinct_id为B,后续会用user_id 2来标识此用户。
      6. 该用户更换了一个新的苹果手机,并进行一系列操作,在未登录前,用新设备ID Y来标识用户,events表中的distinct_id 为Y,对应分配的user_id为 3,将user_id 3 、设备ID Y 存入 users 表的 id, first_id字段。
      7. 该用户在苹果手机上使用账号A进行登录,此时将尝试将设备ID Y与登录ID A 进行关联,由于 A 已经与 X 关联,因此会关联失败,但是依然会切换到以A 为登录ID的用户,其对应的user_id为1。
      8. 该用户登录之后的后续操作,事件表中的distinct_id 为A,所以仍以user_id 1 标识。

      在上述场景中,很大程度上已经实现了跨设备的用户贯通,但仍存在局限性:

      1.当用户换手机后,虽然登录账号之后的行为与换手机之前的行为贯通了,但是在新设备上首次登录之前的行为仍没法贯通,仍被识别为新的用户的行为。

      2.当用户把旧手机送给朋友之后,由于旧手机已被关联到自己的登录 ID 了,无法再与朋友的登录 ID 关联。后续使用这台旧手机的用户们,若不登录就操作,则都会被识别为同一个用户(旧手机成功关联的登录 ID)。

    • 用户唯一性标识方案三:关联登录ID与多个设备ID(又称多对一)

      一对一虽然已经实现了跨设备的用户贯通,但是对于某些应用场景还是不够准确,因此产生了另外一种关联方案,支持一个登录 ID 绑定多个设备 ID 的方案,从而实现更准确的用户追踪。

      适用场景:一个用户在多个设备上进行登录是一种比较常见的场景,比如 Web 端和 App 端可能都需要进行登录。支持一个登录 ID 下关联多设备 ID 之后,用户在多设备下的行为就会贯通,被认为是一个user_id发生的。
      场景举例
      在这里插入图片描述
      行为序列说明

      1. 某用户在小米手机上新安装了 App,并进行了一系列操作,对应的设备ID 为 X,事件表中 distinct_id 为 X,对应分配的user_id为 1,则将user_id 1、设备ID X 存入 users 表的user_id,,first_id 字段。
      2. 该用户进行了注册并登录,其登录ID为A,事件表中 distinct_id 为A,设备ID X 和登录 ID A 关联成功,将登录ID A存入users 表的 second_id 字段,use_id仍为1。
      3. 该用户登录后继续进行一系列操作,事件表中distinct_id为A,user_id仍为1。
      4. 该用户把手机送给朋友了,朋友用自己的账号登录设备 X,登录ID为B,将设 备 ID X与登录ID B进行关联,由于 X 已与 A 关联,所以此次关联会失败,同时会分配一个新的user_id 2来标识此用户,并将登录ID B 同时存入 users 表的 first_id 和 second_id 字段(用户的朋友账号上之前未关联过别的设备,且首次登录设备关联失败,则将登录 ID 同时记录到 first_id 上),此时又称自关联。
      5. 之后,该用户的朋友一直使用账号B在设备 X 上进行了一系列操作,事件表中的 distinct_id为B,后续会用user_id 2来标识此用户。
      6. 该用户更换了一个新的苹果手机,并进行一系列操作,在未登录前,用新设备ID Y来标识用户,events表中的distinct_id 为Y,对应分配的user_id为 3,将user_id 3 、设备ID Y 存入 users 表的 id, first_id字段。
      7. 该用户在苹果手机上使用账号A进行登录,此时将尝试将设备ID Y与登录ID A 进行关联,关联成功,其对应的user_id为依然1,同时将设备ID Y 添加到 users表中user_id 1 的 $device_id_list 字段。
      8. 该用户登录之后的后续操作,事件表中的distinct_id 为A,所以仍以user_id 1 标识。
        后续的修复如下
        1)由于设备 Y 被关联到登录 ID A 下,修复设备 Y 上登录之前的数据:user_id 3 -> user_id 1。
        2)将users表中user_id 3的用户属性合并到user_id 1上,并删除 users 表user_id 3 这条数据。进行属性合并时,如果user_id 1的用户该属性有值,则不会修改该属性的值;如果user_id 1 的用户该属性没值,且user_id 3 的用户该属性有值,则将对应的值合并到user_id 1 的用户上,并删除 users 表中user_id 3 这条数据。

      在上述场景中,真正实现了跨设备的用户贯通,通过修复解决了方案二中换手机登录之前的行为贯通问题,但仍存在局限性:

      一个设备只能关联到一个登录 ID 下,当用户把旧手机送给朋友之后,由于旧手机已被关联到自己的登录 ID 了,无法再与朋友的登录 ID 关联。后续使用这台旧手机的用户们,若不登录就操作,则都会被识别为同一个用户(旧手机成功关联的登录 ID)。

      而事实上,旧手机上后续的匿名登录很难识别到底是谁,可能归为匿名登录之前最近一次登录的用户会更合理一些。

    综合起来看,以上三种对用户唯一性进行识别的方案没有对与错,在决定采取哪种识别方案时,结合产品的具体应用场景以及埋点复杂度来选择合适的方案即可。

    本文作者:呆呆牛的碎碎念

    版权归作者所有,任何形式转载请联系作者。
    it_hr@zybank.com.cn

    展开全文
  • (2)若关系中的某一属性组的值能够唯一标识一个元组,则称该属性组为候选码,从候选码选定的一个码为主码;如果一个属性在表(关系既不是主码也不是候选码,但是他是另一个关系的主码那它就是外码。 (3)...
  • OID,唯一性的标志

    千次阅读 2015-01-09 11:39:11
    Hibernate使用OID来建立内存的对象和数据库记录的对应关系。对象的OID和数据库的表的主键对应。为保证OID的唯一性,应该让Hibernate来为OID赋值。 主键必备条件: 1,不能为null。 2,唯一,不能重复。 ...

    关系数据库用主键区分是否是同一条记录。

    Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系。对象的OID和数据库的表的主键对应。为保证OID的唯一性,应该让Hibernate来为OID赋值。

    主键必备条件:

    1,不能为null

    2,唯一,不能重复。

    3,永远不会改变。

    二, 自然主键和代理主键

    自然主键:把具有业务含义的字段作为主键叫做自然主键。

    代理主键:不具备业务含义的字段,该字段一般取名为“id”。(推荐)



     三, 数据库中的主键介绍

    关系数据库按主键区分不同记录

     

    把主键定义为自动增长类型

    my SQL中,把字段设为auto_increment类型,数据库会自动为主键赋值。

    ms SQL server中,把字段设为identity类型,数据库会自动为主键赋值。

     

    oracle从序列(sequence)中获取自动增长的描述符

    create sequence seq_customer increment by 2 start with 1

    insert into customers  values(seq_customer.curval,’..’)

     四,java与Hibernate如何区分对象

    Java语言按内存地址(==)或equals()方法区分不同的对象

     

    Hibernate中用对象表示符(OID)来区分对象

    OID是关系数据库中的主键在java对象模型中的等价物。在运行时,hibernate根据OID来维持java对象和数据库记录的对应关系。

    Hibernate使用OID来区分对象,不是equals()方法!所以不重写持久化类的hashCode()equals()方法Hibernate也可以正确运行(但要放到HashSet等集合中时要注意需要重写这两个方法)。

    五。ID和 generator元素配置说明

    配置示例:

    <id name=“id” type=“long” column=“ID”>

          <generator class=“increment” />

    </id>

     

    <id>元素说明:

    设定持久化类的 OID 和表的主键的映射,可以有以下属性:

    ¨ name: 标识持久化类 OID 的属性名  

    ¨ column: 设置标识属性所映射的数据列的列名(主键字段的名字). 

    ¨ unsaved-value:若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象,Hibernate3中几乎不再需要.

    ¨ type:指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型然后自动使用与之对应的默认的 Hibernate 映射类型

    ¨ Java 的基本数据类型和包装类型对应相同的 Hibernate 映射类型基本数据类型无法表达 null, 所以对于持久化类的 OID 推荐使用包装类型

     

    <generator>元素说明

    设定持久化类设定标识符生成器,可以有一个class属性:

    ¨ class: 指定使用的标识符生成器全限定类名或其缩写名。

     

    <generator>元素的class属性可以指定的值说明(主键生成策略)

    主键生成器

    描述

    increment

    适用于代理主键。由hibernate自动以递增的方式生成表识符,每次增量为1

    identity

    适用于代理主键。由底层数据库生成表识符。条件是数据库支持自动增长数据类型。

    sequence

    适用于代理主键。Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。

    hilo

    适用于代理主键。Hibernate根据hign/low算法生成标识符。Hibernate把特定表的字段作为“hign”值。默认情况下,采用hibernate_unique_key表的next_hi字段。

    native

    适用于代理主键。根据底层数据库对自动生成表示符的能力来选择identitysequencehilo

    uuid.hex

    适用于代理主键。Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。

    assigned

    适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为private的。尽量避免使用自然主键。

     

    展开全文
  • 在现在的关系型数据库...超键可能包含用于唯一标识记录所不必要的额外的列,我们通常只对仅包含能够唯一标识记录的最小数量的列感兴趣。 其实乍一看很难懂,其实是说法不太容易懂 我们来弄一张表来说明一下 stu...

    在现在的关系型数据库中,存在着常用的几种键,举一个最常见的键: 主键,大家肯定都知道主键吧,

    现在我们要介绍关系型数据库中的4中键:超键、候选键、主键、外键

    1)超键:

    一个列或者列集,唯一标识表中的一条记录。超键可能包含用于唯一标识记录所不必要的额外的列,我们通常只对仅包含能够唯一标识记录的最小数量的列感兴趣。

    其实乍一看很难懂,其实是说法不太容易懂

    我们来弄一张表来说明一下

    student_idnamegradeclassage
    00001A1218
    00002C1117
    00003F1316
    00004G1418
    00005H2216
    00006Q2117
    00007S2216
    00008Z2219

    一张student表 假设姓名没有重复的  学号(student_id) ,姓名都可以确定唯一一条记录,

    学号+姓名的组合也可以确定一条记录,这三种都叫超键

    2)候选键

    仅包含唯一标识记录所必需的最小数量列的超键。表的候选键有三个属性:
    唯一性:在每条记录中,候选键的值唯一标识该记录。
    最小性:具有唯一性属性的超键的最小子集。
    非空性:候选键的值不允许为空。


    在我们的例子中,分公司编号是候选键,如果每个分公司的邮编都不同,那么邮编也可以作为分公司表的候选键。一个表中允许有多个候选键。

    上面的超键中有3个,有2个值包含一个字段,有一个含有2个字段,超键中最少的组合(至少为1,且去掉任何一个字段都不再是超键)为候选键,上图的例子中2个只含一个字段的超键为候选键,那个带2个字段的超键由于去掉其中一字段依然是超键,所以其不是候选键。

     

    3)主键和外键

    主键和外键就不过多介绍了,直接放定义

    主键:


    主键的选择在关系数据模型中非常重要,很多性能问题都是由于主键选择不当引起的。在选择主键时,我们可以参考以下原则:
    1.主键要尽可能地小。
    2.主键值不应该被改变。主键会被其他表所引用。如果改变了主键的值,所有引用该主键的值都需要修改,否则引用就是无效的。
    3.主键通常使用数字类型。数字类型的主键要比其他数据类型效率更高。
    4.主键应该是没有业务含义的,它不应包含实际的业务信息。无意义的数字列不需要修改,因此是主键的理想选择。大部分关系型数据库支持的自增属性或序列对象
    更适合当作主键。
    虽然主键允许由多列组成,但应该使用尽可能少的列,最好是单列。

    外键:

    一个表中的一个列或多个列的集合,这些列匹配某些其他(也可以是同一个)表中的候选键。注意外键所引用的不一定是主键,但一定是候选键。当一列出现在两张
    表中的时候,它通常代表两张表记录之间的关系。

    展开全文
  • 由一个或多个属性组成,其值能够唯一标识关系中一个元组 至多由一个属性组成 [参考答案] 由一个或多个属性组成,其值能够唯一标识关系中一个元组 试题2 有两个关系A(S,SN,D)和B(D,CN,NM),S是A的主码,...
  • 关系

    千次阅读 2013-03-05 09:58:30
    超键(英语:superkey),有的文献称“超码”,是在数据库关系模式设计中能够唯一标示多元組的属性集。 包含所有屬性的集叫做明顯超鍵。 候选键或候选码(英语:candidate key)是某个关系变量的一组...
  • 数据库的主键与外键的关系,通俗易懂

    万次阅读 多人点赞 2017-12-16 16:13:08
    关系型数据库的一条记录有若干个属性,若其中某一个属性组(注意是组)能唯一标识一条记录,该属性组就可以成为一个主键比如学生表(学号,姓名,性别,班级)其中每个学生的学号是唯一的,学号就是一个主键课程表...
  • 数据库设计中关系规范化理论总结

    千次阅读 多人点赞 2020-07-31 11:08:14
    其中关系数据库是目前被应用最广泛的数据库类型,它看起来类似于一张二维表,通过应用数学的方法来处理数据库的数据。在关系数据库的设计过程,最重要的莫过于对数据库的逻辑设计,即针对一个具体的问题,我们...
  • 1.码=能够唯一标识一组元组的属性集 2.主键:能够唯一标识一组元组的属性集 主键是从候选键选择一个作为主键。 3.候选码,能够唯一标识一组元组的属性集 候选码的任一真子集都不能唯一标识一组元组。 主属性:候选...
  • 文章目录关系数据库关系操作基本关系操作关系数据库语言的分类关系模型的完整性实体完整性(Entity Integrity)参照完整性(Referential Integrity)用户定义完整性(User-defined Integrity)E-R图向关系模型的转换...
  • 码:指能够唯一标识一个元组的属性组(是属性组,包含多个一个或多个属性),但是不是一个最小码组合的情况,也就是其子集还能够唯一标识一个记录 候选码:指能够唯一标识一个元组的属性组(是属性组,包含多个一个...
  • 数据库的数据表之间的关系

    万次阅读 2012-12-14 16:56:16
    主键:能够唯一表示数据表的每个记录的字段或者字段的组合就称为主键。一个主键是唯一识别一个表的每一行记录,但这只是其作用的一疗分,主键的主要作用是将记录和存放在其他表的数据进行关联,在这一点上,主键...
  • 在用户看来,关系模型数据的逻辑结构是一张扁平的二维表。 1.1域 域是一组具有相同数据类型值的集合。 1.2笛卡儿积 笛卡儿积是域上的一种集合运算。 定义:给定一组域D1,D2,...,Dn,允许其中某些域是...
  • 层次数据模型     定义:层次数据模型是用树状<...其实层次数据模型就是的图形表示就是一个倒立生长的树,由基本数据结构的树(或者二叉树)的定义可知,每棵树都有且仅有一个根节点,其余的...
  • 候选码是能够唯一标识关系中某一个元组的一个属性或属性集,也叫候选键 如: 学生关系中,学号可以唯一标识学生,班级+姓名也可以唯一标识一个学生,学号和(班级,姓名)都是候选码 候选码需要满足: 唯一性:...
  • 在世界里,「潜意识下的命名空间里,相对的唯一标识」是普遍存在的,例如: 每个人出生的时候,就获得了一个「相对的唯一标识」——姓名。 城市的道路,都基本上采用了唯一的命名(当然...
  • UML图类之间的关系

    万次阅读 2018-01-11 19:09:18
    UML图类之间的关系:依赖,泛化,关联,聚合,组合,实现 类与类图 1) 类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。 2) 在系统,每个类具有一定的职责...
  • 关系操作和关系的完整性

    千次阅读 2017-11-29 00:28:40
    关系模型常用的关系操作:查询操作和插入、删除、修改操作
  • 1、某关系R的外键是指 A.(正确答案)解析:其它关系的候选键,可以是R的主属性或非主属性 B.(错误答案)解析:外键是另一个关系的主键...A.(正确答案)解析:关系中的一个属性组,其值能唯一标识一个元组,若从该属性...
  • ORM基本概念及ORM的映射关系

    万次阅读 2015-09-08 16:55:54
    域模型是面向对象的,而关系模型是面向关系的,一般情况下,一个持久化类和一个表对应,类的每个实例对应表的一条记录。 Hibernate是目前最流行的开源ORM框架 ORM的映射关系: ORM的主要目的是通过类...
  • 关系代数的除法--概念的理解

    千次阅读 2013-08-28 23:58:25
    关系代数的除法
  • 生成全局唯一ID的3个思路

    千次阅读 2017-02-04 21:54:04
    标识(ID / Identifier)...在世界里,「潜意识下的命名空间里,相对的唯一标识」是普遍存在的,例如: 每个人出生的时候,就获得了一个「相对的唯一标识」——姓名。 城市的道路,都基本上采用了唯一的命
  • 1) 类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。 2) 在系统,每个类具有一定的职责,职责指的是类所担任的任务,即类要完成什么样的功能,要承担什么...
  • JAVA备忘录(四):线程池面试题

    千次阅读 2020-01-20 17:52:12
    超键:在关系中能够唯一标识一个元组的属性集称为超键。超键可以是一个属性,也可以是多个属性的组合。 候选键:不含多余元素的超键。也就是说,只要删除候选键的任意一行属性,它就不再能唯一标识一个元组了。 ...
  • 本文成文于2014年2月,背景是TiEAF(基础业务平台)设计之初“模型树”的存储方式选型。笔者在文中对关系数据库常用的树型结构的存储方式进行了描述和辨析。现将其整理发表以供读者参考。
  • 数据库应用程序开发入门篇—— 关系数据库的基本概念 写在前面:关系数据库是目前应用最广泛的的数据库,了解关系型数据库的基本概念,有助于应用开发。 1.关系数据库基本概念 关系数据库,是建立在关系模型...
  •  关系模型是在1970年由IBM的研究员E.F.Codd博士首先提出的,在之后的几十年关系模型的概念得到了充分的发展并逐渐成为主流数据库结构的主流模型。  简单来说,关系模型指的就是二维表格模型,而一个关系型...
  • 关系模型和关系运算

    千次阅读 2015-11-09 22:52:54
    我们可以通过关系模型这种简单的数据结构能够描述出现实世界的实体及实体间的各种联系。 什么是关系模型? 关系模型的基本假定是所有数据都表示为数学上的关系,就是以集合的形式表示。关系模型是采用二维表格...
  • UML在关系型数据库设计的应用

    千次阅读 2006-07-11 08:01:00
    UML在关系型数据库设计的应用 . 转自:swain 介绍许多人认为面向对象概念和关系型数据库相互不一致,并且不能结合。事实上完全相反!经过灵活的使用,一个关系型数据库能够为面向对象(OO)模型提供一套优秀的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 165,591
精华内容 66,236
关键字:

关系中能够唯一标识