文章目录
-
上传创建图片
-
图片信息编辑(标签/分类等)
-
管理图片
-
查看 / 搜索 图片列表
-
查看图片详情
-
图片下载
-
用户上传创建图片
-
审核图片
-
导入图片(URL导入,批量抓取和创建)
-
图片查询优化
-
图片上传优化
-
图片加载优化
-
图片存储
图片模块
一、需求分析
优先确保用户能够查看图片功能的实现,上传功能暂时仅限管理员使用,保证系统的安全和稳定
管理员
-
图片上传与创建,支持本地图片上传,填写相关信息,名称、简介、标签、分类等。系统自动解析图片的基础信息,便于检索。
-
图片管理,管理员对图片信息进行编辑,例如修改名称、简洁、标签、分类等。
-
图片修改
用户
- 查看与搜索:用户可以按照关键词搜索图片,并支持按分类、标签筛选条件分页查看图片列表。
- 查看图片详情:用户点击列表中的图片后,可进入详情页,查看图片的大图及相关信息,如名称、简介、分类、标签、其他图片信息(如宽高和格式等)。
- 图片下载:用户在详情页可以点击 下载图片按钮,将图片保存本地。
二、方案设计
文件上传下载的实现
最简单的方式就是上传到后端服务器,用Java自带文件读写API就能实现。但是这种方式存在缺点:
- 不利于扩展:单个服务器的存储是有限的
- 不利于迁移
- 不安全
- 不利于管理
除了存储一些需要清理的临时文件,通常不会将用户的文件直接上传到服务器,更推荐用专业的第三方存储服务,最常用的就是对象存储。
对象存储
对象存储是一种存储海量文件的分布式存储服务,具有高扩展、低成本、可靠安全等优点。
例如Minio,AmazonS3,OSS,COS
创建图片的流程
上传图片文件+补充图片信息并保存到数据库中
- 先上传再提交数据:用户上传图片,系统生成图片存储的url;然后在用户填写其他相关信息并提交后,才保存图片记录到数据库中。
- 上传图片时保存记录:用户上传图片后,系统立即生成图片的完整数据记录,包括url和其他元数据,无需等待用户点击提交,图片信息立刻存储数据库,之后用户填写其他信息,相当于编辑了已有图片记录的信息。
第一种方法,如果用户取消提交会有存储残留。
第二种方法比较稳健,可以对图片溯源,并对用户上传做安全保障
解析图片信息
主流获取图片信息的方法主要由2种:
1.在后端服务器直接处理图片,比如Java库ImageIO、Python库pillow,还有更成熟的专业图像处理库OpenCV等。
2.通过第三方服务提取图片元数据。这里我们用腾讯的数据万象
三、后端开发
COS存储桶准备
配置
@Configuration
@ConfigurationProperties(prefix = "cos.client")
@Data
public class CosClientConfig {
/**
* 域名
*/
private String host;
/**
* secretId
*/
private String secretId;
/**
* 密钥(注意不要泄露)
*/
private String secretKey;
/**
* 区域
*/
private String region;
/**
* 桶名
*/
private String bucket;
@Bean
public COSClient cosClient() {
// 1 初始化用户身份信息(secretId, secretKey)。
// SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置 bucket 的地域, COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224
// clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
ClientConfig clientConfig = new ClientConfig(new Region(region));
// 这里建议设置使用 https 协议
// 从 5.6.54 版本开始,默认使用了 https
clientConfig.setHttpProtocol(HttpProtocol.https);
// 3 生成 cos 客户端。
return new COSClient(cred, clientConfig);
}
}
# 对象存储配置(需要从腾讯云获取)
cos:
client:
host: xxx
secretId: xxx
secretKey: xxx
region: xxx
bucket: xxx
通用类 manager - CosManager
@Component
public class CosManager {
@Resource
private CosClientConfig cosClientConfig;
@Resource
private COSClient cosClient;
// ... 一些操作 COS 的方法
}
文件上传
public PutObjectResult putObject(String key,File file){
PutObjectResult putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(),key,file);
return cosClient.putObject(putObjectRequest);
}
/**
* 上传对象(附带图片信息)
*
* @param key 唯一键
* @param file 文件
*/
public PutObjectResult putPictureObject(String key, File file) {
PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
file);
// 对图片进行处理(获取基本信息也被视作为一种处理)
PicOperations picOperations = new PicOperations();
// 1 表示返回原图信息
picOperations.setIsPicInfo(1);
// 构造处理参数
putObjectRequest.setPicOperations(picOperations);
return cosClient.putObject(putObjectRequest);
}
文件上传测试
@PostMapping("/test/upload")
public BaseResponse<String> testUploadFile(@RequestPart("file")MutipartFile multipartFile){
//dir
String filename = multipart.getOriginalFilename();
String filepath = String.format("/test/%s",filename);
File file = null;
try{
file = File.createTempFile(filepath,null);
mutipartFile.transferTo(file);
cosManager.putObject(filepath,file);
return ResultUtils.success(filepath);
}catch(Exception e){
throw
}finally{
if(file!=null){
file.delete()
}
}
}
文件下载
COS介绍了2种文件下载方式。一种是直接下载COS文件到后端服务器(适合服务端处理文件),另一种是获取文件下载输入流(适合返回给前端用户)。
还可以通过URL路径访问,适用于单一的、可公开的资源,如用户头像,公开图片。
对于安全性要求高的场景,可以先通过后端进行权限校验,然后从COS下载文件到服务器,再返回给前端,这样可以在后端限制只有登录用户才能下载。
也可以后端先进行权限校验,然后返回给前端一个临时密钥,之后前端可以凭借该密钥直接从对象存储下载,不用经过服务端中转,性能更高。
当前的图片都是公开的,直接通过URL进行访问即可。(这里的负载就交给COS了)
服务端文件下载代码demo
/**
* 下载对象
*
* @param key 唯一键
*/
public COSObject getObject(String key) {
GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
return cosClient.getObject(getObjectRequest);
}
@GetMapping("/test/download")
public void testDownloadFile(String filepath,HttpServletResponse response)
{
COSObjectInputStream cosObjectInput = null;
try{
cosManager.getObject(filepath).var;
cosObjectInput = cosObject.getObjectContent();
IOUtils.toByteArray(cosObjectInput).var;
response.setContentType();
response.setHeader();
response.getOutPutStream().write(bytes);
response.getOutPutStream().flush();
}catch{
}finally{
if(cosObjectInput!=null){
cosObjectInput.close();
}
}
}
picture table 逆向工程
优化picture 实体类
@TableName(value ="picture")
@Data
public class Picture implements Serializable {
/**
* id
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 图片 url
*/
private String url;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签(JSON 数组)
*/
private String tags;
/**
* 图片体积
*/
private Long picSize;
/**
* 图片宽度
*/
private Integer picWidth;
/**
* 图片高度
*/
private Integer picHeight;
/**
* 图片宽高比例
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 创建用户 id
*/
private Long userId;
/**
* 创建时间
*/
private Date createTime;
/**
* 编辑时间
*/
private Date editTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否删除
*/
@TableLogic
private Integer isDelete;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
图片上传
1.数据模型
在dto下新建用于请求参数的类。由于图片需要支持重复上传(信息不变,只改变图片文件),所以要添加图片id参数:
@Data
public class PictureUploadRequest implements Serializable {
/**
* 图片 id(用于修改)
*/
private Long id;
private static final long serialVersionUID = 1L;
}
在vo下新建视图类,可以额外关联用户信息。还可以编写实体类和视图类快速转换方法,便于后续快速传值。
@Data
public class PictureVO implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片 url
*/
private String url;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 标签
*/
private List<String> tags;
/**
* 分类
*/
private String category;
/**
* 文件体积
*/
private Long picSize;
/**
* 图片宽度
*/
private Integer picWidth;
/**
* 图片高度
*/
private Integer picHeight;
/**
* 图片比例
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 用户 id
*/
private Long userId;
/**
* 创建时间
*/
private Date createTime;
/**
* 编辑时间
*/
private Date editTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建用户信息
*/
private UserVO user;
private static final long serialVersionUID = 1L;
/**
* 封装类转对象
*/
public static Picture voToObj(PictureVO pictureVO) {
if (pictureVO == null) {
return null;
}
Picture picture = new Picture();
BeanUtils.copyProperties(pictureVO, picture);
// 类型不同,需要转换
picture.setTags(JSONUtil.toJsonStr(pictureVO.getTags()));
return picture;
}
/**
* 对象转封装类
*/
public static PictureVO objToVo(Picture picture) {
if (picture == null) {
return null;
}
PictureVO pictureVO = new PictureVO();
BeanUtils.copyProperties(picture, pictureVO);
// 类型不同,需要转换
pictureVO.setTags(JSONUtil.toList(picture.getTags(), String.class));
return pictureVO;
}
}
2.通用文件上传服务
虽然以及编写过通用对象存储操作类,但是还不够
问题:
- 图片校验
- 上传路径指定
- 解析图片:数据万象服务
FileManager / FileService
1.由于文件校验规则较复杂,单独抽象为validPicture方法,对文件大小、类型校验。
2.文件上传时,会先在本地创建临时文件,无论是否创建成功,最后都要删除临时文件,否则会导致资源泄露。
3.可以跟自己的需求定义文件上传地址,此处添加了上传日期和UUID随机数,便于了解上传时间并防止文件重复(一般处理)。预留了一个uploadPathPrefix参数,由调用方指定上传文件到哪个目录。
4.不建议存储桶共享
public class FileService{
@Resource
private CosClientConfig cosClientConfig;
@Resource
private CosManager cosManager;
/**
* 上传图片
*
* @param multipartFile 文件
* @param uploadPathPrefix 上传路径前缀
* @return
*/
public UploadPictureResult uploadPicture(MultipartFile multipartFile, String uploadPathPrefix) {
// 校验图片
validPicture(multipartFile);
// 图片上传地址
String uuid = RandomUtil.randomString(16);
String originFilename = multipartFile.getOriginalFilename();
String uploadFilename = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid,
FileUtil.getSuffix(originFilename));
String uploadPath = String.format("/%s/%s", uploadPathPrefix, uploadFilename);
File file = null;
try {
// 创建临时文件
file = File.createTempFile(uploadPath, null);
multipartFile.transferTo(file);
// 上传图片
PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);
ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();
// 封装返回结果
UploadPictureResult uploadPictureResult = new UploadPictureResult();
int picWidth = imageInfo.getWidth();
int picHeight = imageInfo.getHeight();
double picScale = NumberUtil.round(picWidth * 1.0 / picHeight, 2).doubleValue();
uploadPictureResult.setPicName(FileUtil.mainName(originFilename));
uploadPictureResult.setPicWidth(picWidth);
uploadPictureResult.setPicHeight(picHeight);
uploadPictureResult.setPicScale(picScale);
uploadPictureResult.setPicFormat(imageInfo.getFormat());
uploadPictureResult.setPicSize(FileUtil.size(file));
uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + uploadPath);
return uploadPictureResult;
} catch (Exception e) {
log.error("图片上传到对象存储失败", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
} finally {
this.deleteTempFile(file);
}
}
/**
* 校验文件
*
* @param multipartFile multipart 文件
*/
public void validPicture(MultipartFile multipartFile) {
ThrowUtils.throwIf(multipartFile == null, ErrorCode.PARAMS_ERROR, "文件不能为空");
// 1. 校验文件大小
long fileSize = multipartFile.getSize();
final long ONE_M = 1024 * 1024L;
ThrowUtils.throwIf(fileSize > 2 * ONE_M, ErrorCode.PARAMS_ERROR, "文件大小不能超过 2M");
// 2. 校验文件后缀
String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
// 允许上传的文件后缀
final List<String> ALLOW_FORMAT_LIST = Arrays.asList("jpeg", "jpg", "png", "webp");
ThrowUtils.throwIf(!ALLOW_FORMAT_LIST.contains(fileSuffix), ErrorCode.PARAMS_ERROR, "文件类型错误");
}
/**
* 删除临时文件
*/
public void deleteTempFile(File file) {
if (file == null) {
return;
}
// 删除临时文件
boolean deleteResult = file.delete();
if (!deleteResult) {
log.error("file delete error, filepath = {}", file.getAbsolutePath());
}
}
}
图片解析包装类:
@Data
public class UploadPictureResult {
/**
* 图片地址
*/
private String url;
/**
* 图片名称
*/
private String picName;
/**
* 文件体积
*/
private Long picSize;
/**
* 图片宽度
*/
private int picWidth;
/**
* 图片高度
*/
private int picHeight;
/**
* 图片宽高比
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
}
3.图片上传服务
接口
/**
* 上传图片
*
* @param multipartFile
* @param pictureUploadRequest
* @param loginUser
* @return
*/
PictureVO uploadPicture(MultipartFile multipartFile,
PictureUploadRequest pictureUploadRequest,
User loginUser);
实现类
1.我们将所有图片都放到了public 目录下,并且为每个用户的图片存储到public/uid 的目录下
2.如果pid不为空,表示更新已有图片信息,需要判断图片是否存在,并且更新的时候要指定editTime编辑时间。可以调用Mybatis Plus 提供的saveOrUpdate方法兼容创建和更新操作
@Override
public PictureVO uploadPicture(MultipartFile multipartFile, PictureUploadRequest pictureUploadRequest, User loginUser) {
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
Long pictureId = null;
if(pictureUploadRequest!=null){
pictureId=pictureUploadRequest.getId();
}
if(pictureId!=null){
pictureMapper.lambdaQuery().eq(Picture::getId,pictureId).exists();
Throwif()
}
String.format("public/%s",loginUser.getId()).var; // mkdir by uid
fileManager.uploadPicutre(mutipartFile,uploadPrefix).var;
new Picture().var;
set{url,name,size,width,height,scale,format,uid}
if(picutreId!=null){
set{pictureid,edittime}
}
mapper.saveOrUpdate()
ThrowIf()
return PictureVO.objToVo(picture)
}
4.Controller
/**
* 上传图片(可重新上传)
*/
@PostMapping("/upload")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<PictureVO> uploadPicture(
@RequestPart("file") MultipartFile multipartFile,
PictureUploadRequest pictureUploadRequest,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
PictureVO pictureVO = pictureService.uploadPicture(multipartFile, pictureUploadRequest, loginUser);
return ResultUtils.success(pictureVO);
}
5.测试
当上传图片过大时侯,会触发报错,SpringBoot内嵌tomcat默认限制了请求中文件上传的大小。
spring:
# 开放更大的文件上传体积
servlet:
multipart:
max-file-size: 10MB
扩展
1.用枚举类 支持根据业务场景区分文件上传路径、校验规则等,从而复用FileManager。
2.目前文件上传的时候,会先在本地创建临时文件。如果不需要对文件进行额外处理、进一步提高性能,可以直接用流的方式将请求中的文件上传到COS。
3.补充更严格的校验,例如为支持的图片格式定义枚举,仅允许上传枚举定义的格式。
代码demo:
// 上传文件
public static String uploadToCOS(MultipartFile multipartFile, String bucketName, String key) throws Exception {
// 创建 COS 客户端
COSClient cosClient = createCOSClient();
try (InputStream inputStream = multipartFile.getInputStream()) {
// 元信息配置
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(multipartFile.getSize());
metadata.setContentType(multipartFile.getContentType());
// 创建上传请求
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream, metadata);
// 上传文件
cosClient.putObject(putObjectRequest);
// 生成访问链接
return "https://" + bucketName + ".cos." + cosClient.getClientConfig().getRegion().getRegionName()
+ ".myqcloud.com/" + key;
} finally {
cosClient.shutdown();
}
}
图片管理
- 管理员 deleteById
- 管理员 update
- 管理员 page
- 管理员 getById
- page desen
- getById desen
- update picture
1.数据模型
每一个请求都需要一个dto
图片更新请求,给管理员使用,tags类型改为List< String>便于前端上传
@Data
public class PictureUpdateRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
private static final long serialVersionUID = 1L;
}
图片修改
@Data
public class PictureEditRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
private static final long serialVersionUID = 1L;
}
图片查询
@EqualsAndHashCode(callSuper = true)
@Data
public class PictureQueryRequest extends PageRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
/**
* 文件体积
*/
private Long picSize;
/**
* 图片宽度
*/
private Integer picWidth;
/**
* 图片高度
*/
private Integer picHeight;
/**
* 图片比例
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 搜索词(同时搜名称、简介等)
*/
private String searchText;
/**
* 用户 id
*/
private Long userId;
private static final long serialVersionUID = 1L;
}
2.服务开发
1.UserService编写判断用户是否为管理员的方法
/**
* 是否为管理员
*
* @param user
* @return
*/
boolean isAdmin(User user);
@Override
public boolean isAdmin(User user) {
return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
}
查询包装类转换
@Override
public QueryWrapper<Picture> getQueryWrapper(PictureQueryRequest pictureQueryRequest) {
QueryWrapper<Picture> queryWrapper = new QueryWrapper<>();
if (pictureQueryRequest == null) {
return queryWrapper;
}
// 从对象中取值
Long id = pictureQueryRequest.getId();
String name = pictureQueryRequest.getName();
String introduction = pictureQueryRequest.getIntroduction();
String category = pictureQueryRequest.getCategory();
List<String> tags = pictureQueryRequest.getTags();
Long picSize = pictureQueryRequest.getPicSize();
Integer picWidth = pictureQueryRequest.getPicWidth();
Integer picHeight = pictureQueryRequest.getPicHeight();
Double picScale = pictureQueryRequest.getPicScale();
String picFormat = pictureQueryRequest.getPicFormat();
String searchText = pictureQueryRequest.getSearchText();
Long userId = pictureQueryRequest.getUserId();
String sortField = pictureQueryRequest.getSortField();
String sortOrder = pictureQueryRequest.getSortOrder();
// 从多字段中搜索
if (StrUtil.isNotBlank(searchText)) {
// 需要拼接查询条件
queryWrapper.and(qw -> qw.like("name", searchText)
.or()
.like("introduction", searchText)
);
}
queryWrapper.eq(ObjUtil.isNotEmpty(id), "id", id);
queryWrapper.eq(ObjUtil.isNotEmpty(userId), "userId", userId);
queryWrapper.like(StrUtil.isNotBlank(name), "name", name);
queryWrapper.like(StrUtil.isNotBlank(introduction), "introduction", introduction);
queryWrapper.like(StrUtil.isNotBlank(picFormat), "picFormat", picFormat);
queryWrapper.eq(StrUtil.isNotBlank(category), "category", category);
queryWrapper.eq(ObjUtil.isNotEmpty(picWidth), "picWidth", picWidth);
queryWrapper.eq(ObjUtil.isNotEmpty(picHeight), "picHeight", picHeight);
queryWrapper.eq(ObjUtil.isNotEmpty(picSize), "picSize", picSize);
queryWrapper.eq(ObjUtil.isNotEmpty(picScale), "picScale", picScale);
// JSON 数组查询
if (CollUtil.isNotEmpty(tags)) {
for (String tag : tags) {
queryWrapper.like("tags", "\"" + tag + "\"");
}
}
// 排序
queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField);
return queryWrapper;
}
获取图片封装类相关方法
@Override
public PictureVO getPictureVO(Picture picture, HttpServletRequest request) {
// 对象转封装类
PictureVO pictureVO = PictureVO.objToVo(picture);
// 关联查询用户信息
Long userId = picture.getUserId();
if (userId != null && userId > 0) {
User user = userService.getById(userId);
UserVO userVO = userService.getUserVO(user);
pictureVO.setUser(userVO);
}
return pictureVO;
}
先获取到要查询的用户id列表,只发送一次查询用户表的请求,再将查询到的值设置到图片对象中。
/**
* 分页获取图片封装
*/
@Override
public Page<PictureVO> getPictureVOPage(Page<Picture> picturePage, HttpServletRequest request) {
List<Picture> pictureList = picturePage.getRecords();
Page<PictureVO> pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal());
if (CollUtil.isEmpty(pictureList)) {
return pictureVOPage;
}
// 对象列表 => 封装对象列表
List<PictureVO> pictureVOList = pictureList.stream().map(PictureVO::objToVo).collect(Collectors.toList());
// 1. 关联查询用户信息
Set<Long> userIdSet = pictureList.stream().map(Picture::getUserId).collect(Collectors.toSet());
Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
.collect(Collectors.groupingBy(User::getId));
// 2. 填充信息
pictureVOList.forEach(pictureVO -> {
Long userId = pictureVO.getUserId();
User user = null;
if (userIdUserListMap.containsKey(userId)) {
user = userIdUserListMap.get(userId).get(0);
}
pictureVO.setUser(userService.getUserVO(user));
});
pictureVOPage.setRecords(pictureVOList);
return pictureVOPage;
}
图片数据校验
@Override
public void validPicture(Picture picture) {
ThrowUtils.throwIf(picture == null, ErrorCode.PARAMS_ERROR);
// 从对象中取值
Long id = picture.getId();
String url = picture.getUrl();
String introduction = picture.getIntroduction();
// 修改数据时,id 不能为空,有参数则校验
ThrowUtils.throwIf(ObjUtil.isNull(id), ErrorCode.PARAMS_ERROR, "id 不能为空");
if (StrUtil.isNotBlank(url)) {
ThrowUtils.throwIf(url.length() > 1024, ErrorCode.PARAMS_ERROR, "url 过长");
}
if (StrUtil.isNotBlank(introduction)) {
ThrowUtils.throwIf(introduction.length() > 800, ErrorCode.PARAMS_ERROR, "简介过长");
}
}
3.Controller
/**
* 删除图片
*/
@PostMapping("/delete")
public BaseResponse<Boolean> deletePicture(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
if (deleteRequest == null || deleteRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
User loginUser = userService.getLoginUser(request);
long id = deleteRequest.getId();
// 判断是否存在
Picture oldPicture = pictureService.getById(id);
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
// 仅本人或管理员可删除
if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 操作数据库
boolean result = pictureService.removeById(id);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
/**
* 更新图片(仅管理员可用)
*/
@PostMapping("/update")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> updatePicture(@RequestBody PictureUpdateRequest pictureUpdateRequest) {
if (pictureUpdateRequest == null || pictureUpdateRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 将实体类和 DTO 进行转换
Picture picture = new Picture();
BeanUtils.copyProperties(pictureUpdateRequest, picture);
// 注意将 list 转为 string
picture.setTags(JSONUtil.toJsonStr(pictureUpdateRequest.getTags()));
// 数据校验
pictureService.validPicture(picture);
// 判断是否存在
long id = pictureUpdateRequest.getId();
Picture oldPicture = pictureService.getById(id);
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
// 操作数据库
boolean result = pictureService.updateById(picture);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
/**
* 根据 id 获取图片(仅管理员可用)
*/
@GetMapping("/get")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Picture> getPictureById(long id, HttpServletRequest request) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
// 查询数据库
Picture picture = pictureService.getById(id);
ThrowUtils.throwIf(picture == null, ErrorCode.NOT_FOUND_ERROR);
// 获取封装类
return ResultUtils.success(picture);
}
/**
* 根据 id 获取图片(封装类)
*/
@GetMapping("/get/vo")
public BaseResponse<PictureVO> getPictureVOById(long id, HttpServletRequest request) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
// 查询数据库
Picture picture = pictureService.getById(id);
ThrowUtils.throwIf(picture == null, ErrorCode.NOT_FOUND_ERROR);
// 获取封装类
return ResultUtils.success(pictureService.getPictureVO(picture, request));
}
/**
* 分页获取图片列表(仅管理员可用)
*/
@PostMapping("/list/page")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Page<Picture>> listPictureByPage(@RequestBody PictureQueryRequest pictureQueryRequest) {
long current = pictureQueryRequest.getCurrent();
long size = pictureQueryRequest.getPageSize();
// 查询数据库
Page<Picture> picturePage = pictureService.page(new Page<>(current, size),
pictureService.getQueryWrapper(pictureQueryRequest));
return ResultUtils.success(picturePage);
}
/**
* 分页获取图片列表(封装类)
*/
@PostMapping("/list/page/vo")
public BaseResponse<Page<PictureVO>> listPictureVOByPage(@RequestBody PictureQueryRequest pictureQueryRequest,
HttpServletRequest request) {
long current = pictureQueryRequest.getCurrent();
long size = pictureQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
// 查询数据库
Page<Picture> picturePage = pictureService.page(new Page<>(current, size),
pictureService.getQueryWrapper(pictureQueryRequest));
// 获取封装类
return ResultUtils.success(pictureService.getPictureVOPage(picturePage, request));
}
/**
* 编辑图片(给用户使用)
*/
@PostMapping("/edit")
public BaseResponse<Boolean> editPicture(@RequestBody PictureEditRequest pictureEditRequest, HttpServletRequest request) {
if (pictureEditRequest == null || pictureEditRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 在此处将实体类和 DTO 进行转换
Picture picture = new Picture();
BeanUtils.copyProperties(pictureEditRequest, picture);
// 注意将 list 转为 string
picture.setTags(JSONUtil.toJsonStr(pictureEditRequest.getTags()));
// 设置编辑时间
picture.setEditTime(new Date());
// 数据校验
pictureService.validPicture(picture);
User loginUser = userService.getLoginUser(request);
// 判断是否存在
long id = pictureEditRequest.getId();
Picture oldPicture = pictureService.getById(id);
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
// 仅本人或管理员可编辑
if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 操作数据库
boolean result = pictureService.updateById(picture);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
4.获取预置标签和分类
随着系统规模和数据不断扩大,可以再更改为使用配置中心或数据库动态管理这些数据,或者通过定时任务计算出热门图片分类和标签
@GetMapping("/tag_category")
public BaseResponse<PictureTagCategory> listPictureTagCategory() {
PictureTagCategory pictureTagCategory = new PictureTagCategory();
List<String> tagList = Arrays.asList("热门", "搞笑", "生活", "高清", "艺术", "校园", "背景", "简历", "创意");
List<String> categoryList = Arrays.asList("模板", "电商", "表情包", "素材", "海报");
pictureTagCategory.setTagList(tagList);
pictureTagCategory.setCategoryList(categoryList);
return ResultUtils.success(pictureTagCategory);
}
387

被折叠的 条评论
为什么被折叠?



