From 9735253c015075f28f08f9f4d337f4e2d75cf073 Mon Sep 17 00:00:00 2001 From: hupeng Date: Sun, 29 Dec 2019 17:30:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E6=88=90=E8=AE=A2=E5=8D=95=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E7=BC=96=E5=8F=B7=E7=94=B1=E5=8E=9F=E6=9D=A5=E7=AE=80?= =?UTF-8?q?=E5=8D=95=E6=97=B6=E9=97=B4=E6=88=B3=E4=BF=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?idworker=E7=94=9F=E4=BA=A7=E5=88=86=E5=B8=83=E5=BC=8F=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/co/yixiang/ApiRun.java | 2 + .../service/impl/YxStoreOrderServiceImpl.java | 47 ++-- .../src/main/java/org/n3r/idworker/Code.java | 35 +++ .../main/java/org/n3r/idworker/DayCode.java | 19 ++ .../src/main/java/org/n3r/idworker/Id.java | 29 +++ .../main/java/org/n3r/idworker/IdWorker.java | 91 ++++++++ .../org/n3r/idworker/InvalidSystemClock.java | 7 + .../org/n3r/idworker/RandomCodeStrategy.java | 11 + .../src/main/java/org/n3r/idworker/Sid.java | 62 ++++++ .../src/main/java/org/n3r/idworker/Test.java | 12 + .../org/n3r/idworker/WorkerIdStrategy.java | 9 + .../strategy/DayPrefixRandomCodeStrategy.java | 41 ++++ .../strategy/DefaultRandomCodeStrategy.java | 197 +++++++++++++++++ .../strategy/DefaultWorkerIdStrategy.java | 205 ++++++++++++++++++ .../org/n3r/idworker/strategy/FileLock.java | 132 +++++++++++ .../java/org/n3r/idworker/utils/HttpReq.java | 113 ++++++++++ .../org/n3r/idworker/utils/IPv4Utils.java | 60 +++++ .../main/java/org/n3r/idworker/utils/Ip.java | 50 +++++ .../java/org/n3r/idworker/utils/Props.java | 70 ++++++ .../org/n3r/idworker/utils/Serializes.java | 118 ++++++++++ .../java/org/n3r/idworker/utils/Utils.java | 114 ++++++++++ 21 files changed, 1409 insertions(+), 15 deletions(-) create mode 100644 yshop-common/src/main/java/org/n3r/idworker/Code.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/DayCode.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/Id.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/IdWorker.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/InvalidSystemClock.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/RandomCodeStrategy.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/Sid.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/Test.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/WorkerIdStrategy.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/strategy/DayPrefixRandomCodeStrategy.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultRandomCodeStrategy.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultWorkerIdStrategy.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/strategy/FileLock.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/utils/HttpReq.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/utils/IPv4Utils.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/utils/Ip.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/utils/Props.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/utils/Serializes.java create mode 100644 yshop-common/src/main/java/org/n3r/idworker/utils/Utils.java diff --git a/yshop-api/src/main/java/co/yixiang/ApiRun.java b/yshop-api/src/main/java/co/yixiang/ApiRun.java index 324036e1..11e1c1f2 100644 --- a/yshop-api/src/main/java/co/yixiang/ApiRun.java +++ b/yshop-api/src/main/java/co/yixiang/ApiRun.java @@ -5,6 +5,7 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -16,6 +17,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication @EnableTransactionManagement @MapperScan({"co.yixiang.modules.*.mapper"}) +@ComponentScan(basePackages = {"co.yixiang", "org.n3r.idworker"}) public class ApiRun { public static void main(String[] args) { diff --git a/yshop-api/src/main/java/co/yixiang/modules/order/service/impl/YxStoreOrderServiceImpl.java b/yshop-api/src/main/java/co/yixiang/modules/order/service/impl/YxStoreOrderServiceImpl.java index 20aab008..e2546736 100644 --- a/yshop-api/src/main/java/co/yixiang/modules/order/service/impl/YxStoreOrderServiceImpl.java +++ b/yshop-api/src/main/java/co/yixiang/modules/order/service/impl/YxStoreOrderServiceImpl.java @@ -1,12 +1,16 @@ package co.yixiang.modules.order.service.impl; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.*; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import co.yixiang.common.constant.CommonConstant; +import co.yixiang.common.service.impl.BaseServiceImpl; +import co.yixiang.common.web.vo.Paging; import co.yixiang.domain.AlipayConfig; import co.yixiang.domain.vo.TradeVo; import co.yixiang.exception.ErrorRequestException; -import co.yixiang.modules.activity.mapper.YxStoreBargainMapper; import co.yixiang.modules.activity.service.*; import co.yixiang.modules.manage.service.YxExpressService; import co.yixiang.modules.manage.web.dto.ChartDataDTO; @@ -29,8 +33,6 @@ import co.yixiang.modules.order.web.param.OrderParam; import co.yixiang.modules.order.web.param.RefundParam; import co.yixiang.modules.order.web.param.YxStoreOrderQueryParam; import co.yixiang.modules.order.web.vo.YxStoreOrderQueryVo; -import co.yixiang.common.service.impl.BaseServiceImpl; -import co.yixiang.common.web.vo.Paging; import co.yixiang.modules.shop.entity.YxStoreCart; import co.yixiang.modules.shop.entity.YxStoreCouponUser; import co.yixiang.modules.shop.mapper.YxStoreCartMapper; @@ -40,21 +42,29 @@ import co.yixiang.modules.shop.service.YxStoreProductReplyService; import co.yixiang.modules.shop.service.YxStoreProductService; import co.yixiang.modules.shop.service.YxSystemConfigService; import co.yixiang.modules.shop.web.vo.YxStoreCartQueryVo; -import co.yixiang.modules.user.entity.*; -import co.yixiang.modules.user.service.*; +import co.yixiang.modules.task.DelayJobService; +import co.yixiang.modules.user.entity.YxUser; +import co.yixiang.modules.user.entity.YxUserBill; +import co.yixiang.modules.user.entity.YxWechatUser; +import co.yixiang.modules.user.service.YxUserAddressService; +import co.yixiang.modules.user.service.YxUserBillService; +import co.yixiang.modules.user.service.YxUserService; +import co.yixiang.modules.user.service.YxWechatUserService; import co.yixiang.modules.user.web.vo.YxUserAddressQueryVo; import co.yixiang.modules.user.web.vo.YxUserQueryVo; -import co.yixiang.modules.task.DelayJobService; import co.yixiang.modules.user.web.vo.YxWechatUserQueryVo; import co.yixiang.modules.wechat.entity.YxWechatTemplate; -import co.yixiang.mp.service.WxMpTemplateMessageService; import co.yixiang.modules.wechat.service.YxWechatTemplateService; +import co.yixiang.mp.service.WxMpTemplateMessageService; import co.yixiang.service.AlipayService; import co.yixiang.utils.OrderUtil; import co.yixiang.utils.RedisUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; @@ -63,13 +73,10 @@ import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import lombok.extern.slf4j.Slf4j; +import org.n3r.idworker.Sid; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.beans.factory.annotation.Autowired; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.core.metadata.OrderItem; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import java.io.Serializable; import java.math.BigDecimal; @@ -168,6 +175,9 @@ public class YxStoreOrderServiceImpl extends BaseServiceImpl maxWorkerId || workerId < 0) { + int rand = new SecureRandom().nextInt((int) maxWorkerId + 1); + logger.warn("worker Id can't be greater than {} or less than 0, use a random {}", maxWorkerId, rand); + return rand; + } + + return workerId; + } + + public synchronized long nextId() { + long timestamp = millisGen(); + + if (timestamp < lastMillis) { + logger.error("clock is moving backwards. Rejecting requests until {}.", lastMillis); + throw new InvalidSystemClock(String.format( + "Clock moved backwards. Refusing to generate id for {} milliseconds", lastMillis - timestamp)); + } + + if (lastMillis == timestamp) { + sequence = (sequence + 1) & sequenceMask; + if (sequence == 0) + timestamp = tilNextMillis(lastMillis); + } else { + sequence = 0; + } + + lastMillis = timestamp; + long diff = timestamp - getEpoch(); + return (diff << timestampLeftShift) | + (workerId << workerIdShift) | + sequence; + } + + protected long tilNextMillis(long lastMillis) { + long millis = millisGen(); + while (millis <= lastMillis) + millis = millisGen(); + + return millis; + } + + protected long millisGen() { + return System.currentTimeMillis(); + } + + public long getLastMillis() { + return lastMillis; + } + + public long getWorkerId() { + return workerId; + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/InvalidSystemClock.java b/yshop-common/src/main/java/org/n3r/idworker/InvalidSystemClock.java new file mode 100644 index 00000000..24c4c1e2 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/InvalidSystemClock.java @@ -0,0 +1,7 @@ +package org.n3r.idworker; + +public class InvalidSystemClock extends RuntimeException { + public InvalidSystemClock(String message) { + super(message); + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/RandomCodeStrategy.java b/yshop-common/src/main/java/org/n3r/idworker/RandomCodeStrategy.java new file mode 100644 index 00000000..0a4c6b8e --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/RandomCodeStrategy.java @@ -0,0 +1,11 @@ +package org.n3r.idworker; + +public interface RandomCodeStrategy { + void init(); + + int prefix(); + + int next(); + + void release(); +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/Sid.java b/yshop-common/src/main/java/org/n3r/idworker/Sid.java new file mode 100644 index 00000000..7143f765 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/Sid.java @@ -0,0 +1,62 @@ +package org.n3r.idworker; + +import org.n3r.idworker.strategy.DefaultWorkerIdStrategy; +import org.n3r.idworker.utils.Utils; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.Date; + +@Component +public class Sid { + private static WorkerIdStrategy workerIdStrategy; + private static IdWorker idWorker; + + static { + configure(DefaultWorkerIdStrategy.instance); + } + + + public static synchronized void configure(WorkerIdStrategy custom) { + if (workerIdStrategy != null) workerIdStrategy.release(); + workerIdStrategy = custom; + idWorker = new IdWorker(workerIdStrategy.availableWorkerId()) { + @Override + public long getEpoch() { + return Utils.midnightMillis(); + } + }; + } + + /** + * 一天最大毫秒86400000,最大占用27比特 + * 27+10+11=48位 最大值281474976710655(15字),YK0XXHZ827(10字) + * 6位(YYMMDD)+15位,共21位 + * + * @return 固定21位数字字符串 + */ + + public static String next() { + long id = idWorker.nextId(); + String yyMMdd = new SimpleDateFormat("yyMMdd").format(new Date()); + return yyMMdd + String.format("%014d", id); + } + + + /** + * 返回固定16位的字母数字混编的字符串。 + */ + public String nextShort() { + long id = idWorker.nextId(); + String yyMMdd = new SimpleDateFormat("yyMMdd").format(new Date()); + return yyMMdd + Utils.padLeft(Utils.encode(id), 10, '0'); + } + +// public static void main(String[] args) { +// String aa = new Sid().nextShort(); +// String bb = new Sid().next(); +// +// System.out.println(aa); +// System.out.println(bb); +// } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/Test.java b/yshop-common/src/main/java/org/n3r/idworker/Test.java new file mode 100644 index 00000000..1ef1ca07 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/Test.java @@ -0,0 +1,12 @@ +package org.n3r.idworker; + +public class Test { + + public static void main(String[] args) { + + for (int i = 0 ; i < 1000 ; i ++) { +// System.out.println(Sid.nextShort()); + } + } + +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/WorkerIdStrategy.java b/yshop-common/src/main/java/org/n3r/idworker/WorkerIdStrategy.java new file mode 100644 index 00000000..f34d722b --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/WorkerIdStrategy.java @@ -0,0 +1,9 @@ +package org.n3r.idworker; + +public interface WorkerIdStrategy { + void initialize(); + + long availableWorkerId(); + + void release(); +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/strategy/DayPrefixRandomCodeStrategy.java b/yshop-common/src/main/java/org/n3r/idworker/strategy/DayPrefixRandomCodeStrategy.java new file mode 100644 index 00000000..b86bd1c8 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/strategy/DayPrefixRandomCodeStrategy.java @@ -0,0 +1,41 @@ +package org.n3r.idworker.strategy; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class DayPrefixRandomCodeStrategy extends DefaultRandomCodeStrategy { + private final String dayFormat; + private String lastDay; + + public DayPrefixRandomCodeStrategy(String dayFormat) { + this.dayFormat = dayFormat; + } + + @Override + public void init() { + String day = createDate(); + if (day.equals(lastDay)) + throw new RuntimeException("init failed for day unrolled"); + + lastDay = day; + + availableCodes.clear(); + release(); + + prefixIndex = Integer.parseInt(lastDay); + if (tryUsePrefix()) return; + + throw new RuntimeException("prefix is not available " + prefixIndex); + } + + private String createDate() { + return new SimpleDateFormat(dayFormat).format(new Date()); + } + + @Override + public int next() { + if (!lastDay.equals(createDate())) init(); + + return super.next(); + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultRandomCodeStrategy.java b/yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultRandomCodeStrategy.java new file mode 100644 index 00000000..5c9d8c7e --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultRandomCodeStrategy.java @@ -0,0 +1,197 @@ +package org.n3r.idworker.strategy; + +import org.n3r.idworker.Id; +import org.n3r.idworker.RandomCodeStrategy; +import org.n3r.idworker.utils.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.ArrayDeque; +import java.util.BitSet; +import java.util.Queue; + +public class DefaultRandomCodeStrategy implements RandomCodeStrategy { + public static final int MAX_BITS = 1000000; + + Logger log = LoggerFactory.getLogger(DefaultRandomCodeStrategy.class); + + File idWorkerHome = Utils.createIdWorkerHome(); + volatile FileLock fileLock; + BitSet codesFilter; + + int prefixIndex = -1; + File codePrefixIndex; + + int minRandomSize = 6; + int maxRandomSize = 6; + + public DefaultRandomCodeStrategy() { + destroyFileLockWhenShutdown(); + } + + @Override + public void init() { + release(); + + while (++prefixIndex < 1000) { + if (tryUsePrefix()) return; + } + + throw new RuntimeException("all prefixes are used up, the world maybe ends!"); + } + + public DefaultRandomCodeStrategy setMinRandomSize(int minRandomSize) { + this.minRandomSize = minRandomSize; + return this; + } + + public DefaultRandomCodeStrategy setMaxRandomSize(int maxRandomSize) { + this.maxRandomSize = maxRandomSize; + return this; + } + + protected boolean tryUsePrefix() { + codePrefixIndex = new File(idWorkerHome, Id.getWorkerId() + ".code.prefix." + prefixIndex); + + if (!createPrefixIndexFile()) return false; + if (!createFileLock()) return false; + if (!createBloomFilter()) return false; + + log.info("get available prefix index file {}", codePrefixIndex); + + return true; + } + + private boolean createFileLock() { + if (fileLock != null) fileLock.destroy(); + fileLock = new FileLock(codePrefixIndex); + return fileLock.tryLock(); + } + + private boolean createBloomFilter() { + codesFilter = fileLock.readObject(); + if (codesFilter == null) { + log.info("create new bloom filter"); + codesFilter = new BitSet(MAX_BITS); // 2^24 + } else { + int size = codesFilter.cardinality(); + if (size >= MAX_BITS) { + log.warn("bloom filter with prefix file {} is already full", codePrefixIndex); + return false; + } + log.info("recreate bloom filter with cardinality {}", size); + } + + return true; + } + + private void destroyFileLockWhenShutdown() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + release(); + } + }); + } + + private boolean createPrefixIndexFile() { + try { + codePrefixIndex.createNewFile(); + return codePrefixIndex.exists(); + } catch (IOException e) { + e.printStackTrace(); + log.warn("create file {} error {}", codePrefixIndex, e.getMessage()); + } + return false; + } + + @Override + public int prefix() { + return prefixIndex; + } + + static final int CACHE_CODES_NUM = 1000; + + SecureRandom secureRandom = new SecureRandom(); + Queue availableCodes = new ArrayDeque(CACHE_CODES_NUM); + + @Override + public int next() { + if (availableCodes.isEmpty()) generate(); + + return availableCodes.poll(); + } + + @Override + public synchronized void release() { + if (fileLock != null) { + fileLock.writeObject(codesFilter); + fileLock.destroy(); + fileLock = null; + } + } + + private void generate() { + for (int i = 0; i < CACHE_CODES_NUM; ++i) + availableCodes.add(generateOne()); + + fileLock.writeObject(codesFilter); + } + + private int generateOne() { + while (true) { + int code = secureRandom.nextInt(max(maxRandomSize)); + boolean existed = contains(code); + + code = !existed ? add(code) : tryFindAvailableCode(code); + if (code >= 0) return code; + + init(); + } + } + + private int tryFindAvailableCode(int code) { + int next = codesFilter.nextClearBit(code); + if (next != -1 && next < max(maxRandomSize)) return add(next); + + next = codesFilter.previousClearBit(code); + if (next != -1) return add(next); + + return -1; + } + + private int add(int code) { + codesFilter.set(code); + return code; + } + + private boolean contains(int code) { + return codesFilter.get(code); + } + + + private int max(int size) { + switch (size) { + case 1: // fall through + case 2: // fall through + case 3: // fall through + case 4: + return 10000; + case 5: + return 100000; + case 6: + return 1000000; + case 7: + return 10000000; + case 8: + return 100000000; + case 9: + return 1000000000; + default: + return Integer.MAX_VALUE; + } + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultWorkerIdStrategy.java b/yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultWorkerIdStrategy.java new file mode 100644 index 00000000..7b940bf6 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/strategy/DefaultWorkerIdStrategy.java @@ -0,0 +1,205 @@ +package org.n3r.idworker.strategy; + +import org.n3r.idworker.WorkerIdStrategy; +import org.n3r.idworker.utils.HttpReq; +import org.n3r.idworker.utils.Ip; +import org.n3r.idworker.utils.Props; +import org.n3r.idworker.utils.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Properties; +import java.util.Random; + +public class DefaultWorkerIdStrategy implements WorkerIdStrategy { + static long workerIdBits = 10L; + static long maxWorkerId = -1L ^ (-1L << workerIdBits); + static Random random = new SecureRandom(); + + public static final WorkerIdStrategy instance = new DefaultWorkerIdStrategy(); + + private final Properties props = + Props.tryProperties("idworker-client.properties", Utils.DOT_IDWORKERS); + private final String idWorkerServerUrl = + props.getProperty("server.address", "http://id.worker.server:18001"); + + String userName = System.getProperty("user.name"); + + String ipDotUsername = Ip.ip + "." + userName; + String ipudotlock = ipDotUsername + ".lock."; + int workerIdIndex = ipudotlock.length(); + long workerId; + FileLock fileLock; + + Logger logger = LoggerFactory.getLogger(DefaultWorkerIdStrategy.class); + private boolean inited; + + + private void init() { + workerId = findAvailWorkerId(); + if (workerId >= 0) { + destroyFileLockWhenShutdown(); + startSyncThread(); + } else { + syncWithWorkerIdServer(); + workerId = findAvailWorkerId(); + if (workerId < 0) workerId = increaseWithWorkerIdServer(); + } + + if (workerId < 0) workerId = tryToCreateOnIp(); + if (workerId < 0) { + logger.warn("DANGEROUS!!! Try to use random worker id."); + workerId = tryToRandomOnIp(); // Try avoiding! it could cause duplicated + } + + if (workerId < 0) { + logger.warn("the world may be ended!"); + throw new RuntimeException("the world may be ended"); + } + } + + private void destroyFileLockWhenShutdown() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + fileLock.destroy(); + } + }); + } + + private void startSyncThread() { + new Thread() { + @Override + public void run() { + syncWithWorkerIdServer(); + } + }.start(); + } + + private long increaseWithWorkerIdServer() { + String incId = HttpReq.get(idWorkerServerUrl) + .req("/inc") + .param("ipu", ipDotUsername) + .exec(); + if (incId == null || incId.trim().isEmpty()) return -1L; + + long lid = Long.parseLong(incId); + + return checkAvail(lid); + } + + private long tryToCreateOnIp() { + long wid = Ip.lip & maxWorkerId; + + return checkAvail(wid); + } + + private long tryToRandomOnIp() { + long avaiWorkerId = -1L; + long tryTimes = -1; + + while (avaiWorkerId < 0 && ++tryTimes < maxWorkerId) { + long wid = Ip.lip & random.nextInt((int) maxWorkerId); + + avaiWorkerId = checkAvail(wid); + } + return avaiWorkerId; + } + + private long checkAvail(long wid) { + long availWorkerId = -1L; + try { + File idWorkerHome = Utils.createIdWorkerHome(); + new File(idWorkerHome, ipudotlock + String.format("%04d", wid)).createNewFile(); + availWorkerId = findAvailWorkerId(); + } catch (IOException e) { + logger.warn("checkAvail error", e); + } + + return availWorkerId; + } + + private void syncWithWorkerIdServer() { + String syncIds = HttpReq.get(idWorkerServerUrl).req("/sync") + .param("ipu", ipDotUsername).param("ids", buildWorkerIdsOfCurrentIp()) + .exec(); + if (syncIds == null || syncIds.trim().isEmpty()) return; + + String[] syncIdsArr = syncIds.split(","); + File idWorkerHome = Utils.createIdWorkerHome(); + for (String syncId : syncIdsArr) { + try { + new File(idWorkerHome, ipudotlock + syncId).createNewFile(); + } catch (IOException e) { + logger.warn("create workerid lock file error", e); + } + } + } + + private String buildWorkerIdsOfCurrentIp() { + StringBuilder sb = new StringBuilder(); + File idWorkerHome = Utils.createIdWorkerHome(); + for (File lockFile : idWorkerHome.listFiles()) { + // check the format like 10.142.1.151.lock.0001 + if (!lockFile.getName().startsWith(ipudotlock)) continue; + + String workerId = lockFile.getName().substring(workerIdIndex); + if (!workerId.matches("\\d\\d\\d\\d")) continue; + + if (sb.length() > 0) sb.append(','); + sb.append(workerId); + } + + return sb.toString(); + } + + + /** + * Find the local available worker id. + * + * @return -1 when N/A + */ + private long findAvailWorkerId() { + File idWorkerHome = Utils.createIdWorkerHome(); + + for (File lockFile : idWorkerHome.listFiles()) { + // check the format like 10.142.1.151.lock.0001 + if (!lockFile.getName().startsWith(ipudotlock)) continue; + + String workerId = lockFile.getName().substring(workerIdIndex); + if (!workerId.matches("\\d\\d\\d\\d")) continue; + + FileLock fileLock = new FileLock(lockFile); + if (!fileLock.tryLock()) { + fileLock.destroy(); + continue; + } + + this.fileLock = fileLock; + return Long.parseLong(workerId); + } + + return -1; + } + + @Override + public void initialize() { + if (inited) return; + init(); + this.inited = true; + } + + @Override + public long availableWorkerId() { + return workerId; + } + + @Override + public void release() { + if (fileLock != null) fileLock.destroy(); + inited = false; + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/strategy/FileLock.java b/yshop-common/src/main/java/org/n3r/idworker/strategy/FileLock.java new file mode 100644 index 00000000..d2cf66f0 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/strategy/FileLock.java @@ -0,0 +1,132 @@ +package org.n3r.idworker.strategy; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.channels.Channels; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.OverlappingFileLockException; + +/** + * A file lock a la flock/funlock + *

+ * The given path will be created and opened if it doesn't exist. + */ +public class FileLock { + private final File file; + private FileChannel channel; + private java.nio.channels.FileLock flock = null; + Logger logger = LoggerFactory.getLogger(FileLock.class); + + public FileLock(File file) { + this.file = file; + + try { + file.createNewFile(); // create the file if it doesn't exist + channel = new RandomAccessFile(file, "rw").getChannel(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + /** + * Lock the file or throw an exception if the lock is already held + */ + public void lock() { + try { + synchronized (this) { + logger.trace("Acquiring lock on {}", file.getAbsolutePath()); + flock = channel.lock(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Try to lock the file and return true if the locking succeeds + */ + public boolean tryLock() { + synchronized (this) { + logger.trace("Acquiring lock on {}", file.getAbsolutePath()); + try { + // weirdly this method will return null if the lock is held by another + // process, but will throw an exception if the lock is held by this process + // so we have to handle both cases + flock = channel.tryLock(); + return flock != null; + } catch (OverlappingFileLockException e) { + return false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Unlock the lock if it is held + */ + public void unlock() { + synchronized (this) { + logger.trace("Releasing lock on {}", file.getAbsolutePath()); + if (flock == null) return; + try { + flock.release(); + } catch (ClosedChannelException e) { + // Ignore + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Destroy this lock, closing the associated FileChannel + */ + public void destroy() { + synchronized (this) { + unlock(); + if (!channel.isOpen()) return; + + try { + channel.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + + @SuppressWarnings("unchecked") + public T readObject() { + try { + InputStream is = Channels.newInputStream(channel); + ObjectInputStream objectReader = new ObjectInputStream(is); + return (T) objectReader.readObject(); + } catch (EOFException e) { + } catch (Exception e) { + throw new RuntimeException(e); + } + + return null; + } + + + public synchronized boolean writeObject(Object object) { + if (!channel.isOpen()) return false; + + try { + channel.position(0); + OutputStream out = Channels.newOutputStream(channel); + ObjectOutputStream objectOutput = new ObjectOutputStream(out); + objectOutput.writeObject(object); + return true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/utils/HttpReq.java b/yshop-common/src/main/java/org/n3r/idworker/utils/HttpReq.java new file mode 100644 index 00000000..f7d63816 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/utils/HttpReq.java @@ -0,0 +1,113 @@ +package org.n3r.idworker.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.*; + +public class HttpReq { + private final String baseUrl; + private String req; + private StringBuilder params = new StringBuilder(); + Logger logger = LoggerFactory.getLogger(HttpReq.class); + + public HttpReq(String baseUrl) { + this.baseUrl = baseUrl; + } + + public static HttpReq get(String baseUrl) { + return new HttpReq(baseUrl); + } + + public HttpReq req(String req) { + this.req = req; + return this; + } + + public HttpReq param(String name, String value) { + if (params.length() > 0) params.append('&'); + try { + params.append(name).append('=').append(URLEncoder.encode(value, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + return this; + } + + public String exec() { + HttpURLConnection http = null; + try { + http = (HttpURLConnection) new URL(baseUrl + + (req == null ? "" : req) + + (params.length() > 0 ? ("?" + params) : "")).openConnection(); + http.setRequestProperty("Accept-Charset", "UTF-8"); + HttpURLConnection.setFollowRedirects(false); + http.setConnectTimeout(5 * 1000); + http.setReadTimeout(5 * 1000); + http.connect(); + + int status = http.getResponseCode(); + String charset = getCharset(http.getHeaderField("Content-Type")); + + if (status == 200) { + return readResponseBody(http, charset); + } else { + logger.warn("non 200 respoonse :" + readErrorResponseBody(http, status, charset)); + return null; + } + } catch (Exception e) { + logger.error("exec error {}", e.getMessage()); + return null; + } finally { + if (http != null) http.disconnect(); + } + + } + + private static String readErrorResponseBody(HttpURLConnection http, int status, String charset) throws IOException { + InputStream errorStream = http.getErrorStream(); + if (errorStream != null) { + String error = toString(charset, errorStream); + return ("STATUS CODE =" + status + "\n\n" + error); + } else { + return ("STATUS CODE =" + status); + } + } + + private static String readResponseBody(HttpURLConnection http, String charset) throws IOException { + InputStream inputStream = http.getInputStream(); + + return toString(charset, inputStream); + } + + private static String toString(String charset, InputStream inputStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + + int length; + while ((length = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, length); + } + + return new String(baos.toByteArray(), charset); + } + + private static String getCharset(String contentType) { + if (contentType == null) return "UTF-8"; + + String charset = null; + for (String param : contentType.replace(" ", "").split(";")) { + if (param.startsWith("charset=")) { + charset = param.split("=", 2)[1]; + break; + } + } + + return charset == null ? "UTF-8" : charset; + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/utils/IPv4Utils.java b/yshop-common/src/main/java/org/n3r/idworker/utils/IPv4Utils.java new file mode 100644 index 00000000..fed7acc6 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/utils/IPv4Utils.java @@ -0,0 +1,60 @@ +package org.n3r.idworker.utils; + +/** + * This utility provides methods to either convert an IPv4 address to its long format or a 32bit dotted format. + * + * @author Aion + * Created on 22/11/12 + */ +public class IPv4Utils { + + /** + * Returns the long format of the provided IP address. + * + * @param ipAddress the IP address + * @return the long format of ipAddress + * @throws IllegalArgumentException if ipAddress is invalid + */ + public static long toLong(String ipAddress) { + if (ipAddress == null || ipAddress.isEmpty()) { + throw new IllegalArgumentException("ip address cannot be null or empty"); + } + String[] octets = ipAddress.split(java.util.regex.Pattern.quote(".")); + if (octets.length != 4) { + throw new IllegalArgumentException("invalid ip address"); + } + long ip = 0; + for (int i = 3; i >= 0; i--) { + long octet = Long.parseLong(octets[3 - i]); + if (octet > 255 || octet < 0) { + throw new IllegalArgumentException("invalid ip address"); + } + ip |= octet << (i * 8); + } + return ip; + } + + /** + * Returns the 32bit dotted format of the provided long ip. + * + * @param ip the long ip + * @return the 32bit dotted format of ip + * @throws IllegalArgumentException if ip is invalid + */ + public static String toString(long ip) { + // if ip is bigger than 255.255.255.255 or smaller than 0.0.0.0 + if (ip > 4294967295l || ip < 0) { + throw new IllegalArgumentException("invalid ip"); + } + StringBuilder ipAddress = new StringBuilder(); + for (int i = 3; i >= 0; i--) { + int shift = i * 8; + ipAddress.append((ip & (0xff << shift)) >> shift); + if (i > 0) { + ipAddress.append("."); + } + } + return ipAddress.toString(); + } + +} \ No newline at end of file diff --git a/yshop-common/src/main/java/org/n3r/idworker/utils/Ip.java b/yshop-common/src/main/java/org/n3r/idworker/utils/Ip.java new file mode 100644 index 00000000..7dca9da5 --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/utils/Ip.java @@ -0,0 +1,50 @@ +package org.n3r.idworker.utils; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +public class Ip { + static Logger logger = LoggerFactory.getLogger(Ip.class); + + public static String ip; + public static long lip; + + static { + try { + InetAddress localHostLANAddress = getFirstNonLoopbackAddress(); + ip = localHostLANAddress.getHostAddress(); + + byte[] address = localHostLANAddress.getAddress(); + lip = ((address [0] & 0xFFL) << (3*8)) + + ((address [1] & 0xFFL) << (2*8)) + + ((address [2] & 0xFFL) << (1*8)) + + (address [3] & 0xFFL); + } catch (Exception e) { + logger.error("get ipv4 failed ", e); + } + } + + private static InetAddress getFirstNonLoopbackAddress() throws SocketException { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + while (en.hasMoreElements()) { + NetworkInterface i = (NetworkInterface) en.nextElement(); + for (Enumeration en2 = i.getInetAddresses(); en2.hasMoreElements(); ) { + InetAddress addr = (InetAddress) en2.nextElement(); + if (addr.isLoopbackAddress()) continue; + + if (addr instanceof Inet4Address) { + return addr; + } + } + } + return null; + } + +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/utils/Props.java b/yshop-common/src/main/java/org/n3r/idworker/utils/Props.java new file mode 100644 index 00000000..d3d43d7e --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/utils/Props.java @@ -0,0 +1,70 @@ +package org.n3r.idworker.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.Properties; + +import static java.io.File.separator; +import static org.n3r.idworker.utils.Serializes.closeQuietly; + +public class Props { + static Logger log = LoggerFactory.getLogger(Props.class); + + public static Properties tryProperties(String propertiesFileName, String userHomeBasePath) { + Properties properties = new Properties(); + InputStream is = null; + try { + is = Props.tryResource(propertiesFileName, userHomeBasePath, Silent.ON); + if (is != null) properties.load(is); + } catch (IOException e) { + log.error("load properties error: {}", e.getMessage()); + } finally { + closeQuietly(is); + } + + return properties; + } + + + enum Silent {ON, OFF} + + public static InputStream tryResource(String propertiesFileName, String userHomeBasePath, Silent silent) { + InputStream is = currentDirResource(new File(propertiesFileName)); + if (is != null) return is; + + is = userHomeResource(propertiesFileName, userHomeBasePath); + if (is != null) return is; + + is = classpathResource(propertiesFileName); + if (is != null || silent == Silent.ON) return is; + + throw new RuntimeException("fail to find " + propertiesFileName + " in current dir or classpath"); + } + + private static InputStream userHomeResource(String pathname, String appHome) { + String filePath = System.getProperty("user.home") + separator + appHome; + File dir = new File(filePath); + if (!dir.exists()) return null; + + return currentDirResource(new File(dir, pathname)); + } + + private static InputStream currentDirResource(File file) { + if (!file.exists()) return null; + + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + // This should not happened + log.error("read file {} error", file, e); + return null; + } + } + + public static InputStream classpathResource(String resourceName) { + return Props.class.getClassLoader().getResourceAsStream(resourceName); + } + +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/utils/Serializes.java b/yshop-common/src/main/java/org/n3r/idworker/utils/Serializes.java new file mode 100644 index 00000000..859156cc --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/utils/Serializes.java @@ -0,0 +1,118 @@ +package org.n3r.idworker.utils; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +public class Serializes { + + @SuppressWarnings("unchecked") + public static List readObjects(File file) { + ArrayList objects = new ArrayList(); + ObjectInputStream objectReader = null; + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + objectReader = new ObjectInputStream(fis); + while (true) + objects.add((T) objectReader.readObject()); + + } catch (EOFException e) { + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + closeQuietly(objectReader); + closeQuietly(fis); + } + + return objects; + } + + + @SuppressWarnings("unchecked") + public static T readObject(File file) { + ObjectInputStream objectReader = null; + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + objectReader = new ObjectInputStream(fis); + return (T) objectReader.readObject(); + + } catch (EOFException e) { + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + closeQuietly(objectReader); + closeQuietly(fis); + } + + return null; + } + + public static void writeObject(File file, Object object) { + ObjectOutputStream objectOutput = null; + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + objectOutput = new ObjectOutputStream(fos); + objectOutput.writeObject(object); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + closeQuietly(objectOutput); + closeQuietly(fos); + } + } + + public static void writeObject(FileOutputStream fos, Object object) { + FileChannel channel = fos.getChannel(); + if (!channel.isOpen()) throw new RuntimeException("channel is closed"); + + try { + channel.position(0); + ObjectOutputStream objectOutput = new ObjectOutputStream(fos); + objectOutput.writeObject(object); + fos.flush(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + } + } + + public static void writeObjects(File file, Object... objects) { + ObjectOutputStream objectOutput = null; + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + objectOutput = new ObjectOutputStream(fos); + + for (Object object : objects) + objectOutput.writeObject(object); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + closeQuietly(objectOutput); + closeQuietly(fos); + } + + } + + public static void closeQuietly(OutputStream os) { + if (os != null) try { + os.close(); + } catch (IOException e) { + // ignore + } + } + + + public static void closeQuietly(InputStream is) { + if (is != null) try { + is.close(); + } catch (IOException e) { + // ignore + } + + } +} diff --git a/yshop-common/src/main/java/org/n3r/idworker/utils/Utils.java b/yshop-common/src/main/java/org/n3r/idworker/utils/Utils.java new file mode 100644 index 00000000..30f30e5a --- /dev/null +++ b/yshop-common/src/main/java/org/n3r/idworker/utils/Utils.java @@ -0,0 +1,114 @@ +package org.n3r.idworker.utils; + +import java.io.*; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +public class Utils { + + public static final String DOT_IDWORKERS = ".idworkers"; + + public static ClassLoader getClassLoader() { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + return contextClassLoader != null ? contextClassLoader : Utils.class.getClassLoader(); + } + + + public static InputStream classResourceToStream(String resourceName) { + return getClassLoader().getResourceAsStream(resourceName); + } + + + public static String firstLine(String classResourceName) { + InputStream inputStream = null; + try { + inputStream = classResourceToStream(classResourceName); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + + return bufferedReader.readLine(); + } catch (IOException e) { + return null; + } finally { + if (inputStream != null) try { + inputStream.close(); + } catch (IOException e) { + // ignore + } + } + } + + public static String checkNotEmpty(String param, String name) { + if (param == null || param.isEmpty()) + throw new IllegalArgumentException(name + " is empty"); + + return param; + } + + + public static long midnightMillis() { + // today + Calendar date = Calendar.getInstance(); + // reset hour, minutes, seconds and millis + date.set(Calendar.HOUR_OF_DAY, 0); + date.set(Calendar.MINUTE, 0); + date.set(Calendar.SECOND, 0); + date.set(Calendar.MILLISECOND, 0); + + return date.getTimeInMillis(); + } + + public static void main(String[] args) { + // 2013-12-25 00:00:00.000 + System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Timestamp(midnightMillis()))); + System.out.println(encode(281474976710655L)); + } + + public static long decode(String s, String symbols) { + final int B = symbols.length(); + long num = 0; + for (char ch : s.toCharArray()) { + num *= B; + num += symbols.indexOf(ch); + } + return num; + } + + public static String encode(long num) { + return encode(num, defaultRange); + } + + public static String encode(long num, String symbols) { + final int B = symbols.length(); + StringBuilder sb = new StringBuilder(); + while (num != 0) { + sb.append(symbols.charAt((int) (num % B))); + num /= B; + } + return sb.reverse().toString(); + } + + // all un-clearly-recognized letters are skiped. + static String defaultRange = "0123456789ABCDFGHKMNPRSTWXYZ"; + + public static String padLeft(String str, int size, char padChar) { + if (str.length() >= size) return str; + + StringBuilder s = new StringBuilder(); + for (int i = size - str.length(); i > 0; --i) { + s.append(padChar); + } + s.append(str); + + return s.toString(); + } + + public static File createIdWorkerHome() { + String userHome = System.getProperty("user.home"); + File idWorkerHome = new File(userHome + File.separator + DOT_IDWORKERS); + idWorkerHome.mkdirs(); + if (idWorkerHome.isDirectory()) return idWorkerHome; + + throw new RuntimeException("failed to create .idworkers at user home"); + } +}