Feat(ToolStore): Add tool favorite controller

Add controller to add and remove favorite tool
This commit is contained in:
2024-04-26 18:07:49 +08:00
parent 96afb185e7
commit 5c3484a22d
21 changed files with 389 additions and 21 deletions

View File

@@ -8,9 +8,7 @@ import top.fatweb.oxygen.api.annotation.Trim
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.entity.tool.ToolBase
import top.fatweb.oxygen.api.param.tool.ToolCreateParam
import top.fatweb.oxygen.api.param.tool.ToolUpdateParam
import top.fatweb.oxygen.api.param.tool.ToolUpgradeParam
import top.fatweb.oxygen.api.param.tool.*
import top.fatweb.oxygen.api.service.tool.IEditService
import top.fatweb.oxygen.api.vo.tool.ToolCategoryVo
import top.fatweb.oxygen.api.vo.tool.ToolTemplateVo
@@ -207,4 +205,40 @@ class EditController(
fun delete(@PathVariable id: Long): ResponseResult<Nothing> =
if (editService.delete(id)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
else ResponseResult.databaseFail(ResponseCode.DATABASE_DELETE_FAILED)
/**
* Add favorite tool
*
* @param toolFavoriteAddParam Add favorite tool parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolFavoriteAddParam
* @see ResponseResult
*/
@Operation(summary = "收藏工具")
@PostMapping("/favorite")
fun addFavorite(@RequestBody toolFavoriteAddParam: ToolFavoriteAddParam): ResponseResult<Nothing> {
editService.addFavorite(toolFavoriteAddParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_INSERT_SUCCESS)
}
/**
* Remove favorite tool
*
* @param toolFavoriteRemoveParam Remove favorite tool parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolFavoriteRemoveParam
* @see ResponseResult
*/
@Operation(summary = "收藏工具")
@DeleteMapping("/favorite")
fun addFavorite(@RequestBody toolFavoriteRemoveParam: ToolFavoriteRemoveParam): ResponseResult<Nothing> {
editService.removeFavorite(toolFavoriteRemoveParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
}

View File

@@ -41,7 +41,8 @@ object ToolConverter {
publish = tool.publish,
review = tool.review,
createTime = tool.createTime,
updateTime = tool.updateTime
updateTime = tool.updateTime,
favorite = tool.favorite == 1
)
/**

View File

@@ -61,6 +61,7 @@ enum class ResponseCode(val code: Int) {
DATABASE_EXECUTE_ERROR(BusinessCode.DATABASE, 50),
DATABASE_DUPLICATE_KEY(BusinessCode.DATABASE, 51),
DATABASE_NO_RECORD_FOUND(BusinessCode.DATABASE, 52),
DATABASE_RECORD_ALREADY_EXISTS(BusinessCode.DATABASE, 53),
TOOL_SUBMIT_SUCCESS(BusinessCode.TOOL, 10),
TOOL_CANCEL_SUCCESS(BusinessCode.TOOL, 11),

View File

@@ -247,7 +247,16 @@ class Tool : Serializable {
@TableField(exist = false)
var dist: ToolData? = null
/**
* Favorite
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField(exist = false)
var favorite: Int? = null
override fun toString(): String {
return "Tool(id=$id, name=$name, toolId=$toolId, icon=$icon, platform=$platform, description=$description, baseId=$baseId, authorId=$authorId, ver=$ver, keywords=$keywords, sourceId=$sourceId, distId=$distId, entryPoint=$entryPoint, publish=$publish, review=$review, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version, author=$author, base=$base, categories=$categories, source=$source, dist=$dist)"
return "Tool(id=$id, name=$name, toolId=$toolId, icon=$icon, platform=$platform, description=$description, baseId=$baseId, authorId=$authorId, ver=$ver, keywords=$keywords, sourceId=$sourceId, distId=$distId, entryPoint=$entryPoint, publish=$publish, review=$review, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version, author=$author, base=$base, categories=$categories, source=$source, dist=$dist, favorite=$favorite)"
}
}

View File

@@ -0,0 +1,81 @@
package top.fatweb.oxygen.api.entity.tool
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableLogic
import com.baomidou.mybatisplus.annotation.TableName
import com.baomidou.mybatisplus.annotation.Version
import java.io.Serializable
@TableName("t_b_tool_favorite")
class ToolFavorite : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* User ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("user_id")
var userId: Long? = null
/**
* Username
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("username")
var username: String? = null
/**
* Tool ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("tool_id")
var toolId: String? = null
/**
* Platform
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBase.Platform
*/
@TableField("platform")
var platform: ToolBase.Platform? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
override fun toString(): String {
return "ToolFavorite(id=$id, userId=$userId, username=$username, toolId=$toolId, platform=$platform, deleted=$deleted, version=$version)"
}
}

View File

@@ -0,0 +1,10 @@
package top.fatweb.oxygen.api.exception
/**
* Record already exists exception
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RuntimeException
*/
class RecordAlreadyExists : RuntimeException("Record already exists")

View File

@@ -177,7 +177,11 @@ class ExceptionHandler {
is TwoFactorVerificationCodeErrorException -> {
logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.PERMISSION_TWO_FACTOR_VERIFICATION_CODE_ERROR, e.localizedMessage, null)
ResponseResult.fail(
ResponseCode.PERMISSION_TWO_FACTOR_VERIFICATION_CODE_ERROR,
e.localizedMessage,
null
)
}
/* SQL */
@@ -226,6 +230,11 @@ class ExceptionHandler {
ResponseResult.fail(ResponseCode.DATABASE_EXECUTE_ERROR, e.localizedMessage, null)
}
is RecordAlreadyExists -> {
logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.DATABASE_RECORD_ALREADY_EXISTS, e.localizedMessage, null)
}
/* Tool */
is IllegalVersionException -> {
logger.debug(e.localizedMessage, e)

View File

@@ -60,5 +60,5 @@ interface StoreMapper : BaseMapper<Tool> {
* @since 1.0.0
* @see Tool
*/
fun selectListByAuthorToolIds(@Param("ids") ids: List<String>): List<Tool>
fun selectListByAuthorToolIds(@Param("ids") ids: List<String>, @Param("operator") operator: Long?): List<Tool>
}

View File

@@ -0,0 +1,8 @@
package top.fatweb.oxygen.api.mapper.tool
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import org.apache.ibatis.annotations.Mapper
import top.fatweb.oxygen.api.entity.tool.ToolFavorite
@Mapper
interface ToolFavoriteMapper : BaseMapper<ToolFavorite>

View File

@@ -0,0 +1,53 @@
package top.fatweb.oxygen.api.param.tool
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import top.fatweb.oxygen.api.annotation.Trim
import top.fatweb.oxygen.api.entity.tool.ToolBase
/**
* Add favorite tool parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
data class ToolFavoriteAddParam(
/**
* Username
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Trim
@Schema(description = "用户名", required = true)
@field: NotBlank(message = "Username cannot be blank")
var username: String?,
/**
* Tool ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Trim
@Schema(description = "工具唯一 ID", required = true)
@field: NotBlank(message = "ToolId cannot be blank")
@field: Pattern(
regexp = "^[a-zA-Z-_][0-9a-zA-Z-_]{2,19}\$",
message = "ToolId can only match '^[a-zA-Z-_][0-9a-zA-Z-_]{2,19}\$'"
)
var toolId: String?,
/**
* Platform
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBase.Platform
*/
@Schema(description = "平台")
@field:NotNull(message = "Platform can not be null")
val platform: ToolBase.Platform?
)

View File

@@ -0,0 +1,53 @@
package top.fatweb.oxygen.api.param.tool
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import top.fatweb.oxygen.api.annotation.Trim
import top.fatweb.oxygen.api.entity.tool.ToolBase
/**
* Remove favorite tool parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
data class ToolFavoriteRemoveParam(
/**
* Username
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Trim
@Schema(description = "用户名", required = true)
@field: NotBlank(message = "Username cannot be blank")
var username: String?,
/**
* Tool ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Trim
@Schema(description = "工具唯一 ID", required = true)
@field: NotBlank(message = "ToolId cannot be blank")
@field: Pattern(
regexp = "^[a-zA-Z-_][0-9a-zA-Z-_]{2,19}\$",
message = "ToolId can only match '^[a-zA-Z-_][0-9a-zA-Z-_]{2,19}\$'"
)
var toolId: String?,
/**
* Platform
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBase.Platform
*/
@Schema(description = "平台")
@field:NotNull(message = "Platform can not be null")
val platform: ToolBase.Platform?
)

View File

@@ -3,9 +3,7 @@ package top.fatweb.oxygen.api.service.tool
import com.baomidou.mybatisplus.extension.service.IService
import top.fatweb.oxygen.api.entity.tool.Tool
import top.fatweb.oxygen.api.entity.tool.ToolBase
import top.fatweb.oxygen.api.param.tool.ToolCreateParam
import top.fatweb.oxygen.api.param.tool.ToolUpdateParam
import top.fatweb.oxygen.api.param.tool.ToolUpgradeParam
import top.fatweb.oxygen.api.param.tool.*
import top.fatweb.oxygen.api.vo.tool.ToolCategoryVo
import top.fatweb.oxygen.api.vo.tool.ToolTemplateVo
import top.fatweb.oxygen.api.vo.tool.ToolVo
@@ -151,4 +149,24 @@ interface IEditService : IService<Tool> {
* @since 1.0.0
*/
fun delete(id: Long): Boolean
/***
* Add favorite
*
* @param toolFavoriteAddParam Add favorite tool parameters
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolFavoriteAddParam
*/
fun addFavorite(toolFavoriteAddParam: ToolFavoriteAddParam)
/***
* Remove favorite tool
*
* @param toolFavoriteRemoveParam Remove favorite tool parameters
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolFavoriteRemoveParam
*/
fun removeFavorite(toolFavoriteRemoveParam: ToolFavoriteRemoveParam)
}

View File

@@ -0,0 +1,6 @@
package top.fatweb.oxygen.api.service.tool
import com.baomidou.mybatisplus.extension.service.IService
import top.fatweb.oxygen.api.entity.tool.ToolFavorite
interface IToolFavoriteService : IService<ToolFavorite>

View File

@@ -12,9 +12,7 @@ import top.fatweb.oxygen.api.converter.tool.ToolTemplateConverter
import top.fatweb.oxygen.api.entity.tool.*
import top.fatweb.oxygen.api.exception.*
import top.fatweb.oxygen.api.mapper.tool.EditMapper
import top.fatweb.oxygen.api.param.tool.ToolCreateParam
import top.fatweb.oxygen.api.param.tool.ToolUpdateParam
import top.fatweb.oxygen.api.param.tool.ToolUpgradeParam
import top.fatweb.oxygen.api.param.tool.*
import top.fatweb.oxygen.api.service.tool.*
import top.fatweb.oxygen.api.util.WebUtil
import top.fatweb.oxygen.api.vo.tool.ToolCategoryVo
@@ -36,7 +34,8 @@ class EditServiceImpl(
private val toolTemplateService: IToolTemplateService,
private val toolCategoryService: IToolCategoryService,
private val toolDataService: IToolDataService,
private val rToolCategoryService: IRToolCategoryService
private val rToolCategoryService: IRToolCategoryService,
private val toolFavoriteService: IToolFavoriteService
) : ServiceImpl<EditMapper, Tool>(), IEditService {
override fun getTemplate(platform: ToolBase.Platform): List<ToolTemplateVo> =
toolTemplateService.list(
@@ -259,4 +258,43 @@ class EditServiceImpl(
rToolCategoryService.remove(KtQueryWrapper(RToolCategory()).eq(RToolCategory::toolId, tool.id))
return this.removeById(id)
}
@Transactional
override fun addFavorite(toolFavoriteAddParam: ToolFavoriteAddParam) {
if (toolFavoriteService.exists(
KtQueryWrapper(ToolFavorite())
.eq(ToolFavorite::userId, WebUtil.getLoginUserId())
.eq(ToolFavorite::username, toolFavoriteAddParam.username)
.eq(ToolFavorite::toolId, toolFavoriteAddParam.toolId)
.eq(ToolFavorite::platform, toolFavoriteAddParam.platform)
)
) {
throw RecordAlreadyExists()
}
this.detail(toolFavoriteAddParam.username!!, toolFavoriteAddParam.toolId!!, "latest", toolFavoriteAddParam.platform!!)
toolFavoriteService.save(
ToolFavorite().apply {
userId = WebUtil.getLoginUserId()
username = toolFavoriteAddParam.username
toolId = toolFavoriteAddParam.toolId
platform = toolFavoriteAddParam.platform
}
)
}
@Transactional
override fun removeFavorite(toolFavoriteRemoveParam: ToolFavoriteRemoveParam) {
if (!toolFavoriteService.remove(
KtQueryWrapper(ToolFavorite())
.eq(ToolFavorite::userId, WebUtil.getLoginUserId())
.eq(ToolFavorite::username, toolFavoriteRemoveParam.username)
.eq(ToolFavorite::toolId, toolFavoriteRemoveParam.toolId)
.eq(ToolFavorite::platform, toolFavoriteRemoveParam.platform)
)
) {
throw NoRecordFoundException()
}
}
}

View File

@@ -9,6 +9,7 @@ import top.fatweb.oxygen.api.mapper.tool.StoreMapper
import top.fatweb.oxygen.api.param.PageSortParam
import top.fatweb.oxygen.api.param.tool.ToolStoreGetParam
import top.fatweb.oxygen.api.service.tool.IStoreService
import top.fatweb.oxygen.api.util.WebUtil
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.tool.ToolVo
@@ -31,7 +32,7 @@ class StoreServiceImpl : ServiceImpl<StoreMapper, Tool>(), IStoreService {
val toolIdsIPage = baseMapper.selectAuthorToolIdPage(toolIdsPage, toolStoreGetParam?.searchValue)
val toolPage = Page<Tool>(toolIdsIPage.current, toolIdsIPage.size, toolIdsIPage.total)
if (toolIdsIPage.total > 0) {
toolPage.setRecords(baseMapper.selectListByAuthorToolIds(toolIdsIPage.records))
toolPage.setRecords(baseMapper.selectListByAuthorToolIds(toolIdsIPage.records, WebUtil.getLoginUserId()))
}
return ToolConverter.toolPageToToolPageVo(toolPage)
@@ -44,7 +45,7 @@ class StoreServiceImpl : ServiceImpl<StoreMapper, Tool>(), IStoreService {
val toolIdsIPage = baseMapper.selectAuthorToolIdPageByUsername(toolIdsPage, username)
val toolPage = Page<Tool>(toolIdsIPage.current, toolIdsIPage.size, toolIdsIPage.total)
if (toolIdsIPage.total > 0) {
toolPage.setRecords(baseMapper.selectListByAuthorToolIds(toolIdsIPage.records))
toolPage.setRecords(baseMapper.selectListByAuthorToolIds(toolIdsIPage.records, WebUtil.getLoginUserId()))
}
return ToolConverter.toolPageToToolPageVo(toolPage)

View File

@@ -0,0 +1,10 @@
package top.fatweb.oxygen.api.service.tool.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import org.springframework.stereotype.Service
import top.fatweb.oxygen.api.entity.tool.ToolFavorite
import top.fatweb.oxygen.api.mapper.tool.ToolFavoriteMapper
import top.fatweb.oxygen.api.service.tool.IToolFavoriteService
@Service
class ToolFavoriteServiceImpl : ServiceImpl<ToolFavoriteMapper, ToolFavorite>(), IToolFavoriteService

View File

@@ -184,5 +184,14 @@ data class ToolVo(
* @see LocalDateTime
*/
@Schema(description = "修改时间", example = "1900-01-01T00:00:00.000Z")
val updateTime: LocalDateTime?
val updateTime: LocalDateTime?,
/**
* Favorite
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "收藏")
val favorite: Boolean?
)

View File

@@ -0,0 +1,13 @@
drop table if exists t_b_tool_favorite;
create table if not exists t_b_tool_favorite
(
id bigint not null primary key,
user_id bigint not null comment '用户 ID',
username varchar(20) not null comment '用户名',
tool_id varchar(50) not null comment '工具 ID',
platform varchar(20) not null comment '平台',
deleted bigint not null default 0,
version int not null default 0,
constraint t_b_tool_favorite_unique_user_id_username_tool_id_platform unique (user_id, username, tool_id, platform, deleted)
) comment '工具-收藏表';

View File

@@ -237,6 +237,7 @@
<result property="createTime" column="tool_create_time"/>
<result property="deleted" column="tool_deleted"/>
<result property="version" column="tool_version"/>
<result property="favorite" column="tool_favorite"/>
<result property="keywords" column="tool_keywords"
typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
<collection property="categories"

View File

@@ -140,7 +140,8 @@
tbtc.create_time as tool_category_create_time,
tbtc.update_time as tool_category_update_time,
tbtc.deleted as tool_category_deleted,
tbtc.version as tool_category_version
tbtc.version as tool_category_version,
if(tbtf.id is null, 0, 1) as tool_favorite
from (select *, row_number() over (partition by t_b_tool_main.tool_id, t_b_tool_main.author_id, t_b_tool_main.platform order by t_b_tool_main.publish desc) as rn
from t_b_tool_main
where deleted = 0
@@ -154,6 +155,8 @@
on tbtm.id = trtmc.tool_id
left join (select * from t_b_tool_category where deleted = 0 and enable = 1) as tbtc
on tbtc.id = trtmc.category_id
left join (select * from t_b_tool_favorite where deleted = 0) as tbtf
on tbtf.user_id = ${operator} and tbtf.username = tsu.username and tbtf.tool_id = tbtm.tool_id and tbtf.platform = tbtm.platform
<where>
and tbtm.rn = 1
<foreach collection="ids" item="item" index="index" open="and concat(tbtm.author_id, ':', tbtm.tool_id) in (" separator="," close=")"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.fatweb.oxygen.api.mapper.tool.ToolFavoriteMapper">
<resultMap id="toolFavoriteMap" type="toolFavorite">
<id property="id" column="tool_favorite_id"/>
<result property="userId" column="tool_favorite_user_id"/>
<result property="username" column="tool_favorite_username"/>
<result property="toolId" column="tool_favorite_tool_id"/>
</resultMap>
</mapper>