diff --git a/server/skillhub-domain/pom.xml b/server/skillhub-domain/pom.xml
index 32d91ae0..8c546ed5 100644
--- a/server/skillhub-domain/pom.xml
+++ b/server/skillhub-domain/pom.xml
@@ -15,5 +15,10 @@
jakarta.persistence
jakarta.persistence-api
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/SlugValidator.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/SlugValidator.java
new file mode 100644
index 00000000..c166cd49
--- /dev/null
+++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/SlugValidator.java
@@ -0,0 +1,36 @@
+package com.iflytek.skillhub.domain.namespace;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class SlugValidator {
+
+ private static final int MIN_LENGTH = 2;
+ private static final int MAX_LENGTH = 64;
+ private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]([a-z0-9-]*[a-z0-9])?$");
+ private static final Set RESERVED_SLUGS = Set.of(
+ "admin", "api", "dashboard", "search", "auth",
+ "me", "global", "system", "static", "assets", "health"
+ );
+
+ public static void validate(String slug) {
+ if (slug == null || slug.isBlank()) {
+ throw new IllegalArgumentException("Slug cannot be null or blank");
+ }
+ if (slug.length() < MIN_LENGTH || slug.length() > MAX_LENGTH) {
+ throw new IllegalArgumentException(
+ String.format("Slug length must be between %d and %d characters", MIN_LENGTH, MAX_LENGTH));
+ }
+ if (!SLUG_PATTERN.matcher(slug).matches()) {
+ throw new IllegalArgumentException(
+ "Slug must contain only lowercase alphanumeric characters and hyphens, " +
+ "and must start and end with an alphanumeric character");
+ }
+ if (slug.contains("--")) {
+ throw new IllegalArgumentException("Slug cannot contain consecutive hyphens");
+ }
+ if (RESERVED_SLUGS.contains(slug)) {
+ throw new IllegalArgumentException("Slug '" + slug + "' is reserved and cannot be used");
+ }
+ }
+}
diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/namespace/SlugValidatorTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/namespace/SlugValidatorTest.java
new file mode 100644
index 00000000..1caf9d39
--- /dev/null
+++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/namespace/SlugValidatorTest.java
@@ -0,0 +1,57 @@
+package com.iflytek.skillhub.domain.namespace;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class SlugValidatorTest {
+ @Test
+ void shouldAcceptValidSlug() {
+ assertDoesNotThrow(() -> SlugValidator.validate("my-namespace"));
+ assertDoesNotThrow(() -> SlugValidator.validate("ab"));
+ assertDoesNotThrow(() -> SlugValidator.validate("test123"));
+ assertDoesNotThrow(() -> SlugValidator.validate("my-team-2024"));
+ }
+ @Test
+ void shouldRejectTooShort() {
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("a"));
+ assertTrue(ex.getMessage().contains("length"));
+ }
+ @Test
+ void shouldRejectTooLong() {
+ String longSlug = "a".repeat(65);
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate(longSlug));
+ assertTrue(ex.getMessage().contains("length"));
+ }
+ @Test
+ void shouldRejectUpperCase() {
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("MyNamespace"));
+ assertTrue(ex.getMessage().contains("lowercase"));
+ }
+ @Test
+ void shouldRejectStartingWithHyphen() {
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("-namespace"));
+ assertTrue(ex.getMessage().contains("alphanumeric"));
+ }
+ @Test
+ void shouldRejectEndingWithHyphen() {
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("namespace-"));
+ assertTrue(ex.getMessage().contains("alphanumeric"));
+ }
+ @Test
+ void shouldRejectDoubleHyphen() {
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("my--namespace"));
+ assertTrue(ex.getMessage().contains("consecutive"));
+ }
+ @Test
+ void shouldRejectReservedWords() {
+ assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("admin"));
+ assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("api"));
+ assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("global"));
+ assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("system"));
+ }
+ @Test
+ void shouldRejectSpecialCharacters() {
+ Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("my_namespace"));
+ assertTrue(ex.getMessage().contains("lowercase") || ex.getMessage().contains("alphanumeric"));
+ }
+}