SSM个人博客项目
文章目录
- SSM个人博客系统实现
- 项目介绍
- 一、准备工作
- 0. 创建项目添加对应依赖
- 1. 数据库设计
- 2. 定时实体类
- 二、功能实现
- 1.统一功能处理
- 统一返回格式
- 统一异常处理
- 定义登录拦截器
- 2. 注册登录实现
- 生成获取验证码
- 密码加盐实现
- 注册功能
- 登录功能
- 注销功能
- 3.登录用户博客列表
- 获取登录用户信息
- 获取登录用户文章列表
- 4.文章相关操作
- 发布文章
- 删除文章
- 修改文章
- 定时发布文章
- 分页获取所有用户的文章
- 文章详情
- 5.文章草稿箱实现
- 从草稿箱发布文章
- 修改草稿
- 6.个人信息修改
- 头像修改
- 基本信息修改
- 7. 其它密码相关功能实现
- 修改密码
- 设置密保问题
- 找回密码
SSM个人博客系统实现




项目介绍
本项目是一个前后端分离的个人博客系统,实现的主要功能有用户注册、用户登录、找回密码、验证码、文章的发布和删除、定时发布文章功能、草稿箱功能、文章列表分页功能、用户信息修改包括上传头像。利用SpingAOP实现了统一的登录验证、异常处理、统一返回格式。
一、准备工作
0. 创建项目添加对应依赖

从Maven仓库引入SprinAOP依赖和第三方Hutool依赖
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1. 数据库设计
数据库一共有6张表,分别是用户表、文章表、文章草稿表、定时发布文章表、密保问题表

2. 定时实体类
实体类分别对应数据表
用户类
@Data
public class User {private int id;private String username;private String password;private String netName;private String photo;private String git;private Integer articleCount;private Date createTime;private Date updateTime;// 用户是否第一次登录private int state;
}
文章类
@Data
public class Article {private Integer id;private String title;private String content;private Date createTime;private Date updateTime;private Integer userId;private Integer visits;
}
文章草稿
@Data
public class Drafts {private Integer id;private String title;private String content;private Date createTime;private Date updateTime;private Integer userId;
}
定时发布的文章
@Data
public class TimingArticle {private Integer id;private String title;private String content;private Date postTime;private Integer userId;
}
密保问题
@Data
public class QuestionPassword {private Integer id;private String question1;private String question2;private String question3;private String answer1;private String answer2;private String answer3;private Integer userId;
}
二、功能实现
1.统一功能处理
使用SpringAOP可以实现统一功能的处理,统一的功能处理可以避免代码的冗余。
统一返回格式
先定义一个响应类,重载一些方法,success表示执行成功(正确的查询数据、登录成功等),fail表示执行失败(密码错误、参数非法的),重载可以非常灵活的让我们给前端返回数据。
@Getter
@Setter
public class Response {private int code;private String message;private Object data;public static Response success(Object data) {Response response = new Response();response.code = 200;response.message = "";response.data = data;return response;}public static Response success(String message) {Response response = new Response();response.message = message;response.code = 200;return response;}public static Response success(Object data,String message) {Response response = new Response();response.message = message;response.code = 200;response.data = data;return response;}public static Response fail(String message) {Response response = new Response();response.code = -1;response.message = message;return response;}public static Response fail(String message,int code) {Response response = new Response();response.code = code;response.message = message;return response;}
}
实现统一响应格式实现,统一的返回格式有利于后端统一标准规范,降低前后端的沟通成本。定义ResponseAdvice类实现ResponseBodyAdvice接口,并用@ControllerAdvice注解修饰表示该类为一个增强类
@ControllerAdvice
@ResponseBody
public class ResponseAdvice implements ResponseBodyAdvice {@Resourceprivate ObjectMapper mapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {// 如果是定义的返回格式就直接返回if (body instanceof Response) {return body;}if (body instanceof String) {// 转换成json根式的返回格式return mapper.writeValueAsString(Response.success(body));}return Response.success(body);}
}
最后的响应返回格式,后面的所有响应都是如此
正确执行的响应
{code:200data: "自定义的返回数据"message: "自定义的返回消息"
}
错误执行的响应
{code:-1data: ""message: "自定义的返回消息"
}
统一异常处理
统一的异常处理,服务器发送异常后我们可以通过增强类做统一处理后给前端返回响应,可以添加一些预计会发送的异常分别进行处理。
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {@ExceptionHandler(Exception.class)public Object handler(Exception e) {return Response.fail("服务器异常",500);}
}
定义登录拦截器
定义登录拦截器,可以避免大量登录验证的代码冗余,让指定的接口统一验证。
/*** 自定义登录拦截器*/
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession(false);if (session != null && session.getAttribute(Constant.SESSION) != null) {response.setStatus(200);return true;}// 重定向到登录页面response.sendRedirect("/login.html");return false;}
}
添加自定义拦截器,如果某些接口被拦截器拦截就需要经过拦截器验证后才能去执行对应的Controller方法,也就是需要登录后才能使用。
@Configuration
public class AppConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()) // 添加自定义拦截器.addPathPatterns("/**") //拦截所有接口.excludePathPatterns("/user/login") //排除的接口.excludePathPatterns("/user/reg").excludePathPatterns("/user/getVerify").excludePathPatterns("/user/byIdUser").excludePathPatterns("/questionPass/getQuestion").excludePathPatterns("/questionPass/findPass").excludePathPatterns("/questionPass/setNewPass").excludePathPatterns("/article/byIdArticle").excludePathPatterns("/article/pagingGetArticle").excludePathPatterns("/login.html").excludePathPatterns("/blog_detailed.html").excludePathPatterns("/blog_list.html").excludePathPatterns("/find_pass.html").excludePathPatterns("/register.html").excludePathPatterns("/img/**").excludePathPatterns("/js/**").excludePathPatterns("/photo/**").excludePathPatterns("/css/**");}
}
2. 注册登录实现
生成获取验证码
使用第三方工具Hutool来绘制验证码,把生成的验证码保存到session中,通过响应把图片传递个前端
@GetMapping("/getVerify")
public Response getVerify(HttpSession session,HttpServletResponse response) {VerificationCodeUtil.getCode(session,response);return Response.success("ok");
}
密码加盐实现
通过密码加盐来保证用户密码的一定安全性
- 加盐的实现思路为:用户注册把生成一个UUID拼接上用户前端传递的明文密码进行MD5,以
#号分割再把生成的UUID拼接到#后面,生成最终密码存入数据库。 - 解密思路:把数据库加密的代码查询出来,以
#把UUID分割出来,把用户输入的明文密码以同样的方式拼接上分割出来的UUID,加盐后再和数据库查询的密码进行比对
public class PasswordUtil {/*** 密码加盐* @param password 明文密码* @return*/public static String passwordAddSalt(String password) {String uuid = UUID.randomUUID().toString().replace("-","");String finalPass = (DigestUtils.md5DigestAsHex((uuid+password).getBytes())+"#"+uuid);return finalPass;}/*** 验证密码* @param password 明文密码* @param finalPass 加密密码* @return*/public static boolean check(String password,String finalPass) {String uuid = finalPass.split("#")[1];String pass = (DigestUtils.md5DigestAsHex((uuid+password).getBytes())+"#"+uuid);return pass.equals(finalPass);}
}
注册功能

约定前后端交互
请求:
{username : "用户名",password : "密码",confirmPass :"确认密码",verifyCode : "验证码"
}
响应:
{"code":200,"message":"注册成功","data":null
}
先简单的数据校验,密码加盐,再生成随机的网名,注意网名和用户名要区分,考虑到用户名已经存在问题。
@PostMapping("/reg")
public Response reg(String username,String password,String confirmPass,HttpSession session,String verifyCode) {if (verifyCode == null || "".equals(verifyCode.trim()) || !VerificationCodeUtil.check(session,verifyCode)) {return Response.fail("验证码错误");}if (username == null || password == null || confirmPass == null || "".equals(username.trim())|| "".equals(password.trim()) || "".equals(confirmPass.trim())) {return Response.fail("用户名密码非法");}if (!password.equals(confirmPass)) {return Response.fail("两次密码输入不一致");}User user = userService.byNameUser(username);if (user != null) {return Response.fail("用户已经被注册");}// 密码加盐String finalPass = PasswordUtil.passwordAddSalt(password);String netName = "用户"+System.currentTimeMillis()%1000000;int ret = userService.reg(username,finalPass,netName);if (ret == 1) {return Response.success("注册成功");}return Response.fail("用户名密码非法");
}
登录功能

约定前后端交互
请求:
{username:"用户名",password:"密码",inputVerify="验证码"
}
响应:
{"code":200,"message":"登录成功/用户名密码错误/验证码错误","data":null
}
登录成功后把用户信息保存session会话,前端跳转到博客列表页
@PostMapping("/login")
public Response login(String username, String password,String inputVerify ,HttpSession httpSession,HttpServletRequest request) {if (inputVerify == null || "".equals(inputVerify.trim()) || !VerificationCodeUtil.check(httpSession,inputVerify)) {return Response.fail("验证码错误");}if (username == null || password == null || "".equals(username.trim()) || "".equals(password.trim())) {return Response.fail("用户名密码错误");}User user = userService.byNameUser(username);if (user != null && PasswordUtil.check(password,user.getPassword())) {// 把密码置为空user.setPassword("");user.setUsername("");HttpSession session = request.getSession(true);session.setAttribute(Constant.SESSION,user);return Response.success("登录成功");}return Response.fail("用户名密码错误");
}
注销功能
删除session会话即可
@PostMapping("/logout")
public Response login(HttpServletRequest request) {User user = LogInUserInfo.getUserInfo(request);if (user == null) {return Response.fail("当前未登录");}request.removeAttribute(Constant.SESSION);return Response.success("注销成功");
}
3.登录用户博客列表
登录成功后跳转到博客列表页面,需要获取当前用户的所有博客信息,和当前用户的信息。

获取登录用户信息
用户登录后通过用户的id来查询到用户的信息,查询到后要把用户名和密码一些敏感信息给影响。
请求:
"http://127.0.0.1:7070/usre/getUserInfo"
响应:
{"code":200,"message":"","data":{"id":1,"username":"","password":"","netName":"用户11326","photo":"../img/logo.jpg","git":"https://gitee.com/he-hanyu","articleCount":0,"createTime":null,"updateTime":null,"state":0}
}
主意通过用户的state字段判断用户是否第一次登录,如果是第一次登录就在前端提示用户设置密保问题。

@GetMapping("/getUserInfo")
public Response getUserInfo(HttpServletRequest request) {User user = LogInUserInfo.getUserInfo(request);User myUser = userService.byIdUser(user.getId());myUser.setPassword("");myUser.setUsername("");// 判断是否是第一次登录if (myUser.getState() == 1) {//如果是第一登录就修改状态userService.updateState(user.getId());}return Response.success(myUser);
}
获取登录用户文章列表
请求:
"http://127.0.0.1:7070/user/getUserArticle"
响应:
{"code":200,"message":"","data":[{"id":1,"title":"测试","content":"#Hh宇的个人博客","createTime":"2023-08-06","updateTime":null,"userId":1,"visits":0}]
}
通过用户id来查看当前用户的博客,显示博客是博客列表要注意把文章内荣进行一个截取
@GetMapping("/getUserArticle")
public Response getUserArticle(HttpServletRequest request) {User user = LogInUserInfo.getUserInfo(request);List<Article> articleList = articleService.getUserArticle(user.getId());for (Article article : articleList) {if (article.getContent().length() > 100) {article.setContent(article.getContent().substring(0,100)+"......");}}return Response.success(articleList);
}

博客列表有对应的查看文章响应,修改文章,删除文章。
4.文章相关操作

发布文章
发布文章校验标题和正文是否为空,校验完毕后给数据表添加信息。
约定前后端交互:
请求:
{title: "文章标题",content: "文章正文"
}
响应:
{"code":200,"message":"发布成功","data":null
}
@PostMapping("/addArticle")
public Response addArticle(String title,String content,HttpServletRequest request) {if (title == null || content == null || "".equals(title.trim()) || "".equals(content.trim())) {return Response.fail("发布失败");}User user = LogInUserInfo.getUserInfo(request);int row = articleService.addArticle(title,content,user.getId());if (row == 1) {userService.articleCountAuto(user.getId(),1);return Response.success("发布成功");}return Response.fail("发布失败");
}
删除文章
点击登录用户文章列表后通过博客id来删除文章,文章id由前端在生成链接时拼接在querystr中。点击删除链接,就会获取到文章Id给后端发送删除请求。且删除时验证该文章是否属于当前登录用户。

请求:
POST http://127.0.0.1:7070/article/idDeleteArticle
{articleId : 文章Id
}
@PostMapping("/idDeleteArticle")
public Response idDeleteArticle(Integer articleId,HttpServletRequest request) {if (articleId == null || articleId <= 0) {return Response.fail("删除失败");}User user = LogInUserInfo.getUserInfo(request);int ret = articleService.idDeleteArticle(articleId,user.getId());if (ret == 1) {userService.articleCountAuto(user.getId(),-1);return Response.success("删除成功");}return Response.fail("删除失败");
}
修改文章
点击修改文章后,会在url里拼接上一个文章id进入博客编辑页面,再次点击发布博客后会判断url中的querystr中是否有文章Id如果有说明是修改博客。


获取博客请求也就是通过querystr中的id查询博客:
GET http://127.0.0.1:7070/article/byIdArticle?articleId=2
修改博客请求:
POST http://127.0.0.1:7070/article/updateArticle HTTP/1.1
{title: "修改后的标题",content: "修改后的正文"
}
响应:
{"code":200,"message":"修改成功","data":null
}
@PostMapping("/updateArticle")
public Response updateArticle(String title,String content,Integer articleId,HttpServletRequest request) {if (title == null || content == null || "".equals(title.trim()) || "".equals(content.trim())|| articleId == null || articleId <= 0) {return Response.fail("内容非法");}User user = LogInUserInfo.getUserInfo(request);int ret = articleService.updateArticle(title,content,articleId,user.getId());if (ret == 1) {return Response.success("修改成功");}return Response.fail("修改失败");
}
定时发布文章

定时发布文章前端给后端传递格式化的时间,后端再装换成和数据库对应的Date时间存,把待发布文章存入数据库,通过Spring的定时任务,每5秒扫描一下定时任务数据表,如果当前的大于发布时间就从数据库中获取到文章信息并进行发布,且删除对应的文章信息。
请求:
{title: "文章标题",content: "文章正文",postTime : "2023-08-06 16:28:26"
}
响应:
{"code":200,"message":"定时博客任务发布成功","data":null
}
需要通过正则判断前端传递的时间格式是否正确,且发布时间没有大于预期值。
@RestController
@RequestMapping("/timed")
public class TimedArticleController {@Autowiredprivate TimedArticleService timedArticleService;@SneakyThrows@PostMapping("/timingPost")public Response addTimedPost(String title, String content, String postTime, HttpServletRequest request) {if (title == null || content == null || postTime == null ||"".equals(title.trim()) || "".equals(content.trim()) || "".equals(postTime.trim())) {return Response.fail("内容非法");}// 校验时间格式是否正确if (DateTimeUtil.isValidDateTimeFormat(postTime)) {System.out.println(postTime);// 获取当前时间String time = DateUtil.now();// 判断当前时间和发布时间是否合法if (DateTimeUtil.getTimeDifferenceDay(time,postTime) > 7) {return Response.fail("发布时间不合法,请输入小于7天的发布时间");}SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date date = format.parse(postTime);User user = LogInUserInfo.getUserInfo(request);int ret = timedArticleService.addTimedPost(title,content,date,user.getId());if (ret == 1) {return Response.success("定时博客任务发布成功");}}return Response.fail("发布失败");}
}
在SpingBoot的启动类上添加注解开启定时任务,
@SpringBootApplication
@EnableScheduling
public class BlogApplication {public static void main(String[] args) {SpringApplication.run(BlogApplication.class, args);}}
再创建一个定时任务类每5秒扫描一下数据库
@Component
public class ScheduledRelease {@Autowiredprivate TimedArticleService timedArticleService;@Autowiredprivate ArticleService articleService;// 每5秒执行一次@Scheduled(fixedRate = 5000)@Transactionalpublic void scheduledTak() {Date date = new Date();List<TimingArticle> timingArticles = timedArticleService.getPostArticle(date);for(TimingArticle article : timingArticles) {articleService.addArticle(article.getTitle(),article.getContent(),article.getUserId());// 发布后立马删除记录timedArticleService.idDelete(article.getId());}}
}
需要注意的是查询时间对比sql要先把时间转换为时间戳
<select id="getPostArticle" resultMap="TimingArticleInfo">select * from timed_article where unix_timestamp(#{time}) > unix_timestamp(post_time);</select>
分页获取所有用户的文章
所有用户的文章数量比较多,为了减小服务器压力,所以采用分页。一页显示5篇文章。每次进入博客列表页,后端会先查询数据库中的文章数量通过公式计算出一共有多少页,再把最大页数和当前页的文章传递给前端,前端默认获取第一页的文章。
分页公式为文章总数*1.0/每页显示数量向上取整算出页数,前端传递的(页数-1)*每页显示数量求出从数据表中哪一条数据开始查询。
请求:
GET http://127.0.0.1:7070/article/pagingGetArticle?pageNumber=1&size=5 HTTP/1.1
响应:
{
"code":200,
"message":"",
"data":{"pageCount":2,"articles":[{"id":6,"title":"再来一篇文章","content":"#Hh宇的个人博客","createTime":"2023-08- 08","updateTime":null,"userId":2,"visits":0},]}
}
后端代码
/*** 分页获取所有用户博客* @param pageNumber 页数* @param size 每页显示多少篇文章* @return*/@GetMapping("/pagingGetArticle")public Response pagingGetArticle(Integer pageNumber,Integer size) {if (pageNumber < 1) {pageNumber = 1;}// 获取所有文章数量int articleCount = articleService.getArticleCount();// 计算出每页size个最多有多少页int pageCount = (int)(Math.ceil(articleCount*1.0/size));// 公式计算步长int offset = (pageNumber-1)*size;List<Article> articles = articleService.pagingGetArticle(size,offset);System.out.println(articles);HashMap<String,Object> result = new HashMap<>();result.put("pageCount",pageCount);result.put("articles",articles);return Response.success(result);}

文章详情
点击查看全文即可查看文章详情,文章详情里展示了文章标题、文章发布时间、文章的浏览量和文章正文。左边显示的是当前文章的作者信息。先通过querystr中的的文章id查询到文章,再同过文章里返回的当前用户id,来查询到当前文章作者对应的信息。

请求:
GET http://120.25.124.200:7070/article/byIdArticle?articleId=5 HTTP/1.1
响应:
{
"code":200,
"message":"",
"data":{"id":5,"title":"文章分页","content":"#Hh宇的个人博客","createTime":"2023-08-08","updateTime":null,"userId":2,"visits":0}
}
5.文章草稿箱实现

文章草稿包存草稿箱和发布文章类似,点击保存草稿后就把文章保存到草稿箱当中,并更新数据表信息,可以从草稿箱中发布博客,也可以修改草稿箱里的草稿文章或者删除草稿。
需要注意的是修改草稿,点击修改草稿后,通过url中的querystr来查询到对应的草稿
GET http://127.0.0.1:7070/blog_editor.html?draftId=1 HTTP/1.1
响应:
{"code":200,"message":"","data":{"id":1,"title":"这是一篇草稿","content":"#Hh宇的个人博客","createTime":null,"updateTime":null,"userId":1}
}
从草稿箱发布文章
因为发布文章和草稿共用的是一个页面,所以发布文章的时候,需要通过url中的querystr的id来判断是普通发布文章还是从草稿箱中发布文章。从草稿箱发布完文章后,就删除草稿数据报中的文章。

请求:
POST http://127.0.0.1:7070/articleDraft/postArticle HTTP/1.1
{title:"草稿标题"content:"草稿正文",draftId : 1
}
响应:
{"code":200,"message":"发布成功","data":null
}
@PostMapping("/postArticle")
public Response postArticle(String title,String content,Integer draftId,HttpServletRequest request) {if (title == null || content == null || "".equals(title.trim()) || "".equals(content.trim()) || draftId <= 0) {return Response.fail("发布失败");}User user = LogInUserInfo.getUserInfo(request);int ret = articleService.addArticle(title,content,user.getId());// 发布成功就删除草稿if (ret == 1) {int row = articleDraftService.idDeleteDrafts(draftId,user.getId());if (row == 1) {return Response.success("发布成功");}}return Response.fail("发布失败");
}
修改草稿
如果是修改草稿,也就是点击编辑草稿后再次点击保存草稿,此时就是修改草稿了,而不是保存草稿,同样是在前端通过querystr区分。
请求:
POST http://127.0.0.1:7070/articleDraft/updateDraft HTTP/1.1
{draftId : 2,title : "标题",content : "正文"
}
响应:
{"code":200,"message":"保存成功","data":null
}
/*** 修改草稿* @param title* @param content* @param draftId* @return*/
@PostMapping("/updateDraft")
public Response updateDraft(String title,String content,Integer draftId,HttpServletRequest request) {if (title == null || content == null || "".equals(title.trim()) || "".equals(content.trim())|| draftId == null ||draftId < 1) {return Response.fail("内容非法");}User user = LogInUserInfo.getUserInfo(request);int ret = articleDraftService.updateDraft(title,content,draftId,user.getId());if (ret == 1) {return Response.success("保存成功");}return Response.success("保存失败");
}
6.个人信息修改

头像修改
前端传递头像,后端校验一下文件格式,生成唯一的文件名,把头像路径更新到对应的用户数据库。SpingBoot对文件上传大小有默认限制,我们只需处理对应的异常即可。
@SneakyThrows
@PostMapping("/updatePhoto")
public Response updatePhoto(@RequestPart("photo")MultipartFile imgFile, HttpServletRequest request,HttpServletResponse response) {// 设置重定向response.setStatus(302);response.sendRedirect("/user_blog_list.html");if (imgFile != null && !imgFile.isEmpty()) {String fileName = imgFile.getOriginalFilename();if (fileName.contains(".jpg") || fileName.contains(".png")) {// 1.生成唯一文件名String newFileName = UUID.randomUUID().toString().replaceAll("-","")+fileName.substring(fileName.length()-4);// 路径,文件名File file = new File(photoPath,newFileName);//保存文件imgFile.transferTo(file);User user = LogInUserInfo.getUserInfo(request);int ret = userService.updatePhoto(user.getId(),Constant.PHOTO_UPLOAD_PATH+newFileName);if (ret == 1) {return Response.success("修改成功");}}}return Response.fail("修改失败");
}
基本信息修改
修改网名和gitee连接
请求:
POST http://127.0.0.1:7070/user/updateUserInfo HTTP/1.1
{netName:"网名","gitee": "gitee链接"
}
响应:
{"code":200,"message":"修改成功","data":null
}
@PostMapping("/updateUserInfo")
public Response updateUserInfo(String netName,String git,HttpServletRequest request) {if (netName == null || git == null || "".equals(netName.trim()) || "".equals(git.trim())|| (!git.contains("https://"))) {return Response.fail("参数非法或git链接非法");}User user = LogInUserInfo.getUserInfo(request);int ret = userService.updateUserInfo(netName,git,user.getId());if (ret == 1) {return Response.success("修改成功");}return Response.fail("修改失败");
}
7. 其它密码相关功能实现

修改密码
修改密码比较简单,用户登录后,输入原密码和新密码,后端通过解密方式验证。验证通过即可修改密码。
@PostMapping("/updatePassword")
public Response updatePassword(String password,String newPassword,String confirmPassword,HttpServletRequest request) {if (password ==null || newPassword == null || confirmPassword == null ||"".equals(password.trim()) || "".equals(newPassword.trim()) || "".equals(confirmPassword.trim())) {return Response.fail("修改失败");}User user = LogInUserInfo.getUserInfo(request);User myUser = userService.byIdUser(user.getId());if (PasswordUtil.check(password,myUser.getPassword())) {String finalPass = PasswordUtil.passwordAddSalt(newPassword);int ret = userService.updatePassword(finalPass,user.getId());if (ret == 1) {HttpSession session = request.getSession(false);session.removeAttribute(Constant.SESSION);return Response.success("修改成功");}}return Response.fail("修改失败,密码错误");
}
设置密保问题

在用户第一登录的时候提示用户设置密保问题,通过密保问题即可找回密码。
给定3个问题和指定的问题选项,让用户输入答案,和用户密码进行设置密码问题。再对答案进行md5加密存入数据库。
请求:
POST http://127.0.0.1:7070/questionPass/addQuestionPassword HTTP/1.1
{password:hhy,question1=你最相信的人的姓名是,question2=你的出生地是,question3=你最喜欢吃的水果是,answer1=某某,answer2=湖南,answer3=葡萄
}
响应:
{"code":200,"message":"密保问题设置成功","data":null
}
找回密码
通过用户设置的密保问题来找回密码,用户输入查找的用户名来获取对应的密保问题,再输入答案后,对答案进行md5同问题一起比对验证,端验证正确后,再给用户输入新密码,输入新密码后再次提交。修改密码前再次验证一下密保问题,如果验证通过即可修改密码。

相关文章:
SSM个人博客项目
文章目录 SSM个人博客系统实现项目介绍 一、准备工作0. 创建项目添加对应依赖1. 数据库设计2. 定时实体类 二、功能实现1.统一功能处理统一返回格式统一异常处理定义登录拦截器 2. 注册登录实现生成获取验证码密码加盐实现注册功能登录功能注销功能 3.登录用户博客列表获取登录…...
vue插槽是什么?如何使用?
1、意义 插槽是vue提供的一个内置组件,是一个占位符。作用是可以向组件中传递一段html代码,加强了组件封装性以及复用性。 2、分类 插槽通常分为匿名插槽、具名插槽、作用域插槽 匿名插槽: 顾名思义就是没有名字的插槽,我们通…...
yum常用操作命令
目录 查询命令 查看当前所有仓库 检查可升级的程序 安装、卸载、升级 清除缓存命令 生成缓存 查询命令 列出已安装的软件包:yum list installed列出仓库中还未安装的软件包:yum list available列出指定软件包的依赖关系:yum deplist &…...
.Net C# 免费PDF合成软件
最近用到pdf合成,发现各种软件均收费啊,这个技术非常简单,别人写好的库一大把,这里用到了PDFsharp,项目地址Home of PDFsharp and MigraDoc Foundation 软件下载地址 https://download.csdn.net/download/g313105910…...
JAVA集合框架 一:Collection(LIst,Set)和Iterator(迭代器)
目录 一、Java 集合框架体系 1.Collection接口:用于存储一个一个的数据,也称单列数据集合(single)。 2.Map接口:用于存储具有映射关系“key-value对”的集合(couple) 3.Iterator接口&#…...
python ffmpeg合并ts文件
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:点击跳转 当你从网站下载了一集动漫,然后发现是一堆ts文件,虽然可以打开,但是某个都是10秒左右,…...
c++map和set剖析
文章参考文献:cplusplus 博主:拖拉机厂第一代码手 gitee:拖拉机厂第一代码手 c专栏:C 目录 🧙🏼♂set剖析🧚🏼set简介🧚🏼set模板参数列表🧚🏼s…...
kubernetes configmap 的data中的文件内容格式错乱
截取一段错乱的配置: kubectl -n monitoring get cm blackbox-exporter-configuration -o yaml apiVersion: v1 data:config.yml: "\"modules\":\n \"http_2xx\":\n \"http\":\n \"preferred_ip_protocol\"…...
A TupleBackedMap cannot be modified Mybatis分页,使用List<Map>接参,无法修改map的解决方案
问题描述 当使用Mybatis 进行Page分页,再使用Page< map >作为接受参数。此时尝试修改map则会报错。 报错为 java.lang.UnsupportedOperationException: A TupleBackedMap cannot be modified解决方案 使用新的数组,使用反射,构建工具…...
Leetcode-每日一题【剑指 Offer 13. 机器人的运动范围】
题目 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例…...
WEB集群——负载均衡集群
目录 一、 LVS-DR 群集。 1、LVS-DR工作原理 2、LVS-DR模式的特点 3、部署LVS-DR集群 3.1 配置负载调度器(192.168.186.100) 3.2 第一台web节点服务器(192.168.186.103) 3.3 第二台web节点服务器(192.168.186.…...
ubuntu 20.0.4 搭建nvidia 显卡环境
一、安装docker 1、安装dokcer sudo apt install docker.io2、docker 添加到用户组 创建docker用户组 sudo groupadd docker添加当前用户加入docker用户组 sudo usermod -aG docker ${USER}重启docker服务 sudo systemctl restart docker切换或者退出当前账户再从新登入 …...
Windows环境下通过 系统定时 执行脚本方式 压缩并备份文件夹 到其他数据盘
环境配置 压缩时需要使用7-zip进行调用,因此根据自己电脑进行安装 官网:https://www.7-zip.org/ 脚本文件 新建记事本文件,重命名为git_back_up.bat echo off rem 设置utf-8可以正常显示中文 chcp 65001 > nulrem 获取当前日期和时间&…...
C++系列二:STL教程-常用算法
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 常用算法 前言算法列举:算法例子 前言 还有一些我在尝试中迷惑不解的,有点玄幻。 算法列举: 排序算法: sort(first, last);…...
【css】渐变
渐变是设置一种颜色或者多种颜色之间的过度变化。 两种渐变类型: 线性渐变(向下/向上/向左/向右/对角线) 径向渐变(由其中心定义) 1、线性渐变 语法:background-image: linear-gradient(direction, co…...
idea打开多个项目需要开多个窗口(恢复询问弹窗)
【版权所有,文章允许转载,但须以链接方式注明源地址,否则追究法律责任】【创作不易,点个赞就是对我最大的支持】 前言 仅作为学习笔记,供大家参考 总结的不错的话,记得点赞收藏关注哦! 使用…...
篇十三:策略模式:选择不同算法
篇十三:“策略模式:选择不同算法” 设计模式是软件开发中的重要知识,策略模式(Strategy Pattern)是一种行为型设计模式,用于在运行时根据不同的需求选择不同的算法或行为。本文将探讨策略模式的作用和实现…...
Centos7.6 安装mysql过程全记录
在centos 7.6上 离线安装mysql 的步骤,可参考下文: 一、查看当前MySQL的安装情况并卸载 1. 查看当前MySQL的安装情况 查找之前是否安装了MySQL rpm -qa|grep -i mysql 2.卸载mysql 如果已经安装mysql,则需要先停止MySQL,再删除…...
Java中的Guava是什么?
Java中的Guava是一个非常强大的Java库,它提供了很多实用的工具类和方法,可以帮助我们更高效地开发Java应用程序。从新手的角度来看,Guava可以让我们在Java编程中变得更加简单、快速和高效。 Guava的命名来源于“Google’s favorite Java lib…...
vue.js兄弟组件方法调用b组件调用a组件方法
vue.js 中兄弟组件方法调用 场景:父组件中同时引入两个子组件(A和B),此时B组件点击按钮需要调用A组件里面的方法 方案1:vue的事件总线 方案2:自定义事件($emit) 最终方案:…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
