1.集成Swagger 3接口文档
在前后端分离的项目中,接口文档的存在十分重要。swagger 是一个自动生成接口文档的工具,在需求变更十分频繁的情况下,手写接口文档是效率十分低下,这时swagger自动生生文档的的作用就体现出来了,同时swagger还提供了接口测试功能,相当好用。
1.1 添加依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.4</version>
</dependency>
1.2 添加配置
在config包下创建OpenApiConfig.java,用于对swagger页面进行基本的配置。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI qiwenFileOpenAPI() {
return new OpenAPI()
.info(new Info().title("前后端分离网盘项目API")
.description("用于练习前后端分离的项目- springboot+vue技术栈")
.version("v1.0.0")
.license(new License().name("MIT").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("个人网站地址")
.url("www.picacho.top"));
}
}
1.3 常用注解
@Tag 注解
这个注解主要用在类或方法上,当作用在方法是用来定义单个操作,当作用在类上代表所有操作。
- name 标签名
- description 这里可以做一个简短的描述
- externalDocs 添加一个扩展文档
- extensions 可选的扩展列表
@Operation 注解
这个注解用于将资源方法定义为OpenAPI 操作,在该注解中也可以定义该操作的其他属性。
- method HTTP 请求方法
- tags 按照资源对操作进行逻辑分组
- summary 提供此操作的简要说明。
- description 对操作的详细描述
- requestBody 与操作关联的请求报文
- parameters 一个可选的参数数组
- responses 执行此操作返回的可能响应的列表
- deprecated 允许将操作标记为已弃用
- security 可用于此操作的安全机制的声明。
@Schema 注解
这个注解用来定义模型,主要用来定义模型类及模型的属性,请求和响应的内容、报文头等。
- not 提供用于禁止匹配属性的 java 类。
- name 用于描述模型类或属性的名称
- title 用于描述模型类的标题
- maximum 设置属性的最大数值。
- minimum 设置属性的最小数值。
- maxLength 设置字符串值的最大长度。
- minLength 设置字符串值的最大小度。
- pattern 值必须满足的模式。
- required 是否必输
- description 描述
- nullable 如果为 true 则可能为 null。
- example 使用示例
1.4 添加接口信息
为前面实现的接口添加接口信息。
@Tag(name = "user", description = "该接口为用户接口,主要用于用户登录,注册和校验token")
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
UserService userService;
@Resource
JWTUtil jwtUtil;
/**
* 成功响应测试
*/
@GetMapping(value="/testSuccess")
@ResponseBody
public RestResult testSuccess(){
return RestResult.success();
}
/**
* 失败响应测试
*/
@GetMapping(value="/testFail")
@ResponseBody
public RestResult testFail(){
return RestResult.fail();
}
/**
* 空指针异常响应测试
*/
@GetMapping(value="/testNullException")
@ResponseBody
public RestResult testNullException(){
String s = null;
int index = s.length();
return RestResult.success();
}
@Operation(summary = "用户注册", description = "注册账号", tags = {"user"})
@PostMapping(value = "/register")
@ResponseBody
public RestResult<String> register(@RequestBody RegisterDTO registerDTO) {
RestResult<String> restResult = null;
User user = new User();
user.setUsername(registerDTO.getUsername());
user.setTelephone(registerDTO.getTelephone());
user.setPassword(registerDTO.getPassword());
restResult = userService.registerUser(user);
return restResult;
}
@Operation(summary = "用户登录", description = "用户登录认证后才能进入系统", tags = {"user"})
@GetMapping(value = "/login")
@ResponseBody
public RestResult<LoginVO> userLogin(String telephone, String password) {
RestResult<LoginVO> restResult = new RestResult<LoginVO>();
LoginVO loginVO = new LoginVO();
User user = new User();
user.setTelephone(telephone);
user.setPassword(password);
RestResult<User> loginResult = userService.login(user);
if (!loginResult.getSuccess()) {
return RestResult.fail().message("登录失败!");
}
loginVO.setUsername(loginResult.getData().getUsername());
String jwt = "";
try {
ObjectMapper objectMapper = new ObjectMapper();
jwt = jwtUtil.createJWT(objectMapper.writeValueAsString(loginResult.getData()));
} catch (Exception e) {
return RestResult.fail().message("登录失败!");
}
loginVO.setToken(jwt);
return RestResult.success().data(loginVO);
}
@Operation(summary = "检查用户登录信息", description = "验证token的有效性", tags = {"user"})
@GetMapping("/checkToken")
@ResponseBody
public RestResult<User> checkToken(@RequestHeader("token") String token) {
RestResult<User> restResult = new RestResult<User>();
User tokenUserInfo = null;
try {
Claims c = jwtUtil.parseJWT(token);
String subject = c.getSubject();
ObjectMapper objectMapper = new ObjectMapper();
tokenUserInfo = objectMapper.readValue(subject, User.class);
} catch (Exception e) {
return RestResult.fail().message("认证失败");
}
if (tokenUserInfo != null) {
return RestResult.success().data(tokenUserInfo);
} else {
return RestResult.fail().message("用户暂未登录");
}
}
}
@Schema(description="注册DTO")
@Data
public class RegisterDTO {
@Schema(description="用户名")
private String username;
@Schema(description="手机号")
private String telephone;
@Schema(description="密码")
private String password;
}
@Schema(description="登录VO")
@Data
public class LoginVO {
@Schema(description="用户名")
private String username;
@Schema(description="token")
private String token;
}
接着启动项目,就可以访问swagger接口文档界面了。
2.文件夹创建接口开发
2.1 创建文件DTO
创建文件DTO,在dto包下创建CreateFileDTO.java。
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "创建文件DTO",required = true)
public class CreateFileDTO {
@Schema(description="文件名")
private String fileName;
@Schema(description="文件路径")
private String filePath;
}
2.2 创建接口
首先通过token进行身份认证,如果没有登陆,那么直接认证失败返回失败信息;接着通过前端传入的参数信息查询文件,如果文件存在,返回文件重名失败;如果都通过了就将用户文件存入数据库。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.picacho.common.RestResult;
import com.picacho.dto.CreateFileDTO;
import com.picacho.entity.User;
import com.picacho.entity.UserFile;
import com.picacho.service.UserService;
import com.picacho.util.DateUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@Tag(name = "file", description = "该接口为文件接口,主要用来做一些文件的基本操作,如创建目录,删除,移动,复制等。")
@RestController
@Slf4j
@RequestMapping("/file")
public class FileController {
@Resource
UserService userService;
@Resource
UserfileService userfileService;
@Operation(summary = "创建文件", description = "目录(文件夹)的创建", tags = {"file"})
@PostMapping(value = "/createfile")
@ResponseBody
public RestResult<String> createFile(@RequestBody CreateFileDTO createFileDto, @RequestHeader("token") String token) {
User sessionUser = userService.getUserByToken(token);
if (sessionUser == null) {
RestResult.fail().message("token认证失败");
}
LambdaQueryWrapper<UserFile> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserFile::getFileName, "").eq(UserFile::getFilePath, "").eq(UserFile::getUserId, 0);
List<UserFile> userfiles = userfileService.list(lambdaQueryWrapper);
if (!userfiles.isEmpty()) {
RestResult.fail().message("同目录下文件名重复");
}
UserFile userFile = new UserFile();
userFile.setUserId(sessionUser.getUserId());
userFile.setFileName(createFileDto.getFileName());
userFile.setFilePath(createFileDto.getFilePath());
userFile.setIsDir(1);
userFile.setUploadTime(DateUtil.getCurrentTime());
userfileService.save(userFile);
return RestResult.success();
}
}
2.3 创建业务层
第一步通过token获取用户信息。
public interface UserService {
User getUserByToken(String token);
}
@Resource
JWTUtil jwtUtil;
@Override
public User getUserByToken(String token) {
User tokenUserInfo = null;
try {
Claims c = jwtUtil.parseJWT(token);
String subject = c.getSubject();
ObjectMapper objectMapper = new ObjectMapper();
tokenUserInfo = objectMapper.readValue(subject, User.class);
} catch (Exception e) {
log.error("解码异常");
return null;
}
return tokenUserInfo;
}
第二步,接着通过前端传入的参数信息查询文件。
import com.baomidou.mybatisplus.extension.service.IService;
import com.picacho.entity.UserFile;
public interface UserfileService extends IService<UserFile> {
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.picacho.entity.UserFile;
import com.picacho.mapper.UserfileMapper;
import com.picacho.service.UserfileService;
import org.springframework.stereotype.Service;
@Service
public class UserfileServiceImpl extends ServiceImpl<UserfileMapper, UserFile> implements UserfileService {
}
这里使用mybatis plus,因此可以使用其自带的列表方法list()和保存方法save()。
2.4 创建Mapper层
这里需要将mapper进行改造,才能使用mybatis plus自带的方法。
public interface UserfileMapper extends BaseMapper<UserFile> {
}
2.5 测试一下
启动项目:
第一步,进行登陆获取token。
接着文件夹创建,需要携带token。
可以看到创建成功了,接着去数据库查看一下。
3.文件列表查询接口
3.1 创建DTO实体类
创建DTO实体类 UserfileListDTO.java,用来作为文件列表查询接口接收前台请求信息的载体。
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "文件列表DTO",required = true)
public class UserfileListDTO {
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "当前页码")
private Long currentPage;
@Schema(description = "一页显示数量")
private Long pageCount;
}
3.2 创建VO实体类
创建VO实体类UserfileListVO.java,用来作为文件列表查询接口给前台返回的信息载体。
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description="用户文件列表VO")
public class UserfileListVO {
@Schema(description="文件id")
private Long fileId;
@Schema(description="时间戳名称")
private String timeStampName;
@Schema(description="文件url")
private String fileUrl;
@Schema(description="文件大小")
private Long fileSize;
@Schema(description="是否是oss存储")
private Integer isOSS;
@Schema(description="引用数量")
private Integer pointCount;
@Schema(description="md5")
private String identifier;
@Schema(description="用户文件id")
private Long userFileId;
@Schema(description="用户id")
private Long userId;
@Schema(description="文件名")
private String fileName;
@Schema(description="文件路径")
private String filePath;
@Schema(description="扩展名")
private String extendName;
@Schema(description="是否是目录")
private Integer isDir;
@Schema(description="上传时间")
private String uploadTime;
}
3.3 创建控制器
@Operation(summary = "获取文件列表", description = "用来做前台文件列表展示", tags = { "file" })
@GetMapping(value = "/getfilelist")
@ResponseBody
public RestResult<UserfileListVO> getUserfileList(UserfileListDTO userfileListDto,
@RequestHeader("token") String token) {
User sessionUser = userService.getUserByToken(token);
if (sessionUser == null) {
return RestResult.fail().message("token验证失败");
}
List<UserfileListVO> fileList = userfileService.getUserFileByFilePath(userfileListDto.getFilePath(),
sessionUser.getUserId(), userfileListDto.getCurrentPage(), userfileListDto.getPageCount());
LambdaQueryWrapper<UserFile> userFileLambdaQueryWrapper = new LambdaQueryWrapper<>();
userFileLambdaQueryWrapper.eq(UserFile::getUserId, sessionUser.getUserId()).eq(UserFile::getFilePath, userfileListDto.getFilePath());
int total = userfileService.count(userFileLambdaQueryWrapper);
Map<String, Object> map = new HashMap<>();
map.put("total", total);
map.put("list", fileList);
return RestResult.success().data(map);
}
3.4 创建业务层
List<UserfileListVO> getUserFileByFilePath(String filePath, Long userId, Long currentPage, Long pageCount);
@Resource
UserfileMapper userfileMapper;
@Override
public List<UserfileListVO> getUserFileByFilePath(String filePath, Long userId, Long currentPage, Long pageCount) {
Long beginCount = (currentPage - 1) * pageCount;
UserFile userfile = new UserFile();
userfile.setUserId(userId);
userfile.setFilePath(filePath);
List<UserfileListVO> fileList = userfileMapper.userfileList(userfile, beginCount, pageCount);
return fileList;
}
3.5 创建Mapper层
List<UserfileListVO> userfileList(UserFile userfile, Long beginCount, Long pageCount);
<?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.picacho.mapper.UserfileMapper">
<select id="userfileList" resultType="com.picacho.vo.UserfileListVO">
select * from userfile a
left join file on file.fileId = a.fileId
<where>
<if test="userfile.userId != null">
and a.userId = #{userfile.userId}
</if>
<if test="userfile.filePath != null">
and a.filePath = #{userfile.filePath}
</if>
<if test="userfile.extendName != null">
and a.extendName = #{userfile.extendName}
</if>
</where>
ORDER BY isDir desc
limit #{beginCount}, #{pageCount}
</select>
</mapper>
3.6 测试一下
启动项目:
4.文件分类查询接口
4.1 创建文件类型常量类
public class FileConstant {
public static final String[] IMG_FILE = {"bmp", "jpg", "png", "tif", "gif", "jpeg"};
public static final String[] DOC_FILE = {"doc", "docx", "ppt", "pptx", "xls", "xlsx", "txt", "hlp", "wps", "rtf", "html", "pdf"};
public static final String[] VIDEO_FILE = {"avi", "mp4", "mpg", "mov", "swf"};
public static final String[] MUSIC_FILE = {"wav", "aif", "au", "mp3", "ram", "wma", "mmf", "amr", "aac", "flac"};
public static final int IMAGE_TYPE = 1;
public static final int DOC_TYPE = 2;
public static final int VIDEO_TYPE = 3;
public static final int MUSIC_TYPE = 4;
public static final int OTHER_TYPE = 5;
}
4.2 创建控制层
@Operation(summary = "通过文件类型选择文件", description = "该接口可以实现文件格式分类查看", tags = {"file"})
@GetMapping(value = "/selectfilebyfiletype")
@ResponseBody
public RestResult<List<Map<String, Object>>> selectFileByFileType(int fileType, Long currentPage, Long pageCount, @RequestHeader("token") String token) {
User sessionUser = userService.getUserByToken(token);
long userId = sessionUser.getUserId();
Map<String, Object> map = userfileService.getUserFileByType(fileType, currentPage, pageCount, userId);
return RestResult.success().data(map);
}
4.3 创建业务层
Map<String, Object> getUserFileByType(int fileType, Long currentPage, Long pageCount, Long userId);
@Override
public Map<String, Object> getUserFileByType(int fileType, Long currentPage, Long pageCount, Long userId) {
Long beginCount = (currentPage - 1) * pageCount;
List<UserfileListVO> fileList;
Long total;
if (fileType == FileConstant.OTHER_TYPE) {
List<String> arrList = new ArrayList<>();
arrList.addAll(Arrays.asList(FileConstant.DOC_FILE));
arrList.addAll(Arrays.asList(FileConstant.IMG_FILE));
arrList.addAll(Arrays.asList(FileConstant.VIDEO_FILE));
arrList.addAll(Arrays.asList(FileConstant.MUSIC_FILE));
fileList = userfileMapper.selectFileNotInExtendNames(arrList,beginCount, pageCount, userId);
total = userfileMapper.selectCountNotInExtendNames(arrList,beginCount, pageCount, userId);
} else {
List<String> fileExtends = null;
if (fileType == FileConstant.IMAGE_TYPE) {
fileExtends = Arrays.asList(FileConstant.IMG_FILE);
} else if (fileType == FileConstant.DOC_TYPE) {
fileExtends = Arrays.asList(FileConstant.DOC_FILE);
} else if (fileType == FileConstant.VIDEO_TYPE) {
fileExtends = Arrays.asList(FileConstant.VIDEO_FILE);
} else if (fileType == FileConstant.MUSIC_TYPE) {
fileExtends = Arrays.asList(FileConstant.MUSIC_FILE);
}
fileList = userfileMapper.selectFileByExtendName(fileExtends, beginCount, pageCount,userId);
total = userfileMapper.selectCountByExtendName(fileExtends, beginCount, pageCount,userId);
}
Map<String, Object> map = new HashMap<>();
map.put("list",fileList);
map.put("total", total);
return map;
}
4.4 创建Mapper层
List<UserfileListVO> selectFileByExtendName(List<String> fileNameList, Long beginCount, Long pageCount, long userId);
Long selectCountByExtendName(List<String> fileNameList, Long beginCount, Long pageCount, long userId);
List<UserfileListVO> selectFileNotInExtendNames(List<String> fileNameList, Long beginCount, Long pageCount, long userId);
Long selectCountNotInExtendNames(List<String> fileNameList, Long beginCount, Long pageCount, long userId);
}
<sql id="selectByExtendName" >
left join file on file.fileId = userfile.fileId
where extendName in
<foreach collection="fileNameList" open="(" close=")" separator="," item="fileName" >
#{fileName}
</foreach>
and userId = #{userId}
</sql>
<sql id="selectByNotExtendName">
left join file on file.fileId = userfile.fileId
where extendName not in
<foreach collection="fileNameList" open="(" close=")" separator="," item="fileName" >
#{fileName}
</foreach>
and userId = #{userId}
</sql>
<select id="selectFileByExtendName" parameterType="com.picacho.entity.UserFile" resultType="com.picacho.vo.UserfileListVO">
select * from userfile
<include refid="selectByExtendName"></include>
limit #{beginCount}, #{pageCount}
</select>
<select id="selectCountByExtendName" parameterType="com.picacho.entity.UserFile" resultType="java.lang.Long">
select count(*) from userfile
<include refid="selectByExtendName"></include>
</select>
<select id="selectFileNotInExtendNames" parameterType="com.picacho.entity.UserFile" resultType="com.picacho.vo.UserfileListVO">
select * from userfile
<include refid="selectByNotExtendName"></include>
limit #{beginCount}, #{pageCount}
</select>
<select id="selectCountNotInExtendNames" parameterType="com.picacho.entity.UserFile" resultType="java.lang.Long">
select count(*) from userfile
<include refid="selectByNotExtendName"></include>
</select>
4.5 测试一下