项目实战 — 博客系统③ {功能实现}
目录
一、编写注册功能
🍅 1、使用ajax构造请求(前端)
🍅 2、统一处理
🎄 统一对象处理
🎄 保底统一返回处理
🎄 统一异常处理
🍅 3、处理请求
二、编写登录功能
🍅 1、使用ajax构造请求
🍅 2、处理请求
🎄 创建实体类
🎄 添加接口方法
🎄 service层
🎄 controller:处理请求并返回
三、编写博客列表页面
🍅 1、SessionUtils
🍅 2、处理请求
🍅 3、使用ajax构造请求
三、编写注销功能
🍅 1、使用ajax获取请求
🍅 2、处理请求
四、编写删除文章功能
🍅 1、使用ajax构造请求
🍅 2、处理请求
🎄 mapper层:接口方法
🎄 service层
🎄 controller层:处理请求
五、编写文章添加功能
🍅 1、使用ajax构造请求
🍅 2、处理请求
🎄mapper层:添加接口方法
🎄service层:返回受影响行数
🎄 controller:处理添加文章的请求
六、修改功能:查询到修改的文章
🍅 1、通过ajax构造请求
🍅 2、处理请求
🎄Mapper层:添加接口
🎄service层
🎄controller层
七、修改功能:修改查询到的文章
🍅 1、通过ajax构造请求
🍅 2、处理请求
🎄 mapper层
🎄 service层
🎄 controller层
八、文章详情
🍅 1、前端页面编写
🎄 给定id值
🎄 使用ajax构造请求
🍅 2、处理请求
🎄 mapper层
🎄 service层
🎄 controller层
九、阅读量设置
🍅 处理请求
🎄 mapper层
🎄 service层
🎄 controller层
十、分页处理
🍅 1、前端处理
🍅 2、后端处理
🎄 mapper层
🎄 service层
🎄 controller层
十一、密码加盐
🍅 1、定义加密规则
🍅 2、修改UserController代码
前置知识:
前后端交互的关键:使用AJAX(异步局部提交)
写法:
1、原生写法(兼容性差)
2、使用框架,jQuery ajax(简单 / 通用性浩)
语法:
jQuery.ajax({url:"接口地址", //提高的接口地址type:"GET", //请求的类型data:{ //传递的数据"username":"张三","password":"123"},success:function(res){ //相应的结果是什么//后端返回数据后的业务处理代码}});
一、编写注册功能
🍅 1、使用ajax构造请求(前端)
在前端页面的<head></head>添加jquery
<script src="js/jquery.min.js"></script>
添加事件:点击提交按钮,触发mysub()
<div class="row"><button id="submit" onclick="mysub()">提交</button>
</div>
在<script></script>中提交用户注册信息
主要有这几步:
(1)参数效验(获取到数据|非空效验)
(2)将数据提交给后端
(3)将后端返回的结果给用户
username.focus():指将光标返回
trim():去空格操作(校验到全是空格)
<script>// 提交用户注册信息function mysub(){// 参数校验var username = jQuery("#username");var password = jQuery("#password");var password2 = jQuery("#password2");if(username.val().trim()==""){alert("请先输入用户名!");username.focus();return false;}if(password.val().trim()==""){alert("请先输入密码!");password.focus();return false;}if(password2.val().trim()==""){alert("请先输入确认密码!");password2.focus();return false;}// 效验两次输入的密码是否一致if(password.val()!=password2.val()){alert("两次密码不一致,请先检查!");return false;}// 提交数据给后端jQuery.ajax({url:"/user/reg",// 查询使用get,非查询使用posttype:"POST",data:{"username":username.val().trim(),"password":password.val().trim()},success:function (res){// 返回结果给用户if (res.code==200 && res.data==1){alert("注册成功!")location.href = "login.html";}else {alert("注册失败!" + res.msg);}}});}</script>
</body>
🍅 2、统一处理
🎄 统一对象处理
在common包中,编写ResultAjax,表示前后端交互的统一对象,统一处理

@Data
public class ResultAjax {private int code; //状态码private String msg; //状态码的描述信息private Object data;public static ResultAjax success(Object data){ResultAjax result = new ResultAjax();result.setCode(200);result.setMsg("");result.setData(data);return result;}public static ResultAjax success(int code,String msg,Object data){ResultAjax result = new ResultAjax();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}public static ResultAjax fail(int code,String msg){ResultAjax result = new ResultAjax();result.setCode(code);result.setMsg(msg);result.setData(null);return result;}public static ResultAjax fail(int code,String msg,Object data){ResultAjax result = new ResultAjax();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}
}
🎄 保底统一返回处理
在common中创建类

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof ResultAjax){return body;}if (body instanceof String){ResultAjax resultAjax = ResultAjax.success(body);try {return objectMapper.writeValueAsString(resultAjax);} catch (JsonProcessingException e) {e.printStackTrace();}}return ResultAjax.success(body);}
}
🎄 统一异常处理
/*
* 统一异常处理
* */
@RestControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(Exception.class)public ResultAjax doException(Exception e){return ResultAjax.fail(-1,e.getMessage());}
}
🍅 3、处理请求
编写Mapper层,将拿到的数据进行插入
@Mapper
public interface UserMapper {@Insert("insert into userinfo(username,password) values (#{username},#{password})")int reg(Userinfo userinfo);
}
然后在Service层中使用@Autowired将userMapper注入
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public int reg(Userinfo userinfo){return userMapper.reg(userinfo);}
}
在UserController中使用Userinfo对象接收请求信息
(1)校验参数
(2)请求 service 进行添加操作
(3)将执行的结果返回给前端
@RestController
@RequestMapping("/user")
public class UserController {@AutowiredUserService userService;@RequestMapping("/reg")public ResultAjax reg(Userinfo userinfo){if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername())|| !StringUtils.hasLength(userinfo.getPassword())){return ResultAjax.fail(-1,"非法参数");}int result = userService.reg(userinfo);return ResultAjax.success(result);}
检验该功能是否实现

查看数据库是否插入张三的信息:

二、编写登录功能
🍅 1、使用ajax构造请求
在前端页面的<head></head>添加jquery
<script src="js/jquery.min.js"></script>
添加事件:点击提交按钮,触发mysub()
<div class="row"><button id="submit" onclick="doLogin()">提交</button>
</div>
(1)校验参数
(2)将数据提交给后端
(3)将结果展示给前端
<script>function doLogin(){// 参数校验var username = jQuery("#username");var password = jQuery("#password");if(username.val().trim()==""){alert("请先输入用户名!");username.focus();return false;}if(password.val().trim()==""){alert("请先输入密码!");password.focus();return false;}// 将数据提交给后端jQuery.ajax({url:"/user/login",type:"GET",data:{"username":username.val(),"password":password.val()},success:function(res){// 将结果展示给用户if (res.code==200 && res.data==1){alert("登录成功!");location.href="myblog_list.html";}else {alert("登录失败!" + res.msg);}}});}
</script>
🍅 2、处理请求
🎄 创建实体类
首先,再创建一个扩展Userinfo的实体类,UserinfoVO

/*
* userinfo扩展类
* */
@Data
public class UserinfoVO extends Userinfo {private String checkCode;
}
🎄 添加接口方法
在UserMapper中添加以下方法:
@Select("select * from userinfo where username = #{username} order by id desc")Userinfo getUserByName(@Param("username")String username);
🎄 service层
public Userinfo getUserByName(String username){return userMapper.getUserByName(username);}
🎄 controller:处理请求并返回
首先创建一个公共类(common包中):AppVariable
设置一个用户的session key
/*
* 全局变量
* */
public class AppVariable {public static final String SESSION_USERINFO_KEY = "SESSION_USERINFO";
}
(1)参数校验
(2)根据用户名查询对象
(3)适用对象中的密码和用户输入的密码进行比较
登录验证思路:根据用户名查询对象,如果查询到对象,则用查询到的对象和传递过 来的密码对比。如果相同,则说明登陆成功,反之则不能。
(4)比较成功之后,将对象存储到session中
(5)将结果返回给用户
@RequestMapping("/login")public ResultAjax login(UserinfoVO userinfoVO, HttpServletRequest request){//参数校验if (userinfoVO == null || !StringUtils.hasLength(userinfoVO.getUsername()) ||!StringUtils.hasLength(userinfoVO.getPassword())){
// 是非法登录return ResultAjax.fail(-1,"参数有误!");}//根据用户查询对象Userinfo userinfo = userService.getUserByName(userinfoVO.getUsername());if (userinfo == null || userinfo.getId() == 0){return ResultAjax.fail(-2,"用户名或密码错误!");}//使用对象中的密码和用户输入的密码进行比较if (!userinfoVO.getPassword().equals(userinfo.getPassword())){
// 密码错误return ResultAjax.fail(-2,"用户名或密码错误!");}//比较成功,将对象存储到session中HttpSession session = request.getSession();session.setAttribute(AppVariable.SESSION_USERINFO_KEY,userinfo);//将结果返回给用户return ResultAjax.success(1);}
三、编写博客列表页面
🍅 1、SessionUtils
由于要拿到用户的信息,也就是拿到用户对象,比如修改和删除博客等功能(都是需要拿到用户),所以得到用户对象就属于一个高频事件。
所以就创建一个类,可以得到用户对象。

/*
* session工具类
* */
public class SessionUtils {
// 得到登录用户public static Userinfo getUser(HttpServletRequest request){
// false代表有session就创建session,没有就创建HttpSession session = request.getSession(false);if (session != null && session.getAttribute(AppVariable.SESSION_USERINFO_KEY) != null){
// 登录状态return (Userinfo) session.getAttribute(AppVariable.SESSION_USERINFO_KEY);}return null;}
}
🍅 2、处理请求
🎄 添加接口方法(mapper)
@Mapper
public interface ArticleMapper {@Select("select * from articleinfo where uid=#{uid}")List<ArticleMapper> getListByUid(@Param("uid")int uid);
}
🎄 service层
对象注入
@Service
public class ArticleService {@Autowiredprivate ArticleMapper articleMapper;public List<ArticleMapper> getListByUid(int uid){return articleMapper.getListByUid(uid);}
}
🎄controller处理请求并返回响应
使用并行的方式处理文章正文(_DESC_LENGTH是简介)
@RestController
@RequestMapping("art")
public class ArticleController {@Autowiredpublic ArticleService articleService;private static final int _DESC_LENGTH = 120;@RequestMapping("/mylist")public ResultAjax myList(HttpServletRequest request){
// 得到登录用户Userinfo userinfo = SessionUtils.getUser(request);if (userinfo == null){return ResultAjax.fail(-1,"请先登录");}
// 根据用户id查询用户发表的文章List<Articleinfo> list = articleService.getListByUid(userinfo.getId());
// 处理list -> 将文章正文变成简介if (list != null && list.size() > 0){
// 并行处理list集合list.stream().parallel().forEach((art)->{if (art.getContent().length() > _DESC_LENGTH){art.setContent(art.getContent().substring(0,_DESC_LENGTH));}});}
// 返回前端return ResultAjax.success(list);}
}
🍅 3、使用ajax构造请求
在前端页面的<head></head>添加jquery
<script src="js/jquery.min.js"></script>
添加一个id,包含了文章列表的div
<div id="artListDiv" class="container-right">
<script>// 初始化方法function init(){jQuery.ajax({url:"/art/mylist",type:"GET",data:{},success:function(res){if(res.code==200){// 请求成功var createHtml = "";var artList = res.data;if(artList==null || artList.length==0){// 未发表文章createHtml += "<h3 style='margin-left:20px;margin-top:20px'>暂无文章,请先"+"<a href='blog_add.html'>添加</a>!</h3>";}else{for(var i=0;i<artList.length;i++){var art = artList[i];createHtml += '<div class="blog">';createHtml += '<div class="title">'+art.title+'</div>';createHtml += '<div class="date">'+art.createtime+'</div>';createHtml += '<div class="desc">';createHtml += art.content;createHtml += '</div>';createHtml += ' <a href="blog_content.html?aid='+art.id + '" class="detail">查看全文 >></a> ';createHtml += '<a href="blog_edit.html?aid='+art.id + '" class="detail">修改 >></a> ';createHtml += ' <a href="javascript:del('+art.id+')" class="detail">删除 >></a>';createHtml += '</div>';}}jQuery("#artListDiv").html(createHtml);}else{alert("抱歉:操作失败!"+res.msg);}}});}init();</script>
</body>
三、编写注销功能
🍅 1、使用ajax获取请求
由于注销功能,在很多页面都需要用到,所以这里就给一个公共的js

function logout(){if (confirm("是否确定注销?")){// 1、在后端删除session信息jQuery.ajax({url:"/user/logout",type:"POST",data:{},success:function (res){}});// 2、跳转到登录页location.href = "login.html"}
}
进入到myblog_list.html中:
<script src="js/logout.js"></script>
<a href="javascript:logout()">注销</a>
🍅 2、处理请求
//注销@RequestMapping("/logout")public ResultAjax logout(HttpServletRequest request){HttpSession session = request.getSession(false);if (session!=null &&session.getAttribute(AppVariable.SESSION_USERINFO_KEY)!=null){session.removeAttribute(AppVariable.SESSION_USERINFO_KEY);}return ResultAjax.success(1);}
四、编写删除文章功能
🍅 1、使用ajax构造请求
在myblog_list.html中添加对应的js代码
主要是根据id删除文章:
1、校验参数
2、将数据返回给后端进行删除操作
2、将结果展示给用户
// 删除文章操作(根据id删除)function del(aid){// 参数校验if(aid=="" || aid<=0){alert("参数错误");return false;}// 将数据返回给后端进行删除操作jQuery.ajax({url:"/art/del",type:"POST",data:{"aid":aid},success:function (res){// 将结果展示给用户if (res.code==200 && res.data==1){alert("恭喜:删除成功!");// 刷新页面location.href = location.href;}else {// 删除失败alert("抱歉:操作失败!"+res.msg);}}});}
🍅 2、处理请求
🎄 mapper层:接口方法
在ArticleMapper中添加接口方法
@Delete("delete from articleinfo where id=#{aid} and uid=#{uid}")
int del(@Param("aid")Integer aid,int uid);
🎄 service层
public int del(Integer aid,int uid){return articleMapper.del(aid,uid);}
🎄 controller层:处理请求
主要有以下几步:
1、参数校验
2、得到当前登录的用户
3、判断文章的归属人并且进行删除操作
4、将结果返回给前端
// 删除文章@RequestMapping("/del")public ResultAjax del(Integer aid,HttpServletRequest request){if (aid==null || aid<=0){return ResultAjax.fail(-1,"参数错误!");}Userinfo userinfo = SessionUtils.getUser(request);if (userinfo == null){return ResultAjax.fail(-1,"请先登录!");}int result = articleService.del(aid,userinfo.getId());return ResultAjax.success(result);}
五、编写文章添加功能
🍅 1、使用ajax构造请求
(1)非空校验
(2)将用户提交的数据传递给后端
(3)将后端返回的结果展示给用户
进入到blog_add.html中,添加如下代码
function mysub(){var title = jQuery("#title");if (title.val.trim()==""){alert("请先输入标题!");title.focus();return false;}if (editor.getValue()==""){alert("请先输入正文!");return false;}jQuery.ajax({url:"/art/add",type:"POST",data:{"title":title.val(),"content":editor.getValue()},success:function (res){// 文章添加成功if (res.code==200 && res.data==1){if (confirm("恭喜:添加成功!是否继续添加文章?")){location.href=location.href;}else{location.href="myblog_list.html"}}else {//文章添加失败alert("抱歉:操作失败!"+res.msg);}}});}
🍅 2、处理请求
🎄mapper层:添加接口方法
@Insert("insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})")int add(Articleinfo articleinfo);
🎄service层:返回受影响行数
public int add(Articleinfo articleinfo){return articleMapper.add(articleinfo);}
🎄 controller:处理添加文章的请求
主要分为以下几步:
(1)校验参数
(2)组装数据
(3)将数据入库
(4)将结果返回给前端
@RequestMapping("/add")public ResultAjax add(Articleinfo articleinfo,HttpServletRequest request){if (articleinfo==null || !StringUtils.hasLength(articleinfo.getTitle())|| !StringUtils.hasLength(articleinfo.getContent())){return ResultAjax.fail(-1,"非法参数!");}Userinfo userinfo = SessionUtils.getUser(request);if (userinfo == null){return ResultAjax.fail(-2,"请先登录");}articleinfo.setUid(userinfo.getId());int result = articleService.add(articleinfo);return ResultAjax.success(result);}
六、修改功能:查询到修改的文章
🍅 1、通过ajax构造请求
(1)获取url中的参数:
(例如)localhost:8080/blog_edit.html?aid=2&uid=1
a、通过location.search获取“?”后面的参数
b、去除“?”
c、根据“&”将参数分割成多个数组
d、循环对比key,并返回查询value
创建一个公共的js:
// 根据key获取到对应的url中对应的value function getParamValue(key){var params = location.search;if (params.indexOf("?")>=0){params=params.substring(1);var paramArray=params.split("&");if (paramArray.length>=1){for (var i = 0; i < paramArray.length; i++) {var item=paramArray[i].split("=");if (item[0]==key[1]){return item;}}}}return null; }(2)校验 aid
(3)查询文章详情
(4)将文章的详情信息展示到页面
首先在head中添加得到参数的方法
<script src="js/urlutils.js"></script>
在blog_edit.html中的<script></script>中添加代码
var aid=getParamValue("aid");function init(){if (aid==null || aid<=0){alert("非法参数")return false;}jQuert.ajax({url:"/art/getdetail",type:"GET",data:{"aid":aid},success:function(res){if (res.code==200 && res.data!=null && res.data.id>0){jQuery("#title").val(res.data.title);initEdit(res.data.content);}else {alert("抱歉,查询失败!"+res.msg);}}});}init();
🍅 2、处理请求
🎄Mapper层:添加接口
进入到ArticleMapper中
@Select("select * from aeticleinfo where id=#{aid} and uid=#{uid}")Articleinfo getArticleByIdAndUid(@Param("aid")int aid,@Param("uid")int uid);
🎄service层
public Articleinfo getArticleByIdAndUid(int aid,int uid){return ArticleMapper.getArticleByIdAndUid(aid,uid);}
🎄controller层
(1)校验参数
(2)得到当前登录用户id
(3)查询文章并且校验权限
@RequestMapping("/update_init")public ResultAjax updateInit(Integer aid,HttpServletRequest request){if (aid==null || aid<=0 ){return ResultAjax.fail(-1,"参数有误!");}Userinfo userinfo = SessionUtils.getUser(request);if (userinfo == null){return ResultAjax.fail(-2,"请先登录!");}Articleinfo articleinfo = articleService.getArticleByIdAndUid(aid,userinfo.getId())return ResultAjax.success(articleinfo);}
七、修改功能:修改查询到的文章
🍅 1、通过ajax构造请求
进入到blog_edit.html中
<button onclick="doUpdate()">修改文章</button>
function doUpdate(){if(title.val().trim()==""){alert("请先输入标题!");title.focus();return false;}if(editor.getValue()==""){alert("请先输入正文!");return false;}jQuery.ajax({url:"/art/update",type:"POST",data:{"id":aid,"title":title.val(),"content":editor.getValue()},success:function(res){if(res.code==200 && res.data==1){// 修改成功alert("恭喜:修改成功!");// 跳转到我的文章管理员location.href = "myblog_list.html";}else if(res.code==-2){alert("请先的登录!");location.href = "login.html";}else{alert("抱歉:修改失败!"+res.msg);}}});}
🍅 2、处理请求
🎄 mapper层
进入到ArticleMappe中
@Update("update articleinfo set title=#{title},content=#{content} where id=#{id} and uid=#{uid}")int update(Articleinfo articleinfo);
🎄 service层
进入到ArticleService中
public int update(Articleinfo articleinfo){return articleMapper.update(articleinfo);}
🎄 controller层
(1)参数校验
(2)获取登录用户
(3)修改文章,并且校验归属人
(4)返回结果
// 修改文章信息@RequestMapping("/update")public ResultAjax update(Articleinfo articleinfo,HttpServletRequest request){if (articleinfo==null || !StringUtils.hasLength(articleinfo.getTitle())|| !StringUtils.hasLength(articleinfo.getContent())|| articleinfo.getId() == 0){return ResultAjax.fail(-1,"非法参数!");}Userinfo userinfo = SessionUtils.getUser(request);if (userinfo == null){return ResultAjax.fail(-2,"请先登录!");}articleinfo.setUid(userinfo.getId());int result = articleService.update(articleinfo);return ResultAjax.success(result);}
八、文章详情
🍅 1、前端页面编写
进入到blog_content.html中
🎄 给定id值
//左侧的个人信息<img id="photo" src="img/avatar.png" class="avtar" alt=""><h3 id="username"></h3><a href="http:www.github.com">github 地址</a>
//右侧的博客信息<!-- 博客标题 --><h3 id="title"></h3><!-- 博客时间 --><div class="date">发布时间:<span id="createtime"></span>/阅读量:<span id="rcount"></span></div>
🎄 使用ajax构造请求
var aid = getParamValue("aid");
//初始化页面function init(){var aid = getParamValue("aid");if (aid==null || aid<=0){alert("参数有误!");return false;}jQuery.ajax({url:"/art/detail",type:"GET",data:{"aid":aid},success: function (res){if (res.code==200 && res.data!=null){var user = res.data.user;var art = res.data.art;if (user!=null){if(user.photo!=""){jQuery("#photo").art("src,user.photo");}jQuery("#username").html(user.username);jQuery("#artcount").html(user.artCount);}else{alert("抱歉:查询失败!"+reg.msg);}if(art!=null){jQuery("#title").html(art.title);jQuery("#createtime").html(art.createtime);jQuery("#rcount").html(art.rcount);initEdit(art.content);}else{alert("抱歉:查询失败!"+reg.msg)}}else{alert("抱歉,查询失败!"+res.msg);}}});}
🍅 2、处理请求
首先在userinfoVO中添加一个变量:
private int artCount; //用户发布的文章总数
🎄 mapper层
首先在Articleinfo中添加一条sql:根据id查询文章
@Select("select * from articleinfo where id=#{aid}")Articleinfo getDetaiById(@Param("aid") int aid);@Select("select count(*) from articleinfo where uid=#{uid}")int getArticleByUid(@Param("uid")int uid);
然后再Userinfo中添加一条sql:根据id查询用户
@Select("select * from us erinfo where id=#{uid}")UserinfoVO getUserById(@Param("uid") int uid);
🎄 service层
ArticleService中
public Articleinfo getDetail(int aid){return articleMapper.getDetaiById(aid);}public int getArtCountByUid(int uid){return articleMapper.getArticleByUid(uid);}
UserService中
public UserinfoVO getUserById(int uid){return userMapper.getUserById(uid);}
🎄 controller层
(1)参数校验
(2)查询文章详情
(3)根据uid查询用户的详情
(4)根据uid查询用户发表的总文章数
(5)组装数据
(6)返回结果给前端
首先创建一个线程池

使用ThreadPoolExecutor来构造线程池,该方法其实和JDK中的ThreadPool区别就在于,ThreadPoolExecutor式通过参数的方式去设置,而ThreadPool是通过构造方法,ThreadPoolExecutor中的方式更加直观简单。
ThreadPool然后将其注入依赖
@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;
处理请求:
先注入依赖:
@Autowiredprivate UserService userService;
使用多线程并发编程就可以很快的查询到对应的文章详情了。
//查询文章详情页@RequestMapping("/detail")public ResultAjax detail(Integer aid) throws ExecutionException, InterruptedException {if(aid==null || aid<=0){return ResultAjax.fail(-1,"非法参数");}Articleinfo articleinfo = articleService.getDetail(aid);if (articleinfo == null || articleinfo.getId() <= 0){return ResultAjax.fail(-1,"非法参数!");}//根据uid查询用户的详情FutureTask<UserinfoVO> userTask = new FutureTask(()->{return userService.getUserById(articleinfo.getUid());});taskExecutor.submit(userTask);//根据uid查询用户发表的文章数FutureTask<Integer> artCountTask = new FutureTask<>(()->{return articleService.getArtCountByUid(articleinfo.getUid());});taskExecutor.submit(artCountTask);//等待任务(线程池)执行完UserinfoVO userinfoVO = userTask.get();int artCount = artCountTask.get();userinfoVO.setArtCount(artCount);//数据组装HashMap<String,Object> result = new HashMap<>();result.put("user",userinfoVO);result.put("art",articleinfo);return ResultAjax.success(result);}
九、阅读量设置
🍅 使用ajax构造请求
//访问量+1function increamentRCount(){if (aid==null || aid<=0 ){return false;}jQuery.ajax({url:"/art/increment_rcount",type:"POST",data:{"aid":aid},success:function(res){}});}increamentRCount();
🍅 处理请求
🎄 mapper层
@Update("update articleinfo set rcount=rcount+1 where id=#{aid}")int incrementRCount(@Param("aid") int aid);
🎄 service层
public int incrementRCount(int aid){return articleMapper.incrementRCount(aid);}
🎄 controller层
(1)校验参数
(2)更改数据库
(3)返回结果
@RequestMapping("/increment_rcount")public ResultAjax incrementRCount(Integer aid){if (aid==null || aid<=0){return ResultAjax.fail(-1,"参数有误!");}int result = articleService.incrementRCount(aid);return ResultAjax.success(result);}
十、分页处理
核心参数:
(1)页码(当前在第几页)
(2)每页显示的最大条数(前端灵活的控制分页功能)
后端分页返回:
(1)当前页面的文章列表
(2)总共有多少页:根据博客总条数/每一页显示条数的结果,向上取整(比如3.3333 取为4)
🍅 1、前端处理
首先将对应的js引入进,来根据总条数每页显示条数(向上取整
添加一个id属性:主要是为了构造出每一条博客的div出来
<!-- 每一篇博客包含标题, 摘要, 时间 --> <div id="artListDiv"></div>
然后初始化数据:
(1)得到url中的分页参数
(2)qing求后端接口
(3)将结果返回给用户
var psize = 2; // 每页显示条数var pindex = 1; // 页码var totalpage = 1; // 总共有多少页// 初始化数据function init(){// 1.处理分页参数psize = getParamValue("psize");if(psize==null){psize = 2; // 每页显示条数}pindex = getParamValue("pindex");if(pindex==null){pindex = 1; // 页码}jQuery("#pindex").html(pindex);// 2.请求后端接口jQuery.ajax({url:"/art/getlistbypage",type:"GET",data:{"pindex":pindex,"psize":psize},success:function(res){// 3.将结果展示给用户if(res.code==200 && res.data!=null){var createHtml = "";if(res.data.list!=null && res.data.list.length>0){// 有文章totalpage = res.data.size;jQuery("#pszie").html(totalpage);var artlist = res.data.list;for(var i=0;i<artlist.length;i++){var art = artlist[i]; // 文章对象createHtml += '<div class="blog" >';createHtml += '<div class="title">'+art.title+'</div>';createHtml += '<div class="date">'+art.createtime+'</div>';createHtml += '<div class="desc">'+art.content+'</div>';createHtml += '<a href="blog_content.html?aid='+art.id+'" class="detail">查看全文 >></a>';createHtml += '</div>';}}else{// 暂无文章createHtml += '<h3 style="margin-top:20px;margin-left:20px;">暂无文章!</h3>';}jQuery("#artListDiv").html(createHtml);}else{alert("抱歉:查询失败!"+res.msg);}}});}init();//点击首页function doFirst(){if (pindex<=1){alert("已经在首页了,无需跳转");return false;}location.href = "blog_list.html";}//跳转到末页function doLast(){if (pindex>=totalpage){alert("已经在末页了,无需跳转!");return false;}location.href="blog_list.html?pindex="+totalpage;}//点击上一页function doBefore(){if (pindex<=1){alert("已经在首页了,无需跳转");return false;}location.href="blog_list.html?pindex="+(parseInt(pindex)-1);}//点击下一页function doNext(){if (pindex>=totalpage){alert("已经在末页了,无需跳转!");return false;}location.href="blog_list.html?pindex="+(parseInt(pindex)+1);}
🍅 2、后端处理
推导到分页公式:每页显示两条数据
每次只有偏移量offset变了
# 第一页 mysql> select * from articleinfo order by id limit 2 offset 0;# 第三页 mysql> select * from articleinfo order by id limit 2 offset 2;# 第四页 mysql> select * from articleinfo order by id limit 2 offset 2; .....假设每条显示条数为pageSize,页码为pageIndex
故而推到出的公式为:offset = pageSize*(pageIndex-1)
🎄 mapper层
@Select("select * from articleinfo order by id desc limit #{psize} offset #{offset} ")public List<Articleinfo> getListByPage(@Param("psize") int psize,@Param("offset")int offset);@Select("select count(*) from articleinfo")int getCount();
🎄 service层
public List<Articleinfo> getListByPage(int psize,int offset){return articleMapper.getListByPage(psize, offset);}
🎄 controller层
(1)加工矫正
(2)并发进行文章列表和总页数的查询
(3)组装数据
(4)将结果返回给前端
@RequestMapping("/getlistbypage")public ResultAjax getListByPage(Integer pindex,Integer psize) throws ExecutionException, InterruptedException {if (pindex == null || pindex < 1){pindex = 1;}if (psize==null || psize<1){psize = 2;}//查询分页列表数据:Integer finalPsize = psize;Integer finalPindex = pindex;FutureTask<List<Articleinfo>> listTask = new FutureTask<>(()->{int finalOffset = finalPsize *(finalPindex -1);return articleService.getListByPage(finalPsize,finalOffset);}) ;//查询总页数FutureTask<Integer> sizeTask = new FutureTask<>(()->{int totalCount = articleService.getCount();double sizeTemp = (totalCount * 1.0) / (finalPsize * 1.0);return (int) Math.ceil(sizeTemp);});taskExecutor.submit(listTask);taskExecutor.submit(sizeTask);List<Articleinfo> list = listTask.get();int size = sizeTask.get();HashMap<String,Object> map = new HashMap<>();map.put("list",list);map.put("size",size);return ResultAjax.success(map);}
十一、密码加盐
先修改数据库中的字段:
mysql> alter table userinfo modify password varchar(65) not null;
🍅 1、定义加密规则
加密流程:
使用加盐算法,也就是使用一个不重复随机的盐值+密码,得到一个无规律的密码 (1)生成一个盐值
(2)(根据盐值 + 固定密码)进行加密 -> md5(盐值 + 密码)= 最终密码
(3)将[盐值]+ [分隔符$] + 最终密码保存到数据库中
验证密码流程:
(1)得到盐值
(2)md5(盐值+待验证密码)-> 最终待验证的密码
(3)对比最终待验证的密码和数据中的最终密码是否相同-> 相同密码正确
创建一个加密解密的类:

public class PasswordUtils {//加盐加密public static String encrypt(String password){String salt = UUID.randomUUID().toString().replace("-","");String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));return salt+"$"+finalPassword;}//待验证密码public static boolean decrypt(String password,String dbPassword){if (!StringUtils.hasLength(password) || !StringUtils.hasLength(dbPassword) ||dbPassword.length() != 65){return false;}String[] dbPasswordArray = dbPassword.split("$");if (dbPasswordArray.length!=2){return false;}String salt = dbPasswordArray[0];String dbFinalPassword = dbPasswordArray[1];String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));if (finalPassword.equals(dbFinalPassword)){return true;}return false;}
}
🍅 2、修改UserController代码
reg()方法中添加以下代码:
userinfo.setPassword(PasswordUtils.encrypt(userinfo.getPassword()));
在login()方法中添加以下代码
//使用对象中的密码和用户输入的密码进行比较if (!PasswordUtils.decrypt(userinfoVO.getPassword(),userinfo.getPassword())){return ResultAjax.fail(-2,"用户名或者密码错误!");}
相关文章:
项目实战 — 博客系统③ {功能实现}
目录 一、编写注册功能 🍅 1、使用ajax构造请求(前端) 🍅 2、统一处理 🎄 统一对象处理 🎄 保底统一返回处理 🎄 统一异常处理 🍅 3、处理请求 二、编写登录功能 🍅 …...
卷积神经网络全解:(AlexNet/VGG/ GoogLeNet/LeNet/ResNet/卷积/激活/池化/全连接)、现代卷积神经网络、经典卷积神经网络
CNN,卷积神经网络,Convolution Neural Network 卷积计算公式:N (W-F2p)/s1 这个公式每次都得看看,不能忘 1 经典网络 按照时间顺序 1.1 LeNet LeNet是 Yann LeCun在1998年提出,用于解决手…...
WDM 模型(Windows Driver Model)简述
WDM 模型(Windows Driver Model) 是微软公司为 Windows98 和 Windows2000 的驱动程序设计的一种架构,在 WDM 驱动程序模型中,每个硬件设备 至少有两个驱动程序。其中一个为功能驱动程序,它了解硬件工作的所有细节,负 责初始化 …...
【算法刷题之数组篇(1)】
目录 1.leetcode-59. 螺旋矩阵 II(题2.题3相当于二分变形)2.leetcode-33. 搜索旋转排序数组3.leetcode-81. 搜索旋转排序数组 II(与题目2对比理解)(题4和题5都是排序双指针)4.leetcode-15. 三数之和5.leetcode-18. 四数之和6.leet…...
【数据挖掘】使用 Python 分析公共数据【01/10】
一、说明 本文讨论了如何使用 Python 使用 Pandas 库分析官方 COVID-19 病例数据。您将看到如何从实际数据集中收集见解,发现乍一看可能不那么明显的信息。特别是,本文中提供的示例说明了如何获取有关疾病在不同国家/地区传播速度的信息。 二、准备您的…...
html怎么插入视频?视频如何插入页面
html怎么插入视频?视频如何插入页面 HTML 的功能强大,基本所有的静态效果都可以在此轻松呈现,各种视频网站内有大量的视频内容,本篇文章教你如何在 html 中插入视频 代码如下: <!DOCTYPE html> <html> …...
游戏服务端性能测试
导语:近期经历了一系列的性能测试,涵盖了Web服务器和游戏服务器的领域。在这篇文章中,我将会对游戏服务端所做的测试进行详细整理和记录。需要注意的是,本文着重于记录,而并非深入的编程讨论。在这里,我将与…...
【使用Zookeeper当作注册中心】自己定制负载均衡常见策略
自己定制负载均衡常见策略 一、前言随机(Random)策略的实现轮询(Round Robin)策略的实现哈希(Hash)策略 一、前言 大伙肯定知道,在分布式开发中,目前使用较多的注册中心有以下几个&…...
设计模式十七:迭代器模式(Iterator Pattern)
迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种访问聚合对象(例如列表、集合、数组等)中各个元素的方法,而无需暴露其内部表示。迭代器模式将遍历元素和访问元素的责任分离开来࿰…...
Python制作爱心并打包成手机端可执行文件
前言 本文是想要将python代码打包成在手机上能执行的文件 尝试了几个库, 有这也那样的限制,最终还是选了BeeWare 环境:python3.7.x 开始 找到打包有相关工具os-android-apk-builder,buildozer,cx_Freezeÿ…...
使用docker-compose.yml快速搭建开发、部署环境(nginx、tomcat、mysql、jar包、各种程序)以及多容器通信和统一配置
目录 docker-compose语法(更多说明可查看下面代码)imagehostnamecontainer_namevolumesnetworks yml文件的使用启动停止 开发环境(这里以python为例)部署环境nginxmysqltomcatjar包打包后的可执行程序 常见问题与解决方案多个容器…...
管理类联考——逻辑——真题篇——按知识分类——汇总篇——二、论证逻辑——支持加强——第三节——分类3——类比题干支持
文章目录 第三节 支持加强-分类3-类比题干支持真题(2017-28)-支持加强-正面支持-表达“确实如此”真题(2017-36)-支持加强-正面支持-表达“确实如此”真题(2017-39)-支持加强-正面支持-方法有效或方法可行,但多半不选择方法无恶果真题(2017-50)-支持加强真题(2018-2…...
搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nums[1], …, …...
Steam搬砖项目:最长久稳定的副业!
项目应该大家都有听说话,但是细节问题,如何操作可能有些不是很清楚,今天在这里简单分享一下。 这个Steam搬砖项目主要赚钱汇率差和价值差,是一个细分领取的小项目。 不用引流,时间也是比较自由的,你可以兼…...
最小化安装移动云大云操作系统--BCLinux-R8-U8-Server-x86_64-230802版
CentOS 结束技术支持,转为RHEL的前置stream版本后,国内开源Linux服务器OS生态转向了开源龙蜥和开源欧拉两大开源社区,对应衍生出了一系列商用Linux服务器系统。BC-Linux V8.8是中国移动基于龙蜥社区Anolis OS 8.8版本深度定制的企业级X86服务…...
神经网络基础-神经网络补充概念-05-导数
概念 导数是微积分中的一个概念,用于描述函数在某一点的变化率。在数学中,函数的导数表示函数值随着自变量的微小变化而产生的变化量,即斜率或变化率。 假设有一个函数 f(x),其中 x 是自变量,y f(x) 是因变量。函数…...
kubernetes — 安装Ingress
1、 Ingress 1、安装-Nginx-Ingress kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml 2、设为默认的Ingress [rootk8s01 ~]# vim default_ingress.yaml apiVersion: networking.…...
SSR使用HTTPS
1.安装 npm i browser-sync 2. 再angular.json里配置 "serve-ssr": {"builder": "nguniversal/builders:ssr-dev-server","options": {"ssl": true,"sslCert": "./node_modules/browser-sync/certs/server…...
Spring Boot中使用validator如何实现接口入参自动检验
文章目录 一、背景二、使用三、举例 一、背景 在项目开发过程中,经常会对一些字段进行校验,比如字段的非空校验、字段的长度校验等,如果在每个需要的地方写一堆if else 会让你的代码变的冗余笨重且相对不好维护,如何更加规范和优…...
thinkphp 5 实现UNION ALL 3个联表查询,并且带上搜索条件,名称,时间,手机号
在ThinkPHP 5中实现带有搜索条件、名称、时间和手机号的3个联表查询(UNION ALL),您可以按照以下步骤进行操作: 确保已经配置好数据库连接信息和相关的模型。 使用union()方法来构建3个联表查询,同时在每个查询中添加所…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

