苍穹外卖面试题准备(持续更新

苍穹外卖面试题准备

讲讲 redis,它在你的项目中作用是什么?

Redis 是高性能的基于键值对的写入缓存的 内存存储系统。它支持多种数据结构如字符串、哈希表、列表、集合、有序集合等,并提供了丰富的操作命令。项目中引入 Redis 的地方是:查询店铺营业状态 ,像这种店铺营业状态,本项目无非就两个状态:营业中/打样。而且它属于高频查询。只要用户浏览到这个店铺,前端就要自动发送请求到后端查询店铺状态。Redis 是基于键值对这种形式存储的,而且 Redis 也把将数据放到缓存中,而不是磁盘,有效缓解了这种高频查询给磁盘带来的压力。
缓存菜品 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。**结果:**系统响应慢、用户体验差。通过Redis来缓存菜品数据,减少数据库查询操作。
缓存套餐(用到的是Spring Cache)

在你的项目中 redis 作为缓存, MySQL 的数据如何与 redis 进行同步呢?

  苍穹外卖这个项目中用户在查看店铺状态时,需要让数据库与 redis 高度保持一致,因为如果店铺没有营业的话就不能点单了,所以它要求时效性比较高,所以采用的读写锁保证的强一致性。我们采用的是 redisson 实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

如何解决 Redis 的缓存穿透问题

  • 缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。
  • 使用布隆过滤器来解决缓存穿透:主要是用于检索一个元素是否在一个集合中。我们当时使用的是 redisson 实现的布隆过滤器。它的底层主要是先去初始化一个比较大数组,里面存放的二进制 0 或 1。在一开始都是 0,当一个 key 来了之后经过 3 次 hash 计算,模于数组长度找到数据的下标然后把数组中原来的 0 改为 1,这样的话,三个数组的位置就能标明一个 key 的存在。查找的过程也是一样的。当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过 5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。

redis 的淘汰机制是怎么样的?

  • 数据的淘汰策略:当 Redis 中的内存不够用时,此时在向 Redis 中添加新的 key,那么 Redis 就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
  • 这个在 redis 中提供了很多种,默认是 noeviction,不淘汰任何 key,但是内存满时不允许写入新数据。还有随机淘汰以及 LRU(最少最近使用)和 LFU(最少频率使用)等等。

redis 的 IO 多路复用是什么?(没完全理解

Redis 的 IO 多路复用是一种技术,允许 Redis 同时监听多个客户端连接,并在有数据到达时及时处理,提高了 IO 效率和性能。它通过一种机制来管理和处理多个连接,使得 Redis 能够高效地处理大量客户端请求。

商品超卖问题如何解决?

    1. 悲观锁: 在商品购买过程中,使用悲观锁(Pessimistic Locking)对库存进行加锁,确保同一时间只有一个用户可以执行减库存操作,避免并发冲突导致的超卖问题。例如,在数据库层面使用数据库行级锁或者事务进行控制。
  1. 乐观锁: 使用乐观锁(Optimistic Locking)进行并发控制。在购买商品时,先查询当前库存数量,然后在更新库存时进行版本号比对,如果版本号一致才执行更新操作,否则返回库存不足错误。这种方式适用于读多写少的场景。
  2. 分布式锁: 在分布式系统中,使用分布式锁对库存进行加锁,确保同一时间只有一个节点可以执行库存减少操作,避免多个节点之间的并发冲突。
  3. Redis原子操作 Redis与Lua预防库存超卖.Lua可以将redis的多个操作合成一个脚本,然后整体执行,在脚本的执行中,不会出现资源竞争的情况,即保证了操作的原子性。在这里插入图片描述

乐观锁和悲观锁

悲观锁
之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。悲观锁的实现:

  1. 传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
  2. Java 里面的同步 synchronized 关键字的实现。
    乐观锁
    采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁的实现:
  3. **CAS 实现:**Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
  4. **版本号控制:**一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
    在这里插入图片描述

讲讲什么是 Httpclient(DAY06微信登录模块

Httpclient是一个服务器端进行 HTTP 通信的库,他使得后端可以发送各种 HTTP 请求和接收 HTTP 响应,使用 HTTPClient,可以轻松的发送 GET, POST, PUT, DELETE 等各种类型的的请求。
在我们的项目中,在进行微信登录开发时,后端在使用登录凭证校验接口的时候就需要发送指定请求到给定的 URL 中。因此我们使用 Httpclient 去完成该任务。

通过微信登录的流程,如果要完成微信登录的话,最终就要获得微信用户的openid。在小程序端获取授权码后,向后端服务发送请求,并携带授权码,这样后端服务在收到授权码后,就可以去请求微信接口服务。最终,后端向小程序返回openid和token等数据。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

MD5加密

在这里插入图片描述

nginx反向代理与负载均衡(视频P10-P11

在这里插入图片描述

  • 正向代理:
    正向代理是客户端发送请求后通过代理服务器访问目标服务器,代理服务器代表客户端发送请求并将响应返回给客户端。正向代理隐藏了客户端的真实身份和位置信息,为客户端提供代理访问互联网的功能。
  • 反向代理:
    反向代理是指代理服务器接收客户端的请求,然后将请求转发给后端服务器,并将后端服务器的响应返回给客户端。反向代理隐藏了服务器的真实身份和位置信息,客户端只知道与反向代理进行通信,而不知道真正的服务器。

讲一讲员工登录流程

在这里插入图片描述

jwt token ThreadLocal 拦截器(DAY01 – p20

(DAY01用户登录模块
(DAY06微信登录模块

用户登录 – 认证通过 – 生成jwt token返回给前端 – 前端发起请求时携带token – 拦截器请求验证token – 放行/不放行

客户端发起的每一次请求都是一个单独的线程
拦截器验证通过 将用户id通过TreadLocal放入内存,在serviceImpl阶段使用时从内存中拿出获取用户id(在程序中我们已经将TreadLocal封装成一个类 BaseContext

session和cookie

二者区别

①Cookie可以存储在浏览器或者本地,Session只能存在服务器
②session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象
③Session比Cookie更具有安全性(Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击)
④Session占用服务器性能,Session过多,增加服务器压力
⑤单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie,Session是没有大小限制和服务器的内存大小有关。

参考文章

JWT(参考黑马javaweb视频P161-P163

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

ThreadLocal

  • 作用:ThreadLocal 为每一个线程提供一个单独的存储空间,具有线程隔离的作用,只有在同一个线程内才可以获得他的值,以保证线程安全。
  • 在本次开发中,在对 JWT 令牌进行解析,获得当前请求的用户 ID 以后将该 ID 保存至 ThreadLocal 中,以便在之后的操作中查看当前用户。
    在这里插入图片描述

拦截器(参考黑马javaweb视频P16-P170

拦截器的作用是校验JWT令牌 并将用户id通过ThreadLocal放入内存

定义拦截器
在配置类中配置拦截器
在这里插入图片描述

Swagger 作用

  • 定义:Swagger 是一个用于设计、构建和文档化 RESTful Web 服务的工具集。
  • 作用:用来在后端生成接口文档,辅助前端测试。使用 Swagger 只需要按照它的规范定义接口以及接口的相关信息,就可以做到生成接口文档,以及在线接口调试页面。

Knife 4 j

  • Knife 4 j 是 Swagger 的一个增强工具,是基于 Swagger 构建的一款功能强大的文档工具。它提供了一系列注解,用于增强对 API 文档的描述和可视化展示。如在苍穹外卖项目中常用的 Knife 4 j 注解介绍:
    • @Api :用于对 Controller 类进行说明和描述,可以指定 Controller 的名称、描述、标签等信息。
    • @ApiOperation:用于对 Controller 中的方法进行说明和描述,可以指定方法的名称、描述、请求方法(GET、POST 等)等信息。

Pagehelper (Mybatis提供的插件)如何实现分页查询?

PageHelper是MyBatis的一个插件,内部实现了一个PageInterceptor拦截器。Mybatis会加载这个拦截器到拦截器链中。在我们使用过程中先使用PageHelper.startPage这样的语句在当前线程上下文中设置一个ThreadLocal变量,再利用PageInterceptor这个分页拦截器拦截,从ThreadLocal中拿到分页的信息,如果有分页信息拼装分页SQL(limit语句等)进行分页查询,最后再把ThreadLocal中的东西清除掉。

  1. 设置分页参数:在执行查询之前,首先通过 PageHelper.startPage(int pageNum, int pageSize) 方法设置分页的参数,调用该方法时,通过 ThreadLocal 存储分页信息。
  2. 拦截查询语句PageHelper 利用 MyBatis 提供的插件 API(Interceptor 接口)来拦截原始的查询语句。MyBatis 执行任何 SQL 语句前,都会先通过其插件体系中的拦截器链,PageHelper 正是在这个环节介入的。
  3. 修改原始 SQL 语句:在拦截原始查询语句后,PageHelper 会根据分页参数动态地重写或添加 SQL 语句,使其成为一个分页查询。
  4. 执行分页查询:修改后的 SQL 语句被执行,返回当前页的数据。
  5. 查询总记录数(可选):如果需要获取总记录数,PageHelper 会自动执行一个派生的查询,以计算原始查询(不包含分页参数)的总记录数。这通常通过移除原始 SQL 的排序(ORDER BY)和分页(LIMITOFFSET 等)条件,加上 COUNT(*) 的包装来实现。
  6. 返回分页信息:查询结果被封装在 PageInfo 对象中(或其他形式的分页结果对象),这个对象除了包含当前页的数据列表外,还提供了总记录数、总页数、当前页码等分页相关的信息,方便在应用程序中使用。

什么是反射?

反射是一种在程序运行时检查和操作类的机制,通过获取类的信息并动态调用方法、创建对象等。这种机制让程序能够在运行时根据需要动态地获取和操作类的结构和成员。

  1. 获取 Class 对象: 程序通过类的全限定名、对象的 getClass ()方法或. Class 语法来获取对应的 Class 对象。
  2. 查询类信息: 通过 Class 对象可以获取类的信息,包括类名、包名、父类、实现的接口、构造函数、方法、字段等。
  3. 动态创建对象: 通过 Class 对象的 newInstance ()方法调用类的默认构造函数来创建对象,或者通过 Constructor 对象调用类的其他构造函数来创建对象。
  4. 动态调用方法: 通过 Method 对象调用类的方法,传递参数并获取返回值。
  5. 动态访问字段: 通过 Field 对象获取和设置类的字段值。
    整个流程就是通过获取 Class 对象,然后根据需要动态地调用类的方法、创建对象、访问字段等操作,实现了对类的动态操作和调用。

day03–公共字段自动填充(涉及到枚举,注解,AOP,反射

在这里插入图片描述

在这里插入图片描述

spring task 处理定时任务

Spring Task(Spring 任务调度)是 Spring 框架提供的一种任务调度框架,用于执行定时任务、异步任务、任务监听、任务调度等。

在苍穹外卖项目中使用 Spring task

  1. 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”
  2. 通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”

== 或许也可以用RabbitMQ中的延迟队列???==

后端如何与商家端建立链接,实现实时通信?(day10 来单提醒 客户催单

使用 Websocket 来实现用户端和商家端通信:
WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它允许客户端和服务器之间建立持久的、双向的通信通道,使得服务器可以主动向客户端推送消息,而无需客户端发送请求。
客户端和服务器之间可以实时地发送消息和接收消息,不需要频繁地发起请求。这样可以减少网络流量和延迟,并提供更好的用户体验。

  • 通过WebSocket实现管理端页面和服务端保持长连接状态

用户下单并且支付成功后,需要第一时间通知外卖商家。
来单提醒

  • 当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息
  • 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
  • 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
    • type 为消息类型,1为来单提醒 2为客户催单
    • orderId 为订单id
    • content 为消息内容

用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。
*客户催单

  • 通过WebSocket实现管理端页面和服务端保持长连接状态
  • 当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息
  • 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
    约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
    • type 为消息类型,1为来单提醒 2为客户催单
    • orderId 为订单id
    • content 为消息内容

webSocket的作用是什么 怎么实现的 为什么要用ThreadLocal?底层原理是什么

在这里插入图片描述
在这里插入图片描述

Websocket 与 HTTP 有什么区别? 既然 WebSocket 支持双向通信,功能看似比 HTTP 强大,那么是不是可以基于 WebSocket 开发所有的业务功能?

HTTP 协议和 WebSocket 协议对比:

  • HTTP 是短连接
  • WebSocket 是长连接
  • HTTP 通信是单向的,基于请求响应模式
  • WebSocket 支持双向通信
  • HTTP 和 WebSocket 底层都是 TCP 连接

不能使用 WebSocket 并不能完全取代 HTTP,它只适合在特定的场景下使用,原因如下:

  1. 资源开销:WebSocket 需要保持持久连接,对服务器资源有更高要求,不适合所有场景。
  2. 功能与约定:HTTP 提供丰富的功能和约定(如状态码、缓存控制),适合更广泛的业务需求。
  3. 安全性和兼容性:虽然 WebSocket 支持加密,但管理安全性可能更复杂;且某些环境下 WebSocket 不被支持或有限制。
  4. 设计和实践:RESTful API 和相关的 HTTP 设计原则不易直接应用于 WebSocket。

怎么保证在同时操作多张数据库表出现程序错误时保证数据的一致性?

我在涉及多表操作时使用了事务(Transaction): 将涉及到的数据库操作封装在一个事务中。在事务中,要么所有的数据库操作都成功提交,要么全部失败回滚,保证了数据的一致性。如果发生异常,可以通过捕获异常并执行回滚操作来保证数据的一致性。
** 具体操作: **

  • 在启动类上方添加@EnableTransactionManagement
  • 开启事务注解之后,我们只需要在需要捆绑成为一个事务的方法上添加@Transactional
  • 这样就把对两张表的操作捆绑成为了一个事务。

** 项目具体用处 **
在用户下单功能的serviceImpl中
出现的问题是在order表中插入数据成功而因为在orderDetail的mapper.xml中sql语句编写的错误导致orderDetail表中插入数据失败,因此此时需要事务回滚

关于事务的知识(还没看

Spring的事务管理就是对AOP的进一步封装,会为我们提供好一个增强类
在这里插入图片描述

你用什么技术实现数据导出的功能的?

Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。
一般情况下,POI 都是用于操作 Excel 文件。
Apache POI 的应用场景:

  • 银行网银系统导出交易明细
  • 各种业务系统导出Excel报表
  • 批量导入业务数据

项目中的应用

** 产品原型 **
在数据统计页面,有一个数据导出的按钮,点击该按钮时,其实就会下载一个文件。这个文件实际上是一个Excel形式的文件,文件中主要包含最近30日运营相关的数据。表格的形式已经固定,主要由概览数据和明细数据两部分组成。真正导出这个报表之后,相对应的数字就会填充在表格中,就可以进行存档。
项目中的业务规则:

  • 导出Excel形式的报表文件
  • 导出最近30天的运营数据
    项目中的具体做法:
  1. 设计Excel模板文件
  2. 查询近30天的运营数据
  3. 将查询到的运营数据写入模板文件
  4. 通过输出流将Excel文件下载到客户端浏览器

SpringCache(底层基于cglib动态代理技术

  • SpringCache 是 Spring 框架提供的一个抽象层,旨在提供一种透明的方式来缓存应用中的数据。SpringCache 不是一个具体的缓存实现,而是一个集成不同缓存解决方案的接口,如 EHCache、Caffeine、Guava、Redis 等。它允许开发者通过简单的注解来控制方法的缓存行为,例如,使用 @Cacheable 来标记一个方法的返回值应该被缓存,以及使用 @CacheEvict 来标记何时移除缓存。SpringCache 为应用提供了一致的缓存视图,而开发者不需要关心具体使用哪种缓存技术。

  • 简单的说:它也是一种缓存技术,使得所用工具不局限于 Redis。相比较于使用 Redis 的时候需要把相关代码内嵌到方法体种,Spring Cache 是一种基于注解方式来达到内嵌代码相同的效果。

项目中的应用

存在问题: 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。
结果: 系统响应慢、用户体验差
通过Redis来缓存菜品数据,减少数据库查询操作。
缓存逻辑分析:

  • 每个分类下的菜品保存一份缓存数据
  • 数据库中菜品数据有变更时清理缓存数据
  • Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
    Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如: EHCache、 Caffeine、Redis(常用)

为了保证数据库Redis中的数据保持一致,修改管理端接口 DishController 的相关方法,加入清理缓存逻辑。

缓存套餐
1). 导入Spring Cache和Redis相关maven坐标
2). 在启动类上加入@EnableCaching注解,开启缓存注解功能
3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解
4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

stream流

关于事务的知识(还没看

Spring的事务管理就是对AOP的进一步封装,会为我们提供好一个增强类
Spring管理的事务 底层就是AOP,AOP底层用的是动态代理
在这里插入图片描述

AOP

  • 概念:
    AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,以提高代码的模块化性、可维护性和复用性。
    • 横切关注点:
      比如日志、事务、安全性等,这些关注点会横跨多个模块,导致代码重复、耦合性增加、难以维护等问题。AOP 通过将这些横切关注点抽象成一个个“切面”(Aspect),并将其独立于业务逻辑之外,以达到解耦的目的。
  • AOP 的核心概念包括以下几个要素:
    1. 切面(Aspect): 切面是横切关注点的抽象,它包含了一组横切关注点以及在何时何处应用这些关注点的逻辑。通常,切面由一组通知(Advice)和一个切点(Pointcut)组成。
    2. 通知(Advice): 通知是切面中具体的逻辑实现,它定义了在何时何地执行横切关注点的具体行为,包括“前置通知”(Before Advice)、“后置通知”(After Advice)、“环绕通知”(Around Advice)等。
    3. 切点(Pointcut): 切点是在程序中指定的某个位置,通知将在这些位置执行。切点可以使用表达式或其他方式进行定义,以便匹配到程序中的特定方法或代码块。
    4. 连接点(Join Point): 连接点是在程序执行过程中可以应用通知的具体位置,通常是方法调用、方法执行或异常抛出等。
    5. 织入(Weaving): 织入是将切面逻辑应用到目标对象中的过程,可以在编译时、加载时或运行时进行。织入可以通过源代码修改、字节码操作、动态代理等方式实现。

在这里插入图片描述

代理技术

代理模式:
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
== 静态代理==
静态代理,主动创建代理类,将被代理的目标对象声明为成员变量,附加功能由代理类中的代理方法来实现,通过目标对象来实现核心业务逻辑。
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
== 动态代理==
动态代理技术分类

  • JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子)
  • cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹)
    代理总结
    代理方式可以解决附加功能代码干扰核心代码和不方便统一维护的问题!
    他主要是将附加功能代码提取到代理中执行,不干扰目标核心代码!
    但是我们也发现,无论使用静态代理和动态代理(jdk,cglib),程序员的工作都比较繁琐!
    需要自己编写代理工厂等!
    但是,提前剧透,我们在实际开发中,不需要编写代理代码,我们可以使用Spring AOP框架
    他会简化动态代理的实现!!!
    Spring AOP框架,基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架!SpringAOP内部帮助我们实现动态代理,我们只需写少量的配置,指定生效范围即可,即可完成面向切面思维编程的实现!

浮点数精度丢失问题

见该篇讲解

你在项目中遇到了什么困难?

解决前端时间属性显示问题

在这里插入图片描述

解决新增员工时,创建人id和修改人id都为固定值的问题

在这里插入图片描述

处理分页查询问题

在这里插入图片描述

处理公共字段的填充(P31

在这里插入图片描述

spring boot 全局异常处理器

在这里插入图片描述

day03文件上传功能

选用阿里云的OSS服务进行文件存储

  • 49
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值