demo
This commit is contained in:
@@ -1,22 +1,19 @@
|
||||
import ddd.application.DefaultUserService
|
||||
import ddd.application.dto.ChangeUsername
|
||||
import shared.dto.ChangeUsernameDto
|
||||
import ddd.domain.service.ExistsUserDomainService
|
||||
import ddd.infrastructure.repository.MemoryUserRepository
|
||||
import ddd.infrastructure.verification.EmailVerificationService
|
||||
import mvc.controllers.UserController
|
||||
import mvc.dao.UserRepository
|
||||
import mvc.entities.User
|
||||
import mvc.services.UserSimpleService
|
||||
|
||||
//fun main() {
|
||||
// val controller = UserController(UserSimpleService(UserRepository()))
|
||||
// val changeUsername = controller.changeUsername(User(1L, "nian", "3"))
|
||||
// println(changeUsername)
|
||||
//}
|
||||
fun mvc() {
|
||||
val controller = mvc.controllers.UserController(mvc.services.UserComplexService(mvc.dao.UserRepository()))
|
||||
val changeUsername1 = controller.changeUsername(ChangeUsernameDto(1L, "nian", "abc","1234"))
|
||||
println(changeUsername1)
|
||||
val changeUsername2 = controller.changeUsername(ChangeUsernameDto(1L, "nian", "def","1234"))
|
||||
println(changeUsername2)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
fun ddd() {
|
||||
val memoryUserRepository = MemoryUserRepository()
|
||||
|
||||
val userController = ddd.controller.UserController(
|
||||
DefaultUserService(
|
||||
memoryUserRepository,
|
||||
@@ -24,6 +21,12 @@ fun main() {
|
||||
ExistsUserDomainService(memoryUserRepository)
|
||||
)
|
||||
)
|
||||
val changeUsername = userController.changeUsername(ChangeUsername(1L, "nian", "po",""))
|
||||
println(changeUsername)
|
||||
val changeUsername1 = userController.changeUsername(ChangeUsernameDto(1L, "nian", "po","1234"))
|
||||
println(changeUsername1)
|
||||
val changeUsername2 = userController.changeUsername(ChangeUsernameDto(1L, "nian", "po1","1234"))
|
||||
println(changeUsername2)
|
||||
}
|
||||
|
||||
fun main(){
|
||||
mvc()
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package ddd.application
|
||||
|
||||
import ddd.application.dto.ChangeUsername
|
||||
import shared.dto.ChangeUsernameDto
|
||||
import ddd.domain.User
|
||||
import ddd.domain.port.VerificationService
|
||||
import ddd.domain.adapter.VerificationService
|
||||
import ddd.domain.repository.UserRepository
|
||||
import ddd.domain.service.ExistsUserDomainService
|
||||
import ddd.domain.valueobject.Username
|
||||
@@ -13,14 +13,16 @@ class DefaultUserService(
|
||||
val verificationService: VerificationService,
|
||||
val existsUserDomainService: ExistsUserDomainService,
|
||||
) : UserService {
|
||||
override fun changeUsername(userDto: ChangeUsername): User {
|
||||
val user = userRepository.findById(userDto.id) ?: throw NotFoundException("User with id ${userDto.id} not found")
|
||||
override fun changeUsername(userDto: ChangeUsernameDto): User {
|
||||
val user = userRepository.findById(userDto.id) ?: throw NotFoundException("用户${userDto.id}不存在")
|
||||
|
||||
user.changeUsername(
|
||||
Username(userDto.firstName, userDto.lastName),
|
||||
userDto.verificationCode,
|
||||
verificationService,
|
||||
existsUserDomainService
|
||||
)
|
||||
|
||||
userRepository.save(user)
|
||||
return user
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package ddd.application
|
||||
|
||||
import ddd.application.dto.ChangeUsername
|
||||
import shared.dto.ChangeUsernameDto
|
||||
import ddd.domain.User
|
||||
|
||||
interface UserService {
|
||||
fun changeUsername(userDto: ChangeUsername): User
|
||||
fun changeUsername(userDto: ChangeUsernameDto): User
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
package ddd.application.dto
|
||||
|
||||
data class ChangeUsername(val id: Long ,val firstName: String, val lastName: String, val verificationCode: String)
|
@@ -1,9 +1,9 @@
|
||||
package ddd.controller
|
||||
|
||||
import ddd.application.UserService
|
||||
import ddd.application.dto.ChangeUsername
|
||||
import shared.dto.ChangeUsernameDto
|
||||
|
||||
|
||||
class UserController(val service: UserService) {
|
||||
fun changeUsername(changeUsername: ChangeUsername) = service.changeUsername(changeUsername)
|
||||
fun changeUsername(changeUsername: ChangeUsernameDto) = service.changeUsername(changeUsername)
|
||||
}
|
@@ -2,27 +2,28 @@ package ddd.domain
|
||||
|
||||
import ddd.domain.entity.UserRank
|
||||
import ddd.domain.valueobject.UserId
|
||||
import ddd.domain.valueobject.UserStatusEnum
|
||||
import ddd.domain.valueobject.Username
|
||||
import ddd.domain.port.VerificationService
|
||||
import ddd.domain.adapter.VerificationService
|
||||
import ddd.domain.service.ExistsUserDomainService
|
||||
import ddd.domain.validation.changeUsername.EmailVerificationValidation
|
||||
import ddd.domain.validation.changeUsername.ExistsUsernameValidation
|
||||
import ddd.domain.validation.changeUsername.RankPolicyValidation
|
||||
import ddd.domain.validation.changeUsername.TimeIntervalValidation
|
||||
import ddd.domain.validation.changeUsername.UsernameChangeContext
|
||||
import ddd.domain.valueobject.UserStatusEnum
|
||||
import shared.AggregateRoot
|
||||
import shared.exceptions.ChangeUsernameException
|
||||
import shared.validation.ValidationChain
|
||||
import java.time.Clock
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class User(
|
||||
val id: UserId,
|
||||
override val id: UserId,
|
||||
var username: Username,
|
||||
var status: UserStatusEnum,
|
||||
var lastUsernameChange: LocalDateTime?,
|
||||
var rank: UserRank,
|
||||
) {
|
||||
) : AggregateRoot<UserId>() {
|
||||
// 领域方法:修改用户名(入口点)
|
||||
fun changeUsername(
|
||||
newUsername: Username,
|
||||
@@ -32,7 +33,7 @@ class User(
|
||||
clock: Clock = Clock.systemDefaultZone()
|
||||
) {
|
||||
validateState()
|
||||
validateChangeUsername(newUsername, verificationCode, verificationService, existsUserService, clock)
|
||||
validateUsername(newUsername, verificationCode, verificationService, existsUserService, clock)
|
||||
executeUsernameChange(newUsername, clock)
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ class User(
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateChangeUsername(
|
||||
private fun validateUsername(
|
||||
newUsername: Username,
|
||||
verificationCode: String,
|
||||
verificationService: VerificationService,
|
||||
@@ -60,8 +61,8 @@ class User(
|
||||
|
||||
// 组合验证规则(责任链模式)
|
||||
ValidationChain<UsernameChangeContext>()
|
||||
.add(EmailVerificationValidation())
|
||||
.add(RankPolicyValidation())
|
||||
.add(EmailVerificationValidation())
|
||||
.add(TimeIntervalValidation())
|
||||
.add(ExistsUsernameValidation())
|
||||
.validate(context)
|
||||
@@ -71,4 +72,8 @@ class User(
|
||||
username = newUsername
|
||||
lastUsernameChange = LocalDateTime.now(clock)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "User(id=$id, username=$username, status=$status, lastUsernameChange=$lastUsernameChange, rank=${rank.value})"
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package ddd.domain.port
|
||||
|
||||
import ddd.domain.valueobject.UserId
|
||||
|
||||
interface VerificationService {
|
||||
fun isVerified(userId: UserId, code: String): Boolean
|
||||
fun sendVerificationCode(userId: UserId)
|
||||
package ddd.domain.adapter
|
||||
|
||||
import ddd.domain.valueobject.UserId
|
||||
|
||||
interface VerificationService {
|
||||
fun isVerified(userId: UserId, code: String): Boolean
|
||||
fun sendVerificationCode(userId: UserId)
|
||||
}
|
@@ -4,8 +4,6 @@ import ddd.domain.User
|
||||
|
||||
class InternalUserRankPolicy : UserRankPolicy {
|
||||
override fun canChangeUsername(user: User) = true
|
||||
|
||||
override fun requiresEmailVerification() = false
|
||||
|
||||
override fun getMaxChangeIntervalDays() = 0
|
||||
}
|
@@ -7,7 +7,7 @@ class EmailVerificationValidation : AbstractValidationHandler<UsernameChangeCont
|
||||
override fun validate(context: UsernameChangeContext) {
|
||||
if(context.user.rank.policy.requiresEmailVerification()){
|
||||
val emailVerified = context.verificationService.isVerified(context.user.id, context.verificationCode)
|
||||
if (emailVerified) {
|
||||
if (!emailVerified) {
|
||||
throw ChangeUsernameException("验证码错误")
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@ import shared.validation.AbstractValidationHandler
|
||||
|
||||
class ExistsUsernameValidation : AbstractValidationHandler<UsernameChangeContext>() {
|
||||
override fun validate(context: UsernameChangeContext) {
|
||||
check(context.user.username != context.newUsername){
|
||||
throw ChangeUsernameException("不能修改为当前用户名")
|
||||
}
|
||||
require(!context.existsUserService.existsByUsername(context.newUsername)) {
|
||||
throw ChangeUsernameException("用户名已存在")
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package ddd.domain.validation.changeUsername
|
||||
|
||||
import ddd.domain.User
|
||||
import ddd.domain.port.VerificationService
|
||||
import ddd.domain.adapter.VerificationService
|
||||
import ddd.domain.service.ExistsUserDomainService
|
||||
import ddd.domain.valueobject.Username
|
||||
import java.time.Clock
|
||||
|
@@ -1,5 +1,5 @@
|
||||
package ddd.domain.valueobject;
|
||||
package ddd.domain.valueobject
|
||||
|
||||
public enum UserStatusEnum {
|
||||
enum class UserStatusEnum {
|
||||
ACTIVE,DEACTIVATED,BANNED
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ class MemoryUserRepository : UserRepository {
|
||||
Username("nian", "chen"),
|
||||
UserStatusEnum.ACTIVE,
|
||||
null,
|
||||
UserRank.RegularUserRank()
|
||||
UserRank.VipUserRank()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package ddd.infrastructure.verification
|
||||
|
||||
import ddd.domain.port.VerificationService
|
||||
import ddd.domain.adapter.VerificationService
|
||||
import ddd.domain.valueobject.UserId
|
||||
|
||||
class EmailVerificationService : VerificationService {
|
||||
override fun isVerified(userId: UserId, code: String) = true
|
||||
override fun isVerified(userId: UserId, code: String) = code == "1234"
|
||||
override fun sendVerificationCode(userId: UserId) = println("sending verification email")
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package mvc.controllers
|
||||
|
||||
import mvc.entities.User
|
||||
import mvc.services.UserService
|
||||
import shared.dto.ChangeUsernameDto
|
||||
|
||||
class UserController(val service: UserService) {
|
||||
fun changeUsername(user: User) = service.changeUsername(user)
|
||||
fun changeUsername(userDto: ChangeUsernameDto) = service.changeUsername(userDto)
|
||||
}
|
@@ -1,12 +1,14 @@
|
||||
package mvc.dao
|
||||
|
||||
import mvc.entities.User
|
||||
import mvc.entities.UserRankEnum
|
||||
import mvc.entities.UserStatusEnum
|
||||
|
||||
class UserRepository {
|
||||
val users = mutableMapOf<Long, User>()
|
||||
|
||||
init {
|
||||
add(User(1L, "nian", "chen"))
|
||||
add(User(1L, "nian", "chen", UserStatusEnum.ACTIVE, null, UserRankEnum.VIP))
|
||||
}
|
||||
|
||||
fun add(user: User) {
|
||||
@@ -20,4 +22,8 @@ class UserRepository {
|
||||
fun update(user: User) {
|
||||
users[user.id] = user
|
||||
}
|
||||
|
||||
fun findByUsername(firstName: String, lastName: String): User? {
|
||||
return users.entries.find { it.value.firstName == firstName && it.value.lastName == lastName }?.value
|
||||
}
|
||||
}
|
@@ -1,7 +1,12 @@
|
||||
package mvc.entities
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class User (
|
||||
val id: Long,
|
||||
val firstName: String,
|
||||
val lastName: String
|
||||
var firstName: String,
|
||||
var lastName: String,
|
||||
var status: UserStatusEnum,
|
||||
var lastUsernameChange: LocalDateTime?,
|
||||
var rank: UserRankEnum,
|
||||
)
|
5
src/main/kotlin/mvc/entities/UserRankEnum.kt
Normal file
5
src/main/kotlin/mvc/entities/UserRankEnum.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package mvc.entities
|
||||
|
||||
enum class UserRankEnum {
|
||||
REGULAR, VIP, INTERNAL
|
||||
}
|
5
src/main/kotlin/mvc/entities/UserStatusEnum.kt
Normal file
5
src/main/kotlin/mvc/entities/UserStatusEnum.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package mvc.entities
|
||||
|
||||
enum class UserStatusEnum {
|
||||
ACTIVE,DEACTIVATED,BANNED
|
||||
}
|
@@ -3,16 +3,89 @@ package mvc.services
|
||||
import shared.exceptions.NotFoundException
|
||||
import mvc.dao.UserRepository
|
||||
import mvc.entities.User
|
||||
import mvc.entities.UserRankEnum
|
||||
import mvc.entities.UserStatusEnum
|
||||
import shared.dto.ChangeUsernameDto
|
||||
import shared.exceptions.ChangeUsernameException
|
||||
import java.time.Clock
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
class UserComplexService(
|
||||
val repository: UserRepository
|
||||
val repository: UserRepository,
|
||||
) : UserService {
|
||||
// 用户
|
||||
override fun changeUsername(user: User): User {
|
||||
val findUser = repository.findById(user.id) ?: throw NotFoundException("User ${user.id} not found")
|
||||
val copy = findUser.copy(firstName = user.firstName, lastName = user.lastName)
|
||||
repository.update(copy)
|
||||
return copy
|
||||
override fun changeUsername(userDto: ChangeUsernameDto): User {
|
||||
val user = repository.findById(userDto.id) ?: throw NotFoundException("用户${userDto.id}不存在")
|
||||
// 判断用户状态
|
||||
if (user.status != UserStatusEnum.ACTIVE) throw ChangeUsernameException("用户未激活")
|
||||
// 用户名长度
|
||||
if( !(user.firstName.length + user.lastName.length in 4..20)) {
|
||||
throw ChangeUsernameException("用户名长度需在4-20之间")
|
||||
}
|
||||
|
||||
// 检测非法字符
|
||||
val regex = Regex("^[a-zA-Z0-9_]+$")
|
||||
if(!regex.matches(userDto.firstName) || !regex.matches(userDto.lastName)) {
|
||||
throw ChangeUsernameException("包含非法字符")
|
||||
}
|
||||
|
||||
// 检测权限
|
||||
var canChangeUsername = false
|
||||
var requiresEmailVerification = false
|
||||
var maxChangeIntervalDays = 0
|
||||
when (user.rank) {
|
||||
UserRankEnum.REGULAR -> {
|
||||
canChangeUsername = false
|
||||
requiresEmailVerification = false
|
||||
maxChangeIntervalDays = 0
|
||||
}
|
||||
|
||||
UserRankEnum.VIP -> {
|
||||
canChangeUsername = true
|
||||
requiresEmailVerification = true
|
||||
maxChangeIntervalDays = 30
|
||||
}
|
||||
|
||||
UserRankEnum.INTERNAL -> {
|
||||
canChangeUsername = true
|
||||
requiresEmailVerification = false
|
||||
maxChangeIntervalDays = 0
|
||||
}
|
||||
}
|
||||
|
||||
if (!canChangeUsername) throw ChangeUsernameException("用户没有权限修改用户名")
|
||||
|
||||
if (requiresEmailVerification) {
|
||||
val emailVerified = VerificationService().isVerified(user.id, userDto.verificationCode)
|
||||
if (!emailVerified) {
|
||||
throw ChangeUsernameException("验证码错误")
|
||||
}
|
||||
}
|
||||
|
||||
user.lastUsernameChange?.let{
|
||||
val daysBetween =
|
||||
Duration.between(user.lastUsernameChange, LocalDateTime.now(Clock.systemDefaultZone())).toDays()
|
||||
if (daysBetween < maxChangeIntervalDays) {
|
||||
throw ChangeUsernameException("30天内禁止重复修改")
|
||||
}
|
||||
}
|
||||
|
||||
// 检测相同名
|
||||
if(user.firstName == userDto.firstName && user.lastName == userDto.lastName) {
|
||||
throw ChangeUsernameException("不能修改为当前用户名")
|
||||
}
|
||||
val findByUsername = repository.findByUsername(userDto.firstName, userDto.lastName)
|
||||
if(findByUsername != null) {
|
||||
throw ChangeUsernameException("用户名已存在")
|
||||
}
|
||||
|
||||
|
||||
user.firstName = userDto.firstName
|
||||
user.lastName = userDto.lastName
|
||||
user.lastUsernameChange = LocalDateTime.now(Clock.systemDefaultZone())
|
||||
repository.update(user)
|
||||
return user
|
||||
}
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
package mvc.services
|
||||
|
||||
import mvc.entities.User
|
||||
import shared.dto.ChangeUsernameDto
|
||||
|
||||
interface UserService {
|
||||
fun changeUsername(user: User): User
|
||||
fun changeUsername(userDto: ChangeUsernameDto): User
|
||||
}
|
@@ -3,12 +3,14 @@ package mvc.services
|
||||
import shared.exceptions.NotFoundException
|
||||
import mvc.dao.UserRepository
|
||||
import mvc.entities.User
|
||||
import shared.dto.ChangeUsernameDto
|
||||
|
||||
class UserSimpleService(val repository: UserRepository) : UserService {
|
||||
override fun changeUsername(user: User): User {
|
||||
val findUser = repository.findById(user.id) ?: throw NotFoundException("User ${user.id} not found")
|
||||
val copy = findUser.copy(firstName = user.firstName, lastName = user.lastName)
|
||||
repository.update(copy)
|
||||
return copy
|
||||
override fun changeUsername(userDto: ChangeUsernameDto): User {
|
||||
val findUser = repository.findById(userDto.id) ?: throw NotFoundException("用户${userDto.id}不存在")
|
||||
findUser.firstName = userDto.firstName
|
||||
findUser.lastName = userDto.lastName
|
||||
repository.update(findUser)
|
||||
return findUser
|
||||
}
|
||||
}
|
7
src/main/kotlin/mvc/services/VerificationService.kt
Normal file
7
src/main/kotlin/mvc/services/VerificationService.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package mvc.services
|
||||
|
||||
class VerificationService {
|
||||
fun isVerified(userId: Long, code: String): Boolean {
|
||||
return "1234" == code
|
||||
}
|
||||
}
|
5
src/main/kotlin/shared/AggregateRoot.kt
Normal file
5
src/main/kotlin/shared/AggregateRoot.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package shared
|
||||
|
||||
abstract class AggregateRoot<T>{
|
||||
abstract val id: T
|
||||
}
|
3
src/main/kotlin/shared/dto/ChangeUsernameDto.kt
Normal file
3
src/main/kotlin/shared/dto/ChangeUsernameDto.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package shared.dto
|
||||
|
||||
data class ChangeUsernameDto(val id: Long, val firstName: String, val lastName: String, val verificationCode: String)
|
Reference in New Issue
Block a user