SpringBoot離線應用的5種實現方式

在當今高度依賴網絡的環境中,離線應用的價值日益凸顯。無論是在網絡不穩定的區域運行的現場系統,還是需要在斷網環境下使用的企業內部應用,具備離線工作能力已成為許多應用的必備特性。

本文將介紹基于SpringBoot實現離線應用的5種不同方式。

一、離線應用的概念與挑戰

離線應用(Offline Application)是指能夠在網絡連接不可用的情況下,仍然能夠正常運行并提供核心功能的應用程序。這類應用通常具備以下特點:

  1. 本地數據存儲:能夠在本地存儲和讀取數據
  2. 操作緩存:能夠緩存用戶操作,待網絡恢復后同步
  3. 資源本地化:應用資源(如靜態資源、配置等)可以在本地訪問
  4. 狀態管理:維護應用狀態,處理在線/離線切換

實現離線應用面臨的主要挑戰包括:數據存儲與同步、沖突解決、用戶體驗設計以及安全性考慮。

二、嵌入式數據庫實現離線數據存儲

原理介紹

嵌入式數據庫直接集成在應用程序中,無需外部數據庫服務器,非常適合離線應用場景。

在SpringBoot中,可以輕松集成H2、SQLite、HSQLDB等嵌入式數據庫。

實現步驟

  1. 添加依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope>
</dependency>
  1. 配置文件
# 使用文件模式的H2數據庫,支持持久化
spring.datasource.url=jdbc:h2:file:./data/offlinedb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect# 自動創建表結構
spring.jpa.hibernate.ddl-auto=update# 啟用H2控制臺(開發環境)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
  1. 創建實體類
@Entity
@Table(name = "offline_data")
public class OfflineData {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String content;@Column(name = "is_synced")private boolean synced;@Column(name = "created_at")private LocalDateTime createdAt;// 構造函數、getter和setter
}
  1. 創建Repository
@Repository
public interface OfflineDataRepository extends JpaRepository<OfflineData, Long> {List<OfflineData> findBySyncedFalse();
}
  1. 創建Service
@Service
public class OfflineDataService {private final OfflineDataRepository repository;@Autowiredpublic OfflineDataService(OfflineDataRepository repository) {this.repository = repository;}// 保存本地數據public OfflineData saveData(String content) {OfflineData data = new OfflineData();data.setContent(content);data.setSynced(false);data.setCreatedAt(LocalDateTime.now());return repository.save(data);}// 獲取所有未同步的數據public List<OfflineData> getUnsyncedData() {return repository.findBySyncedFalse();}// 標記數據為已同步public void markAsSynced(Long id) {repository.findById(id).ifPresent(data -> {data.setSynced(true);repository.save(data);});}// 當網絡恢復時,同步數據到遠程服務器@Scheduled(fixedDelay = 60000) // 每分鐘檢查一次public void syncDataToRemote() {List<OfflineData> unsyncedData = getUnsyncedData();if (!unsyncedData.isEmpty()) {try {// 嘗試連接遠程服務器if (isNetworkAvailable()) {for (OfflineData data : unsyncedData) {boolean syncSuccess = sendToRemoteServer(data);if (syncSuccess) {markAsSynced(data.getId());}}}} catch (Exception e) {// 同步失敗,下次再試log.error("Failed to sync data: " + e.getMessage());}}}private boolean isNetworkAvailable() {// 實現網絡檢測邏輯try {InetAddress address = InetAddress.getByName("api.example.com");return address.isReachable(3000); // 3秒超時} catch (Exception e) {return false;}}private boolean sendToRemoteServer(OfflineData data) {// 實現發送數據到遠程服務器的邏輯// 這里使用RestTemplate示例try {RestTemplate restTemplate = new RestTemplate();ResponseEntity<String> response = restTemplate.postForEntity("https://api.example.com/data", data, String.class);return response.getStatusCode().isSuccessful();} catch (Exception e) {log.error("Failed to send data: " + e.getMessage());return false;}}
}
  1. 創建Controller
@RestController
@RequestMapping("/api/data")
public class OfflineDataController {private final OfflineDataService service;@Autowiredpublic OfflineDataController(OfflineDataService service) {this.service = service;}@PostMappingpublic ResponseEntity<OfflineData> createData(@RequestBody String content) {OfflineData savedData = service.saveData(content);return ResponseEntity.ok(savedData);}@GetMapping("/unsynced")public ResponseEntity<List<OfflineData>> getUnsyncedData() {return ResponseEntity.ok(service.getUnsyncedData());}@PostMapping("/sync")public ResponseEntity<String> triggerSync() {service.syncDataToRemote();return ResponseEntity.ok("Sync triggered");}
}

優缺點分析

優點:

  • 完全本地化的數據存儲,無需網絡連接
  • 支持完整的SQL功能,可以進行復雜查詢
  • 數據持久化到本地文件,應用重啟不丟失

缺點:

  • 嵌入式數據庫性能和并發處理能力有限
  • 占用本地存儲空間,需要注意容量管理
  • 數據同步邏輯需要自行實現
  • 復雜的沖突解決場景處理困難

適用場景

  • 需要結構化數據存儲的單機應用
  • 定期需要將數據同步到中心服務器的現場應用
  • 對數據查詢有SQL需求的離線系統
  • 數據量適中的企業內部工具

三、本地緩存與離線數據訪問策略

原理介紹

本方案利用Java內存緩存框架(如Caffeine、Ehcache)結合本地持久化存儲,實現數據的本地緩存和離線訪問。該方案特別適合讀多寫少的應用場景。

實現步驟

  1. 添加依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>
  1. 配置緩存
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic Caffeine<Object, Object> caffeineConfig() {return Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.DAYS).initialCapacity(100).maximumSize(1000).recordStats();}@Beanpublic CacheManager cacheManager(Caffeine<Object, Object> caffeine) {CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(caffeine);return cacheManager;}@Beanpublic CacheSerializer cacheSerializer() {return new CacheSerializer();}
}
  1. 創建緩存序列化器
@Component
public class CacheSerializer {private final ObjectMapper objectMapper = new ObjectMapper();private final File cacheDir = new File("./cache");public CacheSerializer() {if (!cacheDir.exists()) {cacheDir.mkdirs();}}public void serializeCache(String cacheName, Map<Object, Object> entries) {try {File cacheFile = new File(cacheDir, cacheName + ".json");objectMapper.writeValue(cacheFile, entries);} catch (IOException e) {throw new RuntimeException("Failed to serialize cache: " + cacheName, e);}}@SuppressWarnings("unchecked")public Map<Object, Object> deserializeCache(String cacheName) {File cacheFile = new File(cacheDir, cacheName + ".json");if (!cacheFile.exists()) {return new HashMap<>();}try {return objectMapper.readValue(cacheFile, Map.class);} catch (IOException e) {throw new RuntimeException("Failed to deserialize cache: " + cacheName, e);}}
}
  1. 創建離線數據服務
@Service
@Slf4j
public class ProductService {private final RestTemplate restTemplate;private final CacheSerializer cacheSerializer;private static final String CACHE_NAME = "products";@Autowiredpublic ProductService(RestTemplate restTemplate, CacheSerializer cacheSerializer) {this.restTemplate = restTemplate;this.cacheSerializer = cacheSerializer;// 初始化時加載持久化的緩存loadCacheFromDisk();}@Cacheable(cacheNames = CACHE_NAME, key = "#id")public Product getProductById(Long id) {try {// 嘗試從遠程服務獲取return restTemplate.getForObject("https://api.example.com/products/" + id, Product.class);} catch (Exception e) {// 網絡不可用時,嘗試從持久化緩存獲取Map<Object, Object> diskCache = cacheSerializer.deserializeCache(CACHE_NAME);Product product = (Product) diskCache.get(id.toString());if (product != null) {return product;}throw new ProductNotFoundException("Product not found in cache: " + id);}}@Cacheable(cacheNames = CACHE_NAME)public List<Product> getAllProducts() {try {// 嘗試從遠程服務獲取Product[] products = restTemplate.getForObject("https://api.example.com/products", Product[].class);return products != null ? Arrays.asList(products) : Collections.emptyList();} catch (Exception e) {// 網絡不可用時,返回所有持久化緩存的產品Map<Object, Object> diskCache = cacheSerializer.deserializeCache(CACHE_NAME);return new ArrayList<>(diskCache.values());}}@CachePut(cacheNames = CACHE_NAME, key = "#product.id")public Product saveProduct(Product product) {try {// 嘗試保存到遠程服務return restTemplate.postForObject("https://api.example.com/products", product, Product.class);} catch (Exception e) {// 網絡不可用時,只保存到本地緩存product.setOfflineSaved(true);// 同時更新持久化緩存Map<Object, Object> diskCache = cacheSerializer.deserializeCache(CACHE_NAME);diskCache.put(product.getId().toString(), product);cacheSerializer.serializeCache(CACHE_NAME, diskCache);return product;}}@Scheduled(fixedDelay = 300000) // 每5分鐘public void persistCacheToDisk() {Cache cache = cacheManager.getCache(CACHE_NAME);if (cache != null) {Map<Object, Object> entries = new HashMap<>();cache.getNativeCache().asMap().forEach(entries::put);cacheSerializer.serializeCache(CACHE_NAME, entries);}}@Scheduled(fixedDelay = 600000) // 每10分鐘public void syncOfflineData() {if (!isNetworkAvailable()) {return;}Map<Object, Object> diskCache = cacheSerializer.deserializeCache(CACHE_NAME);for (Object value : diskCache.values()) {Product product = (Product) value;if (product.isOfflineSaved()) {try {restTemplate.postForObject("https://api.example.com/products", product, Product.class);product.setOfflineSaved(false);} catch (Exception e) {// 同步失敗,下次再試log.error(e.getMessage(),e);}}}// 更新持久化緩存cacheSerializer.serializeCache(CACHE_NAME, diskCache);}private void loadCacheFromDisk() {Map<Object, Object> diskCache = cacheSerializer.deserializeCache(CACHE_NAME);Cache cache = cacheManager.getCache(CACHE_NAME);if (cache != null) {diskCache.forEach((key, value) -> cache.put(key, value));}}private boolean isNetworkAvailable() {try {return InetAddress.getByName("api.example.com").isReachable(3000);} catch (Exception e) {return false;}}
}
  1. 創建數據模型
@Data
public class Product implements Serializable {private Long id;private String name;private String description;private BigDecimal price;private boolean offlineSaved;
}
  1. 創建Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {private final ProductService productService;@Autowiredpublic ProductController(ProductService productService) {this.productService = productService;}@GetMapping("/{id}")public ResponseEntity<Product> getProductById(@PathVariable Long id) {try {return ResponseEntity.ok(productService.getProductById(id));} catch (ProductNotFoundException e) {return ResponseEntity.notFound().build();}}@GetMappingpublic ResponseEntity<List<Product>> getAllProducts() {return ResponseEntity.ok(productService.getAllProducts());}@PostMappingpublic ResponseEntity<Product> createProduct(@RequestBody Product product) {return ResponseEntity.ok(productService.saveProduct(product));}@GetMapping("/sync")public ResponseEntity<String> triggerSync() {productService.syncOfflineData();return ResponseEntity.ok("Sync triggered");}
}

優缺點分析

優點:

  • 內存緩存訪問速度快,用戶體驗好
  • 結合本地持久化,支持應用重啟后恢復緩存
  • 適合讀多寫少的應用場景

缺點:

  • 緩存同步和沖突解決邏輯復雜
  • 大量數據緩存會占用較多內存
  • 不適合頻繁寫入的場景
  • 緩存序列化和反序列化有性能開銷

適用場景

  • 產品目錄、知識庫等讀多寫少的應用
  • 需要快速響應的用戶界面
  • 有限的數據集合且結構相對固定
  • 偶爾離線使用的Web應用

四、離線優先架構與本地存儲引擎

原理介紹

離線優先架構(Offline-First)是一種設計理念,它將離線狀態視為應用的默認狀態,而不是異常狀態。

在這種架構中,數據首先存儲在本地,然后在條件允許時同步到服務器。

該方案使用嵌入式KV存儲(如LevelDB、RocksDB)作為本地存儲引擎。

實現步驟

  1. 添加依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.iq80.leveldb</groupId><artifactId>leveldb</artifactId><version>0.12</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>
  1. 創建LevelDB存儲服務
@Component
public class LevelDBStore implements InitializingBean, DisposableBean {private DB db;private final ObjectMapper objectMapper = new ObjectMapper();private final File dbDir = new File("./leveldb");@Overridepublic void afterPropertiesSet() throws Exception {Options options = new Options();options.createIfMissing(true);db = factory.open(dbDir, options);}@Overridepublic void destroy() throws Exception {if (db != null) {db.close();}}public <T> void put(String key, T value) {try {byte[] serialized = objectMapper.writeValueAsBytes(value);db.put(bytes(key), serialized);} catch (Exception e) {throw new RuntimeException("Failed to store data: " + key, e);}}public <T> T get(String key, Class<T> type) {try {byte[] data = db.get(bytes(key));if (data == null) {return null;}return objectMapper.readValue(data, type);} catch (Exception e) {throw new RuntimeException("Failed to retrieve data: " + key, e);}}public <T> List<T> getAll(String prefix, Class<T> type) {List<T> result = new ArrayList<>();try (DBIterator iterator = db.iterator()) {byte[] prefixBytes = bytes(prefix);for (iterator.seek(prefixBytes); iterator.hasNext(); iterator.next()) {String key = asString(iterator.peekNext().getKey());if (!key.startsWith(prefix)) {break;}T value = objectMapper.readValue(iterator.peekNext().getValue(), type);result.add(value);}} catch (Exception e) {throw new RuntimeException("Failed to retrieve data with prefix: " + prefix, e);}return result;}public boolean delete(String key) {try {db.delete(bytes(key));return true;} catch (Exception e) {return false;}}private byte[] bytes(String s) {return s.getBytes(StandardCharsets.UTF_8);}private String asString(byte[] bytes) {return new String(bytes, StandardCharsets.UTF_8);}
}
  1. 創建離線同步管理器
@Component
public class SyncManager {private final LevelDBStore store;private final RestTemplate restTemplate;@Value("${sync.server.url}")private String syncServerUrl;@Autowiredpublic SyncManager(LevelDBStore store, RestTemplate restTemplate) {this.store = store;this.restTemplate = restTemplate;}// 保存并跟蹤離線操作public <T> void saveOperation(String type, String id, T data) {String key = "op:" + type + ":" + id;OfflineOperation<T> operation = new OfflineOperation<>(UUID.randomUUID().toString(),type,id,data,System.currentTimeMillis());store.put(key, operation);}// 同步所有未同步的操作@Scheduled(fixedDelay = 60000) // 每分鐘嘗試同步public void syncOfflineOperations() {if (!isNetworkAvailable()) {return;}List<OfflineOperation<?>> operations = store.getAll("op:", OfflineOperation.class);// 按時間戳排序,確保按操作順序同步operations.sort(Comparator.comparing(OfflineOperation::getTimestamp));for (OfflineOperation<?> operation : operations) {boolean success = sendToServer(operation);if (success) {// 同步成功后刪除本地操作記錄store.delete("op:" + operation.getType() + ":" + operation.getId());} else {// 同步失敗,下次再試break;}}}private boolean sendToServer(OfflineOperation<?> operation) {try {HttpMethod method;switch (operation.getType()) {case "CREATE":method = HttpMethod.POST;break;case "UPDATE":method = HttpMethod.PUT;break;case "DELETE":method = HttpMethod.DELETE;break;default:return false;}// 構建請求URLString url = syncServerUrl + "/" + operation.getId();if ("DELETE".equals(operation.getType())) {// DELETE請求通常不需要請求體ResponseEntity<Void> response = restTemplate.exchange(url, method, null, Void.class);return response.getStatusCode().is2xxSuccessful();} else {// POST和PUT請求需要請求體HttpEntity<Object> request = new HttpEntity<>(operation.getData());ResponseEntity<Object> response = restTemplate.exchange(url, method, request, Object.class);return response.getStatusCode().is2xxSuccessful();}} catch (Exception e) {return false;}}private boolean isNetworkAvailable() {try {URL url = new URL(syncServerUrl);HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setConnectTimeout(3000);connection.connect();return connection.getResponseCode() == 200;} catch (Exception e) {return false;}}@Data@AllArgsConstructorprivate static class OfflineOperation<T> {private String operationId;private String type; // CREATE, UPDATE, DELETEprivate String id;private T data;private long timestamp;}
}
  1. 創建任務服務
@Service
public class TaskService {private final LevelDBStore store;private final SyncManager syncManager;@Autowiredpublic TaskService(LevelDBStore store, SyncManager syncManager) {this.store = store;this.syncManager = syncManager;}public Task getTaskById(String id) {return store.get("task:" + id, Task.class);}public List<Task> getAllTasks() {return store.getAll("task:", Task.class);}public Task createTask(Task task) {// 生成IDif (task.getId() == null) {task.setId(UUID.randomUUID().toString());}// 設置時間戳task.setCreatedAt(System.currentTimeMillis());task.setUpdatedAt(System.currentTimeMillis());// 保存到本地存儲store.put("task:" + task.getId(), task);// 記錄離線操作,等待同步syncManager.saveOperation("CREATE", task.getId(), task);return task;}public Task updateTask(String id, Task task) {Task existingTask = getTaskById(id);if (existingTask == null) {throw new RuntimeException("Task not found: " + id);}// 更新字段task.setId(id);task.setCreatedAt(existingTask.getCreatedAt());task.setUpdatedAt(System.currentTimeMillis());// 保存到本地存儲store.put("task:" + id, task);// 記錄離線操作,等待同步syncManager.saveOperation("UPDATE", id, task);return task;}public boolean deleteTask(String id) {Task existingTask = getTaskById(id);if (existingTask == null) {return false;}// 從本地存儲刪除boolean deleted = store.delete("task:" + id);// 記錄離線操作,等待同步if (deleted) {syncManager.saveOperation("DELETE", id, null);}return deleted;}
}
  1. 創建任務模型
@Data
public class Task {private String id;private String title;private String description;private boolean completed;private long createdAt;private long updatedAt;
}
  1. 創建Controller
@RestController
@RequestMapping("/api/tasks")
public class TaskController {private final TaskService taskService;@Autowiredpublic TaskController(TaskService taskService) {this.taskService = taskService;}@GetMapping("/{id}")public ResponseEntity<Task> getTaskById(@PathVariable String id) {Task task = taskService.getTaskById(id);if (task == null) {return ResponseEntity.notFound().build();}return ResponseEntity.ok(task);}@GetMappingpublic ResponseEntity<List<Task>> getAllTasks() {return ResponseEntity.ok(taskService.getAllTasks());}@PostMappingpublic ResponseEntity<Task> createTask(@RequestBody Task task) {return ResponseEntity.ok(taskService.createTask(task));}@PutMapping("/{id}")public ResponseEntity<Task> updateTask(@PathVariable String id, @RequestBody Task task) {try {return ResponseEntity.ok(taskService.updateTask(id, task));} catch (Exception e) {return ResponseEntity.notFound().build();}}@DeleteMapping("/{id}")public ResponseEntity<Void> deleteTask(@PathVariable String id) {boolean deleted = taskService.deleteTask(id);if (deleted) {return ResponseEntity.noContent().build();}return ResponseEntity.notFound().build();}@PostMapping("/sync")public ResponseEntity<String> triggerSync() {return ResponseEntity.ok("Sync triggered");}
}
  1. 配置文件
# 同步服務器地址
sync.server.url=https://api.example.com/tasks

優缺點分析

優點:

  • 離線優先設計,保證應用在任何網絡狀態下可用
  • 高性能的本地存儲引擎,適合大量數據
  • 支持完整的CRUD操作和離線同步
  • 細粒度的操作跟蹤,便于解決沖突

缺點:

  • 實現復雜度較高
  • 同步策略需要根據業務場景定制
  • 不支持復雜的關系型查詢

適用場景

  • 需要全面離線支持的企業應用
  • 現場操作類系統,如倉庫管理、物流系統
  • 數據量較大的離線應用
  • 需要嚴格保證離線和在線數據一致性的場景

五、嵌入式消息隊列與異步處理

原理介紹

該方案使用嵌入式消息隊列(如ActiveMQ Artemis嵌入模式)實現離線操作的異步處理和持久化。

操作被發送到本地隊列,在網絡恢復后批量處理。

實現步驟

  1. 添加依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency><groupId>org.apache.activemq</groupId><artifactId>artemis-server</artifactId>
</dependency>
<dependency><groupId>org.apache.activemq</groupId><artifactId>artemis-jms-server</artifactId>
</dependency>
  1. 配置嵌入式Artemis
@Configuration
@Slf4j
public class ArtemisConfig {@Value("${artemis.embedded.data-directory:./artemis-data}")private String dataDirectory;@Value("${artemis.embedded.queues:offlineOperations}")private String queues;@Beanpublic ActiveMQServer activeMQServer() throws Exception {Configuration config = new ConfigurationImpl();config.setPersistenceEnabled(true);config.setJournalDirectory(dataDirectory + "/journal");config.setBindingsDirectory(dataDirectory + "/bindings");config.setLargeMessagesDirectory(dataDirectory + "/largemessages");config.setPagingDirectory(dataDirectory + "/paging");config.addAcceptorConfiguration("in-vm", "vm://0");config.addAddressSetting("#", new AddressSettings().setDeadLetterAddress(SimpleString.toSimpleString("DLQ")).setExpiryAddress(SimpleString.toSimpleString("ExpiryQueue")));ActiveMQServer server = new ActiveMQServerImpl(config);server.start();// 創建隊列Arrays.stream(queues.split(",")).forEach(queue -> {try {server.createQueue(SimpleString.toSimpleString(queue),RoutingType.ANYCAST,SimpleString.toSimpleString(queue),null,true,false);} catch (Exception e) {log.error(e.getMessage(),e);}});return server;}@Beanpublic ConnectionFactory connectionFactory() {return new ActiveMQConnectionFactory("vm://0");}@Beanpublic JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {JmsTemplate template = new JmsTemplate(connectionFactory);template.setDeliveryPersistent(true);return template;}
}
  1. 創建離線操作消息服務
@Service
public class OfflineMessageService {private final JmsTemplate jmsTemplate;private final ObjectMapper objectMapper;@Value("${artemis.queue.operations:offlineOperations}")private String operationsQueue;@Autowiredpublic OfflineMessageService(JmsTemplate jmsTemplate) {this.jmsTemplate = jmsTemplate;this.objectMapper = new ObjectMapper();}public void sendOperation(OfflineOperation operation) {try {String json = objectMapper.writeValueAsString(operation);jmsTemplate.convertAndSend(operationsQueue, json);} catch (Exception e) {throw new RuntimeException("Failed to send operation to queue", e);}}public OfflineOperation receiveOperation() {try {String json = (String) jmsTemplate.receiveAndConvert(operationsQueue);if (json == null) {return null;}return objectMapper.readValue(json, OfflineOperation.class);} catch (Exception e) {throw new RuntimeException("Failed to receive operation from queue", e);}}@Data@AllArgsConstructor@NoArgsConstructorpublic static class OfflineOperation {private String type;      // CREATE, UPDATE, DELETEprivate String endpoint;  // API endpointprivate String id;        // resource idprivate String payload;   // JSON payloadprivate long timestamp;}
}
  1. 創建離線操作處理服務
@Service
public class OrderService {private final OfflineMessageService messageService;private final RestTemplate restTemplate;private final ObjectMapper objectMapper = new ObjectMapper();@Value("${api.base-url}")private String apiBaseUrl;@Autowiredpublic OrderService(OfflineMessageService messageService, RestTemplate restTemplate) {this.messageService = messageService;this.restTemplate = restTemplate;}// 創建訂單 - 直接進入離線隊列public void createOrder(Order order) {try {// 生成IDif (order.getId() == null) {order.setId(UUID.randomUUID().toString());}order.setCreatedAt(System.currentTimeMillis());order.setStatus("PENDING");String payload = objectMapper.writeValueAsString(order);OfflineMessageService.OfflineOperation operation = new OfflineMessageService.OfflineOperation("CREATE","orders",order.getId(),payload,System.currentTimeMillis());messageService.sendOperation(operation);} catch (Exception e) {throw new RuntimeException("Failed to create order", e);}}// 更新訂單狀態 - 直接進入離線隊列public void updateOrderStatus(String orderId, String status) {try {Map<String, Object> update = new HashMap<>();update.put("status", status);update.put("updatedAt", System.currentTimeMillis());String payload = objectMapper.writeValueAsString(update);OfflineMessageService.OfflineOperation operation = new OfflineMessageService.OfflineOperation("UPDATE","orders",orderId,payload,System.currentTimeMillis());messageService.sendOperation(operation);} catch (Exception e) {throw new RuntimeException("Failed to update order status", e);}}// 處理離線隊列中的操作 - 由定時任務觸發@Scheduled(fixedDelay = 60000) // 每分鐘執行一次public void processOfflineOperations() {if (!isNetworkAvailable()) {return; // 網絡不可用,跳過處理}int processedCount = 0;while (processedCount < 50) { // 一次處理50條,防止阻塞太久OfflineMessageService.OfflineOperation operation = messageService.receiveOperation();if (operation == null) {break; // 隊列為空}boolean success = processOperation(operation);if (!success) {// 處理失敗,重新入隊(可以考慮添加重試次數限制)messageService.sendOperation(operation);break; // 暫停處理,等待下一次調度}processedCount++;}}private boolean processOperation(OfflineMessageService.OfflineOperation operation) {try {String url = apiBaseUrl + "/" + operation.getEndpoint();if (operation.getId() != null && !operation.getType().equals("CREATE")) {url += "/" + operation.getId();}HttpMethod method;switch (operation.getType()) {case "CREATE":method = HttpMethod.POST;break;case "UPDATE":method = HttpMethod.PUT;break;case "DELETE":method = HttpMethod.DELETE;break;default:return false;}HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<String> request = operation.getType().equals("DELETE") ? new HttpEntity<>(headers) : new HttpEntity<>(operation.getPayload(), headers);ResponseEntity<String> response = restTemplate.exchange(url, method, request, String.class);return response.getStatusCode().isSuccessful();} catch (Exception e) {log.error(e.getMessage(),e);return false;}}private boolean isNetworkAvailable() {try {URL url = new URL(apiBaseUrl);HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setConnectTimeout(3000);connection.connect();return connection.getResponseCode() == 200;} catch (Exception e) {return false;}}
}
  1. 創建訂單模型
@Data
public class Order {private String id;private String customerName;private List<OrderItem> items;private BigDecimal totalAmount;private String status;private long createdAt;private Long updatedAt;
}@Data
public class OrderItem {private String productId;private String productName;private int quantity;private BigDecimal price;
}
  1. 創建Controller
@RestController
@RequestMapping("/api/orders")
public class OrderController {private final OrderService orderService;@Autowiredpublic OrderController(OrderService orderService) {this.orderService = orderService;}@PostMappingpublic ResponseEntity<String> createOrder(@RequestBody Order order) {orderService.createOrder(order);return ResponseEntity.ok("Order submitted for processing");}@PutMapping("/{id}/status")public ResponseEntity<String> updateOrderStatus(@PathVariable String id, @RequestParam String status) {orderService.updateOrderStatus(id, status);return ResponseEntity.ok("Status update submitted for processing");}@PostMapping("/process")public ResponseEntity<String> triggerProcessing() {orderService.processOfflineOperations();return ResponseEntity.ok("Processing triggered");}
}
  1. 配置文件
# API配置
api.base-url=https://api.example.com# Artemis配置
artemis.embedded.data-directory=./artemis-data
artemis.embedded.queues=offlineOperations
artemis.queue.operations=offlineOperations

優缺點分析

優點:

  • 強大的消息持久化能力,確保操作不丟失
  • 異步處理模式,非阻塞用戶操作
  • 支持大批量數據處理
  • 內置的消息重試和死信機制

缺點:

  • 資源消耗較大,尤其是內存和磁盤
  • 配置相對復雜
  • 需要處理消息冪等性問題
  • 不適合需要即時反饋的場景

適用場景

  • 批量數據處理場景,如訂單處理系統
  • 需要可靠消息處理的工作流應用
  • 高并發寫入場景
  • 對操作順序有嚴格要求的業務場景

六、方案對比與選擇建議

方案對比

方案復雜度數據容量沖突處理適用場景開發維護成本
嵌入式數據庫較復雜單機應用、結構化數據
本地緩存簡單讀多寫少、數據量小
離線優先架構完善企業應用、現場系統
嵌入式消息隊列中等批量處理、異步操作

總結

在實際應用中,可以根據項目特點選擇合適的方案,也可以結合多種方案的優點,定制最適合自己需求的離線解決方案。

無論選擇哪種方案,完善的數據同步策略和良好的用戶體驗都是成功實現離線應用的關鍵因素。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/82855.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/82855.shtml
英文地址,請注明出處:http://en.pswp.cn/web/82855.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據類型 -- 字符

在C中&#xff0c;字符型&#xff08;char&#xff09;用于存儲單個字符&#xff0c;如字母、數字、符號等。字符型是最基本的數據類型之一&#xff0c;常用于處理文本、字符數組&#xff08;字符串&#xff09;等場景。 1. 基本類型 ? char&#xff1a;標準字符類型&#x…

國標GB28181視頻平臺EasyGBS視頻實時監控系統打造換熱站全景可視化管理方案

一、方案背景? 在城市供熱體系中&#xff0c;換熱站作為連接熱源與用戶的核心樞紐&#xff0c;其運行穩定性直接影響供熱質量。面對供熱規模擴大與需求升級&#xff0c;傳統人工巡檢模式暴露出效率低、響應慢、監測不足等問題。基于GB28181協議的EasyGBS視頻實時監控系統&…

174頁PPT家居制造業集團戰略規劃和運營管控規劃方案

甲方集團需要制定一個清晰的集團價值定位&#xff0c;從“指引多元”、“塑造 能力”以及“強化協同”等方面引領甲方做大做強 集團需要通過管控模式、組織架構及職能、授權界面、關鍵流程、戰略 實施和組織演進路徑&#xff0c;平衡風險控制和迅速發展&#xff0c;保證戰略落地…

python打卡第45天

tensorboard的發展歷史和原理 一、發展歷史 起源與 TensorFlow 一同誕生 (2015年底): TensorBoard 最初是作為 TensorFlow 開源項目&#xff08;2015年11月發布&#xff09;的一部分而設計和開發的。其核心目標是解決深度學習模型訓練過程中的“黑盒”問題&#xff0c;提供直觀…

CentOS 7如何編譯安裝升級gcc至7.5版本?

CentOS 7如何編譯安裝升級gcc版本? 由于配置CentOS-SCLo-scl.repo與CentOS-SCLo-scl-rh.repo后執行yum install -y devtoolset-7安裝總是異常&#xff0c;遂決定編譯安裝gcc7.5 # 備份之前的yum .repo文件至 /tmp/repo_bak 目錄 mkdir -p /tmp/repo_bak && cd /etc…

中山大學美團港科大提出首個音頻驅動多人對話視頻生成MultiTalk,輸入一個音頻和提示,即可生成對應唇部、音頻交互視頻。

由中山大學、美團、香港科技大學聯合提出的MultiTalk是一個用于音頻驅動的多人對話視頻生成的新框架。給定一個多流音頻輸入和一個提示&#xff0c;MultiTalk 會生成一個包含提示所對應的交互的視頻&#xff0c;其唇部動作與音頻保持一致。 相關鏈接 論文&#xff1a;https://a…

iOS 門店營收表格功能的實現

iOS 門店營收表格功能實現方案 核心功能需求 數據展示&#xff1a;表格形式展示門店/日期維度的營收數據排序功能&#xff1a;支持按營收金額、增長率等排序篩選功能&#xff1a;按日期范圍/門店/區域篩選交互操作&#xff1a;點擊查看詳情、數據刷新數據可視化&#xff1a;關…

怎么解決cesium加載模型太黑,程序崩潰,不顯示,位置不對模型太大,Cesium加載gltf/glb模型后變暗

有時候咱們cesium加載模型時候型太黑&#xff0c;程序崩潰&#xff0c;不顯示&#xff0c;位置不對模型太大怎么辦 需要處理 可以聯系Q:424081801 謝謝 需要處理 可以聯系Q:424081801 謝謝

移植driver_monitoring_system里的MobileNet到RK3588

根據下面的內容寫一篇技術博客,要求增加更多的解釋,讓普通讀者也能了解為什么這樣做,具體怎么做 移植driver_monitoring_system里的MobileNet到RK3588 一、背景二、操作步驟2.1 下載源碼2.2 Tensorflow轉成ONNX2.2.1 在x86上創建容器,安裝依賴2.2.2 保存為saved-model2.2.3 sav…

低代碼平臺前端頁面表格字段綁定與后端數據傳輸交互主要有哪些方式?華為云Astro在這方面有哪些方式?

目錄 ?? 一、低代碼平臺中常見的數據綁定與交互方式 1. 接口綁定(API 調用) 2. 數據源綁定(DataSource) 3. 變量中轉(臨時變量 / 頁面狀態) 4. 數據模型綁定(模型驅動) ?? 二、華為云 Astro 輕應用的實現方式 ? 1. 數據源綁定(API服務+API網關) ? 2. 變…

《doubao-lite-32k 模型緩存機制使用指南》

doubao-lite-32k 模型緩存機制使用指南 一、緩存概述 1. 緩存作用 doubao-lite-32k 模型的緩存(Session 緩存)主要用于多輪對話場景,實現以下功能: 存儲歷史對話信息(Token),避免重復傳輸上下文,減少計算資源消耗。 優化長上下文(最長 32K Token)處理效率,提升多…

量子計算突破:新型超導芯片重構計算范式

??2024年IBM 1281量子比特超導芯片實現0.001%量子錯誤率&#xff0c;計算速度達經典超算2.5億倍??。本文解析&#xff1a; ??物理突破??&#xff1a;鉭基超導材料使量子相干時間突破??800μs??&#xff08;提升15倍&#xff09;??架構革命??&#xff1a;十字形…

云計算 Linux Rocky day03(which、快捷鍵、mount、家目錄、ls、alias、mkdir、rm、mv、cp、grep)

云計算 Linux Rocky day03&#xff08;which、快捷鍵、mount、家目錄、ls、alias、mkdir、rm、mv、cp、grep&#xff09; 目錄 云計算 Linux Rocky day03&#xff08;which、快捷鍵、mount、家目錄、ls、alias、mkdir、rm、mv、cp、grep&#xff09;1.which找到命令所對應的程序…

負載均衡LB》》HAproxy

Ubuntu 22.04 安裝HA-proxy 官網 資料 # 更新系統包列表&#xff1a; sudo apt update # 安裝 HAproxy sudo apt install haproxy -y # 驗證安裝 haproxy -v # 如下圖配置 Haproxy ##### 基于IP的訪問控制 acl ctrl_ip src 172.25.254.1 172.25.254.20 192.168.0.0/24 #…

輕創業技術方案:基于格行雙目攝像頭的代理系統設計!低成本創業項目有哪些?2025輕資產創業項目排行榜前十名!0成本創業項目推薦!格行代理項目靠譜嗎?

沒本金&#xff0c;沒資源&#xff0c;沒人脈&#xff0c;想掙錢且有持續穩定的現金流&#xff0c;只有一條路就是輕創業&#xff01;這里說個表哥的真實創業故事。 我表哥90后&#xff0c;普通農村人&#xff0c;中專畢業跟朋友一起外出打工&#xff0c;剛開始也是吃喝玩樂不…

【推薦算法】Embedding+MLP:TensorFlow實現經典深度學習推薦模型詳解

EmbeddingMLP&#xff1a;TensorFlow實現經典深度學習模型詳解 1. 算法邏輯模型結構和工作流程關鍵組件 2. 算法原理與數學推導Embedding層原理MLP前向傳播反向傳播與優化 3. 模型評估常用評估指標評估方法 4. 應用案例&#xff1a;推薦系統CTR預測問題描述模型架構性能優化 5.…

黑馬點評【基于redis實現共享session登錄】

目錄 一、基于Session實現登錄流程 1.發送驗證碼&#xff1a; 2.短信驗證碼登錄、注冊&#xff1a; 3.校驗登錄狀態: 4.session共享問題 4.1為什么會出現 Session 集群共享問題&#xff1f; 4.2常見解決方案 1. 基于 Cookie 的 Session&#xff08;客戶端存儲&#xff0…

Python讀取阿里法拍網的html+解決登錄cookie

效果圖 import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from lxml import etreedef get_taobao_auct…

【win | docker開啟遠程配置】使用 SSH 隧道訪問 Docker的前操作

在主機A pycharm如何連接遠程主機B win docker? 需要win docker配置什么&#xff1f; 快捷配置-主機B win OpenSSH SSH Server https://blog.csdn.net/z164470/article/details/121683333 winR,打開命令行&#xff0c;輸入net start sshd,啟動SSH。 或者右擊我的電腦&#…

Cursor生成Java的架構設計圖

文章目錄 整體說明一、背景二、前置條件三、生成 Promt四、結果查看五、結果編輯 摘要&#xff1a; Cursor生成Java的架構設計圖 關鍵詞&#xff1a; Cursor、人工智能 、開發工具、Java 架構設計圖 整體說明 Cursor 作為現在非常好用的開發工具&#xff0c;非常的火爆&#…