项目实战 — 博客系统③ {功能实现}
目录
一、编写注册功能
🍅 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个联表查询,同时在每个查询中添加所…...
React 之 Router - 路由详解
一、Router的基本使用 1. 安装react-router react-router会包含一些react-native的内容,web开发并不需要 npm install react-router-dom 2. 设置使用模式 BrowserRouter或HashRouter Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件Bro…...
框架分析(1)-IT人必须会
框架分析(1)-IT人必须会 专栏介绍当今主流框架前端框架后端框架移动应用框架数据库框架测试框架 Angular关键特点和功能:组件化架构双向数据绑定依赖注入路由功能强大的模板语法测试友好 优缺点分析优点缺点 总结 专栏介绍 link 主要对目前市…...
前端面试的游览器部分(7)每天10个小知识点
目录 系列文章目录前端面试的游览器部分(1)每天10个小知识点前端面试的游览器部分(2)每天10个小知识点前端面试的游览器部分(3)每天10个小知识点前端面试的游览器部分(4)每天10个小知…...
认识Junit
1. 前言 2. Junit注解 2.1. 常用的注解 2.1.1. Test 表示当前方法是一个测试方法(不需要main来执行) Test void Test01() throws InterruptedException {System.out.println("测试用例1");WebDriver webDriver new ChromeDriver();webDriver.get("https:/…...
Unity C# 引用池 ReferencePool
Unity C# 引用池 ReferencePool 1.目的 对于多次创建的数据使用new 关键字是十分消耗性能的,使用完成后由GC去自动释放,当一个类型的数据频繁创建可以使用引用池进行管理。 2.实现 项目目录 IReference 接口 要放入引用池的数据只需要继承这个接口…...
opencv 进阶10-人脸识别原理说明及示例-cv2.CascadeClassifier.detectMultiScale()
人脸识别是指程序对输入的人脸图像进行判断,并识别出其对应的人的过程。人脸识别程 序像我们人类一样,“看到”一张人脸后就能够分辨出这个人是家人、朋友还是明星。 当然,要实现人脸识别,首先要判断当前图像内是否出现了人脸&…...
〔013〕Stable Diffusion 之 图片自动评分和不健康内容过滤器 篇
✨ 目录 🎈 下载咖啡美学评价插件🎈 咖啡美学评价使用🎈 不健康内容过滤器插件🎈 下载咖啡美学评价插件 想让系统帮你的图片作品打分评价,可以下载咖啡美学自动评价插件插件地址:https://github.com/p1atdev/stable-diffusion-webui-cafe-aesthetic也可以通过扩展列表…...
6.RocketMQ之消费索引文件ConsumeQueue
功能:作为CommitLog文件的索引文件。 本文着重分析为consumequeue/topic/queueId目录下的索引文件。 1.ConsumeQueueStore public class ConsumeQueueStore {protected final ConcurrentMap<String>, ConcurrentMap<Integer>, ConsumeQueueInterface…...
Appium-移动端自动测试框架,如何入门?
Appium是一个开源跨平台移动应用自动化测试框架。 既然只是想学习下Appium如何入门,那么我们就直奔主题。文章结构如下: 1、为什么要使用Appium? 2、如何搭建Appium工具环境?(超详细) 3、通过demo演示Appium的使用 4、Appium如何…...
复数混频器、零中频架构和高级算法开发
文章里讲解了关于射频IQ调制器、零中频架构相关的原理及技术,全都是干货!其实好多同行对软件无线电的原理、IQ调制、镜像抑制都是一知半解,知其然不知其所以然。好好研读这篇文章,相信会让你有种恍然大悟的感觉。 RF工程常被视为…...
隧道建设杂志网站/上海网络营销seo
炒金,一般是指对杠杠式的黄金电子合约(如伦敦金、黄金期货)进行短线的多空操作,从中赚取波动价差的行为。无论投资者从事内盘还是外盘交易,K线图都是基础的、必备的知识。 1、什么叫K线图? K线图源于日本的…...
给宝宝做衣服网站好/微信推广
startsWith()方法 startsWith()方法用来判断当前字符串是否是以另外一个给定的子字符串“开头”的,根据判断结果返回 true 或 false 参数: str.startsWith(searchString [, position]) searchString 要搜索的子字符串 position 在 str 中搜索 searchString 的…...
中国定制家具网/seo算法培训
配置示例 server{ server_name aaa.com location /api { proxy_pass http://xxx.com/api; proxy_set_header Host $proxy_host; #$host } } 说明 在同一服务器的IIS 发布了xxx.com 站点和 yyy.com 站点 共有80端口。需要通过Header Host 来分别响应 在通过浏览器访问的情况下&a…...
品牌高端网站制作机构/佛山做网站的公司哪家好
阿里妹导读:选择有时候比努力重要,真正厉害的人不仅仅是埋头苦干,而是会利用好的思维方式、好的方法,看穿事物的本质,顺势而为,找到事情的最优解,并懂得举一反三。架构师是程序员的目标之一&…...
聊城正规网站建设公司电话/推广赚钱的平台有哪些
CI不显示php错误,有错误直接显示500,是因为CI没有开启报错,在根目录下的index.php里修改ini_set(display_errors, 1);根据ENVIRONMENT是development还是production修改。0是不报错,1是报错PHP ci如何开启调试模式输出详细数据库报…...
wordpress =/seo的含义
Collection List Arraylist:Object数组Vector:Object数组LinkedList:双向循环链表 Set HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素LinkedHashSet&…...