当前位置: 首页 > news >正文

实现Java后端的图形验证码和行为验证码

登录添加图形验证码:

Java 中,我们可以使用一些图形处理库(如 java.awtjavax.imageio)生成图形验证码,并将验证码文本存储在会话(session)中以供验证。下面是一个完整的实现步骤,包括 验证码图片生成验证码接口、以及 验证逻辑


1. Maven 依赖(可选)

如需引入依赖管理,可以使用 Maven 项目结构。示例中我们主要依赖 Java 自带的 javax 库,所以无需添加额外依赖。

如果你需要更多验证码功能,kaptcha 是一个常用的 Java 验证码生成库,可以通过 Maven 添加:

xml
<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version>
</dependency>

2. 验证码生成与接口实现

下面是一个使用 javax.imageiojava.awt 库生成验证码图片的例子,并通过 Servlet 提供 HTTP 接口。

生成验证码的 Java Servlet

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {private static final int WIDTH = 160;private static final int HEIGHT = 40;@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 创建验证码图片BufferedImage captchaImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);Graphics2D g = (Graphics2D) captchaImage.getGraphics();Random random = new Random();// 生成随机验证码文本String captchaText = generateCaptchaText(6);HttpSession session = request.getSession();session.setAttribute("captcha", captchaText); // 将验证码存储在 session 中// 设置图片背景颜色g.setColor(Color.LIGHT_GRAY);g.fillRect(0, 0, WIDTH, HEIGHT);// 设置字体与颜色g.setFont(new Font("Arial", Font.BOLD, 30));g.setColor(Color.BLACK);g.drawString(captchaText, 20, 30);// 画干扰线for (int i = 0; i < 5; i++) {g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));int x1 = random.nextInt(WIDTH);int y1 = random.nextInt(HEIGHT);int x2 = random.nextInt(WIDTH);int y2 = random.nextInt(HEIGHT);g.drawLine(x1, y1, x2, y2);}g.dispose(); // 释放资源// 设置响应类型为图片格式response.setContentType("image/png");ImageIO.write(captchaImage, "png", response.getOutputStream()); // 输出图片}// 生成随机验证码文本private String generateCaptchaText(int length) {String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";StringBuilder sb = new StringBuilder();Random random = new Random();for (int i = 0; i < length; i++) {sb.append(chars.charAt(random.nextInt(chars.length())));}return sb.toString();}
}

解释:

  1. CaptchaServlet
    • 使用 BufferedImage 创建验证码图片。
    • 使用 Graphics2D 在图片上绘制文字和干扰线。
    • 验证码文本存储在 session 中,以便后续验证。
  1. 会话存储session.setAttribute("captcha", captchaText); 将验证码保存到用户会话中。
  2. 图片响应ImageIO.write(captchaImage, "png", response.getOutputStream()); 将图片直接写入 HTTP 响应流中。

3. 验证码验证逻辑

在用户提交表单时,后端需要验证用户输入的验证码与 session 中存储的验证码是否一致。

登录接口示例

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String userAccount = request.getParameter("userAccount");String userPassword = request.getParameter("userPassword");String captchaInput = request.getParameter("captcha");HttpSession session = request.getSession();String captcha = (String) session.getAttribute("captcha");// 验证验证码是否正确if (captcha == null || !captcha.equalsIgnoreCase(captchaInput)) {response.getWriter().write("验证码错误");return;}// TODO: 验证账号和密码的逻辑if ("admin".equals(userAccount) && "password".equals(userPassword)) {response.getWriter().write("登录成功");} else {response.getWriter().write("账号或密码错误");}}
}

解释:

  1. 验证码验证:获取用户输入的验证码,并与会话中的验证码进行比对。
  2. 忽略大小写:使用 equalsIgnoreCase 忽略大小写进行比较。
  3. 验证逻辑:如果验证码正确且账号密码验证成功,则返回“登录成功”。

4. 前端代码:提交表单与验证码显示

前端代码需要请求验证码图片,并将验证码输入框的内容一并提交给后端。

HTML 示例:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录页面</title>
</head>
<body><form action="/login" method="POST"><input type="text" name="userAccount" placeholder="请输入账号" required /><input type="password" name="userPassword" placeholder="请输入密码" required /><img src="/captcha" alt="验证码" onclick="this.src='/captcha?'+Math.random()" /><input type="text" name="captcha" placeholder="请输入验证码" required /><button type="submit">登录</button></form>
</body>
</html>

解释:

  1. 验证码刷新:点击验证码图片时,通过 this.src='/captcha?' + Math.random() 强制刷新验证码。
  2. 表单提交:在用户填写验证码后,将表单提交到 /login 接口。

使用行为验证码:

在开发中添加行为验证码是一种有效的防止自动化攻击和机器人行为的方式。以下是一些免费且公开可用的行为验证码解决方案:

1. hCaptcha

  • 概述:hCaptcha 是一个免费的验证码服务,提供用户交互验证,防止恶意自动化。
  • 特点
    • 简单易用,与 Google reCAPTCHA 类似。
    • 提供奖励系统,网站可以通过用户完成验证获取收益。
  • 使用
    • 访问 hCaptcha官网 注册并获取 API 密钥。
    • 根据文档进行集成。

2. Google reCAPTCHA

  • 概述:虽然 reCAPTCHA 主要是为识别机器人设计的,但其 "Checkbox" 和 "Invisible" 选项可以用作行为验证码。
  • 特点
    • 广泛使用,用户熟悉。
    • 提供多种验证方式,包括隐形验证。
  • 使用
    • 注册 Google reCAPTCHA 获取密钥。
    • 按照文档进行集成。

3. Friendly Captcha

  • 概述:Friendly Captcha 提供了一种不依赖于用户交互的验证码系统,适合于保护用户隐私。
  • 特点
    • 不需要用户填写验证码,用户体验更佳。
    • 通过计算资源验证用户行为。
  • 使用
    • 访问 FriendlyCaptcha官网 注册并获取 API 密钥。
    • 根据文档进行集成。

实战hCaptcha:

在vue项目中创建组件:

<template><div><!-- hCaptcha 容器 --><divref="hcaptchaContainer"class="h-captcha":data-sitekey="siteKey"></div></div>
</template><script setup lang="ts">
import { onMounted, ref, defineEmits } from "vue";
const token = ref<string | null>(null); // 存储验证 token// 定义事件,向父组件发送 token
const emit = defineEmits(["verify"]);
interface Props {siteKey: string;
}// const props = defineProps<Props>(); // 接收 siteKey 作为参数
const hcaptchaContainer = ref<HTMLDivElement | null>(null); // hCaptcha 容器的引用// 生命周期钩子,确保 hCaptcha 在组件挂载后初始化
onMounted(() => {if (window.hcaptcha) {console.log("hCaptcha 已加载");window.hcaptcha.render(hcaptchaContainer.value!, {sitekey: "10000000-ffff-ffff-ffff-000000000001",callback: onVerify,// sitekey: "fbc8723c-c356-49d6-a524-bf327f9ac81a",});} else {console.error("hCaptcha 脚本未加载");}
});const onVerify = (response: string) => {console.log("hCaptcha 验证成功,token:", response);emit("verify", response); // 通过事件发送 token 给父组件
};
</script><style>
.h-captcha {margin: 10px 0;
}
</style>

然后在登录页面中使用,并且进行拦截:

<template><div id="userLoginView"><h1 style="margin-bottom: 56px; color: #fff">用户登录</h1><a-formstyle="display: flex;flex-direction: column;justify-content: space-between;max-width: 520px;min-height: 350px;margin: 0 auto;background: rgba(255, 255, 255, 0.9);padding: 20px;border-radius: 10px;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);"label-align="left"auto-label-width:model="form"@submit="handleSubmit"><a-form-item field="userAccount" label="账号" style="margin-top: 40px"><a-input v-model="form.userAccount" placeholder="请输入账号" /></a-form-item><a-form-item field="userPassword" tooltip="密码不少于 8 位" label="密码"><a-input-passwordv-model="form.userPassword"placeholder="请输入密码"/></a-form-item><HCaptcha @verify="onCaptchaVerify" /><a-form-item style="margin-bottom: 40px"><a-buttontype="primary"html-type="submit"style="width: 120px; height: 40px; margin-right: 20%">登录</a-button><a-buttontype="primary"@click="turnToRegister()"style="width: 120px; height: 40px; margin-left: 20%">新用户注册</a-button></a-form-item></a-form></div>
</template><script setup lang="ts">
import HCaptcha from "../../components/HCaptcha.vue";
import { reactive, ref } from "vue";
import { UserControllerService, UserLoginRequest } from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import { useRouter } from "vue-router";
import { useStore } from "vuex";/*** 表单信息*/
const form = reactive({userAccount: "",userPassword: "",
} as UserLoginRequest);const router = useRouter();
const store = useStore();const captchaToken = ref<string | null>(null); // 存储 hCaptcha 的 token
const onCaptchaVerify = (token: string) => {captchaToken.value = token; // 接收 hCaptcha 的 token
};/*** 提交表单* @param data*/
const handleSubmit = async () => {if (!captchaToken.value) {message.error("请先通过验证码验证");return; // 阻止提交}const res = await UserControllerService.userLoginUsingPost(form);// 登录成功,跳转到主页if (res.code === 0) {await store.dispatch("user/getLoginUser");router.push({path: "/",replace: true,});} else {message.error("登陆失败," + res.message);}
};/*** 跳转到注册页面*/
const turnToRegister = () => {router.push({path: "/user/register",replace: true,});
};
</script>
<style>
#userLoginView {margin-top: 20vh;/*margin-left: 40vh;*/
}
</style>

相关文章:

实现Java后端的图形验证码和行为验证码

登录添加图形验证码&#xff1a; 在 Java 中&#xff0c;我们可以使用一些图形处理库&#xff08;如 java.awt 和 javax.imageio&#xff09;生成图形验证码&#xff0c;并将验证码文本存储在会话&#xff08;session&#xff09;中以供验证。下面是一个完整的实现步骤&#x…...

事务的原理、MVCC的原理

事务特性 数据库事务具有以下四个基本特性&#xff0c;通常被称为 ACID 特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务被视为不可分割的最小工作单元&#xff0c;要么全部执行成功&#xff0c;要么全部失败回滚。这意味着如果事务执行过程中发生…...

Golang反射原理

Golang反射原理 Go语言中的反射机制是通过标准库中的reflect包实现的。反射允许程序在运行时检查变量的类型和值&#xff0c;甚至可以修改变量的值。以下是反射的基本原理和使用方法&#xff1a; 基本原理 类型和种类&#xff1a; 反射中的类型信息通过reflect.Type表示&…...

MATLAB计算朗格朗日函数

1. 朗格朗日函数介绍 朗格朗日函数&#xff08;Lagrange function&#xff09;通常用于优化问题&#xff0c;尤其是带有约束的优化问题。其一般形式为&#xff1a; 其中&#xff1a; f(x) 是目标函数。 是约束条件。 是拉格朗日乘子。 为了编写一个MATLAB代码来计算和绘制…...

嵌入式linux跨平台基于mongoose的TCP C++类的源码

嵌入式linux开发中&#xff0c;需要使用http服务器时&#xff0c;mongoose是个很好的选择&#xff0c;linux&#xff0c;win双平台都支持&#xff0c;代码全开放&#xff0c;简单明了&#xff0c;我非常喜欢这种尽在撑控中的感觉&#xff08;关于mongoose实现一个小型的http服务…...

入驻商家必看:如何在TikTok实现多店铺高效上货及运营?

TikTok作为跨境电商平台之一&#xff0c;越来越多人进入其电商赛道——TikTok Shop&#xff0c;运营者想要长远发展&#xff0c;了解平台的政策动向并进行调整店铺至关重要。本文整理了TikTok Shop降低入驻门槛的资讯&#xff0c;并为广大TikTok电商运营者提供实用、有效的开店…...

spring-boot-starter-data-redis

一、几个依赖的关系 在spring与redis整合时有下面几种&#xff1a; spring-boot-starter-data-redis spring-boot-starter-redis spring-data-redis 其中&#xff0c;spring-boot-starter-data-redis和spring-boot-starter-redis中都包含有spring-data-redis&#xff0c; 现在…...

科研绘图神器:机制图、模式图有哪些好用的工具推荐?

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 最近不少学员在问科研绘图相关的问题。前面娜姐介绍过AI辅助绘图的方法和思路&#xff1a; 顶刊的图文摘要Graphical Abstract&#xff0c;如何巧用AI绘制&#xff1f; 目前…...

DIFFUSIONSAT: A GENERATIVE FOUNDATION MODEL FOR SATELLITE IMAGERY(2024-ICLR)

论文&#xff1a;DIFFUSIONSAT: A GENERATIVE FOUNDATION MODEL FOR SATELLITE IMAGERY(2024-ICLR) 习惯用飞书做笔记了&#xff0c;大家见谅 Diffusionsat&#xff1a;卫星图像生成基础模型...

文件中台与安全:集成方案的探索与实践

在企业数字化转型加速的今天&#xff0c;文件中台已成为支撑数据共享与高效协作的关键基础设施。然而&#xff0c;随着企业文件需求的增多和内容复杂性的提升&#xff0c;文件的安全问题也日益突显。如何在构建强大文件中台的同时&#xff0c;保障文件数据的安全性&#xff0c;…...

Redis 哨兵 总结

前言 相关系列 《Redis & 目录》《Redis & 哨兵 & 源码》《Redis & 哨兵 & 总结》《Redis & 哨兵 & 问题》 参考文献 《Redis的主从复制和哨兵机制详解》《Redis中的哨兵&#xff08;Sentinel&#xff09;》《【Redis实现系列】Sentinel自动故…...

Systemd 和 Systemctl命令详解

Systemd 和 Systemctl命令详解 在现代 Linux 系统中&#xff0c;systemd 是一种高度灵活且广泛应用的系统管理工具。它主要负责系统引导和进程管理&#xff0c;支持并行化启动服务&#xff0c;并提供高级的服务管理和依赖控制。systemctl 是 systemd 的核心命令行工具&#xf…...

基于Multisim的音频放大电路设计与仿真

基本设计要求&#xff1a;设计并仿真实现一个音频功率放大器。功率放大器的电源电压为&#xff0b;5V&#xff08;电路其他部分的电源电压不限&#xff09;&#xff0c;负载为8Ω电阻。具体要求如下&#xff1a;1&#xff09;3dB通频带为300&#xff5e;3400Hz&#xff0c;输出…...

这是一款专门为SQL新手小白量身定制的工具!

首先&#xff01;它永久免费&#xff01;SQLynx对于个人用户和教育从业者永久免费&#xff01;且真正实现了跨平台操作&#xff01;支持Windows Linux和Mac&#xff0c;无需任 何安装和配置&#xff0c;更支持国产操作系统&#xff0c;如银河 麒麟统信等。 功能直观&#xff01…...

springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)

刚解决Spring Framework 特定条件下目录遍历漏洞&#xff08;CVE-2024-38816&#xff09;没几天&#xff0c;又来一个新的&#xff0c;真是哭笑不得啊。 springboot 修复 Spring Framework 特定条件下目录遍历漏洞&#xff08;CVE-2024-38816&#xff09;https://blog.csdn.ne…...

Android Input的流程和原理

Android Input事件机制 Android系统是由事件驱动的&#xff0c;而Input是最常见的事件之一&#xff0c;用户的点击、滑动、长按等操作&#xff0c;都属于Input事件驱动&#xff0c;其中的核心就是InputReader和InputDispatcher。InputReader和InputDispatcher是跑在system_serv…...

InfiMM-WebMath-40B——利用由 24 亿数学文档组成的数据集提高 LLM 的数学性能

1. 前言 论文地址&#xff1a;https://arxiv.org/abs/2409.12568 本文提出了一个新的大规模多模态预训练数据集 InfiMM-WebMath-40B&#xff0c;以提高数学推理能力。该数据集包含 24 亿个科学和数学相关的网络文档、85 亿个图片 URL 和约 400 亿个文本标记。该数据集支持多模…...

Swarm-LIO: Decentralized Swarm LiDAR-inertial Odometry论文翻译

文章目录 前言一、介绍二、相关工作三、方法A. 问题表述B. 框架概述C. 群体系统的初始化D. 去中心化激光雷达-惯性状态估计 四. 实验A. 室内飞行B. 退化环境飞行C. 去中心化部署 五. 结论和未来工作 前言 原文&#xff1a;原文 准确的自我状态和相对状态估计是完成群体任务的关…...

第十八章 Vue组件样式范围配置之scoped

目录 一、引言 二、案例演示 2.1. 工程结构图 2.2. 核心代码 2.2.1. main.js 2.2.2. App.vue 2.2.3. BaseOne.vue 2.2.4. BaseTwo.vue 2.3. 运行效果 2.4. 调整代码 2.4.1. BaseTwo.vue 2.4.2. 运行效果 三、scoped原理 一、引言 前面的几个章节在介绍组件的时…...

【JavaScript】JavaScript 进阶-3-编程思想构造函数原型(更新中)

目录 编程思想构造函数原型 编程思想 构造函数 原型...

头歌网络安全爬虫

#!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2020/4/8 8:19 # File : info.py # ---------------------------------------------- # ☆ ☆ ☆ ☆ ☆ ☆ ☆ # >>> Author : Alex # >>> QQ : 2426671397 # >>> Mail…...

二、k8s快速入门之docker+Kubernetes平台搭建

centosmaster192.168.100.10centosnode1192.168.100.20centosnode2192.168.100.30 除特殊说明命令都需要在三台都执行 ⭐️ k8s 的指令&#xff1a; kubeadm&#xff1a;用来初始化集群的指令kubelet: 在集群中的每个节点上用来启动Pod和容器kubectl: 用来与集群通信的命令行…...

k8s的发展历史

Kubernetes&#xff08;通常缩写为 K8s&#xff09;是一个开源的容器编排平台&#xff0c;用于自动化应用程序的部署、扩展和管理。它的发展历史可以追溯到多个关键的里程碑&#xff1a; 1. 起源&#xff08;2013 年&#xff09; Kubernetes 的起源可以追溯到 Google 的内部项…...

Pytorch lightning多机多卡训练通讯问题(NCCL error)排查

一、问题 单机多卡可以正常训练模型&#xff0c;多机多卡数据加载完成后卡住不动&#xff0c;排查两台机器可以ping通&#xff0c;表明网络没有问题&#xff0c;查看bug信息是NCCL通信问题。报错信息大致如下: torch.distributed.DistBackendError: NCCL error in: …/torch/c…...

React如何实现Vue的keepAlive功能

前言 在React中&#xff0c;默认情况下组件在被卸载后会销毁状态&#xff0c;这与Vue的keep-alive功能不同。在Vue中&#xff0c;keep-alive组件可以缓存组件状态&#xff0c;在路由切换时重新挂载。实现这一功能在React中并不简单&#xff0c;但我们可以借助一个第三方库——…...

在 Ubuntu 22.04 LTS 上安装 NVM (Node Version Manager) 管理和切换不同版本的 Node.js npm

安装 nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash# nvm --version 0.40.1安装 Node.js 的不同版本 列出所有可用的 Node.js 远程版本 nvm ls-remotenvm install v18.20.4# node --version v18.20.4# nvm current v18.20.4npm 是 …...

如何搭建题库管理小序❓

土著刷题小&#x1f34a;序不仅能够作为组织考试的利器&#xff0c;它同样可以帮助教育培训机构构建一个强大且高效的题库管理系统。 下面跟随我们的指导&#xff0c;一起来看看如何利用土著刷题小&#x1f34a;序轻松快捷地建立起自己的题库&#xff0c;并享受其所带来的诸多好…...

Spring Boot框架下校园社团信息管理的创新实践

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…...

vscode clangd for cuda 插件配置

这里写目录标题 1. 下载插件clangd,并且安装server到host2. 配置3. 安装调试插件 1. 下载插件clangd,并且安装server到host 步骤 extension下载 altshiftp, 下服务&#xff0c;如果下不下来请考虑用&#x1fa9c; 下载好后check一下&#xff0c;检查是否正常 正常的标志 注意…...

软件测试学习笔记丨SeleniumPO模式

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/22525 本文为霍格沃兹测试开发学社的学习经历分享&#xff0c;写出来分享给大家&#xff0c;希望有志同道合的小伙伴可以一起交流技术&#xff0c;一起进步~ 说明&#xff1a;本篇博客基于sel…...

网站建设方案销售/网络推广方法有哪几种

平时写字&#xff0c;大家都有自己的写法习惯&#xff0c;先写哪一笔后写哪一笔&#xff0c;每个人都不太一样。有些字是我们十几年几十年一直写错的。而汉语是讲究笔顺的&#xff0c;尤其是独体字或者一些偏旁部首&#xff0c;都有自己的写法顺序&#xff0c;写错了&#xff0…...

谁告诉你j2ee是做网站的/网站优化的方法与技巧

使用Ansible的Playbook批量部署多台LAMP环境 Playbook的使用步骤 playbook是一个不同于使用ansible命令行执行方式的模式&#xff0c;功能更强大更灵活。 1、在playbook中定义任务&#xff1a; - name: task description # 任务描述信息module_name:module_args # 需要…...

信阳市人民政府网站官网/徐州seo外包公司

android:ListView中的getView原理 其实这里的复用技术在列表中是十分常见的&#xff0c;iphone中的tableView也有相关的技术&#xff0c;cell的复用 工作原理: ListView 针对List中每个item&#xff0c;要求 adapter “给我一个视图” (getView)。一个新的视图被返回并显示如果…...

上海市人民政府网站/免费网站推广软文发布

定义 策略模式(Strategy):策略模式是一种定义一系列算法的方法&#xff0c;从概念上来看&#xff0c;所有这些算法完成的都是相同的工作&#xff0c;只是实现不同&#xff0c;它可以以相同的方式调用所有的算法&#xff0c;减少各种算法类与使用算法类之间的耦合。 类图 代码…...

北京知名大公司有哪些/seo免费培训教程

1、支付宝 2、ApplePay 原文出处&#xff1a;http://idlelife.org/archives/755 Apple Pay是一个基于NFC的支付系统 1&#xff09;要集成ApplePay首先要设置target为iOS 8.1及以上。 2&#xff09;设置TARGETS&#xff0d;>Capabilities将Apple Pay设置为on&#xff0c;这将…...

呼和浩特做网站哪家好/精准引流获客软件

通常对于普通函数来说,要访问类的保护成员是不可能的&#xff0c;如果想这么做那么必须把类的成员都生命成为public(共用的)&#xff0c;然而这做带来的问题遍是任何外部函数都可以毫无约束的访问它操作它&#xff0c;c利用friend修饰符&#xff0c;可以让一些你设定的函数能够…...