feat(character): 新增用户人设列表接口并支持邮箱登录

- CharacterController 增加 /listByUser 端点,返回当前用户已购人设
- KeyboardCharacterService 新增 selectListByUserId(),通过 Sa-Token 取当前用户 ID
- 引入 KeyboardUserCharacter 中间表及对应 Mapper、VO
- UserController 增加 /login 端点,支持邮箱+密码登录
- 统一将实体与 VO 的 title 字段更名为 characterName
- 补充错误码 USER_NOT_FOUND,调整 Sa-Token 白名单与 Redis 依赖
This commit is contained in:
2025-12-03 16:29:06 +08:00
parent 822fe3c76d
commit c4dbc9e475
17 changed files with 214 additions and 7 deletions

12
pom.xml
View File

@@ -207,6 +207,18 @@
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.44.0</version>
</dependency>
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.44.0</version>
</dependency>
<!-- 提供 Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 添加Hibernate Validator作为Bean Validation提供程序 -->
<dependency>

View File

@@ -27,7 +27,8 @@ public enum ErrorCode {
TOKEN_KICK_OUT(40107, "令牌已被踢下线"),
TOKEN_FREEZE(40108, "令牌已被冻结"),
TOKEN_NO_PREFIX(40109, "未按照指定前缀提交令牌"),
FILE_NAME_ERROR(40002, "文件名错误");
FILE_NAME_ERROR(40002, "文件名错误"),
USER_NOT_FOUND(40401, "用户不存在");
/**
* 状态码
*/

View File

@@ -68,7 +68,11 @@ public class SaTokenConfigure implements WebMvcConfigurer {
"/file/upload",
"/user/logout",
"/tag/list",
"/character/list"
"/character/list",
"/character/detail",
"/character/listByTag",
"/user/login",
"/character/listByUser"
};
}
@Bean

View File

@@ -1,11 +1,14 @@
package com.yolo.keyborad.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.yolo.keyborad.common.BaseResponse;
import com.yolo.keyborad.common.ResultUtils;
import com.yolo.keyborad.model.entity.KeyboardCharacter;
import com.yolo.keyborad.model.entity.KeyboardTag;
import com.yolo.keyborad.model.entity.KeyboardUserCharacter;
import com.yolo.keyborad.model.vo.character.KeyboardCharacterRespVO;
import com.yolo.keyborad.model.vo.character.KeyboardUserCharacterVO;
import com.yolo.keyborad.model.vo.tags.TagsRespVO;
import com.yolo.keyborad.service.KeyboardCharacterService;
import io.swagger.v3.oas.annotations.Operation;
@@ -53,4 +56,10 @@ public class CharacterController {
return ResultUtils.success(BeanUtil.copyToList(list, KeyboardCharacterRespVO.class));
}
@GetMapping("/listByUser")
@Operation(summary = "用户人设列表", description = "用户人设列表接口")
public BaseResponse<List<KeyboardUserCharacterVO>> userList() {
List<KeyboardCharacter> keyboardCharacters = characterService.selectListByUserId();
return ResultUtils.success(BeanUtil.copyToList(keyboardCharacters, KeyboardUserCharacterVO.class));
}
}

View File

@@ -4,14 +4,17 @@ import cn.dev33.satoken.stp.StpUtil;
import com.yolo.keyborad.common.BaseResponse;
import com.yolo.keyborad.common.ResultUtils;
import com.yolo.keyborad.model.dto.AppleLoginReq;
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
import com.yolo.keyborad.service.IAppleService;
import com.yolo.keyborad.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
@@ -29,6 +32,9 @@ public class UserController {
@Resource
private IAppleService appleService;
@Resource
private UserService userService;
/**
* 苹果登录
*
@@ -48,4 +54,9 @@ public class UserController {
return ResultUtils.success(true);
}
@PostMapping("/login")
@Operation(summary = "登录", description = "登录接口")
public BaseResponse<KeyboardUserRespVO> login(@RequestBody UserLoginDTO userLoginDTO) {
return ResultUtils.success(userService.login(userLoginDTO));
}
}

View File

@@ -0,0 +1,12 @@
package com.yolo.keyborad.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yolo.keyborad.model.entity.KeyboardUserCharacter;
/*
* @author: ziin
* @date: 2025/12/3 15:43
*/
public interface KeyboardUserCharacterMapper extends BaseMapper<KeyboardUserCharacter> {
}

View File

@@ -0,0 +1,13 @@
package com.yolo.keyborad.model.dto.user;
import lombok.Data;
/*
* @author: ziin
* @date: 2025/12/3 16:06
*/
@Data
public class UserLoginDTO {
private String mail;
private String password;
}

View File

@@ -21,9 +21,9 @@ public class KeyboardCharacter {
@Schema(description="主键 Id")
private Long id;
@TableField(value = "title")
@TableField(value = "character_name")
@Schema(description="标题")
private String title;
private String characterName;
@TableField(value = "\"character_background\"")
@Schema(description="背景描述")

View File

@@ -0,0 +1,43 @@
package com.yolo.keyborad.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Date;
import lombok.Data;
/*
* @author: ziin
* @date: 2025/12/3 15:43
*/
@Schema
@Data
@TableName(value = "keyboard_user_character")
public class KeyboardUserCharacter {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description="主键 Id")
private Long id;
@TableField(value = "character_id")
@Schema(description="键盘人设 id")
private Long characterId;
@TableField(value = "deleted")
@Schema(description="是否删除")
private Boolean deleted;
@TableField(value = "created_at")
@Schema(description="创建时间")
private Date createdAt;
@TableField(value = "updated_at")
@Schema(description="更新时间")
private Date updatedAt;
@TableField(value = "user_id")
@Schema(description="用户 id")
private Long userId;
}

View File

@@ -21,7 +21,7 @@ public class KeyboardCharacterRespVO {
private Long id;
@Schema(description="标题")
private String title;
private String characterName;
@Schema(description="背景描述")

View File

@@ -0,0 +1,28 @@
package com.yolo.keyborad.model.vo.character;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/*
* @author: ziin
* @date: 2025/12/3 15:43
*/
@Schema
@Data
@TableName(value = "keyboard_user_character")
public class KeyboardUserCharacterVO {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description="主键 Id")
private Long id;
@Schema(description = "人设名")
private String characterName;
}

View File

@@ -3,6 +3,7 @@ package com.yolo.keyborad.service;
import com.yolo.keyborad.model.entity.KeyboardCharacter;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yolo.keyborad.model.entity.KeyboardTag;
import com.yolo.keyborad.model.entity.KeyboardUserCharacter;
import java.util.List;
/*
@@ -16,4 +17,7 @@ public interface KeyboardCharacterService extends IService<KeyboardCharacter>{
List<KeyboardCharacter> selectListWithRank();
List<KeyboardCharacter> selectListByTag(Long tagId);
List<KeyboardCharacter> selectListByUserId();
}

View File

@@ -1,7 +1,9 @@
package com.yolo.keyborad.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
/*
* @author: ziin
@@ -12,4 +14,6 @@ public interface UserService extends IService<KeyboardUser> {
KeyboardUser selectUserWithSubjectId(String sub);
KeyboardUser createUserWithSubjectId(String sub);
KeyboardUserRespVO login(UserLoginDTO userLoginDTO);
}

View File

@@ -1,8 +1,12 @@
package com.yolo.keyborad.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.yolo.keyborad.mapper.KeyboardCharacterMapper;
import com.yolo.keyborad.mapper.KeyboardUserCharacterMapper;
import com.yolo.keyborad.model.entity.KeyboardUserCharacter;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -21,6 +25,9 @@ public class KeyboardCharacterServiceImpl extends ServiceImpl<KeyboardCharacterM
@Resource
private KeyboardCharacterMapper keyboardCharacterMapper;
@Resource
private KeyboardUserCharacterMapper keyboardUserCharacterMapper;
@Override
public List<KeyboardCharacter> selectListWithRank() {
return keyboardCharacterMapper.selectList(new LambdaQueryWrapper<KeyboardCharacter>()
@@ -35,4 +42,25 @@ public class KeyboardCharacterServiceImpl extends ServiceImpl<KeyboardCharacterM
.eq(KeyboardCharacter::getTag, tagId)
.orderByDesc(KeyboardCharacter::getRank));
}
@Override
public List<KeyboardCharacter> selectListByUserId() {
long loginId = StpUtil.getLoginIdAsLong();
List<KeyboardUserCharacter> keyboardUserCharacters = keyboardUserCharacterMapper.selectList(new LambdaQueryWrapper<KeyboardUserCharacter>()
.eq(KeyboardUserCharacter::getDeleted, false)
.eq(KeyboardUserCharacter::getUserId, loginId));
List<Long> characterIds = keyboardUserCharacters.stream()
.map(KeyboardUserCharacter::getCharacterId)
.toList();
if (CollectionUtils.isEmpty(characterIds)) {
return null;
}
return keyboardCharacterMapper.selectList(new LambdaQueryWrapper<KeyboardCharacter>()
.eq(KeyboardCharacter::getDeleted, false)
.in(KeyboardCharacter::getId, characterIds));
}
}

View File

@@ -1,11 +1,17 @@
package com.yolo.keyborad.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yolo.keyborad.common.ErrorCode;
import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.mapper.KeyboardUserMapper;
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
import com.yolo.keyborad.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -40,4 +46,20 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
keyboardUserMapper.insert(keyboardUser);
return keyboardUser;
}
@Override
public KeyboardUserRespVO login(UserLoginDTO userLoginDTO) {
KeyboardUser keyboardUser = keyboardUserMapper.selectOne(
new LambdaQueryWrapper<KeyboardUser>()
.eq(KeyboardUser::getEmail, userLoginDTO.getMail())
.eq(KeyboardUser::getPassword, userLoginDTO.getPassword())
.eq(KeyboardUser::getStatus, false));
if (keyboardUser == null) {
throw new BusinessException(ErrorCode.USER_NOT_FOUND);
}
StpUtil.login(keyboardUser.getId());
KeyboardUserRespVO keyboardUserRespVO = BeanUtil.copyProperties(keyboardUser, KeyboardUserRespVO.class);
keyboardUserRespVO.setToken(StpUtil.getTokenValue());
return keyboardUserRespVO;
}
}

View File

@@ -4,7 +4,7 @@
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardCharacter">
<!--@Table keyboard_character-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="title" jdbcType="VARCHAR" property="title" />
<result column="character_name" jdbcType="VARCHAR" property="characterName" />
<result column="character_background" jdbcType="VARCHAR" property="characterBackground" />
<result column="avatar_url" jdbcType="VARCHAR" property="avatarUrl" />
<result column="download" jdbcType="VARCHAR" property="download" />
@@ -16,7 +16,7 @@
<result column="rank" jdbcType="INTEGER" property="rank" />
</resultMap>
<sql id="Base_Column_List">
id, title, "character_background", avatar_url, download, tag, deleted, created_at,
id, character_name, "character_background", avatar_url, download, tag, deleted, created_at,
updated_at, prompt, "rank"
</sql>
</mapper>

View File

@@ -0,0 +1,16 @@
<?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="com.yolo.keyborad.mapper.KeyboardUserCharacterMapper">
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardUserCharacter">
<!--@Table keyboard_user_character-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="character_id" jdbcType="BIGINT" property="characterId" />
<result column="deleted" jdbcType="BOOLEAN" property="deleted" />
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
<result column="user_id" jdbcType="BIGINT" property="userId" />
</resultMap>
<sql id="Base_Column_List">
id, character_id, deleted, created_at, updated_at, user_id
</sql>
</mapper>