【实战项目之个人博客】

目录

项目背景

项目技术栈

项目介绍

项目亮点

项目启动

1.创建SSM(省略)

2.配置项目信息

3.将前端页面加入到项目中

4.初始化数据库 

5.创建标准分层的目录 

6.创建和编写项目中的公共代码以及常用配置 

7.创建和编写业务的Entity、Mapper、Service、Controller等基础代码 

8.按照页面,从前端或者后端开始实现相应的交互和业务代码 

项目功能实现

实现用户注册功能 

实现用户登录功能 

实现添加文章功能 

实现文章编辑功能 

实现注销功能 

实现文章内容详情页功能 

实现增加阅读量功能

实现判断当前用户登录状态功能 

实现用户的博客列表功能 

实现文章删除功能 

实现统一异常处理功能 

实现统一数据格式返回

实现主页博客展示和分页功能 

实现密码加盐(增强密码安全性)功能 

实现验证码功能 

实现将Session存储的用户信息持久化到Redis功能


项目背景

本项目是我个人为了提升技术能力和积累实战经验,搭建的一个个人博客系统。通过这个项目,将学习并实践Web开发、前端设计、后端编程等技能,同时记录和分享我在学习和实践中的心得体会。

项目技术栈

SpringBoot+SpringWeb+Mybatis+MySQL+Redis

项目介绍

基于SSM的前后端分离的个人博客系统,项目中融入了Editor开源组件,更方便用户对博文的编写,大概包含以下功能:用户的登录与注册、个人博客列表及其所有人博客展示,用户对个人博文的增加、删除、修改、查询,博客列表的分页管理等。

项目亮点

1.注册用户时候,对用户密码进行加盐加密处理,登录用户时候,对密码进行加盐解密处理,从而提高用户信息安全性。

2.将用户信息存入session,实现内存到redis的持久化,支持分布式处理。

3.通过拦截器的方式进行登录验证,减轻代码冗余,提高重用率。

项目启动

1.创建SSM(省略)

2.配置项目信息

application.properties

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/myblog?characterEncoding=utf8&useSSL=false
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mybatis/*Mapper.xml # 映射文件包扫描
  type-aliases-package: com.example.demo.entity # 实体类别名包扫描
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #默认日志级别是info,而这需要的日志级别是debug
  # 配置打印 MyBatis 执行的 SQL
  logging:
    level:
      com:
        example:
          demo: debug #默认info > debug,只有设置为debug才能看到日志

3.将前端页面加入到项目中

4.初始化数据库 

-- 创建数据库
drop database if exists myblog;
create database myblog DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use myblog;
-- 创建表[用户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(65) not null,
    photo varchar(500) default '',
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists  articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
      vid int primary key,
      `title` varchar(250),
      `url` varchar(1000),
                createtime timestamp default current_timestamp,
                updatetime timestamp default current_timestamp,
      uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `myblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(1, 'admin', 'admin', '', '2022-5-20 14:28:57', '2022-5-20 13:14:52', 1);
INSERT INTO `myblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(2, 'themyth', '1234', '', '2022-8-18 14:28:57', '2022-8-18 13:14:52', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
    values('Java','Java正文',1);
    
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.google.com',1); 

5.创建标准分层的目录 

 

ps:真实项目service下面还有个子包impl,里面存放service的实现类。因为本项目比较简单,所以就没有完全标准型的分层。 

6.创建和编写项目中的公共代码以及常用配置 

定义统一格式返回对象:

/*** 最终版的 JSON 统⼀返回对象*/
@Data
public class AjaxResult implements Serializable {private Integer code;private String msg;private Object data;/*** 返回成功数据* @param data* @return*/public static AjaxResult success(Object data) {AjaxResult ajaxObject = new AjaxResult();ajaxObject.setCode(200);ajaxObject.setMsg("");ajaxObject.setData(data);return ajaxObject;}public static AjaxResult success(Object data, String msg) {AjaxResult ajaxObject = new AjaxResult();ajaxObject.setCode(200);ajaxObject.setMsg(msg);ajaxObject.setData(data);return ajaxObject;}/*** 返回失败数据* @param code* @param msg* @return*/public static AjaxResult fail(Integer code, String msg) {AjaxResult ajaxObject = new AjaxResult();ajaxObject.setCode(code);ajaxObject.setMsg(msg);ajaxObject.setData("");return ajaxObject;}public static AjaxResult fail(Integer code, String msg, Object data) {AjaxResult ajaxObject = new AjaxResult();ajaxObject.setCode(code);ajaxObject.setMsg(msg);ajaxObject.setData(data);return ajaxObject;}
}

7.创建和编写业务的Entity、Mapper、Service、Controller等基础代码 

 

8.按照页面,从前端或者后端开始实现相应的交互和业务代码 

项目功能实现

实现用户注册功能 

前端核心代码

写后端接口 

先写controller

写service 

写mapper 

 

对应的xml 

ps:也可以从mapper写起,看个人习惯。 

接下来就是测试: 

假如出问题了如何解决,排除法:先看前端有无问题,可以利用js打断点调试,如果没有问题就是后端的问题,再进行debug调试。缩小问题,定位问题,解决问题。
前端的调试方式:

实现用户登录功能 

前端核心代码

写后端接口 

mapper

xml

service 

controller 

拦截器

 

实现添加文章功能 

前端核心代码

mapper

xml

service

controller 

实现文章编辑功能 

1.要对文章进行编辑,首先得在编辑文章的界面,在编辑页面的界面时候首先得加载文章的信息,于是就要去查询文章的信息将其展示到页面,根据文章的id去查询文章,同时不是本文章的用户不能去编辑,所以在后端给前端返回文章信息的时候,要确定这篇文章是否是属于当前登录中的用户的,只有确定是当前用户的文章才能进行显示。页面展示的内容为标题和内容,只能对标题和内容进行编辑,所以后端返回给前端文章内容时候,前端构建页面的时候只需要构建文章的标题和内容。
2.当页面正常显示,用户就可以编辑自己的文章,之后就要进行编辑之后再次发布文章,从而更新自己的文章,在修改文章的时候,同时也要验证权限的问题,不是自己的文章不能进行修改的提交操作。 

前端核心代码

 

mapper

xml

service

工具类

因为我们经常通过当前session会话获取当前登录用户,所以我们可以封装一个类来专门处理这个事情:

controller 

ps:一定要加上uid的获取,用户只能删除自己的文章。

实现注销功能 

前端核心代码

后端代码

实现文章内容详情页功能 

首先要得到文章的内容,需要文章id(这个id在查询字符串里面)去数据库查询此文章显示到页面,前端只需要提交id就行,后端可以通过当前登录的用户得到用户的id,然后再通过文章id和用户id在数据库查询文章,将查询文章的结果返回给前端,这里的文章内容详情包含了文章的标题、作者、阅读量、发布时间。

前端核心代码

添加拦截器规则

mapper

VO

xml

service

controller 

实现增加阅读量功能

前端核心代码

ps:如果多次刷新阅读量依然没有增加,先去前端查找问题: 

所以需要添加拦截器规则:(之前上面实现文章详情内容的时候,内容加载不出来也是也可能是这个原因,当然具体原因需要具体分析,可以通过打断点的方式去找问题) 

mapper

xml

service

controller 

实现判断当前用户登录状态功能 

首先要对按钮的实现进行调整,我们可以从下面发现问题:

前端核心代码

 

添加拦截器规则 

controller 

实现用户的博客列表功能 

前端核心代码

用于截取文章摘要的工具类

mapper

xml

service

controller 

实现文章删除功能 

删除文章的时候也要判断归属人,不能删除别人的文章,前端只需要传入文章id就行了,uid是可以通过session获取当前登录用户的属性获取的

前端核心代码

mapper

xml

ps:什么文章都可以删取决于id = #{id},但是能否真正的删除是要根据uid = #{uid}来决定的,要先确定此文章归属人是否是当前登录用户的。 

service

controller 

实现统一异常处理功能 

实现统一数据格式返回

我们考虑一种情况:在我们写代码时候,可能有些时候就会忘记有统一的数据返回,或者我们压根不知道有,就会让前端不知所措。
所以这时候我们就需要进行统一数据格式返回。

先演示:返回int的情况,查看返回的结果是否是返回刚刚定义好的统一数据格式返回

我们用fidder进行抓包测试:

现在进行String类型的数据测试,如果返回的是String,结果会是咋样? 

先在controller弄个测试接口

分析为什么会出现此原因?
先将统一异常处理类里面的注解注释掉,记得将注解注释去除

果然发现就是StringHttpMessageConverter惹的祸,后续原因会在统一数据格式返回章节进行解释。

解决方案
第一步: 

第二步: 

再次进行测试:

假如我们忘写了,可以发现保底策略可以将"hi"放进data中,这下String类型的数据返回也就解决了。 

实现主页博客展示和分页功能 

先复习一下数据库上如何实现分页查询

select * from emp limit 0,5    

sql语句通过limit关键字实现数据的分页查询, limit后面可以放两个整数作为参数,前一个参数的意义为从那条数据开始查询,后一个参数的意义是连续取出多少条

如果查询 第n 页,每页x条 数据那么sql语句应该写成Select * from emp limit (n-1)*x,x

分页查询的sql语句代码公式为:SELECT * FROM emp LIMIT (页码数-1)*页大小,页大小

第一点 :    index   ,size   start =(index-1)*size;

第二点:     maxpage =    if (total % size == 0) { maxpage = total / size } 

                                         else { maxpage = total / size + 1 }

假设有100条数据,页大小为5,那么要分20页
假设有103条数据,页大小为5,那么要分21页 第21页装3条数据

即:

如果查询的总记录数
能整除页面大小,那么就刚好
不能整除页面大小,页码数+1

举例:用员工表来演示
我们先查询所有的员工:

查询第二页的员工,前提:且页大小为7

前端给后端传递的参数:
1.pageSize:每页最大显示条数---页大小
2.pageIndex当前页码数 

前端核心代码

后端

分页公式:
                                               页大小                   当前页码数-1        页大小
select * from articleinfo limit pageSize offset (pageIndex - 1) * pageSize

offset = (pageIndex - 1) * pageSize

或者
                                               当前页码数-1          页大小         页大小
select * from articleinfo limit (pageIndex - 1) * pageSize, pageSize

添加拦截器规则 

mapper

xml

service

controller 

 

实现密码加盐(增强密码安全性)功能 

首先解释下为什么有了md5加密还要进行密码加盐,常规来说我们通常使用md5进行加密,假如我们对"123"---》md5加密---》得到:themyth(固定的值)。
即使md5是不可逆的,但是它可以被穷举,因为在其它语言例如python,c++使用md5加密"123",也是得到一个固定的值,如同上面的themyth,于是我们就可以使用一个固定md5的表(彩虹表)来反推出密码,此时使用md5相当于没有加盐,相当于给程序加了一把带钥匙的锁,显然这种做法是不行的,这个问题点在于对于
同一个字符串每次生成的md5密码是一样,是一样的就可以被穷举。解决方案:加盐处理
加盐处理:
简单来说就是仍然采用md5加密的方式,只是对于同一个字符串每次生成的md5密码都不一样。
为什么可以让每次生成的密码不一样呢?
实现的关键:使用
随机数(称之为)
生活案例解释:这好比炒菜来一样,我们每个人在炒菜的时候,加盐的含量不可能完全相同,所以菜的咸淡也会不同。

传统加密vs加盐加密
md5加密流程:
明文密码---》md5(明文密码)---》密码
加盐加密流程:
1)构建期:明文密码+随机盐值
2)md5(明文密码+随机盐值)---》加盐加密之后的密码,数据库此时还不能直接存这个密码,因为这个密码在进行加盐解密是解不出来的。

原因
1.md5不可逆,2.数据库也没有随机盐值。
同一个字符串每次生成的密码都是不一样的,如何确定这个密码是正确的呢?也就是说这个密码在数据库是1对多的关系,那我们如何判定这个密码就是我们原始的密码呢?由此可见加盐加密流程本身是很简单的,所以我们不能直接存储这个加盐加密之后的密码。

解决方案
为了能够正确的验证密码,所以在进行加盐加密存储时,必须存储以下两个内容:
1.加盐加密之后的密码
2.随机盐值

为什么要存随机盐值?
因为md5解密是不可逆的,这里的解密并不是真正意义上的解密,是按照之前加盐加密的流程再去执行一遍,去还原一下它的滚迹,拿还原之后的滚迹和最终的密码进行判断的,如果说它相等说明密码没有问题,反之则有问题。在进行密码还原的过程中是要用到这个随机盐值的。
简单来说解密步骤

md5(明文密码+随机盐值) 和 之前加盐加密之后的密码:之前就已经存在数据库的[md5(明文密码+随机盐值)] 进行对比。
这两个随机盐值必须相同,才能解密成功。

随机盐值有什么作用?
每一个随机盐值对应了一个彩虹数据库,必须要穷举所有的字符,也就是说加入有N个随机盐值,那么就需要N个不同的彩虹数据库,破解成本比较高。
3)将加盐加密之后的密码 + 随机盐值存储到数据库
如何存储?
第一种方案,数据库增加两个字段来存放这两个值,不安全。
第二种方式,既然这个密码和盐值都是相关的,就可以把它们两个整合到一块,也就是存储到一个字段,因为它们本身就是为了密码服务的。
但是又有新的问题?整合之后如何知道哪个是盐值,哪个又是密码呢?如何区分出来?

这个时候就需要我们后端自己约定一个规则来分离盐值和加盐加密之后的密码,只要保证我们加密和解密都是同一个规则就行了。
规则常见的有:分隔符、按位数存储密码等。
ps:分隔符也有个缺点,就是传递明文密码的时候不能有分隔符。
这样做的目的是即使别人已经得到我们的数据库了,但是得不到我们的密码了。

规则:按照一个分隔符"$"来分离盐值和加盐加密之后的密码,那么最终密码一共是

32位(uuid)+32位(md5)+1位(分隔符"$")  --- 65位
接下来我们写一个密码工具类

/*** 密码工具类* 1、加盐加密* 2、加盐解密*/
public class PasswordTools {/*** 加密(加盐)** @param password 明文密码* @return 加盐加密之后的密码*/public static String encrypt(String password) {//1.通过UUID生成随机盐值  ps:UUID是32位String salt = UUID.randomUUID().toString().replace("-", "");//2.得到加盐加密之后的密码(md5(明文密码+随机盐值)) ps:md5是32位  StandardCharsets.UTF_8是仅有中文才必须要设置String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8));//3.合并随机盐值和加盐加密之后的密码,合并规则:分隔符String dbPassword = salt + "$" + finalPassword;//这里加盐加密之后应该是64位+1位分隔符=65位return dbPassword;}/*** 验证加盐加密之后的密码** @param password   要验证的密码(明文密码,未加密)结果不一定对* @param dbPassword 数据库中的盐值加密的密码(salt + $ + 加盐加密密码finalPassword)* @return*/public static Boolean decrypt(String password, String dbPassword) {boolean result = false;if (StringUtils.hasLength(password) && StringUtils.hasLength(dbPassword) &&dbPassword.length() == 65 && dbPassword.contains("$")) {//参数正确String[] dbPasswordArr = dbPassword.split("\\$");//1.得到之前生成的随机盐值String salt = dbPasswordArr[0];//2.得到之前数据库加盐加密之后的密码String finalPassword = dbPasswordArr[1];//3.使用同样的加密算法和随机盐值生成验证密码的最终加盐加密的密码//当然下面重新加盐加密这一步可以去重载encrpy,多传入一个salt参数即可,这个salt就是这里取出来的salt,然后返回密码即可。password = DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8));//4.对比验证密码加盐加密之后的密码和数据库之前已经加盐加密的密码if (password.equals(finalPassword)) {result = true;}}return result;}public static void main(String[] args) {String password = "123";String dbPassword = PasswordTools.encrypt(password);System.out.println("加盐加密密码:" + dbPassword);boolean result = PasswordTools.decrypt("123", dbPassword);System.out.println("对比结果1:" + result);boolean result2 = PasswordTools.decrypt("123456", dbPassword);System.out.println("对比结果2:" + result2);}
}

 

注册用户时候输入密码的时候对用户的密码进行加盐加密: 

当然我们也可以采用Spring Security加盐,从AOP到拦截器到现在的Spring Security,好比三个时代的产物,AOP自行车、拦截器汽车、Spring Security高铁。
Spring Security是我们以后会经常用的,所以接下来我们使用一下这个框架: 

① 添加 Spring Security 框架 
方式1: 

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

方式2:

进行测试

② 关闭 Spring Security 验证   

不要去自动注入Spring Security框架(即不要自动加载Spring Security认证了)。

③ 实现加盐和比对   

controller测试代码

@RequestMapping("/salt")
public String getSalt() {                                                                                BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String password = "123456";String markPassword = passwordEncoder.encode(password);System.out.println(markPassword);System.out.println();System.out.println(passwordEncoder.encode(password));System.out.println();// 参数1:原始密码 / 参数2:已加密密码System.out.println(passwordEncoder.matches(password, markPassword));return "hello salt";
}

实现验证码功能 

在这里引用了第三方工具类:hutools

在pom.xml中添加依赖:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency>

前端核心代码

添加拦截器规则 

全局变量类新增验证码遍历

现在多指定几个配置文件,包含生产配置文件和开发配置文件,并将主配置文件暂时指向开发配置文件。

主配置文件appllication.properties

# 配置数据库的连接字符串

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/myblog?characterEncoding=utf8&useSSL=false

spring.datasource.username=root

spring.datasource.password=111111

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 设置 Mybatis 的 xml 保存路径

mybatis.mapper-locations=classpath:mybatis/*Mapper.xml

# 配置打印 MyBatis 执行的 SQL

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# 配置打印 MyBatis 执行的 SQL

logging.level.com.mybatis.demo=debug

# 配置运行环境

spring.profiles.active=dev

开发配置文件application-dev.properties

imgpath=D:\\Work\\image\\

生产环境配置文件application-prod.properties 

imgpath=/root/image/ 

实现将Session存储的用户信息持久化到Redis功能

添加依赖

或者pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>

在主配置文件中添加redis配置信息

# 设置连接的Redis数据库的索引。默认情况下,索引为0,即连接到默认的数据库。
# 如果设置多个Redis实例,可以通过此项进行区分。
spring.redis.database=0
# 设置连接的Redis服务器的主机名或IP地址
spring.redis.host=x.x.x.x
spring.redis.password=
# 设置连接的Redis服务器的端口号。在此,服务器的端口号为6379,这是Redis默认的端口号,默认的话可以省略不写。
spring.redis.port=6379
# 设置会话存储类型为Redis
spring.session.store-type=redis
# 设置服务器上所有Servlet的会话超时时间为1800秒,即30分钟。
# Spring Boot默认的会话超时时间为30分钟,但在这里,它被明确地设定为1800秒
server.servlet.session.timeout=1800
# 设置Redis的flush mode为'on_save'。flush mode决定了何时将数据写入磁盘。
# 'on_save'意味着每次数据被保存时都会立即写入磁盘,这可以保证数据的持久性,但可能会影响性能。
spring.session.redis.flush-mode=on_save
# 设置Spring Session在Redis中的命名空间为'spring:session'。
# 这是为了防止不同的应用在同一Redis实例中产生数据冲突。每个应用都可以使用不同的命名空间来保存自己的会话数据。
spring.session.redis.namespace=spring:session

让用户类实现序列化接口,并生成序列版本号

当然本项目还有很多可以扩展的点:

1.文章保存草稿
2.定时发布功能
3.用户多次登录,账号冻结的业务
4.评论功能
5.个人中心:支持修改密码、修改昵称(非登录名)、上传头像等功能
6.找回密码

本项目只是作为一个比较基础的巩固自己学习web开发阶段对知识的实践,当然还有很多不足,请大佬多多指点!!!谢谢 

源码地址:https://gitee.com/themyth_ws/sping-family-bucket/tree/master/myblog-ssm

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

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

相关文章

GE IS220PVIBH1A 336A4940CSP16 电源模块

GE IS220PVIBH1A 336A4940CSP16 电源模块是通用电气&#xff08;GE&#xff09;的一种电源模块&#xff0c;用于工业控制和电力系统中&#xff0c;提供电源供应和保护功能。以下是这种类型电源模块的一般特点和功能&#xff1a; 电源供应&#xff1a;GE IS220PVIBH1A 336A4940C…

电脑WIFI突然消失

文章目录 1. 现象2. 解决办法1&#xff1a;重新启用无线网卡设置3. 解决办法2&#xff1a;更新无线网卡驱动4. 解决办法3&#xff1a;释放静电5. 解决办法4&#xff1a;拆机并重新插拔无线网卡 1. 现象 如下图&#xff1a;电脑在使用过程中WIFI消失 设备管理器中的无线网卡驱…

数据通信——应用层(域名系统)

引言 TCP到此就告一段落&#xff0c;这也意味着传输层结束了&#xff0c;紧随其后的就是TCP/IP五层架构的应用层。操作系统、编程语言、用户的可视化界面等等都要通过应用层来体现。应用层和我们息息相关&#xff0c;我们使用电子设备娱乐或办公时&#xff0c;接触到的就是应用…

19.组合模式(Composite)

意图&#xff1a;将对象组成树状结构以表示“部分&#xff0d;整体”的层次结构&#xff0c;使得Client对单个对象和组合对象的使用具有一致性。 上下文&#xff1a;在树型结构的问题中&#xff0c;Client必须以不同的方式处理单个对象和组合对象。能否提供一种封装&#xff0c…

基于jquery开发的Windows 12网页版

预览 https://win12.gitapp.cn 首页代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"refresh" content"0;urldesktop.html" /> <meta name"viewport&…

selenium中ActionChains方法详细讲解

前言 本文将介绍Selenium中的ActionChains类及其使用方法&#xff0c;帮助您模拟用户在网页上的鼠标和键盘操作。了解ActionChains的常用方法和示例代码&#xff0c;可轻松实现移动鼠标、点击元素、拖拽元素等操作。通过本文的学习&#xff0c;您能更好地应用ActionChains解决自…

Vue中开发中Mock和总线了解以及应用

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专栏…

基于微信小程序的校园代送跑腿系统(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

内存对齐--面试常问问题和笔试常考问题

1.内存对齐的意义 C 内存对齐的主要意义可以简练概括为以下几点&#xff1a; 提高访问效率&#xff1a;内存对齐可以使数据在内存中以更加紧凑的方式存储&#xff0c;从而提高了数据的访问效率。处理器通常能够更快地访问内存中对齐的数据&#xff0c;而不需要额外的字节偏移计…

jar包在linux服务器已经运行好,但是访问不到地址

jar包在linux服务器已经运行好&#xff0c;但是访问不到地址 1.将jar包已经上传到linux服务器&#xff0c;并且已经启动 2.但是在本地访问的时候&#xff0c;访问不到,云服务的的安全组策略也开放了相对应的端口。 3.解决方案 修改防火墙开放的接口 iptables -I INPUT -p t…

区块链实验室(26) - 区块链期刊Blockchain: Research and Applications

Elsevier出版物“Blockchain: Research and Applications”是浙江大学编审的期刊。该期刊自2020年创刊&#xff0c;并出版第1卷。每年出版4期&#xff0c;最新期是第4卷第3期(2023年9月)。 目前没有官方的IF&#xff0c;Elsevier的引用因子Citescore是6.4。 虽然是新刊&#xf…

不甘于被强势厂商捆绑,中国移动未来或自研5G基站

一直以来运营商被认为只是做服务&#xff0c;而设备等都是由设备商提供的&#xff0c;甚至由于如今的设备高度复杂&#xff0c;设备商已承包越来越多的基站运维工作&#xff0c;运营商的技术水平越来越低&#xff0c;不过随着中国移动发布5G射频芯片8676&#xff0c;似乎显示出…

CocosCreator3.8研究笔记(二十)CocosCreator UI组件(四)

RichText 组件 RichText 组件是富文本控件&#xff0c;实际是由多个 Label 节点拼装而成&#xff0c;用来显示一段带有不同样式效果的文字&#xff0c;通过BBCode 标签来设置文字的样式。 目前支持的样式有&#xff1a;颜色&#xff08;color&#xff09;、字体大小&#xff08…

介绍 Docker 的基本概念和优势V2.0

介绍 Docker 的基本概念和优势V2.0 一、Docker 的基本概念1.1 Docker 是什么&#xff1f;1.2 Docker 的组成部分1.3 Docker 的基本概念 二、Docker 的优势1. 轻量级&#xff1a;2. 可移植性&#xff1a;3. 自包含&#xff1a;4. 隔离性&#xff1a;5. 可扩展性&#xff1a;6. 易…

Vue中前端导出word文件

很多时候在工作中会碰到完全由前端导出word文件的需求&#xff0c;因此特地记录一下比较常用的几种方式。 一、提供一个word模板 该方法提供一个word模板文件&#xff0c;数据通过参数替换的方式传入word文件中&#xff0c;灵活性较差&#xff0c;适用于简单的文件导出。需要…

28 WEB漏洞-XSS跨站之WAF绕过及安全修复

目录 常规WAF绕过思路标签语法替换特殊符号干扰提交方式更改垃圾数据溢出加密解密算法结合其他漏洞绕过 自动化工具说明强大的fuzzing引擎安全修复方案演示案例&#xff1a; 常规WAF绕过思路 标签语法替换 xss的效果可以由多个代码来实现&#xff0c;就类似于我们使用到的其它…

2023-9-25 排队打水

题目链接&#xff1a;排队打水 #include <iostream> #include <algorithm>using namespace std;typedef long long LL;const int N 100010;int n; int t[N];int main() {scanf("%d", &n);for(int i 0; i < n; i ) scanf("%d", &t…

【Java 基础篇】Executors工厂类详解

在多线程编程中&#xff0c;线程池是一项重要的工具&#xff0c;它可以有效地管理和控制线程的生命周期&#xff0c;提高程序的性能和可维护性。Java提供了java.util.concurrent包来支持线程池的创建和管理&#xff0c;而Executors工厂类是其中的一部分&#xff0c;它提供了一些…

基于UDP协议的网络服务器的模拟实现

目录 服务端类UdpServer的模拟实现 服务端类UdpServer的成员变量 服务端类UdpServer的构造函数、初始化函数initServer、析构函数 服务端类UdpServer的start函数 服务端类UdpServer的整体代码&#xff08;即udp_server.h文件的整体代码&#xff09; 基于服务端类UdpServe…

Tomcat中文路径目录

一、问题描述 linux环境下tomcat发布了包含中文名字的页面和文件&#xff0c;浏览器访问报404&#xff0c;非中文页面没有问题&#xff1b;本人为RP设计的原型图发布&#xff0c;其中包含了大量的中文文件和路径 二、解决步骤 第一步&#xff0c;设置tomcat&#xff0c;配置…