最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot實(shí)現(xiàn)離線應(yīng)用的4種實(shí)現(xiàn)方式

 更新時(shí)間:2025年06月08日 08:45:34   作者:風(fēng)象南  
在當(dāng)今高度依賴網(wǎng)絡(luò)的環(huán)境中,離線應(yīng)用的價(jià)值日益凸顯,而且具備離線工作能力已成為許多應(yīng)用的必備特性,本文將介紹基于SpringBoot實(shí)現(xiàn)離線應(yīng)用的4種不同方式

在當(dāng)今高度依賴網(wǎng)絡(luò)的環(huán)境中,離線應(yīng)用的價(jià)值日益凸顯。

無(wú)論是在網(wǎng)絡(luò)不穩(wěn)定的區(qū)域運(yùn)行的現(xiàn)場(chǎng)系統(tǒng),還是需要在斷網(wǎng)環(huán)境下使用的企業(yè)內(nèi)部應(yīng)用,具備離線工作能力已成為許多應(yīng)用的必備特性。

本文將介紹基于SpringBoot實(shí)現(xiàn)離線應(yīng)用的5種不同方式。

一、離線應(yīng)用的概念與挑戰(zhàn)

離線應(yīng)用(Offline Application)是指能夠在網(wǎng)絡(luò)連接不可用的情況下,仍然能夠正常運(yùn)行并提供核心功能的應(yīng)用程序。

這類應(yīng)用通常具備以下特點(diǎn):

1. 本地?cái)?shù)據(jù)存儲(chǔ):能夠在本地存儲(chǔ)和讀取數(shù)據(jù)

2. 操作緩存:能夠緩存用戶操作,待網(wǎng)絡(luò)恢復(fù)后同步

3. 資源本地化:應(yīng)用資源(如靜態(tài)資源、配置等)可以在本地訪問(wèn)

4. 狀態(tài)管理:維護(hù)應(yīng)用狀態(tài),處理在線/離線切換

實(shí)現(xiàn)離線應(yīng)用面臨的主要挑戰(zhàn)包括:數(shù)據(jù)存儲(chǔ)與同步、沖突解決、用戶體驗(yàn)設(shè)計(jì)以及安全性考慮。

二、嵌入式數(shù)據(jù)庫(kù)實(shí)現(xiàn)離線數(shù)據(jù)存儲(chǔ)

原理介紹

嵌入式數(shù)據(jù)庫(kù)直接集成在應(yīng)用程序中,無(wú)需外部數(shù)據(jù)庫(kù)服務(wù)器,非常適合離線應(yīng)用場(chǎng)景。

在SpringBoot中,可以輕松集成H2、SQLite、HSQLDB等嵌入式數(shù)據(jù)庫(kù)。

實(shí)現(xiàn)步驟

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>

2. 配置文件

# 使用文件模式的H2數(shù)據(jù)庫(kù),支持持久化
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

# 自動(dòng)創(chuàng)建表結(jié)構(gòu)
spring.jpa.hibernate.ddl-auto=update

# 啟用H2控制臺(tái)(開(kāi)發(fā)環(huán)境)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

3. 創(chuàng)建實(shí)體類

@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;
    
    // 構(gòu)造函數(shù)、getter和setter
}

4. 創(chuàng)建Repository

@Repository
public interface OfflineDataRepository extends JpaRepository<OfflineData, Long> {
    List<OfflineData> findBySyncedFalse();
}

5. 創(chuàng)建Service

@Service
public class OfflineDataService {
    
    private final OfflineDataRepository repository;
    
    @Autowired
    public OfflineDataService(OfflineDataRepository repository) {
        this.repository = repository;
    }
    
    // 保存本地?cái)?shù)據(jù)
    public OfflineData saveData(String content) {
        OfflineData data = new OfflineData();
        data.setContent(content);
        data.setSynced(false);
        data.setCreatedAt(LocalDateTime.now());
        return repository.save(data);
    }
    
    // 獲取所有未同步的數(shù)據(jù)
    public List<OfflineData> getUnsyncedData() {
        return repository.findBySyncedFalse();
    }
    
    // 標(biāo)記數(shù)據(jù)為已同步
    public void markAsSynced(Long id) {
        repository.findById(id).ifPresent(data -> {
            data.setSynced(true);
            repository.save(data);
        });
    }
    
    // 當(dāng)網(wǎng)絡(luò)恢復(fù)時(shí),同步數(shù)據(jù)到遠(yuǎn)程服務(wù)器
    @Scheduled(fixedDelay = 60000) // 每分鐘檢查一次
    public void syncDataToRemote() {
        List<OfflineData> unsyncedData = getUnsyncedData();
        if (!unsyncedData.isEmpty()) {
            try {
                // 嘗試連接遠(yuǎn)程服務(wù)器
                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() {
        // 實(shí)現(xiàn)網(wǎng)絡(luò)檢測(cè)邏輯
        try {
            InetAddress address = InetAddress.getByName("api.example.com");
            return address.isReachable(3000); // 3秒超時(shí)
        } catch (Exception e) {
            return false;
        }
    }
    
    private boolean sendToRemoteServer(OfflineData data) {
        // 實(shí)現(xiàn)發(fā)送數(shù)據(jù)到遠(yuǎn)程服務(wù)器的邏輯
        // 這里使用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;
        }
    }
}

6. 創(chuàng)建Controller

@RestController
@RequestMapping("/api/data")
public class OfflineDataController {
    
    private final OfflineDataService service;
    
    @Autowired
    public OfflineDataController(OfflineDataService service) {
        this.service = service;
    }
    
    @PostMapping
    public 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");
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

  • 完全本地化的數(shù)據(jù)存儲(chǔ),無(wú)需網(wǎng)絡(luò)連接
  • 支持完整的SQL功能,可以進(jìn)行復(fù)雜查詢
  • 數(shù)據(jù)持久化到本地文件,應(yīng)用重啟不丟失

缺點(diǎn):

  • 嵌入式數(shù)據(jù)庫(kù)性能和并發(fā)處理能力有限
  • 占用本地存儲(chǔ)空間,需要注意容量管理
  • 數(shù)據(jù)同步邏輯需要自行實(shí)現(xiàn)
  • 復(fù)雜的沖突解決場(chǎng)景處理困難

適用場(chǎng)景

• 需要結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)的單機(jī)應(yīng)用

• 定期需要將數(shù)據(jù)同步到中心服務(wù)器的現(xiàn)場(chǎng)應(yīng)用

• 對(duì)數(shù)據(jù)查詢有SQL需求的離線系統(tǒng)

• 數(shù)據(jù)量適中的企業(yè)內(nèi)部工具

三、本地緩存與離線數(shù)據(jù)訪問(wèn)策略

原理介紹

本方案利用Java內(nèi)存緩存框架(如Caffeine、Ehcache)結(jié)合本地持久化存儲(chǔ),實(shí)現(xiàn)數(shù)據(jù)的本地緩存和離線訪問(wèn)。

該方案特別適合讀多寫(xiě)少的應(yīng)用場(chǎng)景。

實(shí)現(xiàn)步驟

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>

2. 配置緩存

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public Caffeine<Object, Object> caffeineConfig() {
        return Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.DAYS)
                .initialCapacity(100)
                .maximumSize(1000)
                .recordStats();
    }
    
    @Bean
    public CacheManager cacheManager(Caffeine<Object, Object> caffeine) {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }
    
    @Bean
    public CacheSerializer cacheSerializer() {
        return new CacheSerializer();
    }
}

3. 創(chuàng)建緩存序列化器

@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);
        }
    }
}

4. 創(chuàng)建離線數(shù)據(jù)服務(wù)

@Service
@Slf4j
public class ProductService {
    
    private final RestTemplate restTemplate;
    private final CacheSerializer cacheSerializer;
    
    private static final String CACHE_NAME = "products";
    
    @Autowired
    public ProductService(RestTemplate restTemplate, CacheSerializer cacheSerializer) {
        this.restTemplate = restTemplate;
        this.cacheSerializer = cacheSerializer;
        // 初始化時(shí)加載持久化的緩存
        loadCacheFromDisk();
    }
    
    @Cacheable(cacheNames = CACHE_NAME, key = "#id")
    public Product getProductById(Long id) {
        try {
            // 嘗試從遠(yuǎn)程服務(wù)獲取
            return restTemplate.getForObject("https://api.example.com/products/" + id, Product.class);
        } catch (Exception e) {
            // 網(wǎng)絡(luò)不可用時(shí),嘗試從持久化緩存獲取
            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 {
            // 嘗試從遠(yuǎn)程服務(wù)獲取
            Product[] products = restTemplate.getForObject("https://api.example.com/products", Product[].class);
            return products != null ? Arrays.asList(products) : Collections.emptyList();
        } catch (Exception e) {
            // 網(wǎng)絡(luò)不可用時(shí),返回所有持久化緩存的產(chǎn)品
            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 {
            // 嘗試保存到遠(yuǎn)程服務(wù)
            return restTemplate.postForObject("https://api.example.com/products", product, Product.class);
        } catch (Exception e) {
            // 網(wǎng)絡(luò)不可用時(shí),只保存到本地緩存
            product.setOfflineSaved(true);
            
            // 同時(shí)更新持久化緩存
            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;
        }
    }
}

5. 創(chuàng)建數(shù)據(jù)模型

@Data
public class Product implements Serializable {
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private boolean offlineSaved;
}

6. 創(chuàng)建Controller

@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    private final ProductService productService;
    
    @Autowired
    public 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();
        }
    }
    
    @GetMapping
    public ResponseEntity<List<Product>> getAllProducts() {
        return ResponseEntity.ok(productService.getAllProducts());
    }
    
    @PostMapping
    public 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");
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

  • 內(nèi)存緩存訪問(wèn)速度快,用戶體驗(yàn)好
  • 結(jié)合本地持久化,支持應(yīng)用重啟后恢復(fù)緩存
  • 適合讀多寫(xiě)少的應(yīng)用場(chǎng)景

缺點(diǎn):

  • 緩存同步和沖突解決邏輯復(fù)雜
  • 大量數(shù)據(jù)緩存會(huì)占用較多內(nèi)存
  • 不適合頻繁寫(xiě)入的場(chǎng)景
  • 緩存序列化和反序列化有性能開(kāi)銷

適用場(chǎng)景

• 產(chǎn)品目錄、知識(shí)庫(kù)等讀多寫(xiě)少的應(yīng)用

• 需要快速響應(yīng)的用戶界面

• 有限的數(shù)據(jù)集合且結(jié)構(gòu)相對(duì)固定

• 偶爾離線使用的Web應(yīng)用

四、離線優(yōu)先架構(gòu)與本地存儲(chǔ)引擎

原理介紹

離線優(yōu)先架構(gòu)(Offline-First)是一種設(shè)計(jì)理念,它將離線狀態(tài)視為應(yīng)用的默認(rèn)狀態(tài),而不是異常狀態(tài)。

在這種架構(gòu)中,數(shù)據(jù)首先存儲(chǔ)在本地,然后在條件允許時(shí)同步到服務(wù)器。

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

實(shí)現(xiàn)步驟

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>

2. 創(chuàng)建LevelDB存儲(chǔ)服務(wù)

@Component
public class LevelDBStore implements InitializingBean, DisposableBean {
    
    private DB db;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final File dbDir = new File("./leveldb");
    
    @Override
    public void afterPropertiesSet() throws Exception {
        Options options = new Options();
        options.createIfMissing(true);
        db = factory.open(dbDir, options);
    }
    
    @Override
    public 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);
    }
}

3. 創(chuàng)建離線同步管理器

@Component
public class SyncManager {
    
    private final LevelDBStore store;
    private final RestTemplate restTemplate;
    
    @Value("${sync.server.url}")
    private String syncServerUrl;
    
    @Autowired
    public 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);
        
        // 按時(shí)間戳排序,確保按操作順序同步
        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;
            }
            
            // 構(gòu)建請(qǐng)求URL
            String url = syncServerUrl + "/" + operation.getId();
            if ("DELETE".equals(operation.getType())) {
                // DELETE請(qǐng)求通常不需要請(qǐng)求體
                ResponseEntity<Void> response = restTemplate.exchange(
                    url, method, null, Void.class
                );
                return response.getStatusCode().is2xxSuccessful();
            } else {
                // POST和PUT請(qǐng)求需要請(qǐng)求體
                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
    @AllArgsConstructor
    private static class OfflineOperation<T> {
        private String operationId;
        private String type; // CREATE, UPDATE, DELETE
        private String id;
        private T data;
        private long timestamp;
    }
}

4. 創(chuàng)建任務(wù)服務(wù)

@Service
public class TaskService {
    
    private final LevelDBStore store;
    private final SyncManager syncManager;
    
    @Autowired
    public 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) {
        // 生成ID
        if (task.getId() == null) {
            task.setId(UUID.randomUUID().toString());
        }
        
        // 設(shè)置時(shí)間戳
        task.setCreatedAt(System.currentTimeMillis());
        task.setUpdatedAt(System.currentTimeMillis());
        
        // 保存到本地存儲(chǔ)
        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());
        
        // 保存到本地存儲(chǔ)
        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;
        }
        
        // 從本地存儲(chǔ)刪除
        boolean deleted = store.delete("task:" + id);
        
        // 記錄離線操作,等待同步
        if (deleted) {
            syncManager.saveOperation("DELETE", id, null);
        }
        
        return deleted;
    }
}

5. 創(chuàng)建任務(wù)模型

@Data
public class Task {
    private String id;
    private String title;
    private String description;
    private boolean completed;
    private long createdAt;
    private long updatedAt;
}

6. 創(chuàng)建Controller

@RestController
@RequestMapping("/api/tasks")
public class TaskController {
    
    private final TaskService taskService;
    
    @Autowired
    public 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);
    }
    
    @GetMapping
    public ResponseEntity<List<Task>> getAllTasks() {
        return ResponseEntity.ok(taskService.getAllTasks());
    }
    
    @PostMapping
    public 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");
    }
}

7. 配置文件

# 同步服務(wù)器地址
sync.server.url=https://api.example.com/tasks

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

  • 離線優(yōu)先設(shè)計(jì),保證應(yīng)用在任何網(wǎng)絡(luò)狀態(tài)下可用
  • 高性能的本地存儲(chǔ)引擎,適合大量數(shù)據(jù)
  • 支持完整的CRUD操作和離線同步
  • 細(xì)粒度的操作跟蹤,便于解決沖突

缺點(diǎn):

  • 實(shí)現(xiàn)復(fù)雜度較高
  • 同步策略需要根據(jù)業(yè)務(wù)場(chǎng)景定制
  • 不支持復(fù)雜的關(guān)系型查詢

適用場(chǎng)景

• 需要全面離線支持的企業(yè)應(yīng)用

• 現(xiàn)場(chǎng)操作類系統(tǒng),如倉(cāng)庫(kù)管理、物流系統(tǒng)

• 數(shù)據(jù)量較大的離線應(yīng)用

• 需要嚴(yán)格保證離線和在線數(shù)據(jù)一致性的場(chǎng)景

五、嵌入式消息隊(duì)列與異步處理

原理介紹

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

操作被發(fā)送到本地隊(duì)列,在網(wǎng)絡(luò)恢復(fù)后批量處理。

實(shí)現(xiàn)步驟

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>

2. 配置嵌入式Artemis

@Configuration
@Slf4j
public class ArtemisConfig {
    
    @Value("${artemis.embedded.data-directory:./artemis-data}")
    private String dataDirectory;
    
    @Value("${artemis.embedded.queues:offlineOperations}")
    private String queues;
    
    @Bean
    public 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();
        
        // 創(chuàng)建隊(duì)列
        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;
    }
    
    @Bean
    public ConnectionFactory connectionFactory() {
        return new ActiveMQConnectionFactory("vm://0");
    }
    
    @Bean
    public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
        JmsTemplate template = new JmsTemplate(connectionFactory);
        template.setDeliveryPersistent(true);
        return template;
    }
}

3. 創(chuàng)建離線操作消息服務(wù)

@Service
public class OfflineMessageService {
    
    private final JmsTemplate jmsTemplate;
    private final ObjectMapper objectMapper;
    
    @Value("${artemis.queue.operations:offlineOperations}")
    private String operationsQueue;
    
    @Autowired
    public 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
    @NoArgsConstructor
    public static class OfflineOperation {
        private String type;      // CREATE, UPDATE, DELETE
        private String endpoint;  // API endpoint
        private String id;        // resource id
        private String payload;   // JSON payload
        private long timestamp;
    }
}

4. 創(chuàng)建離線操作處理服務(wù)

@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;
    
    @Autowired
    public OrderService(OfflineMessageService messageService, RestTemplate restTemplate) {
        this.messageService = messageService;
        this.restTemplate = restTemplate;
    }
    
    // 創(chuàng)建訂單 - 直接進(jìn)入離線隊(duì)列
    public void createOrder(Order order) {
        try {
            // 生成ID
            if (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);
        }
    }
    
    // 更新訂單狀態(tài) - 直接進(jìn)入離線隊(duì)列
    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);
        }
    }
    
    // 處理離線隊(duì)列中的操作 - 由定時(shí)任務(wù)觸發(fā)
    @Scheduled(fixedDelay = 60000) // 每分鐘執(zhí)行一次
    public void processOfflineOperations() {
        if (!isNetworkAvailable()) {
            return; // 網(wǎng)絡(luò)不可用,跳過(guò)處理
        }
        
        int processedCount = 0;
        while (processedCount < 50) { // 一次處理50條,防止阻塞太久
            OfflineMessageService.OfflineOperation operation = messageService.receiveOperation();
            if (operation == null) {
                break; // 隊(duì)列為空
            }
            
            boolean success = processOperation(operation);
            if (!success) {
                // 處理失敗,重新入隊(duì)(可以考慮添加重試次數(shù)限制)
                messageService.sendOperation(operation);
                break; // 暫停處理,等待下一次調(diào)度
            }
            
            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;
        }
    }
}

5. 創(chuàng)建訂單模型

@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;
}

6. 創(chuàng)建Controller

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    private final OrderService orderService;
    
    @Autowired
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }
    
    @PostMapping
    public 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");
    }
}

7. 配置文件

# API配置
api.base-url=https://api.example.com

# Artemis配置
artemis.embedded.data-directory=./artemis-data
artemis.embedded.queues=offlineOperations
artemis.queue.operations=offlineOperations

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

  • 強(qiáng)大的消息持久化能力,確保操作不丟失
  • 異步處理模式,非阻塞用戶操作
  • 支持大批量數(shù)據(jù)處理
  • 內(nèi)置的消息重試和死信機(jī)制

缺點(diǎn):

  • 資源消耗較大,尤其是內(nèi)存和磁盤
  • 配置相對(duì)復(fù)雜
  • 需要處理消息冪等性問(wèn)題
  • 不適合需要即時(shí)反饋的場(chǎng)景

適用場(chǎng)景

• 批量數(shù)據(jù)處理場(chǎng)景,如訂單處理系統(tǒng)

• 需要可靠消息處理的工作流應(yīng)用

• 高并發(fā)寫(xiě)入場(chǎng)景

• 對(duì)操作順序有嚴(yán)格要求的業(yè)務(wù)場(chǎng)景

六、方案對(duì)比

方案復(fù)雜度數(shù)據(jù)容量沖突處理適用場(chǎng)景開(kāi)發(fā)維護(hù)成本
嵌入式數(shù)據(jù)庫(kù)較復(fù)雜單機(jī)應(yīng)用、結(jié)構(gòu)化數(shù)據(jù)
本地緩存簡(jiǎn)單讀多寫(xiě)少、數(shù)據(jù)量小
離線優(yōu)先架構(gòu)完善企業(yè)應(yīng)用、現(xiàn)場(chǎng)系統(tǒng)
嵌入式消息隊(duì)列中等批量處理、異步操作

總結(jié)

在實(shí)際應(yīng)用中,可以根據(jù)項(xiàng)目特點(diǎn)選擇合適的方案,也可以結(jié)合多種方案的優(yōu)點(diǎn),定制最適合自己需求的離線解決方案。

無(wú)論選擇哪種方案,完善的數(shù)據(jù)同步策略和良好的用戶體驗(yàn)都是成功實(shí)現(xiàn)離線應(yīng)用的關(guān)鍵因素。

以上就是基于SpringBoot實(shí)現(xiàn)離線應(yīng)用的4種實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot離線應(yīng)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java wagon如何打包文件到不同服務(wù)器

    java wagon如何打包文件到不同服務(wù)器

    這篇文章主要介紹了java wagon如何打包文件到不同服務(wù)器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-06-06
  • Java加速讀取復(fù)制超大文件

    Java加速讀取復(fù)制超大文件

    這篇文章主要為大家詳細(xì)介紹了Java加速讀取復(fù)制超大文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • 詳解springmvc常用5種注解

    詳解springmvc常用5種注解

    在本篇里我們給大家總結(jié)了關(guān)于springmvc常用5種注解相關(guān)知識(shí)點(diǎn)以及實(shí)例代碼,需要的朋友們參考下。
    2019-07-07
  • 利用JavaFX工具構(gòu)建Reactive系統(tǒng)

    利用JavaFX工具構(gòu)建Reactive系統(tǒng)

    這篇文章主要介紹了使用JavaFX構(gòu)建Reactive系統(tǒng),利用JavaFX工具集中的新的超棒特性來(lái)構(gòu)建響應(yīng)式的快速應(yīng)用程序,感興趣的小伙伴們可以參考一下
    2016-02-02
  • Java中LinkedList真的是查找慢增刪快

    Java中LinkedList真的是查找慢增刪快

    這篇文章主要介紹了Java中LinkedList真的是查找慢增刪快,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Java8新特性之Collectors.joining()實(shí)例詳解

    Java8新特性之Collectors.joining()實(shí)例詳解

    在項(xiàng)目中我們常常要對(duì)list集合的數(shù)據(jù)做一些字符串拼接/處理等相關(guān)操作,下面這篇文章主要給大家介紹了關(guān)于Java8新特性之Collectors.joining()的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • maven下mybatis-plus和pagehelp沖突問(wèn)題的解決方法

    maven下mybatis-plus和pagehelp沖突問(wèn)題的解決方法

    這篇文章主要介紹了maven下mybatis-plus和pagehelp沖突的解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Springboot啟動(dòng)執(zhí)行特定代碼的方式匯總

    Springboot啟動(dòng)執(zhí)行特定代碼的方式匯總

    這篇文章主要介紹了Springboot啟動(dòng)執(zhí)行特定代碼的幾種方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • 深度優(yōu)先與廣度優(yōu)先Java實(shí)現(xiàn)代碼示例

    深度優(yōu)先與廣度優(yōu)先Java實(shí)現(xiàn)代碼示例

    這篇文章主要介紹了深度優(yōu)先與廣度優(yōu)先Java實(shí)現(xiàn)代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • Spring Boot + MyBatisPlus快速實(shí)現(xiàn)單表CRUD的示例

    Spring Boot + MyBatisPlus快速實(shí)現(xiàn)單表CRUD的示例

    本文主要介紹了Spring Boot + MyBatisPlus快速實(shí)現(xiàn)單表CRUD的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2026-06-06

最新評(píng)論

灵石县| 拜泉县| 通江县| 定襄县| 诸城市| 云梦县| 绥滨县| 高雄市| 库尔勒市| 普陀区| 河北区| 剑川县| 新泰市| 苏州市| 德钦县| 丹阳市| 长宁县| 迁西县| 呼玛县| 遂宁市| 长子县| 上饶县| 桃园市| 定西市| 高邮市| 盈江县| 平武县| 衡南县| 济宁市| 合山市| 双流县| 上蔡县| 泰宁县| 清水河县| 宜昌市| 泰和县| 盐亭县| 龙南县| 屏东市| 崇阳县| 吕梁市|