智能协同云图库项目面试题 - 智能协同云图库项目教程 - 编程导航教程 后端请介绍整个云图库项目后端的架构设计,有哪些模块以及各模块之间的关系?整个云图库项目的后端拆分为以下几个核心模块:用户模块:提供用户注册、登录、鉴权等功能,利用 Redis 实现分布式 Sessio。

后端

请介绍整个云图库项目后端的架构设计,有哪些模块以及各模块之间的关系?

整个云图库项目的后端拆分为以下几个核心模块:

  • 用户模块:提供用户注册、登录、鉴权等功能,利用 Redis 实现分布式 Session 登录。
  • 图片模块:负责图片增删改查、上传、多维搜索、批量编辑、批量抓图等操作,并结合 COS 对象存储完成图片管理,结合数据万象完成图片解析、压缩和缩略图生成,
  • 空间模块:支持创建私有空间和团队空间,并提供空间成员管理、空间额度控制、空间图片共享、多人协同编辑等功能。基于 Sa-Token 实现 RBAC 权限模型,可灵活控制空间成员权限。
  • AI 模块:通过自主封装的 AI 接口,对接 AI 绘画大模型实现 AI 扩图功能,支持异步任务轮询。
  • 协作模块:基于 WebSocket 实现多人实时编辑图片,利用 “编辑锁” 及事件驱动设计降低并发冲突风险,并采用 Disruptor 技术提升吞吐量。
  • 分析模块:对空间使用情况、图片分类、空间排行等进行统计,基于 MyBatis Plus 和分组查询(group by)实现聚合分析。
  • 分表模块:对旗舰团队空间数据进行单独的分表管理,提升查询效率,基于 ShardingSphere 动态分表实现。
  • 缓存模块:引入 Redis + Caffeine 构建多级缓存体系,显著降低主页图片数据的查询耗时,同时通过随机过期时间避免缓存雪崩。

各模块之间的关系如下:

  • 用户登录后,通过权限模块判断其是否能访问对应的空间或图片数据
  • 图片上传与管理需要调用图片模块与 COS 对象存储服务
  • 空间模块与用户模块和图片模块关联,团队空间通过 space_user 关联表管理成员
  • AI 模块提供异步扩图能力,供图片模块或前端直接调用
  • 协作模块基于 WebSocket 来通知前端实时操作,实现多人在线编辑
  • 统计分析模块会调用图片、空间等模块的数据接口来生成统计报表
  • 缓存模块贯穿在各个模块的查询操作中,提高访问效率

你在云图库项目中是如何设计库表的?可以从字段、索引、关联等方面回答。

在云图库项目中,我根据业务逻辑与访问场景分析,共设计了几张关键表:用户表(user)、图片表(picture)、空间表(space)、空间成员表(space_user) 等。我在设计库表时会综合查询性能、存储空间、可读性、开发成本进行考虑,下面简单说明:

1)用户表(user)

  • userAccount 建立唯一索引:快速定位用户并避免重复注册
  • userPassword 采用 MD5 + 盐存储:保障密码安全
  • userRole 角色字段:支持 user / admin 两种枚举值
  • userName 建立普通索引:便于模糊检索昵称
  • isDelete:支持逻辑删除

2)图片表(picture)

  • url / thumbnailUrl:分别保存原图和缩略图地址,加载大图前先显示缩略图提升体验
  • name / introduction / category:添加索引,提高检索性能
  • tags:JSON 字符串格式存储,便于修改维护
  • reviewStatus:审核状态,用枚举值减少存储空间并提高查询性能
  • picSize / picWidth / picHeight / picFormat:存储图片详细信息,有助于后期统计分析
  • userId / spaceId:支持按用户或空间查询
  • updateTime / editTime:分开编辑时间和更新时间,有利于区分用户动作与系统自动更新

3)空间表(space)

  • spaceName / spaceLevel:建立索引提升常用查询性能
  • maxSize / maxCount:空间允许存储的最大容量与图片数,结合 totalSize / totalCount 方便监控剩余空间
  • spaceType:区分个人空间 / 企业空间
  • userId:标记该空间的创建人

4)空间成员表(space_user)

  • spaceId + userId:采用唯一索引 uk_spaceId_userId,保证同一用户在同一空间下只有一条成员记录
  • spaceRole:空间成员角色枚举 viewer / editor / admin
  • idx_spaceId / idx_userId:添加索引,提升按空间或按成员查询的性能

介绍一下你初始化云图库项目后端、打造后端模板的流程?

我在开始编写云图库项目后端时,先使用 Spring Initializr 在 IDE 中创建一个基础的 Spring Boot 项目,并在 pom.xml 文件中整合一些常用依赖,比如 Spring Web、MyBatis Plus、MySQL、Lombok、Knife4j 等,初步搭建起项目骨架。

接着会配置数据库连接(比如 application.yml 中指定 MySQL 地址与登录信息),并进行一些全局设置,如日志输出、逻辑删除、全局跨域配置等。然后,我会新建一个自定义异常类、全局异常处理器,以及统一的响应封装类,用来标准化接口返回格式。

最终,这些基础配置、依赖和统一处理逻辑就形成了一个 “后端项目模板”,后续在编写用户模块、图片模块、空间模块等业务功能时,我只需在这个模板之上扩展即可,避免了大量重复的初始化和基础代码编写工作。

💡 补充:如果你熟悉鱼皮提供的后端万用模板,还可以补充更多业务特性,比如提供 CRUD 操作、代码生成器、Redis 和 Elasticsearch 的整合等。

什么是 MyBatis Plus?你的云图库项目怎么使用它来实现数据库操作?

MyBatis Plus 是对 MyBatis 的增强工具,提供了丰富的 CRUD 接口、分页插件、动态查询构造器等,能大幅减少编写 SQL 的工作量并提升开发效率。

在我的云图库项目里,我首先在 pom.xml 中引入 MyBatis Plus 的依赖,配置了 @MapperScan 注解以扫描 mapper 包,之后就可以在代码中直接使用内置的 BaseMapper 方法(如 selectByIdinsert 等)快速实现数据增删改查。借助 MyBatis Plus 的 QueryWrapper 或 SqlRunner,还可以更灵活地构建动态查询。比如,我会在图片模块使用 lambdaQuery().eq(Picture::getSpaceId, spaceId) 的方式查询特定空间的图片列表。

为了进一步提高构建 MyBatis Plus 查询的效率,我封装了一些 getQueryWrapper 的方法,可以根据查询请求对象转化为 QueryWrapper。

你的云图库项目后端怎么处理异常?

为了统一管理后端异常,我在项目里封装了一个自定义异常类(BusinessException)、全局异常处理器(GlobalExceptionHandler)、并且自定义了一些错误码(ErrorCode),便于前端统一处理错误。

流程如下:

  1. 当遇到业务逻辑问题时,我会抛出 BusinessException,并传入相应的自定义错误码和错误信息。
  2. GlobalExceptionHandler 会捕获这个 BusinessException,并返回一个统一的响应结构给前端。
  3. 如果是未预料到的系统异常(RuntimeException),同样会被 GlobalExceptionHandler 拦截,返回固定的系统错误码(50000)和提示信息(系统内部异常)。

这样做可以让前端快速定位问题原因,也便于后续在不同场景下进行精细化的异常处理。

为什么你的云图库项目后端要统一封装响应类?你是如何实现的?

在项目开发中,接口可能会返回不同类型的结果,如果不统一格式,会给前后端对接带来混乱。为此,我自定义了一个 BaseResponse<T> 类,包含 code、data、message 字段,并提供 ResultUtils 工具类来快速生成成功或失败的响应。

举个例子,当接口正常执行时,我会使用 ResultUtils.success(data) 返回结果,如果出现业务异常或系统异常,则通过 ResultUtils.error(...) 构造标准化的错误信息。这样前端开发者只要解析 code、data、message,就能统一处理各种接口的响应,大幅提升了协作效率和可维护性。

什么是跨域问题?你的云图库项目如何在后端解决跨域?

跨域问题是指浏览器为了安全,对不同域(包含不同协议、不同端口或不同主机名)的请求进行限制,从而导致请求无法正常访问后端接口。

在云图库项目中,为了方便前后端分离部署与调试,我创建了一个全局跨域配置类(CorsConfig),在其中通过 registry.addMapping("/**") 等配置项,允许指定源的跨域请求,并支持发送 Cookie、自定义请求头等。这样一来,无论前端运行在哪个端口,都能正常请求后端,便于本地开发和后续部署上线。

什么是 Knife4j?你的云图库项目后端怎么使用它自动生成接口文档?

Knife4j 是基于 Swagger 的一款接口文档增强工具,提供了更友好的界面和丰富的文档管理功能,比如动态参数调试、接口分组等。

在我的云图库项目中,我首先在 pom.xml 中引入了 knife4j-openapi2-spring-boot-starter 依赖,然后在 application.yml 中配置了 Knife4j 扫描的包路径和接口文档信息。

在编写 Controller 接口时,我会使用 @ApiOperation 等注解描述接口和参数。启动项目后只需要访问 /doc.html,就能看到自动生成的可视化接口文档,前后端都能根据这份文档进行测试与对接,从而大幅提高开发效率。

为什么后端返回给前端的数据会出现精度丢失?你在云图库项目中是怎么解决的?

在云图库项目中,如果后端返回的主键 id(如 Long 类型)超过了 JavaScript 能正常表示的安全整数范围(2^53 - 1),前端在处理响应时就会出现数字精度丢失的问题。常见现象就是数字的末位被置为 0,从而导致前端拿到的数据与实际值不一致。

为了避免这种情况,我在后端的配置层中,对整个项目的 JSON 序列化做了一层自定义处理。通过编写一个全局的 Jackson 配置类,将所有返回给前端的 Long 类型转换为字符串进行输出。这样一来,前端接收到的长整型 id 就是一个字符串,就不会再出现因超出 JS 范围而丢失精度的情况。

云图库项目中,你是如何实现用户注册功能的?

在云图库项目的用户模块中,我对用户注册做了以下几步处理:

  1. 数据校验:对用户输入的账号、密码进行非空判断、长度判断等,如果不符合要求,则抛出自定义异常。
  2. 账号查重:利用 MyBatis Plus 构造查询条件,在 user 表中检测是否已有相同的 userAccount,如果存在则视为重复注册。
  3. 密码加密:对密码进行 MD5 + 盐值(比如 “yupi”)的加密后再存入数据库,防止明文密码泄露。
  4. 数据插入:以上都通过后才执行插入操作,将用户信息保存到 user 表里。若数据库插入失败,则抛出异常提示。

前端发起的注册请求,会映射到 UserController 中的 /register 接口,控制层做完参数判空后就交给 UserService 的 userRegister 方法来处理。注册成功后,会返回新用户的 id 给前端确认。

云图库项目中,你是如何实现用户登录功能的?

在云图库项目中,我采用了基于 Session 的登录态管理实现用户登录,实现步骤如下:

  1. 校验参数:检查账号、密码是否为空,长度是否合理。
  2. 密码对比:将前端传来的密码同样进行加密,并在数据库中查询匹配的记录。如果查无此用户或密码不匹配,就抛出异常。
  3. Session 存储:若验证通过,就在后端的 Session 中保存用户信息,比如 request.getSession().setAttribute(USER_LOGIN_STATE, user)
  4. 返回脱敏数据:由于用户表里还包括密码字段,为了安全起见,会对用户的密码进行脱敏处理后再返回登录成功信息给前端。

这样,当后续请求携带同一个 Session ID 时,后端即可根据 Session 判断当前用户的登录状态,从而进行权限判定。若 Session 中无登录态,则视为未登录。

云图库项目中,你是如何实现统一权限管理功能的?

云图库项目有普通用户、管理员 2 种角色,为了简化权限控制,我做了以下工作:

  1. 角色区分:在 user 表中通过 userRole 字段来区分 user 和 admin。
  2. 注解 + AOP:定义一个 @AuthCheck 注解,用于标注某个接口需要的角色;然后利用 Spring AOP 写了一个拦截器(环绕通知),在接口调用前自动校验当前登录用户是否拥有足够的权限。
  3. 使用注解:在需要管理员权限的接口上加一句 @AuthCheck(mustRole = "admin"),就能统一检查用户是否是管理员。若校验失败,就抛出无权限异常。

这样一来,权限管理逻辑不会分散在各种接口里,而是集中在一个切面类中维护,可读性高、扩展性也更好。若后期要增加新的权限等级或更细粒度的控制,也能在切面逻辑里统一加以实现。

云图库项目中,你如何存储图片数据?怎么获取到图片信息?

在云图库项目中,我选择使用对象存储来存储图片文件。一方面,借助云厂商(如腾讯云 COS)提供的对象存储可以快速解决海量图片的存储、传输、扩容和备份问题;另一方面,腾讯云对象存储还提供 “数据万象” 图像处理服务,可以在图片上传时自动解析并返回图片的尺寸、大小、格式等元数据信息。

具体实现流程:

1)项目中整合对象存储:

  • 在后端配置文件中写好存储桶(bucket)名称、访问域名、密钥等信息,编写一个配置类来读取参数并初始化客户端。
  • 初始化客户端后,我编写了一个通用的对象存储调用类(CosManager),通过 cosClient.putObject 方法实现上传文件到存储桶。
  • 基于对象存储调用类进一步封装了适用于本项目的文件上传管理类(FileManager),简化文件上传的调用。

2)自动获取图片信息:

  • 在上传图片时,启用 “数据万象” 功能,对图片进行基础信息解析。
  • 解析结果中包含宽度、高度、格式等字段,我再将这些信息写到数据库表的相应字段,便于后续查询与筛选。

这样一来,图片实际的二进制数据并不放在后端服务器或数据库中,而是托管给对象存储服务进行管理,既省去了服务器磁盘维护的繁琐,又能利用云存储高可用的特性,提升系统的稳定性和可扩展性。

云图库项目中,你如何实现图片审核功能?

为了避免用户上传的图片内容违规或存在版权风险,给图片模块增加了 “审核功能”。

整体策略包括:

  1. 在数据库中为图片表(picture)添加审核状态字段(reviewStatus)和审核信息(reviewMessage、reviewerId、reviewTime)。
  2. 用户上传或编辑图片时,图片默认状态为 “待审核” 或自动过审(如果是管理员上传);管理员审核后可将状态修改为 “通过” 或 “拒绝”。
  3. 对于审核状态为 “通过” 的图片,普通用户在查询时才能看到;未通过或拒绝的图片仅管理员可见。
  4. 管理员可随时修改图片的审核状态,同时记录审核人、审核时间及原因,方便后期追溯。

这样一来,既能保证图片内容的安全与合规,也能将违规图片拒之门外,提高系统整体质量。

此外,其实我还了解更多企业中的审核策略,比如:

  1. 内容安全审核服务:借助专业的第三方平台的内容审核服务来实现自动审核,像腾讯云、阿里云等基本都支持图片、文本、音视频等内容的审核。
  2. AI 审核:可以将文本内容和审核规则输入给 AI,让 AI 返回是否合规。
  3. 分级审核策略:区分普通用户与高信誉用户,高信誉用户可减少或免除审核流程,比如 VIP 用户自动过审,也可以提高部分效率。
  4. 实名信息和内容溯源:通过用户实名或者手机号注册,提高用户行为的责任感,减少垃圾内容的产生。
  5. 举报机制:通过给平台增加举报机制,还可以给举报行为一些奖励,让用户帮忙维护平台。

云图库项目中,你如何实现通过 URL 上传图片功能?

在云图库项目中,我不仅支持用户上传本地图片文件,也支持直接填写网络图片的 URL 链接,后端自动下载并导入到对象存储中。大致流程如下:

  1. 前端将用户输入的图片 URL 发送给后端。
  2. 后端对该链接进行校验:先用 HEAD 请求检查链接是否存在,并获取其文件大小、类型等元信息,而不是下载文件后再校验,可以节约流量。若检测到格式错误(非 JPEG、PNG 等)或文件过大(超过 2MB),则拒绝上传。
  3. 若链接校验通过,后端使用 Hutool 的 HttpUtil.downloadFile 将图片下载到本地临时文件。
  4. 将临时文件上传到对象存储服务,并利用数据万象接口解析图片宽高、大小、格式等元信息。
  5. 将解析到的信息与文件 URL 一起写入数据库,完成导入操作。

其实步骤 4 和 5 都可以复用我已经实现的本地图片上传功能,因此我也将两种文件上传方式使用模板方法设计模式重构,减少了重复代码,并提高了代码的可扩展性和可维护性。

什么是模板方法设计模式?在云图库项目中如何使用模板方法模式?

模板方法设计模式是一种行为型设计模式,它将某一通用流程的各个步骤抽象出来,并定义在父类的 “模板方法” 里,而将具体差异的实现细节留给子类来完成。简单来说,就是 “固定整体流程,允许子类覆盖部分步骤”。

在云图库项目中,我使用了模板方法模式来统一处理图片上传流程:

  1. 把图片上传的通用步骤(校验 -> 生成临时文件 -> 上传到对象存储 -> 解析图片信息 -> 删除临时文件)定义在抽象类 PictureUploadTemplate 中。
  2. 针对 “本地文件上传” 和 “URL 上传” 两种场景,我分别实现了不同的子类 FilePictureUploadUrlPictureUpload,只需要在 “校验图片”、“获取文件名”、“下载 / 处理文件” 这几个与输入源相关的步骤上实现不同的逻辑即可,其他步骤则在模板父类里复用。

这样,不仅减少了大量重复代码,也让后续扩展新的文件来源时更加容易 —— 只需继承抽象类,覆盖相关方法即可,实现了对扩展开放、对修改封闭的设计思路。

云图库项目中,你怎么实现网络图片的批量抓取和导入功能?

核心思路是:利用 Jsoup 实现 Bing 网页图片抓取,并将图片进行批量上传和入库。

具体步骤:

  1. 指定搜索关键词与抓取数量:管理员在前端界面输入想抓取的关键词(比如 “壁纸”),以及一次要抓取的图片数量。
  2. 爬取图片链接:后端根据关键词拼接目标 URL(Bing 图片搜索的地址),请求返回的 HTML 网页后使用 Jsoup 解析 DOM,获取所有图片的地址。
  3. 逐条下载并上传:对每个图片链接,先做必要的校验(如过滤多余参数),然后将其下载到本地临时文件,最终上传到对象存储服务中。
  4. 插入数据库记录:把每张成功上传的图片信息保存到 picture 表。如果需要分批次命名,可以加个 “名称前缀” 参数,让所有图片名称带上统一前缀。
  5. 容错:为防止爬取过快导致被封,通过 Thread.sleep 增加间隔;如果某张图片下载失败或不通过校验,系统会跳过继续下一张图片,最后统计成功条数。

通过这套批量抓取方案,管理员可一键快速引入大批外部图片,大大减少了前期项目冷启动时手动上传图片的工作量。

云图库项目中,你通过哪些策略对图片功能进行优化?比如性能和成本优化

为提升云图库的性能并降低成本,我对图片查询、上传、加载、存储等环节进行了多方位的优化。

1)图片查询优化:

  • 结合 Redis 和 Caffeine,构建了多级缓存体系,先从本地缓存查询,若未命中再查分布式缓存。这样既能充分利用内存的高速访问,又确保了分布式环境下的数据一致性。
  • 将高频访问的热点图片以及复杂查询结果直接缓存,减少对数据库的压力,成倍缩短了首页响应时间。

2)图片上传优化:

  • 在上传时利用数据万象服务自动将图片格式转换为 WebP 进行压缩,有效降低图片文件大小,节省带宽,提升加载速度。
  • 支持分片上传和断点续传,用于大文件场景,减少网络波动对上传体验的影响。我也尝试过实现文件秒传,通过对文件哈希(MD5)进行校验,避免重复上传。

3)图片加载优化:

  • 生成缩略图,在首页或列表页仅加载缩略图,用户查看图片详情时再加载清晰原图,大幅减少流量。
  • 利用 CDN 加速,将图片资源分配到各地 CDN 节点,用户就近访问,从而降低延迟;并开启浏览器缓存头,进一步提升重复访问的加载速度。
  • 懒加载技术,让页面只在图片真正出现在可视区域时再请求资源,降低一次性加载的压力。

4)图片存储优化:

  • 使用对象存储配合数据沉降(生命周期管理)策略,对 30 天未访问的图片自动切换至低频存储,降低存储费用。
  • 定期清理不再使用或重复的图片文件,减少冗余占用空间。

经过上述优化,系统整体的性能显著提升,同时有效地控制了存储与带宽成本。

什么是 Redis 和 Caffeine?你在云图库项目中如何利用它们构建多级缓存?

Redis 是一种高性能的分布式 KV 存储,支持丰富的数据结构和高并发访问。它能把缓存数据存储在内存中,单节点读写 QPS 可达 10 万,常用于做分布式缓存或分布式锁。

Caffeine 是 Java 主流的本地缓存库,运行在 JVM 内部,访问速度比 Redis 更快,但仅能在单个服务实例内使用,无法在多台服务器之间共享数据。

我在云图库项目中将二者结合,形成多级缓存:

  1. 用户请求先查询 Caffeine 本地缓存
  2. 若本地缓存未命中,则查询 Redis 缓存
  3. 若 Redis 也未命中,则回源到数据库,并将查询结果写回 Redis 与 Caffeine

流程如图:

这样既保证了本地缓存的高速访问,又利用 Redis 做到跨节点数据一致和高可用,提升性能的同时减少对数据库的重复查询。

云图库项目中,你如何实现图片转码压缩和缩略图生成?

主要是利用腾讯云的数据万象服务实现。

1)转码压缩:我在后端集成了云存储服务的数据万象功能。图片上传时,借助对象存储 SDK 给出图片处理规则,如格式转换为 WebP、调整质量等,从而大幅节省带宽、加速用户加载。

2)缩略图生成:同样在上传时,利用数据万象配置额外的 “缩放规则”,再输出一份小尺寸缩略图,并将缩略图地址和原图地址一同写入数据库。 用户在图片列表页只需加载体积很小的缩略图,点击进入详情页时再加载清晰度更高的主图,从而获得更快的页面首屏速度和更好的用户体验。

通过这两步,保证了图片在存储和网络层面都尽可能地 “瘦身”,又能满足用户的浏览需求。

什么是 CDN?云图库项目中如何利用 CDN 提升图片加载性能?

CDN 内容分发网络是通过将文件分发到全球或全国各地的加速节点,让用户就近从最近的节点访问资源,减少跨网络、跨地域带来的延迟,提高加载资源的速度。

在云图库项目里,我将 COS 对象存储作为 “源站”,再给图片地址配置一层 CDN,起到了这些作用:

  1. 请求拦截:用户访问图片时,先由 CDN 节点判断是否有缓存;若已缓存,则直接返回,加快响应;若无缓存,则回源到 COS 存储获取。
  2. 全局分发:CDN 在各地设有大量加速节点,用户与节点间的距离近、线路好,显著缩短 RTT 和传输时延。
  3. 保护源站:高并发时,绝大多数请求被 CDN 节点拦住,源站压力大幅下降。此外我还通过配置防盗链、带宽监控等策略防止资源滥用和 CDN 被刷量。

使用 CDN 后,我也用 F12 网络控制台看了下请求资源的加载时间,确实加载更快了,也更安全稳定。

什么是降频存储?在云图库项目中,它起到了什么作用?

降频存储指的是将长时间不访问或访问极少的文件从 “标准存储” 自动迁移到 “低频存储” 或 “归档存储” 的过程,降低资源占用成本。

COS 对象存储提供了 “生命周期管理” 功能,可以根据设定的规则自动识别并迁移不活跃的数据。

在云图库项目中,图片在上传初期可能访问量较高,但随着时间推移,不少图片会排到后面的分页中,几乎不会有用户访问。对这类 “冷数据” 图片设置降频存储后,日常占用的存储费用会更低,帮助我们显著节省成本。只有当用户重新访问这些冷数据时才会触发取回费用,但这种情况通常很少,所以我还是开启了降频存储策略。

什么是文件秒传?在云图库项目中,你怎么实现文件秒传?

文件秒传主要运用于大文件上传场景,是通过 “文件指纹” 来识别文件是否已上传过的一种技术。若已存在相同文件,就无需再次传输,直接复用之前的文件存储记录,大幅减少上传时间和流量消耗。

我的实现思路:

  1. 指纹计算:用户在上传前计算文件 MD5(或 SHA-256)哈希
  2. 指纹查重:后端检索数据库,看是否已有相同指纹的文件
  3. 复用记录:若找到相同文件,则跳过上传过程,直接返回之前的存储地址;若没找到,则正常上传并存储文件指纹。

在云图库项目中,图片文件不大,因此秒传的收益有限。但如果有用户可能频繁上传同一大文件,通过这种方式可减少重复上传,节省存储与带宽成本。

什么是文件分片上传和断点续传?在云图库项目中,你怎么实现它们?

1)文件分片上传:将一个大文件拆分成多个小块(分片),逐一上传到服务器。这样可以并发上传各分片,提高带宽利用率,同时若部分分片上传失败,仅需重传失败的分片,避免整体重传。

2)断点续传:在网络中断或上传过程中出现异常时,客户端可从已上传成功的分片或位置继续上传,而不必从头再来,实现更加稳定、高效的大文件上传。

在云图库项目中,我基于 COS 对象存储服务的 SDK 来实现分片上传与断点续传。只需在调用接口时指定分片参数,后端会自动管理各分片的合并与校验。若中途中断,下次上传请求带上已上传好的分片信息,继续传剩余分片即可,从而显著提升网络不稳定场景下的用户体验和上传效率。

云图库项目中,什么是空间?为什么需要 “空间” 的概念?

在云图库项目里,“空间” 相当于给用户或团队划分出的一块独立存储区域,用来存储私有或需要共享的图片。与公共图库相比,用户在自己创建的空间内对图片有更高的自主管理权限,比如无需管理员审核。同时,为了防止用户无限制占用存储资源,我给每个空间设置容量与图片数量上的额度限制。

如果没有 “空间” 的概念,可能只是在图片表里简单地通过 userId 区分用户的图片。但这样做难以实现以下场景:

  1. 与公共图库分离:公共图库无需限额也没有私密性,而私有图片往往不希望被他人或管理员随意操作,需要独立管控。
  2. 权限与限额管理:给用户分配多少存储量、能否升级到专业版、如何在用户超限时阻止上传等,这些都需要更灵活的处理,而 “空间” 充当了中间层。

因此,抽象出 “空间” 概念后,公共图库和私有空间互不干扰,也便于后续扩展团队空间和空间成员功能。

云图库项目中,你怎么保证用户最多只能创建一个空间?

我在云图库项目采用了 加锁 + 事务 的方式来限制用户仅能创建一个私有空间:

  1. 数据库设计:在 space 表中根据 userId 查询是否已创建空间,若已创建则阻止再次创建。
  2. 本地分段锁:以 userId 作为锁的 key,进入创建空间的代码前,对该 key 进行 synchronized(或在一个 并发 Map 里)加锁,避免并发下同一个用户多次创建。
  3. 编程式事务:在加锁后,用 transactionTemplate 开启数据库事务,先查询 userId 是否已有空间,再写入新空间记录,若过程中出错则回滚。事务执行完成后释放锁。

云图库项目中,你怎么保证用户上传图片不会超过空间额度?

为了防止用户无限制占用存储资源,我在云图库项目里为每个空间都配置了最大图片数量(maxCount)和最大总大小(maxSize)两种限制,并且在图片上传和图片删除环节都做了相应的更新与校验:

  1. 上传前校验:根据 spaceId 查到该空间的当前已用大小 totalSize 和数量 totalCount,分别与 maxSize、maxCount 对比。若空间或用户已超出限额,则拒绝这次上传请求。
  2. 上传成功后更新: 进入数据库事务,先写入图片表,再将 totalSize 累加上这张图片的大小、totalCount +1。 若任意一步失败,就回滚事务,保证数据的一致性。
  3. 删除图片后释放: 删除图片时,也在同一事务内把对应的大小和数量从 space 的 totalSize、totalCount 字段中减去,确保计量准确。

通过 上传前校验 + 事务内原子更新 的方式,即使在并发场景下,也能让空间用量处于可控状态,一旦某个空间达到了最大额度就禁止再传图,大幅降低系统被滥用的风险。

此外,参考大厂云服务的设计,我允许用户小幅超出额度(比如上传最后一张图时,即使会超额也允许上传),一定程度上减少了开发难度,并提升了用户体验。

云图库项目的后端中,你如何实现多维度的图片搜索功能?

在云图库项目里,我通过封装图片查询请求类,并在查询接口增加多种筛选条件,来实现多维度的图片搜索。主要思路:

  1. 定义一个 PictureQueryRequest 对象,除了基本的分页参数,还支持按名称、简介、分类、标签、编辑时间范围、格式、宽高等维度传参。
  2. 在服务层写一个 getQueryWrapper 方法,根据请求参数动态构造数据库查询条件,比如 queryWrapper.like(StrUtil.isNotBlank(name), "name", name) 等。
  3. 在控制层接收搜索请求并调用 service 进行分页查询,结果返回 Page 列表

前端在搜索表单里,可将这些搜索条件分为常用筛选(关键词、标签、分类)和更多筛选(日期、宽高、格式)等,实现灵活的多维度检索。

这样就能满足用户 “按关键字 + 标签 + 日期 + …” 多种组合搜索需求,大幅提升查询效率和使用体验。

云图库项目的后端中,你如何实现以图搜图功能?

我在云图库项目中采用了 实时爬虫 的方式实现以图搜图。流程如下:

  1. 通过 F12 网络控制台分析百度以图搜图的相关接口
  2. 调用百度以图搜图接口,将图片 URL 传给百度,以获得一个 “搜图页面地址”
  3. 利用 Jsoup 解析返回的 HTML,提取到搜索结果列表的接口地址
  4. 通过 Hutool 的 HttpRequest 调用该接口获取到相似图片、来源链接等信息,封装为一个后端接口返回给前端。

在实现层面,我为这些步骤(获取页面地址、解析 HTML、拉取图片列表)各自写了一个独立的 API 类,然后用 门面模式 将它们统一封装在 ImageSearchApiFacade 类中,对外只暴露一个 searchImage 方法,简化了调用流程,用户只要传入图片 URL 就能得到以图搜图的结果。

什么是门面模式?在云图库项目中如何使用门面模式?

门面模式(Facade Pattern)是一种结构型设计模式,通过提供一个统一的门面接口,把内部复杂的子系统接口隐藏起来,从而简化客户端与子系统的交互,降低耦合度。

我在云图库项目的 “以图搜图” 功能中就用到了门面模式:

  1. 我有多个独立的爬虫或 API 调用类,分别负责获取搜图页面、解析 HTML 脚本、获取搜图结果等
  2. 为了简化调用流程,我创建了一个 ImageSearchApiFacade 门面类,把这几个复杂步骤都封装成一个 searchImage 方法
  3. 外部只需一行代码 ImageSearchApiFacade.searchImage(imageUrl) 就能得到最终的相似图片列表,而不必关心内部分多个 API 去爬取、解析、获取搜索结果的细节。

这样做既减少了重复代码,也让功能调用更直观,符合 “高层接口简化、子系统细节隐藏” 的门面模式思想。

云图库项目的后端中,你如何实现颜色搜图功能?

整体思路是:先提取和存储每张图片的主色调,再用欧氏距离算法来判断颜色相似度。

详细流程:

  1. 提取主色调:上传图片后,我利用数据万象服务自动分析图片的主色调,然后将提取到的十六进制色值(如 0xAFB123)保存在数据库的 picColor 字段中。
  2. 用户发起颜色检索:输入一个十六进制色值(如 #FF0000),后端用 Color 类将其转换为 RGB。
  3. 计算相似度:从数据库中查询出每张图的主色调,同样转换为 RGB 后计算二者的欧氏距离 distance = sqrt((R1-R2)^2 + (G1-G2)^2 + (B1-B2)^2),距离越小表示越相似。
  4. 对结果进行排序,取相似度最高的前 N 张图并返回给前端

这样,前端用户只要操作一个颜色选择器,就能在空间内找出和指定颜色最接近的图片,大大提高搜图效率。

云图库项目的后端中,你如何实现批量编辑功能?怎么优化批量编辑的性能?

为了提高空间图片的管理效率,我提供了批量编辑功能,包括批量修改图片的分类、标签、名称等,能安全快速地完成大量图片的更新操作。

实现过程主要分两步:

  1. 在 Service 中,先一次性查询出要修改的图片并检查权限,然后按需更新分类、标签、名称等字段。
  2. 使用 MyBatis Plus 的 updateBatchById 批量更新记录,结合事务保证原子性。

通过下面几个方法,可以优化批量编辑的性能:

  • 减少循环 SQL:统一分批、批量更新数据库,而不是给每张图都执行一条 Update。
  • 并发执行:如果数据量特别大,可以用 CompletableFuture + 线程池将不同分段的图片并发处理,并在全部处理完后再统一提交或回滚。

云图库项目的后端中,你如何实现 AI 扩图功能?使用 AI 服务时有哪些注意事项?

我在云图库项目中采用了阿里云百炼的大模型服务来实现 AI 扩图功能,流程如下:

1)选择 AI 大模型:我调研了很多国内外的 AI 绘画大模型,发现阿里云百炼提供图像扩展 API,可以通过 HTTP 接入并且响应较快,还提供了一定免费额度,所以选择它。

2)异步调用:该 AI 接口需要先创建任务,然后轮询查看任务状态,又分为 2 个步骤:

  1. 后端调用 AI 的 “创建扩图任务” API,传入原图 URL 及一些扩展参数(如左右、上下加多少像素),得到一个 taskId 。
  2. 前端轮询后端接口,后端再调用 AI “查询任务” 的 API 来判断任务是否完成;如果成功,则返回生成好的图片链接,如果失败则提示用户。

使用 AI 服务的注意事项:

  1. 成本控制:绘图大模型通常按调用量或 GPU 耗时计费,要在使用前就预估好成本,并且通过记录次数或费用,整体观测成本消耗情况。
  2. 异步调用:大模型绘画任务往往耗时数秒到数十秒,同步等待会堵塞线程、影响并发性能,因此必须采用异步任务 + 前端轮询或后台轮询的方式。
  3. 超时控制:一旦任务执行很久还没完成,需要及时停止轮询,避免资源浪费。
  4. 参数校验:调用 AI 接口前先检查图片大小、格式、空间容量等,减少无意义的请求。
  5. 安全性:AI 接口成本高,必须加限流(如每分钟 N 次)和权限校验,防止恶意刷任务;对异常或失败任务也要做好记录与告警,保证系统稳定。

云图库项目中,你开发了哪些分析能力?后端如何实现空间分析功能?

在云图库项目中,我针对空间和公共图库的图片数据,开发了多种分析能力,包括空间资源使用分析、图片分类与标签分布、图片大小分布统计、用户上传行为分析,以及管理员可用的全局空间排行分析等。

对于分析类需求,开发流程都是类似的:

  1. 数据采集:从数据源(比如 MySQL 数据库或者大数据仓库)获取原始数据。要提前明确涉及的表和字段,必要时采用分页查询处理大数据量。
  2. 数据预处理:对数据进行清洗、加工和格式化,包括过滤无效数据(比如逻辑删除或审核未通过)、解析复杂字段(比如 JSON 格式的 tags),以及通过字段关联补充上下文信息。
  3. 数据计算:根据需求进行分组、聚合、排序等,从而计算关键指标,比如计算空间各分类图片的占用比例、用户上传图片的时间趋势。可以根据场景调整计算方案,比如对于大数据量的计算,可以采用 Spark 之类的大数据计算组件做离线计算;对于数据实时性要求较高的实时分析场景,可以用 Flink 做流式处理。
  4. 数据存储(可选):针对频繁查询的分析结果,可将结果数据存储为单独的表或缓存,减少重复计算,提高查询效率。
  5. 数据接口设计:为前端提供统一接口,从而支持查询和展示。需要考虑到数据量较大导致前端渲染卡顿的情况,可以按需精简返回的字符串、分页查询等。
  6. 数据可视化:通过图表直观展示分析结果,前端可以使用 Apache ECharts 等可视化库渲染。当然也可以让后端生成图表图片并返回,但这种实现方法的灵活度有限。

通过这些分析能力,用户可以清晰掌握自己空间的使用状况和图片分布,管理员也能整体观察系统资源利用率,并做好限额管控和数据运营。如果数据量大,还可进一步用 Redis / 定时任务等手段做缓存或离线计算,提升统计性能与响应速度。

云图库项目的后端中,你如何实现团队空间的成员管理功能?

我在项目里新建了一张 space_user 表,记录 spaceId 和 userId 的多对多关联关系,并附带 spaceRole 字段标识了用户在该空间的角色(viewer / editor / admin)。

成员管理操作比较简单,就是编写增删改查接口 + 必要的权限校验。需要注意的是,创建团队空间时,要先在 space 表插入团队空间记录,再自动往 space_user 表插一条数据,赋予创建者管理员角色。

云图库项目的后端中,你如何实现空间的权限管理?

在云图库项目里,空间有 “私有空间” 和 “团队空间” 两种类型,后者需要不同成员进行协同管理和操作,所以必须提供更细粒度的权限控制,比如谁能上传图片、谁能删除图片、谁能管理成员等。为此我使用基于角色的权限管理(RBAC)来实现团队空间的权限管理,并配合 Sa-Token 框架实现统一的注解式权限校验。

实现步骤:

1)空间角色与权限的定义:根据团队空间中的操作场景,设计了权限和角色。并且以 JSON 配置文件的方式存储角色和权限的关系,避免了新建数据表和关联查询。

2)空间成员管理:对于团队空间,通过空间成员关系表实现多对多关联,并标注用户在该空间的角色。

3)Sa-Token 鉴权框架:基于 Sa-Token 的 Kit 模式实现了多账号体系的 RBAC 权限控制,通过从请求上下文中获取参数实现了统一的权限校验逻辑,并运用注解合并简化了鉴权注解的使用,轻松实现方法级别的权限校验。

举个例子:

  1. 在代码中,可使用自定义的 Sa-Token 注解 @SaSpaceCheckPermission("picture:delete") 标记敏感操作接口,让框架在调用方法前自动做校验。
  2. Sa-Token 会调用自定义的 StpInterfaceImpl#getPermissionList(...) 方法,这里读取空间上下文信息 + space_user 表的角色,再用通过角色权限关系获取到最终的权限列表,如果不包含 "picture:delete" 就直接抛异常。

这样就避免了每个接口都手写 “if-else” 或编码来判断权限。

4)前后端交互:为了前端能根据权限隐藏或显示某些按钮,我在 “获取空间详情” 和 “获取图片详情” 时,额外返回 permissionList 字段,让前端知道当前用户对该空间 / 图片拥有哪些权限,从而进行动态 UI 控制。

什么是 RBAC?你在云图库项目中是如何设计和实现 RBAC 的?

对于复杂的权限控制场景,可以采用经典的 RBAC 权限控制模型(基于角色的访问控制,Role-Based Access Control),核心概念包括 用户、角色、权限

  • 一个用户可以有多个角色
  • 一个角色可以有多个权限

这样一来,就可以灵活地配置用户具有的权限了。

一般来说,标准的 RBAC 实现需要 5 张表:用户表、角色表、权限表、用户角色关联表、角色权限关联表,还是有一定开发成本的。由于云图库项目中,团队空间不需要那么多角色,可以简化 RBAC 的实现方式,将角色和权限直接定义到 JSON 配置文件中,在项目启动时读取配置文件到 Java 对象中,然后基于 Sa-Token 权限框架实现具体的校验逻辑。

什么是分库分表?云图库项目中为什么要用分表?你又是怎么实现动态分表的?

分库分表是一种将数据拆分存储在多个库或多张表的策略,可以减小单表体量、提升高并发访问性能,并且减少单点故障。

云图库项目中,团队空间的图片可能非常多,如果全都往同一张 picture 表里插入,后期查询和维护都会遇到性能瓶颈,所以考虑把特定空间(如旗舰版团队空间)的图片单独存到分表里。

普通的静态分表要事先建好 N 张表,但项目里团队空间数量不固定,所以我基于 Apache ShardingSphere 实现动态分表。

  1. 给需要分表的空间动态创建 picture_{spaceId} 表,并维护一份可用分表节点列表
  2. 编写自定义分片算法,路由时根据 spaceId 决定具体访问哪一张物理表

剩下的事情交给框架就好,会自动将 SQL 查询改写为到对应分表进行查询。

什么是 Apache ShardingSphere?你在云图库项目中为什么选用它实现分库分表?

Apache ShardingSphere 是一个提供分库分表、读写分离、分布式事务等功能的开源数据库中间件,支持多种模式(ShardingSphere-JDBC、ShardingSphere-Proxy)和灵活的分片配置方式。

我选择它的 ShardingSphere-JDBC 模式实现分库分表,主要原因是:

  1. 功能全面:开箱即用地实现分库分表、聚合查询、分布式事务等
  2. 配置灵活:能按字段取模、按时间段拆分,也支持自定义分片算法
  3. 社区活跃:遇到 Bug 时能找到很多文档来解决
  4. Java 生态:我用的是 Spring Boot 框架,ShardingSphere-JDBC 的嵌入式方案对小项目更简洁轻量,更容易和现有环境集成。

云图库项目在实现动态分表时,为什么要手动维护可用的分表节点?

因为 ShardingSphere 默认依赖 actual-data-nodes 配置去识别可用表,但是如果提前不知道 spaceId 列表就无法编写固定的节点配置。

在动态分表场景下,每新增一个团队空间就会生成一张新的分表 picture_{spaceId},必须通知 ShardingSphere “又多了一个可用节点”。因此需要在后端写一个分表管理器来手动维护可用的分表节点,它主要做了下面几件事:

  1. 扫描数据库中已有的 picture_{spaceId} 分表
  2. 动态更新 actual-data-nodes 配置
  3. 重载 ShardingSphere 的路由规则

不这么做的话,ShardingSphere 并不知道新的分表存在,查询时就会报找不到分表的错误。通过手动维护可用节点,就能让 ShardingSphere 在运行中支持随时增加分表。

云图库项目的后端中,你如何实现图片的协同编辑功能?

我在已有的图片编辑功能上,结合 WebSocket事件驱动 思想,让多位团队成员能够实时查看对同一张图片的编辑变化。关键的实现点是:

  1. WebSocket 通信:前端与后端在建立 WebSocket 连接后,后端保存每个图片对应的会话集合,以及当前正在编辑的用户信息。
  2. 编辑锁机制:只有一个用户可以进入编辑状态,其他用户只能实时 “围观” 该用户操作,这样从源头上解决了并发冲突。
  3. 事件驱动:每当用户执行放大 / 旋转等编辑动作,相当于生产了一个 “事件”,通过 WebSocket 服务器把该事件分发给其他连接用户,实时广播更新。
  4. 断开连接:若正在编辑用户断线或退出,会自动释放编辑锁,其他用户可申请进入编辑状态。

什么是 WebSocket?为什么在云图库项目中选用它而不是 HTTP 实现协同编辑?

WebSocket 是一种 全双工(双向)通信协议。和传统 “请求 - 响应” 的 HTTP 不同,WebSocket 在初次握手后会保留一个持久连接,客户端和服务器可彼此 随时主动 发送消息,而无需频繁重建连接。

在协同编辑场景中,需要 高频双向 的消息传输 —— 编辑者的操作需要立刻同步给其他用户。如果用 HTTP 就得不断轮询或发起请求,浪费带宽、且延迟高;而使用 WebSocket 保持长连接,可以低延迟地推送彼此的编辑信息,满足实时协同需求。

什么是事件驱动设计?你在云图库项目中是如何运用它实现协同编辑功能的?

事件驱动设计通过将系统内的操作抽象成 “事件”,当事件发生时,把事件交给异步处理器或消息总线,然后通知其他需要接收的组件或用户,从而 解耦 生产者与消费者,并提高并发性能。

在我的协同编辑实现中,每次用户的编辑动作都被视作一个事件:

  1. WebSocket 接收前端事件后,触发相应的处理器
  2. 处理器中将消息再广播给所有正在编辑此图片的客户端
  3. 其他用户收到此事件后即可同步更新编辑状态和操作

云图库项目的后端中,WebSocket 的握手拦截器和编辑锁机制分别起到什么作用?

1)WebSocket 握手拦截器:在真正建立 WebSocket 长连接前,会进行一次 HTTP Upgrade 请求。我在握手拦截器中校验用户是否已登录、有无团队空间的编辑权限,然后把用户、图片 ID 等信息放到 Session 属性里,这样就能在后续通信中直接复用这些会话属性;如果不符合条件则拒绝握手。

2)编辑锁机制:同一时刻只有一个用户能 “进入编辑” 该图片,其余用户只能接收消息并实时浏览效果,但不能操作。这样可以从源头避免并发冲突,不需要维护复杂的实时合并算法。当持锁用户退出或断连时自动释放锁,其他人才能进入编辑状态。

什么是 Disruptor?在云图库项目中为什么要使用它?又是如何使用它的?

Disruptor 是 LMAX 公司开源的一个 高性能无锁环形队列 框架,常用于高并发、低延迟的消息处理场景。它通过环形缓冲区 + 无锁(CAS + 内存屏障)减少线程切换,来显著提升吞吐量。

在协同编辑中,后端 WebSocket 原本同步接收和处理消息,若并发用户多、单条消息处理耗时长,就易造成阻塞,无法处理更多请求。引入 Disruptor 后,将消息生产与消息处理解耦:

  1. 原本的处理消息逻辑修改为 “仅把消息提交到 Disruptor 队列”
  2. 由 Disruptor 的消费者线程异步取出并执行具体业务逻辑

这能保持 WebSocket 主线程轻量快速,不易被阻塞,也能提高实时响应能力。

我在项目中使用 Disruptor 的步骤:

  1. 引入 Disruptor 依赖
  2. 定义事件,承载编辑请求、WebSocket Session、用户信息等
  3. 定义事件处理器,用于消费编辑事件并调用相应的业务逻辑
  4. 在启动时初始化 Disruptor,初始化固定大小的环形队列,并把事件处理器加入消费者池
  5. 在 WebSocket 收到消息后,把它发布到 Disruptor 的环形队列
  6. Disruptor 内部会多线程异步执行这些事件

什么是优雅停机?你在云图库项目中怎么利用 Disruptor 实现优雅停机?

优雅停机指在应用关闭前,不再接收新的请求等待系统中正在进行的任务全部完成后,才真正停止进程。这样可以确保数据或状态不会在停机时刻中断或丢失,避免产生乱序或资源泄露等问题。

Disruptor 本身提供了优雅停机方法。我利用 Bean 的 PreDestroy 注解,在应用关闭前,先调用 disruptor.shutdown(),然后 Disruptor 本身会等待环形队列内的所有事件处理完才返回,同时拒绝新的编辑操作或网络请求。待队列任务都消费完成,再关闭线程池和资源连接,最后退出应用。

前端

为什么云图库项目中使用 create-vue 脚手架来初始化项目?脚手架有什么作用?

因为我希望快速创建一个 Vue3 + TypeScript 的前端项目,并且想整合一些常用功能(如路由、Pinia、ESLint 等),所以我在项目中直接使用了 Vue 官方推荐的 create-vue 脚手架,一键生成基础的目录结构和依赖。它的好处主要包括:

  1. 统一项目结构:自动生成规范的 src / public / router / store 目录,不用手动搭建文件夹。
  2. 自带工程化:内置了 ESLint、Prettier、TypeScript 等常见的代码质量工具,省去很多配置成本。
  3. 自动整合依赖:不用我们手工添加 vue-router、Pinia、Vite、TypeScript 等依赖,开箱即用。
  4. 可扩展:后续还可以灵活改动脚手架生成的文件,随时自定义项目结构,兼顾自由度。

你是怎么初始化云图库项目的?你自定义的前端项目模板有哪些主要功能?

我先使用 create-vue 脚手架快速创建了基础项目结构并整合了常用的依赖和前端工程化配置,然后开发了一系列的 “万用” 功能。比如:

  1. 全局页面布局:复用导航栏、底部 Footer 栏,并且支持根据路由切换多套布局
  2. 全局权限管理:通过路由配置指定各页面的权限,当页面跳转时自动拦截并鉴权
  3. 全局状态管理:实现用户自动登录逻辑,并利用 Pinia 库来存储用户登录态
  4. 导航菜单生成:根据路由配置自动生成导航菜单,支持控制菜单的显隐
  5. Axios:全局自定义实例,简化请求的拦截处理,并可携带 Cookie
  6. 自动生成 API:通过 OpenAPI 工具,自动生成后端接口请求的代码

之后这个模板可以用到我的新项目中,不必反复从 0 开发。

你在云图库项目中使用了 TypeScript、ESLint、Prettier 来保证项目的编码规范,解释一下它们各自的作用?

TypeScript:让 JavaScript 拥有了强类型体系,能在编译阶段发现不少潜在错误,比如参数类型不匹配、属性名写错等问题,大幅减少运行时 bug。

ESLint:主要用于语法和规范校验,会检查代码风格、潜在语法错误等。例如提示未使用的变量、不安全的比较运算等。

Prettier:专门做代码格式化,统一缩进、分号、引号、换行风格等,让团队代码风格高度一致。

三者配合,TypeScript 负责类型安全,ESLint 负责语法 / 规范校验,Prettier 负责格式统一,能提升项目代码质量、可维护性和可读性。

什么是 Vue Router?你在云图库项目中如何根据路由配置文件自动生成导航菜单?

Vue Router 是 Vue 官方提供的路由管理库,用来在单页应用中根据 url 地址来切换不同的页面。它会根据浏览器地址变化,动态加载并渲染对应的 Vue 组件,而无需整页刷新。

在云图库项目中,我编写了一个路由配置文件集中管理路由,例如:

const routes = [
  { path: '/', name: 'home', component: HomePage },
  { path: '/about', name: 'about', component: AboutPage },
  
]

然后为了避免重复维护路由和菜单,我选择把路由配置解析成菜单,步骤如下:

  1. 循环遍历路由数组,从中提取 path、name 等数据
  2. 给每个路由设置 title、icon、hidden 等属性
  3. 通过这些属性生成菜单项,动态展示在导航栏里

这样,新增 / 修改路由就能自动更新菜单,不用写两遍,保持一致性与可维护性。

你在云图库项目前端中是如何实现全局权限管理的?

我采用了 路由守卫 + 全局状态 的方式实现全局权限管理,步骤如下:

  1. 获取登录用户信息:在 Pinia 的 userStore 中维护当前登录用户信息及角色
  2. 路由守卫:在 router.beforeEach 中,根据 to.path 判断是否是管理员才能访问的页面路径(比如以 /admin 开头),如果用户不满足角色则跳转到登录页或者无权限页。
  3. 菜单过滤:通过对路由 / 菜单的配置做一层过滤,将不符合权限的菜单项隐藏,让用户看不到无法访问的功能。

我将上述代码封装到 access 模块中,便于集中维护。

什么是全局状态管理?为什么要在云图库项目中使用 Pinia 来进行全局状态管理?

全局状态管理就是在前端应用中,集中保存多个页面和组件都需要共享的关键数据(如登录用户、主题设置等)。这样可以在任何组件中获取和修改这些数据,并且触发 UI 的更新,避免了组件间相互传递数据。

云图库项目中,很多页面都要根据当前登录用户判断权限、显示隐藏元素,所以要用到全局状态管理。

之所以选 Pinia,首先是因为脚手架自己整合了 Pinia,当然还有更多原因:

  1. 更轻量简单:相较 Vuex,Pinia 写法更贴近组合式 API,可读性高
  2. 支持 TypeScript:对类型推导更友好,Pinia 的定义更简洁

云图库项目中,为什么要自定义 Axios 实例?自定义的实例有哪些功能?

自定义 Axios 实例,可以在发送请求前统一配置一些通用属性,而不用反复设置。比如:

  • 全局请求前缀、超时时间、携带 Cookie
  • 全局请求 / 响应拦截器,对请求头或响应数据做统一处理。

我自定义的实例功能包括:

  • 基础配置:设置 baseURL,不用每次都写完整服务器地址,便于根据环境自动切换请求域名
  • 请求拦截:打印请求日志,便于开发时排查问题
  • 响应拦截:自动处理后端返回的错误码,比如遇到未登录时自动跳转登录页,或弹出错误提示

什么是 OpenAPI 工具?你在云图库项目中如何利用它生成代码?

OpenAPI 是一种 API 描述规范,可以用 YAML 或 JSON 格式描述后端接口的请求 / 响应数据结构。借助它可以自动生成前端或后端的 API 调用代码,从而减少人工写接口的工作量。

在云图库项目里:

  1. 后端通过注解 + Swagger 配置自动生成 OpenAPI 文档
  2. 我使用 @umijs/openapi 工具库,编写生成代码的配置和命令,执行命令就能读取 OpenAPI 文档并生成代码了

工具会自动生成所有接口调用方法、请求体 / 响应体类型定义的 TypeScript 代码。这样前端就不必手写几十个接口,还保证与后端接口的调用方式和参数强一致,极大提高开发效率并减少接口对不上的问题。

你在云图库项目中如何使用 Ant Design 的 Table 组件实现了管理页面?

云图库项目包含了用户管理、图片管理、空间管理等多个管理页面,实现流程都是一致的。我参考官方文档使用了 Ant Design Vue 提供的 Table 组件,快速实现包含 展示 + 分页 + 搜索 + 操作 功能的管理页面。

核心流程如下:

  1. 数据获取:在页面 onMounted 或者点击搜索时,通过我们自定义的接口方法调用后端分页 API,将返回的 records 赋给表格的 dataSource,将 total 赋给表格的 pagination.total。
  2. 分页:使用表格的 pagination 属性,当用户点击分页器切换页号或修改页大小时,触发 onChange 回调函数,更新当前页号和页大小,然后再次请求后端拿数据。
  3. 列定义:表格的 columns 中定义了 title、dataIndex 和自定义渲染插槽,比如图片列可以使用 Image 组件,时间列用 dayjs 格式化,标签列用 Tag 组件。
  4. 操作按钮:比如在操作列里放一个 “删除” 按钮,点击后调用后端删除接口并刷新列表;如果是 “编辑” 按钮则点击后跳转到编辑页面。
  5. 搜索筛选:在表格上方加一个 Form 表单组件,点击搜索按钮后,调用 fetchData 函数触发搜索,并更新数据。

你在云图库项目中用到了哪些 Ant Design 的组件?请举例说明

云图库项目的前端中,我大量使用了 Ant Design Vue 提供的组件,比如:

  1. Layout / Sider / Header 布局:构建整站布局和顶部导航、侧边栏等。
  2. Menu 菜单:做全局导航栏,点击菜单自动跳转 Vue Router 对应路由。
  3. Tabs 页签:在创建图片页面里,用来切换 “文件上传” 和 “URL 上传” 两个 tab。
  4. Form / Input / InputNumber / Select / AutoComplete / Upload / Button 等表单组件:构建各种数据输入的表单。
  5. Table / Pagination:实现管理员查看和操作数据的管理页面,支持分页和排序。
  6. Card / List / Image / Descriptions:在图片列表和图片详情页常用的 UI 组件,直观地展示图片及其描述信息。
  7. Modal / Message / Notification / Popconfirm:根据场景显示不同的消息提示,提高交互体验。比如用户删除等危险操作时会弹出 Popconfirm 确认框。

你在云图库项目中用到了哪些第三方组件?请举例说明

除了 Ant Design 官方组件,我还用到了一些第三方组件,以满足不同的业务需求。比如:

  1. file-saver:用来下载图片文件,支持一键保存 URL 或 Blob 文件到本地,用户可一键下载云图库图片。
  2. dayjs:轻量日期处理库,用于格式化数据库返回的时间,比如 dayjs(picture.createTime).format('YYYY-MM-DD HH:mm:ss')
  3. vue-cropper:前端裁剪图片时,会引入它来实现所见即所得的裁剪,允许用户拖拽裁剪区域。
  4. @umijs/openapi:基于 OpenAPI 规范生成前端请求代码,避免手写接口方法和参数类型。

你在云图库项目中自己开发了哪些组件?开发组件时有哪些经验技巧?

在云图库项目中,我开发了 10 多个可复用的业务组件,举些例子:

  1. PictureUpload:封装了本地图片上传逻辑,支持图片预览、校验、图片上传、上传成功回调等。
  2. UrlPictureUpload:封装 URL 上传图片的场景,内部包含一个输入框 + 提交按钮,将用户输入的 URL 上传给后端。
  3. BatchEditPictureModal:批量编辑图片的弹窗组件,组件内封装了 Form 表单,可以调用后端完成图片批量修改。

组件开发的经验技巧:

  1. 单一职责:一个组件只负责一块功能,不要让组件代码过于臃肿。
  2. 受控与非受控:大多数组件设计为 “受控组件”,将数据和事件抛给父组件管理,组件只关注 “展示和交互”。
  3. 规范命名:组件名称、Props、Event 都要简明易懂,便于理解和维护。
  4. 对外扩展:可以利用 Slot 插槽特性,让调用者自定义组件的部分内容,方便灵活复用。
  5. TypeScript 类型:为组件的属性和回调函数定义好类型,帮助调用者传入正确的参数。

什么是 Vue Router 的动态路由?你在云图库项目中如何使用动态路由?

动态路由指在 Vue Router 中,可以在路径中使用类似 :id 这样的占位符,告诉路由在访问 /xx/:id 时可以渲染同一个页面组件,但里边的内容可根据动态参数的不同进行相应的处理和展示。

在云图库项目中,典型的场景是图片详情页,我在路由配置中写:

{
  path: '/picture/:id',
  name: '图片详情',
  component: PictureDetailPage
}

这样当用户访问 /picture/1001/picture/123 时都能加载同一个 PictureDetailPage 组件。组件内通过 propsuseRoute() 可以拿到 id 参数去调用后端接口获取对应的图片信息。这样就无需给每张图片都写一条路由、开发一个页面,非常灵活。

什么是 Vue 的 watchEffect 函数?你在云图库项目中如何使用它?

watchEffect 是 Vue3 提供的一个自动监听 “响应式变量” 变化的函数式 API。它和 computed、watch 类似,但更简洁、没有显式的依赖声明,Vue 内部会自动跟踪它使用的响应式变量,一旦变量变化,就重新运行这个函数。

在云图库里,当我想在每次响应式变量更新时立刻触发某个函数的调用,就会用到 watchEffect。比如在展示空间分析图表时,我会用 watchEffect 来监听分析参数的变化,然后自动调用接口更新分析数据。写法更方便一些:

watchEffect(() => {
  
  fetchData()
})

云图库项目中,你如何实现图片列表的响应式?以适配不同尺寸的屏幕

我主要通过以下方式,让图片列表在各种屏幕下都能更好展示:

  1. Ant Design 列表的 Grid 布局:在列表组件中通过 grid 属性指定响应式布局。比如 :grid="{ gutter:16, xs:1, md:3, lg:4, xxl:6 }",表示小屏手机上一行 1 张,中等屏一行 3 张,大屏可展示更多,自动响应式。
  2. 图片固定宽高 / 自适应:给图片增加 CSS 样式 object-fit: covercontain + max-height,在不同宽度下让图片保持等比缩放,不会变形。
  3. Flex 布局:在一些布局中使用 Flex 布局,使元素能自动换行或填充剩余空间。

此外,还可以使用 CSS 的媒体查询特性。针对极小 / 极大屏幕写些定制 CSS,比如 @media (max-width: 600px) { ... } 来调整列表或边距。

云图库项目的前端中,你是如何优化图片加载的?

我在云图库项目中主要从四个方面 缩略图 + 懒加载 + CDN + 浏览器缓存 来优化图片加载,尽可能地减少加载时间、降低带宽消耗并提升用户体验:

1)缩略图:在图片上传时,除了保留压缩图外,还生成了一份缩略图,用户在列表页查看图片时只会加载缩略图,而不直接加载原图。这样做可以显著降低流量消耗,大幅加快页面首屏速度。

2)懒加载:对于图片很多的页面,可以用原生 HTML5 的 loading="lazy" 或者利用 Intersection Observer 来实现懒加载,只有当图片即将出现在视口时才触发加载。这样可以避免一次性加载所有图片,节省网络资源。

3)CDN 加速:在后端把图片都存到 COS 对象存储后,再结合 CDN(内容分发网络)进行全国分发。 用户请求图片时,会自动就近访问缓存节点,无需回源站获取;同时我还启用了防盗链、限速等功能,提升安全性。

4)浏览器缓存:对于静态资源(图片等)配置了较长时间的浏览器缓存,下次访问时直接读取本地缓存。如果图片更改了,会自动修改文件名(hash 值),浏览器也会请求新的图片,从而避免了数据不一致的问题。

云图库项目的前端中,你是如何实现多维图片搜索功能的?

在云图库的前端中,我基于 Ant Design Vue 的 Form 表单和各类表单项组件,开发了多维搜索功能。

具体做法如下:

1)封装搜索表单组件:由于搜索项较多,我新建了 PictureSearchForm 组件,集中管理搜索条件的输入,比如关键词、分类、标签、日期、名称、简介、宽高和格式等字段。组件对外暴露一个 onSearch 回调,通过该回调将用户填好的搜索条件传递给父组件。

这个组件不仅空间详情页可以用,管理页面也能用,减少重复代码。

2)搜索条件的维护:在组件内部使用 reactive 对象来存储搜索字段,然后在提交时触发 onSearch,完成 “组件内输入 => 父组件获取” 的流程。

为了简化重置操作,还提供了一个重置按钮,点击后会清空所有搜索字段并触发一次搜索。

3)动态绑定搜索字段:每个搜索字段(如名称、分类、标签、宽高等)都选用 Ant Design 的合适组件(a-inputa-selecta-input-number 等)进行输入。

其中日期区间采用 a-range-picker,用户可快速选取时间段,并在 change 事件中将时间值映射到搜索参数的 startEditTime、endEditTime。

4)前后端交互:父组件在 onSearch 回调里拿到搜索表单对象,然后将其与分页信息合并,调用后端的接口,最终返回搜索结果列表并展示到图片列表中。

云图库项目的前端中,你是如何实现颜色搜图功能的?

我引入了第三方颜色选择器组件 vue3-colorpicker,用户在选择器中选定一个主色调后,会触发一个回调事件,把选中的十六进制色值提交到后端,后端返回一组符合条件的图片列表,展示即可。

此外,我在图片详情页补充了图片主色调的展示,用一个 16px 色块直观展示色彩。

云图库项目的前端中,你是如何实现图片分享功能的?

由于项目多个页面都需要使用分享功能,所以我封装了通用的分享弹窗组件,同时支持复制链接和移动端扫码分享。我使用 Ant Design 的 Typography 组件实现了链接的复制;使用 QrCode 组件实现了移动端扫码分享,输入要分享的超链接文本,就可以生成二维码图片展示到前端。

弹窗组件为受控组件,是否可见的状态由组件内部维护,通过 defineExpose 暴露打开弹窗的函数,父组件通过 ref 调用即可打开弹窗。

云图库项目的前端中,你如何实现图片编辑功能?

我在前端使用了第三方图片裁剪库 vue-cropper,并封装成 ImageCropper 组件。组件内部通过 vue-cropper 提供的方法实现裁剪、旋转、放大、缩小操作,再通过 Ant Design 的 Modal 弹窗实现交互式编辑流程。

用户在弹窗中编辑完图片后,点击 “确认” 会调用 cropperRef.value.getCropBlob() 获取裁剪后的文件,并通过已有的上传接口提交到后端,前端同时更新图片显示,从而完成基础的图片编辑功能。

云图库项目的前端中,你如何实现 AI 扩图功能?

我在前端新建了一个独立的 ImageOutPainting 弹窗组件,用户点击 “AI 扩图” 后会立刻调用后端创建扩图任务的接口,并返回 taskId。由于扩图过程涉及大量计算,后端会采用 “异步任务” 方式执行,前端则通过定时器每隔几秒轮询一次任务状态。

如果状态是 “成功”,就能拿到扩图后的图片地址并显示;如果是 “失败”,则弹出相应的失败提示消息。

用户可以自主决定是否覆盖原图,而不是强制用新图片替代原图,提升了操作的灵活度。

云图库项目前端中,你如何实现统计分析图表的展示?

使用 Apache Echarts 可视化库,该库提供了丰富的图表,比如分组条形图、饼图、词云图、柱状图等。

我先进入该库官网的示例页面,根据统计分析需求找到对应的图表,然后进入详情页修改 options 变量进行调试。调试完成后,编写前端页面,请求后端获取数据,并通过 Vue 的 computed 语法计算出 Echarts 需要的 options 变量,成功在页面上渲染出图表。

值得一提的是,我让 AI 根据分析需求生成了 options 变量,大幅提高了开发效率。

云图库项目的前端中,你如何对空间内的图片进行权限控制?

核心思路是根据后端返回的权限列表,结合 computed 计算属性进行动态判断。

具体流程:

  1. 定义权限常量:我对照后端的权限常量定义了前端的常量代码,比如 SPACE_PERMISSION_ENUM,便于后续使用更方便。
  2. 每次加载空间或图片详情时,后端会返回一个 permissionList(如 ["picture:edit","picture:upload"])。我基于返回的权限列表写了一个通用检查函数 createPermissionChecker(permission) => computed(...),传入需要的权限,就能得到是否具有该权限的判断结果,比如 canUpload = createPermissionChecker("picture:upload")
  3. 在页面中用 v-if="canUpload" 等条件即可判断是否显示按钮。

云图库项目的前端中,你如何实现图片的协同编辑功能?

前端主要通过 WebSocket 实时通信来实现协同编辑,在 “图片编辑弹窗” 中让团队成员能感知并同步其他用户的编辑操作。具体做法是:

  1. WebSocket 连接:每次打开图片编辑弹窗时,会用图片 id 建立 WebSocket 连接,在前端维护一个 websocket 实例,通过 onopen / onmessage / onclose 监听消息。
  2. 同步编辑:当用户点击 “进入编辑” 时,会向服务端发送 ENTER_EDIT 消息;当执行旋转、缩放等操作时,则发 EDIT_ACTION 消息,让其他用户同步到同样的图片状态;退出时发送 EXIT_EDIT 消息,告诉其他用户已经退出编辑。
  3. 编辑锁:为了防止并发冲突,前端与后端都维护了 “正在编辑用户” 的信息。如果有人在编辑,则其他用户按钮置灰或操作被拒绝。
  4. 资源释放:关闭弹窗或离开页面时,会调用 websocket.disconnect 释放连接,并重置 “正在编辑用户” 状态,防止重复占用连接或编辑锁。

什么是 WebSocket?前端如何和服务器建立 WebSocket 连接?有哪些注意事项?

WebSocket 是一种在客户端和服务器之间保持 双向、实时通信 的协议。相比传统的 HTTP 单向请求 - 响应模式,WebSocket 能让服务器随时主动推送数据给客户端,或者客户端主动发送消息给服务器,无需反复创建和关闭连接,实时性更强,也减少了网络开销。

1)前端如何建立连接

先在 JavaScript 中创建一个 new WebSocket("ws://serverURL") 对象,指定后端提供的 WebSocket URL。 然后就可以监听事件并进行相应的处理了:

  • 监听 onopen 事件,表示连接成功后可以开始发送消息
  • 监听 onmessage 事件,用于处理服务器推送过来的数据
  • 监听 oncloseonerror 处理连接断开或异常情况

2)注意事项

  1. 释放资源:前端组件销毁时应该主动断开连接,否则可能会给服务器增加压力。
  2. 断线重连:网络不稳定或服务器异常时连接可能会断开,需要前端实现重连策略,否则用户可能无法持续接收消息。

云图库项目是否有上线?你是如何实现前端页面部署的?

项目有实际上线。我是通过 本地打包 + Nginx 实现了前端页面部署。

具体过程如下:

  1. 购买云服务器
  2. 安装和初始化宝塔 Linux 面板,会自动安装 Nginx 服务器
  3. 在宝塔上创建一个网站
  4. 本地使用 npm run build 命令打包项目,得到 dist 网站静态文件目录
  5. 上传本地打包好的 dist 目录到服务器,然后配置 Nginx 指向文件目录路径,即可访问前端静态文件

还有其他的部署方式,比如使用 Vercel 等 Serverless 服务一键部署到第三方托管服务器,但由于本人有服务器、并且想实践下 Nginx 配置,所以没有选择这种方式。

通用

请介绍一下云图库项目的完整业务流程?

项目分为公共图库、私有图库、团队空间三大模块,整体流程如下:

1)公共图库:任何用户都能浏览公共图库;未登录用户默认仅可查看已审核通过的图片。管理员可执行图片上传与审核,拒绝违规图片。还可以查看公共图库的图片分布情况(如分类、标签、上传趋势等)。

2)私有图库:注册用户登录后,可以将图片上传到自己的私有空间,包含多维检索、批量管理、基础编辑、AI 扩图等功能。用户还可以进行个人图库分析(图片分类、标签、大小等),从而优化存储结构或查找常用图片。

3)团队空间 企业用户或拥有付费权限的用户创建团队空间,系统会自动将创建者设为管理员;管理员可直接输入成员 ID,添加协作者并为其分配编辑者 / 浏览者角色。有编辑权限的成员可以通过 WebSocket 对图片进行协同编辑,如旋转、放大等操作会同步给其他在线成员;

为什么要分为 3 个阶段来实现云图库项目?

主要是为了 循序渐进、分模块迭代,让项目的功能和设计在每个阶段都能相对完整地上线并可扩展。

1)最基础的公共图库场景相对简单,只需实现图片上传、审核和公开展示,适合初期快速构建 MVP(最小可行产品)。同时也能尽早验证核心功能(如图片管理、检索、审核流程),并为后续私有空间和团队空间打下基础。

2)当公共图库稳定后,再扩展出私有空间,满足个人对图片进行精细化管理、分析和编辑的需求。在此阶段引入更丰富的功能如多维检索、批量操作和 AI 扩图等,让个人用户体验并逐步沉淀项目的 “编辑能力”。

3)最后再拓展出团队空间,实现多人协作、成员管理、实时协同编辑等复杂需求。此时,前两阶段的权限设计、图片编辑和统计分析功能都可以 “复用” 到团队空间,只需整合并补充成员角色、协作流程等部分逻辑即可。

分阶段之后,既能 快速上线核心功能,又能 在每个阶段稳步迭代、持续完善项目;同时也利于团队和用户在功能逐步复杂化的过程中不断实践、反馈并优化设计。

你有使用过 AI 工具来辅助编程么?都是如何帮你提高开发效率的?

必须的,我使用的是国内的免费 AI 编程助手 CodeGeex(或者其他家的产品),主要使用了下列功能:

  • 自动生成单元测试
  • 自动给代码补充注释
  • 解释分析代码,帮助我理解一些类库的源码
  • 示例数据生成,帮助我快速构造了一些模拟数据
  • 代码补全,帮我提高了编码效率
  • 代码生成,帮我生成一些简单的算法

在开发过程中,你遇到过比较复杂的技术问题或挑战吗?如果有,请谈谈你是如何解决这些问题的?

可以从以上任意一道主观的面试题出发去讲,比如你是怎么实现协同编辑功能并使用 Disruptor 进行优化的?你是怎么利用 ShardingSphere 实现动态分表的?如何一步步封装了通用的文件上传模块?为什么要使用模板方法模式和门面模式优化代码等等。

通过云图库项目,你收获了哪些知识或经验?

送分题,直接参考鱼皮编程导航的 项目介绍

全文完
本文由 简悦 SimpRead 转码,用以提升阅读体验,原文地址