diff --git a/.env.example b/.env.example index 044c869d4..9a5f2670f 100755 --- a/.env.example +++ b/.env.example @@ -1,25 +1,6 @@ VOLCENGINE_TOS_ACCESS_KEY= VOLCENGINE_TOS_SECRET_KEY= -# MinIO Configuration -MINIO_USER= -MINIO_PASSWORD= -MINIO_PORT= - -# Global Domain Configuration -GLOBAL_DOMAIN= -GLOBAL_IP= - -# Database Configuration -PG_USER= -PG_PASSWORD= -PG_DB= -PG_PORT= - -# Redis Configuration -REDIS_PASSWORD= -REDIS_PORT= - # WeChat Configuration (placeholders) WXPA_VERIFY_TOKEN= WXPA_APP_ID= diff --git a/.idea/modules/compose-server.iml b/.idea/modules/compose-server.iml index b8ff23d5c..331ff96a1 100644 --- a/.idea/modules/compose-server.iml +++ b/.idea/modules/compose-server.iml @@ -14,6 +14,7 @@ + \ No newline at end of file diff --git a/cacheable/build.gradle.kts b/cacheable/build.gradle.kts index aec450a5a..88f559274 100644 --- a/cacheable/build.gradle.kts +++ b/cacheable/build.gradle.kts @@ -11,7 +11,7 @@ Includes Redis integration for distributed caching and Caffeine for high-perform .trimIndent() dependencies { - implementation(libs.org.springframework.boot.spring.boot.starter.data.redis) + api(libs.org.springframework.boot.spring.boot.starter.data.redis) implementation(libs.org.apache.commons.commons.pool2) implementation(libs.com.github.ben.manes.caffeine.caffeine) diff --git a/cacheable/src/main/kotlin/io/github/truenine/composeserver/cacheable/autoconfig/RedisJsonSerializerAutoConfiguration.kt b/cacheable/src/main/kotlin/io/github/truenine/composeserver/cacheable/autoconfig/RedisJsonSerializerAutoConfiguration.kt index 18c1c956d..d826caf9b 100644 --- a/cacheable/src/main/kotlin/io/github/truenine/composeserver/cacheable/autoconfig/RedisJsonSerializerAutoConfiguration.kt +++ b/cacheable/src/main/kotlin/io/github/truenine/composeserver/cacheable/autoconfig/RedisJsonSerializerAutoConfiguration.kt @@ -43,7 +43,6 @@ class RedisJsonSerializerAutoConfiguration(@Qualifier(JacksonAutoConfiguration.N .disableCachingNullValues() @Bean(name = [ICacheNames.IRedis.HANDLE]) - @ConditionalOnBean(name = [VIRTUAL_THREAD_REDIS_FACTORY_BEAN_NAME]) fun customRedisJsonSerializable(@Qualifier(VIRTUAL_THREAD_REDIS_FACTORY_BEAN_NAME) factory: RedisConnectionFactory): RedisTemplate { log.trace("register redisTemplate factory: {}", factory) val rt = RedisTemplate() @@ -58,7 +57,6 @@ class RedisJsonSerializerAutoConfiguration(@Qualifier(JacksonAutoConfiguration.N } @Bean(name = [ICacheNames.IRedis.CACHE_MANAGER]) - @ConditionalOnBean(name = [VIRTUAL_THREAD_REDIS_FACTORY_BEAN_NAME]) fun cacheManager2h(@Qualifier(VIRTUAL_THREAD_REDIS_FACTORY_BEAN_NAME) factory: RedisConnectionFactory): RedisCacheManager { log.debug("register RedisCacheManager , factory: {}", factory) return asCacheConfig(factory) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 741138d99..5dee51b55 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,7 @@ org-springframework-modulith = "2.0.0-M1" org-springframework-security = "6.5.5" org-testcontainers = "1.21.3" org-testng = "7.11.0" -project = "0.0.37" +project = "0.0.38" [libraries] ch-qos-logback-logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "ch-qos-logback" } diff --git a/oss/oss-volcengine-tos/.env.example b/oss/oss-volcengine-tos/.env.example deleted file mode 100644 index addc616d5..000000000 --- a/oss/oss-volcengine-tos/.env.example +++ /dev/null @@ -1,13 +0,0 @@ -# Volcengine TOS 环境变量配置示例 -# 复制此文件为 .env 并填入真实的配置值 - -# Volcengine TOS Access Key ID -VOLCENGINE_TOS_ACCESS_KEY=your_access_key_here - -# Volcengine TOS Secret Access Key -VOLCENGINE_TOS_SECRET_KEY=your_secret_key_here - -# 注意: -# 1. 请勿将包含真实密钥的 .env 文件提交到版本控制系统 -# 2. 确保 .env 文件已添加到 .gitignore 中 -# 3. 在生产环境中使用更安全的密钥管理方案 diff --git a/oss/oss-volcengine-tos/build.gradle.kts b/oss/oss-volcengine-tos/build.gradle.kts index e60fe0c5f..8fcaadef9 100644 --- a/oss/oss-volcengine-tos/build.gradle.kts +++ b/oss/oss-volcengine-tos/build.gradle.kts @@ -1,37 +1,9 @@ plugins { id("buildlogic.kotlinspring-conventions") id("buildlogic.spotless-conventions") + id("buildlogic.loadenv-conventions") } -// 直接实现 dotenv 功能,避免插件依赖 -fun loadDotenv() { - val envFile = rootProject.file(".env") - if (envFile.exists()) { - envFile.readLines().forEach { line -> - val trimmedLine = line.trim() - if (trimmedLine.isNotEmpty() && !trimmedLine.startsWith("#")) { - val parts = trimmedLine.split("=", limit = 2) - if (parts.size == 2) { - val key = parts[0].trim() - val value = parts[1].trim().removeSurrounding("\"").removeSurrounding("'") - if (key.isNotEmpty() && value.isNotEmpty()) { - // 设置到所有任务的环境变量中 - tasks.withType { environment(key, value) } - tasks.withType { environment(key, value) } - logger.debug("Loaded environment variable: $key") - } - } - } - } - logger.info("Loaded .env file from: ${envFile.absolutePath}") - } else { - logger.warn(".env file not found at: ${envFile.absolutePath}") - } -} - -// 加载 dotenv 配置 -loadDotenv() - description = """ Volcengine TOS (Tinder Object Storage) integration for enterprise-grade cloud storage. diff --git a/shared/src/main/kotlin/io/github/truenine/composeserver/AliasExtensions.kt b/shared/src/main/kotlin/io/github/truenine/composeserver/AliasExtensions.kt index f349e8132..831860032 100644 --- a/shared/src/main/kotlin/io/github/truenine/composeserver/AliasExtensions.kt +++ b/shared/src/main/kotlin/io/github/truenine/composeserver/AliasExtensions.kt @@ -21,12 +21,29 @@ inline fun String.isId(): Boolean { return this.isNotEmpty() && this.matches(Regex("^[0-9A-Za-z]+$")) } -@Deprecated("框架内部调用代码,不应由用户直接调用", level = DeprecationLevel.ERROR) inline fun getDefaultNullableId(): Id = Long.MIN_VALUE +@Deprecated("框架内部调用代码,不应由用户直接调用", level = DeprecationLevel.ERROR) +inline fun getDefaultNullableId(): Id = Long.MIN_VALUE inline fun Number.toId(): Id? { return this.toLong().takeIf { it != Long.MIN_VALUE } } +inline fun Number.toId(receiver: (Id) -> T?): T? { + return this.toId()?.let(receiver) +} + +inline fun Number.toIdOrThrow(): Id { + return this.toLong().takeIf { it != Long.MIN_VALUE } ?: throw IllegalArgumentException("Invalid Id: $this") +} + inline fun String.toId(): Id? { return this.toLongOrNull()?.takeIf { it != Long.MIN_VALUE } } + +inline fun String.toIdOrThrow(): Id { + return this.toLongOrNull()?.takeIf { it != Long.MIN_VALUE } ?: throw IllegalArgumentException("Invalid Id: $this") +} + +inline fun String.toId(receiver: (Id) -> T?): T? { + return this.toId()?.let(receiver) +} diff --git a/shared/src/test/kotlin/io/github/truenine/composeserver/AliasExtensionsTest.kt b/shared/src/test/kotlin/io/github/truenine/composeserver/AliasExtensionsTest.kt index bef4f794b..135cca293 100644 --- a/shared/src/test/kotlin/io/github/truenine/composeserver/AliasExtensionsTest.kt +++ b/shared/src/test/kotlin/io/github/truenine/composeserver/AliasExtensionsTest.kt @@ -4,85 +4,249 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull +import kotlin.test.assertSame import kotlin.test.assertTrue +import kotlin.test.fail -/** ID 类型相关扩展函数的测试类 */ class AliasExtensionsTest { + // Long.isId() tests @Test - fun `test long is id`() { - assertFalse((-1L).isId(), "负数不是有效Id") - assertTrue(0L.isId(), "0 是有效Id") - assertTrue(1L.isId(), "正整数是有效Id") - assertTrue(Long.MAX_VALUE.isId(), "Long最大值是有效Id") - assertFalse(Long.MIN_VALUE.isId(), "Long最小值不是有效Id") + fun `Long isId() should return true for valid positive ids`() { + assertTrue(0L.isId(), "Zero should be a valid ID") + assertTrue(1L.isId(), "Positive integer should be a valid ID") + assertTrue(123L.isId(), "Any positive long should be a valid ID") + assertTrue(Long.MAX_VALUE.isId(), "Maximum long value should be a valid ID") } @Test - fun `test string is id`() { - assertFalse("".isId(), "空字符串不是有效Id") - assertTrue("0".isId(), "0 是有效Id") - assertTrue("123456".isId(), "全数字字符串是有效Id") - assertTrue("000123".isId(), "前导0的数字字符串是有效Id") - assertTrue("123a".isId(), "包含字母是有效Id") - assertFalse("12 34".isId(), "包含空格不是有效Id") - assertFalse("1234".isId(), "全角数字不是有效Id") - assertFalse("123!".isId(), "包含符号不是有效Id") - assertFalse("一二三".isId(), "中文不是有效Id") - assertFalse("123.45".isId(), "小数不是有效Id") - assertFalse(" 123".isId(), "前有空格不是有效Id") - assertFalse("123 ".isId(), "后有空格不是有效Id") - // 超大数字字符串(超出Long范围,但只要是数字也算有效) - assertTrue("92233720368547758079223372036854775807".isId(), "超大数字字符串也是有效Id") + fun `Long isId() should return false for negative numbers`() { + assertFalse((-1L).isId(), "Negative one should not be a valid ID") + assertFalse((-123L).isId(), "Any negative number should not be a valid ID") + assertFalse(Long.MIN_VALUE.isId(), "Minimum long value should not be a valid ID") } + // String.isId() tests @Test - fun `isId 当输入为有效Long类型ID时 返回true`() { - assertTrue(1L.isId(), "正数 ID 应该返回 true") - assertTrue(Long.MAX_VALUE.isId(), "最大 Long 值应该返回 true") + fun `String isId() should return true for valid alphanumeric strings`() { + assertTrue("a".isId(), "Single letter should be a valid ID") + assertTrue("A".isId(), "Single uppercase letter should be a valid ID") + assertTrue("0".isId(), "Single digit should be a valid ID") + assertTrue("abc123".isId(), "Mixed alphanumeric should be a valid ID") + assertTrue("ABC123".isId(), "Uppercase alphanumeric should be a valid ID") + assertTrue("123abc".isId(), "Digits followed by letters should be a valid ID") + assertTrue("a1b2c3".isId(), "Alternating letters and digits should be a valid ID") + assertTrue("000123".isId(), "Leading zeros should be allowed") + assertTrue("veryLongIdNameWithNumbers12345".isId(), "Long alphanumeric string should be valid") } @Test - fun `isId 当输入为无效Long类型ID时 返回false`() { - assertTrue(0L.isId(), "0 应该返回 true") - assertFalse((-1L).isId(), "负数应该返回 false") + fun `String isId() should return false for empty strings`() { + assertFalse("".isId(), "Empty string should not be a valid ID") } @Test - fun `isId 当输入为有效字符串ID时 返回true`() { - assertTrue("123".isId(), "数字字符串应该返回 true") - assertTrue("9999999999".isId(), "长数字字符串应该返回 true") + fun `String isId() should return false for strings with non-ASCII characters`() { + assertFalse("一二三".isId(), "Chinese characters should not be valid") + assertFalse("café".isId(), "Accented characters should not be valid") + assertFalse("naïve".isId(), "Special accented characters should not be valid") + assertFalse("Москва".isId(), "Cyrillic characters should not be valid") } @Test - fun `isId 当输入为无效字符串ID时 返回false`() { - assertFalse("".isId(), "空字符串应该返回 false") - assertFalse("-123".isId(), "负数字符串应该返回 false") + fun `String isId() should return false for strings with spaces`() { + assertFalse("abc 123".isId(), "String with space should not be valid") + assertFalse(" abc123".isId(), "String with leading space should not be valid") + assertFalse("abc123 ".isId(), "String with trailing space should not be valid") + assertFalse("abc 123".isId(), "String with multiple spaces should not be valid") } @Test - fun `toId 当输入为有效数字类型时 正确转换为ID`() { - assertEquals(123L, 123.toId(), "Int类型应该正确转换") - assertEquals(123L, 123L.toId(), "Long类型应该正确转换") - assertEquals(123L, 123.0.toId(), "Double类型应该正确转换") + fun `String isId() should return false for strings with special characters`() { + assertFalse("abc@123".isId(), "String with @ symbol should not be valid") + assertFalse("abc-123".isId(), "String with hyphen should not be valid") + assertFalse("abc_123".isId(), "String with underscore should not be valid") + assertFalse("abc.123".isId(), "String with dot should not be valid") + assertFalse("abc/123".isId(), "String with slash should not be valid") + assertFalse("abc!123".isId(), "String with exclamation mark should not be valid") + assertFalse("abc#123".isId(), "String with hash symbol should not be valid") + assertFalse("abc$123".isId(), "String with dollar sign should not be valid") + assertFalse("abc%123".isId(), "String with percent sign should not be valid") + assertFalse("abc^123".isId(), "String with caret should not be valid") + assertFalse("abc&123".isId(), "String with ampersand should not be valid") + assertFalse("abc*123".isId(), "String with asterisk should not be valid") + assertFalse("abc(123)".isId(), "String with parentheses should not be valid") + assertFalse("abc[123]".isId(), "String with brackets should not be valid") + assertFalse("abc{123}".isId(), "String with braces should not be valid") + } + + // getDefaultNullableId() tests - SKIPPED due to ERROR deprecation level + // This function is marked with DeprecationLevel.ERROR and should not be called by users + + // Number.toId() tests + @Test + fun `Number toId() should convert valid numbers to ID`() { + assertEquals(1L, 1.toId(), "Int should convert to Long ID") + assertEquals(123L, 123.toId(), "Positive Int should convert to Long ID") + assertEquals(0L, 0.toId(), "Zero Int should convert to Long ID") + assertEquals(1L, 1L.toId(), "Long should remain as Long ID") + assertEquals(123L, 123L.toId(), "Positive Long should remain as Long ID") + assertEquals(0L, 0L.toId(), "Zero Long should remain as Long ID") + assertEquals(1L, 1.0.toId(), "Double 1.0 should convert to Long ID") + assertEquals(123L, 123.0.toId(), "Double 123.0 should convert to Long ID") + assertEquals(1L, 1.5.toId(), "Double 1.5 should truncate to 1L") + assertEquals(1L, (1.999999).toId(), "Double 1.999999 should truncate to 1L") + assertEquals(123L, 123.99.toId(), "Double 123.99 should truncate to 123L") + assertEquals(1L, 1.0f.toId(), "Float 1.0 should convert to Long ID") + assertEquals(123L, 123.0f.toId(), "Float 123.0 should convert to Long ID") + assertEquals(1L, 1.5f.toId(), "Float 1.5 should truncate to 1L") + assertEquals(1L, (1.999999f).toId(), "Float 1.999999 should truncate to 1L") } @Test - fun `toId 当输入为无效数字类型时 返回null`() { - assertNull(Long.MIN_VALUE.toId(), "Long.MIN_VALUE应该返回null") + fun `Number toId() should return null for Long MIN_VALUE`() { + assertNull(Long.MIN_VALUE.toId(), "Long.MIN_VALUE should return null") } + // Number.toIdOrThrow() tests @Test - fun `toId 当输入为有效字符串时 正确转换为ID`() { - assertEquals(123L, "123".toId(), "有效数字字符串应该正确转换") - assertEquals(-123L, "-123".toId(), "负数字符串应该正确转换") + fun `Number toIdOrThrow() should convert valid numbers to ID`() { + assertEquals(1L, 1.toIdOrThrow(), "Int should convert to Long ID") + assertEquals(123L, 123.toIdOrThrow(), "Positive Int should convert to Long ID") + assertEquals(0L, 0.toIdOrThrow(), "Zero Int should convert to Long ID") + assertEquals(1L, 1L.toIdOrThrow(), "Long should remain as Long ID") + assertEquals(123L, 123L.toIdOrThrow(), "Positive Long should remain as Long ID") + assertEquals(0L, 0L.toIdOrThrow(), "Zero Long should remain as Long ID") + assertEquals(1L, 1.0.toIdOrThrow(), "Double 1.0 should convert to Long ID") + assertEquals(123L, 123.0.toIdOrThrow(), "Double 123.0 should convert to Long ID") + assertEquals(1L, 1.5.toIdOrThrow(), "Double 1.5 should truncate to 1L") + assertEquals(1L, (1.999999).toIdOrThrow(), "Double 1.999999 should truncate to 1L") + assertEquals(123L, 123.99.toIdOrThrow(), "Double 123.99 should truncate to 123L") + assertEquals(1L, 1.0f.toIdOrThrow(), "Float 1.0 should convert to Long ID") + assertEquals(123L, 123.0f.toIdOrThrow(), "Float 123.0 should convert to Long ID") + assertEquals(1L, 1.5f.toIdOrThrow(), "Float 1.5 should truncate to 1L") + assertEquals(1L, (1.999999f).toIdOrThrow(), "Float 1.999999 should truncate to 1L") } @Test - fun `toId 当输入为无效字符串时 返回null`() { - assertNull("".toId(), "空字符串应该返回null") - assertNull("abc".toId(), "非数字字符串应该返回null") - assertNull("123abc".toId(), "混合字符串应该返回null") - assertNull(Long.MIN_VALUE.toString().toId(), "Long.MIN_VALUE字符串应该返回null") + fun `Number toIdOrThrow() should throw exception for Long MIN_VALUE`() { + try { + Long.MIN_VALUE.toIdOrThrow() + fail("Expected IllegalArgumentException to be thrown") + } catch (e: IllegalArgumentException) { + assertEquals("Invalid Id: ${Long.MIN_VALUE}", e.message, "Exception message should contain the invalid ID") + } + } + + // String.toId() tests + @Test + fun `String toId() should convert valid numeric strings to ID`() { + assertEquals(1L, "1".toId(), "String '1' should convert to Long 1") + assertEquals(123L, "123".toId(), "String '123' should convert to Long 123") + assertEquals(0L, "0".toId(), "String '0' should convert to Long 0") + assertEquals(-1L, "-1".toId(), "String '-1' should convert to Long -1") + assertEquals(-123L, "-123".toId(), "String '-123' should convert to Long -123") + assertEquals(Long.MAX_VALUE, Long.MAX_VALUE.toString().toId(), "Max long string should convert to max long") + assertEquals(Long.MIN_VALUE + 1, (Long.MIN_VALUE + 1).toString().toId(), "Min long + 1 string should convert") + } + + @Test + fun `String toId() should return null for invalid numeric strings`() { + assertNull("".toId(), "Empty string should return null") + assertNull("abc".toId(), "Non-numeric string should return null") + assertNull("123abc".toId(), "Mixed alphanumeric string should return null") + assertNull("abc123".toId(), "Letters followed by numbers should return null") + assertNull("12.34".toId(), "Decimal string should return null") + assertNull("12,34".toId(), "String with comma should return null") + assertNull(" 123".toId(), "String with leading space should return null") + assertNull("123 ".toId(), "String with trailing space should return null") + assertNull("12 34".toId(), "String with internal space should return null") + assertNull("++123".toId(), "String with double plus should return null") + assertNull("--123".toId(), "String with double minus should return null") + assertNull("1a2b3c".toId(), "Mixed alphanumeric should return null") + assertNull(Long.MIN_VALUE.toString().toId(), "Long.MIN_VALUE string should return null") + } + + @Test + fun `String toId() should correctly handle strings with plus sign`() { + assertEquals(123L, "+123".toId(), "String with plus sign should be parsed correctly") + } + + // String.toIdOrThrow() tests + @Test + fun `String toIdOrThrow() should convert valid numeric strings to ID`() { + assertEquals(1L, "1".toIdOrThrow(), "String '1' should convert to Long 1") + assertEquals(123L, "123".toIdOrThrow(), "String '123' should convert to Long 123") + assertEquals(0L, "0".toIdOrThrow(), "String '0' should convert to Long 0") + assertEquals(-1L, "-1".toIdOrThrow(), "String '-1' should convert to Long -1") + assertEquals(-123L, "-123".toIdOrThrow(), "String '-123' should convert to Long -123") + assertEquals(Long.MAX_VALUE, Long.MAX_VALUE.toString().toIdOrThrow(), "Max long string should convert to max long") + assertEquals(Long.MIN_VALUE + 1, (Long.MIN_VALUE + 1).toString().toIdOrThrow(), "Min long + 1 string should convert") + } + + @Test + fun `String toIdOrThrow() should throw exception for invalid numeric strings`() { + val invalidInputs = listOf( + "", "abc", "123abc", "abc123", "12.34", "12,34", " 123", "123 ", "12 34", + "++123", "--123", "1a2b3c", Long.MIN_VALUE.toString() + ) + + invalidInputs.forEach { input -> + try { + input.toIdOrThrow() + fail("Expected IllegalArgumentException to be thrown for input: $input") + } catch (e: IllegalArgumentException) { + assertEquals("Invalid Id: $input", e.message, "Exception message should contain the invalid ID") + } + } + } + + @Test + fun `String toIdOrThrow() should correctly handle strings with plus sign`() { + assertEquals(123L, "+123".toIdOrThrow(), "String with plus sign should be parsed correctly") + } + + // Number.toId with receiver function tests + @Test + fun `Number toId() with receiver should apply function when valid ID`() { + val result1 = 123.toId { it * 2 } + assertEquals(246L, result1, "Should apply function to valid ID") + + val result2 = 0.toId { it.toString() } + assertEquals("0", result2, "Should apply function to zero ID") + + val result3 = 1.5.toId { it + 100 } + assertEquals(101L, result3, "Should truncate and apply function") + } + + @Test + fun `Number toId() with receiver should return null when invalid ID`() { + val result1 = Long.MIN_VALUE.toId { it * 2 } + assertNull(result1, "Should return null for invalid ID") + } + + // String.toId with receiver function tests + @Test + fun `String toId() with receiver should apply function when valid ID`() { + val result1 = "123".toId { it * 2 } + assertEquals(246L, result1, "Should apply function to valid ID") + + val result2 = "0".toId { it.toString() } + assertEquals("0", result2, "Should apply function to zero ID") + + val result3 = "-456".toId { it + 100 } + assertEquals(-356L, result3, "Should apply function to negative ID") + } + + @Test + fun `String toId() with receiver should return null when invalid ID`() { + val result1 = "".toId { it * 2 } + assertNull(result1, "Should return null for empty string") + + val result2 = "abc".toId { it.toString() } + assertNull(result2, "Should return null for non-numeric string") + + val result3 = Long.MIN_VALUE.toString().toId { it + 100 } + assertNull(result3, "Should return null for Long.MIN_VALUE string") } -} +} \ No newline at end of file diff --git a/todolist.md b/todolist.md index 0da9c9a45..9035959d9 100644 --- a/todolist.md +++ b/todolist.md @@ -1,7 +1,3 @@ -# coding - +- [ ] 重写所有集成测试支持 `EnableIf` - [ ] write `testtoolkit-testcontainers` testcontainers simple launcher - -# claude code - - [ ] write claude code setup slash command