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

news2024/9/21 2:37:57

文章目录

    • 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));
        // 返回图片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.coloradmin.cn/o/1381765.html

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

相关文章

【JaveWeb教程】(23) MySQL数据库开发之事务与索引 详细代码示例讲解(最全面)

目录 2. 事务2.1 介绍2.2 操作2.3 四大特性 3. 索引3.1 介绍3.2 结构3.3 语法 2. 事务 场景&#xff1a;学工部整个部门解散了&#xff0c;该部门及部门下的员工都需要删除了。 操作&#xff1a; -- 删除学工部 delete from dept where id 1; -- 删除成功-- 删除学工部的员工…

定制一套ERP系统大概要多少钱?ERP软件定制报价

定制一套ERP系统大概要多少钱&#xff1f;ERP软件定制报价 每个企业的需求和情况都是独特的&#xff0c;在不清楚题主所在企业的规模、业务流程、所需功能等情况时&#xff0c;确实没办法给出项目预算。 我们公司也定制过管理系统&#xff0c;经验就是&#xff0c;建议在开始…

MySQL数据库设计原则

0.简单的处理逻辑 一.MySQL完整性约束 主键约束 primary key 自增键约束 auto_increment 唯一键约束 unique 非空约束 not null 默认值约束 default 外键约束 foreign key 下面是一个sql语句创建一个表,可以看出来了使用了哪几个约束吗? create table user( id int…

如何将重复方法封装为Aop切面并结合注解使用

首先要导入依赖 <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId> </dependency> 编写注解 package com.yg.domain.note;import java.lang.annotation.ElementType; import java.lang.annotation.Rete…

u盘监控系统—公司电脑如何监控U盘使用?【详解】

在当今的办公环境中&#xff0c;U盘等移动存储设备已成为数据传输和存储的重要工具。 然而&#xff0c;随着U盘的广泛使用&#xff0c;也带来了潜在的安全风险&#xff0c;如数据泄露、病毒传播等。 因此&#xff0c;对于随时会有数据泄露风险的企业而言&#xff0c;U盘的使用…

Jetpack Compose -> 声明式UI Modifier

前言 本章主要介绍下 Compose 的声明式 UI 以及初级写法&#xff1b; 什么是声明式UI 传统UI 传统 UI 方式来声明UI <androidx.appcompat.widget.LinearLayoutCompat android:layout_width"match_parent" android:layout_height"match_parent&quo…

(Java企业 / 公司项目)分布式事务Seata详解(含Seata+Nacos组合使用)

一. Seata介绍 Seata 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前&#xff0c;其内部版本在阿里系内部一直扮演着应用架构层数据一致性的中间件角色&#xff0c;帮助经济体平稳的度过历年的双11&…

Ubuntu22.04,Nvidia4070配置llama2

大部分内容参考了这篇非常详细的博客&#xff0c;是我最近看到的为数不多的保姆级别的教学博客&#xff0c;建议大家去给博主点个赞【Ubuntu 20.04安装和深度学习环境搭建 4090显卡】_ubuntu20.04安装40系显卡驱动-CSDN博客 本篇主要是基于这篇博客结合自己配置的过程中一些注…

soc算法【周末总结】

1 实验一&#xff08;SOC误差30%放电实验&#xff09; 1.1 实验过程 1、对电池包进行充电&#xff0c;将昨天放空的电池包进行充电&#xff0c;充电至SOC40%左右&#xff1b; 2、电池包SOC为38%时&#xff0c;手动修改SOC值为70%&#xff0c;开始放电 3、SOC由70%缓慢降至4…

BUUCTFMisc (我flag呢???)

刚好&#xff0c;更完密码学&#xff0c;然后就到下一个对新手还算 “友好” 的地方了--->Misc&#xff08;但他还是比密码学难&#xff09; 找不出flag belike &#xff1a; 看了别人的找法以后be like&#xff1a; 这里我就来讲几道我觉得比较有意思的题目吧&…

一键批量翻译,文件夹名称翻译器

文件夹名称往往是我们初步了解文件内容的重要窗口。有时&#xff0c;为了更好地与国际合作伙伴交流或是管理个人文件&#xff0c;我们需要对文件夹名称进行翻译。传统的逐一修改方法既费时又费力&#xff0c;还要借助翻译工具。现在有了【文件批量改名高手】&#xff0c;上面的…

PMP学习考试经验总结

PMP备考日程计划表 我的PMP的备考大概花了三个月的时间, 可以分为以下几个阶段&#xff1a; Week 1-4: 读完PMBoK 前面7个知识领域&#xff08;中英文版PMBoK一起看&#xff09;。每看完一个知识领域&#xff0c;就看参考书里面的相应章节&#xff08;汪博士那本&#xff09;…

oracle 19c容器数据库data dump数据泵传输数据(3)---完全传输

目录 查看pdb1 创建pdb2 从pdb1 中导出元数据 在pdb2中导入元数据&#xff08;dmp文件&#xff09; Full Transportable Export/Import: Example 只传输除了system&#xff0c;sysaux&#xff0c;temp&#xff0c;undo以外的用户表空间&#xff0c;這種方式傳輸的是用戶自定…

6.4、SDN在云数据中心的应用案例分析

云数据中心中的虚拟子网包含网关和IP网段,IP分配给各个服务器,服务器间能够互相通信或通过网关访问外部网络。 在SDN云数据中心内,用户可以随时订购任意网段的虚拟子网,而且这些子网是可以在不同用户之间复用的,也就是说,不同用户可以使用相同的私有网段。 SDN云数据中心…

Linux截图方法推荐

因为经常会遇到以图为证的情况&#xff0c;而办公设备基本都是linux,所以汇总一下常见的linux截图方式。 1&#xff1a;在 Linux 中系统集成的截图的默认方式 你想要截取整个屏幕&#xff1f;屏幕中的某个区域&#xff1f;某个特定的窗口&#xff1f; 如果只需要获取一张屏幕…

SSL安全证书普及

首先&#xff0c;我们来了解一下什么是SSL安全证书。SSL安全证书是一种遵循SSL协议的数字证书&#xff0c;由受信任的第三方机构——认证中心&#xff08;CA&#xff09;颁发&#xff0c;其主要功能是为互联网通信提供加密处理&#xff0c;确保在用户浏览器和服务器之间传输的信…

深入分析 Spring 中 Bean 名称的加载机制

目录 前言 通过前文&#xff1a;《深入分析-Spring BeanDefinition构造元信息》一文我们可以了解到&#xff1a;Spring Framework共有三种方式可以定义Bean&#xff0c;分别为&#xff1a;XML配置文件、注解、Java配置类&#xff0c; 从Spring Framework 3.0&#xff08;2019年…

美力AI变革:生成式AI在美妆和时尚领域的巨大改变

美妆AI技术解决方案提供商—玩美移动于今日发布最新全球趋势报告&#xff1a;《生成式AI在美妆和时尚领域的巨大改变》&#xff0c;就生成式AI在美妆和时尚行业的崛起&#xff0c;为品牌商提供了富有洞见的深入分析。该报告分析了来自玩美移动屡获殊荣的玩美系列APP应用套件的大…

Linux Bonding 技术解析与配置指南

介绍 在复杂的网络环境中&#xff0c;为了提高带宽、负载均衡和冗余备份&#xff0c;Linux 提供了 Bonding 技术。Bonding 技术允许将多个物理网络接口绑定在一起&#xff0c;形成一个逻辑接口&#xff0c;以提高网络性能和可用性。 Bonding 七种模式 Linux Bonding 支持多种模…

jar包部署到linux虚拟机的docker中之后连不上mysql

前言&#xff1a; 跟着黑马学习docker的时候&#xff0c;将java项目部署到了docker中&#xff0c;运行访问报错&#xff0c;反馈连不上mysql。 错误描述&#xff1a; 方法解决&#xff1a; 概述&#xff1a;在虚拟中中&#xff0c;我进入项目容器的内部&#xff0c;尝试ping…