个人博客系统——SSM框架

news2024/11/25 0:40:29

项目特点:

1.使用手工加盐算法代替明文,提高用户隐私安全性

2.登录功能的验证使用了拦截器

3.支持分布式 Session存储和缓存都放到了Redis里面

具体实现步骤

1.创建一个SSM项目

​​​​​​​

2.准备项目

 先删除项目中无用的文件和目录

 引入前端页面(resources-static)

 添加项目常用配置(在resources下创建一个application.yml,并删除application.properties)

在我的gitee的代码片段中复制SSM常用配置

# 配置数据库的连接字符串
 
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
 
# 设置 Mybatis 的 xml 保存路径
 
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
  level:
    com:
      example:
        demo: debug

复制后注意!看我们所使用的数据库名字是否为mycnblog 密码是否正确

3.初始化数据库

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
 
-- 使用数据数据
use mycnblog;
 
-- 创建表[用户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime datetime default current_timestamp,
    updatetime datetime default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';
 
-- 创建文章表
drop table if exists  articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime datetime default current_timestamp,
    updatetime datetime default current_timestamp,
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';
 
-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
 
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
    values('Java','Java正文',1);

4.创建项目分层

先建这几个 后面再有需要再建

 

5.添加统一的返回类型

先在demo下建一个公共包(common)公共类(AjaxResult)

前端和后端使用ajax交互,返回统一的结果 :AjaxResult

//AjaxResult.java
package com.example.demo.common;
 
import lombok.Data;
 
import java.io.Serializable;
 
/**
 * 统一数据格式返回
 * implements Serializable 实现序列化
 */
@Data
public class AjaxResult implements Serializable {
    //状态码
    private Integer code;
    //状态码的描述信息
    private String msg;
    //返回的数据 不知道返回的数据类型是啥 所以使用Object
    private Object data;
 
    /**
     * 操作成功返回的结果
     */
    public static AjaxResult success(Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(200);
        result.setMsg("");
        result.setData(data);
        return result;
    }
    //进行方法的重载 自定义code
    public static AjaxResult success(int code, Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg("");
        result.setData(data);
        return result;
    }
    public static AjaxResult success(int code, String msg, Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
 
    /**
     * 返回失败的结果
     */
    public static AjaxResult fail(int code, String msg){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }
    public static AjaxResult fail(int code, String msg, Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

//ResponseAdvice.java
package com.example.demo.config;
 
import com.example.demo.common.AjaxResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
/**
 * 实现统一数据返回的保底类
 * 说明:在返回数据之前,检测数据的类型是否为统一的对象;如果不是,封装成统一的对象
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * 开关,如果是true的时候,才能调用beforeBodyWrite
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
 
    /**
     *对数据格式进行校验和封装
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof AjaxResult) return body;
        if(body instanceof String){
            //先把body封装成统一的对象,因其为String类型,再把它转为json类型
            return objectMapper.writeValueAsString(AjaxResult.success(body));
 
        }
        return AjaxResult.success(body);
    }
}

6.实现用户的注册功能

前端代码

1.非空校验

2.判断两次密码是否一致

3.ajax提交请求

#在head中增加jquery
<script src="js/jquery.min.js"></script>
#在提交按钮中添加onclick
<button id="submit" onclick="mysub()">提交</button>
<script>
        //提交注册事件
        function mysub(){
            //1.非空校验
            var username = jQuery("#username");
            var password = jQuery("#password");
            var password2 = jQuery("#password2");
            if(username.val()==""){
                alert("请先输入用户名!");
                return;
            }
            if(password.val()==""){
                alert("请先输入密码!");
                return;
            }
            if(password2.val()==""){
                alert("请先输入确认密码!");
                return;
            }
            //2.判断两次密码是否一致
            if(password.val() != password2.val()){
                alert("两次输入的密码不一致,请先检查!");
                return;
            }
            //3.ajax提交请求
            jQuery.ajax({
                url:"/user/reg",
                type:"POST",
                data:{"username":username.val(),"password":password.val()},
                success:function(result) {
                    //响应的结果
                    if(result!=null && result.code==200 &&result.data==1){
                        //执行成功
                        if(confirm("恭喜注册成功!是否要跳转至登陆页面?")){
                            location.href = "/login.html";
                        }
                    }else{
                        alert("抱歉执行失败,请稍后再试!");
                    }
                }
            });
        }
    </script>

前端页面修改完代码不生效,此时极大的概率是缓存

1.先清空idea的缓存,删除项目目录下的target文件夹,然后重启项目

2.强制刷新浏览器

3.如果前两步还没有解决缓存问题,那么尝试给url添加上一个没有意义的参数 比如?v=1

ajax技术


后端代码

 后端代码写的时候注意顺序!一个调用一个 因此我们先在UserMapper.java/ArticleMapper.java中写,再在对应的*Mapper.xml中写SQL语句,再在*Service.java中写这个方法,最后再在*Controller.java中写具体的实现!后面的后端代码基本都是这个顺序写

//demo-entity-UserInfo.java
package com.example.demo.entity;
 
import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class UserInfo {
    //Integer 比int的兼容性更好
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;
}

//mapper-UserMapper.java
package com.example.demo.mapper;
 
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
 
@Mapper
public interface UserMapper {
 
    //注册
    int reg(UserInfo userInfo);
}
//resources-mapper-UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <insert id="reg">
        insert into userinfo(username,password) values(#{username},#{password})
    </insert>
</mapper>
//service-UserService.java
package com.example.demo.service;
 
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
 
@Service
public class UserService {
 
    @Resource
    private UserMapper userMapper;
 
    public int reg(UserInfo userInfo) {
        return userMapper.reg(userInfo);
    }
}

//controller-UserController.java
package com.example.demo.controller;
 
import com.example.demo.common.AjaxResult;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/user")
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @RequestMapping("/reg")
    public AjaxResult reg(UserInfo userInfo) {
        //非空校验和参数有效性校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())||
                !StringUtils.hasLength(userInfo.getPassword())) {
            return AjaxResult.fail(-1,"非法参数");
        }
        return AjaxResult.success(userService.reg(userInfo));
    }
}

7.根据用户查询userinfo对象

前端代码

#在head中增加jquery
<script src="js/jquery.min.js"></script>
#在提交按钮中添加onclick
<button id="submit" onclick="mysub()">提交</button>
<script>
        function mysub() {
            //1.非空校验
            var username = jQuery("#username");
            var password = jQuery("#password");
            if(username.val()==""){
                alert("请先输入用户名!");
                username.focus();
                return;
            }
            if(password.val()==""){
                alert("请先输入密码!");
                password.focus();
                return;
            }
            //2.ajax请求登录接口
            jQuery.ajax({
                url:"/user/login",
                type:"POST",
                data:{"username":username.val(),"password":password.val()},
                success:function(result){
                    if(result!=null && result.code==200 && result.data!=null){
                        //登陆成功
                        location.href = "myblog_list.html";
                    }else{
                        alert("抱歉登陆失败,用户名或密码输入错误,请重试");
                    }
                }
            });
        }
    </script>

后端代码

//在UserMapper.java中加入getUserByName方法
package com.example.demo.mapper;
 
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
@Mapper
public interface UserMapper {
 
    //注册
    int reg(UserInfo userInfo);
 
    /**
     * 登陆 根据用户查询userinfo对象
     * 说明:只传username 在UserController里进行password的比对
     */
    UserInfo getUserByName(@Param("username") String username);
 
}

#在UserMapper.xml中添加select操作
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <insert id="reg">
        insert into userinfo(username,password) values(#{username},#{password})
    </insert>
 
    <select id="getUserByName" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where username=#{username}
    </select>
</mapper>
//在UserService中加入getUserByName()方法,并返回username
package com.example.demo.service;
 
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
 
@Service
public class UserService {
 
    @Resource
    private UserMapper userMapper;
 
    public int reg(UserInfo userInfo) {
        return userMapper.reg(userInfo);
    }
 
    public UserInfo getUserByName(String username){
        return userMapper.getUserByName(username);
    }
}

//在UserController中添加login方法,并传入用户名和密码
在UserController中的login方法里 将用户存储到session中,不然之后的删除博客等操作无法进行
@RequestMapping("/login")
    public AjaxResult login(HttpServletRequest request, String username, String password) {
        //1.非空校验
        if(!StringUtils.hasLength(username)|| !StringUtils.hasLength(password))  {
            return AjaxResult.fail(-1,"非法请求");
        }
        //2.查询数据库
        UserInfo userInfo = userService.getUserByName(username);
        if(userInfo != null && userInfo.getId() > 0){
            //有效的用户名 两个密码是否相同
            if(password.equals(userInfo.getPassword())){
                //登陆成功
                //将用户存储到session中
                HttpSession session = request.getSession();
                session.setAttribute(AppVariable.USER_SESSION_KEY,userInfo);
                userInfo.setPassword("");//返回前端之前,隐藏敏感(密码)信息
                return AjaxResult.success(userInfo);
            }
        }
        return AjaxResult.success(0,null);
 
    }

//在common下建立一个全局变量类 AppVariable.java
public class AppVariable {
    public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
 
}

8.拦截器

先写一个拦截器LoginInterceptor,这个拦截器要实现HandlerInterceptor(拦截管理器)的接口,里面去实现preHandle的方法

然后要把prehandle加到全项目的全局配置文件中

//config-LoginInterceptor.java
package com.example.demo.config;
 
import com.example.demo.common.AppVariable;
import org.springframework.web.servlet.HandlerInterceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
/**
 * 登录拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * true -> 用户已登录
     * false ->用户未登录
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果有与当前的request相关联的HttpSession,则返回该HttpSession;如果没有则返回null;
        HttpSession session =request.getSession(false);
        if(session!=null &&session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
            //用户已登录
            return true;
        }
        //调整到登陆页面
        response.sendRedirect("/login.html");
        return false;
    }
}

//在系统的配置文件中配置拦截规则
//在config下建AppConfig.java 实现一个WebMvcConfigurer的配置文件
package com.example.demo.config;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/blog_list.html")
                .excludePathPatterns("/blog_content.html")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg");
 
 
    }
}

注意:此时的session是借助cookie储存到cookie中的 后期会部署到Redis上

9.我的博客列表页

获得左侧个人信息

前端代码

#在head中增加jquery
<script src="js/jquery.min.js"></script>

更改这两处 实现动态赋值

 <script>
        function showInfo(){
            jQuery.ajax({
                url:"/user/showinfo",
                type:"POST",
                data:{},
                success:function(result){
                    if(result != null && result.code == 200){
                        jQuery("#username").text(result.data.username);
                        jQuery("#artCount").text(result.data.artCount);
                    }else{
                        alert("个人信息加载失败,请重新刷新再试");
                    }
                }
            });
        }
        showInfo();
</script>

后端代码

//此处对应博客列表articleinfo 因此在demo-mapper下建ArticleMapper.java
package com.example.demo.mapper;
 
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
@Mapper
public interface ArticleMapper {
    int getArtCountByUid(@Param("uid") Integer uid);
}
#因此在resources-mapper下建ArticleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleMapper">
 
    <select id="getArtCountByUid" resultType="Integer">
        select count(*) from articleinfo where uid = 1;
    </select>
</mapper>

此时生成一个单元测试 进行测试

package com.example.demo.mapper;
 
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import javax.annotation.Resource;
 
import static org.junit.jupiter.api.Assertions.*;
 
@SpringBootTest
class ArticleMapperTest {
 
    @Resource
    private ArticleMapper articleMapper;
 
    @Test
    void getArtCountByUid() {
        int result = articleMapper.getArtCountByUid(1);
        System.out.println("文章总数: "+result);
    }
}

 可以看到这里文章总数为1 因此ArticleMapper没有问题,接着写

//demo-controller-UserController.java
@Autowired
    private ArticleService articleService;

此时返回的数据不再是基础数据类型,而是articlecount,因此新建vo-UserinfoVO.java 展示的一个对象

package com.example.demo.vo;
 
import com.example.demo.entity.UserInfo;
import lombok.Data;
 
@Data
public class UserinfoVO extends UserInfo {
    private Integer artCount;//此人发表的文章总数
}

接着在demo-controller-UserController里写
  1.得到当前登录用户(从session中获取 )
  2.得到用户发表的文章总数
但是此处的session不是只在我的博客列表页进行获取

//在demo-common下建UserSessionUtils.java
package com.example.demo.common;
 
import com.example.demo.entity.UserInfo;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
/**
 * 当前登陆用户相关的操作
 */
public class UserSessionUtils {
    /**
     * 得到当前的登录用户
     * @param request
     * @return
     */
    public static UserInfo getUser(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        if(session != null &&
            session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
            //说明用户已正常登录
            return (UserInfo) session.getAttribute(AppVariable.USER_SESSION_KEY);
        }
        return null;
    }
}

//demo-controller-UserController.java
@RequestMapping("/showinfo")
    public AjaxResult showInfo(HttpServletRequest request){
        UserinfoVO userinfoVO = new UserinfoVO();
        //1.得到当前登录用户(从session中获取 )
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo ==null){
            return AjaxResult.fail(-1,"非法请求");
        }
        //Spring提供的深克隆方法
        BeanUtils.copyProperties(userInfo,userinfoVO);
        //2.得到用户发表的文章总数
        userinfoVO.setArtCount(articleService.getArtCountByUid(userInfo.getId()));
        return AjaxResult.success(userinfoVO);
    }

获得我的文章列表数据

获取我的文章列表要不要传uid给后端?

不需要传递任何参数,并且是一定不能传参的

前端代码

<script>
        function getMyArtList(){
            jQuery.ajax({
                url:"/art/mylist",
                type:"POST",
                data:{},
                success:function(result) {
                    if(result != null && result.code==200){
                        //有两种情况,一种是发表了文章,一种是没有发表任何文章
                        if(result.data!=null && result.data.length()>0){
                            //此用户发表文章了
                            var artListDiv ="";
                            for(var i=0;i<result.data.length;i++){
                                var artItem = result.data[i];
                                artListDiv += '<div class="blog">';
                                artListDiv += '<div class="title">'+artItem.title+'</div>';
                                artListDiv += '<div class="date">'+artItem.updatetime+'</div>';
                                artListDiv += '<div class="desc">';
                                artListDiv += artItem.content;
                                artListDiv += '</div>';
                                artListDiv += '<a href="blog_content.html?id='+
                                    artItem.id + '" class="detail">查看全文 &gt;&gt;</a>&nbsp;&nbsp;';
                                artListDiv += '<a href="blog_edit.html?id='+
                                    artItem.id + '" class="detail">修改 &gt;&gt;</a>&nbsp;&nbsp;';  
                                artListDiv += '<a href="javascript:myDel('+
                                    artItem.id+');" class="detail">删除 &gt;&gt;</a>'; 
                                artListDiv += '</div>';    
                            }
                            jQuery("#artDiv").html(artListDiv);
                        }else{
                            //此用户未发表文章
                            jQuery("#artDiv").html("<h3>暂无文章</h3>");
                        }
                    }else{
                        alert("查询文章列表出错,请重试!");
                    }     
                }
            });
        }
        getMyArtList();
    </script>

后端代码

//建一个实体类 Articleinfo.java
package com.example.demo.entity;
 
import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class Articleinfo {
    private Integer id;
    private String title;
    private String content;
    private Data createtime;
    private Data updatetime;
    private Integer uid;
    private Integer rcount;
    private Integer state;
}

//在AriticleMapper.java里添加getMyList方法
package com.example.demo.mapper;
 
import com.example.demo.entity.Articleinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
import java.util.List;
 
@Mapper
public interface ArticleMapper {
    int getArtCountByUid(@Param("uid") Integer uid);
 
    List<Articleinfo> getMyList(@Param("uid") Integer uid);
}

//在AriticleMapper.xml里添加
<select id="getMyList" resultType="com.example.demo.entity.Articleinfo">
        select * from articleinfo where uid =#{uid};
</select>
//ArticleService.java
public class ArticleService {
    @Resource
    private ArticleMapper articleMapper;
 
    public int getArtCountByUid(Integer uid){
        return articleMapper.getArtCountByUid(uid);
    }
 
    public List<Articleinfo> getMyList(Integer uid) {
        return articleMapper.getMyList(uid);
    }
}
//controller-ArticleController.java
package com.example.demo.controller;
 
import com.example.demo.common.AjaxResult;
import com.example.demo.common.UserSessionUtils;
import com.example.demo.entity.Articleinfo;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.servlet.http.HttpServletRequest;
import java.util.List;
 
@RestController
@RequestMapping("/art")
public class ArticleController {
 
    @Autowired
    private ArticleService articleService;
 
    @RequestMapping("/mylist")
    public AjaxResult getMyList(HttpServletRequest request){
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo == null){
            return AjaxResult.fail(-1,"非法请求");
        }
        List<Articleinfo> list = articleService.getMyList(userInfo.getId());
        return  AjaxResult.success(list);
    }
}

SpringBoot时间格式化

1.通过配置文件,设置全局的时间格式化

#在application.yml里的spring中设置jaskson
# 配置数据库的连接字符串
 
spring:
  jackson:
jackson:
    date-format: 'yyyy-MM-dd HH:mm:ss'
    time-zone: 'GMT+8'
  datasource:
    url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
    username: root
    password: "zy19991227"
    driver-class-name: com.mysql.cj.jdbc.Driver
注意事项:此配置对LocalDateTime/LocalDate,需要使用Date数据类型

2.使用@JsonFormat注解

@Data
public class Articleinfo {
    private Integer id;
    private String title;
    private String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createtime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updatetime;
    private Integer uid;
    private Integer rcount;
    private Integer state;
}

删除文章操作

前端代码

<script>
        getMyArtList();
        // 删除文章
        function myDel(id){
            if(confirm("确实删除?")){
                // 删除文章
                jQuery.ajax({
                    url:"art/del",
                    type:"POST",
                    data:{"id":id},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data==1){
                            alert("恭喜:删除成功!");
                            // 刷新当前页面
                            location.href = location.href;
                        }else{
                            alert("抱歉:删除失败,请重试!");
                        }
                    }
                });
            }
        }
</script>

后端代码

//ArticleMapper.java中加入del方法
@Mapper
public interface ArticleMapper {
    int getArtCountByUid(@Param("uid") Integer uid);
 
    List<Articleinfo> getMyList(@Param("uid") Integer uid);
 
    int del(@Param("id") Integer id,@Param("uid") Integer uid);
}
//ArticleMapper.xml加入sql语句
<delete id="del">
        delete from articleinfo where id=#{id} and uid=#{uid}
</delete>
//ArticleService.java中加入del方法
public int del(Integer id,Integer uid){
        return articleMapper.del(id,uid);
}

注销功能(公共模块)

前端代码

<script>
 //退出登录
        function logout(){
            if(confirm("确认注销?")){
                jQuery.ajax({
                    url:"user/logout",
                    type:"POST",
                    data:{},
                    success:function(result){
                        if(result!=null && result.code ==200){
                            localhost.href = '/login.html';
                        }
                    }
                });
            }
        }
</script>

后端代码

//UserController.java
@RequestMapping("/logout")
    public AjaxResult logout(HttpSession session){
        session.removeAttribute(AppVariable.USER_SESSION_KEY);
        return AjaxResult.success(1);
    }

测试

同时登陆zhangsan和admin用户,zhangsan用户点击注销

确认是否注销成功? 看注销后的张三能否删除admin用户的文章 不能

10.文章详情页

实现思路:

1.从url中得到文章id

2.从后端查询当前文章的详情信息(以及uid)

3.根据上一步查询的uid查询用户的信息

4.请求后端接口实现阅读量+1

获取当前url参数的公共方法并查询文章详情

前端代码

//在blog_content.html中修改
<script>
//获取当前url参数的公共方法
            function getUrlValue(key){
                // ex:?id=1&v=2
                var params = location.search;
                if(params.length>1){
                    // ex:id=1&v=2
                    params = location.search.substring(1);
                    var paramArr = params.split("&");
                    for(var i=0;i<paramArr.length;i++){
                        var kv = paramArr[i].split("=");
                            if(kv[0]==key){
                            // 是我要查询的参数
                            return kv[1];
                        }
                    }
                }
                return "";
            }
            //查询文章详情
            function getArtDetail(id){
                if(id==""){
                    alert("非法参数!");
                    return;
                }
                jQuery.ajax({
                    url:"art/detail",
                    type:"POST",
                    data:{"id":id},
                    success:function(result){
                        if(result!=null && result.code==200){
                            jQuery("#title").html(result.data.title);
                            jQuery("#updatetime").html(result.data.updatetime);
                            jQuery("#rcount").html(result.data.rcount);
                            initEdit(result.data.content);
                            // 得到用户 id
                            showUser(result.data.uid);
                        }else{
                            alert("查询失败,请重试!");  
                        }
                    }
                });
            }
            getArtDetail(getUrlValue("id"));
</script>

后端代码

//ArticleMapper.java中加入getDetail方法
Articleinfo getDetail(@Param("id") Integer id);
#ArticleMapper.xml中加入SQL语句
<select id="getDetail" resultType="com.example.demo.entity.Articleinfo">
        select * from articleinfo where id=#{id}
</select>
//ArticleService.java中加入getDetail方法
public Articleinfo getDetail(Integer id) {
        return articleMapper.getDetail(id);
}

!!!注意此处需要在AppConfig.java拦截器中开放一个接口

//因为我们还有个所有人的博客列表页,从那里进入博客详情页也是ok的
.excludePathPatterns("/art/detail")

根据上一步查询的uid查询用户的信息

前端代码

<script>
    // 查询用户的详情信息
            function showUser(id){
                jQuery.ajax({
                    url:"/user/getuserbyid",
                    type:"POST",
                    data:{"id":id},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data.id>0){
                            jQuery("#username").text(result.data.username);
                            jQuery("#artCount").text(result.data.artCount);
                        }else{
                            alert("抱歉:查询用户信息失败,请重试!");
                        }
                    }
                });
            }
</script>

后端代码

//根据id获取用户 UserMapper.java
UserInfo getUserById(@Param("id") Integer id);
#UserMapper.xml中加入SQL语句
<select id="getUserById" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where id=#{id}
</select>
//UserService.java中加入getUserById方法
public UserInfo getUserById(Integer id){
        return userMapper.getUserById(id);
}
//UserController.java中加入getUserById方法
@RequestMapping("/getuserbyid")
    public AjaxResult getUserById(Integer id){
        if(id==null || id <= 0){
            //无效参数
            return AjaxResult.fail(-1,"非法参数");
        }
        UserInfo userinfo = userService.getUserById(id);
        if(userinfo == null || userinfo.getId() <=0){
            //无效参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //取出userinfo中的敏感信息 密码
        userinfo.setPassword("");
        UserinfoVO userinfoVO = new UserinfoVO();
        BeanUtils.copyProperties(userinfo,userinfoVO);
        //查询当前用户发表的文章数
        userinfoVO.setArtCount(articleService.getArtCountByUid(id));
        return AjaxResult.success(userinfoVO);
    }

!!!注意此处需要在AppConfig.java拦截器中开放一个接口

因为文章列表页针对所有由用户开放

.excludePathPatterns("/user/getuserbyid")

请求后端接口实现阅读量+1

实现思路1:

先查询文章的阅读量,然后再+1设置到数据库中

实现思路2:

将两步合二为一,update article set rcount=rcount+1 where id =xxx

前端代码

<script>
    // 阅读量 +1
            function updataRCount(){
                // 先得到文章 id
                var id = getUrlValue("id");
                if(id!=""){
                    jQuery.ajax({
                        url:"/art/incr-rcount",
                        type:"POST",
                        data:{"id":id},
                        success:function(result){}
                    });
                }
            }
            updataRCount();
</script>

后端代码

//ArticleMapper.java中定义incrRCount
int incrRCount(@Param("id") Integer id);
#ArticleMapper.xml中更新SQL语句
<update id="incrRCount">
        update articleinfo set rcount=rcount+1 where id=#{id}
</update>
//ArticleService.java中定义incrRCount方法
public int incrRCount(Integer id){
        return articleMapper.incrRCount(id);
}
//ArticleController.java中定义incrRCount方法
@RequestMapping("/incr-rcount")
    public AjaxResult incrRCount(Integer id){
        if(id != null && id > 0){
            return  AjaxResult.success(articleService.incrRCount(id));
        }
        return AjaxResult.success(-1,"未知错误");
    }

!!!注意此处需要在AppConfig.java拦截器中开放一个接口 否则出现302错误

.excludePathPatterns("/user/incr-rcount")

11.增加文章页

前端代码

//blog_add.html
<script>
// 提交
        function mysub(){
            if(confirm("确认提交?")){
                // 1.非空效验
                var title = jQuery("#title");
                if(title.val()==""){
                    alert("请先输入标题!");
                    title.focus();
                    return;
                }
                if(editor.getValue()==""){
                    alert("请先输入文章内容!");
                    return;
                }
                // 2.请求后端进行博客添加操作
                jQuery.ajax({
                    url:"/art/add",
                    type:"POST",
                    data:{"title":title.val(),"content":editor.getValue()},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data==1){
                            if(confirm("恭喜:文章添加成功!是否继续添加文章?")){
                                // 刷新当前页面
                                location.href = location.href;
                            }else{
                                location.href = "/myblog_list.html";
                            }
                        }else{
                            alert("抱歉,文章添加失败,请重试!");
                        }
                    }
                });
            }
</script>

后端代码

//ArticleMapper.java
int add(Articleinfo articleinfo);
#ArticleMapper.xml中添加SQL语句
<insert id="add">
    insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
</insert>
//ArticleService.java中定义 add方法
public int add(Articleinfo articleinfo){
        return articleMapper.add(articleinfo);
}
ArticleController.java中定义 add方法
@RequestMapping("/add")
    public AjaxResult add(HttpServletRequest request, Articleinfo articleinfo){
        //1.非空校验
        if(articleinfo == null || !StringUtils.hasLength(articleinfo.getTitle()) ||
                !StringUtils.hasLength(articleinfo.getContent())){
            //非法参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //2.数据库添加操作
        //a.得到当前登陆用户的id
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo == null || userInfo.getId() <= 0){
            //无效的登录用户
            return AjaxResult.fail(-2,"无效的登录用户");
        }
        articleinfo.setUid(userInfo.getId());
        //b.添加数据库并返回结果
        return AjaxResult.success(articleService.add(articleinfo));
    }

12.修改文章页

1.得到文章id

2.去后端查询文章的详情信息并设置到页面上

3.进行文章修改操作(调用后台)

得到文章id并将文章详情显示到页面

前端代码

<script>
// 文章初始化
        function initArt(){
            // 得到当前页面 url 中的参数 id(文章id)
            id = getUrlValue("id");
            if(id==""){
                alert("无效参数");
                location.href = "myblog_list.html";
                return;
            }
            // 请求后端,查询文章的详情信息
            jQuery.ajax({
                url:"art/detail",
                type:"POST",
                data:{"id":id},
                success:function(result){
                    if(result!=null && result.code==200){
                        jQuery("#title").val(result.data.title);
                        initEdit(result.data.content);
                    }else{
                        alert("查询失败,请重试!");  
                    }
                }
            });
        }
        initArt();
</script>

修改文章

前端代码

<script>
function mysub(){
            // 1.非空效验
            var title = jQuery("#title");
            if(title.val()==""){
                alert("请先输入标题!");
                title.focus();
                return;
            }
            if(editor.getValue()==""){
                alert("请先输入正文!");
                return;
            }
            // 2.进行修改操作
            jQuery.ajax({
                url:"/art/update",
                type:"POST",
                data:{"id":id,"title":title.val(),"content":editor.getValue()},
                success:function(result){
                    if(result!=null && result.code==200 && result.data==1){
                        alert("恭喜:修改成功!");
                        location.href = "myblog_list.html";
                    }else{
                        alert("抱歉:操作失败,请重试!");
                    }
                }
            });
        }
</script>

后端代码

//ArticleMapper.java
int update(Articleinfo articleinfo);
#ArticleMapper.xml中添加SQL语句
<update id="update">
        update articleinfo set title=#{title},content=#{content},updatetime=#{updatetime}
            where id=#{id} and uid=#{uid}
</update>
//ArticleService.java中定义 update方法
public int update(Articleinfo articleinfo){
        return articleMapper.update(articleinfo);
}
//ArticleController.java中定义 update方法
@RequestMapping("/update")
    public AjaxResult update(HttpServletRequest request, Articleinfo articleinfo){
        //1.非空校验
        if(articleinfo == null || !StringUtils.hasLength(articleinfo.getTitle()) ||
                !StringUtils.hasLength(articleinfo.getContent())||
                articleinfo.getId() ==null){
            //非法参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //2.得到当前用户id
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo == null && userInfo.getId() == null){
            //无效用户
            return AjaxResult.fail(-2,"无效用户");
        }
        //核心代码!!(解决了修改文章归属人判定的问题)
        articleinfo.setUid(userInfo.getId());
        articleinfo.setUpdatetime(LocalDateTime.now());
        return AjaxResult.success(articleService.update(articleinfo));
    }

13.密码加盐算法

为什么要进行加盐加密?

1.铭文不行,会泄露隐私

2.传统的MD5有规律可循,虽然不可逆,但是有规律可循,可以被暴力破解

【彩虹表:记录了几乎所有字符串的MD5】
3.加盐加密

随机,没有规律可言

加盐实现思路

每次调用方法的时候产生盐值(唯一)+密码 = 最终密码

最终密码还使用MD5加密,但是没关系,此时的MD5没有规律

使用UUID实现唯一密码

解密思路

需要两个密码:

1.需要验证的密码(用户输入的密码)

2.最终加密的密码(存在数据库中的密码)

核心思想:得到盐值 这个盐值会放到最终密码的某个位置

最终密码格式(65位):盐值(32位)$加密后的密码(32位)

难以解密的原因:没有绝对安全的密码,解密者不知道加密的规律,就算你知道我的加盐的密码,但是构造彩虹表是需要时间的,一个密码就需要构建一个彩虹表,但是数据库中有成千上万的彩虹表,破解成本极高

验证密码的伪代码:

已知:用户输入的明文密码、此用户在数据库存储的最终密码=盐值$加密后的密码

1.从最终密码中得到盐值

2.将用户输入的明文密码+盐值进行加密操作=加密后的密码

3.使用 盐值$加密后的密码 生成数据库存储的密码

4.对比生成的最终密码和数据库最终的密码是否相等

如果相等,那么用户名和密码就是对的,反之密码输入错误

后端代码

package com.example.demo.common;
 
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
 
import java.util.UUID;
 
public class PasswordUtils {
    /**
     * 1.加盐并生成密码
     * @param password 明文密码
     * @return 保存到数据库中的密码
     */
    public static  String encrypt(String password){
        //a.产生盐值(UUID生成的36位,有4位-,因此删去后为32位)
        String salt = UUID.randomUUID().toString().replace("-","");
        //b.生成加盐之后的密码
        String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //c.生成最终密码(保存到数据库中的密码)【约定:盐值(32位)+$+加密后的密码(32位)】
        String finalPassword = salt + "$" + saltPassword;
        return finalPassword;
    }
 
    /**
     * 2.生成加盐密码(步骤1的重载)
     * @param password 明文
     * @param salt 固定的盐值
     * @return 最终密码
     */
    public static String encrypt(String password,String salt){
        //a.生成一个加盐后的密码
        String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //b.生成最终的密码【约定:盐值(32位)+$+加密后的密码(32位)】
        String finalPassword = salt + "$" + saltPassword;
        return finalPassword;
    }
 
    /**
     * 3.验证密码
     * @param inputPassword 用户输入的明文密码
     * @param finalPassword 数据库保存的最终密码
     * @return
     */
    public static boolean check(String inputPassword,String finalPassword){
        if(StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword) &&
                finalPassword.length() == 65){
            //a.得到盐值
            String salt = finalPassword.split("\\$")[0];
            //b.使用之前加密的步骤,将明文密码和已经得到的盐值进行加密,生成最终的密码
            String confirmPassword = PasswordUtils.encrypt(inputPassword,salt);
            //c.对比两个最终密码是否相同
            return confirmPassword.equals(finalPassword);
        }
        return false;
    }
 
/*    public static void main(String[] args) {
        String password = "admin";
        String finalPassword = PasswordUtils.encrypt(password);
        System.out.println("加密" + finalPassword);
        //对比
*//*        String inputPassword = "12345";
        System.out.println("对比 "+ inputPassword
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword,finalPassword));*//*
        String inputPassword2 = "admin";
        System.out.println("对比 "+ inputPassword2
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword2,finalPassword));
    }*/
}

测试

public static void main(String[] args) {
        String password = "123456";
        String finalPassword = PasswordUtils.encrypt(password);
        System.out.println("加密" + finalPassword);
 
        //对比
        String inputPassword = "12345";
        System.out.println("对比 "+ inputPassword
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword,finalPassword));
        String inputPassword2 = "123456";
        System.out.println("对比 "+ inputPassword2
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword2,finalPassword));
    }

将数据库明文密码加密

update userinfo set password='12ad512af18f426e8e2248a17d8f2f92$40ffec117f74b4bf2a3fc0a849b7e0db';

在UserController.java的login方法中更改

if(password.equals(userInfo.getPassword()))
if(PasswordUtils.check(password,userInfo.getPassword())

在UserController.java的reg方法中更改

//密码加盐处理
userInfo.setPassword(PasswordUtils.encrypt(userInfo.getPassword()));

14.使用Spring Security 进行加盐和验证实现步骤

1.引入Spring Security 框架

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>

只用其类库,不用自动注入等功能

2.排除Spring Security自动加载

启动类中加这样一句话

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})

3.调用Spring Security加盐和验证

测试

package com.example.demo;
 
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.DigestUtils;
 
@SpringBootTest
class DemoApplicationTests {
 
    @Test
    void contextLoads() {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = "123456";
        String finalPassword = passwordEncoder.encode(password);
        System.out.println("第1次加密:" + finalPassword);
        System.out.println("第2次加密:" + passwordEncoder.encode(password));
        System.out.println("第3次加密:" + passwordEncoder.encode(password));
        // 验证
        String inputPassword = "12345";
        System.out.println("错误密码比对结果:" +
                (passwordEncoder.matches(inputPassword, finalPassword)));
        String inputPassword2 = "123456";
        System.out.println("正确密码比对结果:" +
                (passwordEncoder.matches(inputPassword2, finalPassword)));
 
    }
 
 
/*    public static void main(String[] args) {
        String password = "123456";
        String mdString = DigestUtils.md5DigestAsHex(password.getBytes());
        System.out.println(mdString);
    }*/
}

 15.所有博客列表页

分页页面

分页关键实现分析:

前端:当前页面【每页显示条数固定显示最大2条】

后端:当前页码、每页显示最大条数

公式:(当前页码n-1)*每页显示最大条数psize =offset

后端代码

//ArticleMapper.java
List<Articleinfo> getListByPage(@Param("psize") Integer psize,
                                    @Param("offsize") Integer offsize);
<select id="getListByPage" resultType="com.example.demo.entity.Articleinfo">
        select * from articleinfo limit #{psize} offset #{offsize}
</select>

单元测试

package com.example.demo.mapper;
 
import com.example.demo.entity.Articleinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import javax.annotation.Resource;
 
import java.util.List;
 
import static org.junit.jupiter.api.Assertions.*;
 
@SpringBootTest
class ArticleMapperTest {
 
    @Resource
    private ArticleMapper articleMapper;
 
    @Test
    void getArtCountByUid() {
        int result = articleMapper.getArtCountByUid(1);
        System.out.println("文章总数: "+result);
    }
 
    @Test
    void getListByPage() {
        List<Articleinfo> list = articleMapper.getListByPage(3,0);
        System.out.println(list);
        System.out.println("-------------- ");
        List<Articleinfo> list2 = articleMapper.getListByPage(3,3);
        System.out.println(list);
    }
}

//ArticleService.java中定义 getListByPage方法
public List<Articleinfo> getListByPage(Integer psize,Integer offsize){
        return articleMapper.getListByPage(psize,offsize);
    }
//ArticleController.java中定义 getListByPage方法
/**
     *查询列表根据分页
     * @param pindex 当前页码(从1开始)
     * @param psize 每页显示条数
     * @return
     */
    @RequestMapping("/listbypage")
    public AjaxResult getListByPage(Integer pindex, Integer psize){
        //1.参数校正
        if(pindex == null || pindex <= 1){
            pindex = 1;
        }
        if(psize == null || psize <= 1){
            psize = 2;
        }
        //分页公式的值 = (当前页码n-1)*每页显示最大条数psize
        int offsize = (pindex - 1) * psize;
        List<Articleinfo> list = articleService.getListByPage(psize,offsize);
        return AjaxResult.success(list);
    }

前端代码

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表</title>
    <link rel="stylesheet" href="css/list.css">
    <link rel="stylesheet" href="css/blog_list.css">
    <style>
        
        .nav{
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            height: 50px;
        }
        .container{
            padding-top: 80px;
            height: auto;
        }
        .container-right{
            width: auto;
        }
        .blog-pagnation-wrapper{
            height: 40px;
            margin: 16px 0;
            text-align: center;
        }
        .blog-pagnation-item{
            display: inline-block;
            padding: 8px;
            border: 1px solid #d0d0d5;
            color: #333;
        }
        .blog-pagnation-item:hover{
            background: #4e4eeb;
            color: #fff;
        }
        .blog-pagnation-item.actvie{
            background: #4e4eeb;
            color: #fff;
        }
 
    </style>
    <script src="js/jquery.min.js"></script>
    <script src="js/common.js"></script>
</head>
 
<body>
    <!-- 导航栏 -->
    <div class="nav">
        <img src="img/logo2.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 用来占据中间位置 -->
        <span class="spacer"></span>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="login.html">登陆</a>
    </div>
    <!-- 版心 -->
    <div class="container">
        <!-- 右侧内容详情 -->
        <div class="container-right" style="width: 100%;">
            <div id="artListDiv">
            </div>
            
            <hr>
            <div class="blog-pagnation-wrapper">
                <button onclick="goFirstPage()" class="blog-pagnation-item">首页</button> 
                <button onclick="goBeforePage()" class="blog-pagnation-item">上一页</button>  
                <button onclick="goNextPage()" class="blog-pagnation-item">下一页</button>
                <button onclick="goLastPage()" class="blog-pagnation-item">末页</button>
            </div>
        </div>
    </div>
    <script>
        // 当前页码
        var pindex = 1;
        // 每页显示条数
        var psize = 2;
        // 最大页数
        var pcount =1;
        // 1.先尝试得到当前 url 中的页码
        pindex = (getUrlValue("pindex")==""?1:getUrlValue("pindex"));
        // 2.查询后端接口得到当前页面的数据,进行展示
        function initPage(){
           jQuery.ajax({
            url:"/art/listbypage",
            type:"POST",
            data:{"pindex":pindex,"psize":psize},
            success:function(result){
                if(result!=null && result.code==200 && result.data.list.length>0){
                    var artListHtml = "";
                    for(var i=0;i<result.data.list.length;i++){
                        var articleinfo = result.data.list[i];
                        artListHtml +='<div class="blog">';
                        artListHtml +='<div class="title">'+articleinfo.title+'</div>';
                        artListHtml +='<div class="date">'+articleinfo.updatetime+'</div>';
                        artListHtml +='<div class="desc">'+articleinfo.content+'</div>';
                        artListHtml +='<a href="blog_content.html?id='+ articleinfo.id
                            +'" class="detail">查看全文 &gt;&gt;</a>';
                        artListHtml +='</div>';    
                    }
                    jQuery("#artListDiv").html(artListHtml);
                    pcount = result.data.pcount;
                }
            }
           });     
        }
        initPage();
        // 跳转到首页
        function goFirstPage(){
            if(pindex<=1){
                alert("当前已经在首页了");
                return;
            }
            location.href = "blog_list.html";
        }
        // 点击上一页按钮
        function goBeforePage(){
            if(pindex<=1){
                alert("当前已经在首页了");
                return;
            }
            pindex = parseInt(pindex) -1;
            location.href ="blog_list.html?pindex="+pindex;
        }
        function goNextPage(){
            if(pindex>=pcount){
               alert("已经在末页了");
               return; 
            }
            pindex = parseInt(pindex)+1;
            location.href ="blog_list.html?pindex="+pindex;
        }
        function goLastPage(){
            if(pindex>=pcount){
               alert("已经在末页了");
               return; 
            }
            location.href ="blog_list.html?pindex="+pcount;
        }
    </script>
</body>
</html>

获取文章总条数

后端代码

//ArticleMapper.java
int getCount();
#ArticleMapper.xml
<select id="getCount" resultType="Integer">
        select count(*) from articleinfo;
</select>
//ArticleService.java
public int getCount() {
       return articleMapper.getCount();
}
//ArticleController.java
/**
     *查询列表根据分页
     * @param pindex 当前页码(从1开始)
     * @param psize 每页显示条数
     * @return
     */
    @RequestMapping("/listbypage")
    public AjaxResult getListByPage(Integer pindex, Integer psize){
        //1.参数校正
        if(pindex == null || pindex <= 1){
            pindex = 1;
        }
        if(psize == null || psize <= 1){
            psize = 2;
        }
        //分页公式的值 = (当前页码n-1)*每页显示最大条数psize
        int offsize = (pindex - 1) * psize;
        //文章列表数据
        List<Articleinfo> list = articleService.getListByPage(psize,offsize);
        //当前列表总共多少页
        //a.总共多少条数据
        int totalCount = articleService.getCount();
        //b.总条数/psize(每页显示条数)
        double pcountdb = totalCount / (psize * 1.0);
        //c.使用进一法得到总页数
        int pcount = (int)Math.ceil(pcountdb);
        HashMap<String,Object> result = new HashMap<>();
        result.put("list",list);
        result.put("pcount",pcount);
        return AjaxResult.success(result);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/939523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目进度管理:项目经理做了无用功,如何解决?

李思是一个职场新人&#xff0c;项目经理分配了一个简单的任务给她&#xff0c;完成一份关于竞品功能的调查报告&#xff0c;以便为公司的产品提供参考。 李思第二天回复称进展顺利&#xff0c;预计两天时间能完成。 然而&#xff0c;当项目经理收到厚厚的调研报告时&#x…

Gitlab创建一个空项目

1. 创建项目 Project slug是访问地址的后缀&#xff0c;跟前边的ProjectUrl拼在一起&#xff0c;就是此项目的首页地址&#xff1b; Visibility Level选择默认私有即可&#xff0c;选择内部或者公开&#xff0c;就会暴露代码。 勾选Readme选项&#xff0c;这样项目内默认会带…

怎么看待目前的游戏市场格局,看好哪儿家公司?

近三十年来&#xff0c;中国游戏砥砺前行经过近四十年的发展&#xff0c;将计算机技术、互动媒体技术、艺术设计、经济系统、商业模式等进行了充分融合应用&#xff0c;作为 “ 第九艺术 ” 已经成为文化产业 的重要支柱&#xff0c;其硬件和软件创新也不断改变着人们的娱乐消费…

小研究 - 多租户Java虚拟机的设计与实现(二)

多租户技术&#xff0c;让一个软件实例同时服务于不同的组织&#xff0c;在云计算环境中被广泛运用&#xff0c;极大的节约了基础设施资源。但是&#xff0c;云计算环境中使用最广的Java语言却没有提供相应的多租户功能。为此&#xff0c;云服务提供商不得不对自己的应用服务器…

arcgis的MapServer服务查询出来的结果geometry坐标点带*的问题

不知道小伙伴使用arcgis server服务做查询的时候&#xff0c;有没有遇到下面的问题 原因是查询结果中出现*字符 这个问题一直困扰了我很久&#xff1a;因为从数据库查询的坐标点是没有问题的。 一开始有同事遇到过&#xff0c;说重新插入下就好了&#xff0c;有时候确实能解决…

Qt-creater 在线安装太慢,换国内源

Qt 在线安装太慢,换国内源 下载安装包 实例使用清华源 如下图先下载安装包exe文件 url: 链接: https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/online_installers/ 下载安装包到本地目录D:\ Powershell进入本地目录D:\ 使用参数方式换国内清华源 换国内清华源 …

嵌入式AI助力当代商业的发展

数字化转型的业务影响是广泛的&#xff0c;但购买者应寻求嵌入式AI在以下领域具有最大的影响力&#xff1a; 1.业务流程和任务的自动化 当买家搜索购买包含AI的软件时&#xff0c;他们应该研究该解决方案为员工自动执行日常任务的方式。嵌入式AI应该节省员工的时间和精力&#…

Maven之高版本的 lombok 和 tomcat 7 插件冲突问题

高版本的 lombok 和 tomcat 7 插件冲突问题 在开发期间&#xff0c;当我们使用 tomcat7-maven-plugin 来作为运行环境运行我们项目使&#xff0c;如果我们项目中使用了 1.16.20 及以上版本的 lombok 包&#xff0c;项目启动时会报错&#xff1a; for annotations org.apache.…

工业级PDA高精度导航定位

工业级PDA是指能到达防尘、防水、防摔三防等级&#xff0c;并具备实时采集、自动存储、即时显示、即时反馈、自动处理和自动传输等功能的移动智能终端。为满足如农业、铁路、空间、勘测与绘图等复杂环境领域的需要&#xff0c;目前高端工业级PDA普遍具备高精度的导航定位功能&a…

Apple Configurator iphone ipad 设备管控 描述文件使用方法

一、准备 App Store 下载安装 Apple Configurator 二、Apple Configurator 注册组织&#xff0c; -----------这个组织可以是个人&#xff0c;或者其它组织导出-------再导入进来&#xff1a; 三、描述文件配置&#xff1a;“” 根据管控需求进行配置 “” 四、使用 Ap…

Django(7)-项目实战-发布会管理

登录功能 模板页面 sign/templates/index.html <!DOCTYPE html> <html> <head><title>Login Page</title> </head> <body><h1>发布会管理</h1><form action"/login/" method"post"><la…

Flask加amis学校管理系统java学生教务信息jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 Flask加amis学校管理系统 系统有3权限&#xff1a;学…

lnmp架构-mysql1

1.MySQL数据库编译 make完之后是这样的 mysql 初始化 所有这种默认不在系统环境中的路径里 就这样加 这样就可以直接调用 不用输入路径调用 2.初始化 重置密码 3.mysql主从复制 配置master 配置slave 当master 端中还没有插入数据时 在server2 上配slave 此时master 还没进…

【java基础——interface接口】

JAVA基础 interface接口 文章目录 JAVA基础接口 Interface 接口 Interface 接口是一种特殊的抽象类&#xff0c;它定义了一组抽象方法和常量&#xff0c;并且不包含具体实现。 只允许声明静态常量&#xff1a;必须且默认为 public static final 。声明抽象方法&#xff1a;必…

数据结构1

数据结构是计算机科学中存储和组织数据的一种方式&#xff0c;它定义了数据的表示方式和对数据进行操作的方法&#xff0c;常见的数据结构包括数组、栈、链表、队列、树、图等。 目录 一、常见的数据结构 1.数组 2.栈 3.队列 4.链表 5.树 6.图 一、常见的数据结构 1.数…

H5 + C3基础(五)(seo相关标签 顶部快捷栏实践 Logo seo)

seo相关标签 & 顶部快捷栏实践 seo相关标签titledescriptionkeywords 顶部快捷栏实践Logo seo seo相关标签 seo&#xff1a;搜索引擎优化&#xff0c;是一种利用搜索引擎规则提高网站在搜索引擎结果中自然排名的方式&#xff0c;进而提高网站的自然流量。 前端开发中主要s…

【Tkinter系列01/15】界面初步和布局

一、说明 一般来说&#xff0c;界面开发中&#xff0c;如果不是大型的软件&#xff0c;就不必用QT之类的实现&#xff0c;用Tkinter已经足够&#xff0c;然而即便是Tkinter规模不大&#xff0c;也需要一个系统专业的学习过程&#xff0c;本篇将是对Tkinter系列介绍的一篇博文。…

JavaSE(下)

一、集合进阶 双列集合Map<>【Java】Map集合概述 双列集合特点 Map常见API 三种遍历方式 1、由键找值,创建键的集合 2、通过键值对对对象进行遍历 3、利用lambda表达式进行遍历 HashMap HashMap的特点 HashMap的底层 总结 例题 package com.itheima.Mapdemo;import java.…

探索音视频出海的无限可能|中企出海领袖班第八期成功举办

​近年来&#xff0c;音视频社交出海赛道的热度吸引了众多企业纷纷进军海外市场。然而&#xff0c;面对复杂多变的海外市场&#xff0c;无论是产品的本地化策略&#xff0c;还是对海外网络环境的适应以及网络安全防御等因素&#xff0c;都成为决定产品能否在海外市场脱颖而出的…

Error while validating pooled Jedis object.

如何处理 jedis得连接池得异常&#xff0c;顺着找到Jedis的工厂类JedisFactory&#xff0c;对应看下这两个方法的实现&#xff0c;熟悉的代码找到了&#xff0c;可疑的点也找到了&#xff1a; public boolean validateObject(PooledObject<Jedis> pooledJedis) {final Bi…