智能协同云图库
约 1434 字大约 5 分钟
2026-04-04
项目地址
编程导航:https://www.codefather.cn/course/1864210260732116994
学习要点
用户登录、注册、注销流程

// 4. 保存用户的登录态
request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
// 移除登录态
request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);用户权限控制
/**
* 用户角色枚举
*/
@Getter
public enum UserRoleEnum {
USER("用户", "user"),
VIP("会员", "vip"),
ADMIN("管理员", "admin");
private final String text;
private final String value;
UserRoleEnum(String text, String value) {
this.text = text;
this.value = value;
}
/**
* 根据 value 获取枚举
*
* @param value 枚举值的 value
* @return 枚举值
*/
public static UserRoleEnum getEnumByValue(String value) {
if (ObjUtil.isEmpty(value)) {
return null;
}
for (UserRoleEnum userRoleEnum : UserRoleEnum.values()) {
if (userRoleEnum.value.equals(value)) {
return userRoleEnum;
}
}
return null;
}
}
@Aspect
@Component
public class AuthInterceptor {
@Resource
private UserService userService;
/**
* 执行拦截
*
* @param joinPoint 切入点
* @param authCheck 权限校验注解
*/
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
String mustRole = authCheck.mustRole();
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 获取当前登录用户
User loginUser = userService.getLoginUser(request);
UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
// 如果不需要权限,放行
if (mustRoleEnum == null) {
return joinPoint.proceed();
}
// 以下的代码:必须有权限,才会通过
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
if (userRoleEnum == null) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 要求必须有管理员权限,但用户没有管理员权限,拒绝
if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 通过权限校验,放行
return joinPoint.proceed();
}
}图片上传
//文件形式上传
@RequestPart("file") MultipartFile multipartFile,
//url地址上传
String fileUrl = pictureUploadRequest.getFileUrl();图片上传压缩、缩略图
/**
* 上传对象(附带图片信息)
*
* @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);
// 图片处理规则列表
List<PicOperations.Rule> rules = new ArrayList<>();
// 1. 图片压缩(转成 webp 格式)
String webpKey = FileUtil.mainName(key) + ".webp";
PicOperations.Rule compressRule = new PicOperations.Rule();
compressRule.setFileId(webpKey);
compressRule.setBucket(cosClientConfig.getBucket());
compressRule.setRule("imageMogr2/format/webp");
rules.add(compressRule);
// 2. 缩略图处理,仅对 > 20 KB 的图片生成缩略图
if (file.length() > 2 * 1024) {
PicOperations.Rule thumbnailRule = new PicOperations.Rule();
// 拼接缩略图的路径
String thumbnailKey = FileUtil.mainName(key) + "_thumbnail." + FileUtil.getSuffix(key);
thumbnailRule.setFileId(thumbnailKey);
thumbnailRule.setBucket(cosClientConfig.getBucket());
// 缩放规则 /thumbnail/<Width>x<Height>>(如果大于原图宽高,则不处理)
thumbnailRule.setRule(String.format("imageMogr2/thumbnail/%sx%s>", 256, 256));
rules.add(thumbnailRule);
}
// 构造处理参数
picOperations.setRules(rules);
putObjectRequest.setPicOperations(picOperations);
return cosClient.putObject(putObjectRequest);
}
/**
* 上传图片
*
* @param inputSource 文件
* @param uploadPathPrefix 上传路径前缀
* @return
*/
public UploadPictureResult uploadPicture(Object inputSource, String uploadPathPrefix) {
// 1. 校验图片
validPicture(inputSource);
// 2. 图片上传地址
String uuid = RandomUtil.randomString(16);
String originalFilename = getOriginFilename(inputSource);
// 自己拼接文件上传路径,而不是使用原始文件名称,可以增强安全性
String uploadFilename = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid,
FileUtil.getSuffix(originalFilename));
String uploadPath = String.format("/%s/%s", uploadPathPrefix, uploadFilename);
File file = null;
try {
// 3. 创建临时文件,获取文件到服务器
file = File.createTempFile(uploadPath, null);
// 处理文件来源
processFile(inputSource, file);
// 4. 上传图片到对象存储
PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);
// 5. 获取图片信息对象,封装返回结果
ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();
// 获取到图片处理结果
ProcessResults processResults = putObjectResult.getCiUploadResult().getProcessResults();
List<CIObject> objectList = processResults.getObjectList();
if (CollUtil.isNotEmpty(objectList)) {
// 获取压缩之后得到的文件信息
CIObject compressedCiObject = objectList.get(0);
// 缩略图默认等于压缩图
CIObject thumbnailCiObject = compressedCiObject;
// 有生成缩略图,才获取缩略图
if (objectList.size() > 1) {
thumbnailCiObject = objectList.get(1);
}
// 封装压缩图的返回结果
return buildResult(originalFilename, compressedCiObject, thumbnailCiObject, imageInfo);
}
return buildResult(originalFilename, file, uploadPath, imageInfo);
} catch (Exception e) {
log.error("图片上传到对象存储失败", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
} finally {
// 6. 临时文件清理
this.deleteTempFile(file);
}
}图片秒传
通过MD5、sha256方式生成文件唯一指纹,对于大文件有用
查询(多级缓存)
/**
* 分页获取图片列表(封装类,有缓存)
*/
@Deprecated
@PostMapping("/list/page/vo/cache")
public BaseResponse<Page<PictureVO>> listPictureVOByPageWithCache(@RequestBody PictureQueryRequest pictureQueryRequest,
HttpServletRequest request) {
long current = pictureQueryRequest.getCurrent();
long size = pictureQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
// 普通用户默认只能看到审核通过的数据
pictureQueryRequest.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
// 查询缓存,缓存中没有,再查询数据库
// 构建缓存的 key
String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());
String cacheKey = String.format("yupicture:listPictureVOByPage:%s", hashKey);
// 1. 先从本地缓存中查询
String cachedValue = LOCAL_CACHE.getIfPresent(cacheKey);
if (cachedValue != null) {
// 如果缓存命中,返回结果
Page<PictureVO> cachedPage = JSONUtil.toBean(cachedValue, Page.class);
return ResultUtils.success(cachedPage);
}
// 2. 本地缓存未命中,查询 Redis 分布式缓存
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
cachedValue = opsForValue.get(cacheKey);
if (cachedValue != null) {
// 如果缓存命中,更新本地缓存,返回结果
LOCAL_CACHE.put(cacheKey, cachedValue);
Page<PictureVO> cachedPage = JSONUtil.toBean(cachedValue, Page.class);
return ResultUtils.success(cachedPage);
}
// 3. 查询数据库
Page<Picture> picturePage = pictureService.page(new Page<>(current, size),
pictureService.getQueryWrapper(pictureQueryRequest));
Page<PictureVO> pictureVOPage = pictureService.getPictureVOPage(picturePage, request);
// 4. 更新缓存
// 更新 Redis 缓存
String cacheValue = JSONUtil.toJsonStr(pictureVOPage);
// 设置缓存的过期时间,5 - 10 分钟过期,防止缓存雪崩
int cacheExpireTime = 300 + RandomUtil.randomInt(0, 300);
opsForValue.set(cacheKey, cacheValue, cacheExpireTime, TimeUnit.SECONDS);
// 写入本地缓存
LOCAL_CACHE.put(cacheKey, cacheValue);
// 获取封装类
return ResultUtils.success(pictureVOPage);
}图片编辑
GitHub - xyxiao001/vue-cropper: A simple picture clipping plugin for vue
空间管理
私有空间权限控制、限额
图片搜索
基础属性搜索
关键词、标签、分类
编辑时间
图片名称、宽度、高度、格式以图搜图
颜色搜索


图片分享
复制链接
排版 Typography - Ant Design Vue
移动端扫码分享
成员邀请和空间成员管理
RBAC权限控制
一个用户多个角色,一个角色多个权限
空间权限控制(sa-token)
数据管理(分库分表 sharding)
协同编辑(websocket)
贡献者
更新日志
2026/4/5 03:39
查看所有更新日志
fb8bc-更新为vuepress于