diff --git a/pom.xml b/pom.xml
index 28fe6b6..62840b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -169,6 +169,11 @@
avatar-generator
1.1.0
+
+ com.talanlabs
+ avatar-generator-8bit
+ 1.1.0
+
diff --git a/src/main/kotlin/top/fatweb/api/controller/api/v1/AvatarController.kt b/src/main/kotlin/top/fatweb/api/controller/api/v1/AvatarController.kt
index c8d0808..855f631 100644
--- a/src/main/kotlin/top/fatweb/api/controller/api/v1/AvatarController.kt
+++ b/src/main/kotlin/top/fatweb/api/controller/api/v1/AvatarController.kt
@@ -2,11 +2,16 @@ package top.fatweb.api.controller.api.v1
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
+import jakarta.validation.Valid
+import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import top.fatweb.api.entity.common.ResponseResult
+import top.fatweb.api.param.api.v1.avatar.AvatarBaseParam
+import top.fatweb.api.param.api.v1.avatar.AvatarEightBitParam
+import top.fatweb.api.param.api.v1.avatar.AvatarGitHubParam
import top.fatweb.api.service.api.v1.IAvatarService
import top.fatweb.api.vo.api.v1.avatar.DefaultBase64Vo
@@ -27,4 +32,34 @@ class AvatarController(
fun getDefault(@PathVariable apiVersion: String): ResponseResult {
return ResponseResult.success(data = avatarService.getDefault())
}
+
+ @Operation(summary = "三角形头像")
+ @GetMapping("/triangle", produces = [MediaType.IMAGE_PNG_VALUE])
+ fun triangle(@PathVariable apiVersion: String, @Valid avatarBaseParam: AvatarBaseParam?): ByteArray {
+ return avatarService.triangle(avatarBaseParam)
+ }
+
+ @Operation(summary = "正方形头像")
+ @GetMapping("/square", produces = [MediaType.IMAGE_PNG_VALUE])
+ fun square(@PathVariable apiVersion: String, @Valid avatarBaseParam: AvatarBaseParam?): ByteArray {
+ return avatarService.square(avatarBaseParam)
+ }
+
+ @Operation(summary = "Identicon 头像")
+ @GetMapping("/identicon", produces = [MediaType.IMAGE_PNG_VALUE])
+ fun identicon(@PathVariable apiVersion: String, @Valid avatarBaseParam: AvatarBaseParam?): ByteArray {
+ return avatarService.identicon(avatarBaseParam)
+ }
+
+ @Operation(summary = "GitHub 头像")
+ @GetMapping("/github", produces = [MediaType.IMAGE_PNG_VALUE])
+ fun github(@PathVariable apiVersion: String, @Valid avatarGitHubParam: AvatarGitHubParam?): ByteArray {
+ return avatarService.github(avatarGitHubParam)
+ }
+
+ @Operation(summary = "8 Bit 头像")
+ @GetMapping("/8bit", produces = [MediaType.IMAGE_PNG_VALUE])
+ fun eightBit(@PathVariable apiVersion: String, @Valid avatarEightBitParam: AvatarEightBitParam): ByteArray {
+ return avatarService.eightBit(avatarEightBitParam)
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/fatweb/api/entity/common/BusinessCode.kt b/src/main/kotlin/top/fatweb/api/entity/common/BusinessCode.kt
index e4bc399..046ddd8 100644
--- a/src/main/kotlin/top/fatweb/api/entity/common/BusinessCode.kt
+++ b/src/main/kotlin/top/fatweb/api/entity/common/BusinessCode.kt
@@ -8,5 +8,6 @@ package top.fatweb.api.entity.common
*/
enum class BusinessCode(val code: Int) {
SYSTEM(100),
- DATABASE(200)
+ DATABASE(200),
+ API_AVATAR(501)
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt b/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt
index 4de6865..ff6ec9f 100644
--- a/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt
+++ b/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt
@@ -35,7 +35,9 @@ enum class ResponseCode(val code: Int) {
DATABASE_DELETE_SUCCESS(BusinessCode.DATABASE, 30),
DATABASE_DELETE_FILED(BusinessCode.DATABASE, 35),
DATABASE_EXECUTE_ERROR(BusinessCode.DATABASE, 40),
- DATABASE_DUPLICATE_KEY(BusinessCode.DATABASE, 45);
+ DATABASE_DUPLICATE_KEY(BusinessCode.DATABASE, 45),
+
+ API_AVATAR_ERROR(BusinessCode.API_AVATAR, 5);
constructor(businessCode: BusinessCode, code: Int) : this(businessCode.code * 100 + code)
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt b/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt
index a1d3100..f3d284f 100644
--- a/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt
+++ b/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt
@@ -3,6 +3,7 @@ package top.fatweb.api.handler
import com.auth0.jwt.exceptions.JWTDecodeException
import com.auth0.jwt.exceptions.SignatureVerificationException
import com.auth0.jwt.exceptions.TokenExpiredException
+import com.talanlabs.avatargenerator.AvatarException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.dao.DuplicateKeyException
@@ -88,6 +89,11 @@ class ExceptionHandler {
ResponseResult.fail(ResponseCode.DATABASE_DUPLICATE_KEY, "Duplicate key", null)
}
+ is AvatarException -> {
+ logger.debug(e.localizedMessage, e)
+ ResponseResult.fail(ResponseCode.API_AVATAR_ERROR, e.localizedMessage, null)
+ }
+
else -> {
logger.error(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_ERROR, e.toString(), null)
diff --git a/src/main/kotlin/top/fatweb/api/param/PageSortParam.kt b/src/main/kotlin/top/fatweb/api/param/PageSortParam.kt
index a90d411..0a38ac1 100644
--- a/src/main/kotlin/top/fatweb/api/param/PageSortParam.kt
+++ b/src/main/kotlin/top/fatweb/api/param/PageSortParam.kt
@@ -19,8 +19,8 @@ open class PageSortParam {
var pageSize: Long = 20
@Schema(description = "排序字段", example = "id")
- val sortField: String? = null
+ var sortField: String? = null
@Schema(description = "排序方式", example = "desc", allowableValues = ["desc", "asc"])
- val sortOrder: String? = null
+ var sortOrder: String? = null
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarBaseParam.kt b/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarBaseParam.kt
new file mode 100644
index 0000000..669db14
--- /dev/null
+++ b/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarBaseParam.kt
@@ -0,0 +1,28 @@
+package top.fatweb.api.param.api.v1.avatar
+
+import io.swagger.v3.oas.annotations.media.Schema
+import jakarta.validation.constraints.Max
+import jakarta.validation.constraints.Pattern
+
+open class AvatarBaseParam {
+ @Schema(description = "种子")
+ var seed: Long? = null
+
+ @Schema(description = "图像大小", defaultValue = "128")
+ @field:Max(256, message = "Size must be less than or equal to 256")
+ var size: Int? = null
+
+ @Schema(description = "外边距", defaultValue = "0")
+ var margin: Int? = null
+
+ @Schema(description = "内边距", defaultValue = "0")
+ var padding: Int? = null
+
+ @Schema(defaultValue = "颜色列表", example = "#FFFFFFAA")
+ var colors: List? = null
+
+ @Schema(defaultValue = "背景颜色", example = "#FFFFFFAA")
+ @field:Pattern(regexp = "^#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}$", message = "Background color must be a hex color code")
+ var background: String? = null
+
+}
diff --git a/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarEightBitParam.kt b/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarEightBitParam.kt
new file mode 100644
index 0000000..f47178b
--- /dev/null
+++ b/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarEightBitParam.kt
@@ -0,0 +1,8 @@
+package top.fatweb.api.param.api.v1.avatar
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+data class AvatarEightBitParam(
+ @Schema(description = "性别", defaultValue = "male", allowableValues = ["male", "female"])
+ val gender: String?,
+) : AvatarBaseParam()
diff --git a/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarGitHubParam.kt b/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarGitHubParam.kt
new file mode 100644
index 0000000..9c74c1c
--- /dev/null
+++ b/src/main/kotlin/top/fatweb/api/param/api/v1/avatar/AvatarGitHubParam.kt
@@ -0,0 +1,13 @@
+package top.fatweb.api.param.api.v1.avatar
+
+import io.swagger.v3.oas.annotations.media.Schema
+import jakarta.validation.constraints.Max
+
+data class AvatarGitHubParam(
+ @Schema(description = "元素大小", defaultValue = "400")
+ @field:Max(1000, message = "Element size must be less than or equal to 1000")
+ val elementSize: Int = 400,
+
+ @Schema(description = "精确度", defaultValue = "3")
+ val precision: Int = 3
+) : AvatarBaseParam()
diff --git a/src/main/kotlin/top/fatweb/api/service/api/v1/AvatarServiceImpl.kt b/src/main/kotlin/top/fatweb/api/service/api/v1/AvatarServiceImpl.kt
index 24ce514..57a0e6e 100644
--- a/src/main/kotlin/top/fatweb/api/service/api/v1/AvatarServiceImpl.kt
+++ b/src/main/kotlin/top/fatweb/api/service/api/v1/AvatarServiceImpl.kt
@@ -1,8 +1,18 @@
package top.fatweb.api.service.api.v1
import com.talanlabs.avatargenerator.GitHubAvatar
+import com.talanlabs.avatargenerator.IdenticonAvatar
+import com.talanlabs.avatargenerator.SquareAvatar
+import com.talanlabs.avatargenerator.TriangleAvatar
+import com.talanlabs.avatargenerator.eightbit.EightBitAvatar
+import com.talanlabs.avatargenerator.layers.backgrounds.ColorPaintBackgroundLayer
import org.springframework.stereotype.Service
+import top.fatweb.api.param.api.v1.avatar.AvatarBaseParam
+import top.fatweb.api.param.api.v1.avatar.AvatarEightBitParam
+import top.fatweb.api.param.api.v1.avatar.AvatarGitHubParam
+import top.fatweb.api.util.NumberUtil
import top.fatweb.api.vo.api.v1.avatar.DefaultBase64Vo
+import java.awt.Color
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@@ -15,4 +25,103 @@ class AvatarServiceImpl : IAvatarService {
return DefaultBase64Vo(Base64.encode(bytes))
}
+ override fun triangle(avatarBaseParam: AvatarBaseParam?): ByteArray {
+ val avatar = (
+ if (avatarBaseParam == null || avatarBaseParam.colors.isNullOrEmpty())
+ TriangleAvatar.newAvatarBuilder()
+ else TriangleAvatar.newAvatarBuilder(
+ *avatarBaseParam.colors!!.map { decodeColor(it) }.toTypedArray())
+ ).apply {
+ avatarBaseParam?.size?.let { size(it, it) }
+ avatarBaseParam?.margin?.let { margin(it) }
+ avatarBaseParam?.padding?.let { padding(it) }
+ avatarBaseParam?.background?.let { layers(ColorPaintBackgroundLayer(decodeColor(it))) }
+ }.build()
+
+ return avatar.createAsPngBytes(avatarBaseParam?.seed ?: NumberUtil.getRandomLong())
+ }
+
+ override fun square(avatarBaseParam: AvatarBaseParam?): ByteArray {
+ val avatar = (
+ if (avatarBaseParam == null || avatarBaseParam.colors.isNullOrEmpty())
+ SquareAvatar.newAvatarBuilder()
+ else SquareAvatar.newAvatarBuilder(
+ *avatarBaseParam.colors!!.map { decodeColor(it) }.toTypedArray())
+ ).apply {
+ avatarBaseParam?.size?.let { size(it, it) }
+ avatarBaseParam?.margin?.let { margin(it) }
+ avatarBaseParam?.padding?.let { padding(it) }
+ avatarBaseParam?.background?.let { layers(ColorPaintBackgroundLayer(decodeColor(it))) }
+ }.build()
+
+ return avatar.createAsPngBytes(avatarBaseParam?.seed ?: NumberUtil.getRandomLong())
+ }
+
+ override fun identicon(avatarBaseParam: AvatarBaseParam?): ByteArray {
+ val avatar = IdenticonAvatar.newAvatarBuilder().apply {
+ avatarBaseParam?.size?.let { size(it, it) }
+ avatarBaseParam?.margin?.let { margin(it) }
+ avatarBaseParam?.padding?.let { padding(it) }
+ if (avatarBaseParam != null && !avatarBaseParam.colors.isNullOrEmpty()) {
+ color(decodeColor(avatarBaseParam.colors!!.random()))
+ }
+ avatarBaseParam?.background?.let { layers(ColorPaintBackgroundLayer(decodeColor(it))) }
+ }.build()
+
+ return avatar.createAsPngBytes(avatarBaseParam?.seed ?: NumberUtil.getRandomLong())
+ }
+
+ override fun github(avatarGitHubParam: AvatarGitHubParam?): ByteArray {
+ val avatar = (avatarGitHubParam?.let { GitHubAvatar.newAvatarBuilder(it.elementSize, it.precision) }
+ ?: let { GitHubAvatar.newAvatarBuilder() }).apply {
+ avatarGitHubParam?.size?.let { size(it, it) }
+ avatarGitHubParam?.margin?.let { margin(it) }
+ avatarGitHubParam?.padding?.let { padding(it) }
+ if (avatarGitHubParam != null && !avatarGitHubParam.colors.isNullOrEmpty()) {
+ color(decodeColor(avatarGitHubParam.colors!!.random()))
+ }
+ avatarGitHubParam?.background?.let { layers(ColorPaintBackgroundLayer(decodeColor(it))) }
+ }.build()
+
+ return avatar.createAsPngBytes(avatarGitHubParam?.seed ?: NumberUtil.getRandomLong())
+ }
+
+ override fun eightBit(avatarEightBitParam: AvatarEightBitParam?): ByteArray {
+ val avatar = if (avatarEightBitParam?.gender?.equals("female") ?: false) {
+ EightBitAvatar.newFemaleAvatarBuilder().apply {
+ avatarEightBitParam?.size?.let { size(it, it) }
+ avatarEightBitParam?.margin?.let { margin(it) }
+ avatarEightBitParam?.padding?.let { padding(it) }
+ if (avatarEightBitParam != null && !avatarEightBitParam.colors.isNullOrEmpty()) {
+ color(decodeColor(avatarEightBitParam.colors!!.random()))
+ }
+ avatarEightBitParam?.background?.let { layers(ColorPaintBackgroundLayer(decodeColor(it))) }
+ }.build()
+ } else {
+ EightBitAvatar.newMaleAvatarBuilder().apply {
+ avatarEightBitParam?.size?.let { size(it, it) }
+ avatarEightBitParam?.margin?.let { margin(it) }
+ avatarEightBitParam?.padding?.let { padding(it) }
+ if (avatarEightBitParam != null && !avatarEightBitParam.colors.isNullOrEmpty()) {
+ color(decodeColor(avatarEightBitParam.colors!!.random()))
+ }
+ avatarEightBitParam?.background?.let { layers(ColorPaintBackgroundLayer(decodeColor(it))) }
+ }.build()
+ }
+
+ return avatar.createAsPngBytes(avatarEightBitParam?.seed ?: NumberUtil.getRandomLong())
+ }
+
+ fun decodeColor(nm: String): Color {
+ return if (Regex("^#[0-9a-fA-F]{6}$").matches(nm)) {
+ Color.decode(nm)
+ } else if (Regex("^#[0-9a-fA-F]{8}$").matches(nm)) {
+ val intVal = Integer.decode(nm.substring(1..6).prependIndent("#"))
+ val alpha = Integer.decode(nm.substring(7).prependIndent("#"))
+ Color(intVal shr 16 and 0xFF, intVal shr 8 and 0XFF, intVal and 0xFF, alpha and 0xFF)
+ } else {
+ Color.WHITE
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/fatweb/api/service/api/v1/IAvatarService.kt b/src/main/kotlin/top/fatweb/api/service/api/v1/IAvatarService.kt
index c07bd20..5737b79 100644
--- a/src/main/kotlin/top/fatweb/api/service/api/v1/IAvatarService.kt
+++ b/src/main/kotlin/top/fatweb/api/service/api/v1/IAvatarService.kt
@@ -1,7 +1,21 @@
package top.fatweb.api.service.api.v1
+import top.fatweb.api.param.api.v1.avatar.AvatarBaseParam
+import top.fatweb.api.param.api.v1.avatar.AvatarEightBitParam
+import top.fatweb.api.param.api.v1.avatar.AvatarGitHubParam
import top.fatweb.api.vo.api.v1.avatar.DefaultBase64Vo
interface IAvatarService {
fun getDefault(): DefaultBase64Vo
+
+ fun triangle(avatarBaseParam: AvatarBaseParam?): ByteArray
+
+ fun square(avatarBaseParam: AvatarBaseParam?): ByteArray
+
+ fun identicon(avatarBaseParam: AvatarBaseParam?): ByteArray
+
+ fun github(avatarGitHubParam: AvatarGitHubParam?): ByteArray
+
+ fun eightBit(avatarEightBitParam: AvatarEightBitParam?): ByteArray
+
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/fatweb/api/util/NumberUtil.kt b/src/main/kotlin/top/fatweb/api/util/NumberUtil.kt
new file mode 100644
index 0000000..ebc074b
--- /dev/null
+++ b/src/main/kotlin/top/fatweb/api/util/NumberUtil.kt
@@ -0,0 +1,11 @@
+package top.fatweb.api.util
+
+object NumberUtil {
+ fun getRandomInt(start: Int = Int.MIN_VALUE, end: Int = Int.MAX_VALUE): Int {
+ return (start..end).random()
+ }
+
+ fun getRandomLong(start: Long = Long.MIN_VALUE, end: Long = Long.MAX_VALUE): Long {
+ return (start..end).random()
+ }
+}
\ No newline at end of file