diff --git a/doc/db/so-im.chnr.json b/doc/db/so-im.chnr.json index 54f518c..ef4f192 100644 --- a/doc/db/so-im.chnr.json +++ b/doc/db/so-im.chnr.json @@ -4,7 +4,7 @@ "avatar": "", "version": "3.5.7", "createdTime": "2022-3-7 15:11:08", - "updatedTime": "2022-4-1 17:26:44", + "updatedTime": "2022-4-2 15:02:54", "profile": { "default": { "db": "8FE7C65E-40EB-41B6-B60E-79063AFF6273", @@ -153,7 +153,7 @@ "type": "appCode", "applyFor": "1E385761-67B3-4B80-A194-E302E590E54E", "isDefault": true, - "content": "\n$blankline\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport java.util.Date;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n$blankline\n{{\n var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n var hours=today.getHours();\n var minutes=today.getMinutes();\n}}\n /**\n * @author : CHINER\n * @date : {{=fullYear}}-{{=month}}-{{=days}} {{=hours}}:{{=minutes}}\n * @desc : {{=it.entity.defName}}{{? it.entity.comment}},{{=it.entity.comment}}{{?}}\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Accessors(chain = true)\n@ApiModel(value = \"{{=it.entity.defName}}\"{{? it.entity.comment}}, description = \"{{=it.entity.comment}}\"{{?}})\n@TableName(\"{{=it.entity.defKey}}\")\npublic class {{=it.func.camel(it.entity.defKey,true) }} extends Model<{{=it.func.camel(it.entity.defKey,true) }}> {\n{{~it.entity.fields:field:index}}\n $blankline\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n @ApiModelProperty(\"{{=field.defName}}\")\n {{? field.primaryKey }}\n @TableId(value = \"{{=field.defKey}}\"{{? field.autoIncrement}}, type=IdType.AUTO{{?}})\n {{??}}\n @TableField(value = \"{{=field.defKey}}\")\n {{?}}\n private {{=field.type}} {{=it.func.camel(field.defKey,false)}};\n{{~}}\n$blankline\n}\n$blankline" + "content": "\n$blankline\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport java.util.Date;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n$blankline\n{{\n var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n var hours=today.getHours();\n var minutes=today.getMinutes();\n}}\n /**\n * @author : CHINER\n * @date : {{=fullYear}}-{{=month}}-{{=days}} {{=hours}}:{{=minutes}}\n * @desc : {{=it.entity.defName}}{{? it.entity.comment}},{{=it.entity.comment}}{{?}}\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Accessors(chain = true)\n@ApiModel(value = \"{{=it.entity.defName}}\"{{? it.entity.comment}}, description = \"{{=it.entity.comment}}\"{{?}})\n@TableName(\"{{=it.entity.defKey}}\")\npublic class {{=it.func.camel(it.entity.defKey,true) }} extends Model<{{=it.func.camel(it.entity.defKey,true) }}> {\n{{~it.entity.fields:field:index}}\n $blankline\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n {{? field.primaryKey }}\n @TableId(value = \"{{=field.defKey}}\"{{? field.autoIncrement}}, type=IdType.AUTO{{?}})\n {{??}}\n @TableField(value = \"{{=field.defKey}}\")\n {{?}}\n private {{=field.type}} {{=it.func.camel(field.defKey,false)}};\n{{~}}\n$blankline\n}\n$blankline" }, { "type": "dbDDL", @@ -223,7 +223,7 @@ "id": "9965DCFB-54BE-4C5A-9518-DF09D4E58140" } ], - "menuWidth": "280px" + "menuWidth": "244px" }, "entities": [ { @@ -995,6 +995,408 @@ ], "correlations": [], "indexes": [] + }, + { + "id": "E79C9C4C-FFC3-44BB-BD98-4F44F528B4B8", + "defKey": "im_segment_id", + "defName": "分段id", + "comment": "", + "properties": {}, + "nameTemplate": "{defKey}[{defName}]", + "headers": [ + { + "freeze": false, + "refKey": "hideInGraph", + "hideInGraph": true + }, + { + "freeze": true, + "refKey": "defKey", + "hideInGraph": false + }, + { + "freeze": true, + "refKey": "defName", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "primaryKey", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "notNull", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "autoIncrement", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "domain", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "type", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "len", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "scale", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "comment", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "refDict", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "defaultValue", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "isStandard", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "uiHint", + "hideInGraph": true + } + ], + "fields": [ + { + "defKey": "biz_tag", + "defName": "业务标签", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": true, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "BB0241B4-A9DB-46CB-AB7D-EA6DDA6A02D6", + "id": "F6D445F1-ADD3-4A28-AFDA-2606A912C007" + }, + { + "defKey": "current_id", + "defName": "当前id值", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "3E9E2CBB-B458-49D5-B6DC-454A11FB9633" + }, + { + "defKey": "init_step", + "defName": "业务标签初始步长", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "38C65486-2A4E-4862-9875-EFE113FFD627" + }, + { + "defKey": "create_time", + "defName": "创建时间", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "4E3ADE61-41F4-4E74-8068-C1E543F9095C", + "id": "87279682-D6B5-4BA0-9BE5-03FC436313C9" + }, + { + "defKey": "update_time", + "defName": "更新时间", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "4E3ADE61-41F4-4E74-8068-C1E543F9095C", + "id": "1F373AF6-4C60-4AC4-B75F-604B91AEC0C6" + }, + { + "defKey": "version", + "defName": "版本号", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "D376659D-0FFA-4D1A-AA3C-0C5CBA66B41B" + } + ], + "correlations": [], + "indexes": [] + }, + { + "id": "A843E8FC-C8E8-4F0A-8EB0-C5C104BE241F", + "defKey": "im_segment_fragment", + "defName": "分段id碎片id段(id端回收)", + "comment": "", + "properties": {}, + "nameTemplate": "{defKey}[{defName}]", + "headers": [ + { + "freeze": false, + "refKey": "hideInGraph", + "hideInGraph": true + }, + { + "freeze": true, + "refKey": "defKey", + "hideInGraph": false + }, + { + "freeze": true, + "refKey": "defName", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "primaryKey", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "notNull", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "autoIncrement", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "domain", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "type", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "len", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "scale", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "comment", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "refDict", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "defaultValue", + "hideInGraph": true + }, + { + "freeze": false, + "refKey": "isStandard", + "hideInGraph": false + }, + { + "freeze": false, + "refKey": "uiHint", + "hideInGraph": true + } + ], + "fields": [ + { + "defKey": "id", + "defName": "主键id", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": true, + "notNull": true, + "autoIncrement": true, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "32D9CFBB-3E32-4614-B576-E0E062E8FBF6" + }, + { + "defKey": "biz_tag", + "defName": "业务标签", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "BB0241B4-A9DB-46CB-AB7D-EA6DDA6A02D6", + "id": "031DE904-D768-4F8B-958C-B97213421A46" + }, + { + "defKey": "min_id", + "defName": "id段最小值", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "BF29BB31-2AA3-4CF3-BD29-304DA6F60F97" + }, + { + "defKey": "end_id", + "defName": "id段最大值", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "27254611-5E86-4EAA-BADA-AE78CAD14528" + }, + { + "defKey": "begin_id", + "defName": "id段当前值", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "BEBCA2FF-01B5-4834-8218-7F7051250C3D" + }, + { + "defKey": "create_time", + "defName": "创建时间", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "72DD10F8-60AE-4F2A-89E1-D69C51B30849", + "id": "4989C582-86B0-4011-A3D7-A2C43FFC3F76" + }, + { + "defKey": "update_time", + "defName": "更新时间", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "72DD10F8-60AE-4F2A-89E1-D69C51B30849", + "id": "9DC77CD9-4006-42B2-9F63-13B309BC7BA1" + }, + { + "defKey": "version", + "defName": "版本号", + "comment": "", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "domain": "1E5537DF-B204-49BC-AC1D-457BC6DEE707", + "id": "C83AAC89-59CB-49E9-8C8F-2F5796BF65FE" + } + ], + "correlations": [], + "indexes": [] } ], "views": [], diff --git a/im-common/src/main/java/net/sopod/soim/common/util/ObjectUtil.java b/im-common/src/main/java/net/sopod/soim/common/util/ObjectUtil.java new file mode 100644 index 0000000..11b6bcb --- /dev/null +++ b/im-common/src/main/java/net/sopod/soim/common/util/ObjectUtil.java @@ -0,0 +1,29 @@ +package net.sopod.soim.common.util; + +import com.google.common.base.Preconditions; + +/** + * ObjectUtil + * + * @author tmy + * @date 2022-04-02 16:54 + */ +public class ObjectUtil { + + public static T defaultValue(T value, T...defaultValues) { + if (value != null) { + return value; + } + Preconditions.checkArgument(defaultValues != null && defaultValues.length > 0, "默认值不能为空"); + T result = null; + for (T defaultValue : defaultValues) { + if (defaultValue != null) { + result = defaultValue; + break; + } + } + Preconditions.checkNotNull(result, "默认值至少一个不为空"); + return result; + } + +} diff --git a/im-das-api/im-das-user-api/pom.xml b/im-das-api/im-das-user-api/pom.xml index 0573fc3..4cd41e5 100644 --- a/im-das-api/im-das-user-api/pom.xml +++ b/im-das-api/im-das-user-api/pom.xml @@ -17,11 +17,6 @@ mybatis-plus-annotation 3.5.0 - - org.projectlombok - lombok - provided - \ No newline at end of file diff --git a/im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/test/DasUserApplicationTest.java b/im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/test/DasUserApplicationTest.java index 169c44c..8f0e4c5 100644 --- a/im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/test/DasUserApplicationTest.java +++ b/im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/test/DasUserApplicationTest.java @@ -26,8 +26,8 @@ public class DasUserApplicationTest { @Test public void test() { ImUser user1 = new ImUser() - .setId(4L) - .setUsername("野孩子2") + .setId(5L) + .setUsername("野孩子5") .setPassword("123456") .setPhone("17882451908") .setCreateTime(ImClock.date()); diff --git a/im-logic-api/im-segment-id-api/pom.xml b/im-logic-api/im-segment-id-api/pom.xml new file mode 100644 index 0000000..ed9ccff --- /dev/null +++ b/im-logic-api/im-segment-id-api/pom.xml @@ -0,0 +1,16 @@ + + + + so-im + net.sopod + 1.0.0 + ../../pom.xml + + 4.0.0 + + im-segment-id-api + + + \ No newline at end of file diff --git a/im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/model/Segment.java b/im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/model/Segment.java new file mode 100644 index 0000000..d23c0c9 --- /dev/null +++ b/im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/model/Segment.java @@ -0,0 +1,13 @@ +package net.sopod.soim.logic.segmentid.model; + +/** + * Segment + * + * @author tmy + * @date 2022-04-02 11:58 + */ +public class Segment { + + + +} diff --git a/im-logic/im-segment-id/pom.xml b/im-logic/im-segment-id/pom.xml new file mode 100644 index 0000000..0d429c7 --- /dev/null +++ b/im-logic/im-segment-id/pom.xml @@ -0,0 +1,78 @@ + + + + im-logic + net.sopod + 1.0.0 + + 4.0.0 + + im-segment-id + + + + net.sopod + im-segment-id-api + ${soim.version} + + + net.sopod + im-common + ${soim.version} + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.apache.dubbo + dubbo-spring-boot-starter + + + org.apache.dubbo + dubbo-registry-nacos + + + mysql + mysql-connector-java + + + com.zaxxer + HikariCP + + + com.baomidou + mybatis-plus-boot-starter + + + org.springframework + spring-orm + + + \ No newline at end of file diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/SegmentIdApplication.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/SegmentIdApplication.java new file mode 100644 index 0000000..71be1fb --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/SegmentIdApplication.java @@ -0,0 +1,21 @@ +package net.sopod.soim.logic.segmentid; + +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * SegmentIdApplication + * + * @author tmy + * @date 2022-04-02 14:55 + */ +@EnableDubbo(scanBasePackages = {"net.sopod.soim.logic.segmentid.service"}) +@SpringBootApplication +public class SegmentIdApplication { + + public static void main(String[] args) { + SpringApplication.run(SegmentIdApplication.class); + } + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/config/SegmentConfig.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/config/SegmentConfig.java new file mode 100644 index 0000000..8e1210b --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/config/SegmentConfig.java @@ -0,0 +1,28 @@ +package net.sopod.soim.logic.segmentid.config; + +import lombok.Data; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +/** + * SegmentConfigration + * + * @author tmy + * @date 2022-04-02 17:02 + */ +@EnableConfigurationProperties +@Configuration() +@Component +@Data +public class SegmentConfig { + + public static final String KEY_PREFIX_SEGMENT_ID_INSERT = "SEGMENT_ID_INSERT_"; + + private long initId = 10000L; + + private long initStep = 1000L; + + private int dbSegmentVersionRetryTimes = 3; + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/mapper/SegmentIdMapper.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/mapper/SegmentIdMapper.java new file mode 100644 index 0000000..f0ff626 --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/mapper/SegmentIdMapper.java @@ -0,0 +1,14 @@ +package net.sopod.soim.logic.segmentid.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.sopod.soim.logic.segmentid.model.entity.SegmentId; + +/** + * SegmentIdMapper + * + * @author tmy + * @date 2022-04-02 15:00 + */ +public interface SegmentIdMapper extends BaseMapper { + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/dto/NextSegmentParam.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/dto/NextSegmentParam.java new file mode 100644 index 0000000..60270f0 --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/dto/NextSegmentParam.java @@ -0,0 +1,20 @@ +package net.sopod.soim.logic.segmentid.model.dto; + +import lombok.Data; + +/** + * NextSegmentParam + * + * @author tmy + * @date 2022-04-02 16:51 + */ +@Data +public class NextSegmentParam { + + /** 业务标签 */ + private String bizTag; + + /** 步长,为空则使用数据库默认 */ + private Long step; + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/dto/SegmentDTO.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/dto/SegmentDTO.java new file mode 100644 index 0000000..44ec87b --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/dto/SegmentDTO.java @@ -0,0 +1,20 @@ +package net.sopod.soim.logic.segmentid.model.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * SegmentDTO + * + * @author tmy + * @date 2022-04-02 17:27 + */ +@Data +@Accessors(chain = true) +public class SegmentDTO { + + private long beginId; + + private long endId; + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/entity/SegmentId.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/entity/SegmentId.java new file mode 100644 index 0000000..5b93074 --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/model/entity/SegmentId.java @@ -0,0 +1,47 @@ +package net.sopod.soim.logic.segmentid.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * SegmentId + * + * @author tmy + * @date 2022-04-02 15:01 + */ +@Data +@Accessors(chain = true) +@TableName("im_segment_id") +public class SegmentId { + + /** 业务标签 */ + @TableId(value = "biz_tag", type = IdType.INPUT) + private String bizTag; + + /** 当前id值 */ + @TableField(value = "current_id") + private Long currentId; + + /** 业务标签初始步长 */ + @TableField(value = "init_step") + private Long initStep; + + /** 创建时间 */ + @TableField(value = "create_time") + private Date createTime; + + /** 更新时间 */ + @TableField(value = "update_time") + private Date updateTime; + + /** 版本号 */ + @TableField(value = "version") + private Long version; + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/service/SegmentIdService.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/service/SegmentIdService.java new file mode 100644 index 0000000..f4457f1 --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/service/SegmentIdService.java @@ -0,0 +1,139 @@ +package net.sopod.soim.logic.segmentid.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.google.common.base.Preconditions; +import lombok.AllArgsConstructor; +import net.sopod.soim.common.util.ImClock; +import net.sopod.soim.common.util.ObjectUtil; +import net.sopod.soim.logic.segmentid.config.SegmentConfig; +import net.sopod.soim.logic.segmentid.mapper.SegmentIdMapper; +import net.sopod.soim.logic.segmentid.model.dto.NextSegmentParam; +import net.sopod.soim.logic.segmentid.model.dto.SegmentDTO; +import net.sopod.soim.logic.segmentid.model.entity.SegmentId; +import net.sopod.soim.logic.segmentid.util.RedisLockUtil; +import org.apache.dubbo.config.annotation.DubboService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * SegmentIdService + * + * @author tmy + * @date 2022-04-02 15:22 + */ +@DubboService +@AllArgsConstructor +public class SegmentIdService { + + private static final Logger logger = LoggerFactory.getLogger(SegmentIdService.class); + + private final SegmentIdMapper segmentIdMapper; + + private final RedisTemplate redisTemplate; + + private final SegmentConfig segmentConfig; + + public SegmentDTO nextSegmentId(NextSegmentParam param) { + String bizTag = param.getBizTag(); + SegmentId segmentId = segmentIdMapper.selectById(bizTag); + if (segmentId == null) { + String key = SegmentConfig.KEY_PREFIX_SEGMENT_ID_INSERT + bizTag; + String val = String.valueOf(ThreadLocalRandom.current().nextInt()); + // 锁二十秒,创建数据 + boolean locked = RedisLockUtil.acquireLock(redisTemplate, key, val, 20000L); + if (locked) { + try { + // 插入分段id数据 + SegmentId newSegmentId = new SegmentId() + .setBizTag(bizTag) + .setCreateTime(ImClock.date()) + .setCurrentId(segmentConfig.getInitId()) + .setVersion(0L) + .setInitStep(segmentConfig.getInitStep()); + segmentIdMapper.insert(newSegmentId); + segmentId = newSegmentId; + } finally { + // 释放锁 + RedisLockUtil.releaseLock(redisTemplate, key, val); + } + } else { + // 等待锁释放 + long timeout = 2000L; + boolean released = waitReleaseRedisLock(key, timeout); + if (!released) { + logger.warn("{} 插入数据超过 {} 未释放", bizTag, timeout); + } + segmentId = segmentIdMapper.selectById(bizTag); + } + if (segmentId == null) { + throw new RuntimeException(String.format("%s分段数据不存在,新增失败", bizTag)); + } + } + int i = 0; + do { + SegmentDTO segment = getSegment(segmentId, param.getStep()); + if (segment != null) { + return segment; + } + i++; + } while (i < segmentConfig.getDbSegmentVersionRetryTimes()); + + throw new RuntimeException("id分段获取失败"); + } + + private boolean waitReleaseRedisLock(String key, long timeout) { + long begin = ImClock.millis(); + // while 阻塞等待 + while (true) { + long current = ImClock.millis(); + if (!RedisLockUtil.hasKey(redisTemplate, key)) { + return true; + } + if (current - begin > timeout) { + return false; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + logger.error("wait release redis lock interrupted!", e); + } + } + } + + /** + * 获取id段 + * @param dbSegmentId 当前DB segmentId + * @param step 步长 + */ + private SegmentDTO getSegment(SegmentId dbSegmentId, Long step) { + step = ObjectUtil.defaultValue(step, dbSegmentId.getInitStep(), null); + Preconditions.checkState(step > 0, "步长需大于0"); + // 开始id, 结束id + Long currentId = dbSegmentId.getCurrentId(); + long endId = dbSegmentId.getCurrentId() + step; + // 版本号更新条件 + LambdaQueryWrapper segmentIdUpdate = new QueryWrapper().lambda() + .eq(SegmentId::getBizTag, dbSegmentId.getBizTag()) + .eq(SegmentId::getVersion, dbSegmentId.getVersion()); + SegmentId newSegment = new SegmentId() + .setBizTag(dbSegmentId.getBizTag()) + .setUpdateTime(ImClock.date()) + .setCurrentId(endId + 1) + .setVersion(dbSegmentId.getVersion() + 1); + // 根据版本号条件尝试更新 + int row = segmentIdMapper.update(newSegment, segmentIdUpdate); + if (row == 0) { + // 未更新成功,被其他连接并发更新 + return null; + } + // 更新成功 + return new SegmentDTO() + .setBeginId(currentId) + .setEndId(endId); + } + +} diff --git a/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/util/RedisLockUtil.java b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/util/RedisLockUtil.java new file mode 100644 index 0000000..fdfb607 --- /dev/null +++ b/im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/util/RedisLockUtil.java @@ -0,0 +1,120 @@ +package net.sopod.soim.logic.segmentid.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +/** + * Redis分布式锁工具 + * + * @author tangmingyou + * @date 2022-03-18 14:21 + */ +public class RedisLockUtil { + + private static final Logger logger = LoggerFactory.getLogger(RedisLockUtil.class); + + private static final RedisScript SCRIPT_LOCK = new DefaultRedisScript<>("return redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2])", String.class); + private static final RedisScript SCRIPT_UNLOCK = new DefaultRedisScript<>("if redis.call('get',KEYS[1]) == ARGV[1] then return tostring(redis.call('del', KEYS[1])==1) else return 'false' end", String.class); + private static final String LOCK_SUCCESS = "OK"; + + /** + * 加锁,往 redis 中设置值并设置过期时间 + * @param lockKey 键 + * @param lockValue 值 + * @param acquireExpire 超期时间(毫秒) + * @return 是否获取成功 + */ + public static boolean acquireLock(RedisTemplate redisTemplate, + String lockKey, String lockValue, long acquireExpire) { + Object lockResult = redisTemplate.execute(SCRIPT_LOCK, + redisTemplate.getStringSerializer(), + redisTemplate.getStringSerializer(), + Collections.singletonList(lockKey), + lockValue, String.valueOf(acquireExpire)); + // 加锁失败 lockResult 为 null + return LOCK_SUCCESS.equals(lockResult); + } + + /** + *
+     * 为何解锁需要校验lockValue
+     * 客户端A加锁,一段时间之后客户端A解锁,在执行releaseLock之前,锁突然过期了。
+     * 此时客户端B尝试加锁成功,然后客户端A再执行releaseLock方法,则将客户端B的锁给解除了。
+     * 
+ * @param lockKey redis 键 + * @param lockValue lock 的值 + * @return 是否成功 + */ + public static boolean releaseLock(RedisTemplate redisTemplate, + String lockKey, String lockValue) { + Object releaseResult = redisTemplate.execute(SCRIPT_UNLOCK, + redisTemplate.getStringSerializer(), + redisTemplate.getStringSerializer(), + Collections.singletonList(lockKey), + lockValue); + return Boolean.parseBoolean(releaseResult.toString()); + } + + /** + * 指定缓存失效时间 + * @param key 键 + * @param time 时间(秒) + * @return 是否成功 + */ + public static boolean expire(RedisTemplate redisTemplate, + String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + throw new RuntimeException("指定缓存失效时间异常"); + } + } + + /** + * 根据key 获取过期时间 + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public static long getExpire(RedisTemplate redisTemplate, String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * @param key 键 + * @return true 存在 false不存在 + */ + public static boolean hasKey(RedisTemplate redisTemplate, String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除缓存 + * @param keys 可以传一个值 或多个 + */ + public static void del(RedisTemplate redisTemplate, String... keys) { + if (keys != null && keys.length > 0) { + if (keys.length == 1) { + redisTemplate.delete(keys[0]); + } else { + redisTemplate.delete(Arrays.asList(keys)); + } + } + } + +} diff --git a/im-logic/im-segment-id/src/main/resources/application.yml b/im-logic/im-segment-id/src/main/resources/application.yml new file mode 100644 index 0000000..479f900 --- /dev/null +++ b/im-logic/im-segment-id/src/main/resources/application.yml @@ -0,0 +1,22 @@ +spring: + application: + name: segment-id + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://cd-cdb-mrz9fw80.sql.tencentcdb.com:61843/soim_db?serverTimezone=GMT%2B8 + username: root + password: sopod@2347# + redis: + host: 124.222.131.236:3379 + password: sopod@redis# + +dubbo: + application: + name: ${spring.application.name} + registry: + address: nacos://124.222.131.236:3848 + protocol: + port: 3001 + +mybatis-plus: + mapper-locations: classpath:mapper/*.xml diff --git a/im-logic/im-segment-id/src/main/resources/mapper/SegmentIdMapper.xml b/im-logic/im-segment-id/src/main/resources/mapper/SegmentIdMapper.xml new file mode 100644 index 0000000..ce0703f --- /dev/null +++ b/im-logic/im-segment-id/src/main/resources/mapper/SegmentIdMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 439096b..e616194 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,8 @@ im-core im-das im-das-api + im-logic-api/im-segment-id-api + im-logic/im-segment-id @@ -122,6 +124,11 @@ spring-boot-starter-log4j2 ${spring-boot.version} + + org.springframework.boot + spring-boot-starter-data-redis + ${spring-boot.version} + org.apache.dubbo dubbo-spring-boot-starter @@ -208,6 +215,11 @@ com.lmax disruptor + + org.projectlombok + lombok + provided +