diff --git a/.gitignore b/.gitignore index 081d63d..0f2da04 100755 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ build/ /han-note-oss/han-note-oss-biz/src/main/resources/application-dev.yml /han-note-user/han-note-user-biz/src/main/resources/application-dev.yml /han-note-user/han-note-user-biz/logs/ +/han-note-kv/han-note-kv-biz/src/main/resources/application-dev.yml +/han-note-kv/han-note-kv-biz/src/main/resources/application-prod.yml +/han-note-kv/han-note-kv-biz/logs/ diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 25b2c58..dbc0060 100755 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -3,8 +3,20 @@ + + + + + + + + + + + + diff --git a/han-note-distributed-id-generator/han-note-distributed-id-generator-api/pom.xml b/han-note-distributed-id-generator/han-note-distributed-id-generator-api/pom.xml new file mode 100644 index 0000000..23996ee --- /dev/null +++ b/han-note-distributed-id-generator/han-note-distributed-id-generator-api/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + + com.hanserwei + han-note-distributed-id-generator + ${revision} + + + + jar + + han-note-distributed-id-generator-api + ${project.artifactId} + RPC层, 供其他服务调用 + + + + com.hanserwei + hanserwei-common + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/pom.xml b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/pom.xml new file mode 100644 index 0000000..44262d5 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/pom.xml @@ -0,0 +1,136 @@ + + 4.0.0 + + + com.hanserwei + han-note-distributed-id-generator + ${revision} + + + + jar + + hannote-distributed-id-generator-biz + ${project.artifactId} + 分布式 ID 生成业务层 + + 2.4 + 0.9.16 + 1.0.18 + 3.3.0 + 2.6.0 + 3.6.0 + + + + + com.hanserwei + hanserwei-common + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + commons-io + commons-io + ${common-io.version} + + + + org.perf4j + perf4j + ${perf4j.version} + + + mysql + mysql-connector-java + 8.0.29 + + + com.alibaba + druid + ${druid.version} + + + + org.mybatis + mybatis + ${mybatis.version} + + + + + org.apache.curator + curator-recipes + ${curator-recipes.version} + + + + log4j + log4j + + + org.slf4j + slf4j-reload4j + + + org.slf4j + slf4j-api + + + org.apache.zookeeper + zookeeper + + + + + org.apache.zookeeper + zookeeper + ${zookeeper.version} + + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/HannoteDistributedIdGeneratorBizApplication.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/HannoteDistributedIdGeneratorBizApplication.java new file mode 100644 index 0000000..d9bf603 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/HannoteDistributedIdGeneratorBizApplication.java @@ -0,0 +1,11 @@ +package com.hanserwei.hannote.distributed.id.generator.biz; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class HannoteDistributedIdGeneratorBizApplication { + public static void main(String[] args) { + SpringApplication.run(HannoteDistributedIdGeneratorBizApplication.class, args); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/constant/Constants.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/constant/Constants.java new file mode 100644 index 0000000..811c6c1 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/constant/Constants.java @@ -0,0 +1,12 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.constant; + +public class Constants { + public static final String LEAF_SEGMENT_ENABLE = "leaf.segment.enable"; + public static final String LEAF_JDBC_URL = "leaf.jdbc.url"; + public static final String LEAF_JDBC_USERNAME = "leaf.jdbc.username"; + public static final String LEAF_JDBC_PASSWORD = "leaf.jdbc.password"; + + public static final String LEAF_SNOWFLAKE_ENABLE = "leaf.snowflake.enable"; + public static final String LEAF_SNOWFLAKE_PORT = "leaf.snowflake.port"; + public static final String LEAF_SNOWFLAKE_ZK_ADDRESS = "leaf.snowflake.zk.address"; +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/controller/LeafController.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/controller/LeafController.java new file mode 100644 index 0000000..fa2ef1a --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/controller/LeafController.java @@ -0,0 +1,47 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.controller; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Result; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Status; +import com.hanserwei.hannote.distributed.id.generator.biz.exception.LeafServerException; +import com.hanserwei.hannote.distributed.id.generator.biz.exception.NoKeyException; +import com.hanserwei.hannote.distributed.id.generator.biz.service.SegmentService; +import com.hanserwei.hannote.distributed.id.generator.biz.service.SnowflakeService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/id") +@Slf4j +public class LeafController { + + @Resource + private SegmentService segmentService; + @Resource + private SnowflakeService snowflakeService; + + @RequestMapping(value = "/segment/get/{key}") + public String getSegmentId(@PathVariable("key") String key) { + return get(key, segmentService.getId(key)); + } + + @RequestMapping(value = "/snowflake/get/{key}") + public String getSnowflakeId(@PathVariable("key") String key) { + return get(key, snowflakeService.getId(key)); + } + + private String get(@PathVariable("key") String key, Result id) { + Result result; + if (key == null || key.isEmpty()) { + throw new NoKeyException(); + } + result = id; + if (result.getStatus().equals(Status.EXCEPTION)) { + throw new LeafServerException(result.toString()); + } + return String.valueOf(result.getId()); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/controller/LeafMonitorController.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/controller/LeafMonitorController.java new file mode 100644 index 0000000..4da363d --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/controller/LeafMonitorController.java @@ -0,0 +1,104 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.controller; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.SegmentIDGenImpl; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.LeafAlloc; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.SegmentBuffer; +import com.hanserwei.hannote.distributed.id.generator.biz.model.SegmentBufferView; +import com.hanserwei.hannote.distributed.id.generator.biz.service.SegmentService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Controller +public class LeafMonitorController { + private Logger logger = LoggerFactory.getLogger(LeafMonitorController.class); + + @Autowired + private SegmentService segmentService; + + @RequestMapping(value = "cache") + public String getCache(Model model) { + Map data = new HashMap<>(); + SegmentIDGenImpl segmentIDGen = segmentService.getIdGen(); + if (segmentIDGen == null) { + throw new IllegalArgumentException("You should config leaf.segment.enable=true first"); + } + Map cache = segmentIDGen.getCache(); + for (Map.Entry entry : cache.entrySet()) { + SegmentBufferView sv = new SegmentBufferView(); + SegmentBuffer buffer = entry.getValue(); + sv.setInitOk(buffer.isInitOk()); + sv.setKey(buffer.getKey()); + sv.setPos(buffer.getCurrentPos()); + sv.setNextReady(buffer.isNextReady()); + sv.setMax0(buffer.getSegments()[0].getMax()); + sv.setValue0(buffer.getSegments()[0].getValue().get()); + sv.setStep0(buffer.getSegments()[0].getStep()); + + sv.setMax1(buffer.getSegments()[1].getMax()); + sv.setValue1(buffer.getSegments()[1].getValue().get()); + sv.setStep1(buffer.getSegments()[1].getStep()); + + data.put(entry.getKey(), sv); + + } + logger.info("Cache info {}", data); + model.addAttribute("data", data); + return "segment"; + } + + @RequestMapping(value = "db") + public String getDb(Model model) { + SegmentIDGenImpl segmentIDGen = segmentService.getIdGen(); + if (segmentIDGen == null) { + throw new IllegalArgumentException("You should config leaf.segment.enable=true first"); + } + List items = segmentIDGen.getAllLeafAllocs(); + logger.info("DB info {}", items); + model.addAttribute("items", items); + return "db"; + } + + /** + * the output is like this: + * { + * "timestamp": "1567733700834(2019-09-06 09:35:00.834)", + * "sequenceId": "3448", + * "workerId": "39" + * } + */ + @RequestMapping(value = "decodeSnowflakeId") + @ResponseBody + public Map decodeSnowflakeId(@RequestParam("snowflakeId") String snowflakeIdStr) { + Map map = new HashMap<>(); + try { + long snowflakeId = Long.parseLong(snowflakeIdStr); + + long originTimestamp = (snowflakeId >> 22) + 1288834974657L; + Date date = new Date(originTimestamp); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + map.put("timestamp", String.valueOf(originTimestamp) + "(" + sdf.format(date) + ")"); + + long workerId = (snowflakeId >> 12) ^ (snowflakeId >> 22 << 10); + map.put("workerId", String.valueOf(workerId)); + + long sequence = snowflakeId ^ (snowflakeId >> 12 << 12); + map.put("sequenceId", String.valueOf(sequence)); + } catch (NumberFormatException e) { + map.put("errorMsg", "snowflake Id反解析发生异常!"); + } + return map; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/IDGen.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/IDGen.java new file mode 100644 index 0000000..a6ec404 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/IDGen.java @@ -0,0 +1,9 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Result; + +public interface IDGen { + Result get(String key); + boolean init(); +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/CheckVO.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/CheckVO.java new file mode 100644 index 0000000..21f7209 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/CheckVO.java @@ -0,0 +1,27 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.common; + +public class CheckVO { + private long timestamp; + private int workID; + + public CheckVO(long timestamp, int workID) { + this.timestamp = timestamp; + this.workID = workID; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public int getWorkID() { + return workID; + } + + public void setWorkID(int workID) { + this.workID = workID; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/PropertyFactory.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/PropertyFactory.java new file mode 100644 index 0000000..d3e5526 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/PropertyFactory.java @@ -0,0 +1,22 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Properties; + +public class PropertyFactory { + private static final Logger logger = LoggerFactory.getLogger(PropertyFactory.class); + private static final Properties prop = new Properties(); + static { + try { + prop.load(PropertyFactory.class.getClassLoader().getResourceAsStream("leaf.properties")); + } catch (IOException e) { + logger.warn("Load Properties Ex", e); + } + } + public static Properties getProperties() { + return prop; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Result.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Result.java new file mode 100644 index 0000000..9485370 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Result.java @@ -0,0 +1,39 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.common; + +public class Result { + private long id; + private Status status; + + public Result() { + + } + public Result(long id, Status status) { + this.id = id; + this.status = status; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Result{"); + sb.append("id=").append(id); + sb.append(", status=").append(status); + sb.append('}'); + return sb.toString(); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Status.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Status.java new file mode 100644 index 0000000..f53ec02 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Status.java @@ -0,0 +1,6 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.common; + +public enum Status { + SUCCESS, + EXCEPTION +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Utils.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Utils.java new file mode 100644 index 0000000..53156a5 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/Utils.java @@ -0,0 +1,75 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +public class Utils { + private static final Logger logger = LoggerFactory.getLogger(Utils.class); + + public static String getIp() { + String ip; + try { + List ipList = getHostAddress(null); + // default the first + ip = (!ipList.isEmpty()) ? ipList.get(0) : ""; + } catch (Exception ex) { + ip = ""; + logger.warn("Utils get IP warn", ex); + } + return ip; + } + + public static String getIp(String interfaceName) { + String ip; + interfaceName = interfaceName.trim(); + try { + List ipList = getHostAddress(interfaceName); + ip = (!ipList.isEmpty()) ? ipList.get(0) : ""; + } catch (Exception ex) { + ip = ""; + logger.warn("Utils get IP warn", ex); + } + return ip; + } + + /** + * 获取已激活网卡的IP地址 + * + * @param interfaceName 可指定网卡名称,null则获取全部 + * @return List + */ + private static List getHostAddress(String interfaceName) throws SocketException { + List ipList = new ArrayList(5); + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface ni = interfaces.nextElement(); + Enumeration allAddress = ni.getInetAddresses(); + while (allAddress.hasMoreElements()) { + InetAddress address = allAddress.nextElement(); + if (address.isLoopbackAddress()) { + // skip the loopback addr + continue; + } + if (address instanceof Inet6Address) { + // skip the IPv6 addr + continue; + } + String hostAddress = address.getHostAddress(); + if (null == interfaceName) { + ipList.add(hostAddress); + } else if (interfaceName.equals(ni.getDisplayName())) { + ipList.add(hostAddress); + } + } + } + return ipList; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/ZeroIDGen.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/ZeroIDGen.java new file mode 100644 index 0000000..9766c56 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/common/ZeroIDGen.java @@ -0,0 +1,16 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.common; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.IDGen; + +public class ZeroIDGen implements IDGen { + @Override + public Result get(String key) { + return new Result(0, Status.SUCCESS); + } + + @Override + public boolean init() { + return true; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/SegmentIDGenImpl.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/SegmentIDGenImpl.java new file mode 100644 index 0000000..44c9578 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/SegmentIDGenImpl.java @@ -0,0 +1,294 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.IDGen; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Result; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Status; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocDao; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.LeafAlloc; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.Segment; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.SegmentBuffer; +import org.perf4j.StopWatch; +import org.perf4j.slf4j.Slf4JStopWatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +public class SegmentIDGenImpl implements IDGen { + private static final Logger logger = LoggerFactory.getLogger(SegmentIDGenImpl.class); + + /** + * IDCache未初始化成功时的异常码 + */ + private static final long EXCEPTION_ID_IDCACHE_INIT_FALSE = -1; + /** + * key不存在时的异常码 + */ + private static final long EXCEPTION_ID_KEY_NOT_EXISTS = -2; + /** + * SegmentBuffer中的两个Segment均未从DB中装载时的异常码 + */ + private static final long EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL = -3; + /** + * 最大步长不超过100,0000 + */ + private static final int MAX_STEP = 1000000; + /** + * 一个Segment维持时间为15分钟 + */ + private static final long SEGMENT_DURATION = 15 * 60 * 1000L; + private ExecutorService service = new ThreadPoolExecutor(5, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), new UpdateThreadFactory()); + private volatile boolean initOK = false; + private Map cache = new ConcurrentHashMap(); + private IDAllocDao dao; + + public static class UpdateThreadFactory implements ThreadFactory { + + private static int threadInitNumber = 0; + + private static synchronized int nextThreadNum() { + return threadInitNumber++; + } + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "Thread-Segment-Update-" + nextThreadNum()); + } + } + + @Override + public boolean init() { + logger.info("Init ..."); + // 确保加载到kv后才初始化成功 + updateCacheFromDb(); + initOK = true; + updateCacheFromDbAtEveryMinute(); + return initOK; + } + + private void updateCacheFromDbAtEveryMinute() { + ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("check-idCache-thread"); + t.setDaemon(true); + return t; + } + }); + service.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + updateCacheFromDb(); + } + }, 60, 60, TimeUnit.SECONDS); + } + + private void updateCacheFromDb() { + logger.info("update cache from db"); + StopWatch sw = new Slf4JStopWatch(); + try { + List dbTags = dao.getAllTags(); + if (dbTags == null || dbTags.isEmpty()) { + return; + } + List cacheTags = new ArrayList(cache.keySet()); + Set insertTagsSet = new HashSet<>(dbTags); + Set removeTagsSet = new HashSet<>(cacheTags); + //db中新加的tags灌进cache + for(int i = 0; i < cacheTags.size(); i++){ + String tmp = cacheTags.get(i); + if(insertTagsSet.contains(tmp)){ + insertTagsSet.remove(tmp); + } + } + for (String tag : insertTagsSet) { + SegmentBuffer buffer = new SegmentBuffer(); + buffer.setKey(tag); + Segment segment = buffer.getCurrent(); + segment.setValue(new AtomicLong(0)); + segment.setMax(0); + segment.setStep(0); + cache.put(tag, buffer); + logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer); + } + //cache中已失效的tags从cache删除 + for(int i = 0; i < dbTags.size(); i++){ + String tmp = dbTags.get(i); + if(removeTagsSet.contains(tmp)){ + removeTagsSet.remove(tmp); + } + } + for (String tag : removeTagsSet) { + cache.remove(tag); + logger.info("Remove tag {} from IdCache", tag); + } + } catch (Exception e) { + logger.warn("update cache from db exception", e); + } finally { + sw.stop("updateCacheFromDb"); + } + } + + @Override + public Result get(final String key) { + if (!initOK) { + return new Result(EXCEPTION_ID_IDCACHE_INIT_FALSE, Status.EXCEPTION); + } + if (cache.containsKey(key)) { + SegmentBuffer buffer = cache.get(key); + if (!buffer.isInitOk()) { + synchronized (buffer) { + if (!buffer.isInitOk()) { + try { + updateSegmentFromDb(key, buffer.getCurrent()); + logger.info("Init buffer. Update leafkey {} {} from db", key, buffer.getCurrent()); + buffer.setInitOk(true); + } catch (Exception e) { + logger.warn("Init buffer {} exception", buffer.getCurrent(), e); + } + } + } + } + return getIdFromSegmentBuffer(cache.get(key)); + } + return new Result(EXCEPTION_ID_KEY_NOT_EXISTS, Status.EXCEPTION); + } + + public void updateSegmentFromDb(String key, Segment segment) { + StopWatch sw = new Slf4JStopWatch(); + SegmentBuffer buffer = segment.getBuffer(); + LeafAlloc leafAlloc; + if (!buffer.isInitOk()) { + leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key); + buffer.setStep(leafAlloc.getStep()); + buffer.setMinStep(leafAlloc.getStep());//leafAlloc中的step为DB中的step + } else if (buffer.getUpdateTimestamp() == 0) { + leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key); + buffer.setUpdateTimestamp(System.currentTimeMillis()); + buffer.setStep(leafAlloc.getStep()); + buffer.setMinStep(leafAlloc.getStep());//leafAlloc中的step为DB中的step + } else { + long duration = System.currentTimeMillis() - buffer.getUpdateTimestamp(); + int nextStep = buffer.getStep(); + if (duration < SEGMENT_DURATION) { + if (nextStep * 2 > MAX_STEP) { + //do nothing + } else { + nextStep = nextStep * 2; + } + } else if (duration < SEGMENT_DURATION * 2) { + //do nothing with nextStep + } else { + nextStep = nextStep / 2 >= buffer.getMinStep() ? nextStep / 2 : nextStep; + } + logger.info("leafKey[{}], step[{}], duration[{}mins], nextStep[{}]", key, buffer.getStep(), String.format("%.2f",((double)duration / (1000 * 60))), nextStep); + LeafAlloc temp = new LeafAlloc(); + temp.setKey(key); + temp.setStep(nextStep); + leafAlloc = dao.updateMaxIdByCustomStepAndGetLeafAlloc(temp); + buffer.setUpdateTimestamp(System.currentTimeMillis()); + buffer.setStep(nextStep); + buffer.setMinStep(leafAlloc.getStep());//leafAlloc的step为DB中的step + } + // must set value before set max + long value = leafAlloc.getMaxId() - buffer.getStep(); + segment.getValue().set(value); + segment.setMax(leafAlloc.getMaxId()); + segment.setStep(buffer.getStep()); + sw.stop("updateSegmentFromDb", key + " " + segment); + } + + public Result getIdFromSegmentBuffer(final SegmentBuffer buffer) { + while (true) { + buffer.rLock().lock(); + try { + final Segment segment = buffer.getCurrent(); + if (!buffer.isNextReady() && (segment.getIdle() < 0.9 * segment.getStep()) && buffer.getThreadRunning().compareAndSet(false, true)) { + service.execute(new Runnable() { + @Override + public void run() { + Segment next = buffer.getSegments()[buffer.nextPos()]; + boolean updateOk = false; + try { + updateSegmentFromDb(buffer.getKey(), next); + updateOk = true; + logger.info("update segment {} from db {}", buffer.getKey(), next); + } catch (Exception e) { + logger.warn(buffer.getKey() + " updateSegmentFromDb exception", e); + } finally { + if (updateOk) { + buffer.wLock().lock(); + buffer.setNextReady(true); + buffer.getThreadRunning().set(false); + buffer.wLock().unlock(); + } else { + buffer.getThreadRunning().set(false); + } + } + } + }); + } + long value = segment.getValue().getAndIncrement(); + if (value < segment.getMax()) { + return new Result(value, Status.SUCCESS); + } + } finally { + buffer.rLock().unlock(); + } + waitAndSleep(buffer); + buffer.wLock().lock(); + try { + final Segment segment = buffer.getCurrent(); + long value = segment.getValue().getAndIncrement(); + if (value < segment.getMax()) { + return new Result(value, Status.SUCCESS); + } + if (buffer.isNextReady()) { + buffer.switchPos(); + buffer.setNextReady(false); + } else { + logger.error("Both two segments in {} are not ready!", buffer); + return new Result(EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL, Status.EXCEPTION); + } + } finally { + buffer.wLock().unlock(); + } + } + } + + private void waitAndSleep(SegmentBuffer buffer) { + int roll = 0; + while (buffer.getThreadRunning().get()) { + roll += 1; + if(roll > 10000) { + try { + TimeUnit.MILLISECONDS.sleep(10); + break; + } catch (InterruptedException e) { + logger.warn("Thread {} Interrupted",Thread.currentThread().getName()); + break; + } + } + } + } + + public List getAllLeafAllocs() { + return dao.getAllLeafAllocs(); + } + + public Map getCache() { + return cache; + } + + public IDAllocDao getDao() { + return dao; + } + + public void setDao(IDAllocDao dao) { + this.dao = dao; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/IDAllocDao.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/IDAllocDao.java new file mode 100644 index 0000000..d80d46f --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/IDAllocDao.java @@ -0,0 +1,13 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.LeafAlloc; + +import java.util.List; + +public interface IDAllocDao { + List getAllLeafAllocs(); + LeafAlloc updateMaxIdAndGetLeafAlloc(String tag); + LeafAlloc updateMaxIdByCustomStepAndGetLeafAlloc(LeafAlloc leafAlloc); + List getAllTags(); +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/IDAllocMapper.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/IDAllocMapper.java new file mode 100644 index 0000000..c36cbdf --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/IDAllocMapper.java @@ -0,0 +1,35 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao; + +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.LeafAlloc; +import org.apache.ibatis.annotations.*; + +import java.util.List; + +public interface IDAllocMapper { + + @Select("SELECT biz_tag, max_id, step, update_time FROM leaf_alloc") + @Results(value = { + @Result(column = "biz_tag", property = "key"), + @Result(column = "max_id", property = "maxId"), + @Result(column = "step", property = "step"), + @Result(column = "update_time", property = "updateTime") + }) + List getAllLeafAllocs(); + + @Select("SELECT biz_tag, max_id, step FROM leaf_alloc WHERE biz_tag = #{tag}") + @Results(value = { + @Result(column = "biz_tag", property = "key"), + @Result(column = "max_id", property = "maxId"), + @Result(column = "step", property = "step") + }) + LeafAlloc getLeafAlloc(@Param("tag") String tag); + + @Update("UPDATE leaf_alloc SET max_id = max_id + step WHERE biz_tag = #{tag}") + void updateMaxId(@Param("tag") String tag); + + @Update("UPDATE leaf_alloc SET max_id = max_id + #{step} WHERE biz_tag = #{key}") + void updateMaxIdByCustomStep(@Param("leafAlloc") LeafAlloc leafAlloc); + + @Select("SELECT biz_tag FROM leaf_alloc") + List getAllTags(); +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/impl/IDAllocDaoImpl.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/impl/IDAllocDaoImpl.java new file mode 100644 index 0000000..bdd36fb --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/dao/impl/IDAllocDaoImpl.java @@ -0,0 +1,75 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.impl; + + +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocDao; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocMapper; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model.LeafAlloc; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; + +import javax.sql.DataSource; +import java.util.List; + +public class IDAllocDaoImpl implements IDAllocDao { + + SqlSessionFactory sqlSessionFactory; + + public IDAllocDaoImpl(DataSource dataSource) { + TransactionFactory transactionFactory = new JdbcTransactionFactory(); + Environment environment = new Environment("development", transactionFactory, dataSource); + Configuration configuration = new Configuration(environment); + configuration.addMapper(IDAllocMapper.class); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); + } + + @Override + public List getAllLeafAllocs() { + SqlSession sqlSession = sqlSessionFactory.openSession(false); + try { + return sqlSession.selectList("com.hanserwei.hannote.segment.dao.IDAllocMapper.getAllLeafAllocs"); + } finally { + sqlSession.close(); + } + } + + @Override + public LeafAlloc updateMaxIdAndGetLeafAlloc(String tag) { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + sqlSession.update("com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocMapper.updateMaxId", tag); + LeafAlloc result = sqlSession.selectOne("com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocMapper.getLeafAlloc", tag); + sqlSession.commit(); + return result; + } finally { + sqlSession.close(); + } + } + + @Override + public LeafAlloc updateMaxIdByCustomStepAndGetLeafAlloc(LeafAlloc leafAlloc) { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + sqlSession.update("com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocMapper.updateMaxIdByCustomStep", leafAlloc); + LeafAlloc result = sqlSession.selectOne("com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocMapper.getLeafAlloc", leafAlloc.getKey()); + sqlSession.commit(); + return result; + } finally { + sqlSession.close(); + } + } + + @Override + public List getAllTags() { + SqlSession sqlSession = sqlSessionFactory.openSession(false); + try { + return sqlSession.selectList("com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocMapper.getAllTags"); + } finally { + sqlSession.close(); + } + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/LeafAlloc.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/LeafAlloc.java new file mode 100644 index 0000000..5e6d558 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/LeafAlloc.java @@ -0,0 +1,40 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model; + +public class LeafAlloc { + private String key; + private long maxId; + private int step; + private String updateTime; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public long getMaxId() { + return maxId; + } + + public void setMaxId(long maxId) { + this.maxId = maxId; + } + + public int getStep() { + return step; + } + + public void setStep(int step) { + this.step = step; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/Segment.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/Segment.java new file mode 100644 index 0000000..7c0324f --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/Segment.java @@ -0,0 +1,59 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model; + +import java.util.concurrent.atomic.AtomicLong; + +public class Segment { + private AtomicLong value = new AtomicLong(0); + private volatile long max; + private volatile int step; + private SegmentBuffer buffer; + + public Segment(SegmentBuffer buffer) { + this.buffer = buffer; + } + + public AtomicLong getValue() { + return value; + } + + public void setValue(AtomicLong value) { + this.value = value; + } + + public long getMax() { + return max; + } + + public void setMax(long max) { + this.max = max; + } + + public int getStep() { + return step; + } + + public void setStep(int step) { + this.step = step; + } + + public SegmentBuffer getBuffer() { + return buffer; + } + + public long getIdle() { + return this.getMax() - getValue().get(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Segment("); + sb.append("value:"); + sb.append(value); + sb.append(",max:"); + sb.append(max); + sb.append(",step:"); + sb.append(step); + sb.append(")"); + return sb.toString(); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/SegmentBuffer.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/SegmentBuffer.java new file mode 100644 index 0000000..fdf078d --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/segment/model/SegmentBuffer.java @@ -0,0 +1,129 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.segment.model; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * 双buffer + */ +public class SegmentBuffer { + private String key; + private Segment[] segments; //双buffer + private volatile int currentPos; //当前的使用的segment的index + private volatile boolean nextReady; //下一个segment是否处于可切换状态 + private volatile boolean initOk; //是否初始化完成 + private final AtomicBoolean threadRunning; //线程是否在运行中 + private final ReadWriteLock lock; + + private volatile int step; + private volatile int minStep; + private volatile long updateTimestamp; + + public SegmentBuffer() { + segments = new Segment[]{new Segment(this), new Segment(this)}; + currentPos = 0; + nextReady = false; + initOk = false; + threadRunning = new AtomicBoolean(false); + lock = new ReentrantReadWriteLock(); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Segment[] getSegments() { + return segments; + } + + public Segment getCurrent() { + return segments[currentPos]; + } + + public int getCurrentPos() { + return currentPos; + } + + public int nextPos() { + return (currentPos + 1) % 2; + } + + public void switchPos() { + currentPos = nextPos(); + } + + public boolean isInitOk() { + return initOk; + } + + public void setInitOk(boolean initOk) { + this.initOk = initOk; + } + + public boolean isNextReady() { + return nextReady; + } + + public void setNextReady(boolean nextReady) { + this.nextReady = nextReady; + } + + public AtomicBoolean getThreadRunning() { + return threadRunning; + } + + public Lock rLock() { + return lock.readLock(); + } + + public Lock wLock() { + return lock.writeLock(); + } + + public int getStep() { + return step; + } + + public void setStep(int step) { + this.step = step; + } + + public int getMinStep() { + return minStep; + } + + public void setMinStep(int minStep) { + this.minStep = minStep; + } + + public long getUpdateTimestamp() { + return updateTimestamp; + } + + public void setUpdateTimestamp(long updateTimestamp) { + this.updateTimestamp = updateTimestamp; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SegmentBuffer{"); + sb.append("key='").append(key).append('\''); + sb.append(", segments=").append(Arrays.toString(segments)); + sb.append(", currentPos=").append(currentPos); + sb.append(", nextReady=").append(nextReady); + sb.append(", initOk=").append(initOk); + sb.append(", threadRunning=").append(threadRunning); + sb.append(", step=").append(step); + sb.append(", minStep=").append(minStep); + sb.append(", updateTimestamp=").append(updateTimestamp); + sb.append('}'); + return sb.toString(); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/SnowflakeIDGenImpl.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/SnowflakeIDGenImpl.java new file mode 100644 index 0000000..e615cc7 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/SnowflakeIDGenImpl.java @@ -0,0 +1,113 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake; + +import com.google.common.base.Preconditions; +import com.hanserwei.hannote.distributed.id.generator.biz.core.IDGen; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Result; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Status; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Random; + +public class SnowflakeIDGenImpl implements IDGen { + + @Override + public boolean init() { + return true; + } + + private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeIDGenImpl.class); + + private final long twepoch; + private final long workerIdBits = 10L; + private final long maxWorkerId = ~(-1L << workerIdBits);//最大能够分配的workerid =1023 + private final long sequenceBits = 12L; + private final long workerIdShift = sequenceBits; + private final long timestampLeftShift = sequenceBits + workerIdBits; + private final long sequenceMask = ~(-1L << sequenceBits); + private long workerId; + private long sequence = 0L; + private long lastTimestamp = -1L; + private static final Random RANDOM = new Random(); + + public SnowflakeIDGenImpl(String zkAddress, int port) { + //Thu Nov 04 2010 09:42:54 GMT+0800 (中国标准时间) + this(zkAddress, port, 1288834974657L); + } + + /** + * @param zkAddress zk地址 + * @param port snowflake监听端口 + * @param twepoch 起始的时间戳 + */ + public SnowflakeIDGenImpl(String zkAddress, int port, long twepoch) { + this.twepoch = twepoch; + Preconditions.checkArgument(timeGen() > twepoch, "Snowflake not support twepoch gt currentTime"); + final String ip = Utils.getIp(); + SnowflakeZookeeperHolder holder = new SnowflakeZookeeperHolder(ip, String.valueOf(port), zkAddress); + LOGGER.info("twepoch:{} ,ip:{} ,zkAddress:{} port:{}", twepoch, ip, zkAddress, port); + boolean initFlag = holder.init(); + if (initFlag) { + workerId = holder.getWorkerID(); + LOGGER.info("START SUCCESS USE ZK WORKERID-{}", workerId); + } else { + Preconditions.checkArgument(initFlag, "Snowflake Id Gen is not init ok"); + } + Preconditions.checkArgument(workerId >= 0 && workerId <= maxWorkerId, "workerID must gte 0 and lte 1023"); + } + + @Override + public synchronized Result get(String key) { + long timestamp = timeGen(); + if (timestamp < lastTimestamp) { + long offset = lastTimestamp - timestamp; + if (offset <= 5) { + try { + wait(offset << 1); + timestamp = timeGen(); + if (timestamp < lastTimestamp) { + return new Result(-1, Status.EXCEPTION); + } + } catch (InterruptedException e) { + LOGGER.error("wait interrupted"); + return new Result(-2, Status.EXCEPTION); + } + } else { + return new Result(-3, Status.EXCEPTION); + } + } + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + if (sequence == 0) { + //seq 为0的时候表示是下一毫秒时间开始对seq做随机 + sequence = RANDOM.nextInt(100); + timestamp = tilNextMillis(lastTimestamp); + } + } else { + //如果是新的ms开始 + sequence = RANDOM.nextInt(100); + } + lastTimestamp = timestamp; + long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; + return new Result(id, Status.SUCCESS); + + } + + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + protected long timeGen() { + return System.currentTimeMillis(); + } + + public long getWorkerId() { + return workerId; + } + +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/SnowflakeZookeeperHolder.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/SnowflakeZookeeperHolder.java new file mode 100644 index 0000000..b25648f --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/SnowflakeZookeeperHolder.java @@ -0,0 +1,292 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Maps; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.PropertyFactory; +import com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake.exception.CheckLastTimeException; +import org.apache.commons.io.FileUtils; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.RetryUntilElapsed; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.data.Stat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +public class SnowflakeZookeeperHolder { + private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeZookeeperHolder.class); + private String zk_AddressNode = null;//保存自身的key ip:port-000000001 + private String listenAddress = null;//保存自身的key ip:port + private int workerID; + private static final String PREFIX_ZK_PATH = "/snowflake/" + PropertyFactory.getProperties().getProperty("leaf.name"); + private static final String PROP_PATH = System.getProperty("java.io.tmpdir") + File.separator + PropertyFactory.getProperties().getProperty("leaf.name") + "/leafconf/{port}/workerID.properties"; + private static final String PATH_FOREVER = PREFIX_ZK_PATH + "/forever";//保存所有数据持久的节点 + private String ip; + private String port; + private String connectionString; + private long lastUpdateTime; + + public SnowflakeZookeeperHolder(String ip, String port, String connectionString) { + this.ip = ip; + this.port = port; + this.listenAddress = ip + ":" + port; + this.connectionString = connectionString; + } + + public boolean init() { + try { + CuratorFramework curator = createWithOptions(connectionString, new RetryUntilElapsed(1000, 4), 10000, 6000); + curator.start(); + Stat stat = curator.checkExists().forPath(PATH_FOREVER); + if (stat == null) { + //不存在根节点,机器第一次启动,创建/snowflake/ip:port-000000000,并上传数据 + zk_AddressNode = createNode(curator); + //worker id 默认是0 + updateLocalWorkerID(workerID); + //定时上报本机时间给forever节点 + ScheduledUploadData(curator, zk_AddressNode); + return true; + } else { + Map nodeMap = Maps.newHashMap();//ip:port->00001 + Map realNode = Maps.newHashMap();//ip:port->(ipport-000001) + //存在根节点,先检查是否有属于自己的根节点 + List keys = curator.getChildren().forPath(PATH_FOREVER); + for (String key : keys) { + String[] nodeKey = key.split("-"); + realNode.put(nodeKey[0], key); + nodeMap.put(nodeKey[0], Integer.parseInt(nodeKey[1])); + } + Integer workerid = nodeMap.get(listenAddress); + if (workerid != null) { + //有自己的节点,zk_AddressNode=ip:port + zk_AddressNode = PATH_FOREVER + "/" + realNode.get(listenAddress); + workerID = workerid;//启动worder时使用会使用 + if (!checkInitTimeStamp(curator, zk_AddressNode)) { + throw new CheckLastTimeException("init timestamp check error,forever node timestamp gt this node time"); + } + //准备创建临时节点 + doService(curator); + updateLocalWorkerID(workerID); + LOGGER.info("[Old NODE]find forever node have this endpoint ip-{} port-{} workid-{} childnode and start SUCCESS", ip, port, workerID); + } else { + //表示新启动的节点,创建持久节点 ,不用check时间 + String newNode = createNode(curator); + zk_AddressNode = newNode; + String[] nodeKey = newNode.split("-"); + workerID = Integer.parseInt(nodeKey[1]); + doService(curator); + updateLocalWorkerID(workerID); + LOGGER.info("[New NODE]can not find node on forever node that endpoint ip-{} port-{} workid-{},create own node on forever node and start SUCCESS ", ip, port, workerID); + } + } + } catch (Exception e) { + LOGGER.error("Start node ERROR {}", e); + try { + Properties properties = new Properties(); + properties.load(new FileInputStream(new File(PROP_PATH.replace("{port}", port + "")))); + workerID = Integer.valueOf(properties.getProperty("workerID")); + LOGGER.warn("START FAILED ,use local node file properties workerID-{}", workerID); + } catch (Exception e1) { + LOGGER.error("Read file error ", e1); + return false; + } + } + return true; + } + + private void doService(CuratorFramework curator) { + ScheduledUploadData(curator, zk_AddressNode);// /snowflake_forever/ip:port-000000001 + } + + private void ScheduledUploadData(final CuratorFramework curator, final String zk_AddressNode) { + Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "schedule-upload-time"); + thread.setDaemon(true); + return thread; + } + }).scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + updateNewData(curator, zk_AddressNode); + } + }, 1L, 3L, TimeUnit.SECONDS);//每3s上报数据 + + } + + private boolean checkInitTimeStamp(CuratorFramework curator, String zk_AddressNode) throws Exception { + byte[] bytes = curator.getData().forPath(zk_AddressNode); + Endpoint endPoint = deBuildData(new String(bytes)); + //该节点的时间不能小于最后一次上报的时间 + return !(endPoint.getTimestamp() > System.currentTimeMillis()); + } + + /** + * 创建持久顺序节点 ,并把节点数据放入 value + * + * @param curator + * @return + * @throws Exception + */ + private String createNode(CuratorFramework curator) throws Exception { + try { + return curator.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(PATH_FOREVER + "/" + listenAddress + "-", buildData().getBytes()); + } catch (Exception e) { + LOGGER.error("create node error msg {} ", e.getMessage()); + throw e; + } + } + + private void updateNewData(CuratorFramework curator, String path) { + try { + if (System.currentTimeMillis() < lastUpdateTime) { + return; + } + curator.setData().forPath(path, buildData().getBytes()); + lastUpdateTime = System.currentTimeMillis(); + } catch (Exception e) { + LOGGER.info("update init data error path is {} error is {}", path, e); + } + } + + /** + * 构建需要上传的数据 + * + * @return + */ + private String buildData() throws JsonProcessingException { + Endpoint endpoint = new Endpoint(ip, port, System.currentTimeMillis()); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(endpoint); + return json; + } + + private Endpoint deBuildData(String json) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + Endpoint endpoint = mapper.readValue(json, Endpoint.class); + return endpoint; + } + + /** + * 在节点文件系统上缓存一个workid值,zk失效,机器重启时保证能够正常启动 + * + * @param workerID + */ + private void updateLocalWorkerID(int workerID) { + File leafConfFile = new File(PROP_PATH.replace("{port}", port)); + boolean exists = leafConfFile.exists(); + LOGGER.info("file exists status is {}", exists); + if (exists) { + try { + FileUtils.writeStringToFile(leafConfFile, "workerID=" + workerID, false); + LOGGER.info("update file cache workerID is {}", workerID); + } catch (IOException e) { + LOGGER.error("update file cache error ", e); + } + } else { + //不存在文件,父目录页肯定不存在 + try { + boolean mkdirs = leafConfFile.getParentFile().mkdirs(); + LOGGER.info("init local file cache create parent dis status is {}, worker id is {}", mkdirs, workerID); + if (mkdirs) { + if (leafConfFile.createNewFile()) { + FileUtils.writeStringToFile(leafConfFile, "workerID=" + workerID, false); + LOGGER.info("local file cache workerID is {}", workerID); + } + } else { + LOGGER.warn("create parent dir error==="); + } + } catch (IOException e) { + LOGGER.warn("craete workerID conf file error", e); + } + } + } + + private CuratorFramework createWithOptions(String connectionString, RetryPolicy retryPolicy, int connectionTimeoutMs, int sessionTimeoutMs) { + return CuratorFrameworkFactory.builder().connectString(connectionString) + .retryPolicy(retryPolicy) + .connectionTimeoutMs(connectionTimeoutMs) + .sessionTimeoutMs(sessionTimeoutMs) + .build(); + } + + /** + * 上报数据结构 + */ + static class Endpoint { + private String ip; + private String port; + private long timestamp; + + public Endpoint() { + } + + public Endpoint(String ip, String port, long timestamp) { + this.ip = ip; + this.port = port; + this.timestamp = timestamp; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + } + + public String getZk_AddressNode() { + return zk_AddressNode; + } + + public void setZk_AddressNode(String zk_AddressNode) { + this.zk_AddressNode = zk_AddressNode; + } + + public String getListenAddress() { + return listenAddress; + } + + public void setListenAddress(String listenAddress) { + this.listenAddress = listenAddress; + } + + public int getWorkerID() { + return workerID; + } + + public void setWorkerID(int workerID) { + this.workerID = workerID; + } + +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/CheckLastTimeException.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/CheckLastTimeException.java new file mode 100644 index 0000000..8d7d0ea --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/CheckLastTimeException.java @@ -0,0 +1,7 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake.exception; + +public class CheckLastTimeException extends RuntimeException { + public CheckLastTimeException(String msg){ + super(msg); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/CheckOtherNodeException.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/CheckOtherNodeException.java new file mode 100644 index 0000000..4d6f7ae --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/CheckOtherNodeException.java @@ -0,0 +1,7 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake.exception; + +public class CheckOtherNodeException extends RuntimeException { + public CheckOtherNodeException(String message) { + super(message); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/ClockGoBackException.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/ClockGoBackException.java new file mode 100644 index 0000000..9d0e3ce --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/core/snowflake/exception/ClockGoBackException.java @@ -0,0 +1,7 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake.exception; + +public class ClockGoBackException extends RuntimeException { + public ClockGoBackException(String message) { + super(message); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/InitException.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/InitException.java new file mode 100644 index 0000000..bb2d38f --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/InitException.java @@ -0,0 +1,7 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.exception; + +public class InitException extends Exception{ + public InitException(String msg) { + super(msg); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/LeafServerException.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/LeafServerException.java new file mode 100644 index 0000000..ebeced4 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/LeafServerException.java @@ -0,0 +1,11 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR) +public class LeafServerException extends RuntimeException { + public LeafServerException(String msg) { + super(msg); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/NoKeyException.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/NoKeyException.java new file mode 100644 index 0000000..297713b --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/exception/NoKeyException.java @@ -0,0 +1,8 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR,reason="Key is none") +public class NoKeyException extends RuntimeException { +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/model/SegmentBufferView.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/model/SegmentBufferView.java new file mode 100644 index 0000000..a45bcb6 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/model/SegmentBufferView.java @@ -0,0 +1,95 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.model; + +public class SegmentBufferView { + private String key; + private long value0; + private int step0; + private long max0; + + private long value1; + private int step1; + private long max1; + private int pos; + private boolean nextReady; + private boolean initOk; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public long getValue1() { + return value1; + } + + public void setValue1(long value1) { + this.value1 = value1; + } + + public int getStep1() { + return step1; + } + + public void setStep1(int step1) { + this.step1 = step1; + } + + public long getMax1() { + return max1; + } + + public void setMax1(long max1) { + this.max1 = max1; + } + + public long getValue0() { + return value0; + } + + public void setValue0(long value0) { + this.value0 = value0; + } + + public int getStep0() { + return step0; + } + + public void setStep0(int step0) { + this.step0 = step0; + } + + public long getMax0() { + return max0; + } + + public void setMax0(long max0) { + this.max0 = max0; + } + + public int getPos() { + return pos; + } + + public void setPos(int pos) { + this.pos = pos; + } + + public boolean isNextReady() { + return nextReady; + } + + public void setNextReady(boolean nextReady) { + this.nextReady = nextReady; + } + + public boolean isInitOk() { + return initOk; + } + + public void setInitOk(boolean initOk) { + this.initOk = initOk; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/service/SegmentService.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/service/SegmentService.java new file mode 100644 index 0000000..b1c7617 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/service/SegmentService.java @@ -0,0 +1,67 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.service; + +import com.alibaba.druid.pool.DruidDataSource; +import com.hanserwei.hannote.distributed.id.generator.biz.constant.Constants; +import com.hanserwei.hannote.distributed.id.generator.biz.core.IDGen; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.PropertyFactory; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Result; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.ZeroIDGen; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.SegmentIDGenImpl; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.IDAllocDao; +import com.hanserwei.hannote.distributed.id.generator.biz.core.segment.dao.impl.IDAllocDaoImpl; +import com.hanserwei.hannote.distributed.id.generator.biz.exception.InitException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.Properties; + +@Service("SegmentService") +public class SegmentService { + private Logger logger = LoggerFactory.getLogger(SegmentService.class); + + private IDGen idGen; + private DruidDataSource dataSource; + + public SegmentService() throws SQLException, InitException { + Properties properties = PropertyFactory.getProperties(); + boolean flag = Boolean.parseBoolean(properties.getProperty(Constants.LEAF_SEGMENT_ENABLE, "true")); + if (flag) { + // Config dataSource + dataSource = new DruidDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + dataSource.setUrl(properties.getProperty(Constants.LEAF_JDBC_URL)); + dataSource.setUsername(properties.getProperty(Constants.LEAF_JDBC_USERNAME)); + dataSource.setPassword(properties.getProperty(Constants.LEAF_JDBC_PASSWORD)); + dataSource.setValidationQuery("select 1"); + dataSource.init(); + + // Config Dao + IDAllocDao dao = new IDAllocDaoImpl(dataSource); + + // Config ID Gen + idGen = new SegmentIDGenImpl(); + ((SegmentIDGenImpl) idGen).setDao(dao); + if (idGen.init()) { + logger.info("Segment Service Init Successfully"); + } else { + throw new InitException("Segment Service Init Fail"); + } + } else { + idGen = new ZeroIDGen(); + logger.info("Zero ID Gen Service Init Successfully"); + } + } + + public Result getId(String key) { + return idGen.get(key); + } + + public SegmentIDGenImpl getIdGen() { + if (idGen instanceof SegmentIDGenImpl) { + return (SegmentIDGenImpl) idGen; + } + return null; + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/service/SnowflakeService.java b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/service/SnowflakeService.java new file mode 100644 index 0000000..c4f395c --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/java/com/hanserwei/hannote/distributed/id/generator/biz/service/SnowflakeService.java @@ -0,0 +1,44 @@ +package com.hanserwei.hannote.distributed.id.generator.biz.service; + + +import com.hanserwei.hannote.distributed.id.generator.biz.constant.Constants; +import com.hanserwei.hannote.distributed.id.generator.biz.core.IDGen; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.PropertyFactory; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.Result; +import com.hanserwei.hannote.distributed.id.generator.biz.core.common.ZeroIDGen; +import com.hanserwei.hannote.distributed.id.generator.biz.core.snowflake.SnowflakeIDGenImpl; +import com.hanserwei.hannote.distributed.id.generator.biz.exception.InitException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.Properties; + +@Service("SnowflakeService") +public class SnowflakeService { + private Logger logger = LoggerFactory.getLogger(SnowflakeService.class); + + private IDGen idGen; + + public SnowflakeService() throws InitException { + Properties properties = PropertyFactory.getProperties(); + boolean flag = Boolean.parseBoolean(properties.getProperty(Constants.LEAF_SNOWFLAKE_ENABLE, "true")); + if (flag) { + String zkAddress = properties.getProperty(Constants.LEAF_SNOWFLAKE_ZK_ADDRESS); + int port = Integer.parseInt(properties.getProperty(Constants.LEAF_SNOWFLAKE_PORT)); + idGen = new SnowflakeIDGenImpl(zkAddress, port); + if(idGen.init()) { + logger.info("Snowflake Service Init Successfully"); + } else { + throw new InitException("Snowflake Service Init Fail"); + } + } else { + idGen = new ZeroIDGen(); + logger.info("Zero ID Gen Service Init Successfully"); + } + } + + public Result getId(String key) { + return idGen.get(key); + } +} diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application-dev.yml b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application-dev.yml new file mode 100644 index 0000000..88ff417 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application-dev.yml @@ -0,0 +1,5 @@ +spring: + cassandra: + keyspace-name: hannote + contact-points: 127.0.0.1 + port: 9042 diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application-prod.yml b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application-prod.yml new file mode 100644 index 0000000..e69de29 diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application.yml b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application.yml new file mode 100644 index 0000000..c828c64 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8085 # 项目启动的端口 + +spring: + profiles: + active: dev # 默认激活 dev 本地开发环境 diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/bootstrap.yml b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..29156bb --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/bootstrap.yml @@ -0,0 +1,12 @@ +spring: + application: + name: han-note-distributed-id-generator # 应用名称 + profiles: + active: dev # 默认激活 dev 本地开发环境 + cloud: + nacos: + discovery: + enabled: true # 启用服务发现 + group: DEFAULT_GROUP # 所属组 + namespace: han-note # 命名空间 + server-addr: 127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址 diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/leaf.properties b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/leaf.properties new file mode 100644 index 0000000..2fd6562 --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/leaf.properties @@ -0,0 +1,12 @@ +leaf.name=com.sankuai.leaf.opensource.test +leaf.segment.enable=true +leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/leaf?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai +leaf.jdbc.username=root +leaf.jdbc.password=mysql + +# ???? snowflake ?? +leaf.snowflake.enable=true +# snowflake ???? zk ?? +leaf.snowflake.zk.address=127.0.0.1:2181 +# snowflake ?????????? +leaf.snowflake.port=2222 diff --git a/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/logback-spring.xml b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..2bd72da --- /dev/null +++ b/han-note-distributed-id-generator/hannote-distributed-id-generator-biz/src/main/resources/logback-spring.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + ${LOG_FILE}-%i.log + + 30 + + 10MB + + 0 + + false + + + ${LOG_PATTERN} + UTF-8 + + + + + + + 0 + + 256 + + + + + + + + + + + + + + + + + + + + + diff --git a/han-note-distributed-id-generator/pom.xml b/han-note-distributed-id-generator/pom.xml new file mode 100644 index 0000000..447346b --- /dev/null +++ b/han-note-distributed-id-generator/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + + + com.hanserwei + han-note + ${revision} + + + + pom + + + + han-note-distributed-id-generator-api + hannote-distributed-id-generator-biz + + + han-note-distributed-id-generator + + ${project.artifactId} + + 分布式 ID 生成服务 + + diff --git a/han-note-kv/han-note-kv-api/pom.xml b/han-note-kv/han-note-kv-api/pom.xml new file mode 100644 index 0000000..27b2f7c --- /dev/null +++ b/han-note-kv/han-note-kv-api/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + + + com.hanserwei + han-note-kv + ${revision} + + + + jar + + han-note-kv-api + ${project.artifactId} + RPC层, 供其他服务调用 + + + + com.hanserwei + hanserwei-common + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java new file mode 100644 index 0000000..46ce29f --- /dev/null +++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java @@ -0,0 +1,24 @@ +package com.hanserwei.hannote.kv.api; + +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.kv.constant.ApiConstants; +import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.SERVICE_NAME) +public interface KeyValueFeignApi { + + String PREFIX = "/kv"; + + @PostMapping(value = PREFIX + "/note/content/add") + Response addNoteContent(@RequestBody AddNoteContentReqDTO addNoteContentReqDTO); + + @PostMapping(value = PREFIX + "/note/content/find") + Response findNoteContent(@RequestBody AddNoteContentReqDTO addNoteContentReqDTO); + + @PostMapping(value = PREFIX + "/note/content/delete") + Response deleteNoteContent(@RequestBody AddNoteContentReqDTO addNoteContentReqDTO); + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/constant/ApiConstants.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/constant/ApiConstants.java new file mode 100644 index 0000000..bf5ae3b --- /dev/null +++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/constant/ApiConstants.java @@ -0,0 +1,9 @@ +package com.hanserwei.hannote.kv.constant; + +public interface ApiConstants { + + /** + * 服务名称 + */ + String SERVICE_NAME = "han-note-kv"; +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/AddNoteContentReqDTO.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/AddNoteContentReqDTO.java new file mode 100644 index 0000000..c5299db --- /dev/null +++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/AddNoteContentReqDTO.java @@ -0,0 +1,22 @@ +package com.hanserwei.hannote.kv.dto.req; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AddNoteContentReqDTO { + + @NotNull(message = "笔记 ID 不能为空") + private Long noteId; + + @NotBlank(message = "笔记内容不能为空") + private String content; + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/DeleteNoteContentReqDTO.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/DeleteNoteContentReqDTO.java new file mode 100644 index 0000000..7def538 --- /dev/null +++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/DeleteNoteContentReqDTO.java @@ -0,0 +1,18 @@ +package com.hanserwei.hannote.kv.dto.req; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DeleteNoteContentReqDTO { + + @NotBlank(message = "笔记 ID 不能为空") + private String noteId; + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/FindNoteContentReqDTO.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/FindNoteContentReqDTO.java new file mode 100644 index 0000000..77f43ba --- /dev/null +++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/FindNoteContentReqDTO.java @@ -0,0 +1,18 @@ +package com.hanserwei.hannote.kv.dto.req; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindNoteContentReqDTO { + + @NotBlank(message = "笔记 ID 不能为空") + private String noteId; + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/resp/FindNoteContentRspDTO.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/resp/FindNoteContentRspDTO.java new file mode 100644 index 0000000..3895b99 --- /dev/null +++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/resp/FindNoteContentRspDTO.java @@ -0,0 +1,26 @@ +package com.hanserwei.hannote.kv.dto.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindNoteContentRspDTO { + + /** + * 笔记 ID + */ + private UUID noteId; + + /** + * 笔记内容 + */ + private String content; + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-biz/pom.xml b/han-note-kv/han-note-kv-biz/pom.xml new file mode 100644 index 0000000..558ef57 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + + com.hanserwei + han-note-kv + ${revision} + + + + jar + + han-note-kv-biz + ${project.artifactId} + Key-Value 键值存储业务层 + + + + com.hanserwei + hanserwei-common + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + org.springframework.boot + spring-boot-starter-data-cassandra + + + + com.hanserwei + han-note-kv-api + + + diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/HannoteKVBizApplication.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/HannoteKVBizApplication.java new file mode 100644 index 0000000..52d152e --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/HannoteKVBizApplication.java @@ -0,0 +1,11 @@ +package com.hanserwei.hannote.kv.biz; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class HannoteKVBizApplication { + public static void main(String[] args) { + SpringApplication.run(HannoteKVBizApplication.class, args); + } +} diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/config/CassandraConfig.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/config/CassandraConfig.java new file mode 100644 index 0000000..c9ab30f --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/config/CassandraConfig.java @@ -0,0 +1,32 @@ +package com.hanserwei.hannote.kv.biz.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.cassandra.config.AbstractCassandraConfiguration; + +@Configuration +public class CassandraConfig extends AbstractCassandraConfiguration { + + @Value("${spring.cassandra.keyspace-name}") + private String keySpace; + + @Value("${spring.cassandra.contact-points}") + private String contactPoints; + + @Value("${spring.cassandra.port}") + private int port; + @Override + protected String getKeyspaceName() { + return keySpace; + } + + @Override + public String getContactPoints() { + return contactPoints; + } + + @Override + public int getPort() { + return port; + } +} diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/NoteContentController.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/NoteContentController.java new file mode 100644 index 0000000..d9d2dcf --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/NoteContentController.java @@ -0,0 +1,40 @@ +package com.hanserwei.hannote.kv.biz.controller; + +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.kv.biz.service.NoteContentService; +import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/kv") +@Slf4j +public class NoteContentController { + + @Resource + private NoteContentService noteContentService; + + @PostMapping(value = "/note/content/add") + public Response addNoteContent(@Validated @RequestBody AddNoteContentReqDTO addNoteContentReqDTO) { + return noteContentService.addNoteContent(addNoteContentReqDTO); + } + + @PostMapping(value = "/note/content/find") + public Response findNoteContent(@Validated @RequestBody FindNoteContentReqDTO findNoteContentReqDTO) { + return noteContentService.findNoteContent(findNoteContentReqDTO); + } + + @PostMapping(value = "/note/content/delete") + public Response deleteNoteContent(@Validated @RequestBody DeleteNoteContentReqDTO deleteNoteContentReqDTO) { + return noteContentService.deleteNoteContent(deleteNoteContentReqDTO); + } + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/NoteContentDO.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/NoteContentDO.java new file mode 100644 index 0000000..c9224dc --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/NoteContentDO.java @@ -0,0 +1,23 @@ +package com.hanserwei.hannote.kv.biz.domain.dataobject; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.cassandra.core.mapping.PrimaryKey; +import org.springframework.data.cassandra.core.mapping.Table; + +import java.util.UUID; + +@Table("note_content") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NoteContentDO { + + @PrimaryKey("id") + private UUID id; + + private String content; +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/repository/NoteContentRepository.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/repository/NoteContentRepository.java new file mode 100644 index 0000000..e67a0b4 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/repository/NoteContentRepository.java @@ -0,0 +1,9 @@ +package com.hanserwei.hannote.kv.biz.domain.repository; + +import com.hanserwei.hannote.kv.biz.domain.dataobject.NoteContentDO; +import org.springframework.data.cassandra.repository.CassandraRepository; + +import java.util.UUID; + +public interface NoteContentRepository extends CassandraRepository { +} diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/enums/ResponseCodeEnum.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/enums/ResponseCodeEnum.java new file mode 100644 index 0000000..c41119e --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/enums/ResponseCodeEnum.java @@ -0,0 +1,24 @@ +package com.hanserwei.hannote.kv.biz.enums; + +import com.hanserwei.framework.common.exception.BaseExceptionInterface; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ResponseCodeEnum implements BaseExceptionInterface { + + // ----------- 通用异常状态码 ----------- + SYSTEM_ERROR("KV-10000", "出错啦,后台小哥正在努力修复中..."), + PARAM_NOT_VALID("KV-10001", "参数错误"), + + // ----------- 业务异常状态码 ----------- + NOTE_CONTENT_NOT_FOUND("KV-20000", "该笔记内容不存在"), + ; + + // 异常码 + private final String errorCode; + // 错误信息 + private final String errorMsg; + +} \ No newline at end of file diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/exception/GlobalExceptionHandler.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..c308bee --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/exception/GlobalExceptionHandler.java @@ -0,0 +1,103 @@ +package com.hanserwei.hannote.kv.biz.exception; + +import com.hanserwei.framework.common.exception.ApiException; +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.kv.biz.enums.ResponseCodeEnum; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Optional; + +@SuppressWarnings("LoggingSimilarMessage") +@ControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + /** + * 捕获自定义业务异常 + * + * @return Response.fail(e) + */ + @ExceptionHandler({ApiException.class}) + @ResponseBody + public Response handleApiException(HttpServletRequest request, ApiException e) { + log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg()); + return Response.fail(e); + } + + /** + * 捕获参数校验异常 + * + * @return Response.fail(errorCode, errorMessage) + */ + @ExceptionHandler({MethodArgumentNotValidException.class}) + @ResponseBody + public Response handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) { + // 参数错误异常码 + String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode(); + + // 获取 BindingResult + BindingResult bindingResult = e.getBindingResult(); + + StringBuilder sb = new StringBuilder(); + + // 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com'; + Optional.of(bindingResult.getFieldErrors()).ifPresent(errors -> { + errors.forEach(error -> + sb.append(error.getField()) + .append(" ") + .append(error.getDefaultMessage()) + .append(", 当前值: '") + .append(error.getRejectedValue()) + .append("'; ") + + ); + }); + + // 错误信息 + String errorMessage = sb.toString(); + + log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage); + + return Response.fail(errorCode, errorMessage); + } + + /** + * 捕获 guava 参数校验异常 + * + * @return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID) + */ + @ExceptionHandler({IllegalArgumentException.class}) + @ResponseBody + public Response handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) { + // 参数错误异常码 + String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode(); + + // 错误信息 + String errorMessage = e.getMessage(); + + log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage); + + return Response.fail(errorCode, errorMessage); + } + + /** + * 其他类型异常 + * + * @param request 请求 + * @param e 异常 + * @return Response.fail(ResponseCodeEnum.SYSTEM_ERROR) + */ + @ExceptionHandler({Exception.class}) + @ResponseBody + public Response handleOtherException(HttpServletRequest request, Exception e) { + log.error("{} request error, ", request.getRequestURI(), e); + return Response.fail(ResponseCodeEnum.SYSTEM_ERROR); + } +} + diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/NoteContentService.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/NoteContentService.java new file mode 100644 index 0000000..8e637f2 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/NoteContentService.java @@ -0,0 +1,34 @@ +package com.hanserwei.hannote.kv.biz.service; + +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO; + +public interface NoteContentService { + /** + * 添加笔记内容 + * + * @param addNoteContentReqDTO 添加笔记内容请求参数 + * @return 响应 + */ + Response addNoteContent(AddNoteContentReqDTO addNoteContentReqDTO); + + /** + * 查询笔记内容 + * + * @param findNoteContentReqDTO 查询笔记内容请求参数 + * @return 响应 + */ + Response findNoteContent(FindNoteContentReqDTO findNoteContentReqDTO); + + /** + * 删除笔记内容 + * + * @param deleteNoteContentReqDTO 删除笔记内容请求参数 + * @return 响应 + */ + Response deleteNoteContent(DeleteNoteContentReqDTO deleteNoteContentReqDTO); + +} diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/NoteContentServiceImpl.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/NoteContentServiceImpl.java new file mode 100644 index 0000000..03e06a8 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/NoteContentServiceImpl.java @@ -0,0 +1,67 @@ +package com.hanserwei.hannote.kv.biz.service.impl; + +import com.hanserwei.framework.common.exception.ApiException; +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.kv.biz.domain.dataobject.NoteContentDO; +import com.hanserwei.hannote.kv.biz.domain.repository.NoteContentRepository; +import com.hanserwei.hannote.kv.biz.enums.ResponseCodeEnum; +import com.hanserwei.hannote.kv.biz.service.NoteContentService; +import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO; +import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.UUID; + +@Service +@Slf4j +public class NoteContentServiceImpl implements NoteContentService { + + @Resource + private NoteContentRepository noteContentRepository; + + @Override + public Response addNoteContent(AddNoteContentReqDTO addNoteContentReqDTO) { + // 笔记ID + Long noteId = addNoteContentReqDTO.getNoteId(); + // 笔记内容 + String content = addNoteContentReqDTO.getContent(); + + NoteContentDO noteContent = NoteContentDO.builder() + .id(UUID.randomUUID()) + .content(content) + .build(); + + // 插入数据 + noteContentRepository.save(noteContent); + return Response.success(); + } + + @Override + public Response findNoteContent(FindNoteContentReqDTO findNoteContentReqDTO) { + // 笔记ID + String noteId = findNoteContentReqDTO.getNoteId(); + Optional optional = noteContentRepository.findById(UUID.fromString(noteId)); + if (optional.isEmpty()){ + throw new ApiException(ResponseCodeEnum.NOTE_CONTENT_NOT_FOUND); + } + NoteContentDO noteContentDO = optional.get(); + // 构建回参 + FindNoteContentRspDTO findNoteContentRspDTO = FindNoteContentRspDTO.builder() + .noteId(noteContentDO.getId()) + .content(noteContentDO.getContent()) + .build(); + return Response.success(findNoteContentRspDTO); + } + + @Override + public Response deleteNoteContent(DeleteNoteContentReqDTO deleteNoteContentReqDTO) { + String noteId = deleteNoteContentReqDTO.getNoteId(); + noteContentRepository.deleteById(UUID.fromString(noteId)); + return Response.success(); + } +} diff --git a/han-note-kv/han-note-kv-biz/src/main/resources/application.yml b/han-note-kv/han-note-kv-biz/src/main/resources/application.yml new file mode 100644 index 0000000..304daa6 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8084 # 项目启动的端口 + +spring: + profiles: + active: dev # 默认激活 dev 本地开发环境 diff --git a/han-note-kv/han-note-kv-biz/src/main/resources/bootstrap.yml b/han-note-kv/han-note-kv-biz/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..dafd461 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/resources/bootstrap.yml @@ -0,0 +1,12 @@ +spring: + application: + name: han-note-kv # 应用名称 + profiles: + active: dev # 默认激活 dev 本地开发环境 + cloud: + nacos: + discovery: + enabled: true # 启用服务发现 + group: DEFAULT_GROUP # 所属组 + namespace: han-note # 命名空间 + server-addr: 127.0.0.1:8848 # 指定 NaCos 配置中心的服务器地址 diff --git a/han-note-kv/han-note-kv-biz/src/main/resources/logback-spring.xml b/han-note-kv/han-note-kv-biz/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..bc50448 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/main/resources/logback-spring.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + ${LOG_FILE}-%i.log + + 30 + + 10MB + + 0 + + false + + + ${LOG_PATTERN} + UTF-8 + + + + + + + 0 + + 256 + + + + + + + + + + + + + + + + + + + + + diff --git a/han-note-kv/han-note-kv-biz/src/test/java/com/hanserwei/hannote/kv/biz/CassandraTests.java b/han-note-kv/han-note-kv-biz/src/test/java/com/hanserwei/hannote/kv/biz/CassandraTests.java new file mode 100644 index 0000000..87f0a19 --- /dev/null +++ b/han-note-kv/han-note-kv-biz/src/test/java/com/hanserwei/hannote/kv/biz/CassandraTests.java @@ -0,0 +1,65 @@ +package com.hanserwei.hannote.kv.biz; + +import com.hanserwei.framework.common.utils.JsonUtils; +import com.hanserwei.hannote.kv.biz.domain.dataobject.NoteContentDO; +import com.hanserwei.hannote.kv.biz.domain.repository.NoteContentRepository; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Optional; +import java.util.UUID; + +@SpringBootTest +@Slf4j +class CassandraTests { + + @Resource + private NoteContentRepository noteContentRepository; + + /** + * 测试插入数据 + */ + @Test + void testInsert() { + NoteContentDO nodeContent = NoteContentDO.builder() + .id(UUID.randomUUID()) + .content("代码测试笔记内容插入") + .build(); + + noteContentRepository.save(nodeContent); + } + + /** + * 测试修改数据 + */ + @Test + void testUpdate() { + NoteContentDO nodeContent = NoteContentDO.builder() + .id(UUID.fromString("8a0e491d-49f0-41cf-95b1-c9f541567156")) + .content("代码测试笔记内容更新") + .build(); + + noteContentRepository.save(nodeContent); + } + + /** + * 测试查询数据 + */ + @Test + void testSelect() { + Optional optional = noteContentRepository.findById(UUID.fromString("8a0e491d-49f0-41cf-95b1-c9f541567156")); + optional.ifPresent(noteContentDO -> log.info("查询结果:{}", JsonUtils.toJsonString(noteContentDO))); + } + + /** + * 测试删除数据 + */ + @Test + void testDelete() { + noteContentRepository.deleteById(UUID.fromString("8a0e491d-49f0-41cf-95b1-c9f541567156")); + } + + +} \ No newline at end of file diff --git a/han-note-kv/pom.xml b/han-note-kv/pom.xml new file mode 100644 index 0000000..b1d8615 --- /dev/null +++ b/han-note-kv/pom.xml @@ -0,0 +1,26 @@ + + 4.0.0 + + + com.hanserwei + han-note + ${revision} + + + + pom + + + + han-note-kv-api + han-note-kv-biz + + + han-note-kv + + ${project.artifactId} + + Key-Value 键值存储服务 + + diff --git a/pom.xml b/pom.xml index 517e121..0883301 100755 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ han-note-gateway han-note-oss han-note-user + han-note-kv + han-note-distributed-id-generator @@ -220,7 +222,11 @@ han-note-user-api ${revision} - + + com.hanserwei + han-note-kv-api + ${revision} +