mirror of
https://github.com/tiennm99/store-scraper-bot-java.git
synced 2026-05-17 13:53:50 +00:00
Migrate from Redis to Couchbase for data storage
Replaces Redis with Couchbase as the primary data store. Updates environment variables, dependencies, and Docker Compose configuration to use Couchbase. Refactors repository and model classes to use Couchbase APIs, removes Redis utility and related code, and adds Couchbase utility for collection management. Updates AdminRepository to use 'admin' as the key instead of an empty string.
This commit is contained in:
+5
-2
@@ -1,8 +1,11 @@
|
||||
# Copy this file to .env and customize the values for your environment
|
||||
# cp .env.example .env
|
||||
|
||||
# Redis configuration
|
||||
REDIS_URL=localhost:6379
|
||||
# Couchbase configuration
|
||||
COUCHBASE_CONNECTION_STRING=couchbase://localhost
|
||||
COUCHBASE_USERNAME=admin
|
||||
COUCHBASE_PASSWORD=your_password_here
|
||||
COUCHBASE_BUCKET_NAME=store_scraper
|
||||
|
||||
# Telegram Bot configuration
|
||||
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
|
||||
|
||||
+1
-2
@@ -22,15 +22,14 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.36")
|
||||
implementation("com.couchbase.client:java-client:3.4.11")
|
||||
implementation("com.google.code.gson:gson:2.11.0")
|
||||
implementation("com.google.guava:guava:33.4.0-jre")
|
||||
implementation("org.apache.commons:commons-text:1.13.0")
|
||||
implementation("org.apache.logging.log4j:log4j-core:2.24.3")
|
||||
implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3")
|
||||
implementation("org.telegram:telegrambots-client:8.0.0")
|
||||
implementation("org.telegram:telegrambots-extensions:8.0.0")
|
||||
implementation("org.telegram:telegrambots-longpolling:8.0.0")
|
||||
implementation("redis.clients:jedis:5.2.0")
|
||||
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.36")
|
||||
testImplementation(platform("org.junit:junit-bom:5.11.4"))
|
||||
|
||||
+12
-6
@@ -1,12 +1,18 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis:7.2-alpine
|
||||
couchbase:
|
||||
image: couchbase:community-7.6.2
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "6379:6379"
|
||||
- "8091-8097:8091-8097"
|
||||
- "9123:9123"
|
||||
- "11207:11207"
|
||||
- "11210:11210"
|
||||
- "11280:11280"
|
||||
- "18091-18097:18091-18097"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
- couchbase_data:/opt/couchbase/var
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
couchbase_data:
|
||||
|
||||
+1
-1
@@ -3,5 +3,5 @@ services:
|
||||
build:
|
||||
context: .
|
||||
|
||||
# If you need redis, add redis service
|
||||
# If you need database, add database service
|
||||
# Check compose.dev.yml for example
|
||||
|
||||
@@ -8,7 +8,11 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Config {
|
||||
public static final String REDIS_URL = System.getenv("REDIS_URL");
|
||||
public static final String COUCHBASE_CONNECTION_STRING =
|
||||
System.getenv("COUCHBASE_CONNECTION_STRING");
|
||||
public static final String COUCHBASE_USERNAME = System.getenv("COUCHBASE_USERNAME");
|
||||
public static final String COUCHBASE_PASSWORD = System.getenv("COUCHBASE_PASSWORD");
|
||||
public static final String COUCHBASE_BUCKET_NAME = System.getenv("COUCHBASE_BUCKET_NAME");
|
||||
|
||||
public static final String TELEGRAM_BOT_TOKEN = System.getenv("TELEGRAM_BOT_TOKEN");
|
||||
public static final String TELEGRAM_BOT_USERNAME = System.getenv("TELEGRAM_BOT_USERNAME");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.miti99.storescraperbot.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
@@ -11,6 +11,6 @@ import lombok.Setter;
|
||||
public abstract class AbstractModel<K> {
|
||||
protected K key;
|
||||
|
||||
@SerializedName("class")
|
||||
@JsonProperty("class")
|
||||
protected String clazz = getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
package com.miti99.storescraperbot.repository;
|
||||
|
||||
import com.couchbase.client.java.Collection;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.miti99.storescraperbot.config.Config;
|
||||
import com.miti99.storescraperbot.constant.Constant;
|
||||
import com.miti99.storescraperbot.model.AbstractModel;
|
||||
import com.miti99.storescraperbot.util.GsonUtil;
|
||||
import com.miti99.storescraperbot.util.RedisUtil;
|
||||
import com.miti99.storescraperbot.util.CouchbaseUtil;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.time.Duration;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import redis.clients.jedis.params.SetParams;
|
||||
|
||||
@Log4j2
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
public static final String SEPARATOR = ":";
|
||||
public static final String SEPARATOR = "_";
|
||||
// protected final Class<K> classK = getKeyClass();
|
||||
protected final Class<V> classV = getDataClass();
|
||||
protected final String prefix =
|
||||
String.join(
|
||||
SEPARATOR,
|
||||
Constant.APP_NAME,
|
||||
Config.ENV.name().toLowerCase(),
|
||||
CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, classV.getSimpleName()));
|
||||
protected final String scopeName = Config.ENV.name().toLowerCase();
|
||||
protected final String collectionName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, classV.getSimpleName());
|
||||
|
||||
protected AbstractRepository() {
|
||||
CouchbaseUtil.createCollection(scopeName, collectionName);
|
||||
}
|
||||
|
||||
// protected Class<K> getKeyClass() {
|
||||
// return (Class<K>)
|
||||
@@ -42,8 +39,12 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
|
||||
}
|
||||
|
||||
protected Collection collection() {
|
||||
return CouchbaseUtil.BUCKET.scope(scopeName).collection(collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return expire seconds. <= 0 mean never expire.
|
||||
* @return expire seconds. 0 mean never expire.
|
||||
*/
|
||||
protected long getExpireSeconds() {
|
||||
return 0;
|
||||
@@ -63,17 +64,18 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
}
|
||||
|
||||
protected String getDatabaseKey(K key) {
|
||||
return String.join(SEPARATOR, prefix, String.valueOf(key));
|
||||
return String.valueOf(key);
|
||||
}
|
||||
|
||||
public void save(K key, V data) {
|
||||
var databaseKey = getDatabaseKey(key);
|
||||
try (var jedis = RedisUtil.getJedis()) {
|
||||
var json = GsonUtil.toJson(data);
|
||||
if (getExpireSeconds() <= 0) {
|
||||
jedis.set(databaseKey, json);
|
||||
try {
|
||||
if (getExpireSeconds() == 0) {
|
||||
collection().upsert(databaseKey, data);
|
||||
} else {
|
||||
jedis.set(databaseKey, json, SetParams.setParams().ex(getExpireSeconds()));
|
||||
var upsertOptions =
|
||||
UpsertOptions.upsertOptions().expiry(Duration.ofSeconds(getExpireSeconds()));
|
||||
collection().upsert(databaseKey, data, upsertOptions);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("save error - key {}, databaseKey {}", key, databaseKey, e);
|
||||
@@ -82,8 +84,8 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
|
||||
public boolean exist(K key) {
|
||||
var databaseKey = getDatabaseKey(key);
|
||||
try (var jedis = RedisUtil.getJedis()) {
|
||||
return jedis.exists(databaseKey);
|
||||
try {
|
||||
return collection().exists(databaseKey).exists();
|
||||
} catch (Exception e) {
|
||||
log.error("exist error - key {}, databaseKey {}", key, databaseKey, e);
|
||||
return false;
|
||||
@@ -92,9 +94,12 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
|
||||
public V load(K key) {
|
||||
var databaseKey = getDatabaseKey(key);
|
||||
try (var jedis = RedisUtil.getJedis()) {
|
||||
var json = jedis.get(databaseKey);
|
||||
return GsonUtil.fromJson(json, classV);
|
||||
try {
|
||||
var getResult = collection().get(databaseKey);
|
||||
if (getResult == null) {
|
||||
return null;
|
||||
}
|
||||
return getResult.contentAs(classV);
|
||||
} catch (Exception e) {
|
||||
log.error("load error - key {}, databaseKey {}", key, databaseKey, e);
|
||||
return null;
|
||||
@@ -103,8 +108,8 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
|
||||
public void delete(K key) {
|
||||
var databaseKey = getDatabaseKey(key);
|
||||
try (var jedis = RedisUtil.getJedis()) {
|
||||
jedis.del(databaseKey);
|
||||
try {
|
||||
collection().remove(databaseKey);
|
||||
} catch (Exception e) {
|
||||
log.error("delete error", e);
|
||||
}
|
||||
|
||||
@@ -2,23 +2,19 @@ package com.miti99.storescraperbot.repository;
|
||||
|
||||
import com.miti99.storescraperbot.model.Admin;
|
||||
|
||||
/** Đây là repository chỉ chứa 1 key duy nhất, key là "" (rỗng) */
|
||||
/** Đây là repository chỉ chứa 1 key duy nhất, key là "admin" */
|
||||
public class AdminRepository extends AbstractRepository<String, Admin> {
|
||||
public static final AdminRepository INSTANCE = new AdminRepository();
|
||||
|
||||
protected AdminRepository() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
init("");
|
||||
init("admin");
|
||||
}
|
||||
|
||||
public Admin load() {
|
||||
return load("");
|
||||
return load("admin");
|
||||
}
|
||||
|
||||
public void save(Admin data) {
|
||||
save("", data);
|
||||
save("admin", data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.miti99.storescraperbot.util;
|
||||
|
||||
import static com.miti99.storescraperbot.config.Config.COUCHBASE_BUCKET_NAME;
|
||||
import static com.miti99.storescraperbot.config.Config.COUCHBASE_CONNECTION_STRING;
|
||||
import static com.miti99.storescraperbot.config.Config.COUCHBASE_PASSWORD;
|
||||
import static com.miti99.storescraperbot.config.Config.COUCHBASE_USERNAME;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.ClusterOptions;
|
||||
import com.couchbase.client.java.manager.collection.CollectionSpec;
|
||||
import java.time.Duration;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Log4j2
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class CouchbaseUtil {
|
||||
public static final Cluster CLUSTER;
|
||||
public static final Bucket BUCKET;
|
||||
|
||||
static {
|
||||
CLUSTER =
|
||||
Cluster.connect(
|
||||
COUCHBASE_CONNECTION_STRING,
|
||||
ClusterOptions.clusterOptions(COUCHBASE_USERNAME, COUCHBASE_PASSWORD)
|
||||
.environment(env -> {}));
|
||||
|
||||
BUCKET = CLUSTER.bucket(COUCHBASE_BUCKET_NAME);
|
||||
BUCKET.waitUntilReady(Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
public static void createScope(String scopeName) {
|
||||
var collectionManager = BUCKET.collections();
|
||||
try {
|
||||
boolean scopeExists =
|
||||
collectionManager.getAllScopes().stream().anyMatch(s -> s.name().equals(scopeName));
|
||||
|
||||
if (!scopeExists) {
|
||||
collectionManager.createScope(scopeName);
|
||||
log.info("Scope created: {}", scopeName);
|
||||
} else {
|
||||
log.info("Scope existed: {}", scopeName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("createScope error - scopeName: '{}'", scopeName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createCollection(String scopeName, String collectionName) {
|
||||
var collectionManager = BUCKET.collections();
|
||||
try {
|
||||
var scopeSpecOpt =
|
||||
collectionManager.getAllScopes().stream()
|
||||
.filter(s -> s.name().equals(scopeName))
|
||||
.findFirst();
|
||||
if (scopeSpecOpt.isEmpty()) {
|
||||
createScope(scopeName);
|
||||
createCollection(scopeName, collectionName);
|
||||
return;
|
||||
}
|
||||
|
||||
var scopeSpec = scopeSpecOpt.get();
|
||||
boolean collectionExists =
|
||||
scopeSpec.collections().stream().anyMatch(c -> c.name().equals(collectionName));
|
||||
|
||||
if (!collectionExists) {
|
||||
var spec = CollectionSpec.create(collectionName, scopeName);
|
||||
collectionManager.createCollection(spec);
|
||||
log.info("Collection created: {} in {}", collectionName, scopeName);
|
||||
} else {
|
||||
log.info("Collection existed: {} in {}", collectionName, scopeName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"createCollection error - collectionName: '{}', scopeName: '{}'",
|
||||
collectionName,
|
||||
scopeName,
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.miti99.storescraperbot.util;
|
||||
|
||||
import static com.miti99.storescraperbot.config.Config.REDIS_URL;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class RedisUtil {
|
||||
private static final JedisPool REDIS_POOL = new JedisPool(REDIS_URL);
|
||||
|
||||
public static Jedis getJedis() {
|
||||
return REDIS_POOL.getResource();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user