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同问题一起比对验证,端验证正确后,再给用户输入新密码,输入新密码后再次提交。修改密码前再次验证一下密保问题,如果验证通过即可修改密码。
相关文章:
![](https://img-blog.csdnimg.cn/2c0c66c8159b49e28dc0c3fbb488c26e.png)
SSM个人博客项目
文章目录 SSM个人博客系统实现项目介绍 一、准备工作0. 创建项目添加对应依赖1. 数据库设计2. 定时实体类 二、功能实现1.统一功能处理统一返回格式统一异常处理定义登录拦截器 2. 注册登录实现生成获取验证码密码加盐实现注册功能登录功能注销功能 3.登录用户博客列表获取登录…...
![](https://www.ngui.cc/images/no-images.jpg)
vue插槽是什么?如何使用?
1、意义 插槽是vue提供的一个内置组件,是一个占位符。作用是可以向组件中传递一段html代码,加强了组件封装性以及复用性。 2、分类 插槽通常分为匿名插槽、具名插槽、作用域插槽 匿名插槽: 顾名思义就是没有名字的插槽,我们通…...
![](https://www.ngui.cc/images/no-images.jpg)
yum常用操作命令
目录 查询命令 查看当前所有仓库 检查可升级的程序 安装、卸载、升级 清除缓存命令 生成缓存 查询命令 列出已安装的软件包:yum list installed列出仓库中还未安装的软件包:yum list available列出指定软件包的依赖关系:yum deplist &…...
![](https://img-blog.csdnimg.cn/856e907206894995a51be72343ec1ffd.png)
.Net C# 免费PDF合成软件
最近用到pdf合成,发现各种软件均收费啊,这个技术非常简单,别人写好的库一大把,这里用到了PDFsharp,项目地址Home of PDFsharp and MigraDoc Foundation 软件下载地址 https://download.csdn.net/download/g313105910…...
![](https://img-blog.csdnimg.cn/30e51a0a47184da593b2c3db11009286.png)
JAVA集合框架 一:Collection(LIst,Set)和Iterator(迭代器)
目录 一、Java 集合框架体系 1.Collection接口:用于存储一个一个的数据,也称单列数据集合(single)。 2.Map接口:用于存储具有映射关系“key-value对”的集合(couple) 3.Iterator接口&#…...
![](https://img-blog.csdnimg.cn/251fed9f416647918661abaced8765b8.png)
python ffmpeg合并ts文件
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:点击跳转 当你从网站下载了一集动漫,然后发现是一堆ts文件,虽然可以打开,但是某个都是10秒左右,…...
![](https://img-blog.csdnimg.cn/681f66d3e62041589109ea70c5f43c89.png#pic_center)
c++map和set剖析
文章参考文献:cplusplus 博主:拖拉机厂第一代码手 gitee:拖拉机厂第一代码手 c专栏:C 目录 🧙🏼♂set剖析🧚🏼set简介🧚🏼set模板参数列表🧚🏼s…...
![](https://www.ngui.cc/images/no-images.jpg)
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\"…...
![](https://www.ngui.cc/images/no-images.jpg)
A TupleBackedMap cannot be modified Mybatis分页,使用List<Map>接参,无法修改map的解决方案
问题描述 当使用Mybatis 进行Page分页,再使用Page< map >作为接受参数。此时尝试修改map则会报错。 报错为 java.lang.UnsupportedOperationException: A TupleBackedMap cannot be modified解决方案 使用新的数组,使用反射,构建工具…...
![](https://img-blog.csdnimg.cn/9b41a4c68ad842b682a0bc603a4dc42b.png)
Leetcode-每日一题【剑指 Offer 13. 机器人的运动范围】
题目 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例…...
![](https://img-blog.csdnimg.cn/c57ddc3bf8a04534ac21f121b5bf2d5f.png)
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.…...
![](https://img-blog.csdnimg.cn/a1946f8723a04745aabdacc014354690.png)
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切换或者退出当前账户再从新登入 …...
![](https://img-blog.csdnimg.cn/dba48936da934d9d8f9fe1478fbc35b5.png)
Windows环境下通过 系统定时 执行脚本方式 压缩并备份文件夹 到其他数据盘
环境配置 压缩时需要使用7-zip进行调用,因此根据自己电脑进行安装 官网:https://www.7-zip.org/ 脚本文件 新建记事本文件,重命名为git_back_up.bat echo off rem 设置utf-8可以正常显示中文 chcp 65001 > nulrem 获取当前日期和时间&…...
![](https://www.ngui.cc/images/no-images.jpg)
C++系列二:STL教程-常用算法
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 常用算法 前言算法列举:算法例子 前言 还有一些我在尝试中迷惑不解的,有点玄幻。 算法列举: 排序算法: sort(first, last);…...
![](https://img-blog.csdnimg.cn/183c1a182be442e2a46406b921758642.png)
【css】渐变
渐变是设置一种颜色或者多种颜色之间的过度变化。 两种渐变类型: 线性渐变(向下/向上/向左/向右/对角线) 径向渐变(由其中心定义) 1、线性渐变 语法:background-image: linear-gradient(direction, co…...
![](https://img-blog.csdnimg.cn/4d4f04c5cf8c4b58b81a2bc0b5d5c3b7.png)
idea打开多个项目需要开多个窗口(恢复询问弹窗)
【版权所有,文章允许转载,但须以链接方式注明源地址,否则追究法律责任】【创作不易,点个赞就是对我最大的支持】 前言 仅作为学习笔记,供大家参考 总结的不错的话,记得点赞收藏关注哦! 使用…...
![](https://www.ngui.cc/images/no-images.jpg)
篇十三:策略模式:选择不同算法
篇十三:“策略模式:选择不同算法” 设计模式是软件开发中的重要知识,策略模式(Strategy Pattern)是一种行为型设计模式,用于在运行时根据不同的需求选择不同的算法或行为。本文将探讨策略模式的作用和实现…...
![](https://img-blog.csdnimg.cn/22fe512ffcb340fc8a8cd7be1a0f0c89.png)
Centos7.6 安装mysql过程全记录
在centos 7.6上 离线安装mysql 的步骤,可参考下文: 一、查看当前MySQL的安装情况并卸载 1. 查看当前MySQL的安装情况 查找之前是否安装了MySQL rpm -qa|grep -i mysql 2.卸载mysql 如果已经安装mysql,则需要先停止MySQL,再删除…...
![](https://www.ngui.cc/images/no-images.jpg)
Java中的Guava是什么?
Java中的Guava是一个非常强大的Java库,它提供了很多实用的工具类和方法,可以帮助我们更高效地开发Java应用程序。从新手的角度来看,Guava可以让我们在Java编程中变得更加简单、快速和高效。 Guava的命名来源于“Google’s favorite Java lib…...
![](https://www.ngui.cc/images/no-images.jpg)
vue.js兄弟组件方法调用b组件调用a组件方法
vue.js 中兄弟组件方法调用 场景:父组件中同时引入两个子组件(A和B),此时B组件点击按钮需要调用A组件里面的方法 方案1:vue的事件总线 方案2:自定义事件($emit) 最终方案:…...
![](https://www.ngui.cc/images/no-images.jpg)
【Kubernetes】二进制搭建
目录 二进制搭建 Kubernetes v1.20 操作系统初始化配置 关闭防火墙 关闭selinux 关闭swap 根据规划设置主机名 在master添加hosts 调整内核参数 时间同步 部署 etcd 集群 准备签发证书环境 准备cfssl证书生成工具 生成Etcd证书 上传 etcd-cert.sh 和 etcd.sh 到 …...
![](https://img-blog.csdnimg.cn/d07d1578c5394d4b8298faf2c87fe922.png)
【MFC】08.MFC消息,自定义消息,常用控件(MFC菜单创建大总结),工具栏,状态栏-笔记
本专栏上几篇文章讲解了MFC几大机制,今天带领大家学习MFC自定义消息以及常用控件,最常用的控件请查看本专栏第一二篇文章,今天这篇文章介绍工具栏,菜单和状态栏,以及菜单创建大总结。 文章目录 MFC消息分类࿱…...
![](https://img-blog.csdnimg.cn/7c75bc3c1c5b438e9c39e310bf3bfe63.png)
Clickhouse 数据存储
一、数据分区 数据是以分区目录的形式组织的,每个分区独立分开存储.这种形式,查询数据时,可以有效的跳过无用的数据文件。 1.1 数据分区的规则 分区键的取值,生成分区ID,分区根据ID决定。根据分区键的数据类型不同&am…...
![](https://img-blog.csdnimg.cn/35ccfe352f6f4bd8aca33bef12f9e40d.png)
c语言每日一练(3)
前言:每日一练系列,每一期都包含5道选择题,2道编程题,博主会尽可能详细地进行讲解,令初学者也能听的清晰。每日一练系列会持续更新,暑假时三天之内必有一更,到了开学之后,将看学业情…...
![](https://www.ngui.cc/images/no-images.jpg)
java基础-Stream(流)、File(文件)和IO
Java中的流(Stream)提供了一个统一的接口来处理输入和输出数据,文件(File)提供了一种简单的方式来操作磁盘上的文件,而I/O则允许我们在Java程序中读写数据。 一、流Stream java中得stream是一种抽象概念,流可以从多种来源读取数据ÿ…...
![](https://img-blog.csdnimg.cn/73152f7b02cb4b8cb3a06c109adbb502.png)
el-table实现指定列合并
table传入span-method方法可以实现合并行或列,方法的参数是一个对象,里面包含当前行row、当前列column、当前行号rowIndex、当前列号columnIndex四个属性。该函数可以返回一个包含两个元素的数组,第一个元素代表rowspan,第二个元素…...
![](https://img-blog.csdnimg.cn/4442a5c932904f238fb9de2467462764.png)
38.利用matlab解 有约束无约束的参数估计对比(matlab程序)
1.简述 1.离散型随机变量的极大似然估计法: (1) 似然函数 若X为离散型, 似然函数为 (2) 求似然函数L(θ)的最大值点 θ, 则θ就是未知参数的极大似然估计值. 2.连续型随机变量的极大似然估计法: (1) 似然函数 若 X 为连续型, 似然函数为 (2) 求似然函数L(θ)的最大值点θ, 则…...
![](https://img-blog.csdnimg.cn/img_convert/02804f5495e489f5bdcfaf7444ab2d2f.png)
什么是React?React与VU的优缺点有哪些?
什么是React?什么是VUE? 维基百科上的概念解释,Vue.js是一个用于创建用户界面的开源MVVM前端JavaScript框架,也是一个创建单页应用的Web应用框架。Vue.js由尤雨溪(Evan You)创建,由他和其他活跃…...
![](https://img-blog.csdnimg.cn/af4fb42df61f484b87ebaa91482edcf6.png)
区块链技术助力慈善,为您的善举赋予全新力量!
我们怀揣着一颗温暖的心,秉承着公开透明的理念,带着信任与责任,倾力打造了一套区块链技术驱动的去中心化捐赠与物资分发系统,通过智能生态网络(IEN)解决捐赠不透明问题的系统,让您的善举直接温暖…...
![](https://img-blog.csdnimg.cn/2a3c5e7cf5b046b683f1adf03894b108.png)
模拟实现消息队列项目(系列4) -- 服务器模块(内存管理)
目录 前言 1. 创建MemoryDataCenter 2. 封装Exchange 和 Queue方法 3. 封装Binding操作 4. 封装Message操作 4.1 封装消息中心集合messageMap 4.2 封装消息与队列的关系集合queueMessageMap的操作 5. 封装未确认消息集合waitMessage的操作 6. 从硬盘中恢复数据到内存中 7. Memo…...
![](/images/no-images.jpg)
wordpress 图片服务器配置/百度网站推广价格查询
登录远程SQL服务器 一 看ping 服务器IP能否ping通。 这个实际上是看和远程sql server 2000服务器的物理连接是否存在。 如果不行,请检查网络,查看配置,当然得确保远程sql server 2000服务器的IP拼写正确。 二 在Dos或命令行下输入telnet …...
![](/images/no-images.jpg)
网页设计尺寸大小指的是什么/网站关键词优化排名软件
本节书摘来自异步社区《配置管理最佳实践》一书中的第1章,第1.1节,作者: 【美】Bob Aiello , Leslie Sachs著,更多章节内容可以访问云栖社区“异步社区”公众号查看 第I部分 配置管理核心实践 第1章 源代码管理 源代码管理是保护…...
![](http://www.hanjunxing.com/blog/wp-content/uploads/2009/08/15.jpg)
农用地转建设用地结果查询网站/百度云搜索引擎入口
2019独角兽企业重金招聘Python工程师标准>>> 随着宽带的普及和网速的提高,人们上网冲浪时对网站打开速度的容忍度在不断降低,网站的打开速度已经成为可用性的前提,甚至直接影响网站的收入。 Google最近第一次完整地书面提出网页访…...
![](https://img-blog.csdnimg.cn/img_convert/34dae55e334f6899598ccac5e54faf6a.png)
杭州竞彩网站开发/产品全网营销推广
微信小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,微信用户只需要扫一扫或搜一下即可打开应用。那么如何开发微信小程序?接下来让我们看看搭建微信小程序的零基础玩法。小程序是依托微信诞生的,由于微信属于强…...
![](/images/no-images.jpg)
皮肤测试网站怎么做/网络工程师
我有连接2级不同的服务器2个PHP页面,传球MySQL账户到远程服务器在第一台服务器有数据库(MySQL的)与“电子邮件”表,由PHP页面生成。在第二个服务器。 有一个PHP文件至极需要阅读“电子邮件”表...第二PHP页面不需要用户的事实被打开的是,我需…...
![](/images/no-images.jpg)
淘宝做网站推广怎么样/头条号权重查询
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API Reflect设计目的: 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。修改某些Object方法的返回结果&#…...