diff --git a/sql/create_table.sql b/sql/create_table.sql index 46aba32..f49bfc3 100644 --- a/sql/create_table.sql +++ b/sql/create_table.sql @@ -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); \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java index 4d22916..bf32794 100644 --- a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java +++ b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java @@ -84,6 +84,19 @@ public class PictureController { return ResultUtils.success(true); } + /** + * 编辑图片(给用户使用)或创建图片时,编辑标签、分类的时候 + */ + @PostMapping("/edit") + public BaseResponse 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 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 listPictureTagCategory() { diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java b/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java new file mode 100644 index 0000000..86ed450 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java @@ -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 程序员鱼皮 编程导航原创项目 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/space") +public class SpaceController { + + private final UserService userService; + + private final SpaceService spaceService; + + + @PostMapping("/add") + public BaseResponse 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 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 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 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 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> listSpaceByPage(@RequestBody SpaceQueryRequest spaceQueryRequest) { + long current = spaceQueryRequest.getCurrent(); + long size = spaceQueryRequest.getPageSize(); + // 查询数据库 + Page spacePage = spaceService.page(new Page<>(current, size), + spaceService.getQueryWrapper(spaceQueryRequest)); + return ResultUtils.success(spacePage); + } + + /** + * 分页获取空间列表(封装类) 用户使用 + */ + @PostMapping("/list/page/vo") + public BaseResponse> listSpaceVOByPage(@RequestBody SpaceQueryRequest spaceQueryRequest, + HttpServletRequest request) { + long current = spaceQueryRequest.getCurrent(); + long size = spaceQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page spacePage = spaceService.page(new Page<>(current, size), + spaceService.getQueryWrapper(spaceQueryRequest)); + // 获取封装类 + return ResultUtils.success(spaceService.getSpaceVOPage(spacePage, request)); + } + + /** + * 编辑空间(给用户使用) + */ + @PostMapping("/edit") + public BaseResponse 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); + } + +} diff --git a/src/main/java/edu/whut/smilepicturebackend/mapper/SpaceMapper.java b/src/main/java/edu/whut/smilepicturebackend/mapper/SpaceMapper.java new file mode 100644 index 0000000..ba94149 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/mapper/SpaceMapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java index 43bce23..686ddfc 100644 --- a/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java @@ -27,5 +27,10 @@ public class PictureUploadRequest implements Serializable { */ private String picName; + /** + * 空间 id + */ + private Long spaceId; + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceAddRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceAddRequest.java new file mode 100644 index 0000000..b78f279 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceAddRequest.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceEditRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceEditRequest.java new file mode 100644 index 0000000..3d7e454 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceEditRequest.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceLevel.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceLevel.java new file mode 100644 index 0000000..aa97382 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceLevel.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceQueryRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceQueryRequest.java new file mode 100644 index 0000000..a4ad4d3 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceQueryRequest.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceUpdateRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceUpdateRequest.java new file mode 100644 index 0000000..2752d60 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/space/SpaceUpdateRequest.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java b/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java index 41c9ce2..9835a80 100644 --- a/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java +++ b/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java @@ -84,6 +84,11 @@ public class Picture implements Serializable { */ private Long userId; + /** + * 空间 id + */ + private Long spaceId; + /** * 审核状态:0-待审核; 1-通过; 2-拒绝 */ diff --git a/src/main/java/edu/whut/smilepicturebackend/model/entity/Space.java b/src/main/java/edu/whut/smilepicturebackend/model/entity/Space.java new file mode 100644 index 0000000..d265782 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/entity/Space.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/enums/SpaceLevelEnum.java b/src/main/java/edu/whut/smilepicturebackend/model/enums/SpaceLevelEnum.java new file mode 100644 index 0000000..9f64827 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/enums/SpaceLevelEnum.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/vo/SpaceVO.java b/src/main/java/edu/whut/smilepicturebackend/model/vo/SpaceVO.java new file mode 100644 index 0000000..d519e70 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/vo/SpaceVO.java @@ -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 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java b/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java index 75b427e..6e09b23 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java @@ -118,4 +118,6 @@ public interface PictureService extends IService { * @param oldPicture */ void clearPictureFile(Picture oldPicture); + + void checkPictureAuth(User loginUser, Picture picture); } diff --git a/src/main/java/edu/whut/smilepicturebackend/service/SpaceService.java b/src/main/java/edu/whut/smilepicturebackend/service/SpaceService.java new file mode 100644 index 0000000..ac547c9 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/service/SpaceService.java @@ -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 { + /** + * 创建空间 + * + * @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 getSpaceVOPage(Page spacePage, HttpServletRequest request); + + /** + * 获取查询对象 + * + * @param spaceQueryRequest + * @return + */ + LambdaQueryWrapper getQueryWrapper(SpaceQueryRequest spaceQueryRequest); + + /** + * 根据空间级别填充空间对象 + * + * @param space + */ + void fillSpaceBySpaceLevel(Space space); + + /** + * 校验空间权限 + * + * @param loginUser + * @param space + */ + void checkSpaceAuth(User loginUser, Space space); +} diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java index fe4393f..c997e20 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java @@ -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 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 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 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 ) ); picture.setUserId(loginUser.getId()); + picture.setSpaceId(spaceId); // 补充审核参数 this.fillReviewParams(picture, loginUser); // 操作数据库 @@ -204,6 +234,8 @@ public class PictureServiceImpl extends ServiceImpl && !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 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 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 // 删除缩略图 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); + } + } + } } diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java new file mode 100644 index 0000000..c998b8d --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java @@ -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 + implements SpaceService { + private final UserService userService; + // 静态锁表,JVM 级别共享 + private static final ConcurrentHashMap 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 getSpaceVOPage(Page spacePage, HttpServletRequest request) { + List spaceList = spacePage.getRecords(); + Page spaceVOPage = new Page<>(spacePage.getCurrent(), spacePage.getSize(), spacePage.getTotal()); + if (CollUtil.isEmpty(spaceList)) { + return spaceVOPage; + } + // 对象列表 => 封装对象列表 + List spaceVOList = spaceList.stream() + .map(SpaceVO::objToVo) + .collect(Collectors.toList()); + // 1. 关联查询用户信息 + // 1,2,3,4 + Set userIdSet = spaceList.stream().map(Space::getUserId).collect(Collectors.toSet()); + // 1 => user1, 2 => user2 + Map> 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 getQueryWrapper(SpaceQueryRequest req) { + if (req == null) { + // 请求参数为空时,抛出异常或根据业务自行处理 + throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); + } + // 创建 LambdaQueryWrapper + LambdaQueryWrapper 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); + } + } +} + + + + diff --git a/src/main/resources/mapper/PictureMapper.xml b/src/main/resources/mapper/PictureMapper.xml index 9a1c003..05bef8a 100644 --- a/src/main/resources/mapper/PictureMapper.xml +++ b/src/main/resources/mapper/PictureMapper.xml @@ -19,18 +19,24 @@ + + + + + - 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 diff --git a/src/main/resources/mapper/SpaceMapper.xml b/src/main/resources/mapper/SpaceMapper.xml new file mode 100644 index 0000000..87e72fa --- /dev/null +++ b/src/main/resources/mapper/SpaceMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + id,space_name,space_level, + max_size,max_count,total_size, + total_count,user_id,create_time, + edit_time,update_time,is_delete + +