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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;非常的火爆&#…