智能协同云图库改进方案

云图库todo

待分类(未解决)

1.搜索标签&分类

查数据库,或者redis

2.点赞量(收藏)、下载量

没接触过点赞系统(后续学一下搞)

收藏怎么搞,表?pictureid userid?


前端优化

1.删除后不能返回创建页面 ✅

js
复制代码
if (res.data.code === 0) { message.success('删除成功') // 删除成功返回 主页 router.push({ path: '/' }) } else { message.error('删除失败') }

2.优化展示图片(首页、空间等等)✅

js
复制代码
<img :alt="picture.name" :src="picture.thumbnailUrl ?? picture.url" style="height: 180px; object-fit: contain" loading="lazy" />

3.多个标签省略代替后面的✅

js
复制代码
<a-tag v-for="tag in picture.tags.length <= 1 ? picture.tags : picture.tags.slice(0, 1)" :key="tag" > {{ tag }} </a-tag> <a-tag v-if="picture.tags.length > 1"> ... </a-tag>

4.编辑个人信息后需要刷新才能显示信息✅

save 方法成功保存后,手动重新获取用户信息:

text
复制代码
const save = async () => { if ( editableData.value.userName === loginUserStore.loginUser.userName && editableData.value.userAccount === loginUserStore.loginUser.userAccount && editableData.value.userProfile === loginUserStore.loginUser.userProfile && editableData.value.userAvatar === loginUserStore.loginUser.userAvatar ) { message.info('没有修改任何内容,无需保存') isEditing.value = false return } try { const response = await editUserUsingPost(editableData.value) if (response.data.code === 0) { message.success('保存成功') isEditing.value = false await fetchLoginUser() // 重新获取用户信息 } else { message.error(`保存失败: ${response.data.message}`) } } catch (error) { console.error('保存出错:', error) message.error('保存失败,请稍后重试') } }

后端优化

1. 编辑跨域 ✅

本服务与腾讯cos,腾讯云平台进行配置解决

2. 用户默认头像✅

后台设置

java
复制代码
user.setUserPassword("http://cloudimgs.huifly.cn/favicon.ico"); // 默认头像

前后端联调

1. 超级管理员 ✅

用户管理,只有超级管理员才能设置管理员、普通用户,并且不能删除超级管理员

管理员只能管理普通用户(其他管理类似,不再操作了)

前端

js
复制代码
<!-- 编辑用户表单 --> <a-modal v-model:visible="isEditModalVisible" title="编辑用户" @ok="submitEditForm" @cancel="cancelEdit" > <a-form :model="editForm" :label-col="{ span: 5 }" :wrapper-col="{ span: 15 }"> <a-form-item label="用户名"> <a-input v-model:value="editForm.userName" placeholder="请输入用户名" /> </a-form-item> <a-form-item label="头像"> <a-input v-model:value="editForm.userAvatar" placeholder="请输入头像链接" /> </a-form-item> <a-form-item label="简介"> <a-textarea v-model:value="editForm.userProfile" placeholder="请输入用户简介" /> </a-form-item> <a-form-item label="角色" v-if="loginUserStore.loginUser.userRole !== 'user' && !isEditingSelf" > <a-select v-model:value="editForm.userRole" :disabled="loginUserStore.loginUser.userRole !== 'su_admin' && editForm.userRole === 'admin'" > <a-select-option value="admin">管理员</a-select-option> <a-select-option value="user">普通用户</a-select-option> </a-select> </a-form-item> </a-form> </a-modal> const submitEditForm = async () => { try { const apiMethod = loginUserStore.loginUser.userRole === "superAdmin" ? updateRootUserUsingPost : updateUserUsingPost; const res = await apiMethod(editForm.value); if (res.data.code === 0) { message.success("用户信息更新成功"); isEditModalVisible.value = false; fetchData(); } else { message.error("更新失败:" + res.data.message); } } catch (error) { console.error(error); message.error( "请求失败,请检查网络连接或稍后再试:" + (error.response?.data?.message || error.message) ); } };

后端

java
复制代码
/** * root更新用户 */ @PostMapping("/root/update") @AuthCheck(mustRole = UserConstant.SU_ADMIN_ROLE) public BaseResponse<Boolean> updateRootUser(@RequestBody UserUpdateAdminRequest userUpdateAdminRequest) { if (userUpdateAdminRequest == null || userUpdateAdminRequest.getId() == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 检查 userRole 是否合法 String userRole = userUpdateAdminRequest.getUserRole(); if (userRole != null && !Objects.equals(userRole, "user") && !Objects.equals(userRole, "admin")) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "Invalid user role. Allowed values are 'user' or 'admin'."); } User user = new User(); BeanUtils.copyProperties(userUpdateAdminRequest, user); boolean result = userService.updateById(user); ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); return ResultUtils.success(true); } /** * 更新用户 */ @PostMapping("/update") @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) public BaseResponse<Boolean> updateUser(@RequestBody UserUpdateRequest userUpdateRequest) { if (userUpdateRequest == null || userUpdateRequest.getId() == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 检查 userRole 是否合法 String userRole = userUpdateRequest.getUserRole(); if (userRole != null && !Objects.equals(userRole, "user")) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "Invalid user role. Allowed values are 'user'."); } User user = new User(); BeanUtils.copyProperties(userUpdateRequest, user); boolean result = userService.updateById(user); ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); return ResultUtils.success(true); }

2. 会员兑换码

之后看教程搞一下吧

3.邮箱校验登录 ✅

以腾讯QQ邮箱为例

开启SMTP服务

  1. 登录QQ邮箱:进入QQ邮箱的网页端,使用您的QQ账号和密码登录。
  2. 进入设置:点击右上角的“设置”按钮,然后选择“帐户”。
  3. 开启SMTP服务:在“帐户”页面中,找到“POP3/SMTP服务”和“IMAP/SMTP服务”项,点击“开启”。
  4. 生成授权码:开启服务后,点击“生成授权码”。这个授权码将作为邮箱的身份认证密码。

邮件发送后端

java
复制代码
package com.lihui.picturebackend.utils; import org.apache.commons.mail.Email; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.SimpleEmail; import java.util.logging.Level; import java.util.logging.Logger; /** * 验证获取操作安全证书 */ public class CheckCodeUtils { // 常量配置,可根据实际情况修改 private static final String SMTP_HOST = "smtp.qq.com"; private static final String SENDER_EMAIL = "xxxxxxx@qq.com"; private static final String SENDER_AUTH_CODE = "xxxxxxxxx"; private static final String SENDER_NAME = "lihuibear"; private static final String EMAIL_SUBJECT = "注册验证码"; private static final int SSL_SMTP_PORT = 465; private static final Logger LOGGER = Logger.getLogger(CheckCodeUtils.class.getName()); /** * 发送邮件代码 * * @param targetEmail 目标用户邮箱 * @param authCode 发送的验证码 */ public static String getEmailCode(String targetEmail, String authCode) { try { // 创建邮箱对象 Email mail = new SimpleEmail(); // 设置发送邮件的服务器 mail.setHostName(SMTP_HOST); // 设置邮箱认证信息 mail.setAuthentication(SENDER_EMAIL, SENDER_AUTH_CODE); // 设置发件人信息 mail.setFrom(SENDER_EMAIL, SENDER_NAME); // 设置发送服务端口 mail.setSslSmtpPort(String.valueOf(SSL_SMTP_PORT)); // 使用安全链接 mail.setSSLOnConnect(true); System.setProperty("mail.smtp.ssl.enable", "true"); System.setProperty("mail.smtp.ssl.protocols", "TLSv1.2"); // 接收用户的邮箱 mail.addTo(targetEmail); // 邮件的主题(标题) mail.setSubject(EMAIL_SUBJECT); // 邮件的内容 mail.setMsg("【慧·图】您的验证码为:" + authCode + "(5分钟内有效)"); // 发送 mail.send(); return "发送成功,请注意查收"; } catch (EmailException e) { LOGGER.log(Level.SEVERE, "邮件发送失败,目标邮箱: " + targetEmail, e); return "邮件发送失败,请稍后重试"; } } }
java
复制代码
package com.lihui.picturebackend.controller; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.lihui.picturebackend.utils.CheckCodeUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.DigestUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.Random; import java.util.concurrent.TimeUnit; @Slf4j @RestController @RequestMapping("/mail") public class SendMailController { @Resource private RedisTemplate<String, String> redisTemplate; private static final Cache<String, String> LOCAL_CACHE = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.MINUTES) .build(); /** * 发送验证码 */ @GetMapping("/getCode") public String mail(@RequestParam("targetEmail") String targetEmail) { String cleanEmail = targetEmail.trim(); if (!isValidEmail(cleanEmail)) { return "邮箱格式错误"; } String hashKey = DigestUtils.md5DigestAsHex(cleanEmail.getBytes()); String cacheKey = "picture:sendMailCode:" + hashKey; // 防止频繁请求 if (LOCAL_CACHE.getIfPresent(cacheKey) != null) { return "请勿重复发送验证码"; } // 查询 Redis 是否已有验证码 if (redisTemplate.opsForValue().get(cacheKey) != null) { LOCAL_CACHE.put(cacheKey, "1"); return "请勿重复发送验证码"; } // 生成 6 位验证码 int authNum = new Random().nextInt(899999) + 100000; String authCode = String.valueOf(authNum); // 发送邮件 CheckCodeUtils.getEmailCode(cleanEmail, "你的验证码为: " + authCode + " (五分钟内有效)"); // 存入 Redis (5 分钟) redisTemplate.opsForValue().set(cacheKey, authCode, 5, TimeUnit.MINUTES); // 存入本地缓存 (1 分钟防刷) LOCAL_CACHE.put(cacheKey, "1"); return "发送成功"; } /** * 校验邮箱格式 */ private static boolean isValidEmail(String email) { return email.matches("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"); } }

然后填上信息就能测试了!

注册接口更改

UserRegisterRequest、User、UserMapper等需要加字段userEmail

text
复制代码
@PostMapping("/register") public BaseResponse<Long> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) { ThrowUtils.throwIf(userRegisterRequest == null, ErrorCode.PARAMS_ERROR); String userAccount = userRegisterRequest.getUserAccount(); String userPassword = userRegisterRequest.getUserPassword(); String checkPassword = userRegisterRequest.getCheckPassword(); String userEmail = userRegisterRequest.getUserEmail(); String verifyCode = userRegisterRequest.getVerifyCode(); //输入的验证码 long result = userService.userRegister(userAccount, userPassword, checkPassword, userEmail, verifyCode); return ResultUtils.success(result); } ------------ /** * @param userAccount 用户账户 * @param userPassword 用户密码 * @param checkPassword 校验密码 * @return */ @Override public long userRegister(String userAccount, String userPassword, String checkPassword, String userEmail, String verifyCode) { // 1. 校验基本参数 if (StrUtil.hasBlank(userAccount, userPassword, checkPassword, userEmail, verifyCode)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); } if (userAccount.length() < 4) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短"); } if (userPassword.length() < 8 || checkPassword.length() < 8) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短"); } if (!userPassword.equals(checkPassword)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致"); } if (!isValidEmail(userEmail)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式不正确"); } // 2. 校验验证码 String hashKey = DigestUtils.md5DigestAsHex(userEmail.trim().getBytes()); String cacheKey = "picture:sendMailCode:" + hashKey; String redisCode = redisTemplate.opsForValue().get(cacheKey); if (redisCode == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码已过期,请重新获取"); } if (!redisCode.equalsIgnoreCase(verifyCode)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误"); } // 3. 检查用户账号是否重复 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("userAccount", userAccount); long count = this.baseMapper.selectCount(queryWrapper); if (count > 0) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复"); } // 4. 加密密码 String encryptPassword = getEncryptPassword(userPassword); // 5. 插入用户数据 User user = new User(); user.setUserAccount(userAccount); user.setUserPassword(encryptPassword); user.setUserName("无名"); user.setUserRole(UserRoleEnum.USER.getValue()); user.setUserEmail(userEmail); user.setUserAvatar("http://cloudimgs.huifly.cn/favicon.ico"); // 默认头像 boolean saveResult = this.save(user); if (!saveResult) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误"); } // 6. 删除验证码 redisTemplate.delete(cacheKey); return user.getId(); } /** * 校验邮箱格式 */ private static boolean isValidEmail(String email) { return email.matches("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"); }

前端修改

本人不太会前端,基本上ai改的

vue
复制代码
<template> <div id="userRegisterPage"> <h2 class="title">云图库 - 用户注册</h2> <div class="desc">企业级智能协同云图库</div> <a-form :model="formState" name="basic" label-align="left" autocomplete="off" @finish="handleSubmit"> <a-form-item name="userAccount" :rules="[{ required: true, message: '请输入账号' }]"> <a-input v-model:value="formState.userAccount" placeholder="请输入账号" /> </a-form-item> <a-form-item name="userEmail" :rules="[{ required: true, message: '请输入邮箱' }, { type: 'email', message: '请输入正确的邮箱格式' }]" > <a-input v-model:value="formState.userEmail" placeholder="请输入邮箱" /> </a-form-item> <a-form-item name="verifyCode" :rules="[{ required: true, message: '请输入验证码' }]"> <div style="display: flex; gap: 8px"> <a-input v-model:value="formState.verifyCode" placeholder="请输入验证码" /> <a-button @click="getverifyCode" :disabled="isButtonDisabled">{{ buttonText }}</a-button> </div> </a-form-item> <a-form-item name="userPassword" :rules="[{ required: true, message: '请输入密码' }, { min: 8, message: '密码不能小于 8 位' }]" > <a-input-password v-model:value="formState.userPassword" placeholder="请输入密码" /> </a-form-item> <a-form-item name="checkPassword" :rules="[{ required: true, message: '请输入确认密码' }, { min: 8, message: '确认密码不能小于 8 位' }]" > <a-input-password v-model:value="formState.checkPassword" placeholder="请输入确认密码" /> </a-form-item> <div class="tips"> 已有账号? <RouterLink to="/user/login">去登录</RouterLink> </div> <a-form-item> <a-button type="primary" html-type="submit" style="width: 100%">注册</a-button> </a-form-item> </a-form> </div> </template> <script setup lang="ts"> import { message } from 'ant-design-vue' import { userRegisterUsingPost } from '@/api/userController.ts' import { reactive, ref } from 'vue' import { useRouter } from 'vue-router' import { mailUsingGet } from '@/api/sendMailController.ts' const formState = reactive<API.UserRegisterRequest>({ userAccount: '', userEmail: '', verifyCode: '', userPassword: '', checkPassword: '', }) const router = useRouter() const isButtonDisabled = ref(false) const buttonText = ref('获取验证码') let timer: number | null = null const getverifyCode = async () => { if (!formState.userEmail) { message.warning('请先输入邮箱') return } isButtonDisabled.value = true buttonText.value = '请 60 秒后再重试' try { // 确保邮箱地址没有二次编码,直接传递原始邮箱 const encodedEmail = formState.userEmail // 不做多余的 encodeURIComponent 编码 // 使用 mailUsingGet 来发送邮件请求,传递正确的邮箱地址 const response = await mailUsingGet({ targetEmail: encodedEmail }) if (response) { message.success('验证码已发送,请查收邮箱') } else { throw new Error('验证码发送失败') } } catch (error) { message.error(error.message || '获取验证码失败') isButtonDisabled.value = false buttonText.value = '获取验证码' return } let second = 60 const countdown = () => { if (second === 0) { buttonText.value = '获取验证码' isButtonDisabled.value = false return } buttonText.value = `请 ${second} 秒后再重试` second-- timer = window.setTimeout(countdown, 1000) } countdown() } const handleSubmit = async () => { if (formState.userPassword !== formState.checkPassword) { message.error('两次输入的密码不一致') return } try { const res = await userRegisterUsingPost({ userAccount: formState.userAccount, userEmail: formState.userEmail, verifyCode: formState.verifyCode, userPassword: formState.userPassword, checkPassword: formState.checkPassword, }) if (res.data.code === 0) { message.success('注册成功') router.push('/user/login') } else { message.error(`注册失败:${res.data.message}`) } } catch (error) { message.error('注册请求失败,请稍后重试') } } </script> <style scoped> #userRegisterPage { max-width: 360px; margin: 0 auto; } .title { text-align: center; margin-bottom: 16px; } .desc { text-align: center; color: #bbb; margin-bottom: 16px; } .tips { margin-bottom: 16px; color: #bbb; font-size: 13px; text-align: right; } </style>

4.批量传图输入分类 ✅

可自定义分类 ❌

多传一个参,不多写了

5.超级管理员引发的其他问题

权限如下:

超级管理员 > 管理员 > 普通用户

改了用户管理,发现

  1. 拉取图片,无法超级管理员自动通过
  2. 超级管理员其他管理无权限

解决1✅

text
复制代码
if (userService.isAdmin(loginUser)) { // 管理员自动过审 picture.setReviewStatus(PictureReviewStatusEnum.PASS.getValue()); picture.setReviewerId(loginUser.getId()); picture.setReviewMessage("管理员自动过审"); picture.setReviewTime(new Date()); } if (userService.isSUAdmin(loginUser)) { // 管理员自动过审 picture.setReviewStatus(PictureReviewStatusEnum.PASS.getValue()); picture.setReviewerId(loginUser.getId()); picture.setReviewMessage("超级管理员自动过审"); picture.setReviewTime(new Date()); }

解决2✅

java
复制代码
package com.lihui.picturebackend.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthCheck { /** * 必须有某个角色 */ String mustRole() default ""; /** * 需要的多个权限(满足其中一个即可) */ String[] mustRoles() default {}; } ------------- package com.lihui.picturebackend.aop; import com.lihui.picturebackend.annotation.AuthCheck; import com.lihui.picturebackend.exception.BusinessException; import com.lihui.picturebackend.exception.ErrorCode; import com.lihui.picturebackend.model.entity.User; import com.lihui.picturebackend.model.enums.UserRoleEnum; import com.lihui.picturebackend.service.UserService; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @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(); String[] mustRoles = authCheck.mustRoles(); // 新增 mustRoles RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); // 当前登录用户 User loginUser = userService.getLoginUser(request); UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole()); // 没有用户角色,直接拒绝访问 if (userRoleEnum == null) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 如果 mustRole 存在,保持原逻辑 if (mustRole != null && !mustRole.isEmpty()) { UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole); if (mustRoleEnum != null && !mustRoleEnum.equals(userRoleEnum)) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } return joinPoint.proceed(); } // 新增逻辑:如果 mustRoles 存在,用户必须属于其中之一 if (mustRoles != null && mustRoles.length > 0) { List<UserRoleEnum> requiredRoles = Arrays.stream(mustRoles) .map(UserRoleEnum::getEnumByValue) .collect(Collectors.toList()); if (!requiredRoles.contains(userRoleEnum)) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } return joinPoint.proceed(); } // 没有权限要求,直接放行 return joinPoint.proceed(); } }

然后 超级管理员

text
复制代码
@AuthCheck(mustRole = UserConstant.SU_ADMIN_ROLE)

管理员

text
复制代码
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)

都有权限的

text
复制代码
@AuthCheck(mustRoles = {UserConstant.ADMIN_ROLE, UserConstant.SU_ADMIN_ROLE})

sa-token对应也改

text
复制代码
List<String> SU_ADMIN_PERMISSIONS = getPermissionsByRole(SpaceRoleEnum.SU_ADMIN.getValue()); // 公共图库 if (space == null) { if (userService.isAdmin(loginUser)) { return ADMIN_PERMISSIONS; } if (userService.isSUAdmin(loginUser)) { return SU_ADMIN_PERMISSIONS; }

安全性

1.防盗链 ✅

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
lihuibear
下载 APP