3.25 空间模块 增删查改基础代码

This commit is contained in:
zhangsan 2025-03-25 17:58:51 +08:00
parent 1026b764dd
commit c2d8391166
20 changed files with 1102 additions and 50 deletions

View File

@ -62,3 +62,31 @@ ALTER TABLE picture
-- 添加新列
ADD COLUMN original_url varchar(512) NULL COMMENT '原图 url',
ADD COLUMN thumbnail_url varchar(512) NULL COMMENT '缩略图 url';
-- 空间表
create table if not exists space
(
id bigint auto_increment comment 'id' primary key,
space_name varchar(128) null comment '空间名称',
space_level int default 0 null comment '空间级别0-普通版 1-专业版 2-旗舰版',
max_size bigint default 0 null comment '空间图片的最大总大小',
max_count bigint default 0 null comment '空间图片的最大数量',
total_size bigint default 0 null comment '当前空间下图片的总大小',
total_count bigint default 0 null comment '当前空间下的图片数量',
user_id bigint not null comment '创建用户 id',
create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
edit_time datetime default CURRENT_TIMESTAMP not null comment '编辑时间',
update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
is_delete tinyint default 0 not null comment '是否删除',
-- 索引设计
index idx_userId (user_id), -- 提升基于用户的查询效率
index idx_spaceName (space_name), -- 提升基于空间名称的查询效率
index idx_spaceLevel (space_level) -- 提升按空间级别查询的效率
) comment '空间' collate = utf8mb4_unicode_ci;
-- 添加新列
ALTER TABLE picture
ADD COLUMN space_id bigint null comment '空间 id为空表示公共空间';
-- 创建索引
CREATE INDEX idx_spaceId ON picture (space_id);

View File

@ -84,6 +84,19 @@ public class PictureController {
return ResultUtils.success(true);
}
/**
* 编辑图片给用户使用或创建图片时编辑标签分类的时候
*/
@PostMapping("/edit")
public BaseResponse<Boolean> editPicture(@RequestBody PictureEditRequest pictureEditRequest, HttpServletRequest request) {
if (pictureEditRequest == null || pictureEditRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
User loginUser = userService.getLoginUser(request);
pictureService.editPicture(pictureEditRequest, loginUser);
return ResultUtils.success(true);
}
/**
* 更新图片仅管理员可用
*
@ -196,18 +209,6 @@ public class PictureController {
return ResultUtils.success(page);
}
/**
* 编辑图片给用户使用或创建图片时编辑标签分类的时候
*/
@PostMapping("/edit")
public BaseResponse<Boolean> editPicture(@RequestBody PictureEditRequest pictureEditRequest, HttpServletRequest request) {
if (pictureEditRequest == null || pictureEditRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
User loginUser = userService.getLoginUser(request);
pictureService.editPicture(pictureEditRequest, loginUser);
return ResultUtils.success(true);
}
@GetMapping("/tag_category")
public BaseResponse<PictureTagCategory> listPictureTagCategory() {

View File

@ -0,0 +1,190 @@
package edu.whut.smilepicturebackend.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import edu.whut.smilepicturebackend.annotation.AuthCheck;
import edu.whut.smilepicturebackend.common.BaseResponse;
import edu.whut.smilepicturebackend.common.DeleteRequest;
import edu.whut.smilepicturebackend.common.ResultUtils;
import edu.whut.smilepicturebackend.constant.UserConstant;
import edu.whut.smilepicturebackend.exception.BusinessException;
import edu.whut.smilepicturebackend.exception.ErrorCode;
import edu.whut.smilepicturebackend.exception.ThrowUtils;
import edu.whut.smilepicturebackend.model.dto.space.*;
import edu.whut.smilepicturebackend.model.entity.Space;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.enums.SpaceLevelEnum;
import edu.whut.smilepicturebackend.model.vo.SpaceVO;
import edu.whut.smilepicturebackend.service.SpaceService;
import edu.whut.smilepicturebackend.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author 程序员鱼皮 <a href="https://www.codefather.cn">编程导航原创项目</a>
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/space")
public class SpaceController {
private final UserService userService;
private final SpaceService spaceService;
@PostMapping("/add")
public BaseResponse<Long> addSpace(@RequestBody SpaceAddRequest spaceAddRequest, HttpServletRequest request) {
ThrowUtils.throwIf(spaceAddRequest == null, ErrorCode.PARAMS_ERROR);
User loginUser = userService.getLoginUser(request);
long newId = spaceService.addSpace(spaceAddRequest, loginUser);
return ResultUtils.success(newId);
}
@PostMapping("/delete")
public BaseResponse<Boolean> deleteSpace(@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();
// 判断是否存在
Space oldSpace = spaceService.getById(id);
ThrowUtils.throwIf(oldSpace == null, ErrorCode.NOT_FOUND_ERROR);
// 仅本人或者管理员可删除
spaceService.checkSpaceAuth(loginUser, oldSpace);
// 操作数据库
boolean result = spaceService.removeById(id);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
/**
* 更新空间仅管理员可用
*
* @param spaceUpdateRequest
* @param request
* @return
*/
@PostMapping("/update")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> updateSpace(@RequestBody SpaceUpdateRequest spaceUpdateRequest,
HttpServletRequest request) {
if (spaceUpdateRequest == null || spaceUpdateRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 将实体类和 DTO 进行转换
Space space = new Space();
BeanUtils.copyProperties(spaceUpdateRequest, space);
// 自动填充数据
spaceService.fillSpaceBySpaceLevel(space);
// 数据校验
spaceService.validSpace(space, false); //不是新增时的操作
// 判断是否存在
long id = spaceUpdateRequest.getId();
Space oldSpace = spaceService.getById(id);
ThrowUtils.throwIf(oldSpace == null, ErrorCode.NOT_FOUND_ERROR);
// 操作数据库
boolean result = spaceService.updateById(space);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
/**
* 根据 id 获取空间仅管理员可用
*/
@GetMapping("/get")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Space> getSpaceById(long id, HttpServletRequest request) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
// 查询数据库
Space space = spaceService.getById(id);
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR);
// 获取封装类
return ResultUtils.success(space);
}
/**
* 根据 id 获取空间封装类
*/
@GetMapping("/get/vo")
public BaseResponse<SpaceVO> getSpaceVOById(long id, HttpServletRequest request) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
// 查询数据库
Space space = spaceService.getById(id);
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR);
return ResultUtils.success(spaceService.getSpaceVO(space,request));
}
/**
* 分页获取空间列表仅管理员可用
*/
@PostMapping("/list/page")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Page<Space>> listSpaceByPage(@RequestBody SpaceQueryRequest spaceQueryRequest) {
long current = spaceQueryRequest.getCurrent();
long size = spaceQueryRequest.getPageSize();
// 查询数据库
Page<Space> spacePage = spaceService.page(new Page<>(current, size),
spaceService.getQueryWrapper(spaceQueryRequest));
return ResultUtils.success(spacePage);
}
/**
* 分页获取空间列表封装类 用户使用
*/
@PostMapping("/list/page/vo")
public BaseResponse<Page<SpaceVO>> listSpaceVOByPage(@RequestBody SpaceQueryRequest spaceQueryRequest,
HttpServletRequest request) {
long current = spaceQueryRequest.getCurrent();
long size = spaceQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
// 查询数据库
Page<Space> spacePage = spaceService.page(new Page<>(current, size),
spaceService.getQueryWrapper(spaceQueryRequest));
// 获取封装类
return ResultUtils.success(spaceService.getSpaceVOPage(spacePage, request));
}
/**
* 编辑空间给用户使用
*/
@PostMapping("/edit")
public BaseResponse<Boolean> editSpace(@RequestBody SpaceEditRequest spaceEditRequest, HttpServletRequest request) {
if (spaceEditRequest == null || spaceEditRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 在此处将实体类和 DTO 进行转换
Space space = new Space();
BeanUtils.copyProperties(spaceEditRequest, space);
// 自动填充数据
spaceService.fillSpaceBySpaceLevel(space);
// 设置编辑时间
space.setEditTime(new Date());
// 数据校验
spaceService.validSpace(space, false);
User loginUser = userService.getLoginUser(request);
// 判断是否存在
long id = spaceEditRequest.getId();
Space oldSpace = spaceService.getById(id);
ThrowUtils.throwIf(oldSpace == null, ErrorCode.NOT_FOUND_ERROR);
// 仅本人或管理员可编辑
spaceService.checkSpaceAuth(loginUser, oldSpace);
// 操作数据库
boolean result = spaceService.updateById(space);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
}

View File

@ -0,0 +1,17 @@
package edu.whut.smilepicturebackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import edu.whut.smilepicturebackend.model.entity.Space;
/**
* @author 张三
* @description 针对表space(空间)的数据库操作Mapper
* @createDate 2025-06-16 16:49:09
* @Entity generator.domain.Space
*/
public interface SpaceMapper extends BaseMapper<Space> {
}

View File

@ -27,5 +27,10 @@ public class PictureUploadRequest implements Serializable {
*/
private String picName;
/**
* 空间 id
*/
private Long spaceId;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,29 @@
package edu.whut.smilepicturebackend.model.dto.space;
import lombok.Data;
import java.io.Serializable;
/**
* 创建空间请求
*/
@Data
public class SpaceAddRequest implements Serializable {
/**
* 空间名称
*/
private String spaceName;
/**
* 空间级别0-普通版 1-专业版 2-旗舰版
*/
private Integer spaceLevel;
/**
* 空间类型0-私有 1-团队
*/
private Integer spaceType;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,24 @@
package edu.whut.smilepicturebackend.model.dto.space;
import lombok.Data;
import java.io.Serializable;
/**
* 编辑空间请求
*/
@Data
public class SpaceEditRequest implements Serializable {
/**
* 空间 id
*/
private Long id;
/**
* 空间名称
*/
private String spaceName;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,32 @@
package edu.whut.smilepicturebackend.model.dto.space;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 空间级别
*/
@Data
@AllArgsConstructor
public class SpaceLevel {
/**
*
*/
private int value;
/**
* 中文
*/
private String text;
/**
* 最大数量
*/
private long maxCount;
/**
* 最大容量
*/
private long maxSize;
}

View File

@ -0,0 +1,42 @@
package edu.whut.smilepicturebackend.model.dto.space;
import edu.whut.smilepicturebackend.common.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 查询空间请求
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class SpaceQueryRequest extends PageRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 用户 id
*/
private Long userId;
/**
* 空间名称
*/
private String spaceName;
/**
* 空间级别0-普通版 1-专业版 2-旗舰版
*/
private Integer spaceLevel;
/**
* 空间类型0-私有 1-团队
*/
private Integer spaceType;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,39 @@
package edu.whut.smilepicturebackend.model.dto.space;
import lombok.Data;
import java.io.Serializable;
/**
* 更新空间请求
*/
@Data
public class SpaceUpdateRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 空间名称
*/
private String spaceName;
/**
* 空间级别0-普通版 1-专业版 2-旗舰版
*/
private Integer spaceLevel;
/**
* 空间图片的最大总大小
*/
private Long maxSize;
/**
* 空间图片的最大数量
*/
private Long maxCount;
private static final long serialVersionUID = 1L;
}

View File

@ -84,6 +84,11 @@ public class Picture implements Serializable {
*/
private Long userId;
/**
* 空间 id
*/
private Long spaceId;
/**
* 审核状态0-待审核; 1-通过; 2-拒绝
*/

View File

@ -0,0 +1,80 @@
package edu.whut.smilepicturebackend.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 空间
* @TableName space
*/
@TableName(value ="space")
@Data
public class Space implements Serializable {
/**
* id
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 空间名称
*/
private String spaceName;
/**
* 空间级别0-普通版 1-专业版 2-旗舰版
*/
private Integer spaceLevel;
/**
* 空间图片的最大总大小
*/
private Long maxSize;
/**
* 空间图片的最大数量
*/
private Long maxCount;
/**
* 当前空间下图片的总大小
*/
private Long totalSize;
/**
* 当前空间下的图片数量
*/
private Long totalCount;
/**
* 创建用户 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;
}

View File

@ -0,0 +1,52 @@
package edu.whut.smilepicturebackend.model.enums;
import cn.hutool.core.util.ObjUtil;
import lombok.Getter;
/**
* 空间级别枚举
*/
@Getter
public enum SpaceLevelEnum {
COMMON("普通版", 0, 100, 100L * 1024 * 1024),
PROFESSIONAL("专业版", 1, 1000, 1000L * 1024 * 1024),
FLAGSHIP("旗舰版", 2, 10000, 10000L * 1024 * 1024);
private final String text;
private final int value;
private final long maxCount;
private final long maxSize;
/**
* @param text 文本
* @param value
* @param maxSize 最大图片总大小
* @param maxCount 最大图片总数量
*/
SpaceLevelEnum(String text, int value, long maxCount, long maxSize) {
this.text = text;
this.value = value;
this.maxCount = maxCount;
this.maxSize = maxSize;
}
/**
* 根据 value 获取枚举
*/
public static SpaceLevelEnum getEnumByValue(Integer value) {
if (ObjUtil.isEmpty(value)) {
return null;
}
for (SpaceLevelEnum spaceLevelEnum : SpaceLevelEnum.values()) {
if (spaceLevelEnum.value == value) {
return spaceLevelEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,117 @@
package edu.whut.smilepicturebackend.model.vo;
import edu.whut.smilepicturebackend.model.entity.Space;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 空间视图
*/
@Data
public class SpaceVO implements Serializable {
/**
* id
*/
private Long id;
/**
* 空间名称
*/
private String spaceName;
/**
* 空间级别0-普通版 1-专业版 2-旗舰版
*/
private Integer spaceLevel;
/**
* 空间类型0-私有 1-团队
*/
private Integer spaceType;
/**
* 空间图片的最大总大小
*/
private Long maxSize;
/**
* 空间图片的最大数量
*/
private Long maxCount;
/**
* 当前空间下图片的总大小
*/
private Long totalSize;
/**
* 当前空间下的图片数量
*/
private Long totalCount;
/**
* 创建用户 id
*/
private Long userId;
/**
* 创建时间
*/
private Date createTime;
/**
* 编辑时间
*/
private Date editTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建用户信息
*/
private UserVO user;
/**
* 权限列表
*/
private List<String> permissionList = new ArrayList<>();
private static final long serialVersionUID = 1L;
/**
* 封装类转对象
*
* @param spaceVO
* @return
*/
public static Space voToObj(SpaceVO spaceVO) {
if (spaceVO == null) {
return null;
}
Space space = new Space();
BeanUtils.copyProperties(spaceVO, space);
return space;
}
/**
* 对象转封装类
*
* @param space
* @return
*/
public static SpaceVO objToVo(Space space) {
if (space == null) {
return null;
}
SpaceVO spaceVO = new SpaceVO();
BeanUtils.copyProperties(space, spaceVO);
return spaceVO;
}
}

View File

@ -118,4 +118,6 @@ public interface PictureService extends IService<Picture> {
* @param oldPicture
*/
void clearPictureFile(Picture oldPicture);
void checkPictureAuth(User loginUser, Picture picture);
}

View File

@ -0,0 +1,78 @@
package edu.whut.smilepicturebackend.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import edu.whut.smilepicturebackend.model.dto.space.SpaceAddRequest;
import edu.whut.smilepicturebackend.model.dto.space.SpaceQueryRequest;
import edu.whut.smilepicturebackend.model.entity.Space;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.vo.SpaceVO;
import javax.servlet.http.HttpServletRequest;
/**
* @author 张三
* @description 针对表space(空间)的数据库操作Service
* @createDate 2025-06-16 16:49:09
*/
public interface SpaceService extends IService<Space> {
/**
* 创建空间
*
* @param spaceAddRequest
* @param loginUser
* @return
*/
long addSpace(SpaceAddRequest spaceAddRequest, User loginUser);
/**
* 校验空间
*
* @param space
* @param add 是否为创建时检验
*/
void validSpace(Space space, boolean add);
/**
* 获取空间包装类单条
*
* @param space
* @param request
* @return
*/
SpaceVO getSpaceVO(Space space, HttpServletRequest request);
/**
* 获取空间包装类分页
*
* @param spacePage
* @param request
* @return
*/
Page<SpaceVO> getSpaceVOPage(Page<Space> spacePage, HttpServletRequest request);
/**
* 获取查询对象
*
* @param spaceQueryRequest
* @return
*/
LambdaQueryWrapper<Space> getQueryWrapper(SpaceQueryRequest spaceQueryRequest);
/**
* 根据空间级别填充空间对象
*
* @param space
*/
void fillSpaceBySpaceLevel(Space space);
/**
* 校验空间权限
*
* @param loginUser
* @param space
*/
void checkSpaceAuth(User loginUser, Space space);
}

View File

@ -5,7 +5,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -24,12 +23,14 @@ import edu.whut.smilepicturebackend.manager.upload.UrlPictureUpload;
import edu.whut.smilepicturebackend.mapper.PictureMapper;
import edu.whut.smilepicturebackend.model.dto.picture.*;
import edu.whut.smilepicturebackend.model.entity.Picture;
import edu.whut.smilepicturebackend.model.entity.Space;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.enums.PictureReviewStatusEnum;
import edu.whut.smilepicturebackend.model.file.UploadPictureResult;
import edu.whut.smilepicturebackend.model.vo.PictureVO;
import edu.whut.smilepicturebackend.model.vo.UserVO;
import edu.whut.smilepicturebackend.service.PictureService;
import edu.whut.smilepicturebackend.service.SpaceService;
import edu.whut.smilepicturebackend.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -40,7 +41,6 @@ import org.jsoup.select.Elements;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
@ -68,6 +68,7 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
private final UrlPictureUpload urlPictureUpload;
private final MyCacheManager cacheManager;
private final CosManager cosManager;
private final SpaceService spaceService;
@Override
public void validPicture(Picture picture) {
ThrowUtils.throwIf(picture == null, ErrorCode.PARAMS_ERROR);
@ -90,7 +91,17 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) {
// 校验参数
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
// 校验空间是否存在
Long spaceId = pictureUploadRequest.getSpaceId();
if (spaceId != null) {
Space space = spaceService.getById(spaceId);
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
// 改为使用统一的权限校验
// 校验是否有空间的权限仅空间管理员才能上传
if (!loginUser.getId().equals(space.getUserId())) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "没有空间权限");
}
}
// 判断是创建还是替换
Long pictureId = pictureUploadRequest == null ? null : pictureUploadRequest.getId();
Picture oldPicture = null;
@ -103,11 +114,29 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 校验空间是否一致
// 没传 spaceId则复用原有图片的 spaceId这样也兼容了公共图库
if (spaceId == null) {
if (oldPicture.getSpaceId() != null) {
spaceId = oldPicture.getSpaceId();
}
} else {
// 传了 spaceId必须和原图片的空间 id 一致
if (ObjUtil.notEqual(spaceId, oldPicture.getSpaceId())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间 id 不一致");
}
}
}
// 上传图片得到图片信息
// 按照用户 id 划分目录 => 按照空间划分目录
String uploadPathPrefix;
//公共图库下每个用户有自己的userid管理的文件夹
uploadPathPrefix = String.format("public/%s", loginUser.getId());
if (spaceId == null) {
// 公共图库+用户id
uploadPathPrefix = String.format("public/%s", loginUser.getId());
} else {
// 私人空间+空间id
uploadPathPrefix = String.format("space/%s", spaceId);
}
// 根据 inputSource 的类型区分上传方式!!
PictureUploadTemplate pictureUploadTemplate = filePictureUpload;
if (inputSource instanceof String) {
@ -127,6 +156,7 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
)
);
picture.setUserId(loginUser.getId());
picture.setSpaceId(spaceId);
// 补充审核参数
this.fillReviewParams(picture, loginUser);
// 操作数据库
@ -204,6 +234,8 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
&& !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 校验权限
checkPictureAuth(loginUser, oldPicture);
// 操作数据库
boolean result = this.removeById(pictureId);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
@ -211,6 +243,30 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
this.clearPictureFile(oldPicture);
}
@Override
public void editPicture(PictureEditRequest pictureEditRequest, User loginUser) {
// 在此处将实体类和 DTO 进行转换
Picture picture = new Picture();
BeanUtils.copyProperties(pictureEditRequest, picture);
// 注意将 list 转为 string
picture.setTags(JSONUtil.toJsonStr(pictureEditRequest.getTags()));
// 设置编辑时间
picture.setEditTime(new Date());
// 数据校验
this.validPicture(picture);
// 判断是否存在
long id = pictureEditRequest.getId();
Picture oldPicture = this.getById(id);
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
// 校验权限
checkPictureAuth(loginUser, oldPicture);
// 补充审核参数,每次编辑图片都要重新过审
this.fillReviewParams(picture, loginUser);
// 操作数据库
boolean result = this.updateById(picture);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
}
@Override
public PictureVO getPictureVO(Picture picture, HttpServletRequest request) {
// 对象转封装类
@ -261,32 +317,6 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
return pictureVOPage;
}
@Override
public void editPicture(PictureEditRequest pictureEditRequest, User loginUser) {
// 在此处将实体类和 DTO 进行转换
Picture picture = new Picture();
BeanUtils.copyProperties(pictureEditRequest, picture);
// 注意将 list 转为 string
picture.setTags(JSONUtil.toJsonStr(pictureEditRequest.getTags()));
// 设置编辑时间
picture.setEditTime(new Date());
// 数据校验
this.validPicture(picture);
// 补充审核参数,每次编辑图片都要重新过审
this.fillReviewParams(picture, loginUser);
// 判断是否存在
long id = pictureEditRequest.getId();
Picture oldPicture = this.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 = this.updateById(picture);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
}
@Override
public void doPictureReview(PictureReviewRequest pictureReviewRequest, User loginUser) {
// 1. 校验参数
@ -458,9 +488,23 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
// 删除缩略图
String thumbnailUrl = oldPicture.getThumbnailUrl();
cosManager.deleteIfNotBlank(thumbnailUrl);
}
@Override
public void checkPictureAuth(User loginUser, Picture picture) {
Long spaceId = picture.getSpaceId();
Long loginUserId = loginUser.getId();
if (spaceId == null) {
// 公共图库仅本人或管理员可操作
if (!picture.getUserId().equals(loginUserId) && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
} else {
// 私有空间仅空间管理员可操作
if (!picture.getUserId().equals(loginUserId)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
}
}
}

View File

@ -0,0 +1,233 @@
package edu.whut.smilepicturebackend.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import edu.whut.smilepicturebackend.exception.BusinessException;
import edu.whut.smilepicturebackend.exception.ErrorCode;
import edu.whut.smilepicturebackend.exception.ThrowUtils;
import edu.whut.smilepicturebackend.mapper.SpaceMapper;
import edu.whut.smilepicturebackend.model.dto.space.SpaceAddRequest;
import edu.whut.smilepicturebackend.model.dto.space.SpaceQueryRequest;
import edu.whut.smilepicturebackend.model.entity.Space;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.enums.SpaceLevelEnum;
import edu.whut.smilepicturebackend.model.vo.SpaceVO;
import edu.whut.smilepicturebackend.model.vo.UserVO;
import edu.whut.smilepicturebackend.service.SpaceService;
import edu.whut.smilepicturebackend.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author 张三
* @description 针对表space(空间)的数据库操作Service实现
* @createDate 2025-06-16 16:49:09
*/
@Service
@RequiredArgsConstructor
public class SpaceServiceImpl extends ServiceImpl<SpaceMapper, Space>
implements SpaceService {
private final UserService userService;
// 静态锁表JVM 级别共享
private static final ConcurrentHashMap<Long, Object> USER_LOCKS = new ConcurrentHashMap<>();
private final TransactionTemplate transactionTemplate;
// 为了方便部署注释掉分表
// @Resource
// @Lazy
// private DynamicShardingManager dynamicShardingManager;
/**
* 创建空间 加锁和事务
*
* @param spaceAddRequest
* @param loginUser
* @return
*/
@Override
public long addSpace(SpaceAddRequest spaceAddRequest, User loginUser) {
// 1. 填充参数默认值
// 转换实体类和 DTO
Space space = new Space();
BeanUtils.copyProperties(spaceAddRequest, space);
if (StrUtil.isBlank(space.getSpaceName())) {
space.setSpaceName("默认空间");
}
if (space.getSpaceLevel() == null) {
space.setSpaceLevel(SpaceLevelEnum.COMMON.getValue());
}
// 填充容量和大小
this.fillSpaceBySpaceLevel(space);
// 2. 校验参数
this.validSpace(space, true);
// 3. 校验权限非管理员只能创建普通级别的空间
Long userId = loginUser.getId();
space.setUserId(userId);
if (SpaceLevelEnum.COMMON.getValue() != space.getSpaceLevel() && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限创建指定级别的空间");
}
// 4. 控制同一用户只能创建一个私有空间以及一个团队空间
//// 1. 获取或创建该 userId 对应的锁对象避免使用String.intern()来定义锁能避免常量池内容越来越多
Object lock = USER_LOCKS.computeIfAbsent(userId, id -> new Object());
synchronized (lock) {
try {
// 2. 在事务内检查并创建空间
Long newSpaceId = transactionTemplate.execute(status -> {
boolean exists = this.lambdaQuery()
.eq(Space::getUserId, userId)
.exists();
ThrowUtils.throwIf(exists, ErrorCode.OPERATION_ERROR, "每个用户每类空间只能创建一个");
boolean result = this.save(space);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "保存空间到数据库失败");
return space.getId();
});
return Optional.ofNullable(newSpaceId).orElse(-1L);
} finally {
// 3. 可选移除锁对象防止 Map 膨胀仅当你确定没有并发需求时才移除
USER_LOCKS.remove(userId, lock);
}
}
}
@Override
public void validSpace(Space space, boolean add) {
ThrowUtils.throwIf(space == null, ErrorCode.PARAMS_ERROR);
// 从对象中取值
String spaceName = space.getSpaceName();
Integer spaceLevel = space.getSpaceLevel();
SpaceLevelEnum spaceLevelEnum = SpaceLevelEnum.getEnumByValue(spaceLevel);
// 创建时校验
if (add) {
if (StrUtil.isBlank(spaceName)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间名称不能为空");
}
if (spaceLevel == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间级别不能为空");
}
}
// 修改数据时空间名称进行校验
if (StrUtil.isNotBlank(spaceName) && spaceName.length() > 30) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间名称过长");
}
// 修改数据时空间级别进行校验
if (spaceLevel != null && spaceLevelEnum == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间级别不存在");
}
}
@Override
public SpaceVO getSpaceVO(Space space, HttpServletRequest request) {
// 对象转封装类
SpaceVO spaceVO = SpaceVO.objToVo(space);
// 关联查询用户信息
Long userId = space.getUserId();
if (userId != null && userId > 0) {
User user = userService.getById(userId);
UserVO userVO = userService.getUserVO(user);
spaceVO.setUser(userVO);
}
return spaceVO;
}
@Override
public Page<SpaceVO> getSpaceVOPage(Page<Space> spacePage, HttpServletRequest request) {
List<Space> spaceList = spacePage.getRecords();
Page<SpaceVO> spaceVOPage = new Page<>(spacePage.getCurrent(), spacePage.getSize(), spacePage.getTotal());
if (CollUtil.isEmpty(spaceList)) {
return spaceVOPage;
}
// 对象列表 => 封装对象列表
List<SpaceVO> spaceVOList = spaceList.stream()
.map(SpaceVO::objToVo)
.collect(Collectors.toList());
// 1. 关联查询用户信息
// 1,2,3,4
Set<Long> userIdSet = spaceList.stream().map(Space::getUserId).collect(Collectors.toSet());
// 1 => user1, 2 => user2
Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
.collect(Collectors.groupingBy(User::getId));
// 2. 填充信息
spaceVOList.forEach(spaceVO -> {
Long userId = spaceVO.getUserId();
User user = null;
if (userIdUserListMap.containsKey(userId)) {
user = userIdUserListMap.get(userId).get(0);
}
spaceVO.setUser(userService.getUserVO(user));
});
spaceVOPage.setRecords(spaceVOList);
return spaceVOPage;
}
@Override
public LambdaQueryWrapper<Space> getQueryWrapper(SpaceQueryRequest req) {
if (req == null) {
// 请求参数为空时抛出异常或根据业务自行处理
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
}
// 创建 LambdaQueryWrapper
LambdaQueryWrapper<Space> qw = Wrappers.lambdaQuery(Space.class);
// 基本等值与模糊匹配
qw.eq(ObjUtil.isNotNull(req.getId()), Space::getId, req.getId())
.eq(ObjUtil.isNotNull(req.getUserId()), Space::getUserId, req.getUserId())
.like(StrUtil.isNotBlank(req.getSpaceName()), Space::getSpaceName, req.getSpaceName())
.eq(ObjUtil.isNotNull(req.getSpaceLevel()), Space::getSpaceLevel, req.getSpaceLevel());
// 动态排序将驼峰字段转成下划线再拼到 SQL ORDER BY
if (StrUtil.isNotBlank(req.getSortField())) {
String column = StrUtil.toUnderlineCase(req.getSortField());
boolean asc = "ascend".equalsIgnoreCase(req.getSortOrder());
qw.last("ORDER BY " + column + (asc ? " ASC" : " DESC"));
}
return qw;
}
@Override
public void fillSpaceBySpaceLevel(Space space) {
SpaceLevelEnum spaceLevelEnum = SpaceLevelEnum.getEnumByValue(space.getSpaceLevel());
if (spaceLevelEnum != null) {
long maxSize = spaceLevelEnum.getMaxSize();
if (space.getMaxSize() == null) {
space.setMaxSize(maxSize);
}
long maxCount = spaceLevelEnum.getMaxCount();
if (space.getMaxCount() == null) {
space.setMaxCount(maxCount);
}
}
}
@Override
public void checkSpaceAuth(User loginUser, Space space) {
// 仅本人或管理员可编辑
if (!space.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
}
}

View File

@ -19,18 +19,24 @@
<result property="picScale" column="pic_scale" jdbcType="DOUBLE"/>
<result property="picFormat" column="pic_format" jdbcType="VARCHAR"/>
<result property="userId" column="user_id" jdbcType="BIGINT"/>
<result property="spaceId" column="space_id" jdbcType="BIGINT"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="editTime" column="edit_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="isDelete" column="is_delete" jdbcType="TINYINT"/>
<result property="reviewStatus" column="reviewStatus" jdbcType="INTEGER"/>
<result property="reviewMessage" column="reviewMessage" jdbcType="VARCHAR"/>
<result property="reviewerId" column="reviewerId" jdbcType="BIGINT"/>
<result property="reviewTime" column="reviewTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,url,thumbnail_url,name,
id,url,thumbnailUrl,name,
introduction,category,tags,
pic_size,pic_width,pic_height,
pic_scale,pic_format,user_id,
create_time,edit_time,update_time,
is_delete
picSize,picWidth,picHeight,
picScale,picFormat,userId,spaceId,
createTime,editTime,updateTime,
isDelete,reviewStatus,reviewMessage,
reviewerId,reviewTime
</sql>
</mapper>

View File

@ -0,0 +1,28 @@
<?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="edu.whut.smilepicturebackend.mapper.SpaceMapper">
<resultMap id="BaseResultMap" type="edu.whut.smilepicturebackend.model.entity.Space">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="spaceName" column="space_name" jdbcType="VARCHAR"/>
<result property="spaceLevel" column="space_level" jdbcType="INTEGER"/>
<result property="maxSize" column="max_size" jdbcType="BIGINT"/>
<result property="maxCount" column="max_count" jdbcType="BIGINT"/>
<result property="totalSize" column="total_size" jdbcType="BIGINT"/>
<result property="totalCount" column="total_count" jdbcType="BIGINT"/>
<result property="userId" column="user_id" jdbcType="BIGINT"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="editTime" column="edit_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="isDelete" column="is_delete" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,space_name,space_level,
max_size,max_count,total_size,
total_count,user_id,create_time,
edit_time,update_time,is_delete
</sql>
</mapper>