H5 离线数据同步冲突解决
1. 引言
在移动应用和 Web 应用日益普及的今天,用户对于在任何网络环境下都能无缝使用应用的需求愈发强烈。离线功能成为了提升用户体验的关键因素之一,它允许用户在无网络时继续操作应用,待网络恢复后再同步数据。然而,当多个设备或同一设备在不同时间对同一数据进行离线修改后,网络恢复时就会出现数据同步冲突的问题。
H5(HTML5)技术凭借其跨平台、无需安装等优势,广泛应用于各类应用开发中。在 H5 应用里实现离线数据同步并有效解决冲突,能够极大地提升应用的可用性和用户满意度。本文将深入探讨 H5 离线数据同步冲突解决的相关技术,包括其技术背景、应用场景、具体实现方法、原理以及实际代码示例等内容。
2. 技术背景
2.1 离线数据存储技术
H5 提供了多种离线数据存储技术,如 LocalStorage、SessionStorage、IndexedDB 等。LocalStorage 和 SessionStorage 是简单的键值对存储方式,适合存储少量的、不需要复杂查询的数据,它们的存储容量有限,且数据在页面刷新后(LocalStorage)或会话结束后(SessionStorage)的留存情况不同。IndexedDB 则是一个更强大的客户端存储数据库,支持存储大量结构化数据,具备事务处理能力,适合存储复杂的离线数据,如用户的操作记录、待同步的业务数据等。
2.2 Service Worker 与网络状态监听
Service Worker 是 H5 中的一项重要技术,它是一种运行在浏览器后台的脚本,独立于网页主线程。通过 Service Worker,可以拦截网络请求,实现离线缓存和数据同步。在离线数据同步场景中,Service Worker 可以监听网络状态的变化,当网络断开时,将用户的操作数据存储在本地;当网络恢复时,触发数据同步过程。
2.3 数据同步冲突的产生原因
数据同步冲突主要源于多个数据源对同一数据的并发修改。在离线场景下,用户可能在不同的设备上对同一数据进行操作,或者在同一设备的不同时间段对数据进行修改。当网络恢复时,这些离线修改的数据会尝试同步到服务器或其他设备,由于修改时间和内容的不同,就可能导致冲突。例如,用户在手机端离线修改了订单的状态,同时在电脑端也对同一订单进行了不同的状态修改,网络恢复后,这两个修改就需要进行冲突解决。
3. 应用使用场景
3.1 移动办公应用
在移动办公场景中,用户可能需要在手机、平板和电脑等不同设备上处理文档、表格等办公数据。当用户在离线状态下对文档进行编辑,之后在不同设备上继续编辑并同步时,就可能出现数据冲突。例如,用户在手机上离线修改了文档的某一部分内容,同时在电脑上也对该文档进行了修改,网络恢复后,需要解决这两个修改的冲突,以确保文档的一致性。
3.2 电商购物应用
电商购物应用中,用户可能在离线状态下将商品加入购物车、提交订单或修改订单信息。当网络恢复时,这些离线操作需要与服务器进行同步。如果多个设备或同一设备在不同时间对同一订单进行了不同的操作,就会产生冲突。比如,用户在手机端离线修改了订单的收货地址,同时在电脑端对同一订单进行了支付操作,网络恢复后,需要解决这些操作的冲突,保证订单的正确处理。
3.3 社交应用
社交应用中,用户可能在离线状态下发布动态、评论或点赞。当网络恢复时,这些离线操作需要同步到服务器并展示给其他用户。如果多个设备或同一设备在不同时间对同一内容进行了不同的操作,就会引发冲突。例如,用户在手机端离线发布了一条动态,同时在电脑端对该动态进行了删除操作,网络恢复后,需要解决这两个操作的冲突,确保社交数据的准确性和一致性。
4. 不同场景下的详细代码实现
4.1 环境准备
- 开发工具:可以使用 Visual Studio Code 等文本编辑器,结合 Chrome 浏览器进行开发和调试。
- 核心技术:HTML5、JavaScript、IndexedDB、Service Worker。
- 关键资源:需要准备一个简单的后端服务器用于模拟数据同步,或者使用一些在线的模拟 API 服务。
4.2 场景 1:移动办公文档编辑冲突解决
4.2.1 核心逻辑
用户在离线状态下对文档进行编辑,将编辑的内容存储在本地 IndexedDB 中。当网络恢复时,将本地的编辑内容同步到服务器。如果服务器上的文档已经被其他设备修改,就检测到冲突,通过合并编辑内容或提示用户选择解决冲突的方式。
4.2.2 代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>移动办公文档离线同步冲突解决</title>
</head>
<body>
<textarea id="document-content" rows="10" cols="50"></textarea>
<button id="save-button">保存</button>
<button id="sync-button">同步</button>
<script>
// 打开或创建 IndexedDB 数据库
const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('documentDB', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('documents')) {
db.createObjectStore('documents', { keyPath: 'id' });
}
};
});
};
// 保存文档到本地 IndexedDB
const saveDocumentLocally = async (content) => {
const db = await openDB();
const transaction = db.transaction(['documents'], 'readwrite');
const store = transaction.objectStore('documents');
const documentData = { id: 1, content, lastModified: new Date().getTime() };
const request = store.put(documentData);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
};
// 从本地 IndexedDB 获取文档
const getDocumentLocally = async () => {
const db = await openDB();
const transaction = db.transaction(['documents'], 'readonly');
const store = transaction.objectStore('documents');
const request = store.get(1);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// 模拟同步到服务器
const syncDocumentToServer = async (localContent) => {
// 模拟服务器上的文档数据
const serverDocument = { id: 1, content: '服务器上的原始内容', lastModified: new Date().getTime() - 10000 };
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
if (new Date(localContent.lastModified).getTime() > serverDocument.lastModified) {
// 本地修改时间晚于服务器,以本地为准
return { success: true, content: localContent.content, conflict: false };
} else if (new Date(localContent.lastModified).getTime() < serverDocument.lastModified) {
// 服务器修改时间晚于本地,检测到冲突
return { success: false, content: serverDocument.content, conflict: true };
} else {
// 修改时间相同,无冲突
return { success: true, content: localContent.content, conflict: false };
}
};
// 保存按钮点击事件
document.getElementById('save-button').addEventListener('click', async () => {
const content = document.getElementById('document-content').value;
try {
await saveDocumentLocally({ content, lastModified: new Date().getTime() });
alert('文档已保存到本地');
} catch (error) {
console.error('保存到本地失败:', error);
alert('保存到本地失败');
}
});
// 同步按钮点击事件
document.getElementById('sync-button').addEventListener('click', async () => {
try {
const localDocument = await getDocumentLocally();
if (!localDocument) {
alert('没有本地文档可同步');
return;
}
const result = await syncDocumentToServer(localDocument);
if (result.success && !result.conflict) {
alert('文档同步成功,内容为: ' + result.content);
} else if (result.conflict) {
// 处理冲突,这里简单提示用户
const userChoice = confirm('检测到冲突,服务器内容为: ' + result.content + ',是否使用服务器内容覆盖本地内容?');
if (userChoice) {
document.getElementById('document-content').value = result.content;
alert('已使用服务器内容覆盖本地内容');
} else {
alert('已保留本地内容,您可以在后续手动处理冲突');
}
}
} catch (error) {
console.error('同步失败:', error);
alert('同步失败');
}
});
</script>
</body>
</html>
4.2.3 原理解释
- 本地存储:使用 IndexedDB 将用户离线编辑的文档内容存储在本地,同时记录文档的最后修改时间。当用户点击保存按钮时,将文档内容和修改时间保存到 IndexedDB 中。
- 同步检测:当用户点击同步按钮时,从本地 IndexedDB 中获取文档内容,模拟向服务器发送同步请求。通过比较本地文档和服务器文档的最后修改时间来判断是否发生冲突。
- 冲突解决:如果本地修改时间晚于服务器,以本地内容为准,同步成功;如果服务器修改时间晚于本地,检测到冲突,提示用户选择使用服务器内容覆盖本地内容或保留本地内容。
4.3 场景 2:电商购物订单修改冲突解决
4.3.1 核心逻辑
用户在离线状态下对电商购物订单进行修改,如修改收货地址、支付方式等,将修改信息存储在本地。当网络恢复时,将本地的订单修改信息同步到服务器。如果服务器上的订单已经被其他设备修改,就检测到冲突,通过合并修改信息或提示用户选择解决冲突的方式。
4.3.2 代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电商购物订单离线同步冲突解决</title>
</head>
<body>
<div>
<label for="order-id">订单 ID:</label>
<input type="text" id="order-id" value="1" readonly><br>
<label for="shipping-address">收货地址:</label>
<input type="text" id="shipping-address" placeholder="请输入收货地址"><br>
<label for="payment-method">支付方式:</label>
<select id="payment-method">
<option value="credit-card">信用卡</option>
<option value="paypal">PayPal</option>
</select><br>
<button id="save-order-button">保存订单修改</button>
<button id="sync-order-button">同步订单</button>
</div>
<script>
// 打开或创建 IndexedDB 数据库
const openOrderDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('orderDB', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('orders')) {
db.createObjectStore('orders', { keyPath: 'id' });
}
};
});
};
// 保存订单修改到本地 IndexedDB
const saveOrderLocally = async (orderId, shippingAddress, paymentMethod) => {
const db = await openOrderDB();
const transaction = db.transaction(['orders'], 'readwrite');
const store = transaction.objectStore('orders');
const orderData = { id: orderId, shippingAddress, paymentMethod, lastModified: new Date().getTime() };
const request = store.put(orderData);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
};
// 从本地 IndexedDB 获取订单
const getOrderLocally = async (orderId) => {
const db = await openOrderDB();
const transaction = db.transaction(['orders'], 'readonly');
const store = transaction.objectStore('orders');
const request = store.get(orderId);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// 模拟同步订单到服务器
const syncOrderToServer = async (orderId, localShippingAddress, localPaymentMethod) => {
// 模拟服务器上的订单数据
const serverOrder = { id: orderId, shippingAddress: '服务器上的原始收货地址', paymentMethod: 'credit-card', lastModified: new Date().getTime() - 10000 };
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
if (new Date(localShippingAddress.lastModified || new Date().getTime()).getTime() > serverOrder.lastModified) {
// 本地修改时间晚于服务器,以本地为准
return { success: true, shippingAddress: localShippingAddress.shippingAddress, paymentMethod: localPaymentMethod, conflict: false };
} else if (new Date(localShippingAddress.lastModified || new Date().getTime()).getTime() < serverOrder.lastModified) {
// 服务器修改时间晚于本地,检测到冲突
return { success: false, shippingAddress: serverOrder.shippingAddress, paymentMethod: serverOrder.paymentMethod, conflict: true };
} else {
// 修改时间相同,无冲突
return { success: true, shippingAddress: localShippingAddress.shippingAddress, paymentMethod: localPaymentMethod, conflict: false };
}
};
// 保存订单修改按钮点击事件
document.getElementById('save-order-button').addEventListener('click', async () => {
const orderId = document.getElementById('order-id').value;
const shippingAddress = document.getElementById('shipping-address').value;
const paymentMethod = document.getElementById('payment-method').value;
try {
await saveOrderLocally(orderId, shippingAddress, paymentMethod);
alert('订单修改已保存到本地');
} catch (error) {
console.error('保存到本地失败:', error);
alert('保存到本地失败');
}
});
// 同步订单按钮点击事件
document.getElementById('sync-order-button').addEventListener('click', async () => {
const orderId = document.getElementById('order-id').value;
try {
const localOrder = await getOrderLocally(orderId);
if (!localOrder) {
alert('没有本地订单可同步');
return;
}
const result = await syncOrderToServer(orderId, localOrder, localOrder);
if (result.success && !result.conflict) {
alert('订单同步成功,收货地址为: ' + result.shippingAddress + ',支付方式为: ' + result.paymentMethod);
} else if (result.conflict) {
// 处理冲突,这里简单提示用户
const userChoice = confirm('检测到冲突,服务器收货地址为: ' + result.shippingAddress + ',支付方式为: ' + result.paymentMethod + ',是否使用服务器内容覆盖本地内容?');
if (userChoice) {
document.getElementById('shipping-address').value = result.shippingAddress;
document.getElementById('payment-method').value = result.paymentMethod;
alert('已使用服务器内容覆盖本地内容');
} else {
alert('已保留本地内容,您可以在后续手动处理冲突');
}
}
} catch (error) {
console.error('同步失败:', error);
alert('同步失败');
}
});
</script>
</body>
</html>
4.3.3 原理解释
- 本地存储:使用 IndexedDB 存储用户对电商购物订单的修改信息,包括收货地址、支付方式和修改时间。当用户保存订单修改时,将相关信息保存到 IndexedDB 中。
- 同步检测:当用户点击同步订单按钮时,从本地 IndexedDB 中获取订单修改信息,模拟向服务器发送同步请求。通过比较本地订单和服务器订单的最后修改时间来判断是否发生冲突。
- 冲突解决:如果本地修改时间晚于服务器,以本地内容为准,同步成功;如果服务器修改时间晚于本地,检测到冲突,提示用户选择使用服务器内容覆盖本地内容或保留本地内容。
5. 原理解释
5.1 离线数据存储原理
H5 中的离线数据存储技术(如 IndexedDB)允许在客户端本地存储大量的结构化数据。在离线状态下,用户的操作数据(如文档编辑内容、订单修改信息)被存储在本地数据库中,这些数据在网络恢复前不会丢失。IndexedDB 通过事务处理机制保证了数据的一致性和完整性,支持对数据的增删改查操作。
5.2 网络状态监听与同步触发
通过 Service Worker 或原生的网络状态监听 API(如 navigator.onLine
),可以实时监听网络状态的变化。当网络断开时,应用将用户的操作数据存储在本地;当网络恢复时,触发数据同步过程。在同步过程中,应用会将本地存储的数据发送到服务器,并接收服务器的响应。
5.3 冲突检测原理
冲突检测主要通过比较本地数据和服务器数据的版本信息(如最后修改时间、版本号)来判断是否发生冲突。如果本地数据的版本信息比服务器数据的版本信息新,说明本地数据是最新修改的;反之,如果服务器数据的版本信息比本地数据的版本信息新,说明服务器数据是最新修改的。当本地数据和服务器数据的版本信息不一致时,就检测到冲突。
5.4 冲突解决策略
- 以本地为准:当本地数据的修改时间晚于服务器数据时,认为本地的修改是最新的,以本地数据为准进行同步,覆盖服务器上的旧数据。
- 以服务器为准:当服务器数据的修改时间晚于本地数据时,认为服务器的数据是最新的,以服务器数据为准进行同步,覆盖本地数据。
- 合并数据:对于一些可以合并的数据(如文档编辑内容),可以将本地和服务器的修改内容进行合并,生成一个新的数据版本,既保留了本地的修改,也保留了服务器的修改。
- 提示用户选择:当无法自动解决冲突时,提示用户选择以本地数据为准、以服务器数据为准或手动合并数据,让用户根据自己的需求来决定如何处理冲突。
6. 核心特性
6.1 离线可用性
H5 离线数据同步冲突解决技术使得应用在无网络环境下仍能正常操作,用户可以继续编辑文档、修改订单等,待网络恢复后再进行数据同步,保证了应用的可用性。
6.2 数据一致性
通过有效的冲突解决策略,能够确保本地数据和服务器数据的一致性。无论是以本地为准、以服务器为准还是合并数据,都能保证最终的数据是准确和一致的,避免了数据冲突导致的错误和混乱。
6.3 用户体验优化
在处理冲突时,通过提示用户选择解决方式,让用户参与到冲突解决过程中,增强了用户对应用的控制感和信任感。同时,自动解决冲突的策略也能减少用户的操作负担,提升用户体验。
6.4 可扩展性
该技术可以应用于各种类型的应用场景,如移动办公、电商购物、社交应用等。可以根据不同应用的需求,灵活调整冲突解决策略和数据存储方式,具有良好的可扩展性。
7. 原理流程图及原理解释
7.1 H5 离线数据同步冲突解决原理流程图
graph TD;
A[用户操作数据] --> B{网络状态};
B -->|网络正常| C[直接同步到服务器];
B -->|网络断开| D[将数据存储到本地 IndexedDB];
D --> E{网络恢复?};
E -->|否| D;
E -->|是| F[从本地 IndexedDB 获取数据];
F --> G[模拟同步到服务器];
G --> H{是否冲突?};
H -->|否| I[同步成功];
H -->|是| J[检测到冲突];
J --> K{解决冲突方式};
K -->|以本地为准| I;
K -->|以服务器为准| L[使用服务器数据覆盖本地数据];
K -->|合并数据| M[生成新数据版本];
K -->|提示用户选择| N[用户选择解决方式];
N --> I;
N --> L;
N --> M;
7.2 原理解释
- 用户操作数据:用户在应用中进行各种操作,如编辑文档、修改订单等,产生需要同步的数据。
- 网络状态判断:通过监听网络状态,判断当前网络是否正常。如果网络正常,数据直接同步到服务器;如果网络断开,将数据存储到本地 IndexedDB 中。
- 本地存储:当网络断开时,将用户操作数据存储到本地 IndexedDB 中,同时记录数据的版本信息(如最后修改时间)。
- 网络恢复检测:当网络恢复时,检测到网络状态变化,从本地 IndexedDB 中获取存储的数据,准备进行同步。
- 同步过程:将本地数据发送到服务器进行同步,服务器返回响应。通过比较本地数据和服务器数据的版本信息来判断是否发生冲突。
- 冲突检测与解决:如果检测到冲突,根据不同的冲突解决策略(以本地为准、以服务器为准、合并数据、提示用户选择)来处理冲突,最终实现数据的同步和一致性。
8. 环境准备
8.1 开发环境
- 文本编辑器:如 Visual Studio Code、Sublime Text 等,用于编写 HTML、JavaScript 代码。
- 浏览器:推荐使用 Chrome 浏览器,它对 H5 技术(如 IndexedDB、Service Worker)的支持较好,方便进行开发和调试。
8.2 技术依赖
- HTML5:用于构建应用的用户界面,包含输入框、按钮等元素,用于用户输入数据和操作。
- JavaScript:实现离线数据存储、网络状态监听、数据同步和冲突解决等核心逻辑。
- IndexedDB:作为本地数据存储的数据库,用于存储用户离线操作的数据。
- 网络模拟工具:可以使用浏览器的开发者工具中的网络模拟功能,模拟网络断开和恢复的场景,方便进行测试。
9. 实际详细应用代码示例实现(综合示例)
以下是一个综合示例,结合了文档编辑和订单修改的场景,展示了完整的离线数据同步冲突解决过程。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>H5 离线数据同步冲突解决综合示例</title>
</head>
<body>
<h2>文档编辑与订单修改离线同步冲突解决</h2>
<div>
<h3>文档编辑</h3>
<textarea id="document-content" rows="5" cols="50"></textarea>
<button id="save-document-button">保存文档</button>
<button id="sync-document-button">同步文档</button>
</div>
<div>
<h3>订单修改</h3>
<div>
<label for="order-id">订单 ID:</label>
<input type="text" id="order-id" value="1" readonly><br>
<label for="shipping-address">收货地址:</label>
<input type="text" id="shipping-address" placeholder="请输入收货地址"><br>
<label for="payment-method">支付方式:</label>
<select id="payment-method">
<option value="credit-card">信用卡</option>
<option value="paypal">PayPal</option>
</select><br>
<button id="save-order-button">保存订单修改</button>
<button id="sync-order-button">同步订单</button>
</div>
</div>
<script>
// 打开或创建文档 IndexedDB 数据库
const openDocumentDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('documentDB', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('documents')) {
db.createObjectStore('documents', { keyPath: 'id' });
}
};
});
};
// 保存文档到本地 IndexedDB
const saveDocumentLocally = async (content) => {
const db = await openDocumentDB();
const transaction = db.transaction(['documents'], 'readwrite');
const store = transaction.objectStore('documents');
const documentData = { id: 1, content, lastModified: new Date().getTime() };
const request = store.put(documentData);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
};
// 从本地 IndexedDB 获取文档
const getDocumentLocally = async () => {
const db = await openDocumentDB();
const transaction = db.transaction(['documents'], 'readonly');
const store = transaction.objectStore('documents');
const request = store.get(1);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// 模拟同步文档到服务器
const syncDocumentToServer = async (localContent) => {
// 模拟服务器上的文档数据
const serverDocument = { id: 1, content: '服务器上的原始文档内容', lastModified: new Date().getTime() - 10000 };
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
if (new Date(localContent.lastModified).getTime() > serverDocument.lastModified) {
return { success: true, content: localContent.content, conflict: false };
} else if (new Date(localContent.lastModified).getTime() < serverDocument.lastModified) {
return { success: false, content: serverDocument.content, conflict: true };
} else {
return { success: true, content: localContent.content, conflict: false };
}
};
// 打开或创建订单 IndexedDB 数据库
const openOrderDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('orderDB', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('orders')) {
db.createObjectStore('orders', { keyPath: 'id' });
}
};
});
};
// 保存订单修改到本地 IndexedDB
const saveOrderLocally = async (orderId, shippingAddress, paymentMethod) => {
const db = await openOrderDB();
const transaction = db.transaction(['orders'], 'readwrite');
const store = transaction.objectStore('orders');
const orderData = { id: orderId, shippingAddress, paymentMethod, lastModified: new Date().getTime() };
const request = store.put(orderData);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
};
// 从本地 IndexedDB 获取订单
const getOrderLocally = async (orderId) => {
const db = await openOrderDB();
const transaction = db.transaction(['orders'], 'readonly');
const store = transaction.objectStore('orders');
const request = store.get(orderId);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// 模拟同步订单到服务器
const syncOrderToServer = async (orderId, localShippingAddress, localPaymentMethod) => {
// 模拟服务器上的订单数据
const serverOrder = { id: orderId, shippingAddress: '服务器上的原始收货地址', paymentMethod: 'credit-card', lastModified: new Date().getTime() - 10000 };
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
if (new Date(localShippingAddress.lastModified || new Date().getTime()).getTime() > serverOrder.lastModified) {
return { success: true, shippingAddress: localShippingAddress.shippingAddress, paymentMethod: localPaymentMethod, conflict: false };
} else if (new Date(localShippingAddress.lastModified || new Date().getTime()).getTime() < serverOrder.lastModified) {
return { success: false, shippingAddress: serverOrder.shippingAddress, paymentMethod: serverOrder.paymentMethod, conflict: true };
} else {
return { success: true, shippingAddress: localShippingAddress.shippingAddress, paymentMethod: localPaymentMethod, conflict: false };
}
};
// 保存文档按钮点击事件
document.getElementById('save-document-button').addEventListener('click', async () => {
const content = document.getElementById('document-content').value;
try {
await saveDocumentLocally({ content, lastModified: new Date().getTime() });
alert('文档已保存到本地');
} catch (error) {
console.error('保存到本地失败:', error);
alert('保存到本地失败');
}
});
// 同步文档按钮点击事件
document.getElementById('sync-document-button').addEventListener('click', async () => {
try {
const localDocument = await getDocumentLocally();
if (!localDocument) {
alert('没有本地文档可同步');
return;
}
const result = await syncDocumentToServer(localDocument);
if (result.success && !result.conflict) {
alert('文档同步成功,内容为: ' + result.content);
} else if (result.conflict) {
const userChoice = confirm('检测到冲突,服务器内容为: ' + result.content + ',是否使用服务器内容覆盖本地内容?');
if (userChoice) {
document.getElementById('document-content').value = result.content;
alert('已使用服务器内容覆盖本地内容');
} else {
alert('已保留本地内容,您可以在后续手动处理冲突');
}
}
} catch (error) {
console.error('同步失败:', error);
alert('同步失败');
}
});
// 保存订单修改按钮点击事件
document.getElementById('save-order-button').addEventListener('click', async () => {
const orderId = document.getElementById('order-id').value;
const shippingAddress = document.getElementById('shipping-address').value;
const paymentMethod = document.getElementById('payment-method').value;
try {
await saveOrderLocally(orderId, shippingAddress, paymentMethod);
alert('订单修改已保存到本地');
} catch (error) {
console.error('保存到本地失败:', error);
alert('保存到本地失败');
}
});
// 同步订单按钮点击事件
document.getElementById('sync-order-button').addEventListener('click', async () => {
const orderId = document.getElementById('order-id').value;
try {
const localOrder = await getOrderLocally(orderId);
if (!localOrder) {
alert('没有本地订单可同步');
return;
}
const result = await syncOrderToServer(orderId, localOrder, localOrder);
if (result.success && !result.conflict) {
alert('订单同步成功,收货地址为: ' + result.shippingAddress + ',支付方式为: ' + result.paymentMethod);
} else if (result.conflict) {
const userChoice = confirm('检测到冲突,服务器收货地址为: ' + result.shippingAddress + ',支付方式为: ' + result.paymentMethod + ',是否使用服务器内容覆盖本地内容?');
if (userChoice) {
document.getElementById('shipping-address').value = result.shippingAddress;
document.getElementById('payment-method').value = result.paymentMethod;
alert('已使用服务器内容覆盖本地内容');
} else {
alert('已保留本地内容,您可以在后续手动处理冲突');
}
}
} catch (error) {
console.error('同步失败:', error);
alert('同步失败');
}
});
</script>
</body>
</html>
9.1 运行结果
- 本地保存:当用户点击保存文档或保存订单修改按钮时,相应的操作数据(文档内容或订单修改信息)会被保存到本地 IndexedDB 中,同时弹出提示框告知用户保存成功。
- 同步成功(无冲突):当网络恢复后,点击同步文档或同步订单按钮,如果本地数据的修改时间晚于服务器数据,同步成功,弹出提示框显示同步后的内容。
- 冲突解决:当检测到冲突时,弹出提示框告知用户检测到冲突,并提供选择项(使用服务器内容覆盖本地内容或保留本地内容)。用户选择后,根据选择结果更新界面数据,并弹出相应的提示框。
9.2 测试步骤及详细代码
9.2.1 测试目标
验证 H5 离线数据同步冲突解决功能是否正常工作,包括本地数据保存、网络状态变化时的同步触发、冲突检测和冲突解决。
9.2.2 测试步骤
- 本地保存测试:在文档编辑区域输入一些内容,点击保存文档按钮,验证是否弹出保存成功的提示框。同样,在订单修改区域输入收货地址和选择支付方式,点击保存订单修改按钮,验证是否保存成功。
- 网络断开与本地存储测试:可以使用浏览器的开发者工具中的网络模拟功能,将网络设置为离线状态。然后进行文档编辑和订单修改操作,点击保存按钮,验证数据是否保存到本地 IndexedDB 中(可以通过调试工具查看 IndexedDB 数据)。
- 同步测试(无冲突):将网络恢复为在线状态,点击同步文档和同步订单按钮,验证是否弹出同步成功的提示框,且显示的内容为本地保存的数据。
- 冲突测试:在网络断开状态下,分别在本地和模拟服务器上对同一文档或订单进行不同的修改(通过修改代码中的服务器数据模拟)。然后恢复网络,点击同步按钮,验证是否检测到冲突,弹出冲突解决的提示框,用户选择不同的解决方式后,验证界面数据是否相应更新。
9.2.3 详细代码(本地服务器启动示例,可选)
如果需要模拟真实的服务器环境,可以使用 Node.js 的 http-server
或 express
框架搭建一个简单的服务器。以下是使用 http-server
的示例:
# 安装 http-server(如果未安装)
npm install -g http-server
# 启动本地服务器(端口 8080)
http-server -p 8080
将上述 HTML 文件保存到服务器的根目录下,然后在浏览器中访问 http://localhost:8080
即可进行测试。
10. 部署场景
10.1 移动办公应用部署
可以将移动办公应用部署到企业的内部服务器或云平台上,员工通过手机、平板等设备访问应用。在离线状态下,员工可以继续编辑文档,网络恢复后,数据自动同步到服务器,保证文档的一致性和实时性。
10.2 电商购物应用部署
电商购物应用可以部署到电商平台的服务器上,用户通过手机浏览器或 PWA 应用访问。在离线状态下,用户可以对购物车、订单等进行修改,网络恢复后,数据同步到服务器,确保订单的正确处理和商品的及时配送。
10.3 社交应用部署
社交应用可以部署到社交平台的服务器上,用户通过手机或电脑浏览器访问。在离线状态下,用户可以发布动态、评论等,网络恢复后,数据同步到服务器,保证社交信息的及时传播和互动。
11. 疑难解答
11.1 问题 1:数据无法保存到本地 IndexedDB
- 可能原因:浏览器不支持 IndexedDB、代码中存在语法错误、数据库版本升级失败等。
- 解决方案:
- 点赞
- 收藏
- 关注作者
评论(0)