跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

SpringBoot 登录认证全栈实现:Session、统一结果封装、MD5 加密与拦截器

SpringBoot 登录认证方案整合 Session 管理、统一返回结果封装、MD5 加盐加密及拦截器机制。通过三层架构设计 Controller、Service、Mapper,实现用户信息持久化与状态保持。引入 Hutool 生成图形验证码防止自动化攻击,结合 SecurityUtil 工具类完成密码安全存储与校验。配置 WebMvcConfigurer 注册拦截器拦截未授权请求,确保前后端交互一致性与安全性。

KernelLab发布于 2026/3/21更新于 2026/5/2213 浏览
SpringBoot 登录认证全栈实现:Session、统一结果封装、MD5 加密与拦截器

概述

目的: Spring 生态为 Java 后端开发提供了强大支持,但将分散的技术点整合成完整解决方案往往令人困惑。本文将以登录接口为切入点,系统演示如何将 IOC/DI、MyBatis 数据持久化、MD5 加密、Session/Cookie 管理、JWT 令牌和拦截器机制融合运用,打造企业级认证方案。 技术栈: 前端:HTML + CSS + JavaScript + jQuery;后端:SpringBoot + Mybatis + JWT

搭建环境: 数据库:MySQL8.4.0;项目结构:Maven;前端框架:jQuery;后端框架:SpringBoot;JDK:17;编译器:IDEA

项目搭建及配置

  1. 创建 SpringBoot3.0.0+ 项目并添加依赖:Spring Web、MyBatis Framework、MySQL Driver、Lombok
  2. 初始化数据库:
CREATE DATABASE spring_blog_login CHARACTER SET utf8mb4;
USE spring_blog_login;
CREATE TABLE user_info (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(128) UNIQUE,
    password VARCHAR(128) NOT NULL,
    delete_flag INT DEFAULT 0,
    create_time DATETIME DEFAULT NOW(),
    update_time DATETIME DEFAULT NOW()
);
INSERT INTO user_info (user_name, password) VALUES ('张三', '123456'), ('李四', '123456'), ('王五', '123456');
  1. 将 application.properties 修改为 application.yml 并添加如下配置:
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/spring_blog_login?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  mybatis:
    configuration:
      map-underscore-to-camel-case: true
server:
  port: 8080

按住 Ctrl + F5,如果程序能运行成功则说明搭建及配置都没问题(MySQL 服务器必须要处于运行状态)。

图片描述

1. 登录认证全栈实现 -> 基础版

1.1 后端实现

1.1.1 架构设计

本次登录功能采用 Controller、Service、Mapper 三层架构:Controller 层依赖于 Service 层来执行业务逻辑并获取处理结果,而 Service 层又依赖于 Mapper 层来进行数据持久化操作

图片描述

1.1.2 实体类

实体类用于封装业务数据,需要与数据库表结构一一对应

import lombok.Data;
import java.util.Date;

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}
1.1.3 Controller

处理 HTTP 请求、参数校验、返回响应

import org.example.springlogin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/login")
    public String login(String userName, String password) {
        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return "用户或密码为空";
        }
        return userService.getUserInfoByUserName(userName, password);
    }
}
1.1.4 Service

业务逻辑处理

import org.example.springlogin.mapper.UserMapper;
import org.example.springlogin.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserMapper userMapper;

    @Autowired
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public String getUserInfoByUserName(String userName, String password) {
        UserInfo userInfo = userMapper.getUserInfoByUserName(userName);
        if (userInfo == null) {
            return "用户不存在";
        }
        if (!password.equals(userInfo.getPassword())) {
            return "密码错误";
        }
        return "登录成功";
    }
}
1.1.5 Mapper

数据持久化操作

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.springlogin.model.UserInfo;

@Mapper
public interface UserMapper {
    @Select("select * from user_info where user_name = #{userName}")
    UserInfo getUserInfoByUserName(String userName);
}

1.2 前端实现

效果演示:

4. 登录成功

图片描述

3. 密码错误

图片描述

2. 用户不存在

图片描述

1. 用户或密码为空

图片描述

2. Cookie/Session

HTTP(超文本传输协议) 设计为无状态协议,指服务器默认不保留客户端请求之间的任何状态信息。每个请求独立处理,服务器不会记忆之前的交互内容 (如下图)

优点:请求独立性:每次请求被视为新请求,服务器不依赖历史请求数据;简单高效:无状态设计降低服务器资源消耗,简化实现逻辑

缺点:身份识别困难:需通过额外机制 (如 Cookies、Session) 跟踪用户状态;重复传输数据:每次请求需携带完整信息,可能增加冗余 (如认证信息)

cookie:是存储在客户端 (浏览器) 的小型文本数据,由服务器通过 HTTP 响应头 Set-Cookie 发送给客户端,并在后续请求中自动携带

session:是存储在服务器端的用户状态信息,通常通过一个唯一的 Session ID 标识,该 ID 可能通过 Cookie 或 URL 传递

如上图片引用自我的博客:Java EE(13)——网络原理——应用层 HTTP 协议,服务器内部实际上专门开辟了一个 session 空间用于存储用户信息,每当新用户发送第一次请求时服务器会将用户信息存储在 session 中并生成一个 session id 通过 Set-Cookie 方法返回给客户端,即 cookie

session 结构如下:

修改 Controller 类代码:

import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.example.springlogin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/login")
    public String login(String userName, String password, HttpSession session) {
        log.info("接收到参数,userName:{},password:{}", userName, password);
        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return "用户或密码为空";
        }
        String result = userService.getUserInfoByUserName(userName, password);
        if (result.equals("登录成功")) {
            HashMap<String, String> map = new HashMap<>();
            map.put("userName", userName);
            map.put("password", password);
            // 将 map 作为用户信息存储到 session/会话中
            session.setAttribute("userInfo", map);
            log.info("登录成功");
        }
        return result;
    }
}

修改前端代码:

function login() {
    $.ajax({
        url: '/user/login',
        type: 'POST',
        data: {
            userName: $('#username').val(),
            password: $('#password').val(),
        },
        success: function(result) {
            alert(result);
        }
    });
}

Fiddler 抓包结果: 前端/浏览器按住 Ctrl + Shift + i 打开控制台点击应用程序/application,打开 Cookie:

3. 统一返回结果封装

统一返回结果封装 是后端开发中的重要设计模式,能够保持 API 响应格式的一致性,便于前端处理

1. 创建枚举类:统一管理接口或方法的返回状态码和描述信息,标准化业务逻辑中的成功或失败状态

import lombok.Getter;

@Getter
public enum ResultStatus {
    SUCCESS(200, "成功"),
    FAIL(-1, "失败");

    private final Integer code;
    private final String message;

    ResultStatus(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

2. 创建 Result< T >类:主要用于规范服务端返回给客户端的响应数据格式。通过固定结构 (状态码、错误信息、数据) 确保前后端交互的一致性

import lombok.Data;

@Data
// 通过泛型<T>设计,可以灵活封装任意类型的数据对象到 data 字段
public class Result<T> {
    // 业务码
    private ResultStatus code;
    // 错误信息
    private String errorMessage;
    // 数据
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(ResultStatus.SUCCESS);
        result.setErrorMessage(null);
        result.setData(data);
        return result;
    }

    public static <T> Result<T> fail(String errorMessage) {
        Result<T> result = new Result<>();
        result.setCode(ResultStatus.FAIL);
        result.setErrorMessage(errorMessage);
        result.setData(null);
        return result;
    }
}

3. 修改 Controller 代码:

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/login")
    public Result<String> login(String userName, String password, HttpSession session) {
        log.info("接收到参数,userName:{},password:{}", userName, password);
        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return Result.fail("用户或密码为空");
        }
        String result = userService.getUserInfoByUserName(userName, password);
        if (!result.equals("登录成功")) {
            return Result.fail(result);
        }
        HashMap<String, String> map = new HashMap<>();
        map.put("userName", userName);
        map.put("password", password);
        // 将 map 作为用户信息存储到 session/会话中
        session.setAttribute("userInfo", map);
        log.info("登录成功");
        return Result.success(result);
    }
}

4. 修改前端代码:

function login() {
    $.ajax({
        url: '/user/login',
        type: 'POST',
        data: {
            userName: $('#username').val(),
            password: $('#password').val(),
        },
        success: function(result) {
            if (result.code === 200) {
                alert(result.data);
            } else {
                alert(result.errorMessage);
            }
        }
    });
}

4. 图形验证码

图形验证码 (captcha) 是一种区分用户是人类还是自动化程序的技术,主要通过视觉或交互任务实现。其核心意义体现在以下方面:防止自动化攻击:通过复杂图形或扭曲文字,阻止爬虫、暴力破解工具等自动化程序批量注册或登录,降低服务器压力;提升安全性:在敏感操作 (如支付、修改密码) 前增加验证步骤,减少数据泄露或恶意操作风险

Hutool 提供了 CaptchaUtil 类用于快速生成验证码,支持图形验证码和 GIF 动态验证码。在 pom.xml 文件中添加图下配置:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <!-- 版本号应与 springboot 版本兼容 -->
    <version>5.8.40</version>
</dependency>

1. 创建 CaptchaController 类,用于生成验证码并返回给前端

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;

@RestController
@RequestMapping("/captcha")
@Slf4j
public class CaptchaController {
    // 设置过期时间
    public final static long delay = 60_000L;

    @RequestMapping("/get")
    public void getCaptcha(HttpSession session, HttpServletResponse response) {
        log.info("getCaptcha");
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
        // 设置返回类型
        response.setContentType("image/jpeg");
        // 禁止缓存
        response.setHeader("Pragma", "No-cache");
        try {
            // 通过响应输出生成的图形验证码
            lineCaptcha.write(response.getOutputStream());
            // 保存 code
            session.setAttribute("captchaCode", lineCaptcha.getCode());
            // 保存当前时间
            session.setAttribute("captchaTime", System.currentTimeMillis());
            // 关闭输出流
            response.getOutputStream().close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2. 修改前端代码:最终版

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户登录</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <link rel="stylesheet" href="css/login.css">
</head>
<body>
<div class="login-container">
    <div class="logo"><i class="fab fa-weixin"></i></div>
    <h2>微信登录</h2>
    <form id="loginForm">
        <div class="input-group">
            <i class="fas fa-user"></i>
            <label for="username">用户名</label>
            <input type="text" id="username" placeholder="请输入用户名" required>
        </div>
        <div class="input-group">
            <i class="fas fa-lock"></i>
            <label for="password">密码</label>
            <input type="password" id="password" placeholder="请输入密码" required>
        </div>
        <div class="input-group">
            <div class="captcha-container">
                <label for="inputCaptcha">验证码</label>
                <input type="text" id="inputCaptcha" class="captcha-input" placeholder="输入验证码">
                <img id="verificationCodeImg" src="/captcha/get" class="captcha-img" title="看不清?换一张" alt="验证码">
            </div>
        </div>
        <div class="agreement">
            <input type="checkbox" id="agreeCheck" checked>
            <label for="agreeCheck">我已阅读并同意<a href="#">《服务条款》</a>和<a href="#">《隐私政策》</a></label>
        </div>
        <button type="submit" class="login-btn" onclick="login()">登录</button>
    </form>
    <div class="footer">
        <p>版权所有 ©九转苍翎</p>
    </div>
</div>
<!-- 引入 jQuery 依赖 -->
<script src="js/jquery.min.js"></script>
<script>
// 刷新验证码
$("#verificationCodeImg").click(function () {
    // new Date().getTime()).fadeIn() 防止前端缓存
    $(this).hide().attr('src', '/captcha/get?dt=' + new Date().getTime()).fadeIn();
});

// 登录
function login() {
    $.ajax({
        url: '/user/login',
        type: 'POST',
        data: {
            userName: $('#username').val(),
            password: $('#password').val(),
            captcha: $('#inputCaptcha').val(),
        },
        success: function(result) {
            console.log(result);
            if (result.code === 200) {
                alert(result.data);
            } else {
                alert(result.errorMessage);
            }
        }
    });
}
</script>
</body>
</html>
  1. 在 UserController 类新增 captcha 形参接收来自 CaptchaController 类的请求,并传递给 UserService

图片描述

import jakarta.servlet.http.HttpSession;
import org.example.springlogin.controller.CaptchaController;
import org.example.springlogin.mapper.UserMapper;
import org.example.springlogin.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserMapper userMapper;

    @Autowired
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public String getUserInfoByUserName(String userName, String password, String captcha, HttpSession session) {
        UserInfo userInfo = userMapper.getUserInfoByUserName(userName);
        if (userInfo == null) {
            return "用户不存在";
        }
        if (!password.equals(userInfo.getPassword())) {
            return "密码错误";
        }
        Long saveTime = (Long) session.getAttribute("captchaTime");
        if (System.currentTimeMillis() - saveTime > CaptchaController.delay) {
            return "验证码超时";
        }
        if (!captcha.equalsIgnoreCase((String) session.getAttribute("captchaCode"))) {
            return "验证码错误";
        }
        return "登录成功";
    }
}

实现效果:

5. MD5 加密

MD5(Message-Digest Algorithm 5) 是一种广泛使用的哈希函数,可将任意长度数据生成固定长度 (128 位,16 字节) 的哈希值,通常表示为 32 位十六进制字符串,常用于校验数据完整性或存储密码。但因其安全性不足,通常结合盐值 (Salt) 配合使用

不可逆性:无法通过哈希值反推原始数据

唯一性:理论上不同输入产生相同哈希值的概率极低 (哈希碰撞)

固定长度:无论输入数据大小,输出均为 32 位十六进制字符串

1. 创建 SecurityUtil 类用于生成和验证密文

import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.UUID;

public class SecurityUtil {
    // 加密
    public static String encrypt(String inputPassword) {
        // 生成随机盐值
        String salt = UUID.randomUUID().toString().replaceAll("-", "");
        // (密码 + 盐值) 进行加密
        String finalPassword = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());
        return salt + finalPassword;
    }

    // 验证
    public static boolean verify(String inputPassword, String sqlPassword) {
        if (!StringUtils.hasLength(inputPassword)) {
            return false;
        }
        if (sqlPassword == null || sqlPassword.length() != 64) {
            return false;
        }
        // 取出盐值
        String salt = sqlPassword.substring(0, 32);
        // (输入密码 + 盐值) 重新生成 加密密码
        String finalPassword = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());
        // 判断数据库中储存的密码与输入密码是否一致
        return (salt + finalPassword).equals(sqlPassword);
    }

    public static void main(String[] args) {
        System.out.println(SecurityUtil.encrypt("123456"));
    }
}

2. 将数据库中的密码替换为加密后的值 3. 修改验证密码的逻辑 (UserService 类)

if (!SecurityUtil.verify(password, userInfo.getPassword())) {
    return "密码错误";
}

6. 拦截器

Spring 拦截器 (Interceptor) 是一种基于 AOP 的机制,用于在请求处理的不同阶段插入自定义逻辑。常用于权限校验、日志记录、参数预处理等场景

1. 创建拦截器类并实现 HandlerInterceptor 接口,该接口提供了三种方法:

  • preHandle:在 Controller 方法执行前调用
  • postHandle:Controller 方法执行后、视图渲染前调用
  • afterCompletion:请求完成、视图渲染完毕后调用
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Slf4j
@Component
public class Interceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 1. 获取 Token
        String cookie = request.getHeader("Cookie");
        if (cookie == null) {
            response.setStatus(401);
            return false;
        }
        log.info("Received cookie: {}", cookie);
        // 2. 校验 token
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        log.info("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.info("afterCompletion");
    }
}

2. 注册拦截器

import org.example.springlogin.interceptor.Interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.List;

@Configuration
public class Config implements WebMvcConfigurer {
    private final Interceptor interceptor;

    @Autowired
    public Config(Interceptor interceptor) {
        this.interceptor = interceptor;
    }

    // 排除不需要拦截的路径
    private static final List<String> excludes = Arrays.asList("/login.html", "/user/login", "/captcha/get");

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截器
        registry.addInterceptor(interceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludes);
    }
}

3. 创建 home.html 文件,并且在登录成功后跳转到该页面 (在 login.html 中添加 location.href="/home.html")

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

实现效果:

未登录直接访问 home.html 页面时

图片描述

成功登陆时

图片描述

图片描述

目录

  1. 概述
  2. 项目搭建及配置
  3. 1. 登录认证全栈实现 -> 基础版
  4. 1.1 后端实现
  5. 1.1.1 架构设计
  6. 1.1.2 实体类
  7. 1.1.3 Controller
  8. 1.1.4 Service
  9. 1.1.5 Mapper
  10. 1.2 前端实现
  11. 2. Cookie/Session
  12. 3. 统一返回结果封装
  13. 4. 图形验证码
  14. 5. MD5 加密
  15. 6. 拦截器
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于 AR 的低代码可视化远程协作与工业巡检方案
  • Go 语言结合 DeepSeek 大模型构建智能监控系统
  • 面向 C++ 开发的 Web 自动化测试入门:从概念到 Selenium 实战
  • Llama-Factory 微调参数详解与调优建议
  • Python 核心技术栈与多领域应用实战指南
  • 网络安全工程师岗位需求分析:市场前景与技能方向
  • CSS 样式基础与界面布局实战指南
  • SimVLA: 一种简单的机器人操作 VLA 基线模型
  • Python Pandas 库核心功能与实战指南
  • 36 岁非科班转行 AI 算法工程师:半年学习与面试经验
  • 构建私有化知识库:融合 ChatGPT 与向量数据库的技术实践
  • Microi 吾码:基于 Spring Boot 的低代码微服务框架与表单引擎
  • 老板电器发布全球首个 AI 烹饪大模型「食神」
  • 青少年软件编程 Python 等级考试一级解析
  • Microi 吾码:基于 Spring Boot 的低代码微服务框架
  • Coze 抓取小红书爆款视频并写入飞书多维表实战
  • 在线图书借阅平台设计与实现:基于飞算 JavaAI
  • 智算云平台架构解析与 AI 模型部署实战
  • 数据库迁移 TCO 全景账本:MySQL 替代中的隐性成本与工程化工具链实测
  • Flutter 在 OpenHarmony 上适配 eip55 库进行以太坊地址校验

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online