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

文章目录

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

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

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

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

环境搭建
文章链接

已录制视频
视频链接

仓库地址
https://github.com/xuhuafeifei/fgbg-font-and-back.git

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));
        // 返回图片url
        String 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 --save

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

模板

<template>
  <div style="border: 1px solid #ccc">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editorRef"
      :mode="mode"
    />
    <Editor
      style="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 格式(而不上传),默认为 0
  base64LimitSize: 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 格式(而不上传),默认为 0
      base64LimitSize: 5 * 1024 // 5kb
    };
    
    const handleChange = editor => {
      // TS 语法
      console.log("content", editor.getHtml());
    };
    </script>
    
    <template>
      <div style="border: 1px solid #ccc; margin-top: 10px">
        <Toolbar
          style="border-bottom: 1px solid #ccc"
          :editor="editorRef"
          :mode="mode"
        />
        <Editor
          style="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));
            // 返回图片url
            String 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.mfbz.cn/a/319466.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于卡尔曼滤波的视频跟踪,基于卡尔曼滤波的运动小球跟踪

目录 完整代码和数据下载链接&#xff1a;基于卡尔曼滤波的视频跟踪&#xff0c;基于卡尔曼滤波的运动小球跟踪&#xff08;代码完整&#xff0c;数据齐全&#xff09;资源-CSDN文库 https://download.csdn.net/download/abc991835105/88738577 卡尔曼滤波原理 RBF的定义 RBF理…

rime中州韵小狼毫 敏感词脱敏滤镜

快速录入&#xff0c;是任何一个输入法&#xff0c;以及输入人员&#xff08;无论是否专业&#xff09;的追求目标之一。现实中&#xff0c;由于各种输入法在录入文本时&#xff0c;都无法完全避免重码的问题&#xff0c;所以在输入过程中都或多或少的需要进行选字/选词操作。这…

camtasia studio2024免费版如何下载?怎么录屏?

camtasia studio怎么录屏&#xff1f;Camtasia Studio是一款专门录制屏幕动作的工具&#xff0c;它能在任何颜色模式下轻松地记录屏幕动作&#xff0c;包括影像、音效、鼠标移动轨迹、解说声音等等。一般情况下&#xff0c;用户使用camtasia studio进行录屏时&#xff0c;需要注…

计算机网络夯实之路-HTTP详解

了解 Web 及网络基础 根据 Web浏览器地址栏中指定的 URL&#xff0c;Web浏览器从Web服务器端获取文件资源&#xff08;resource&#xff09;等信息&#xff0c;从而显示出 Web 页面。 通过发送请求获取服务器资源的 Web 浏览器等&#xff0c;都可称为客户端&#xff08;clien…

RibbonGroup 添加QRadioButton

RibbonGroup添加QRadioButton&#xff1a; QRadioButton * pRadio new QRadioButton(tr("Radio")); pRadio->setToolTip(tr("Radio")); groupClipboard->addWidget(pRadio); connect(pRadio, SIGNAL(clicked(…

如何使用C++编程使得在Windows和Linux输入密码的时候保密 linux:tcgetattr tcsetattr

在C编程中&#xff0c;在执行一些操作的时候&#xff0c;终端需要接收用户名和密码&#xff0c;那么在终端输入密码的时候&#xff0c;如何不让别人看见自己的密码&#xff0c;是一个较为关注的问题&#xff1b; 1、问题分析 定义一个登录函数Login //用户登录主循环bool Lo…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机的高速图像保存(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机的高速图像保存&#xff08;C&#xff09;&#xff09; Baumer工业相机Baumer工业相机的图像高速保存的技术背景Baumer工业相机通过NEOAPI SDK函数图像高速保存在NEOAPI SDK里实现线程高速图像保存&#xff1a;工业相机高…

(分享) 音乐软件Spotify-声破天8.9.4

​【应用名称】&#xff1a;Spotify-声破天 ​【适用平台】&#xff1a;#Android ​【软件标签】&#xff1a;#Spotify ​【应用版本】&#xff1a;8.8.96 → 8.9.4 ​【应用大小】&#xff1a;67MB ​【软件说明】&#xff1a;软件升级更新。iOS可配合qx小火箭类的工具对…

SpringMVC RESTful

文章目录 1、RESTful简介a>资源b>资源的表述c>状态转移 2、RESTful的实现3、HiddenHttpMethodFilter 1、RESTful简介 REST&#xff1a;Representational State Transfer&#xff0c;表现层资源状态转移。 a>资源 资源是一种看待服务器的方式&#xff0c;即&…

一、Mindspore 公开课 - Transformer

课程链接&#xff1a;Mindspore 技术公开课 Transformer 论文地址&#xff0c;建议看完课程以后简单看看论文 前言 Transformer是一种神经网络结构&#xff0c;由Vaswani等人在2017年的论文“Attention Is All You Need” 中提出&#xff0c;用于处理机器翻译、语言建模和文…

行为型设计模式——命令模式

命令模式 日常生活中&#xff0c;我们出去吃饭都会遇到下面的场景。 定义&#xff1a; 将一个请求封装为一个对象&#xff0c;使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通&#xff0c;这样方便将命令对象进行存储、传递、调用、增加与管理。命…

【Golang开源项目】Golang高性能内存缓存库BigCache设计与分析

项目地址 BigCache 是一个快速&#xff0c;支持并发访问&#xff0c;自淘汰的内存型缓存&#xff0c;可以在存储大量元素时依然保持高性能。BigCache将元素保存在堆上却避免了GC的开销。 背景介绍 BigCache的作者在项目里遇到了如下的需求&#xff1a; 支持http协议支持 10…

四、Qt 的第一个demo

在上一篇章节里《三、Qt Creator 使用》&#xff0c;我们介绍了如何使用Qt Creator创建一个简单的带窗体的demo&#xff0c;在这一章节里&#xff0c;我们详细讲解一下这个demo的文件组成&#xff0c;及主函数&#xff0c;并在UI上加一些控件&#xff0c;实现一些简单的功能。 …

C# Winform翻牌子记忆小游戏

效果 源码 新建一个winform项目命名为Matching Game&#xff0c;选用.net core 6框架 并把Form1.cs代码修改为 using Timer System.Windows.Forms.Timer;namespace Matching_Game {public partial class Form1 : Form{private const int row 4;private const int col 4;p…

yolov5口罩检测模型

1 项目背景及意义 全球范围内的公共卫生安全和人脸识别技术的发展。在面对新型冠状病毒等传染病的爆发和传播风险时&#xff0c;佩戴口罩成为一种重要的防护措施。然而&#xff0c;现有的人脸识别系统在识别戴口罩的人脸时存在一定的困难。 通过口罩识别技术&#xff0c;可以…

分布式系统中的CAP原理

分布式系统中的CAP原理 本文已收录至我的个人网站&#xff1a;程序员波特&#xff0c;主要记录Java相关技术系列教程&#xff0c;共享电子书、Java学习路线、视频教程、简历模板和面试题等学习资源&#xff0c;让想要学习的你&#xff0c;不再迷茫。 简介 在分布式系统中&…

LeetCode刷题(ACM模式)-05栈与队列

参考引用&#xff1a;代码随想录 注&#xff1a;每道 LeetCode 题目都使用 ACM 代码模式&#xff0c;可直接在本地运行&#xff0c;蓝色字体为题目超链接 0. 栈与队列理论基础 21天学通C读书笔记&#xff08;二十三&#xff1a;自适应容器&#xff1a;栈和队列&#xff09; 堆…

利用GraalVM将java文件变成exe可执行文件

上期文章已经配置好了环境&#xff1a;Springboot3新特性&#xff1a;开发第一个 GraalVM 本机应用程序(完整教程)-CSDN博客 在桌面上创建一个HelloWorld.java的文件。 public class HelloWorld{public static void main (String[] args){System.out.println("Hello,Wor…

Clickhouse表引擎之CollapsingMergeTree引擎的原理与使用

前言 继续上次关于clickhouse的一些踩坑点&#xff0c;今天讲讲另外一个表引擎——CollapsingMergeTree。这个对于引擎对于数据量较大的场景是个不错的选择。注意&#xff0c;选择clickhouse的一般原因都是为了高效率查询&#xff0c;提高用户体验感&#xff0c;说白了就是以空…
最新文章