Como o sistema do site está ficando cada vez maior, cookies de diferentes nomes de domínio e até mesmo de diferentes sites de parceiros podem precisar ser mais ou menos compartilhados. Ao se deparar com essa situação, o que todos geralmente pensam é usar o centro de login para distribuir o status do cookie. e depois sincronizá-lo Solução, o custo é maior e a implementação é mais complicada e problemática.
Como os cookies são de domínio cruzado, os navegadores não permitem acesso mútuo. Para romper essa restrição, o plano de implementação a seguir é usado para compartilhar dados entre domínios usando pós-mensagem e armazenamento local.
O princípio é relativamente simples, mas existem muitas armadilhas. Vamos resolver isso aqui e fazer um backup.
2. Projeto de APIConforme mencionado anteriormente, usamos armazenamento local em vez de cookies. Existem algumas diferenças no uso entre armazenamento local e cookies. Por exemplo, o armazenamento local tem uma capacidade maior, mas não há tempo de expiração. navegadores diferentes. O limite superior de espaço facilita o travamento se a operação não for boa. Além disso, embora o postmessage suporte vários domínios, problemas de segurança e API assíncrona também trazem alguns problemas de uso. mais fácil de usar?
Vamos dar uma olhada na API que projetei primeiro:
import { crosData } from 'base-tools-crossDomainData';var store = new crosData({ iframeUrl:somefile.html, //Endereço iframe compartilhado, iframe tem requisitos especiais, veja o arquivo de modelo expirar:'d,h,s' / /Tempo de expiração padrão em dias, horas, segundos, também pode ser substituído durante o plantio});store.set('key','val',{ expire:'d,h,s' //option Pode trazer o tempo de expiração, substituir expire}).then((data)=>{ //Método assíncrono, se falhar, ele entrará no evento catch //data {val:'val',key:'key',domain :' domínio'};}).catch((err)=>{ console.log(err);}); store.get('chave',{ domínio:'(.*).sina.cn' //Você pode especificar um nome de domínio ou usar (.*) para corresponder a strings regulares. As informações de val retornadas incluirão informações de domínio. =>{ console.log (val) //Obtém os dados armazenados de forma assíncrona, pode haver vários, é um array [{},{}]}).catch((err)=>{});store.clear ('chave').então(). //Limpa apenas as chaves do domínio atual. As chaves de outros domínios não podem ser apagadas. Elas só podem ser lidas.Se um módulo é rápido de usar depende da API, portanto, para um módulo de compartilhamento de dados, acho que não há problema em oferecer suporte aos três métodos de set, get e clear, porque a pós-mensagem em si é um comportamento assíncrono completo, e deve ser embalado em uma promessa. Mais adequado e mais fácil de usar. Como o armazenamento local não suporta tempo de expiração, é necessária uma configuração de tempo de expiração global. Claro, ele também pode ser configurado individualmente durante a obtenção, podemos especificar para obter dados em um determinado domínio ou dados em vários domínios, porque os nomes das chaves. pode ser repetido, mas existe apenas um domínio. Isso envolve gerenciamento de dados. Falaremos sobre isso separadamente mais tarde. Finalmente, as APIs claras e definidas só podem plantar dados neste domínio e não podem operar dados em outros domínios.
Vamos dar uma olhada nas configurações do cliente e na API:
<!DOCTYPE html><html> <head> <meta charset=utf-8> <title>crosData</title> </head> <body> <script> window.CROS = { domínio:/(.*). sina.cn/, //Ou o nome de domínio que você permite, suporta caracteres curinga regulares e * lz:false //Se ativar a compactação lz de caracteres val} </script> <script src=http://cdn/sdk.js></script> </body></html>Você pode introduzir de forma flexível o js sdk do cliente em um documento html em qualquer domínio e, em seguida, configurar uma lista de permissões de domínio que permite que você seja plantado no domínio onde este documento está localizado por meio de atributos globais. Ele suporta expressões regulares e, em seguida, lz é se. Inicie a compactação de string lz. Apresentarei o que é compactação lz mais tarde.
Neste ponto, um design relativamente geral da API está concluído. Vamos dar uma olhada nos princípios de implementação e em alguns problemas específicos.
3. Princípio de implementaçãoParece muito simples, mas na verdade não está escrito. Primeiro precisamos saber como usar o postMessage. Esta é uma API muito comum. Há um ponto importante a ser dito aqui, ou seja, o postMessage só pode ser usado em um iframe. ou usar uma janela.open é uma maneira de abrir uma nova página para se comunicarem entre si. É claro que aqui primeiro precisamos criar um iframe oculto para vários domínios.
Tenho preguiça de usar ferramentas para fazer desenhos, porque o processo é relativamente claro. Aqui vou recontar todo o processo de comunicação em palavras. Primeiro, a página pai cria um iframe oculto e, em seguida, comandos como set, get, clear. , etc. são executados, a mensagem é transmitida por meio de postMessage Depois que a página recebe a mensagem, ela analisa o comando, os dados e o ID de retorno de chamada (postMessage não pode passar funções e referências devido a problemas de compatibilidade. É melhor passar apenas o tipo de string. , portanto, os dados precisam ser restringidos). Então, quando a página filha conclui a operação de armazenamento local, ela retorna o cbid e os dados correspondentes para a página pai por meio de postMessage. A página pai escuta o evento de mensagem e processa os resultados.
4. CodificaçãoBem, são apenas algumas linhas, vamos começar a codificar:
Primeiro, vamos apresentar quais pacotes de terceiros usamos e por que os usamos:
Primeiro, url-parse analisa o URL, principalmente usando o atributo origin nele, porque o próprio postMessage tem verificação rigorosa de origem e também precisamos oferecer suporte à lista de permissões e ao gerenciamento de nomes de domínio.
2. ms é uma biblioteca de ferramentas para converter abreviaturas de tempo em milissegundos.
3. lz-string é um kit de ferramentas para compactar strings. Aqui está uma introdução científica popular ao algoritmo de compactação LZ. Primeiro, para entender LZ, você precisa entender RLZ, Run Length Encoding, que é um algoritmo muito simples para compactação sem perdas. Ele substitui bytes repetidos por uma descrição simples de bytes repetidos e o número de repetições. A ideia por trás do algoritmo de compressão LZ é usar o algoritmo RLE para substituir uma ocorrência anterior de uma referência à mesma sequência de bytes. Simplificando, o algoritmo LZ é considerado um algoritmo de correspondência de strings. Por exemplo: uma determinada string aparece frequentemente em um trecho de texto e pode ser representada por um ponteiro de string que aparece no texto anterior.
A vantagem do lz-string em si é que ele pode reduzir bastante sua capacidade de armazenamento. Se o armazenamento local de 5 MB for usado para suportar o armazenamento de dados de vários nomes de domínio, ele será compactado e consumido rapidamente. mais lento e consome mais dinheiro. Se você tiver requisitos de tamanho para a quantidade de dados a serem transmitidos no trabalho, você pode tentar usar este algoritmo de compactação para otimizar o comprimento da string.
4. A própria API de armazenamento local do store2 é relativamente simples. Para reduzir a complexidade da lógica do código, uma biblioteca popular de implementação de armazenamento local é selecionada para realizar operações de armazenamento.
Depois de falar sobre o pacote de terceiros, vamos dar uma olhada em como escrever o js da página pai:
class crosData {construtor(opções) {supportCheck(); this.options = Object.assign({ iframeUrl: '', expire: '30d' }, this.cid = 0; .iframeBeforeFuns = []; this.parent = janela; this.origin = new url(this.options.iframeUrl).origin; this.createIframe(this.options.iframeUrl); addEvent(this.parent, 'message', (evt) => { var data = JSON.parse(evt.data); var origin = evt.origin || evt.originalEvent .origin; //Recebo apenas a mensagem do iframe que abri, os demais são ilegais e um erro será reportado diretamente if (origin !== this.origin) { rejeitar('origem ilegal!'); return } if (data.err) { this.cbs[data.cbid].reject(data.err } else { this.cbs[data.cbid].resolve(data.err); .ret); } excluir this.cbs[data.cbid]; } createIframe(url) { addEvent(document, 'domready', () => { var frame = document.createElement('iframe'); frame.style.cssText = 'largura:1px;altura:1px;borda:0;posição:absoluto;esquerda:-9999px;topo:-9999px;'; 'src', url);frame.onload = () => { this.child = frame.contentWindow; this.iframeBeforeFuns.forEach(item => item()); document.body.appendChild(frame }); postHandle(type, args) { return new Promise((resolver, rejeitar) => { var cbid = this.cid; var mensagem = { cbid: cbid, origem: novo url (localização.href).origin, ação: tipo, args: args } this.child.postMessage(JSON.stringify(message), this.origin); this.cbs[cbid] = { resolver, rejeitar } this.cid++; => { if (this.child) { return this.postHandle(type, args).then(resolver } else { var self = this); this.iframeBeforeFuns.push(function() { self.postHandle(type, args).then(resolve); }); } } }) } set(key, val, options) { options = Object.assign({ expira: ms (this.options.expire) }, opções); return this.send('set', [chave, val, opções] } get(chave, opções) { opções = Object.assign({domínio: new url(location.href).origin }, opções); return this.send('get', [chave, opções]); ', [chave]); }}Provavelmente existem apenas alguns métodos. Aqui estão alguns pontos-chave, deixe-me falar sobre eles.
1. Os métodos get, set e clear são todos uniformemente chamados de métodos send, mas a parte de opções é complementada.
2. O método send retorna um objeto de promessa. Se o iframe tiver sido carregado com sucesso, o método postHandle será chamado diretamente para executar a operação postMessage. Se o iframe ainda estiver carregando, a operação atual será enviada para o array iframeBeforeFuns, encapsulada com um. função e aguarda o onload do iframe terminar. Após a chamada unificada, a função também é encapsulada no método postHandle.
3. O método postHandle agrupa os dados antes de enviar a solicitação e gera cbid, origem, ação e argumentos. O objeto cbs salva a resolução e rejeição em cada cbid e aguarda o retorno da postMessage da subpágina antes do processamento. Como postMessage não pode reter referências e não pode passar funções, este método é escolhido aqui para associação.
4. O construtor é fácil de entender. Quando esta classe é inicializada, definimos alguns atributos de opções que precisamos, criamos um iframe, depois ouvimos o evento da mensagem e processamos a mensagem retornada pela subpágina.
5. No evento de mensagem da página pai, precisamos verificar se a mensagem enviada para mim deve ser o iframe da janela que abri, caso contrário um erro será relatado, e então a resolução e rejeição no cbs serão executadas de acordo com o errar identificador nos dados.
6. No método createIframe, o retorno de chamada no iframe onload lida com o método de chamada de cache antes da criação. Preste atenção ao uso de domready aqui, porque o SDK pode ser executado antes que o corpo seja analisado.
Aqui está o código da parte filha:
class iframe { set(key, val, options, origin) { //Verifique o tamanho do val, que não pode exceder 20k. valsize = sizeof(val, 'utf16'); //localStorage armazena bytes usando codificação utf16 if (valsize > this.maxsize) { return { err: 'seu valor armazenado: ' + valstr + 'tamanho é' + valsize + 'b, maxsize:' + this.maxsize + 'b, use utf16' } } key = `${this.prefix}_${key}, ${new url(origin).origin}`; var data = { val: val, última hora: Date.now(), expire: Date.now() + options.expire }; store.set(key, data); //Se for maior que o número máximo de armazenamento, exclua o último atualizado if (store.size() > this.storemax) { var keys = store.keys() keys =; chaves.sort( (a, b) => { var item1 = store.get(a), item2 = store.get(b); return item2.lasttime - item1.lasttime; }); este.storemax - store.size()); while (removesize) { store.remove(keys.pop()); chaves = store.keys(); var regexp = new RegExp('^' + this.prefix + '_' + chave + ',' + opções.domínio + '$'); keys.filter((key) => { return regexp.test(key); }).map((storeKey) => { var data = store.get(storeKey); data.key = key; data.domain = storeKey .split(',')[1]; if (data.expire < Date.now()) { store.remove(storeKey); val: data.val, última hora: Date.now(), expira: data.expire }); data.val = this.lz ?lzstring.decompressFromUTF16(data.val) : data.val }). filter(item => { return !!item; //Filtro indefinido }); return message; store.remove(`${this.prefix}_${key},${origin}`); return {} } clearOtherKey() { //Excluir chaves ilegais var keys = store.keys(); new RegExp('^' + this.prefix); keys.forEach(key => { if (!keyReg.test(key)) { store.remove(key); } }); } construtor(safeDomain, lz) { supportCheck(); this.safeDomain = safeDomain || this.prefix = '_cros'; safeDomain) !== '[objeto RegExp]') { throw new Error('safeDomain deve ser regexp' } this.lz = lz; this.storemax = 100; this.maxsize = 20 * 1024; //byte addEvent(window, 'message', (evt) => { var data = JSON.parse(evt.data); var originHostName = new url( evt .origin).hostname; var origem = evt.origin, ação = dados.action, cbid = dados.cbid, args = dados.args; //Transmissão legal if (evt.origin === data.origin && this.safeDomain.test(originHostName)) { args.push(origin); if (whiteAction.indexOf(action) > -1) { var mensagem = this[action].apply(this, args message.cbid = cbid); window.top.postMessage(JSON.stringify(message), origin); else { window.top.postMessage(JSON.stringify({ cbid: cbid, err: 'Domínio ilegal' }), origin ;}}Não há muito código. Aqui está uma breve introdução ao uso e relacionamento organizacional de cada método:
1. Na parte do construtor, a classe acima também verifica o suporte aos recursos do navegador e, em seguida, define atributos como o valor do prefixo da loja, o número máximo e o tamanho máximo de cada chave. Em seguida, criamos um canal de mensagens e esperamos que a página pai o chame.
2. Na mensagem, verificamos a origem da transmissão, em seguida, verificamos o método chamado, chamamos os métodos set, get e clear correspondentes, em seguida, obtemos o resultado da execução, vinculamos cbid e, finalmente, enviamos de volta a página pai postMessage.
3. clearOtherKey exclui alguns dados de armazenamento ilegais e retém apenas os dados que estão em conformidade com o formato.
4. No método set, a verificação de tamanho e a compactação lz são realizadas em cada dado salvo, incluindo val, chave, tempo de expiração e tempo de atualização (usado para cálculo de LRU).
5. No método set, se o número de ls armazenados exceder o limite máximo, uma operação de exclusão será necessária neste momento. LRU é a abreviatura de Menos Usado Recentemente, ou seja, o menos usado recentemente. Percorremos todos os valores-chave, classificamos os valores-chave, passamos pela última vez e, em seguida, executamos a operação pop do array de chaves para obter as chaves que precisam ser limpas no final da pilha e, em seguida, excluí-las uma por uma .
6. No método get, percorremos todos os valores-chave, combinamos a chave do domínio que precisamos obter e, em seguida, desmontamos a chave no valor de retorno (armazenamos no formato de chave e domínio), porque a API requer o retorno de vários valores correspondentes. Fazemos um filtro final nos dados expirados e, em seguida, usamos lz para descompactar o valor val para garantir que o usuário obtenha o resultado correto.
O texto acima é nosso processo geral de codificação de implementação e revisão. Vamos falar sobre as armadilhas encontradas.
5. Algumas armadilhas encontradasComo apenas o código principal é fornecido acima, não é o código completo. Como a lógica em si é relativamente clara, ela pode ser escrita em pouco tempo. Vamos falar sobre as armadilhas abaixo.
1. Calcule o valor de armazenamento do armazenamento local.
Como todos sabemos que existe um limite de 5 MB, o requisito máximo para cada dado não pode exceder 20*1024 bytes. Para cálculo de bytes, o armazenamento local precisa usar a codificação utf16 para conversão. Ocupado por Strings número de seções
2. Compatibilidade
É melhor passar strings em postMessage no IE8. Os eventos precisam ser suavizados e o JSON precisa ser suavizado.
3. Processamento assíncrono ao criar iframe
Aqui, anteriormente fizemos uma espera recursiva por setTimeout, mas posteriormente alteramos para o método de implementação acima. Após onload, o reslove da promessa é processado uniformemente para garantir a unificação da API da promessa.
4. Ao salvar dados, complexidade de espaço versus complexidade de tempo.
A primeira versão não é a implementação acima, implementei 3 versões:
A primeira versão salva um array LRU para reduzir a complexidade do tempo, mas desperdiça a complexidade do espaço. Além disso, após o teste, o método get do armazenamento é relativamente demorado, principalmente devido ao tempo de análise.
Na segunda versão, para maximizar a taxa de compactação da string lz, salvei todos os dados, incluindo o array LRU, em um valor-chave. Como resultado, lz-string e getItem, o consumo de tempo de análise é muito grande quando há. muitos dados. Embora o cálculo A complexidade do tempo seja a mais baixa.
A última versão é a acima, sacrifiquei alguma complexidade de tempo e complexidade de espaço, mas como o gargalo está na velocidade de leitura e gravação de set e get, a velocidade de leitura e gravação de um único salvamento é extremamente rápida. chaves é porque a camada inferior é usada no armazenamento local, o desempenho ainda é muito bom, 100 entradas podem ser armazenadas em 20kb e o tempo de leitura e gravação é de cerca de 1 segundo.
6. Resumo e comparaçãoDepois de escrever o módulo, percebi que existe tal biblioteca: zendesk/cross-storage
Mas verifiquei sua API e código-fonte e comparei os métodos de implementação. Acho que minha versão é mais importante.
1. Minha versão tem controle sobre nome de domínio e gerenciamento de dados.
2. Minha versão da API de promessa é mais simplificada, com um onConnect a menos que ela. Você pode consultar a implementação dele. É muito mais do que escrevi e não resolve o problema de iframe aguardando assíncrono.
3. Dados compactados LZ não são suportados.
4. O gerenciamento do pool de armazenamento LRU não é suportado, portanto, pode haver muito armazenamento que pode causar falha de gravação.
5. Ele parece criar um iframe para cada interação, o que é um desperdício de operações e transmissão do DOM. Acho que não há problema em deixá-lo ativado. É claro que ele pode precisar se conectar a vários clientes para lidar com isso dessa maneira. .
ResumirO texto acima é a introdução do editor ao problema de usar armazenamento local em vez de cookies para realizar o compartilhamento de dados entre domínios. Espero que seja útil para você. Se você tiver alguma dúvida, deixe-me uma mensagem e o editor responderá. a tempo. Gostaria também de agradecer a todos pelo apoio ao site de artes marciais VeVb!