太仓专业网站建设我想创个网站
news/
2025/10/3 19:50:16/
文章来源:
太仓专业网站建设,我想创个网站,软件项目管理pdf,网站关键字优化工具目录
项目背景
项目技术栈
项目介绍
项目亮点
项目启动
1.创建SSM#xff08;省略#xff09;
2.配置项目信息
3.将前端页面加入到项目中
4.初始化数据库
5.创建标准分层的目录
6.创建和编写项目中的公共代码以及常用配置
7.创建和编写业务的Entity、Mapper、…目录
项目背景
项目技术栈
项目介绍
项目亮点
项目启动
1.创建SSM省略
2.配置项目信息
3.将前端页面加入到项目中
4.初始化数据库
5.创建标准分层的目录
6.创建和编写项目中的公共代码以及常用配置
7.创建和编写业务的Entity、Mapper、Service、Controller等基础代码
8.按照页面从前端或者后端开始实现相应的交互和业务代码
项目功能实现
实现用户注册功能
实现用户登录功能
实现添加文章功能
实现文章编辑功能
实现注销功能
实现文章内容详情页功能
实现增加阅读量功能
实现判断当前用户登录状态功能
实现用户的博客列表功能
实现文章删除功能
实现统一异常处理功能
实现统一数据格式返回
实现主页博客展示和分页功能
实现密码加盐(增强密码安全性)功能
实现验证码功能
实现将Session存储的用户信息持久化到Redis功能 项目背景 本项目是我个人为了提升技术能力和积累实战经验搭建的一个个人博客系统。通过这个项目将学习并实践Web开发、前端设计、后端编程等技能同时记录和分享我在学习和实践中的心得体会。 项目技术栈 SpringBootSpringWebMybatisMySQLRedis 项目介绍 基于SSM的前后端分离的个人博客系统项目中融入了Editor开源组件更方便用户对博文的编写大概包含以下功能用户的登录与注册、个人博客列表及其所有人博客展示用户对个人博文的增加、删除、修改、查询博客列表的分页管理等。 项目亮点 1.注册用户时候对用户密码进行加盐加密处理登录用户时候对密码进行加盐解密处理从而提高用户信息安全性。 2.将用户信息存入session实现内存到redis的持久化支持分布式处理。 3.通过拦截器的方式进行登录验证减轻代码冗余提高重用率。 项目启动
1.创建SSM省略
2.配置项目信息
application.properties # 配置数据库的连接字符串 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/myblog?characterEncodingutf8useSSLfalse 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是不可逆的但是它可以被穷举因为在其它语言例如pythonc使用md5加密123也是得到一个固定的值如同上面的themyth于是我们就可以使用一个固定md5的表(彩虹表)来反推出密码此时使用md5相当于没有加盐相当于给程序加了一把带钥匙的锁显然这种做法是不行的这个问题点在于对于同一个字符串每次生成的md5密码是一样是一样的就可以被穷举。解决方案加盐处理。 加盐处理 简单来说就是仍然采用md5加密的方式只是对于同一个字符串每次生成的md5密码都不一样。 为什么可以让每次生成的密码不一样呢 实现的关键使用随机数(称之为盐) 生活案例解释这好比炒菜来一样我们每个人在炒菜的时候加盐的含量不可能完全相同所以菜的咸淡也会不同。 传统加密vs加盐加密 md5加密流程明文密码---》md5(明文密码)---》密码 加盐加密流程1构建期明文密码随机盐值 2md5(明文密码随机盐值)---》加盐加密之后的密码数据库此时还不能直接存这个密码因为这个密码在进行加盐解密是解不出来的。原因 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生成随机盐值 psUUID是32位String salt UUID.randomUUID().toString().replace(-, );//2.得到加盐加密之后的密码(md5(明文密码随机盐值)) psmd5是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 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-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中添加依赖 dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.16/version
/dependency 前端核心代码 添加拦截器规则 全局变量类新增验证码遍历 现在多指定几个配置文件包含生产配置文件和开发配置文件并将主配置文件暂时指向开发配置文件。
主配置文件appllication.properties # 配置数据库的连接字符串 spring.datasource.urljdbc:mysql://127.0.0.1:3306/myblog?characterEncodingutf8useSSLfalse spring.datasource.usernameroot spring.datasource.password111111 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # 设置 Mybatis 的 xml 保存路径 mybatis.mapper-locationsclasspath:mybatis/*Mapper.xml # 配置打印 MyBatis 执行的 SQL mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis 执行的 SQL logging.level.com.mybatis.demodebug # 配置运行环境 spring.profiles.activedev 开发配置文件application-dev.properties imgpathD:\\Work\\image\\ 生产环境配置文件application-prod.properties imgpath/root/image/ 实现将Session存储的用户信息持久化到Redis功能
添加依赖 或者pom.xml
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
dependencygroupIdorg.springframework.session/groupIdartifactIdspring-session-data-redis/artifactId
/dependency
在主配置文件中添加redis配置信息 # 设置连接的Redis数据库的索引。默认情况下索引为0即连接到默认的数据库。 # 如果设置多个Redis实例可以通过此项进行区分。 spring.redis.database0 # 设置连接的Redis服务器的主机名或IP地址 spring.redis.hostx.x.x.x spring.redis.password # 设置连接的Redis服务器的端口号。在此服务器的端口号为6379这是Redis默认的端口号默认的话可以省略不写。 spring.redis.port6379 # 设置会话存储类型为Redis spring.session.store-typeredis # 设置服务器上所有Servlet的会话超时时间为1800秒即30分钟。 # Spring Boot默认的会话超时时间为30分钟但在这里它被明确地设定为1800秒 server.servlet.session.timeout1800 # 设置Redis的flush mode为on_save。flush mode决定了何时将数据写入磁盘。 # on_save意味着每次数据被保存时都会立即写入磁盘这可以保证数据的持久性但可能会影响性能。 spring.session.redis.flush-modeon_save # 设置Spring Session在Redis中的命名空间为spring:session。 # 这是为了防止不同的应用在同一Redis实例中产生数据冲突。每个应用都可以使用不同的命名空间来保存自己的会话数据。 spring.session.redis.namespacespring:session 让用户类实现序列化接口并生成序列版本号 当然本项目还有很多可以扩展的点
1.文章保存草稿 2.定时发布功能 3.用户多次登录账号冻结的业务 4.评论功能 5.个人中心支持修改密码、修改昵称非登录名、上传头像等功能 6.找回密码
本项目只是作为一个比较基础的巩固自己学习web开发阶段对知识的实践当然还有很多不足请大佬多多指点谢谢
源码地址https://gitee.com/themyth_ws/sping-family-bucket/tree/master/myblog-ssm
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/926257.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!