.NET शॉर्ट यूनिक आइडेंटिफ़ायर


91

मुझे .NET में एक अद्वितीय पहचानकर्ता की आवश्यकता है (GUID का उपयोग नहीं कर सकते क्योंकि यह इस मामले के लिए बहुत लंबा है)।

क्या लोग सोचते हैं कि यहाँ उपयोग किया जाने वाला एल्गोरिदम एक अच्छा उम्मीदवार है या आपके पास कोई अन्य सुझाव है?


3
कितना छोटा है? और कितना अनोखा? ईथरनेट एडॉप्टर के हार्डवेयर पते के आधार पर GUID को विशिष्ट होने की गारंटी दी जाती है; विशुद्ध रूप से गणितीय रूप से निर्मित कुछ भी कभी भी अनूठे रूप से अद्वितीय नहीं हो सकता है - बस शायद अद्वितीय (एक खगोलीय उच्च संभावना के साथ)।
जॉन

लंबाई में 15, और जितना संभव हो उतना अनूठा (शायद)
नोएल

4
var यादृच्छिक = 4; // काफी अच्छा
क्रिस्टोफर

4
15 लंबाई में क्या ? 15 बाइट्स? यदि हां, तो सिर्फ एक गाइड से बाइट क्यों छीनें ..?
क्रिस्टोफर

3
@KristoferA एक गाइड में एक बाइट छीनने से खगोलीय रूप से महत्वपूर्ण टकराव की संभावना बढ़ जाती है। यदि आप गलत ऑर्डर की गई बाइट को अलग करते हैं तो इससे टकराना निश्चित हो सकता है।
क्रिस मैरिसिक

जवाबों:


100

यह एक अच्छा एक है - http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

और यहाँ भी YouTube की तरह GUID है

आप Base64 का उपयोग कर सकते हैं:

string base64Guid = Convert.ToBase64String(Guid.NewGuid().ToByteArray());

यह E1HKfn68Pkms5zsZsvKONw == जैसी स्ट्रिंग उत्पन्न करता है। चूंकि एक GUID हमेशा 128 बिट्स होता है, आप == को छोड़ सकते हैं, जो आपको पता है कि हमेशा अंत में मौजूद रहेगा और इससे आपको 22 अक्षर का स्ट्रिंग मिलेगा। हालांकि यह YouTube जितना छोटा नहीं है।


11
एक छोटा नोट: यदि यह URL के लिए आवश्यक है, तो जो कोई भी इसका उपयोग करता है वह '+' और '/' वर्णों को
पवित्र करना चाहता है


1
मैं एकता में UnnyNet के लिए एक अधिकतम 23 चार लंबी लंबी अद्वितीय आईडी चाहता था, और मैं अपने बड़े गूंगे के साथ फंस गया था, और आपने मुझे बहुत खुश कर दिया :)
nipunasudha

दिलचस्प है (यदि आप ज़ूम एपीआई के साथ काम कर रहे हैं), तो यह लगभग निश्चित रूप से है कि वे अपने यूयूआईडी कैसे बनाते हैं। वे 22 अक्षर लंबे हैं और base64 एन्कोडेड हैं।
vGL

39

मैं डोर कोहेन के समान दृष्टिकोण का उपयोग करता हूं लेकिन कुछ विशेष पात्रों को हटा रहा हूं:

var uid = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");     

यह सिर्फ अल्फ़ान्यूमेरिक वर्णों का उत्पादन करेगा। यूआईडी की हमेशा एक ही लंबाई की गारंटी नहीं होती है। यहाँ एक नमूना रन है:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg

6
आप GUID द्वारा गारंटीकृत कुछ संपत्तियों को इस तरह से जानकारी फेंककर ढीला कर देंगे। मैं उन पात्रों की जगह लेने की सलाह दूंगा, जो कि GUID और उनके क्रमांकन प्रारूप के बीच आपत्ति को संरक्षित करते हुए विभिन्न पात्रों द्वारा सहज नहीं हैं।
लुकास लैन्स्कि

3
यह उपयोग करने के लिए एक अच्छा एक है यदि आप एक GUID में वापस बदलना चाहते हैं, लेकिन बस कुछ और के लिए यादृच्छिक पात्रों की जरूरत है .. उदाहरण के लिए बहु सूत्र में श्रमिकों पर नज़र रखने, या पिरोया वस्तुओं के लिए लॉगिंग उपसर्ग, आदि
Piotr Kula

23
var ticks = new DateTime(2016,1,1).Ticks;
var ans = DateTime.Now.Ticks - ticks;
var uniqueId = ans.ToString("x");

एक आधारभूत तिथि (जो इस मामले में पहली जनवरी 2016 है) रखें, जब से आप इन आईडी का निर्माण शुरू करेंगे। इससे आपकी id छोटी हो जाएगी।

उत्पन्न संख्या: 3af3c14996e54


millisecondsहमेशा उस DateTimeवस्तु के लिए 0 है
तीज

अंतिम वाक्य को भी हटा दें।
तीजय

1
oneliner: var uniqueId = (DateTime.Now.Ticks - नई DateTime (2016, 1, 1 .Ticks)) .ToString ("x");
स्वेदा

4
लगभग एक ही समय में लगभग एक ही समय में आईडी की पीढ़ी के लिए अच्छा नहीं है जैसे- forop.eg dotnetfiddle.net/L3MIgZ
Jaider

17

सरल प्रयोग करने योग्य पैकेज। मैं अस्थायी अनुरोध आईडी जनरेटर के लिए इसका इस्तेमाल करता हूं।

https://www.nuget.org/packages/shortid

https://github.com/bolorundurowb/shortid

उपयोग System.Random

string id = ShortId.Generate();
// id = KXTR_VzGVUoOY

(जीथूब पेज से)

यदि आप यह निर्दिष्ट करके आईडी के प्रकार को नियंत्रित करना चाहते हैं कि क्या आप संख्या, विशेष वर्ण और लंबाई चाहते हैं, तो जेनरेट विधि को कॉल करें और तीन पैरामीटर पास करें, पहला बूलियन यह कहते हुए कि क्या आप संख्या चाहते हैं, दूसरा बूलियन यह कहते हुए कि क्या आप चाहते हैं विशेष वर्ण, अंतिम संख्या आपकी लंबाई की प्राथमिकता को दर्शाती है।

string id = ShortId.Generate(true, false, 12);
// id = VvoCDPazES_w

10

जहां तक ​​मुझे पता है, बस एक GUID के एक हिस्से को अलग करना अद्वितीय होने की गारंटी नहीं है - वास्तव में, यह अद्वितीय होने से बहुत दूर है।

सबसे छोटी चीज जो मुझे पता है कि वैश्विक विशिष्टता की गारंटी देता है जेफ एटवुड द्वारा इस ब्लॉग पोस्ट में चित्रित किया गया है । लिंक किए गए पोस्ट में, वह एक GUID को छोटा करने के कई तरीकों पर चर्चा करता है, और अंत में इसे Ascii85 एन्कोडिंग के माध्यम से 20 बाइट्स तक ले जाता है ।

हालाँकि, अगर आपको किसी ऐसे समाधान की आवश्यकता है जो अब 15 बाइट्स से अधिक नहीं है, तो मुझे डर है कि आपके पास कोई ऐसी चीज़ का उपयोग करने के अलावा और कोई विकल्प नहीं है जो विश्व स्तर पर अद्वितीय होने की गारंटी नहीं है।


6

एक डेटाबेस में पहचान का मूल्य अद्वितीय होना चाहिए, लेकिन आपको सीमाओं के बारे में पता होना चाहिए ... उदाहरण के लिए, यह थोक डेटा आवेषण मूल रूप से असंभव बनाता है जो आपको धीमा कर देगा यदि आप बहुत बड़ी संख्या में रिकॉर्ड के साथ काम कर रहे हैं।

आप दिनांक / समय मान का उपयोग करने में भी सक्षम हो सकते हैं। मैंने कई डेटाबेस देखे हैं जहां वे PK होने के लिए दिनांक / समय का उपयोग करते हैं, और जब तक यह सुपर क्लीन नहीं होता है - यह काम करता है। यदि आप आवेषण को नियंत्रित करते हैं, तो आप प्रभावी रूप से गारंटी दे सकते हैं कि मूल्य कोड में अद्वितीय होंगे।


6

अपने स्थानीय एप्लिकेशन के लिए मैं इस समय आधारित दृष्टिकोण का उपयोग कर रहा हूं:

/// <summary>
/// Returns all ticks, milliseconds or seconds since 1970.
/// 
/// 1 tick = 100 nanoseconds
/// 
/// Samples:
/// 
/// Return unit     value decimal           length      value hex       length
/// --------------------------------------------------------------------------
/// ticks           14094017407993061       17          3212786FA068F0  14
/// milliseconds    1409397614940           13          148271D0BC5     11
/// seconds         1409397492              10          5401D2AE        8
///
/// </summary>
public static string TickIdGet(bool getSecondsNotTicks, bool getMillisecondsNotTicks, bool getHexValue)
{
    string id = string.Empty;

    DateTime historicalDate = new DateTime(1970, 1, 1, 0, 0, 0);

    if (getSecondsNotTicks || getMillisecondsNotTicks)
    {
        TimeSpan spanTillNow = DateTime.UtcNow.Subtract(historicalDate);

        if (getSecondsNotTicks)
            id = String.Format("{0:0}", spanTillNow.TotalSeconds);
        else
            id = String.Format("{0:0}", spanTillNow.TotalMilliseconds);
    }
    else
    {
        long ticksTillNow = DateTime.UtcNow.Ticks - historicalDate.Ticks;
        id = ticksTillNow.ToString();
    }

    if (getHexValue)
        id = long.Parse(id).ToString("X");

    return id;
}

3

यहाँ मेरा समाधान, संगामिति के लिए सुरक्षित नहीं है, 1000 GUID प्रति सेकंड और थ्रेड सुरक्षित नहीं है।

public static class Extensors
{

    private static object _lockGuidObject;

    public static string GetGuid()
    {

        if (_lockGuidObject == null)
            _lockGuidObject = new object();


        lock (_lockGuidObject)
        {

            Thread.Sleep(1);
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            var epochLong = Convert.ToInt64((DateTime.UtcNow - epoch).TotalMilliseconds);

            return epochLong.DecimalToArbitrarySystem(36);

        }

    }

    /// <summary>
    /// Converts the given decimal number to the numeral system with the
    /// specified radix (in the range [2, 36]).
    /// </summary>
    /// <param name="decimalNumber">The number to convert.</param>
    /// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
    /// <returns></returns>
    public static string DecimalToArbitrarySystem(this long decimalNumber, int radix)
    {
        const int BitsInLong = 64;
        const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        if (radix < 2 || radix > Digits.Length)
            throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());

        if (decimalNumber == 0)
            return "0";

        int index = BitsInLong - 1;
        long currentNumber = Math.Abs(decimalNumber);
        char[] charArray = new char[BitsInLong];

        while (currentNumber != 0)
        {
            int remainder = (int)(currentNumber % radix);
            charArray[index--] = Digits[remainder];
            currentNumber = currentNumber / radix;
        }

        string result = new String(charArray, index + 1, BitsInLong - index - 1);
        if (decimalNumber < 0)
        {
            result = "-" + result;
        }

        return result;
    }

कोड अनुकूलित नहीं है, बस नमूना!।


एक दिलचस्प समाधान के दौरान, इसमें कोई गारंटी नहीं है कि UtcNowप्रत्येक मिलीसेकंड के लिए एक अद्वितीय टिक मूल्य देता है: टिप्पणियों के अनुसार , संकल्प सिस्टम टाइमर पर निर्भर करता है। इसके अतिरिक्त, आप बेहतर सुनिश्चित करेंगे कि सिस्टम घड़ी पिछड़े नहीं बदले! (चूंकि user13971889 के जवाब ने इस सवाल को मेरे फ़ीड के शीर्ष पर पहुंचा दिया, और मैंने उस उत्तर की आलोचना की, मुझे लगता है कि मुझे उस आलोचना को यहाँ दोहराना चाहिए।)
जो सेवेल

3

यदि आपके ऐप में कुछ MILLIION लोग नहीं हैं, तो SAME MILLISECOND पर उस छोटी सी अनोखी स्ट्रिंग का उपयोग करके, आप नीचे दिए गए फ़ंक्शन का उपयोग करने के बारे में सोच सकते हैं।

private static readonly Object obj = new Object();
private static readonly Random random = new Random();
private string CreateShortUniqueString()
{
    string strDate = DateTime.Now.ToString("yyyyMMddhhmmssfff");
    string randomString ;
    lock (obj)
    {
        randomString = RandomString(3);
    }
    return strDate + randomString; // 16 charater
}
private string RandomString(int length)
{

    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxy";
    var random = new Random();
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

अगर आप को अगले 99 साल में अपने ऐप का उपयोग करने की आवश्यकता है तो yyyy को yy में बदलें।
अद्यतन 20160511 : सही रैंडम फ़ंक्शन
- लॉक ऑब्जेक्ट जोड़ें
- रैंडमस्ट्रीमिंग फ़ंक्शन रेफ से रैंडम वेरिएबल को स्थानांतरित करें


2
यह बहुत अच्छा है, हालांकि आपको हर बार एक नया रैंडम इनिशियलाइज़ नहीं करना चाहिए - इसका कारण lockयह है कि आप उसी Randomउदाहरण का फिर से उपयोग कर सकते हैं । मुझे लगता है कि आप उस लाइन को हटाना भूल गए!
निब्लीपिग

1

मुझे पता है कि यह पोस्ट की तारीख से काफी दूर है ... :)

मेरे पास एक जनरेटर है जो केवल 9 हेक्सा वर्णों का उत्पादन करता है , जैसे: C9D6F7FF3, C9D6FB52C

public class SlimHexIdGenerator : IIdGenerator
{
    private readonly DateTime _baseDate = new DateTime(2016, 1, 1);
    private readonly IDictionary<long, IList<long>> _cache = new Dictionary<long, IList<long>>();

    public string NewId()
    {
        var now = DateTime.Now.ToString("HHmmssfff");
        var daysDiff = (DateTime.Today - _baseDate).Days;
        var current = long.Parse(string.Format("{0}{1}", daysDiff, now));
        return IdGeneratorHelper.NewId(_cache, current);
    }
}


static class IdGeneratorHelper
{
    public static string NewId(IDictionary<long, IList<long>> cache, long current)
    {
        if (cache.Any() && cache.Keys.Max() < current)
        {
            cache.Clear();
        }

        if (!cache.Any())
        {
            cache.Add(current, new List<long>());
        }

        string secondPart;
        if (cache[current].Any())
        {
            var maxValue = cache[current].Max();
            cache[current].Add(maxValue + 1);
            secondPart = maxValue.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            cache[current].Add(0);
            secondPart = string.Empty;
        }

        var nextValueFormatted = string.Format("{0}{1}", current, secondPart);
        return UInt64.Parse(nextValueFormatted).ToString("X");
    }
}

1

@ Dorcohen के जवाब और @ pootzko की टिप्पणी के आधार पर। आप इसका उपयोग कर सकते हैं। यह तार के ऊपर सुरक्षित है।

var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());

अगर किसी को आश्चर्य होता है तो बाहर निकलें : Jzhw2oVozkSNa2IkyK4ilA2या अपने आप को dotnetfiddle.net/VIrZ8j
chriszo111

1

कुछ अन्य लोगों के आधार पर, यहां मेरा समाधान है जो एक अलग एन्कोडेड गाइड प्रदान करता है जो कि URL (और डॉकर) सुरक्षित है और इसमें कोई अंतर नहीं है:

Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("=", "").Replace("+", "-").Replace("/", "_");

उदाहरण आउटपुट हैं:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

1

C # में एक longमान 64 बिट्स का है, जिसे अगर Base64 के साथ एनकोड किया गया तो 1 पैडिंग सहित 12 अक्षर होंगे =। यदि हम पैडिंग को ट्रिम करते हैं =, तो 11 वर्ण होंगे।

एक पागल विचार यहाँ है कि हम यूनिक्स एपोच के संयोजन और एक युग मूल्य के लिए एक काउंटर का उपयोग मूल्य बनाने के लिए कर सकते हैं long। यूनिक्स काल सी में # DateTimeOffset.ToUnixEpochMillisecondsमें है longप्रारूप है, लेकिन 8 बाइट्स के पहले 2 बाइट्स हमेशा 0 कर रहे हैं, क्योंकि अन्यथा दिनांक समय मूल्य अधिकतम दिनांक समय मूल्य से अधिक हो जाएगा। ताकि हमें जगह देने के लिए 2 बाइट्स मिलेushort काउंटर ।

इसलिए, कुल मिलाकर, जब तक आईडी पीढ़ी की संख्या 65536 प्रति मिलीसेकंड से अधिक नहीं होती, हमारे पास एक अद्वितीय आईडी हो सकती है:

// This is the counter for current epoch. Counter should reset in next millisecond
ushort currentCounter = 123;

var epoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Because epoch is 64bit long, so we should have 8 bytes
var epochBytes = BitConverter.GetBytes(epoch);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    epochBytes = epochBytes.Reverse().ToArray();
}

// The first two bytes are always 0, because if not, the DateTime.UtcNow is greater 
// than DateTime.Max, which is not possible
var counterBytes = BitConverter.GetBytes(currentCounter);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    counterBytes = counterBytes.Reverse().ToArray();
}

// Copy counter bytes to the first 2 bytes of the epoch bytes
Array.Copy(counterBytes, 0, epochBytes, 0, 2);

// Encode the byte array and trim padding '='
// e.g. AAsBcTCCVlg
var shortUid = Convert.ToBase64String(epochBytes).TrimEnd('=');

1
    public static string ToTinyUuid(this Guid guid)
    {
        return Convert.ToBase64String(guid.ToByteArray())[0..^2]  // remove trailing == padding 
            .Replace('+', '-')                          // escape (for filepath)
            .Replace('/', '_');                         // escape (for filepath)
    }

प्रयोग

Guid.NewGuid().ToTinyUuid()

यह रॉकेट विज्ञान नहीं है कि मैं इसे वापस बदल सकूं, इसलिए मैं आपको छोड़ दूंगा।


0

यदि आपको स्ट्रिंग का उपयोग करने की आवश्यकता नहीं है, तो आप निम्नलिखित का उपयोग कर सकते हैं:

static class GuidConverter
{
    public static string GuidToString(Guid g)
    {
        var bytes = g.ToByteArray();
        var sb = new StringBuilder();
        for (var j = 0; j < bytes.Length; j++)
        {
            var c = BitConverter.ToChar(bytes, j);
            sb.Append(c);
            j++;
        }
        return sb.ToString();
    }

    public static Guid StringToGuid(string s) 
        => new Guid(s.SelectMany(BitConverter.GetBytes).ToArray());
}

यह मार्गदर्शिका को 8 वर्ण स्ट्रिंग की तरह परिवर्तित कर देगा:

{b77a49a5-182b-42fa-83a9-824ebd6ab58d} -> "" a a ᠫ a a a a a "

{c5f8f7f5-8a7c-4511-b667-8ad36b446617} -> "" - f f f f f f f "


0

एक यादृच्छिक और छोटी अनोखी आईडी बनाने के लिए यहां मेरी छोटी विधि है। सुरक्षित यादृच्छिक संख्या पीढ़ी के लिए एक क्रिप्टोग्राफ़िक आरएनजी का उपयोग करता है। charsस्ट्रिंग के लिए आपको जो भी वर्ण चाहिए उन्हें जोड़ें ।

private string GenerateRandomId(int length)
{
    char[] stringChars = new char[length];
    byte[] randomBytes = new byte[length];
    using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(randomBytes);
    }

    string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";           

    for (int i = 0; i < stringChars.Length; i++)
    {
        stringChars[i] = chars[randomBytes[i] % chars.Length];
    }

    return new string(stringChars);
}

0

अक्षरों को खोना नहीं (+ / -) और यदि आप अपने गाइड का उपयोग url में करना चाहते हैं, तो उसे आधार ३२ में बदलना चाहिए

10 000 000 के लिए कोई डुप्लिकेट कुंजी नहीं

    public static List<string> guids = new List<string>();
    static void Main(string[] args)
    {
        for (int i = 0; i < 10000000; i++)
        {
            var guid = Guid.NewGuid();
            string encoded = BytesToBase32(guid.ToByteArray());
            guids.Add(encoded);
            Console.Write(".");
        }
        var result = guids.GroupBy(x => x)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key);

        foreach (var res in result)
            Console.WriteLine($"Duplicate {res}");

        Console.WriteLine($"*********** end **************");
        Console.ReadLine();
    }

    public static string BytesToBase32(byte[] bytes)
    {
        const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        string output = "";
        for (int bitIndex = 0; bitIndex < bytes.Length * 8; bitIndex += 5)
        {
            int dualbyte = bytes[bitIndex / 8] << 8;
            if (bitIndex / 8 + 1 < bytes.Length)
                dualbyte |= bytes[bitIndex / 8 + 1];
            dualbyte = 0x1f & (dualbyte >> (16 - bitIndex % 8 - 5));
            output += alphabet[dualbyte];
        }

        return output;
    }


-1
private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{       
    lock(_getUniqueIdLock)
    {
        System.Threading.Thread.Sleep(1);
        return DateTime.UtcNow.Ticks.ToString("X");
    }
}

1
एक दिलचस्प समाधान के दौरान, इसमें कोई गारंटी नहीं है कि UtcNowप्रत्येक मिलीसेकंड के लिए एक अद्वितीय टिक मूल्य देता है: टिप्पणियों के अनुसार , संकल्प सिस्टम टाइमर पर निर्भर करता है। इसके अतिरिक्त, आप बेहतर सुनिश्चित करेंगे कि सिस्टम घड़ी पिछड़े नहीं बदले! (ur3an0 का जवाब भी इन मुद्दों की है।)
जो सेवेल

माना। यह एक गरीब आदमी का दृष्टिकोण है और इसे आपके अपने नियंत्रित वातावरण से परे उपयोग नहीं किया जाना चाहिए।
user13971889

-2

आप उपयोग कर सकते हैं

code = await UserManager.GenerateChangePhoneNumberTokenAsync(input.UserId, input.MobileNumber);

6केवल इसके अच्छे चरित्र 599527,143354

और जब उपयोगकर्ता इसे सरल रूप से बताता है

var result = await UserManager.VerifyChangePhoneNumberTokenAsync(input.UserId, input.Token, input.MobileNumber);

आशा है कि यह आपकी मदद करेगा


मैं हमेशा अपने पासवर्ड सरल, याद रखने में आसान रखता हूं
टूलकिट

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.