【前后端的那些事】快速上手富文本+富文本图片上传

文章目录

    • fullText富文本
      • 1. 后端接口
        • 1.1 定义常量
        • 1.2 定义返回实体类
        • 1.3 上传图片接口
        • 1.4 下载图片接口
      • 2. 前端代码编写
        • 2.1 安装
        • 2.2 快速使用
      • 3. 配置富文本图片上传地址
        • 3.1 配置图片上传配置
      • 4. 全部代码展示

前言:最近写项目,发现了一些很有意思的功能,想写文章,录视频把这些内容记录下。但这些功能太零碎,如果为每个功能都单独搭建一个项目,这明显不合适。于是我想,就搭建一个项目,把那些我想将的小功能全部整合到一起。实现 搭一次环境,处处使用。

本文主要实现一下两个功能

  1. 富文本
  2. 图片上传+下载

环境搭建
文章链接

已录制视频
视频链接

fullText富文本

使用wangEditor(vue3) + springboot实现富文本功能

效果图:
在这里插入图片描述

1. 后端接口

图片存储的逻辑:

  • 接收前端传递图片数据
  • 将图片下载到后端本地
  • 返回图片访问URL

图片下载的逻辑:

  • 提供下载文件的名字
  • 在后端服务器根据文件名寻找文件所在位置
  • 将文件以流数据形式导出,并通过HttpServletResponse返回

tip: 图片访问URL,本质上是访问下载文件接口URL

1.1 定义常量
/**
* 文件访问域名(请求下载的接口) 
* DOMAIN本质是访问图片下载接口
*/
private static final String DOMAIN = "http://localhost:9005/api_demo/fullText/file/download/";/**
* 文件物理存储位置
*/
private static final String STORE_DIR = "E:\\B站视频创作\\前后端项目构建-小功能实现\\代码\\backend\\src\\main\\resources\\pict\\";
1.2 定义返回实体类
    static class Success {public final int errno;public final Object data;public Success(String url) {this.errno = 0;HashMap<String, String> map = new HashMap<>();map.put("url", url);this.data = map;}}

tip: 后端接口返回的图片需要按照一定的格式返回,具体可以参考文档[图片上传](菜单配置 | wangEditor)

  • 上传成功
{"errno": 0, // 注意:值是数字,不能是字符串"data": {"url": "xxx", // 图片 src ,必须"alt": "yyy", // 图片描述文字,非必须"href": "zzz" // 图片的链接,非必须}
}
  • 上传失败
{"errno": 1, // 只要不等于 0 就行"message": "失败信息"
}
1.3 上传图片接口
    /*** 获取后缀*/public static String getFileSuffix(String fileName) {// 检查文件名是否为null或空if (fileName == null || fileName.isEmpty()) {return "";}// 查找最后一个点(.)的位置int dotIndex = fileName.lastIndexOf('.');// 检查是否找到点,且不是在字符串开头if (dotIndex > 0) {// 从点开始截取,直到字符串末尾return fileName.substring(dotIndex);}// 如果没有找到点,或点在字符串开头,则返回空字符串return "";}/*** 上传文件接口* @param file* @return* @throws IOException*/@RequestMapping("/file/upload")public Object uploadPict(@RequestParam("image") MultipartFile file) throws IOException {// 获取文件流InputStream is = file.getInputStream();// 获取文件真实名字String fileName = UUID.randomUUID().toString().substring(0, 10) + getFileSuffix(file.getOriginalFilename());// 在服务器中存储文件FileUtils.copyInputStreamToFile(is, new File(STORE_DIR + fileName));// 返回图片urlString url = DOMAIN + fileName;return new Success(url);}
1.4 下载图片接口
    /*** 文件下载接口* @param fileName 文件名* @param request* @param response*/@GetMapping("/file/download/{fileName}")public void download(@PathVariable("fileName") String fileName, HttpServletRequest request, HttpServletResponse response) {// 获取真实的文件路径String filePath = STORE_DIR + fileName;System.out.println("++++完整路径为:"+filePath);try {// 下载文件// 设置响应头response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);// 读取文件内容并写入输出流Files.copy(Paths.get(filePath), response.getOutputStream());response.getOutputStream().flush();} catch (IOException e) {response.setStatus(404);}}

2. 前端代码编写

2.1 安装
pnpm install @wangeditor/editor --savepnpm install @wangeditor/editor-for-vue@next --save
2.2 快速使用

模板

<template><div style="border: 1px solid #ccc"><Toolbarstyle="border-bottom: 1px solid #ccc":editor="editorRef":mode="mode"/><Editorstyle="height: 500px; overflow-y: hidden"v-model="valueHtml":defaultConfig="editorConfig":mode="mode"@onCreated="handleCreated"/></div>
</template>

script

使用setup语法糖

<script setup lang="ts">
import "@wangeditor/editor/dist/css/style.css";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { IEditorConfig } from "@wangeditor/editor";
import { shallowRef, ref } from "vue";// 初始化 MENU_CONF 属性
const editorConfig: Partial<IEditorConfig> = {MENU_CONF: {}
};
const mode = "default";// 编辑器实例,必须用 shallowRef,重要!
const editorRef = shallowRef();const handleCreated = editor => {console.log("created", editor);editorRef.value = editor; // 记录 editor 实例,重要!
};// 绑定数据
const valueHtml = ref("");// 组件销毁时,也及时销毁编辑器,重要!
onBeforeUnmount(() => {const editor = editorRef.value;if (editor == null) return;editor.destroy();
});
</script>

3. 配置富文本图片上传地址

3.1 配置图片上传配置
<script>
// 配置上传地址
editorConfig.MENU_CONF["uploadImage"] = {// form-data fieldName ,默认值 'wangeditor-uploaded-image'fieldName: "image",server: baseUrlApi("fullText/file/upload"),// 小于该值就插入 base64 格式(而不上传),默认为 0base64LimitSize: 5 * 1024 // 5kb
};
</script>

tip: fieldName对应的是后端的文件上传接口:@RequestParam(“xxx”) MultipartFile中xxx的内容

4. 全部代码展示

  • 前端

    <script setup lang="ts">
    import "@wangeditor/editor/dist/css/style.css";
    import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
    import { IEditorConfig } from "@wangeditor/editor";
    import { shallowRef, ref, onBeforeUnmount } from "vue";
    import { baseUrlApi } from "@/api/utils";// 初始化 MENU_CONF 属性
    const editorConfig: Partial<IEditorConfig> = {MENU_CONF: {}
    };
    const mode = "default";// 编辑器实例,必须用 shallowRef,重要!
    const editorRef = shallowRef();const handleCreated = editor => {console.log("created", editor);editorRef.value = editor; // 记录 editor 实例,重要!
    };// 绑定数据
    const valueHtml = ref("");// 组件销毁时,也及时销毁编辑器,重要!
    onBeforeUnmount(() => {const editor = editorRef.value;if (editor == null) return;editor.destroy();
    });// 配置上传地址
    editorConfig.MENU_CONF["uploadImage"] = {// form-data fieldName ,默认值 'wangeditor-uploaded-image'fieldName: "image",server: baseUrlApi("fullText/file/upload"),// 小于该值就插入 base64 格式(而不上传),默认为 0base64LimitSize: 5 * 1024 // 5kb
    };const handleChange = editor => {// TS 语法console.log("content", editor.getHtml());
    };
    </script><template><div style="border: 1px solid #ccc; margin-top: 10px"><Toolbarstyle="border-bottom: 1px solid #ccc":editor="editorRef":mode="mode"/><Editorstyle="height: 500px; overflow-y: hidden"v-model="valueHtml":defaultConfig="editorConfig":mode="mode"@onCreated="handleCreated"@onChange="handleChange"/></div>
    </template><style lang="scss" scoped></style>
    
  • 后端

    @RequestMapping("/fullText")
    @RestController
    public class FullTextController {/*** 文件访问域名(请求下载的接口)*/private static final String DOMAIN = "http://localhost:9005/api_demo/fullText/file/download/";/*** 文件物理存储位置*/private static final String STORE_DIR = "E:\\B站视频创作\\前后端项目构建-小功能实现\\代码\\backend\\src\\main\\resources\\pict\\";static class Success {public final int errno;public final Object data;public Success(String url) {this.errno = 0;HashMap<String, String> map = new HashMap<>();map.put("url", url);this.data = map;}}/*** 获取后缀*/public static String getFileSuffix(String fileName) {// 检查文件名是否为null或空if (fileName == null || fileName.isEmpty()) {return "";}// 查找最后一个点(.)的位置int dotIndex = fileName.lastIndexOf('.');// 检查是否找到点,且不是在字符串开头if (dotIndex > 0) {// 从点开始截取,直到字符串末尾return fileName.substring(dotIndex);}// 如果没有找到点,或点在字符串开头,则返回空字符串return "";}/*** 上传文件接口* @param file* @return* @throws IOException*/@RequestMapping("/file/upload")public Object uploadPict(@RequestParam("image") MultipartFile file) throws IOException {// 获取文件流InputStream is = file.getInputStream();// 获取文件真实名字String fileName = UUID.randomUUID().toString().substring(0, 10) + getFileSuffix(file.getOriginalFilename());// 在服务器中存储文件FileUtils.copyInputStreamToFile(is, new File(STORE_DIR + fileName));// 返回图片urlString url = DOMAIN + fileName;return new Success(url);}/*** 文件下载接口* @param fileName 文件名* @param request* @param response*/@GetMapping("/file/download/{fileName}")public void download(@PathVariable("fileName") String fileName, HttpServletRequest request, HttpServletResponse response) {// 获取真实的文件路径String filePath = STORE_DIR + fileName;System.out.println("++++完整路径为:"+filePath);try {// 下载文件// 设置响应头response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);// 读取文件内容并写入输出流Files.copy(Paths.get(filePath), response.getOutputStream());response.getOutputStream().flush();} catch (IOException e) {response.setStatus(404);}}
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/618522.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RTMP 视频数据封装

RTMP 协议 与HTTP(超文本传输协议)同样是一个基于TCP的Real Time Messaging Protocol(实时消息传输协议)。由Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的一种开放协议 。在国内被广泛的应用于直 播领域。HTTP默认端口为80&#xff0c;RTMP则为1935…

上海AI实验室等开源,音频、音乐统一开发工具包Amphion

上海AI实验室、香港中文大学数据科学院、深圳大数据研究院联合开源了一个名为Amphion的音频、音乐和语音生成工具包。 Amphion可帮助开发人员研究文本生成音频、音乐等与音频相关的领域&#xff0c;可以在一个框架内完成&#xff0c;以解决生成模型黑箱、代码库分散、缺少评估…

蓝桥杯AcWing学习笔记 8-1数论的学习(上)

蓝桥杯 我的AcWing 题目及图片来自蓝桥杯C AB组辅导课 数论&#xff08;上&#xff09; 蓝桥杯省赛中考的数论不是很多&#xff0c;这里讲几个蓝桥杯常考的知识点。 欧几里得算法——辗转相除法 欧几里得算法代码&#xff1a; import java.util.Scanner ;public class Main…

现代火箭发展的一个重要方向是无人火箭技术

现代火箭发展的一个重要方向是无人火箭技术。无人火箭指的是不需要人员操控的火箭系统&#xff0c;可以自主进行发射、飞行、返回和着陆等任务。 在无人火箭发展方面&#xff0c;有以下重要形态和应用&#xff1a; 空间探测&#xff1a;无人火箭可以被用于太空探测任务&#x…

无人驾驶技术在交通领域逐渐成熟

随着技术的不断发展&#xff0c;无人驾驶技术在交通领域逐渐成熟&#xff0c;对于无人站台、无人公交车和无人公交车综合系统的发展也变得越来越重要。以下是这些系统的重要发展形势&#xff1a; 无人站台&#xff1a;无人站台可以提供自动售票、乘客导航和安全监控等功能&…

Gradle的安装及换源详解

Gradle是一种自动化构建工具&#xff0c;基于Apache Ant和Apache Maven的概念。以下是Gradle的安装步骤和换源方法&#xff1a; 下载安装包&#xff1a;可以从官网下载Gradle的安装包。为了避免官网下载速度慢&#xff0c;也可以通过腾讯的镜像地址进行下载。例如&#xff0c;…

解决Android Studio Unexpected tokens (use ; to separate expressions on the same line)

[TOC](Unexpected tokens (use ; to separate expressions on the same line)) 问题描述&#xff1a;Unexpected tokens (use ; to separate expressions on the same line) 原因&#xff1a;Android Studio 更新到最新的版本之后&#xff0c;gradle工程目录结构发生改变 问…

【华为鸿蒙】HarmonyOS概述:技术特性

来源&#xff1a;HarmnyOS 官网 https://developer.harmonyos.com/cn/docs/documentation/doc-guides/harmonyos-features-0000000000011907 技术特性 硬件互助&#xff0c;资源共享 多种设备之间能够实现硬件互助、资源共享&#xff0c;依赖的关键技术包括分布式软总线、分布…

Vant-ui图片懒加载

核心代码 在你的全局顶部引入和初始化 Vue.use(vant.Lazyload, {loading: /StaticFile/img/jiazai.jpg,error: /StaticFile/img/jiazai.jpg,lazyComponent: false, });//图片懒加载 <img v-lazy"https://img-blog.csdnimg.cn/direct/3d2c8a7e2c0040488a8128c3e381d58…

CenOS系统软件依赖包安装常见命令

sudo yum --excludekernel* --excludecentos-release* -y update 这个命令的含义是使用sudo权限运行yum命令&#xff0c;通过更新软件包来升级系统。 具体选项的解释如下&#xff1a; --excludekernel*&#xff1a;排除以"kernel"开头的所有软件包&#xff0c;即不…

8.云原生存储之Ceph集群

1. 私有云实战之基础环境搭建 2. 云原生实战之kubesphere搭建 3.云原生之kubesphere运维 4. 云原生之kubesphere基础服务搭建 5.云原生安全之kubesphere应用网关配置域名TLS证书 6.云原生之DevOps和CICD 7.云原生之jenkins集成SonarQube 8.云原生存储之Ceph集群 文章目录 为什么…

DynastyPersist:一款功能强大的Linux持久化安全审计与测试工具

关于DynastyPersist DynastyPersist是一款专为红队研究人员和CTF玩家设计的Linux安全测试工具&#xff0c;该工具可以适用于各种安全评估任务和安全测试场景。 DynastyPersist本质上是一个Linux持久化脚本&#xff0c;并提供了大量的安全测试功能&#xff0c;可以为我们展示在…

Java多线程并发篇----第十一篇

系列文章目录 文章目录 系列文章目录前言一、什么是悲观锁二、什么是自旋锁三、Synchronized 同步锁前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一、什么是悲观…

【大数据架构】日志采集方案对比

整体架构 日志采集端 Flume Flume的设计宗旨是向Hadoop集群批量导入基于事件的海量数据。系统中最核心的角色是agent&#xff0c;Flume采集系统就是由一个个agent所连接起来形成。每一个agent相当于一个数据传递员&#xff0c;内部有三个组件&#xff1a; source: 采集源&…

json类型转换对象含有泛型

接受数据的对象1 public class Student<T> {/*** 学号*/private String studentNumber;public String getStudentNumber() {return studentNumber;}public void setStudentNumber(String studentNumber) {this.studentNumber studentNumber;}public String getStudentN…

Unity中URP下实现深度贴花(雾效支持和BRP适配)

文章目录 前言一、让我们的贴画支持雾效1、我们舍弃内部的MixFog方法2、使用 雾效混合因子 对最后输出颜色进行线性插值相乘 二、在Shader中&#xff0c;限制贴花纹理的采样方式1、申明 纹理 和 限制采样方式的采样器2、在片元着色器进行纹理采样 三、BRP适配1、C#脚本中&#…

贪心算法的“左最优“与“右最优“及其对应的堆处理和预处理方法

1 答疑 1.1 什么是贪心算法的"左最优"与"右最优" "左最优"和"右最优"是贪心算法中的两种策略&#xff1a; 左最优 (Leftmost Greedy): 在每一步选择中&#xff0c;总是选择最左边&#xff08;最早出现的&#xff09;可行的选项。 右…

仿真验证方法(3)——物理验证

目录 一、物理验证的分类 二、DRC 2.1 设计规则 2.2 规则示例 2.3 线宽违例 2.4 间距违例 2.5 交叠违例 三、金属覆盖图形密度检查 四、天线比率检查 4.1 起因 4.2 计算 4.3 改进 五、LVS检查 六、物理验证常用的EDA工具 七、总结 一、物理验证的分类 对于物理验…

专业课145+合肥工业大学833信号分析与处理考研经验合工大电子信息通信

今年专业课145也是考研科目中最满意的一门&#xff0c;其他基本相对平平&#xff0c;所以这里我总结一下自己的专业课合肥工业大学833信号分析与处理的复习经验。 我所用的教材是郑君里的《信号与系统》&#xff08;第三版&#xff09;和高西全、丁玉美的《数字信号处理》&…

java方法的定义和使用

方法 今日目标&#xff1a; 能够知道方法的好处 能够根据两个明确分析方法的参数和返回值 能够编写方法完成授课案例&#xff0c;并在主方法中完成方法的调用 能够知道方法重载及其特点 1&#xff1a;方法的定义和使用 1.1 方法概述 方法(method)&#xff1a;就是完成特…