diff --git a/src/beckend/gradle.properties b/src/beckend/gradle.properties new file mode 100644 index 0000000..72dddb5 --- /dev/null +++ b/src/beckend/gradle.properties @@ -0,0 +1,2 @@ +kotlin.jvm.target.validation=warning +kotlin.jvm.target=25 \ No newline at end of file diff --git a/src/beckend/gradle/wrapper/gradle-wrapper.properties b/src/beckend/gradle/wrapper/gradle-wrapper.properties index df6a6ad..a9db115 100644 --- a/src/beckend/gradle/wrapper/gradle-wrapper.properties +++ b/src/beckend/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/src/beckend/gradlew b/src/beckend/gradlew index b9bb139..249efbb 100755 --- a/src/beckend/gradlew +++ b/src/beckend/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/src/beckend/gradlew.bat b/src/beckend/gradlew.bat index 24c62d5..a51ec4f 100644 --- a/src/beckend/gradlew.bat +++ b/src/beckend/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/AuthorizeController.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/AuthorizeController.kt index 8f1389f..9ca1444 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/AuthorizeController.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/AuthorizeController.kt @@ -1,21 +1,16 @@ package club.itstudio.itmanager.controller.v1 import club.itstudio.itmanager.entity.ApiResponse +import club.itstudio.itmanager.entity.enums.EmailType import club.itstudio.itmanager.entity.vo.request.EmailRegisterVO import club.itstudio.itmanager.entity.vo.request.PasswordResetVO -import club.itstudio.itmanager.service.AccountService +import club.itstudio.itmanager.service.v1.AuthorizationService import jakarta.validation.Valid import jakarta.validation.constraints.Email import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Pattern import org.slf4j.LoggerFactory import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono @@ -23,34 +18,34 @@ import reactor.core.publisher.Mono @RestController @RequestMapping("/api/v1/auth") class AuthorizeController( - private val service: AccountService + private val service: AuthorizationService ) { private val logger = LoggerFactory.getLogger(this::class.java) @GetMapping("/ask-code") fun askVerifyCode( @RequestParam @NotBlank @Email email: String, - @RequestParam @Pattern(regexp = "(register|reset)") type: String, + @RequestParam type: EmailType, exchange: ServerWebExchange ): Mono> { logger.info("Trying to ask code") val ip = exchange.request.remoteAddress?.address?.hostAddress ?: "unknown" return service.askEmailVerifyCode(type, email, ip) - .map { messageHandler(it) } + .map(::messageHandler) } @PostMapping("/register") fun emailRegister(@RequestBody @Valid vo: EmailRegisterVO): Mono> { logger.info("Trying to register") return service.registerEmailAccount(vo) - .map { messageHandler(it) } + .map(::messageHandler) } @PostMapping("/reset") fun emailResetPassword(@RequestBody @Valid vo: PasswordResetVO): Mono> { logger.info("Trying to reset password") return service.resetEmailAccountPassword(vo) - .map { messageHandler(it) } + .map(::messageHandler) } @GetMapping("/logout") @@ -71,7 +66,7 @@ class AuthorizeController( return service .jwtTokenRelogin(authorization) ?.map { ApiResponse.success(it) } - ?: Mono.just(messageHandler("Please login with password.")) + ?: Mono.just(ApiResponse.failure(401, null, "Please login with password.")) } private fun messageHandler(wrongMessage: String): ApiResponse { diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/EmailVerificationMsg.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/EmailVerificationMsg.kt index 791a98c..f309845 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/EmailVerificationMsg.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/EmailVerificationMsg.kt @@ -1,10 +1,11 @@ package club.itstudio.itmanager.entity.dto +import club.itstudio.itmanager.entity.enums.EmailType import kotlinx.serialization.Serializable @Serializable data class EmailVerificationMsg( val email: String, - val type: String, + val type: EmailType, val code: String ) \ No newline at end of file diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/AccountRole.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/AccountRole.kt new file mode 100644 index 0000000..050128e --- /dev/null +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/AccountRole.kt @@ -0,0 +1,16 @@ +package club.itstudio.itmanager.entity.enums + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class AccountRole { + @SerialName("user") + USER, + @SerialName("admin") + ADMIN; + + companion object { + fun fromIsStaff(isStaff: Boolean): AccountRole = if (isStaff) ADMIN else USER + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/EmailType.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/EmailType.kt new file mode 100644 index 0000000..db95edf --- /dev/null +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/EmailType.kt @@ -0,0 +1,13 @@ +package club.itstudio.itmanager.entity.enums + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class EmailType { + @SerialName("register") + REGISTER, + + @SerialName("reset") + RESET, +} \ No newline at end of file diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/listener/MailQueueListener.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/listener/MailQueueListener.kt index ea0d601..1a9b526 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/listener/MailQueueListener.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/listener/MailQueueListener.kt @@ -1,6 +1,7 @@ package club.itstudio.itmanager.listener import club.itstudio.itmanager.entity.dto.EmailVerificationMsg +import club.itstudio.itmanager.entity.enums.EmailType import kotlinx.serialization.json.Json import org.slf4j.LoggerFactory import org.springframework.amqp.rabbit.annotation.RabbitHandler @@ -28,21 +29,19 @@ class MailQueueListener( val (email, type, code) = data val message = when (type) { - "register" -> createMailMessage( + EmailType.REGISTER -> createMailMessage( "Welcome to Our Service", "Thank you for registering! Your verification code is: $code" + "\nThis code is valid for 3 minutes.", email ) - "reset" -> createMailMessage( + EmailType.RESET -> createMailMessage( "Password Reset Request", "You requested a password reset. Your verification code is: $code" + "\nThis code is valid for 3 minutes.", email ) - - else -> return } sender.send(message) logger.info("Email sent to $email.") diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AccountService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AccountService.kt index 9afbc43..1bb3a0f 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AccountService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AccountService.kt @@ -10,9 +10,13 @@ import reactor.core.publisher.Mono interface AccountService : IService, ReactiveUserDetailsService { fun findAccountByNameOrEmail(text: String): Mono - fun askEmailVerifyCode(type: String, email: String, ip: String): Mono - fun registerEmailAccount(vo: EmailRegisterVO): Mono - fun resetEmailAccountPassword(vo: PasswordResetVO): Mono - fun invalidateJwt(headerToken: String?): Boolean - fun jwtTokenRelogin(headerToken: String?): Mono? + fun findAccountByName(username: String): Mono + fun findAccountByEmail(email: String): Mono + fun existAccountByName(username: String): Mono + fun existAccountByEmail(email: String): Mono + fun resetPasswordByEmail(email: String, encodedPassword: String): Mono + +// fun batchInsert(accounts: List): Mono +// fun replaceAll(accounts: List): Mono +// fun exportAll(): Mono> } \ No newline at end of file diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AttachmentService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AttachmentService.kt index c0249ec..0282736 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AttachmentService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/AttachmentService.kt @@ -2,5 +2,14 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Attachment +import reactor.core.publisher.Mono -interface AttachmentService : IService +interface AttachmentService : IService { + fun getAttachmentById(id: Long): Mono + fun listByRelated(relatedTable: String, relatedId: Long): Mono> + fun listByUploader(uploadedBy: Long): Mono> + fun createAttachment(attachment: Attachment): Mono + fun deleteAttachment(id: Long): Mono + fun deleteByRelated(relatedTable: String, relatedId: Long): Mono + fun exportAllAttachments(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt index 0c48c56..2ff8617 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt @@ -2,5 +2,16 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.BorrowRecord +import reactor.core.publisher.Mono -interface BorrowRecordService : IService +interface BorrowRecordService : IService { + fun getBorrowRecordById(id: Long): Mono + fun listByItem(itemId: Long): Mono> + fun listByBorrower(borrowerId: Long): Mono> + fun listByStatus(status: String): Mono> + fun listOverdueRecords(): Mono> + fun createBorrowRecord(record: BorrowRecord): Mono + fun updateBorrowRecord(record: BorrowRecord): Mono + fun deleteBorrowRecord(id: Long): Mono + fun exportAllBorrowRecords(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/CategoryService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/CategoryService.kt index 52e7421..7446342 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/CategoryService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/CategoryService.kt @@ -2,5 +2,14 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Category +import reactor.core.publisher.Mono -interface CategoryService : IService +interface CategoryService : IService { + fun getCategoryById(id: Long): Mono + fun listAllCategories(): Mono> + fun listByParentId(parentId: Long): Mono> + fun createCategory(category: Category): Mono + fun updateCategory(category: Category): Mono + fun deleteCategory(id: Long): Mono + fun exportAllCategories(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/DepartmentService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/DepartmentService.kt index e3eec2c..f8221d1 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/DepartmentService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/DepartmentService.kt @@ -2,5 +2,14 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Department +import reactor.core.publisher.Mono -interface DepartmentService : IService +interface DepartmentService : IService { + fun getDepartmentById(id: Long): Mono + fun listAllDepartments(): Mono> + fun listByParentId(parentId: Long): Mono> + fun createDepartment(department: Department): Mono + fun updateDepartment(department: Department): Mono + fun deleteDepartment(id: Long): Mono + fun exportAllDepartments(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EmailNoticeService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EmailNoticeService.kt index 056e44d..3e21afe 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EmailNoticeService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EmailNoticeService.kt @@ -2,5 +2,15 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.EmailNotice +import reactor.core.publisher.Mono -interface EmailNoticeService : IService +interface EmailNoticeService : IService { + fun getEmailNoticeById(id: Long): Mono + fun listAllNotices(): Mono> + fun listEnabledNotices(): Mono> + fun createEmailNotice(notice: EmailNotice): Mono + fun updateEmailNotice(notice: EmailNotice): Mono + fun deleteEmailNotice(id: Long): Mono + fun toggleEnabled(id: Long): Mono + fun exportAllEmailNotices(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt index 54b0934..ac1bcb8 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt @@ -2,5 +2,16 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Evaluation +import reactor.core.publisher.Mono -interface EvaluationService : IService +interface EvaluationService : IService { + fun getEvaluationById(id: Long): Mono + fun listByEvaluator(evaluatorId: Long): Mono> + fun listByRelatedItem(itemId: Long): Mono> + fun listByScore(score: Short): Mono> + fun createEvaluation(evaluation: Evaluation): Mono + fun updateEvaluation(evaluation: Evaluation): Mono + fun deleteEvaluation(id: Long): Mono + fun deleteByEvaluator(evaluatorId: Long): Mono + fun exportAllEvaluations(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/FinanceRecordService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/FinanceRecordService.kt index d383e34..eca6510 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/FinanceRecordService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/FinanceRecordService.kt @@ -2,5 +2,16 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.FinanceRecord +import reactor.core.publisher.Mono -interface FinanceRecordService : IService +interface FinanceRecordService : IService { + fun getFinanceRecordById(id: Long): Mono + fun listByType(type: String): Mono> + fun listByDateRange(start: String, end: String): Mono> + fun listByVendor(vendor: String): Mono> + fun listByRelatedItem(itemId: Long): Mono> + fun createFinanceRecord(record: FinanceRecord): Mono + fun updateFinanceRecord(record: FinanceRecord): Mono + fun deleteFinanceRecord(id: Long): Mono + fun exportAllFinanceRecords(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemService.kt index 43b8863..448675c 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemService.kt @@ -1,6 +1,23 @@ package club.itstudio.itmanager.service +import com.baomidou.mybatisplus.extension.plugins.pagination.Page import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Item +import reactor.core.publisher.Mono -interface ItemService : IService +interface ItemService : IService { + fun pageItems(page: Page, keyword: String?, status: String?): Mono> + fun getItemById(id: Long): Mono + fun listAvailableItems(): Mono> + fun listInUseItems(): Mono> + fun listItemsByCategory(categoryId: Long): Mono> + fun createItem(item: Item): Mono + fun updateItem(item: Item): Mono + fun removeItemById(id: Long): Mono + fun setPrimaryImage(id: Long, imagePath: String): Mono + fun borrowItem(id: Long, quantity: Int): Mono + fun returnItem(id: Long, quantity: Int): Mono + fun batchInsertItems(items: List): Mono + fun replaceAllItems(items: List): Mono + fun exportAllItems(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemTransactionService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemTransactionService.kt index befd743..88d5404 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemTransactionService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/ItemTransactionService.kt @@ -2,5 +2,15 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.ItemTransaction +import reactor.core.publisher.Mono -interface ItemTransactionService : IService +interface ItemTransactionService : IService { + fun getTransactionById(id: Long): Mono + fun listByItem(itemId: Long): Mono> + fun listByType(transType: String): Mono> + fun listByOperator(operatorId: Long): Mono> + fun createTransaction(transaction: ItemTransaction): Mono + fun updateTransaction(transaction: ItemTransaction): Mono + fun deleteTransaction(id: Long): Mono + fun exportAllTransactions(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt index e3f28b5..ab7003c 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt @@ -2,5 +2,16 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Memo +import reactor.core.publisher.Mono -interface MemoService : IService +interface MemoService : IService { + fun getMemoById(id: Long): Mono + fun searchMemos(keyword: String?): Mono> + fun listByAuthor(authorId: Long): Mono> + fun listByRelatedItem(itemId: Long): Mono> + fun listPublicMemos(): Mono> + fun createMemo(memo: Memo): Mono + fun updateMemo(memo: Memo): Mono + fun deleteMemo(id: Long): Mono + fun exportAllMemos(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt index becdf3b..fa3c44c 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt @@ -2,5 +2,16 @@ package club.itstudio.itmanager.service import com.baomidou.mybatisplus.extension.service.IService import club.itstudio.itmanager.entity.dto.Personnel +import reactor.core.publisher.Mono -interface PersonnelService : IService +interface PersonnelService : IService { + fun getPersonnelById(id: Long): Mono + fun searchPersonnel(keyword: String?): Mono> + fun listByDepartment(departmentId: Long): Mono> + fun listByRole(role: String): Mono> + fun createPersonnel(personnel: Personnel): Mono + fun updatePersonnel(personnel: Personnel): Mono + fun deletePersonnel(id: Long): Mono + fun setActive(id: Long, active: Boolean): Mono + fun exportAllPersonnel(): Mono> +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AccountServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AccountServiceImpl.kt index e909420..4f1f762 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AccountServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AccountServiceImpl.kt @@ -1,207 +1,144 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.core.toolkit.Wrappers -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Account -import club.itstudio.itmanager.entity.dto.EmailVerificationMsg -import club.itstudio.itmanager.entity.vo.request.EmailRegisterVO -import club.itstudio.itmanager.entity.vo.request.PasswordResetVO -import club.itstudio.itmanager.entity.vo.response.ReloginVO +import club.itstudio.itmanager.entity.enums.AccountRole import club.itstudio.itmanager.mapper.AccountMapper import club.itstudio.itmanager.service.AccountService -import club.itstudio.itmanager.utils.Const - -import club.itstudio.itmanager.utils.FlowUtils -import club.itstudio.itmanager.utils.JwtUtils -import org.springframework.amqp.core.AmqpTemplate -import org.springframework.data.redis.core.ReactiveStringRedisTemplate +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers -import java.time.Duration +import javax.security.auth.login.AccountNotFoundException +import kotlin.jvm.java @Service -class AccountServiceImpl( - private val utils: FlowUtils, - private val jwtUtils: JwtUtils, - private val amqpTemplate: AmqpTemplate, - private val stringRedisTemplate: ReactiveStringRedisTemplate, - private val encoder: PasswordEncoder -) : ServiceImpl(), AccountService { +class AccountServiceImpl : ServiceImpl(), AccountService { - override fun findByUsername(username: String): Mono { - return findAccountByNameOrEmail(username) - .switchIfEmpty(Mono.error(UsernameNotFoundException("Account with name $username not found"))) - .map { account -> - User.withUsername(username) - .password(account.password) - .roles(if (account.isStaff) "STAFF" else "USER") + private val logger = LoggerFactory.getLogger(this::class.java) + + override fun findByUsername( + username: String + ): Mono { + + return findAccountByName(username) + .map { + User.withUsername(it.username) + .password(it.password) + .roles(AccountRole.fromIsStaff(it.isStaff).name) .build() } + .onErrorMap( + AccountNotFoundException::class.java + ) { + UsernameNotFoundException( + "User not found" + ) + } } - override fun findAccountByNameOrEmail(text: String): Mono { - return Mono.fromCallable { - this.query() - .eq("username", text).or() - .eq("email", text) + override fun findAccountByName(username: String): Mono = + findAccount("username") { + ktQuery() + .eq(Account::username, username) .one() - }.flatMap { account -> - Mono.just(account) } + + override fun findAccountByEmail(email: String): Mono = + findAccount("email") { + ktQuery() + .eq(Account::email, email) + .one() + } + + private fun blockingCall( + block: () -> T? + ): Mono = + Mono.fromCallable(block) + .subscribeOn(Schedulers.boundedElastic()) + + private fun findAccount( + fieldName: String, + query: () -> Account? + ): Mono { + return blockingCall { + logger.info( + "Searching account by {}", + fieldName + ) + query() + }.switchIfEmpty( + Mono.error( + AccountNotFoundException() + ) + ) } - override fun askEmailVerifyCode(type: String, email: String, ip: String): Mono { - return verifyLimit(ip).flatMap { allowed -> - if (allowed) { - val code = (100000..999999).random().toString() - val data = EmailVerificationMsg(email, type, code) - - Mono.fromRunnable { - amqpTemplate.convertAndSend("mail", data) - }.then( - stringRedisTemplate.opsForValue() - .set( - Const.VERIFY_EMAIL_DATA + email, - code, - Duration.ofMinutes(3) - ) - ).thenReturn("").subscribeOn(Schedulers.boundedElastic()) - } else { - Mono.just("Request limit exceeded. Please try again later.") - } - } - } - - override fun registerEmailAccount(vo: EmailRegisterVO): Mono { - val (email, code, username, password) = vo - - return verifyCode(email, code).flatMap { error -> - if (error.isNotBlank()) { - return@flatMap Mono.just(error) - } - - existAccountByEmail(email).flatMap { emailExists -> - if (emailExists) { - return@flatMap Mono.just("account with the same email already exists.") + override fun findAccountByNameOrEmail( + text: String + ): Mono { + return blockingCall { + logger.info( + "Searching account by identifier" + ) + ktQuery() + .and { + it.eq(Account::email, text) + .or() + .eq(Account::username, text) } + .one() + }.switchIfEmpty( + Mono.error(AccountNotFoundException()) + ) + } - existAccountByUsername(username).flatMap { usernameExists -> - if (usernameExists) { - return@flatMap Mono.just("username already exists.") - } + override fun existAccountByName( + username: String + ): Mono { + return blockingCall { + ktQuery() + .eq(Account::username, username) + .exists() - val encodedPassword = encoder.encode(password) - val account = Account( - username = username, - password = encodedPassword!!, - email = email, - isActive = true, - isStaff = false + } + } + + override fun existAccountByEmail( + email: String + ): Mono { + return blockingCall { + ktQuery() + .eq(Account::email, email) + .exists() + + } + } + + override fun resetPasswordByEmail( + email: String, + encodedPassword: String + ): Mono { + return blockingCall { + ktUpdate() + .eq(Account::email, email) + .set( + Account::password, + encodedPassword + ) + .update() + } + .flatMap { updated -> + if (updated) { + Mono.empty() + } else { + Mono.error( + AccountNotFoundException() ) - - Mono.fromCallable { this.save(account) } - .flatMap { success -> - if (success) { - stringRedisTemplate.delete(Const.VERIFY_EMAIL_DATA + email) - .thenReturn("") - } else { - Mono.just("something went wrong. Please contact the administrator.") - } - } } } - } - } - - override fun resetEmailAccountPassword(vo: PasswordResetVO): Mono { - val (email, code, password) = vo - - return verifyCode(email, code).flatMap { error -> - if (error.isNotBlank()) { - return@flatMap Mono.just(error) - } - - existAccountByEmail(email).flatMap { exists -> - if (!exists) { - return@flatMap Mono.just("account with the email does not exist.") - } - - val encodedPassword = encoder.encode(password) - - Mono.fromCallable { - this.update() - .eq("email", email) - .set("password", encodedPassword) - .update() - }.flatMap { success -> - if (success) { - stringRedisTemplate.delete(Const.VERIFY_EMAIL_DATA + email) - .thenReturn("") - } else { - Mono.just("something went wrong. Please contact the administrator.") - } - } - } - } - } - - override fun jwtTokenRelogin(headerToken: String?): Mono? { - val user = jwtUtils.resolveJwt(headerToken?.substring("Bearer ".length)) - ?.let { jwtUtils.toUser(it) } - ?: return null - val accountMono = findAccountByNameOrEmail(user.username) - val voMono = accountMono.map { - ReloginVO( - jwtUtils.createJwt(user, it.id!!, it.username), - jwtUtils.expiresTime().toInstant().toString() - ) - } - return voMono - } - - override fun invalidateJwt(headerToken: String?): Boolean { - return jwtUtils.invalidateJwt(headerToken) - } - - private fun existAccountByEmail(email: String): Mono { - return Mono.fromCallable { - this.baseMapper.exists( - Wrappers.query() - .eq("email", email) - ) - } - } - - private fun existAccountByUsername(username: String): Mono { - return Mono.fromCallable { - this.baseMapper.exists( - Wrappers.query() - .eq("username", username) - ) - } - } - - private fun verifyLimit(ip: String): Mono { - val key = Const.VERIFY_EMAIL_LIMIT + ip - return utils.limitOnceCheck(key, 60) - } - - private fun verifyCode(email: String, receivedCode: String?): Mono { - return stringRedisTemplate.opsForValue() - .get(Const.VERIFY_EMAIL_DATA + email) - .flatMap { code -> - when { - receivedCode == null || code != receivedCode -> - Mono.just("verify code is wrong.") - - else -> Mono.just("") - } - } - .switchIfEmpty(Mono.just("verify code has not been sent")) } } \ No newline at end of file diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AttachmentServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AttachmentServiceImpl.kt index a7b9ac1..d4cd02e 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AttachmentServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/AttachmentServiceImpl.kt @@ -1,10 +1,62 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Attachment import club.itstudio.itmanager.mapper.AttachmentMapper import club.itstudio.itmanager.service.AttachmentService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class AttachmentServiceImpl : ServiceImpl(), AttachmentService +class AttachmentServiceImpl : ServiceImpl(), AttachmentService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getAttachmentById(id: Long): Mono = + blockingCall { + logger.info("Searching attachment by id") + ktQuery().eq(Attachment::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Attachment not found"))) + + override fun listByRelated(relatedTable: String, relatedId: Long): Mono> = + blockingCall { + logger.info("Searching attachments by related table and id") + ktQuery().eq(Attachment::relatedTable, relatedTable).eq(Attachment::relatedId, relatedId).list() + } + + override fun listByUploader(uploadedBy: Long): Mono> = + blockingCall { + logger.info("Searching attachments by uploader") + ktQuery().eq(Attachment::uploadedBy, uploadedBy).list() + } + + override fun createAttachment(attachment: Attachment): Mono = + blockingCall { + logger.info("Creating attachment") + save(attachment) + } + + override fun deleteAttachment(id: Long): Mono = + blockingCall { + logger.info("Deleting attachment by id") + removeById(id) + } + + override fun deleteByRelated(relatedTable: String, relatedId: Long): Mono = + blockingCall { + logger.info("Deleting attachments by related table and id") + remove(ktQuery().eq(Attachment::relatedTable, relatedTable).eq(Attachment::relatedId, relatedId)) + } + + override fun exportAllAttachments(): Mono> = + blockingCall { + logger.info("Exporting all attachments") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt index 4abc50f..3dc6e3f 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt @@ -1,10 +1,74 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.BorrowRecord import club.itstudio.itmanager.mapper.BorrowRecordMapper import club.itstudio.itmanager.service.BorrowRecordService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class BorrowRecordServiceImpl : ServiceImpl(), BorrowRecordService +class BorrowRecordServiceImpl : ServiceImpl(), BorrowRecordService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getBorrowRecordById(id: Long): Mono = + blockingCall { + logger.info("Searching borrow record by id") + ktQuery().eq(BorrowRecord::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("BorrowRecord not found"))) + + override fun listByItem(itemId: Long): Mono> = + blockingCall { + logger.info("Searching borrow records by item id") + ktQuery().eq(BorrowRecord::itemId, itemId).list() + } + + override fun listByBorrower(borrowerId: Long): Mono> = + blockingCall { + logger.info("Searching borrow records by borrower id") + ktQuery().eq(BorrowRecord::borrowerId, borrowerId).list() + } + + override fun listByStatus(status: String): Mono> = + blockingCall { + logger.info("Searching borrow records by status") + ktQuery().eq(BorrowRecord::status, status).list() + } + + override fun listOverdueRecords(): Mono> = + blockingCall { + logger.info("Searching overdue borrow records") + ktQuery().eq(BorrowRecord::status, "OVERDUE").list() + } + + override fun createBorrowRecord(record: BorrowRecord): Mono = + blockingCall { + logger.info("Creating borrow record") + save(record) + } + + override fun updateBorrowRecord(record: BorrowRecord): Mono = + blockingCall { + logger.info("Updating borrow record") + updateById(record) + } + + override fun deleteBorrowRecord(id: Long): Mono = + blockingCall { + logger.info("Deleting borrow record by id") + removeById(id) + } + + override fun exportAllBorrowRecords(): Mono> = + blockingCall { + logger.info("Exporting all borrow records") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/CategoryServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/CategoryServiceImpl.kt index a51ec41..841bb9c 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/CategoryServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/CategoryServiceImpl.kt @@ -1,10 +1,62 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Category import club.itstudio.itmanager.mapper.CategoryMapper import club.itstudio.itmanager.service.CategoryService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class CategoryServiceImpl : ServiceImpl(), CategoryService +class CategoryServiceImpl : ServiceImpl(), CategoryService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getCategoryById(id: Long): Mono = + blockingCall { + logger.info("Searching category by id") + ktQuery().eq(Category::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Category not found"))) + + override fun listAllCategories(): Mono> = + blockingCall { + logger.info("Listing all categories") + list() + } + + override fun listByParentId(parentId: Long): Mono> = + blockingCall { + logger.info("Searching categories by parent id") + ktQuery().eq(Category::parentId, parentId).list() + } + + override fun createCategory(category: Category): Mono = + blockingCall { + logger.info("Creating category") + save(category) + } + + override fun updateCategory(category: Category): Mono = + blockingCall { + logger.info("Updating category") + updateById(category) + } + + override fun deleteCategory(id: Long): Mono = + blockingCall { + logger.info("Deleting category by id") + removeById(id) + } + + override fun exportAllCategories(): Mono> = + blockingCall { + logger.info("Exporting all categories") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/DepartmentServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/DepartmentServiceImpl.kt index 126be72..d74adb8 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/DepartmentServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/DepartmentServiceImpl.kt @@ -1,10 +1,62 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Department import club.itstudio.itmanager.mapper.DepartmentMapper import club.itstudio.itmanager.service.DepartmentService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class DepartmentServiceImpl : ServiceImpl(), DepartmentService +class DepartmentServiceImpl : ServiceImpl(), DepartmentService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getDepartmentById(id: Long): Mono = + blockingCall { + logger.info("Searching department by id") + ktQuery().eq(Department::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Department not found"))) + + override fun listAllDepartments(): Mono> = + blockingCall { + logger.info("Listing all departments") + list() + } + + override fun listByParentId(parentId: Long): Mono> = + blockingCall { + logger.info("Searching departments by parent id") + ktQuery().eq(Department::parentId, parentId).list() + } + + override fun createDepartment(department: Department): Mono = + blockingCall { + logger.info("Creating department") + save(department) + } + + override fun updateDepartment(department: Department): Mono = + blockingCall { + logger.info("Updating department") + updateById(department) + } + + override fun deleteDepartment(id: Long): Mono = + blockingCall { + logger.info("Deleting department by id") + removeById(id) + } + + override fun exportAllDepartments(): Mono> = + blockingCall { + logger.info("Exporting all departments") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EmailNoticeServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EmailNoticeServiceImpl.kt index ece0c4d..a176e28 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EmailNoticeServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EmailNoticeServiceImpl.kt @@ -1,10 +1,72 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.EmailNotice import club.itstudio.itmanager.mapper.EmailNoticeMapper import club.itstudio.itmanager.service.EmailNoticeService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class EmailNoticeServiceImpl : ServiceImpl(), EmailNoticeService +class EmailNoticeServiceImpl : ServiceImpl(), EmailNoticeService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getEmailNoticeById(id: Long): Mono = + blockingCall { + logger.info("Searching email notice by id") + ktQuery().eq(EmailNotice::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("EmailNotice not found"))) + + override fun listAllNotices(): Mono> = + blockingCall { + logger.info("Listing all email notices") + list() + } + + override fun listEnabledNotices(): Mono> = + blockingCall { + logger.info("Listing enabled email notices") + ktQuery().eq(EmailNotice::enabled, true).list() + } + + override fun createEmailNotice(notice: EmailNotice): Mono = + blockingCall { + logger.info("Creating email notice") + save(notice) + } + + override fun updateEmailNotice(notice: EmailNotice): Mono = + blockingCall { + logger.info("Updating email notice") + updateById(notice) + } + + override fun deleteEmailNotice(id: Long): Mono = + blockingCall { + logger.info("Deleting email notice by id") + removeById(id) + } + + override fun toggleEnabled(id: Long): Mono = + blockingCall { + val notice = ktQuery().eq(EmailNotice::id, id).one() + if (notice != null) { + updateById(notice.copy(enabled = !notice.enabled)) + } else { + false + } + } + + override fun exportAllEmailNotices(): Mono> = + blockingCall { + logger.info("Exporting all email notices") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt index f10ab88..bed43a4 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt @@ -1,10 +1,74 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Evaluation import club.itstudio.itmanager.mapper.EvaluationMapper import club.itstudio.itmanager.service.EvaluationService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class EvaluationServiceImpl : ServiceImpl(), EvaluationService +class EvaluationServiceImpl : ServiceImpl(), EvaluationService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getEvaluationById(id: Long): Mono = + blockingCall { + logger.info("Searching evaluation by id") + ktQuery().eq(Evaluation::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Evaluation not found"))) + + override fun listByEvaluator(evaluatorId: Long): Mono> = + blockingCall { + logger.info("Searching evaluations by evaluator id") + ktQuery().eq(Evaluation::evaluatorId, evaluatorId).list() + } + + override fun listByRelatedItem(itemId: Long): Mono> = + blockingCall { + logger.info("Searching evaluations by related item id") + ktQuery().eq(Evaluation::relatedItemId, itemId).list() + } + + override fun listByScore(score: Short): Mono> = + blockingCall { + logger.info("Searching evaluations by score") + ktQuery().eq(Evaluation::score, score).list() + } + + override fun createEvaluation(evaluation: Evaluation): Mono = + blockingCall { + logger.info("Creating evaluation") + save(evaluation) + } + + override fun updateEvaluation(evaluation: Evaluation): Mono = + blockingCall { + logger.info("Updating evaluation") + updateById(evaluation) + } + + override fun deleteEvaluation(id: Long): Mono = + blockingCall { + logger.info("Deleting evaluation by id") + removeById(id) + } + + override fun deleteByEvaluator(evaluatorId: Long): Mono = + blockingCall { + logger.info("Deleting evaluations by evaluator id") + remove(ktQuery().eq(Evaluation::evaluatorId, evaluatorId)) + } + + override fun exportAllEvaluations(): Mono> = + blockingCall { + logger.info("Exporting all evaluations") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/FinanceRecordServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/FinanceRecordServiceImpl.kt index 6a4e154..438e3d7 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/FinanceRecordServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/FinanceRecordServiceImpl.kt @@ -1,10 +1,74 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.FinanceRecord import club.itstudio.itmanager.mapper.FinanceRecordMapper import club.itstudio.itmanager.service.FinanceRecordService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class FinanceRecordServiceImpl : ServiceImpl(), FinanceRecordService +class FinanceRecordServiceImpl : ServiceImpl(), FinanceRecordService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getFinanceRecordById(id: Long): Mono = + blockingCall { + logger.info("Searching finance record by id") + ktQuery().eq(FinanceRecord::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("FinanceRecord not found"))) + + override fun listByType(type: String): Mono> = + blockingCall { + logger.info("Searching finance records by type") + ktQuery().eq(FinanceRecord::type, type).list() + } + + override fun listByDateRange(start: String, end: String): Mono> = + blockingCall { + logger.info("Searching finance records by date range") + ktQuery().apply("occurred_at BETWEEN {0} AND {1}", start, end).list() + } + + override fun listByVendor(vendor: String): Mono> = + blockingCall { + logger.info("Searching finance records by vendor") + ktQuery().eq(FinanceRecord::vendor, vendor).list() + } + + override fun listByRelatedItem(itemId: Long): Mono> = + blockingCall { + logger.info("Searching finance records by related item id") + ktQuery().eq(FinanceRecord::relatedItemId, itemId).list() + } + + override fun createFinanceRecord(record: FinanceRecord): Mono = + blockingCall { + logger.info("Creating finance record") + save(record) + } + + override fun updateFinanceRecord(record: FinanceRecord): Mono = + blockingCall { + logger.info("Updating finance record") + updateById(record) + } + + override fun deleteFinanceRecord(id: Long): Mono = + blockingCall { + logger.info("Deleting finance record by id") + removeById(id) + } + + override fun exportAllFinanceRecords(): Mono> = + blockingCall { + logger.info("Exporting all finance records") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemServiceImpl.kt index 1b0243c..7d73c58 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemServiceImpl.kt @@ -1,10 +1,123 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Item import club.itstudio.itmanager.mapper.ItemMapper import club.itstudio.itmanager.service.ItemService +import com.baomidou.mybatisplus.extension.plugins.pagination.Page +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class ItemServiceImpl : ServiceImpl(), ItemService +class ItemServiceImpl : ServiceImpl(), ItemService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun pageItems(page: Page, keyword: String?, status: String?): Mono> = + blockingCall { + logger.info("Paging items with keyword and status") + val wrapper = ktQuery() + if (!keyword.isNullOrBlank()) { + wrapper.and { + it.like(Item::name, keyword).or().like(Item::code, keyword) + } + } + if (!status.isNullOrBlank()) { + wrapper.eq(Item::status, status) + } + page(page, wrapper) + } + + override fun getItemById(id: Long): Mono = + blockingCall { + logger.info("Searching item by id") + ktQuery().eq(Item::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Item not found"))) + + override fun listAvailableItems(): Mono> = + blockingCall { + logger.info("Listing available items") + ktQuery().gt(Item::availableQuantity, 0).eq(Item::status, "normal").list() + } + + override fun listInUseItems(): Mono> = + blockingCall { + logger.info("Listing in-use items") + ktQuery().apply("total_quantity - available_quantity > 0").list() + } + + override fun listItemsByCategory(categoryId: Long): Mono> = + blockingCall { + logger.info("Listing items by category") + ktQuery().eq(Item::categoryId, categoryId).list() + } + + override fun createItem(item: Item): Mono = + blockingCall { + logger.info("Creating item") + save(item) + } + + override fun updateItem(item: Item): Mono = + blockingCall { + logger.info("Updating item") + updateById(item) + } + + override fun removeItemById(id: Long): Mono = + blockingCall { + logger.info("Removing item by id") + removeById(id) + } + + override fun setPrimaryImage(id: Long, imagePath: String): Mono = + blockingCall { + logger.info("Setting primary image for item") + ktUpdate().eq(Item::id, id).set(Item::imagePath, imagePath).update() + } + + override fun borrowItem(id: Long, quantity: Int): Mono = + blockingCall { + val item = ktQuery().eq(Item::id, id).one() + if (item != null && item.availableQuantity >= quantity) { + ktUpdate().eq(Item::id, id).set(Item::availableQuantity, item.availableQuantity - quantity).update() + } else { + false + } + } + + override fun returnItem(id: Long, quantity: Int): Mono = + blockingCall { + val item = ktQuery().eq(Item::id, id).one() + if (item != null && item.totalQuantity >= item.availableQuantity + quantity) { + ktUpdate().eq(Item::id, id).set(Item::availableQuantity, item.availableQuantity + quantity).update() + } else { + false + } + } + + override fun batchInsertItems(items: List): Mono = + blockingCall { + logger.info("Batch inserting items") + saveBatch(items) + } + + override fun replaceAllItems(items: List): Mono = + blockingCall { + logger.info("Replacing all items") + remove(ktQuery()) + saveBatch(items) + } + + override fun exportAllItems(): Mono> = + blockingCall { + logger.info("Exporting all items") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemTransactionServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemTransactionServiceImpl.kt index 59b1030..b5447a5 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemTransactionServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/ItemTransactionServiceImpl.kt @@ -1,10 +1,68 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.ItemTransaction import club.itstudio.itmanager.mapper.ItemTransactionMapper import club.itstudio.itmanager.service.ItemTransactionService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class ItemTransactionServiceImpl : ServiceImpl(), ItemTransactionService +class ItemTransactionServiceImpl : ServiceImpl(), ItemTransactionService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getTransactionById(id: Long): Mono = + blockingCall { + logger.info("Searching transaction by id") + ktQuery().eq(ItemTransaction::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("ItemTransaction not found"))) + + override fun listByItem(itemId: Long): Mono> = + blockingCall { + logger.info("Searching transactions by item id") + ktQuery().eq(ItemTransaction::itemId, itemId).list() + } + + override fun listByType(transType: String): Mono> = + blockingCall { + logger.info("Searching transactions by type") + ktQuery().eq(ItemTransaction::transType, transType).list() + } + + override fun listByOperator(operatorId: Long): Mono> = + blockingCall { + logger.info("Searching transactions by operator id") + ktQuery().eq(ItemTransaction::operatorId, operatorId).list() + } + + override fun createTransaction(transaction: ItemTransaction): Mono = + blockingCall { + logger.info("Creating transaction") + save(transaction) + } + + override fun updateTransaction(transaction: ItemTransaction): Mono = + blockingCall { + logger.info("Updating transaction") + updateById(transaction) + } + + override fun deleteTransaction(id: Long): Mono = + blockingCall { + logger.info("Deleting transaction by id") + removeById(id) + } + + override fun exportAllTransactions(): Mono> = + blockingCall { + logger.info("Exporting all transactions") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt index b241a85..2045119 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt @@ -1,10 +1,80 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Memo import club.itstudio.itmanager.mapper.MemoMapper import club.itstudio.itmanager.service.MemoService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class MemoServiceImpl : ServiceImpl(), MemoService +class MemoServiceImpl : ServiceImpl(), MemoService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getMemoById(id: Long): Mono = + blockingCall { + logger.info("Searching memo by id") + ktQuery().eq(Memo::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Memo not found"))) + + override fun searchMemos(keyword: String?): Mono> = + blockingCall { + logger.info("Searching memos with keyword") + if (keyword.isNullOrBlank()) { + list() + } else { + ktQuery().and { + it.like(Memo::title, keyword).or().like(Memo::content, keyword) + }.list() + } + } + + override fun listByAuthor(authorId: Long): Mono> = + blockingCall { + logger.info("Searching memos by author id") + ktQuery().eq(Memo::authorId, authorId).list() + } + + override fun listByRelatedItem(itemId: Long): Mono> = + blockingCall { + logger.info("Searching memos by related item id") + ktQuery().eq(Memo::relatedItemId, itemId).list() + } + + override fun listPublicMemos(): Mono> = + blockingCall { + logger.info("Listing public memos") + ktQuery().eq(Memo::isPublic, true).list() + } + + override fun createMemo(memo: Memo): Mono = + blockingCall { + logger.info("Creating memo") + save(memo) + } + + override fun updateMemo(memo: Memo): Mono = + blockingCall { + logger.info("Updating memo") + updateById(memo) + } + + override fun deleteMemo(id: Long): Mono = + blockingCall { + logger.info("Deleting memo by id") + removeById(id) + } + + override fun exportAllMemos(): Mono> = + blockingCall { + logger.info("Exporting all memos") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt index 501e7f2..ff1463f 100644 --- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt @@ -1,10 +1,83 @@ package club.itstudio.itmanager.service.impl -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import club.itstudio.itmanager.entity.dto.Personnel import club.itstudio.itmanager.mapper.PersonnelMapper import club.itstudio.itmanager.service.PersonnelService +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.util.NoSuchElementException @Service -class PersonnelServiceImpl : ServiceImpl(), PersonnelService +class PersonnelServiceImpl : ServiceImpl(), PersonnelService { + + private val logger = LoggerFactory.getLogger(this::class.java) + + private fun blockingCall(block: () -> T?): Mono = + Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic()) + + override fun getPersonnelById(id: Long): Mono = + blockingCall { + logger.info("Searching personnel by id") + ktQuery().eq(Personnel::id, id).one() + }.switchIfEmpty(Mono.error(NoSuchElementException("Personnel not found"))) + + override fun searchPersonnel(keyword: String?): Mono> = + blockingCall { + logger.info("Searching personnel with keyword") + if (keyword.isNullOrBlank()) { + list() + } else { + ktQuery().and { + it.like(Personnel::name, keyword) + .or().like(Personnel::employeeNo, keyword) + .or().like(Personnel::phone, keyword) + .or().like(Personnel::email, keyword) + }.list() + } + } + + override fun listByDepartment(departmentId: Long): Mono> = + blockingCall { + logger.info("Searching personnel by department id") + ktQuery().eq(Personnel::departmentId, departmentId).list() + } + + override fun listByRole(role: String): Mono> = + blockingCall { + logger.info("Searching personnel by role") + ktQuery().eq(Personnel::role, role).list() + } + + override fun createPersonnel(personnel: Personnel): Mono = + blockingCall { + logger.info("Creating personnel") + save(personnel) + } + + override fun updatePersonnel(personnel: Personnel): Mono = + blockingCall { + logger.info("Updating personnel") + updateById(personnel) + } + + override fun deletePersonnel(id: Long): Mono = + blockingCall { + logger.info("Deleting personnel by id") + removeById(id) + } + + override fun setActive(id: Long, active: Boolean): Mono = + blockingCall { + logger.info("Setting personnel active status") + ktUpdate().eq(Personnel::id, id).set(Personnel::isActive, active).update() + } + + override fun exportAllPersonnel(): Mono> = + blockingCall { + logger.info("Exporting all personnel") + list() + } +} diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/AuthorizationService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/AuthorizationService.kt new file mode 100644 index 0000000..e84d1ff --- /dev/null +++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/AuthorizationService.kt @@ -0,0 +1,148 @@ +package club.itstudio.itmanager.service.v1 + +import club.itstudio.itmanager.entity.dto.Account +import club.itstudio.itmanager.entity.dto.EmailVerificationMsg +import club.itstudio.itmanager.entity.enums.EmailType +import club.itstudio.itmanager.entity.vo.request.EmailRegisterVO +import club.itstudio.itmanager.entity.vo.request.PasswordResetVO +import club.itstudio.itmanager.entity.vo.response.ReloginVO +import club.itstudio.itmanager.service.AccountService +import club.itstudio.itmanager.utils.Const +import club.itstudio.itmanager.utils.DateUtils.getCurrentDateTime +import club.itstudio.itmanager.utils.FlowUtils +import club.itstudio.itmanager.utils.JwtUtils +import org.springframework.amqp.core.AmqpTemplate +import org.springframework.data.redis.core.ReactiveStringRedisTemplate +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.time.Duration + +@Service +class AuthorizationService( + private val accountService: AccountService, + private val utils: FlowUtils, + private val jwtUtils: JwtUtils, + private val amqpTemplate: AmqpTemplate, + private val stringRedisTemplate: ReactiveStringRedisTemplate, + private val encoder: PasswordEncoder +) { + fun askEmailVerifyCode(type: EmailType, email: String, ip: String): Mono { + return verifyLimit(ip).flatMap { allowed -> + if (allowed) { + val code = (100000..999999).random().toString() + val data = EmailVerificationMsg(email, type, code) + + Mono.fromRunnable { + amqpTemplate.convertAndSend("mail", data) + }.then( + stringRedisTemplate.opsForValue() + .set( + Const.VERIFY_EMAIL_DATA + email, + code, + Duration.ofMinutes(3) + ) + ).thenReturn("").subscribeOn(Schedulers.boundedElastic()) + } else { + Mono.just("Request limit exceeded. Please try again later.") + } + } + } + + fun registerEmailAccount(vo: EmailRegisterVO): Mono { + val (email, code, username, password) = vo + + return verifyCode(email, code) + .flatMap { result -> + if (result.isNotBlank()) { + return@flatMap Mono.just(result) + } + accountService.existAccountByEmail(email) + .flatMap { emailExists -> + if (emailExists) { + return@flatMap Mono.just("account with the same email already exists.") + } + accountService.existAccountByName(username) + .flatMap { usernameExists -> + if (usernameExists) { + return@flatMap Mono.just("username already exists.") + } + val account = Account( + id = null, + username = username, + password = encoder.encode(password)!!, + email = email, + isStaff = false, + createdAt = getCurrentDateTime() + ) + Mono.fromCallable { accountService.save(account) } + .flatMap { success -> + if (success) { + stringRedisTemplate.delete(Const.VERIFY_EMAIL_DATA + email) + .thenReturn("") + } else { + Const.INTERNAL_ERROR_MONO + } + } + } + } + } + } + + fun resetEmailAccountPassword(vo: PasswordResetVO): Mono { + val (email, code, password) = vo + + val encodedPassword = + encoder.encode(password) + ?: return Const.INTERNAL_ERROR_MONO + + return verifyCode(email, code) + .then( + accountService.resetPasswordByEmail( + email, + encodedPassword + ) + ) + .then( + stringRedisTemplate.delete( + Const.VERIFY_EMAIL_DATA + email + ) + ) + .thenReturn("Password reset successfully") + } + + fun jwtTokenRelogin(headerToken: String?): Mono? { + val user = jwtUtils.resolveJwt(headerToken?.substring("Bearer ".length)) + ?.let { jwtUtils.toUser(it) } + ?: return null + val accountMono = accountService.findAccountByName(user.username) + val voMono = accountMono.map { + ReloginVO( + jwtUtils.createJwt(user, it.id!!, it.username), + jwtUtils.expiresTime().toInstant().toString() + ) + } + return voMono + } + + fun invalidateJwt(headerToken: String?): Boolean { + return jwtUtils.invalidateJwt(headerToken) + } + + private fun verifyLimit(ip: String): Mono { + val key = Const.VERIFY_EMAIL_LIMIT + ip + return utils.limitOnceCheck(key, 60) + } + + private fun verifyCode(email: String, receivedCode: String?): Mono { + return stringRedisTemplate.opsForValue() + .get(Const.VERIFY_EMAIL_DATA + email) + .flatMap { code -> + if (receivedCode == null || code != receivedCode) + Mono.just("verify code is wrong.") + else Mono.just("") + } + .switchIfEmpty(Mono.just("verify code has not been sent")) + } +} \ No newline at end of file