博客系统开发流程

1. 框架搭建

  • 使用SpringBoot搭建,编写配置文件

  • yml文件配置-application.yml

  • spring:
      thymeleaf:
        mode: HTML
      profiles:
        active: pro
    
    

mybatis:
type-aliases-package: com.star.entity
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true


- application-dev.yml

-

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myblog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: 123456

logging:
level:
root: info
com.star: debug
file:
name: log/blog-dev.log


- application.pro.yml

-

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myblog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&serverTimezone=GMT%2B8
username: root
password: 123456

logging:
level:
root: warn
com.star: info
file:
name: log/blog-pro.log


**公共部分application.yml配置了thymeleaf模板,当前配置文件,数据库持久层配置(mapper),开发环境和部署环境配置了数据库和日志**

- logback-spring.xml配置(重写SpringBoot默认日志配置)

- ```xml
  <?xml version="1.0" encoding="UTF-8" ?>
  <configuration>
      <!--包含Spring boot对logback日志的默认配置-->
      <include resource="org/springframework/boot/logging/logback/defaults.xml" />
      <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
      <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
  
      <!--重写了Spring Boot框架 org/springframework/boot/logging/logback/file-appender.xml 配置-->
      <appender name="TIME_FILE"
                class="ch.qos.logback.core.rolling.RollingFileAppender">
          <encoder>
              <pattern>${FILE_LOG_PATTERN}</pattern>
          </encoder>
          <file>${LOG_FILE}</file>
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
              <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern>
              <!--保留历史日志一个月的时间-->
              <maxHistory>30</maxHistory>
              <!--
              Spring Boot默认情况下,日志文件10M时,会切分日志文件,这样设置日志文件会在100M时切分日志
              -->
              <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                  <maxFileSize>10MB</maxFileSize>
              </timeBasedFileNamingAndTriggeringPolicy>
  
          </rollingPolicy>
      </appender>
  
      <root level="INFO">
          <appender-ref ref="CONSOLE" />
          <appender-ref ref="TIME_FILE" />
      </root>
  
  </configuration>
          <!--
              1、继承Spring boot logback设置(可以在appliaction.yml或者application.properties设置logging.*属性)
              2、重写了默认配置,设置日志文件大小在10MB时,按日期切分日志
          -->

spring.profiles.active=dev 使用参数指定SpringBoot启动

PageHelper配置模板

@GetMapping("/pictures")
public String pictures(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
    PageHelper.startPage(pageNum,10);
    List<Picture> pictureList = pictureService.listPicture();
    PageInfo<Picture> pageInfo = new PageInfo<>(pictureList);
    model.addAttribute("pageInfo",pageInfo);
    return "admin/pictures";
}

2. 异常处理

在页面访问时候需要错误页面,以及出现错误跳转到错误页面的处理

  • 404.html
  • 500.html
  • error.html

全局异常处理

对于404和500界面,出现错误进行捕捉,自定义的错误我们需要自己拦截,定义一个类来捕捉,通过这个类来拦截所有的异常

package com.star.hander;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
//拦截所有带controller注解的控制器
@ControllerAdvice
public class ControllerExceptionHandler {

    //日志记录异常
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    //异常处理方法

    /**
     * @Descripion 处理错误信息(全局异常处理)
     * @Author lsh
     * @param request : 访问的异常URL
     * @param e : 异常参数
     * @return  返回错误信息页面
     * @throws Exception
     */
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request,Exception e) throws Exception{

        //记录异常信息
        logger.error("Request URL : {},Exception : {}",request.getRequestURL(),e);

        //当标识了状态码的时候就不拦截
        if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class)!=null){
            throw e;
        }

        //返回记录的异常信息
        ModelAndView mv = new ModelAndView();
        mv.addObject("url",request.getRequestURL());
        mv.addObject("exception",e);
        //跳转到error包下的error界面
        mv.setViewName("error/error");
        return mv;
    }
}
  • 对于资源找不到的类,作异常处理

  • package com.star;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    /**
     * 自定义异常
     * @ResponseStatus(HttpStatus.NOT_FOUND) 注解表示资源找不到的状态码,标识了状态码的时候就不拦截
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public class NotFoundException extends RuntimeException{
        public NotFoundException() {
        }
    
        public NotFoundException(String message) {
            super(message);
        }
    
        public NotFoundException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    <!--5-->
  • 切面处理

  • package com.star.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Arrays;
    
    /**
     * @Pointcut("execution(* com.star.controller..(..))"):定义切面,声明log()是一个切面,通过execution来表示需要拦截的类,
     * 这里表示拦截控制器下面的所有类所有方法
     * 在访问页面(controller)之前,拦截请求的URL、IP、调用的方法、传递的参数、返回的内容,并记录到日志
     */
    @Aspect
    @Component
    public class LogAspect {
        //获取日志信息
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        //定义切面
        @Pointcut("execution(* com.star.controller.*.*(..))")
        public void log(){}
    
        //在切面之前执行
        @Before("log()")
        public void doBefore(JoinPoint joinPoint){
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            //获取URL,IP
            String url = request.getRequestURL().toString();
            String ip =  request.getRemoteAddr();
    
            //获取请求方法
            String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
    
            //获取请求参数
            Object[] args = joinPoint.getArgs();
            RequestLog requestLog = new RequestLog(url,ip,classMethod,args);
            logger.info("Request : {}",requestLog);
        }
    
    
//在切面之后执行
@After("log()")
public void doAfter(){
    logger.info("----doAfter----");
}


//返回之后拦截
@AfterReturning(returning = "result",pointcut = "log()")
public void doAfterReturn(Object result){
    logger.info("Result : {}",result);
}

//封装请求参数
private class RequestLog{
    private String url;
    private String ip;
    private String classMethod;
    private Object[] args;

    public RequestLog(String url, String ip, String classMethod, Object[] args) {
        this.url = url;
        this.ip = ip;
        this.classMethod = classMethod;
        this.args = args;
    }

    @Override
    public String toString() {
        return "RequestLog{" +
                "url='" + url + '\'' +
                ", ip='" + ip + '\'' +
                ", classMethod='" + classMethod + '\'' +
                ", args=" + Arrays.toString(args) +
                '}';
    }
}

}




## 4. 登陆功能

- 首先定义用户实体类

- ```java
  package com.star.entity;
  
  import lombok.AllArgsConstructor;
  import lombok.Getter;
  import lombok.NoArgsConstructor;
  import lombok.Setter;
  
  import java.util.Date;
  
  /**
   * 用户实体类
   * - 昵称
   * - 用户名
   * - 密码
   * - 邮箱
   * - 类型
   * - 头像
   * - 创建时间
   * - 更新时间
   */
  @Getter
  @Setter
  @NoArgsConstructor
  @AllArgsConstructor
  public class User {
      private Long id;
      private String nickname;
      private String username;
      private String password;
      private String email;
      private String avatar;
      private Integer type;
      private Date createTime;
      private Date updateTime;
  }

  • 定义MD5加密,思路是将明文密码通过MD5加密存储到数据库,在校验的时候通过输入的密码进行MD5再次加密,和数据库的密码比对

  • package com.star.util;
    
    

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**

  • MD5加密工具类

  • /
    public class MD5Utils {

    /**

    • @Description: MD5加密

    • @Auther: ONESTAR

    • @Date: 17:19 2020/5/27

    • @Param: 要加密的字符串

    • @Return: 加密后的字符串

    • /
      public static String code(String str){
      try {

      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(str.getBytes());
      byte[]byteDigest = md.digest();
      int i;
      StringBuffer buf = new StringBuffer("");
      for (int offset = 0; offset < byteDigest.length; offset++) {
          i = byteDigest[offset];
          if (i < 0)
              i += 256;
          if (i < 16)
              buf.append("0");
          buf.append(Integer.toHexString(i));
      }
      //32位加密
      return buf.toString();
      // 16位的加密
      //return buf.toString().substring(8, 24);

      } catch (NoSuchAlgorithmException e) {

      e.printStackTrace();
      return null;

      }

      }

      //根据明文输出密码
      public static void main(String[] args) {
      System.out.println(code(“123456”));
      }

    }

    
    
    
    - 建立持久层接口
    
    - ```java
      package com.star.dao;
      
      import com.star.entity.User;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import org.springframework.stereotype.Repository;
      
      @Mapper
      @Repository
      public interface UserDao {
          /**
           * @param 定义下个属性的别名,在xml中直接可以用 username = #{username} 来进行判断
           * @param username  用户名
           * @param password  密码
           * @return  返回用户对象
           */
          User findByUsernameAndPassword(@Param("username") String username,@Param("password") String password);
      }
  • mapper.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.star.dao.UserDao">
        <select id="findByUsernameAndPassword" resultType="com.star.entity.User">
            select * from myblog.t_user
            where username = #{username} and password = #{password};
        </select>
    </mapper>
    
    
    - Service层及SerivceImpl
    
    - ```java
      package com.star.service;
      
      import com.star.entity.User;
      
      public interface UserService {
          //核对用户名和密码
          User checkUser(String username,String password);
      }
  • package com.star.service.impl;
    
    import com.star.dao.UserDao;
    import com.star.entity.User;
    import com.star.service.UserService;
    import com.star.util.MD5Utils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
    
        @Override
        public User checkUser(String username, String password) {
            User user = userDao.findByUsernameAndPassword(username, MD5Utils.code(password));
            return user;
        }
    }
    <!--9-->
  • 登陆拦截器

  • 在没有登陆的情况下不能让客户访问到后台管理页面,所以添加一个登陆拦截器,将访问路径过滤,使用SpringBoot内置的interceptor

  • package com.star.interceptor;
    
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    /**
     * @Description: 登录过滤拦截
     */
    public class LoginInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if(request.getSession().getAttribute("user")==null){
                response.sendRedirect("/admin");
                return false;
            }
            return true;
        }
    }
    <!--10-->
  • 通过前端的表单传入参数 (th:action="@{/admin/login}" 表示动作,将参数传入页面,通过后端校验)

<form class="ui large form" method="post" action="#" th:action="@{/admin/login}">
  <div class="ui  segment">
    <div class="field">
      <div class="ui left icon input">
        <i class="user icon"></i>
        <input type="text" name="username" placeholder="用户名">
      </div>
    </div>
    <div class="field">
      <div class="ui left icon input">
        <i class="lock icon"></i>
        <input type="password" name="password" placeholder="密码">
      </div>
    </div>
    <button class="ui fluid large teal submit button">登   录</button>
  </div>

  <div class="ui error mini message"></div>
  <div class="ui mini negative message" >用户名和密码错误</div>

</form>

5. 实体类构建

  • 直接上代码了,没有什么好说的
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 博客实体类,和评论一对多关系
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Blog {
    private Long id;
    private String title;
    private String content;
    private String firstPicture;
    private String flag;
    private Integer views;
    private Integer commentCount;
    private boolean appreciation;
    private boolean shareStatement;
    private boolean commentabled;
    private boolean published;
    private boolean recommend;
    private Date createTime;
    private Date updateTime;
    private String description;

    private Type type;
    private User user;
    private Long typeId;
    private Long userId;
    private List<Comment> comments = new ArrayList<>();
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 评论实体类,和子评论一对多
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Comment {
    private Long id;
    private String nickname;
    private String email;
    private String content;
    private String avatar;
    private Date createTime;
    private Long blogId;
    private Long parentCommentId;
    private boolean adminComment;

    //回复评论
    private List<Comment> replyComments = new ArrayList<>();
    private Comment ParentComment;
    private String parentNickname;
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

/**
 * 友情链接实体类
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class FriendLink {
    private Long id;
    private String blogname;
    private String blogaddress;
    private String pictureaddress;
    private Date createTime;
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 留言实体类,和回复留言一对多
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Message {
    private Long id;
    private String nickname;
    private String email;
    private String content;
    private String avatar;
    private Date createTime;
    private Long parentMessageId;
    private boolean adminMessage;

    //回复留言
    private List<Message> replyMessages = new ArrayList<>();
    private Message parentMessage;
    private String parentNickname;
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 相册实体类
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Picture {
    private Long id;
    private String picturename;
    private String picturetime;
    private String pictureaddress;
    private String picturedescription;
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

/**
 * 分类实体类,和博客一对多
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Type {
    private Long id;
    private String name;
    private List<Blog> blogs = new ArrayList<>();
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

/**
 * 用户实体类
 * - 昵称
 * - 用户名
 * - 密码
 * - 邮箱
 * - 类型
 * - 头像
 * - 创建时间
 * - 更新时间
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String nickname;
    private String username;
    private String password;
    private String email;
    private String avatar;
    private Integer type;
    private Date createTime;
    private Date updateTime;
}

6. 分类管理

1. 持久层接口

package com.star.dao;


import com.star.entity.Type;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface TypeDao {

    //新增保存分类
    int saveType(Type type);

    //根据id查询分类
    Type getType(Long id);
    //查询所有分类
    List<Type> getAllType();
    //根据分类名称查询分类
    Type getTypeByName(String name);
    //编辑修改分类
    int updateType(Type type);
    //删除分类
    void deleteType(Long id);}

2. mapper文件

<?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.star.dao.TypeDao">
    <!-- 新增保存分类 -->
    <insert id="saveType" parameterType="com.star.entity.Type">
        insert into myblog.t_type values(#{id},#{name});
    </insert>

    <!-- 根据id查询分类 -->
    <select id="getType" resultType="com.star.entity.Type">
        select id,name from myblog.t_type where id = #{id};
    </select>

    <!-- 查询所有分类 -->
    <select id="getAllType" resultType="com.star.entity.Type">
        select * from myblog.t_type;
    </select>

    <!-- 根据分类名字查询 -->
    <select id="getTypeByName" resultType="com.star.entity.Type">
        select * from myblog.t_type where name = #{name};
    </select>

    <!-- 编辑修改分类 -->
    <update id="updateType" parameterType="com.star.entity.Type">
        update myblog.t_type set name = #{name} where id = #{id};
    </update>

    <!-- 删除分类 -->
    <delete id="deleteType">
        delete from myblog.t_type where id = #{id};
    </delete>
</mapper>

3. service层及Impl

  • 对应mapper文件的方法
  • @Transactional注解: 实现事务操作
package com.star.service.impl;

import com.star.dao.TypeDao;
import com.star.entity.Type;
import com.star.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @Transactional注解: 实现事务操作
 */
@Service
public class TypeServiceImpl implements TypeService {

    @Autowired
    private TypeDao typeDao;
    @Transactional
    @Override
    public int saveType(Type type) {
        return typeDao.saveType(type);
    }
    @Transactional
    @Override
    public Type getType(Long id) {
        return typeDao.getType(id);
    }
    @Transactional
    @Override
    public List<Type> getAllType() {
        return typeDao.getAllType();
    }

    @Override
    public Type getTypeByName(String name) {
        return typeDao.getTypeByName(name);
    }
    @Transactional
    @Override
    public int updateType(Type type) {
        return typeDao.updateType(type);
    }
    @Transactional
    @Override
    public void deleteType(Long id) {
        typeDao.deleteType(id);
    }
}
package com.star.service;

import com.star.entity.Type;

import java.util.List;

public interface TypeService {
    //新增保存分类
    int saveType(Type type);

    //根据id查询分类
    Type getType(Long id);

    //查询所有分类
    List<Type> getAllType();

    //根据分类名称查询分类
    Type getTypeByName(String name);

    //编辑修改分类
    int updateType(Type type);

    //删除分类
    void deleteType(Long id);
}

4. 引入分页插件

<!--引入分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

5. Controller

package com.star.controller;
import jdk.nashorn.internal.objects.annotations.Getter;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.star.entity.Type;
import com.star.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.List;

@Controller
@RequestMapping("/admin")
public class TypeController {

    @Autowired
    private TypeService typeService;

    //分页查询分类列表
    @GetMapping("/types")
    public String list(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
        String orderBy = "id desc";
        PageHelper.startPage(pageNum,10,orderBy);
        List<Type> list = typeService.getAllType();
        PageInfo<Type> pageInfo = new PageInfo<Type>(list);
        model.addAttribute("pageInfo",pageInfo);
        return "admin/types";
    }

    //返回新增分类页面
    @GetMapping("types/input")
    public String input(Model model){
        model.addAttribute("type",new Type());
        return "admin/types-input";
    }

    //新增分类
    @PostMapping("/types")
    public String post(Type type, RedirectAttributes attributes){
        Type type1 = typeService.getTypeByName(type.getName());
        if(type1 != null){
            attributes.addFlashAttribute("message","不能添加重复的分类");
        }
        int t = typeService.saveType(type);
        if(t == 0){
            attributes.addFlashAttribute("message","新增失败");
        }else{
            attributes.addFlashAttribute("message","新增成功");
        }
        return "redirect:/admin/types";
    }

    //跳转修改分类界面
    @GetMapping("/types/{id}/input")
    public String editInput(@PathVariable Long id,Model model){
        model.addAttribute("type",typeService.getType(id));
        return "admin/types-input";
    }

    //编辑修改分类
    @PostMapping("/types/{id}")
    public String editPost(Type type,RedirectAttributes attributes){
        Type type1 = typeService.getTypeByName(type.getName());
        if(type1 != null){
            attributes.addFlashAttribute("message","不能添加重复的分类");
            return "redirect:/admin/types/input";
        }
        int t = typeService.updateType(type);
        if(t == 0){
            attributes.addFlashAttribute("message","编辑失败");
        }else{
            attributes.addFlashAttribute("message","编辑成功");
        }
        return "redirect:/admin/types";
    }

    //删除分类
    @GetMapping("/types/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes){
        typeService.deleteType(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/types";
    }
}

7. 博客管理

1. 创建查询实体类BlogQuery,ShowBlog,SearchBlog

  • 建立不同的查询类是为了分离,如BlogQuery就是为了显示列表,显示列表的所有元素就是当前BlogQuery的属性
  • 同理,ShowBlog就是编辑修改页面,需要用到的属性
  • SearchBlog,由于搜索博客只需要两个属性,建立一个SearchBlog单独用来创立视图层引用
  • 以上三个业务层和持久层都是用的BlogDao和BlogService层
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

/**
 * 查询博客列表
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class BlogQuery {
    private Long id;
    private String title;
    private Date updateTime;
    private Boolean recommend;
    private Boolean published;
    private Long typeId;
    private Type type;
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

/**
 * 编辑修改文章实体类
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class ShowBlog {
    private Long id;
    private String flag;
    private String title;
    private String content;
    private Long typeId;
    private String firstPicture;
    private String description;
    private boolean recommend;
    private boolean published;
    private boolean shareStatement;
    private boolean appreciation;
    private boolean commentabled;
    private Date updateTime;
}
package com.star.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 搜索博客管理列表
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class SearchBlog {
    private String title;
    private Long typeId;
}

2. Dao层和Service及Impl

package com.star.dao;

import com.star.entity.Blog;
import com.star.entity.BlogQuery;
import com.star.entity.SearchBlog;
import com.star.entity.ShowBlog;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface BlogDao {
    //保存新增博客
    int saveBlog(Blog blog);

    //查询文章管理列表
    List<BlogQuery> getAllBlogQuery();

    //删除博客
    void deleteBlog(Long id);

    //更新博客
    int updateBlog(ShowBlog showBlog);

    //查询编辑修改的文章
    ShowBlog getBlogById(Long id);

    //搜索博客管理列表
    List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
}
package com.star.service;

import com.star.entity.Blog;
import com.star.entity.BlogQuery;
import com.star.entity.SearchBlog;
import com.star.entity.ShowBlog;

import java.util.List;

public interface BlogService {
    int saveBlog(Blog blog);
    List<BlogQuery> getAllBlog();
    void deleteBlog(Long id);
    ShowBlog getBlogById(Long id);
    int updateBlog(ShowBlog showBlog);
    List<BlogQuery> getBlogBySearch(SearchBlog searchBlog);
}
package com.star.service.impl;

import com.star.dao.BlogDao;
import com.star.entity.Blog;
import com.star.entity.BlogQuery;
import com.star.entity.SearchBlog;
import com.star.entity.ShowBlog;
import com.star.service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

@Service
public class BlogServiceImpl implements BlogService {

    @Autowired
    private BlogDao blogDao;

    @Override
    public int saveBlog(Blog blog) {
        blog.setCreateTime(new Date());
        blog.setUpdateTime(new Date());
        blog.setViews(0);
        blog.setCommentCount(0);
        return blogDao.saveBlog(blog);
    }

    @Override
    public List<BlogQuery> getAllBlog() {
        return blogDao.getAllBlogQuery();
    }

    @Override
    public void deleteBlog(Long id) {
        blogDao.deleteBlog(id);
    }

    @Override
    public ShowBlog getBlogById(Long id) {
        return blogDao.getBlogById(id);
    }

    @Override
    public int updateBlog(ShowBlog showBlog) {
        showBlog.setUpdateTime(new Date());
        return blogDao.updateBlog(showBlog);
    }

    @Override
    public List<BlogQuery> getBlogBySearch(SearchBlog searchBlog) {
        return blogDao.searchByTitleAndType(searchBlog);
    }
}

3. mapper文件,这里设计多表查询,较为复杂

<?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.star.dao.BlogDao">
    <insert id="saveBlog" parameterType="com.star.entity.Blog">
        insert into myblog.t_blog (id,title, content, first_picture, flag,
    views, comment_count, appreciation, share_statement, commentabled, published,
    recommend, create_time, update_time, type_id, user_id, description)
    values (#{id},#{title},#{content},#{firstPicture},#{flag},#{views},#{commentCount},#{appreciation},
    #{shareStatement},#{commentabled},#{published},#{recommend},#{createTime},
    #{updateTime},#{typeId},#{userId},#{description});
    </insert>

    <!-- 查询文章管理列表多对一配置 -->
    <resultMap id="blog" type="com.star.entity.BlogQuery">
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="updateTime" column="update_Time"/>
        <result property="recommend" column="recommend"/>
        <result property="published" column="published"/>
        <result property="typeId" column="type_id"/>
        <association property="type" javaType="com.star.entity.Type">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
        </association>
    </resultMap>

    <!-- 查询文章管理列表 -->
    <select id="getAllBlogQuery" resultMap="blog">
        select b.id,b.title,b.update_time,b.recommend,b.published,b.type_id,t.id,t.name
    from myblog.t_blog b left outer join
    myblog.t_type t on b.type_id = t.id order by b.update_time desc
    </select>

    <!-- 删除博客 -->
    <delete id="deleteBlog">
        delete from myblog.t_blog where id = #{id};
    </delete>

    <!-- 更新博客 -->
    <update id="updateBlog" parameterType="com.star.entity.ShowBlog">
    update myblog.t_blog set published = #{published},flag = #{flag} ,
    title = #{title}, content = #{content}, type_id = #{typeId},
    first_picture = #{firstPicture} , description = #{description} , recommend = #{recommend} ,
    share_statement = #{shareStatement}, appreciation = #{appreciation},
    commentabled = #{commentabled} ,update_time = #{updateTime} where id = #{id};
    </update>

    <!-- 查询编辑修改的文章 -->
    <select id="getBlogById" resultType="com.star.entity.ShowBlog">
    select b.id,b.flag,b.title,b.content,b.type_id,
    b.first_picture,b.description,b.recommend,b.published,b.share_statement,
    b.appreciation,b.commentabled from myblog.t_blog b  where  b.id = #{id};
    </select>
	<!-- 模糊查询 -->
    <select id="searchByTitleAndType" parameterType="com.star.entity.SearchBlog" resultMap="blog">
        <bind name="pattern" value="'%' + title + '%'" />
        select b.id,b.title,b.type_id,t.id,t.name from myblog.t_blog b ,myblog.t_type t
        <where>
            <if test="1 == 1">
                b.type_id = t.id
            </if>
            <if test="typeId != null">
                and b.type_id = #{typeId}
            </if>
            <if test="title != null">
                and b.title like #{pattern}
            </if>
        </where>
    </select>
</mapper>

4. resultMap/xml详解

<!--column不做限制,可以为任意表的字段,而property须为type 定义的pojo属性-->
<resultMap id="唯一的标识" type="映射的pojo对象">
  <id column="表的主键字段,或者可以为查询语句中的别名字段" jdbcType="字段类型" property="映射pojo对象的主键属性" />
  <result column="表的一个字段(可以为任意表的一个字段)" jdbcType="字段类型" property="映射到pojo对象的一个属性(须为type定义的pojo对象中的一个属性)"/>
  <association property="pojo的一个对象属性" javaType="pojo关联的pojo对象">
    <id column="关联pojo对象对应表的主键字段" jdbcType="字段类型" property="关联pojo对象的主席属性"/>
    <result  column="任意表的字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
  </association>
  <!-- 集合中的property须为oftype定义的pojo对象的属性-->
  <collection property="pojo的集合属性" ofType="集合中的pojo对象">
    <id column="集合中pojo对象对应的表的主键字段" jdbcType="字段类型" property="集合中pojo对象的主键属性" />
    <result column="可以为任意表的字段" jdbcType="字段类型" property="集合中的pojo对象的属性" />  
  </collection>
</resultMap>

5. Controller

  • 注意controller下的方法互相跳转,需要在return中加 “redirect:
package com.star.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.star.entity.*;
import com.star.service.BlogService;
import com.star.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpSession;
import java.util.List;

@Controller
@RequestMapping("/admin")
public class BlogController {

    @Autowired
    private BlogService blogService;

    @Autowired
    private TypeService typeService;

    //跳转博客新增页面
    @GetMapping("/blogs/input")
    public String input(Model model){
        model.addAttribute("types",typeService.getAllType());
        model.addAttribute("blog",new Blog());
        return "admin/blogs-input";
    }

    //博客新增
    @PostMapping("/blogs")
    public String post(Blog blog, RedirectAttributes attributes, HttpSession session){
        blog.setUser((User)session.getAttribute("user"));
        blog.setType(typeService.getType(blog.getType().getId()));
        blog.setTypeId(blog.getType().getId());
        blog.setUserId(blog.getUser().getId());

        int b = blogService.saveBlog(blog);
        if(b == 0){
            attributes.addFlashAttribute("message","新增失败");
        }else{
            attributes.addFlashAttribute("message","新增新增");
        }
        return "redirect:/admin/blogs";
    }

    //博客列表
    @GetMapping("/blogs")
    public String blogs(Model model, @RequestParam(defaultValue = "1",value = "pageNum") Integer pageNum){

        String orderBy = "update_time desc";
        PageHelper.startPage(pageNum,10,orderBy);
        List<BlogQuery> list = blogService.getAllBlog();
        PageInfo<BlogQuery> pageInfo = new PageInfo<BlogQuery>(list);
        model.addAttribute("types",typeService.getAllType());
        model.addAttribute("pageInfo",pageInfo);
        return "admin/blogs";
    }

    //删除博客
    @GetMapping("/blogs/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes){
        blogService.deleteBlog(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/blogs";
    }

    //跳转到编辑修改文章
    @GetMapping("/blogs/{id}/input")
    public String editInput(@PathVariable Long id,Model model){
        ShowBlog blogById = blogService.getBlogById(id);
        List<Type> allType = typeService.getAllType();
        model.addAttribute("blog",blogById);
        model.addAttribute("types",allType);
        return "admin/blogs-input";
    }

    //编辑修改文章
    @PostMapping("blogs/{id}")
    public String editPost(ShowBlog showBlog,RedirectAttributes attributes){
        int b = blogService.updateBlog(showBlog);
        if(b == 0){
            attributes.addFlashAttribute("message","编辑成功");
        }else{
            attributes.addFlashAttribute("message","编辑失败");
        }
        return "redirect:/admin/blogs";
    }

    //搜索博客管理列表
    @PostMapping("/blogs/search")
    public String search(SearchBlog searchBlog,Model model,
                         @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
        List<BlogQuery> blogBySearch = blogService.getBlogBySearch(searchBlog);
        PageHelper.startPage(pageNum,10);
        PageInfo<BlogQuery> pageInfo = new PageInfo<>(blogBySearch);
        model.addAttribute("pageInfo",pageInfo);
        //这是thymeleaf的一个模板片断,相当于返回admin/blogs模板中的某个片段。
        return "admin/blogs :: blogList";
    }
}

8. 友链管理

友链功能:CRUD,还需要编辑修改友链传递数据,新增作重复判断

Dao文件接口

package com.star.dao;

import com.star.entity.FriendLink;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface FriendLinkDao {
    //查询友链管理列表
    List<FriendLink> listFriendLink();

    //新增友链
    int saveFriendLink(FriendLink friendLink);

    //根据网址查询友链
    FriendLink getFriendLinkByBlogaddress(String blogaddress);

    //根据id查询友链
    FriendLink getFriendLink(Long id);

    //编辑修改友链
    int updateFriendLink(FriendLink friendLink);

    //删除友链
    void deleteFriendLink(Long id);
}

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.star.dao.FriendLinkDao">

    <!-- 查询所有友链 -->
    <select id="listFriendLink" resultType="com.star.entity.FriendLink">
        select * from myblog.t_friend order by t_friend.create_time desc
    </select>

    <!-- 添加友链 -->
    <insert id="saveFriendLink" parameterType="com.star.entity.FriendLink">
        insert into myblog.t_friend(blogaddress, blogname, create_time, pictureaddress)
        values (#{blogaddress},#{blogname},#{createTime},#{pictureaddress})
    </insert>

    <!-- 根据网址查询友链 -->
    <select id="getFriendLinkByBlogaddress" resultType="com.star.entity.FriendLink">
        select * from myblog.t_friend f where f.blogaddress = #{blogaddress};
    </select>

    <!-- 根据id查询友链 -->
    <select id="getFriendLink" resultType="com.star.entity.FriendLink">
        select * from myblog.t_friend f where f.id = #{id};
    </select>

    <!-- 编辑修改友链 -->
    <update id="updateFriendLink" parameterType="com.star.entity.FriendLink">
        update myblog.t_friend set blogname = #{blogname},blogaddress = #{blogaddress}, pictureaddress = #{pictureaddress}
        where id = #{id};
    </update>

    <!-- 删除友链 -->
    <delete id="deleteFriendLink">
        delete from myblog.t_friend where id= #{id};
    </delete>
</mapper>

Service及Impl

package com.star.service;

import com.star.entity.FriendLink;

import java.util.List;

public interface FriendLinkService {
    //查询友链管理列表
    List<FriendLink> listFriendLink();

    //新增友链
    int saveFriendLink(FriendLink friendLink);

    //根据网址查询友链
    FriendLink getFriendLinkByBlogaddress(String blogaddress);

    //根据id查询友链
    FriendLink getFriendLink(Long id);

    //编辑修改友链
    int updateFriendLink(FriendLink friendLink);

    //删除友链
    void deleteFriendLink(Long id);
}
package com.star.service.impl;

import com.star.dao.FriendLinkDao;
import com.star.entity.FriendLink;
import com.star.service.FriendLinkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class FriendLinkServiceImpl implements FriendLinkService {

    @Autowired
    private FriendLinkDao friendLinkDao;
    @Override
    public List<FriendLink> listFriendLink() {
        return friendLinkDao.listFriendLink();
    }

    @Override
    public int saveFriendLink(FriendLink friendLink) {
        return friendLinkDao.saveFriendLink(friendLink);
    }

    @Override
    public FriendLink getFriendLinkByBlogaddress(String blogaddress) {
        return  friendLinkDao.getFriendLinkByBlogaddress(blogaddress);
    }

    @Override
    public FriendLink getFriendLink(Long id) {
        return friendLinkDao.getFriendLink(id);
    }

    @Override
    public int updateFriendLink(FriendLink friendLink) {
        return friendLinkDao.updateFriendLink(friendLink);
    }

    @Override
    public void deleteFriendLink(Long id) {
        friendLinkDao.deleteFriendLink(id);
    }
}

Controller层

package com.star.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.star.entity.FriendLink;
import com.star.service.FriendLinkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.Date;
import java.util.List;

@Controller
@RequestMapping("/admin")
public class FriendController {

    @Autowired
    private FriendLinkService friendLinkService;

    //查询所有友链
    @GetMapping("/friendlinks")
    public String friend(Model model, @RequestParam(defaultValue = "1",value = "pageNum") Integer pageNum){
        PageHelper.startPage(pageNum,10);
        List<FriendLink> friendLinkList = friendLinkService.listFriendLink();
        PageInfo<FriendLink> pageInfo = new PageInfo<>(friendLinkList);
        model.addAttribute("pageInfo",pageInfo);
        return "admin/friendlinks";
    }


    //跳转友链新增界面
    @GetMapping("/friendlinks/input")
    public String input(Model model){
        model.addAttribute("friendlink",new FriendLink());
        return "admin/friendlinks-input";
    }


    //新增友链
    @PostMapping("/friendlinks")
    public String post(FriendLink friendLink, BindingResult result, RedirectAttributes attributes){
        FriendLink type1 = friendLinkService.getFriendLinkByBlogaddress(friendLink.getBlogaddress());
        if(type1 != null){
            attributes.addFlashAttribute("message","不能添加相同网址");
            return "redirect:/admin/friendlinks/input";
        }
        if(result.hasErrors()){
            return "admin/friendlinks-input";
        }
        friendLink.setCreateTime(new Date());

        int f = friendLinkService.saveFriendLink(friendLink);
        if(f == 0){
            attributes.addFlashAttribute("message","新增失败");
        }else{
            attributes.addFlashAttribute("message","新增成功");
        }
        return "redirect:/admin/friendlinks";
    }


    //跳转友链修改界面
    @GetMapping("friendlinks/{id}/input")
    public String editInput(@PathVariable Long id,Model model){
        //修改时,自动给界面赋值需要修改的友链字段,方便用户修改
        model.addAttribute("friendlink",friendLinkService.getFriendLink(id));
        return "admin/friendlinks-input";
    }
    //友链修改
    @PostMapping("/friendlinks/{id}")
    public String editPost(FriendLink friendLink,RedirectAttributes attributes){
        int t = friendLinkService.updateFriendLink(friendLink);
        if(t == 0){
            attributes.addFlashAttribute("message","编辑成功");
        }else{
            attributes.addFlashAttribute("message","编辑失败");
        }
        return "redirect:/admin/friendlinks";
    }
    //友链删除
    @GetMapping("/friendlinks/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes){
        friendLinkService.deleteFriendLink(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/friendlinks";
    }

}

9. 相册管理

CRUD功能

Dao接口

package com.star.dao;

import com.star.entity.Picture;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface PictureDao {

    //查询照片
    List<Picture> listPicture();
    //添加图片
    int savePicture(Picture picture);
    //根据id查询图片
    Picture getPicture(Long id);
    //编辑修改相册
    int updatePicture(Picture picture);
    //删除照片
    void deletePicture(Long id);
}

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.star.dao.PictureDao">
    <!-- 查询所有照片 -->
    <select id="listPicture" resultType="com.star.entity.Picture">
        select * from myblog.t_picture order by t_picture.id desc
    </select>
    <!-- 添加照片 -->
    <insert id="savePicture" parameterType="com.star.entity.Picture">
        insert into myblog.t_picture (picturename,picturetime,pictureaddress,picturedescription)
        values (#{picturename},#{picturetime},#{pictureaddress},#{picturedescription})
    </insert>
    <!-- 根据id查询照片 -->
    <select id="getPicture" resultType="com.star.entity.Picture">
        select * from myblog.t_picture p where p.id = #{id};
    </select>
    <!-- 编辑修改相册 -->
    <update id="updatePicture" parameterType="com.star.entity.Picture">
        update myblog.t_picture
        set picturename = #{picturename}, picturetime = #{picturetime}, pictureaddress = #{pictureaddress}, picturedescription = #{picturedescription}
        where id = #{id};
    </update>
    <!-- 删除照片 -->
    <delete id="deletePicture">
        delete from myblog.t_picture where id = #{id};
    </delete>
</mapper>

Service及Impl

package com.star.service;

import com.star.entity.Picture;

import java.util.List;

public interface PictureService {

    //查询照片
    List<Picture> listPicture();
    //添加图片
    int savePicture(Picture picture);
    //根据id查询图片
    Picture getPicture(Long id);
    //编辑修改相册
    int updatePicture(Picture picture);
    //删除照片
    void deletePicture(Long id);
}
package com.star.service.impl;

import com.star.dao.PictureDao;
import com.star.entity.Picture;
import com.star.service.PictureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class PictureServiceImpl implements PictureService {

    @Autowired
    private PictureDao pictureDao;

    @Override
    public List<Picture> listPicture() {
        return pictureDao.listPicture();
    }

    @Override
    public int savePicture(Picture picture) {
        return pictureDao.savePicture(picture);
    }

    @Override
    public Picture getPicture(Long id) {
        return pictureDao.getPicture(id);
    }

    @Override
    public int updatePicture(Picture picture) {
        return pictureDao.updatePicture(picture);
    }

    @Override
    public void deletePicture(Long id) {
        pictureDao.deletePicture(id);
    }
}

Controller

package com.star.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.star.entity.Picture;
import com.star.service.PictureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.List;

@Controller
@RequestMapping("/admin")
public class PictureController {

    @Autowired
    private PictureService pictureService;

    //查询照片列表
    @GetMapping("/pictures")
    public String pictures(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
        PageHelper.startPage(pageNum,10);
        List<Picture> pictureList = pictureService.listPicture();
        PageInfo<Picture> pageInfo = new PageInfo<>(pictureList);
        model.addAttribute("pageInfo",pageInfo);
        return "admin/pictures";
    }
    //跳转到新增页面
    @GetMapping("/pictures/input")
    public String input(Model model){
        model.addAttribute("picture",new Picture());
        return "admin/pictures-input";
    }
    //照片新增
    @PostMapping("/pictures")
    public String post(Picture picture, BindingResult result, RedirectAttributes attributes){
        if(result.hasErrors()){
            return "admin/pictures-input";
        }
        int p = pictureService.savePicture(picture);
        if(p == 0){
            attributes.addFlashAttribute("message","新增失败");
        }else{
            attributes.addFlashAttribute("message","新增成功");
        }
        return "redirect:/admin/pictures";
    }
    //跳转到编辑页面
    @GetMapping("/pictures/{id}/input")
    public String editInput(@PathVariable Long id,Model model){
        model.addAttribute("picture",pictureService.getPicture(id));
        return "admin/pictures-input";
    }
    //照片编辑
    @PostMapping("/pictures/{id}")
    public String editPost(Picture picture,RedirectAttributes attributes){
        int p = pictureService.updatePicture(picture);
        if(p == 0){
            attributes.addFlashAttribute("message","编辑失败");
        }else{
            attributes.addFlashAttribute("message","编辑成功");
        }
        return "redirect:/admin/pictures";
    }
    //删除照片
    @GetMapping("/pictures/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes){
        pictureService.deletePicture(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/pictures";
    }
}

10. 博客首页显示

功能分析:

  • 查询最新文章列表
  • 查询最新推荐文章
  • 搜索功能(根据关键字搜索功能)
  • 统计博客信息
    • 博客总数
    • 访问总数
    • 评论总数
    • 留言总数

功能实现思路

  • 查询最新文章列表:定义一个实体类来查询文章列表信息,并定义相应接口实现查询
  • 查询最新推荐文章:定义一个实体类来查询推荐文章信息,定义相应接口实现查询
  • 搜索博客:显示的还是列表博客,因此用文章列表信息的实体类,定义一个接口实现搜索
  • 统计博客:定义接口关联SQL来实现统计博客信息

定义实体类:1. 最新博客实体类 2. 最新推荐实体类

package com.star.entity;

import lombok.Data;

import java.util.Date;

/**
 * 首页博客信息实体类
 */
@Data
public class FirstPageBlog {

    //博客信息
    private Long id;
    private String title;
    private String firstPicture;
    private Integer views;
    private Integer commentCount;
    private Date updateTime;
    private String description;

    //分类名称
    private String typeName;

    //用户名
    private String nickname;

    //用户头像
    private String avatar;
}
package com.star.entity;

import lombok.Data;

/**
 * 推荐博客数据实体类
 */
@Data
public class RecommendBlog {
    private Long id;
    private String title;
    private String firstPicture;
    private boolean recommend;
}

持久层接口

//查询首页最新博客列表信息
List<FirstPageBlog> getFirstPageBlog();

//查询首页最新推荐信息
List<RecommendBlog> getAllRecommendBlog();

//搜索博客列表
List<FirstPageBlog> getSearchBlog(String query);

//统计博客总数
Integer getBlogTotal();

//统计访问总数
Integer getBlogViewTotal();

//统计评论总数
Integer getBlogCommentTotal();

//统计留言总数
Integer getBlogMessageTotal();

xml文件

<!--查询首页最新博客列表信息-->
<resultMap id="firstPageBlog" type="com.star.queryvo.FirstPageBlog">
    <id property="id" column="id"/>
    <result property="title" column="title"/>
    <result property="firstPicture" column="first_picture"/>
    <result property="views" column="views"/>
    <result property="commentCount" column="comment_count"/>
    <result property="createTime" column="create_time"/>
    <result property="updateTime" column="update_time"/>
    <result property="description" column="description"/>
    <result property="typeName" column="name"/>
    <result property="nickname" column="nickname"/>
    <result property="avatar" column="avatar"/>
</resultMap>
<select id="getFirstPageBlog" resultMap="firstPageBlog">
    select b.id,b.title,b.first_picture, b.views, b.comment_count,b.create_time,b.update_time,b.description,
    t.name ,
    u.nickname, u.avatar
    from myblog.t_blog b, myblog.t_type t,myblog.t_user u
    where b.type_id = t.id and  u.id = b.user_id order by b.create_time desc
</select>

<!--查询推荐文章-->
<select id="getAllRecommendBlog" resultType="com.star.queryvo.RecommendBlog">
    select * from myblog.t_blog where t_blog.recommend = true order by t_blog.create_time desc limit 4;
</select>

<!--搜索文章-->
<select id="getSearchBlog" resultMap="firstPageBlog">
    <bind name="pattern" value="'%' + query + '%'" />
    select b.id,b.title,b.first_picture, b.views,b.comment_count,b.update_time,b.description,
    t.name ,
    u.nickname, u.avatar
    from myblog.t_blog b, myblog.t_type t,myblog.t_user u
    where b.type_id = t.id and  u.id = b.user_id and (b.title like #{pattern} or b.content like  #{pattern})
    order by b.update_time desc
</select>

<!--统计博客信息-->
<select id="getBlogTotal" resultType="Integer">
    select count(*) from myblog.t_blog
</select>
<select id="getBlogViewTotal" resultType="Integer">
    select coalesce (sum(views),0) from myblog.t_blog
</select>
<select id="getBlogCommentTotal" resultType="Integer">
    select count(*) from myblog.t_comment
</select>
<select id="getBlogMessageTotal" resultType="Integer">
    select count(*) from myblog.t_message
</select>

注意:SQL语句有一个需要注意的地方

  • 搜索文章使用的是模糊查询
  • 本来使用的统计访问总数的SQL语句是 select sum(views) from myblog.t_blog
  • 发现当sum求和返回为null的时候,会报空指针异常
  • 所以改用 coalesce(sum(views),0),当sum求和为null的时候赋为0

Service及Impl

//查询首页最新博客列表信息
List<FirstPageBlog> getAllFirstPageBlog();

//查询首页最新推荐信息
List<RecommendBlog> getRecommendedBlog();

//搜索博客列表
List<FirstPageBlog> getSearchBlog(String query);

//统计博客总数
Integer getBlogTotal();

//统计访问总数
Integer getBlogViewTotal();

//统计评论总数
Integer getBlogCommentTotal();

//统计留言总数
Integer getBlogMessageTotal();
//查询首页最新博客列表信息
@Override
public List<FirstPageBlog> getAllFirstPageBlog() {
    return blogDao.getFirstPageBlog();
}

//查询首页最新推荐信息
@Override
public List<RecommendBlog> getRecommendedBlog() {
    List<RecommendBlog> allRecommendBlog = blogDao.getAllRecommendBlog();
    return allRecommendBlog;
}

//搜索博客列表
@Override
public List<FirstPageBlog> getSearchBlog(String query) {
    return blogDao.getSearchBlog(query);
}

//统计博客总数
@Override
public Integer getBlogTotal() {
    return blogDao.getBlogTotal();
}

//统计访问总数
@Override
public Integer getBlogViewTotal() {
    return blogDao.getBlogViewTotal();
}

//统计评论总数
@Override
public Integer getBlogCommentTotal() {
    return blogDao.getBlogCommentTotal();
}

//统计留言总数
@Override
public Integer getBlogMessageTotal() {
    return blogDao.getBlogMessageTotal();
}

Controller

package com.star.controller;


import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.star.entity.DetailBlog;
import com.star.entity.FirstPageBlog;
import com.star.entity.RecommendBlog;
import com.star.service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.List;

/**
 * 首页控制器
 */
@Controller
public class IndexController {

    @Autowired
    private BlogService blogService;

    //分页查询博客列表
    @GetMapping("/")
    public String index(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum,
                        RedirectAttributes attributes){
        PageHelper.startPage(pageNum,10);
        List<FirstPageBlog> firstPageBlogs = blogService.getFirstPageBlog();
        List<RecommendBlog> recommendBlogs = blogService.getAllRecommendBlog();
        PageInfo<FirstPageBlog> pageInfo = new PageInfo<>(firstPageBlogs);
        model.addAttribute("pageInfo",pageInfo);
        model.addAttribute("recommendedBlogs",recommendBlogs);
        return "index";
    }

    //搜索博客
    @GetMapping("search")
    public String search(Model model,
                         @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum,
                         @RequestParam String query){
        PageHelper.startPage(pageNum,1000);
        List<FirstPageBlog> firstPageBlogs = blogService.getFirstPageBlog();

        PageInfo<FirstPageBlog> pageInfo = new PageInfo<>(firstPageBlogs);
        model.addAttribute("pageInfo",pageInfo);
        model.addAttribute("query",query);
        return "search";
    }

    //博客信息统计
    @GetMapping("footer/blogmessage")
    public String blogMessage(Model model){
        int blogTotal = blogService.getBlogTotal();
        int blogViewTotal = blogService.getBlogViewTotal();
        int blogCommentTotal = blogService.getBlogCommentTotal();
        int blogMessageTotal = blogService.getBlogMessageTotal();

        model.addAttribute("blogTotal",blogTotal);
        model.addAttribute("blogViewTotal",blogViewTotal);
        model.addAttribute("blogCommentTotal",blogCommentTotal);
        model.addAttribute("blogMessageTotal",blogMessageTotal);

        return "index :: blogMessage";
    }

    //跳转博客详细界面
    @GetMapping("/blog/{id}")
    public String blog(@PathVariable Long id,Model model){
        DetailBlog detailBlog = blogService.getDetailedBlog(id);
        model.addAttribute("blog",detailBlog);
        return "blog";
    }
}

11. 博客详情页面显示

功能分析:

  • 文章内容
    • 跳转博客详情界面,返回文章详情和评论内容
  • 评论内容

博客详情实体类,需要有博客信息和类型信息

package com.star.entity;

import lombok.Data;

import java.util.Date;

@Data
public class DetailBlog {
    //博客信息
    private Long id;
    private String firstPicture;
    private String flag;
    private String title;
    private String content;
    private Integer views;
    private Integer commentCount;
    private Date updateTime;
    private boolean commentabled;
    private boolean shareStatement;
    private boolean appreciation;
    private String nickname;
    private String avatar;
    //分类名称
    private String typeName;
}

添加MarkDown编辑器工具类

<dependency>
    <groupId>com.atlassian.commonmark</groupId>
    <artifactId>commonmark</artifactId>
    <version>0.10.0</version>
</dependency>

<dependency>
    <groupId>com.atlassian.commonmark</groupId>
    <artifactId>commonmark-ext-heading-anchor</artifactId>
    <version>0.10.0</version>
</dependency>

<dependency>
    <groupId>com.atlassian.commonmark</groupId>
    <artifactId>commonmark-ext-gfm-tables</artifactId>
    <version>0.10.0</version>
</dependency>
package com.star.util;

import org.commonmark.Extension;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
import org.commonmark.node.Link;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.AttributeProviderContext;
import org.commonmark.renderer.html.AttributeProviderFactory;
import org.commonmark.renderer.html.HtmlRenderer;

import java.util.*;

public class MarkdownUtils {
    public static String markdownToHtml(String markdown) {
        Parser parser = Parser.builder().build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder().build();
        return renderer.render(document);
    }

    /**
     * 增加扩展[标题锚点,表格生成]
     * Markdown转换成HTML
     * @param markdown
     * @return
     */
    public static String markdownToHtmlExtensions(String markdown) {
        //h标题生成id
        Set<Extension> headingAnchorExtensions = Collections.singleton(HeadingAnchorExtension.create());
        //转换table的HTML
        List<Extension> tableExtension = Arrays.asList(TablesExtension.create());
        Parser parser = Parser.builder()
                .extensions(tableExtension)
                .build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder()
                .extensions(headingAnchorExtensions)
                .extensions(tableExtension)
                .attributeProviderFactory(new AttributeProviderFactory() {
                    public AttributeProvider create(AttributeProviderContext context) {
                        return new CustomAttributeProvider();
                    }
                })
                .build();
        return renderer.render(document);
    }

    /**
     * 处理标签的属性
     */
    static class CustomAttributeProvider implements AttributeProvider {
        @Override
        public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
            //改变a标签的target属性为_blank
            if (node instanceof Link) {
                attributes.put("target", "_blank");
            }
            if (node instanceof TableBlock) {
                attributes.put("class", "ui celled table");
            }
        }
    }

}

持久层接口:详情显示

//查询博客详情
DetailedBlog getDetailedBlog(Long id);

//文章访问更新
int updateViews(Long id);

//根据博客id查询出评论数量
int getCommentCountById(Long id);

xml文件

<resultMap id="detailedBlog" type="com.star.queryvo.DetailedBlog">
    <id property="id" column="bid"/>
    <result property="firstPicture" column="first_picture"/>
    <result property="flag" column="flag"/>
    <result property="title" column="title"/>
    <result property="content" column="content"/>
    <result property="typeName" column="name"/>
    <result property="views" column="views"/>
    <result property="commentCount" column="comment_count"/>
    <result property="updateTime" column="update_time"/>
    <result property="commentabled" column="commentabled"/>
    <result property="shareStatement" column="share_statement"/>
    <result property="appreciation" column="appreciation"/>
    <result property="nickname" column="nickname"/>
    <result property="avatar" column="avatar"/>
</resultMap>

<!--博客详情查询-->
<select id="getDetailedBlog" resultMap="detailedBlog">
    select b.id 			 bid,b.first_picture,b.flag,b.title,b.content,b.views,b.comment_count,b.update_time,b.commentabled,b.share_statement,b.appreciation,    u.nickname,u.avatar,t.name
    from myblog.t_blog b,myblog.t_user u, myblog.t_type t
    where b.user_id = u.id and b.type_id = t.id and b.id = #{id}
</select>

<!--文章访问自增-->
<update id="updateViews" parameterType="com.star.entity.Blog">
    update myblog.t_blog b set b.views = b.views+1 where b.id = #{id}
</update>

<!--查询出文章评论数量并更新-->
<update id="getCommentCountById" parameterType="com.star.entity.Blog">
    update myblog.t_blog b set b.comment_count = (
    select count(*) from myblog.t_comment c where c.blog_id = #{id} and b.id = #{id}
    ) WHERE b.id = #{id}
</update>

持久层接口:添加博客

添加博客,访问数量自增,评论数更新

//查询博客详情
DetailedBlog getDetailedBlog(Long id);

ServiceImpl新增实现类

@Override
public DetailedBlog getDetailedBlog(Long id) {
    DetailedBlog detailedBlog = blogDao.getDetailedBlog(id);
    if (detailedBlog == null) {
        throw new NotFoundException("该博客不存在");
    }
    String content = detailedBlog.getContent();
    detailedBlog.setContent(MarkdownUtils.markdownToHtmlExtensions(content));
    //文章访问数量自增
    blogDao.updateViews(id);
    //文章评论数量更新
    blogDao.getCommentCountById(id);
    return detailedBlog;
}

Controller

//跳转博客详情页面
@GetMapping("/blog/{id}")
public String blog(@PathVariable Long id, Model model) {
    DetailedBlog detailedBlog = blogService.getDetailedBlog(id);
    model.addAttribute("blog", detailedBlog);
    return "blog";
}

12. 其他页面显示

功能分析:

  • 查询出所有分类
  • 查询出该分类下的文章数目

持久层

//查询所有分类	TypeDao
List<Type> getAllTypeAndBlog();
//根据TypeId查询博客列表,显示在分类页面	BlogDao
List<FirstPageBlog> getByTypeId(Long typeId);

xml文件

<resultMap id="type" type="com.star.entity.Type">
    <id property="id" column="tid"/>
    <result property="name" column="name"/>
    <collection property="blogs" ofType="com.star.entity.Blog">
        <id property="id" column="bid"/>
        <result property="title" column="title"/>
        <result property="typeId" column="type_id"/>
    </collection>
</resultMap>
<!--查询分类-->
<select id="getAllTypeAndBlog" resultMap="type">
    select t.id tid, t.name, b.id bid, b.title,b.type_id
    from myblog.t_type t,myblog.t_blog b
    where t.id = b.type_id
</select>
<!--根据TypeId查询博客列表,显示在分类页面-->
<select id="getByTypeId" resultMap="firstPageBlog">
    select b.id,b.title,b.first_picture, b.views, b.comment_count, b.update_time, b.description,t.name ,u.nickname, u.avatar
    from myblog.t_blog b, myblog.t_type t,myblog.t_user u
    where b.type_id = t.id and u.id = b.user_id and b.type_id = #{typeId} order by b.update_time desc
</select>

业务层

@Transactional
@Override
public List<Type> getAllTypeAndBlog() {
    return typeDao.getAllTypeAndBlog();
}
//分类页面查询
@Override
public List<FirstPageBlog> getByTypeId(Long typeId) {
    return blogDao.getByTypeId(typeId);
}

Controller

package com.star.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.star.entity.Type;
import com.star.queryvo.FirstPageBlog;
import com.star.service.BlogService;
import com.star.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * @Description: 分类页面控制器
 * @Date: Created in 10:03 2020/6/24
 * @Author: ONESTAR
 * @QQ群: 530311074
 * @URL: https://onestar.newstar.net.cn/
 */
@Controller
public class TypeShowController {

    @Autowired
    private TypeService typeService;

    @Autowired
    private BlogService blogService;

    //    分页查询分类
    @GetMapping("/types/{id}")
    public String types(@RequestParam(defaultValue = "1",value = "pageNum") Integer pageNum, @PathVariable Long id, Model model) {
        List<Type> types = typeService.getAllTypeAndBlog();

        //id为-1表示从首页导航栏点击进入分类页面
        if (id == -1) {
            id = types.get(0).getId();
        }
        model.addAttribute("types", types);
        List<FirstPageBlog> blogs = blogService.getByTypeId(id);

        PageHelper.startPage(pageNum, 10000);
        PageInfo<FirstPageBlog> pageInfo = new PageInfo<>(blogs);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("activeTypeId", id);
        return "types";
    }

}

其他页面Controller层直接配置即可

时间轴

package com.star.controller;

import com.star.queryvo.BlogQuery;
import com.star.service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

/**
 * @Description: 时间轴页面显示控制器
 */
@Controller
public class ArchiveShowController {

    @Autowired
    private BlogService blogService;

    @GetMapping("/archives")
    public String archive(Model model){
        List<BlogQuery> blogs = blogService.getAllBlog();
        model.addAttribute("blogs", blogs);
        return "archives";
    }

}

音乐盒

package com.star.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @Description: 音乐盒页面显示控制器
 */
@Controller
public class MusicShowController {

    @GetMapping("/music")
    public String about() {
        return "music";
    }

}

友人帐

package com.star.controller;

import com.star.service.FriendLinkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @Description: 友链显示控制器
 * @Date: Created in 21:12 2020/6/27
 * @Author: ONESTAR
 * @QQ群: 530311074
 * @URL: https://onestar.newstar.net.cn/
 */
@Controller
public class FriendsShowController {

    @Autowired
    private FriendLinkService friendLinkService;

    @GetMapping("/friends")
    public String friends(Model model) {
        model.addAttribute("friendlinks",friendLinkService.listFriendLink());
        return "friends";
    }

}

照片墙

package com.star.controller;


import com.star.service.PictureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PictureShowController {
    @Autowired
    private PictureService pictureService;

    @GetMapping("/picture")
    public String pictures(Model model){
        model.addAttribute("pictures",pictureService.listPicture());
        return "picture";
    }
}

关于我

package com.star.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AboutShowController {

    @GetMapping("/about")
    public String about(){
        return "about";
    }
}

13. 注册界面

功能分析:

  • 需要有一个register页面来注册用户,而不是单纯的在本机数据库上添加用户
  • 用户对应User类,存入数据库的密码应该是MD5加密以后的密码

功能实现

  • 我们需要将前台用户输入的密码传输给后端,后端拿到密码后运行MD5加密
  • 然后将加密后的密码存入数据库,每次用户登陆时,将用户输入的密码加密,与数据库中的密码比较

Dao层

/**
* 返回插入是否成功 0/1
* @param user  用户对线
* @return
*/
int  insert(User user);

xml文件

<insert id="insert" parameterType="com.star.entity.User">
    insert into myblog.t_user(avatar, create_time, email, nickname, password, type, update_time, username)
    values (#{avatar},#{createTime},#{email},#{nickname},#{password},#{type},#{updateTime},#{username});
</insert>

业务层

int  insert(User user);
@Override
public int insert(User user) {
    user.setCreateTime(new Date());
    user.setUpdateTime(new Date());
    String realPassword = MD5Utils.code(user.getPassword());
    user.setPassword(realPassword);
    return userDao.insert(user);
}

Controller

//跳转到注册界面
@GetMapping("/registerPage")
public String registerPage(){
    return "admin/register";
}

/**
 * 根据user对象,注册用户
 * @param user  user由前端传入部分,业务层设置时间
 * @return
 */
@PostMapping("/register")
public String register(User user){
    int r = userService.insert(user);
    //System.out.println(user.toString());
    if(r == 0){
       return "error";
    }else{
       return "redirect:/admin";
    }
}

前端代码:register.html

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<!-- 你的HTML代码  th:action="@{/admin/register}" th:object="${user}" method="post"-->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客管理登录</title>
    <link href="../static/images/favicon.ico" th:href="@{/images/me.jpg}" rel="icon" type="image/x-ico">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
    <link rel="stylesheet" href="../../static/css/me.css" th:href="@{/css/me.css}">
    <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
</head>
<body>

<br>
<br>
<br>
<div class="m-container-small m-padded-tb-massive" style="max-width: 30em !important;">
    <div class="ur container">
        <div class="ui middle aligned center aligned grid">
            <div class="column">
                <h2 class="ui teal image header">
                    <div class="content">
                        管理后台注册
                    </div>
                </h2>

                <form class="layui-form" action="/admin/register" method="post" >
                    <div class="layui-form-item">
                        <label class="layui-form-label">用户名</label>
                        <div class="layui-input-block">
                            <input type="text" name="username" required  lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <label class="layui-form-label">密码</label>
                        <div class="layui-input-block">
                            <input type="password" name="password" required lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <label class="layui-form-label">昵称</label>
                        <div class="layui-input-block">
                            <input type="text" name="nickname" required  lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <label class="layui-form-label">邮箱</label>
                        <div class="layui-input-block">
                            <input type="text" name="email" required  lay-verify="required" placeholder="请输入邮箱" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <label class="layui-form-label">头像</label>
                        <div class="layui-input-block">
                            <input type="text" name="avatar" required  lay-verify="required" placeholder="请输入头像地址" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <label class="layui-form-label">类型</label>
                        <div class="layui-input-block">
                            <input type="text" name="type" required  lay-verify="required" placeholder="请输入类型" autocomplete="off" class="layui-input">
                        </div>
                    </div>

                    <div class="layui-form-item">
                        <div class="layui-input-block">
                            <button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
                            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                        </div>
                    </div>


                </form>
            </div>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>
<script th:src="@{/layui/layui.js}"></script>
<script>
    //Demo
    layui.use('form', function(){
        var form = layui.form;
        console.log(form);
        //监听提交
        form.on('submit(formDemo)', function(data){
            var message =  JSON.stringify(data.field);
            var $;
            $.ajax({
                url:"/admin/register",
                async: false,
                type:"POST",
                contentType: "application/json",
                data: message,
                // success: function(data){
                //     console.log(123);
                // }
            })
            return false;
        });
    });
</script>
</body>


</html>

14. 邮箱注册

功能分析:

  • 使用qq邮箱登陆,开启qq登陆的用户设置-账户-POP3服务-开启
  • 得到一串字符串,放入application.yml中
  • 引入相应的jar包,然后写出邮件传输的业务层代码
  • 写出用户账户实体类,业务层,xml文件
  • 实体类通过邮箱注册,status为0,代表未激活,status为1,代表已激活
  • 用户需要通过点击链接激活
  • 在LoginController中添加一层判断,取两个表中的并集作为账户

maven文件

<!--邮件-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

邮件发送业务层

package com.star.service;


public interface IMailService {
    /**
     * 发送文本邮件
     * @param to    收件人
     * @param subject   主题
     * @param content   内容
     */
    void sendSimpleMail(String to,String subject,String content);

    /**
     * 发送HTML邮件
     * @param to    收件人
     * @param subject   主题
     * @param content   内容
     */
    public void sentHtmlMail(String to,String subject,String content);

    /**
     * 发送带附件的邮件
     * @param to    收件人
     * @param subject   主题
     * @param content   内容
     * @param filePath  附件
     */
    public void sendAttachmentsMail(String to,String subject,String content,String filePath);

}
package com.star.service.impl;

import com.star.service.IMailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
@Service
public class IMailServiceImpl implements IMailService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private JavaMailSender mailSender;
    /**
     * 配置文件中我的qq邮箱
     */
    @Value("${spring.mail.from}")
    private String from;

    /**
     * 简单邮件
     * @param to    收件人
     * @param subject   主题
     * @param content   内容
     */
    @Override
    public void sendSimpleMail(String to, String subject, String content) {
        //SimpleMailMessage对象
        SimpleMailMessage message = new SimpleMailMessage();
        //邮件发送人
        message.setFrom(from);
        //邮件接收人
        message.setTo(to);
        //邮件主题
        message.setSubject(subject);
        //邮件内容
        message.setText(content);
        //发送邮件
        mailSender.send(message);
    }

    /**
     * HTML邮件
     * @param to    收件人
     * @param subject   主题
     * @param content   内容
     */
    @Override
    public void sentHtmlMail(String to, String subject, String content) {
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper messageHelper;
        try{
            messageHelper = new MimeMessageHelper(message,true);
            //邮件发送人
            messageHelper.setFrom(from);
            //邮件接收人
            messageHelper.setTo(to);
            //邮件主题
            message.setSubject(subject);
            //邮件内容,html格式
            messageHelper.setText(content, true);
            //发送
            mailSender.send(message);
            //日志信息
            logger.info("邮件已经发送。");
        } catch (MessagingException e) {
            logger.error("发送邮件时发生异常!", e);
        }
    }

    /**
     * 带附件的邮件
     * @param to    收件人
     * @param subject   主题
     * @param content   内容
     * @param filePath  附件
     */
    @Override
    public void sendAttachmentsMail(String to, String subject, String content, String filePath) {
        MimeMessage message = mailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);

            FileSystemResource file = new FileSystemResource(new File(filePath));
            String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
            helper.addAttachment(fileName, file);
            mailSender.send(message);
            //日志信息
            logger.info("邮件已经发送。");
        } catch (MessagingException e) {
            logger.error("发送邮件时发生异常!", e);
        }
    }
}

用户实体类

package com.star.entity;

import lombok.Data;

@Data
public class UserMail {
    private Integer id;
    private String username;
    private String password;
    private String useremail;
    private Integer status;
    private String code;
    private String avatar;
}

持久层

package com.star.dao;

import com.star.entity.UserMail;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserMailDao {

    void register(UserMail userMail);

    UserMail checkCode(String code);

    void updateUserStatus(UserMail userMail);

    UserMail loginUserMail(UserMail userMail);
}

业务层

package com.star.service;

import com.star.entity.UserMail;

public interface UserMailService {
    void register(UserMail userMail);

    UserMail checkCode(String code);

    void updateUserStatus(UserMail userMail);

    UserMail loginUserMail(UserMail userMail);
}
package com.star.service.impl;

import com.star.dao.UserMailDao;
import com.star.entity.UserMail;
import com.star.service.IMailService;
import com.star.service.UserMailService;
import com.star.util.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserMailServiceImpl implements UserMailService {

    @Autowired
    private UserMailDao userMailDao;

    @Autowired
    private IMailService iMailService;
    /**
     * 用户注册,同时发送一封激活邮件
     * @param userMail
     */
    @Override
    public void register(UserMail userMail) {
        userMailDao.register(userMail);
        //获取激活码
        String code = userMail.getCode();
        System.out.println("code: "+code);
        //主题
        String subject = "来自博客的激活邮件";
        //正文
        String context = "<a href=\"/mail/checkCode?code="+code+"\">激活请点击:"+code+"</a>";
        //发送
        iMailService.sentHtmlMail(userMail.getUseremail(),subject,context);
    }

    /**
     * 根据激活码code进行查询用户,之后修改状态
     * @param code
     * @return
     */
    @Override
    public UserMail checkCode(String code) {
        return userMailDao.checkCode(code);
    }

    /**
     * 激活账户,修改状态
     * @param userMail
     */
    @Override
    public void updateUserStatus(UserMail userMail) {

        userMailDao.updateUserStatus(userMail);
    }

    /**
     * 登陆
     * @param userMail
     * @return
     */
    @Override
    public UserMail loginUserMail(UserMail userMail) {
        UserMail userMail1 = userMailDao.loginUserMail(userMail);
        if(userMail1 != null){
            return userMail1;
        }
        return null;
    }
}

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.star.dao.UserMailDao" >

    <!--注册用户-->
    <insert id="register" parameterType="com.star.entity.UserMail" >
    insert into myblog.t_usermail ( username, password,useremail,status,code)
    values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{useremail,jdbcType=VARCHAR},
      #{status,jdbcType=INTEGER},#{code,jdbcType=VARCHAR})
  </insert>

    <!--根据激活码code查询用户-->
    <select id="checkCode" parameterType="String" resultType="com.star.entity.UserMail">
    select * from myblog.t_usermail where code = #{code}
  </select>

    <!--激活账户,修改用户状态-->
    <update id="updateUserStatus" parameterType="com.star.entity.UserMail">
    update myblog.t_usermail set status=1,code=null where id=#{id}
  </update>

    <!--登录,根据 status=1 进行查询-->
    <select id="loginUserMail" resultType="com.star.entity.UserMail" >
    select * from myblog.t_usermail where username=#{username} and password=#{password} and status=1
  </select>
</mapper>

LoginController文件修改

需要在以前判断 t_user表的基础上,再判断 t_usermail,取两个表符合条件的并集

添加以下代码

/**
 * @Description: 登录校验
 * @Param: username:用户名
 * @Param: password:密码
 * @Param: session:session域
 * @Param: attributes:返回页面消息
 * @Return: 登录成功跳转登录成功页面,登录失败返回登录页面
 */
@PostMapping("/login")
public String login(@RequestParam String username,
                    @RequestParam String password,
                    HttpSession session,
                    RedirectAttributes attributes){
    //System.out.println("------------username"+username);
    //System.out.println("------------password"+password);

    User user = userService.checkUser(username,password);
    UserMail userMail = userService.checkUserMail(username,password);
    UserMail RealUserMail = userMailService.loginUserMail(userMail);
    if(user!=null){
        session.setAttribute("user",user);
        return "admin/index";
    }else if(RealUserMail!=null){
        session.setAttribute("user",new User());
        return "admin/index";
    }else{
        attributes.addFlashAttribute("message","用户名或密码错误");
        return "redirect:/admin";
    }

邮箱用户登陆Controller

package com.star.controller;

import com.star.entity.UserMail;
import com.star.service.UserMailService;
import com.star.util.MD5Utils;
import com.star.util.UUIDUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping("/mail")
public class UserMailController {

    @Autowired
    private UserMailService userMailService;


    @RequestMapping(value = "/returnIndex")
    public String index(){
        return "test/index";
    }

    /**
     * 注册
     */
    @RequestMapping(value = "/registerUserMail")
    public String registerMail(UserMail userMail){
        String password = userMail.getPassword();
        String RealPassword = MD5Utils.code(password);
        userMail.setPassword(RealPassword);
        userMail.setStatus(0);
        String code = UUIDUtils.getUUID() + UUIDUtils.getUUID();
        userMail.setCode(code);
        userMailService.register(userMail);
        return "test/success";
    }

    /**
     * 检验邮箱中的code激活账户
     */
    @RequestMapping(value = "/checkCode")
    public String checkCode(String code){
        UserMail userMail = userMailService.checkCode(code);
        //System.out.println(userMail);
        if(userMail != null){
            userMail.setStatus(1);
            userMail.setCode("");
            userMailService.updateUserStatus(userMail);
        }
        return "admin/login";
    }

    /**
     * 跳转到登陆页面
     */
    @RequestMapping(value = "/loginPage")
    public String MailLoginPage(){
        return "admin/login";
    }


}

前端页面

  • index.html

  • <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head>
        <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
        <!-- 你的HTML代码  th:action="@{/admin/register}" th:object="${user}" method="post"-->
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>博客管理邮箱注册</title>
        <link href="../static/images/favicon.ico" th:href="@{/images/me.jpg}" rel="icon" type="image/x-ico">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
        <link rel="stylesheet" href="../../static/css/me.css" th:href="@{/css/me.css}">
        <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
    </head>
    <body>
    <!--<form action="/mail/registerUserMail" method="post">-->
    <!--    用户名:<input type="text" id="username" name="username"/><br>-->
    <!--    密码:<input type="password" id="password" name="password"/><br>-->
    <!--    邮箱:<input type="email" id="email" name="useremail"><br>-->
    <!--    <input type="submit" value="注册">-->
    <!--</form>-->
    <!--<a href="/templates/test/login.html">登录</a>-->
    <br>
    <br>
    <br>
    <div class="m-container-small m-padded-tb-massive" style="max-width: 30em !important;">
        <div class="ur container">
            <div class="ui middle aligned center aligned grid">
                <div class="column">
                    <h2 class="ui teal image header">
                        <div class="content">
                            管理后台邮箱注册
                        </div>
                    </h2>
    
                    <form class="layui-form" action="/mail/registerUserMail" method="post" >
                        <div class="layui-form-item">
                            <label class="layui-form-label">用户名</label>
                            <div class="layui-input-block">
                                <input type="text" name="username" required  lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">密码</label>
                            <div class="layui-input-block">
                                <input type="password" name="password" required lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">邮箱</label>
                            <div class="layui-input-block">
                                <input type="email" name="useremail" required  lay-verify="required" placeholder="请输入邮箱" autocomplete="off" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <div class="layui-input-block">
                                <button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
                                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                            </div>
                        </div>
    
    
            </form>
        </div>
    </div>
</div>

```html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册成功,请激活账户</title>
    <link href="../static/images/favicon.ico" th:href="@{/images/me.jpg}" rel="icon" type="image/x-ico">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
    <link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">
</head>
<body>




<br>
<br>
<br>

<div class="m-container-small m-padded-tb-massive">
    <div class="ui error message m-padded-tb-huge" >
        <div class="ui contianer">

            <p>已经发送邮件到您的邮箱,请点击邮件中的链接进行激活</p>
        </div>
    </div>
</div>
<div class="m-margin-top-max" align="center">
    <a href="#" th:href="@{/}">
        <button type="button" class="ui teal button m-mobile-wide"><i class="home icon"></i>返回首页</button>
    </a>
</div>
<br>
<br>
<br>
<br>


<!--底部栏-->
<footer id="waypoint" class="ui inverted vertical segment m-padded-tb-massive m-opacity">
    <div class="ui center aligned container">
        <div class="ui inverted divided stackable grid">
            <div class="three wide column">
                <div class="ui inverted link list">
                    <div class="item">
                        <img src="../static/images/oneStarWechat.jpg" th:src="@{/images/oneStarWechat.jpg}"  class="ui rounded image" alt="" style="width: 110px">
                    </div>
                </div>
            </div>
            <div class="four wide column" >
                <h4 class="ui inverted header m-text-thin m-text-spaced " >最新博客</h4>
                <div id="newblog-container">
                    <div class="ui inverted link list" th:fragment="newblogList">
                        <a href="#" th:href="@{/blog/{id}(id=${blog.id})}" target="_blank" class="item m-text-thin" th:each="blog : ${newblogs}" th:text="${blog.title}">最新博文</a>
                    </div>
                </div>
            </div>
            <div class="four wide column">
                <h4 class="ui inverted header m-text-thin m-text-spaced ">联系我</h4>
                <div class="ui inverted link list">
                    <a href="#" class="item m-text-thin">Email:7142220@qq.com</a>
                    <a href="#" class="item m-text-thin">QQ:7142220</a>
                </div>
            </div>
            <div class="five wide column">
                <h4 class="inverted header m-text-thin m-text-spaced">我的客栈已经营</h4>
                <p id="htmer_time" class="item m-text-thin">
            </div>
        </div>

    </div>

</footer>


<script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>

<script>
    $('#newblog-container').load(/*[[@{/footer/newblog}]]*/"/footer/newblog");

    // 运行时间统计
    function secondToDate(second) {
        if (!second) {
            return 0;
        }
        var time = new Array(0, 0, 0, 0, 0);
        if (second >= 365 * 24 * 3600) {
            time[0] = parseInt(second / (365 * 24 * 3600));
            second %= 365 * 24 * 3600;
        }
        if (second >= 24 * 3600) {
            time[1] = parseInt(second / (24 * 3600));
            second %= 24 * 3600;
        }
        if (second >= 3600) {
            time[2] = parseInt(second / 3600);
            second %= 3600;
        }
        if (second >= 60) {
            time[3] = parseInt(second / 60);
            second %= 60;
        }
        if (second > 0) {
            time[4] = second;
        }
        return time;
    }
    function setTime() {
        /*此处为网站的创建时间*/
        var create_time = Math.round(new Date(Date.UTC(2020, 01, 25, 15, 15, 15)).getTime() / 1000);
        var timestamp = Math.round((new Date().getTime() + 8 * 60 * 60 * 1000) / 1000);
        currentTime = secondToDate((timestamp - create_time));
        currentTimeHtml = currentTime[0] + '年' + currentTime[1] + '天'
            + currentTime[2] + '时' + currentTime[3] + '分' + currentTime[4]
            + '秒';
        document.getElementById("htmer_time").innerHTML = currentTimeHtml;
    }
    setInterval(setTime, 1000);
</script>
</body>
</html>
  • 上面是welcome.html

15. 手机验证码登陆

功能分析:

  • 使用手机验证码登陆,直接跳转到管理界面,但是不存储到数据库,只能维持这一次的登陆
  • 考虑到验证码的过期,可以使用redis来存储 手机号码-验证码的键值对
  • 使用阿里云的功能来开通短信的收发

功能技术栈:

  • Redis
  • 阿里云

首先测试短信的正常收发,使用PostMan

  • 短信收发,根据阿里云官方API改编
package com.star.util;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.star.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Random;

@Component
public class SendNoteUtil {
    @Autowired
    private RedisService redisService;
    @Value("${redis.key.prefix.authCode}")
    private String REDIS_KEY_PREFIX_AUTH_CODE;
    @Value("${redis.key.expire.authCode}")
    private Long AUTH_CODE_EXPIRE_SECONDS;

    //验证平台信息 开发者无需任何更改
    private static final String dysmsapi = "dysmsapi.aliyuncs.com";

    DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "*******************", "**************************");
    IAcsClient client = new DefaultAcsClient(profile);
    //这一步的两个参数,一个是要发送验证码的手机号 一个是模板Code用来区分登录注册
    public String sendNoteMessgae(String PhoneNumbers){
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for(int i=0;i<6;i++){
            int x = random.nextInt(10);
            if(x == 0){
                i--;
                continue;
            }
            sb.append(x);
        }
        CommonRequest request = new CommonRequest();
        //request.setSysProtocol(ProtocolType.HTTPS);
        request.setSysMethod(MethodType.POST);
        request.putQueryParameter("RegionId", "cn-hangzhou");
        request.setSysDomain(dysmsapi);
        request.setSysVersion("2017-05-25");
        request.setSysAction("SendSms");
        request.putQueryParameter("PhoneNumbers", PhoneNumbers);//接受验证码的手机号
        request.putQueryParameter("SignName", "Halo博客");//签名

        //模板代码,我暂时用的参数,你可以直接写成模板码,模板码参考第八步
        request.putQueryParameter("TemplateCode", "SMS_205811339");

        //用户定义的验证码内容
        request.putQueryParameter("TemplateParam","{code:"+sb.toString()+"}");


        //验证码绑定手机号并存储到redis
        redisService.set(REDIS_KEY_PREFIX_AUTH_CODE + PhoneNumbers, sb.toString());
        redisService.expire(REDIS_KEY_PREFIX_AUTH_CODE + PhoneNumbers,AUTH_CODE_EXPIRE_SECONDS);
        try {
            CommonResponse response = client.getCommonResponse(request);
            String returnStr = response.getData();
            System.out.println(returnStr);
            JSONObject jsonObject = JSONObject.parseObject(returnStr);
            //返回的信息
            return jsonObject.getString("Message");
        } catch (ServerException e) {
            return e.getErrMsg();
        } catch (ClientException e) {
            return e.getErrMsg();
        }
    };

}

Redis和Mail的yml配置文件

spring:
  thymeleaf:
    mode: HTML
  profiles:
    active: pro
  messages:
    basename: i18n/messages
  redis:
    host: localhost
    database: 0
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
    timeout: 3000ms
  mail:
    host: smtp.qq.com #发送邮件服务器
    username: 7142220@qq.com #发送邮件的邮箱地址
    password:  atnrxkbmzzmnbijg #客户端授权码,不是邮箱密码,这个在qq邮箱设置里面自动生成的
    properties.mail.smtp.port: 465 #端口号465或587
    from: 7142220@qq.com # 发送邮件的地址,和上面username一致
    properties.mail.smtp.starttls.enable: true
    properties.mail.smtp.starttls.required: true
    properties.mail.smtp.ssl.enable: true
    default-encoding: utf-8

comment.avatar: /images/avatar.png
message.avatar: /images/avatar.png

#自定义redis key
redis:
  key:
    prefix:
      authCode: protal:authCode
    expire:
      authCode: 300

Redis业务层,工具类集成

整合RedisTemplate

package com.star.service;

public interface RedisService {
    /**
     * 存储数据
     */
    void set(String key, String value);

    /**
     * 获取数据
     */
    String get(String key);

    /**
     * 设置超期时间
     */
    boolean expire(String key, long expire);

    /**
     * 删除数据
     */
    void remove(String key);

    /**
     * 自增操作
     * @param delta 自增步长
     */
    Long increment(String key, long delta);

}
package com.star.service.impl;

import com.star.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;
@Service
public class RedisServiceImpl implements RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key,value);
    }

    @Override
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    @Override
    public boolean expire(String key, long expire) {
        return stringRedisTemplate.expire(key,expire, TimeUnit.SECONDS);
    }

    @Override
    public void remove(String key) {
        stringRedisTemplate.delete(key);
    }

    @Override
    public Long increment(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key,delta);
    }
}

在LoginController中添加代码

@Autowired
private SendNoteUtil sendNoteUtil;

@Autowired
private RedisService redisService;

@Value("${redis.key.prefix.authCode}")
private String REDIS_KEY_PREFIX_AUTH_CODE;

/**
 * 跳转界面
 */
@GetMapping("/api/note")
public String loginPage1(){
    return "redis/login";
}

/**
 * 获取验证码
 * @param phone
 * @param response
 */
@RequestMapping(value = "/api/note/sendNote/{phone}",method = RequestMethod.GET)
public void sendNote(@PathVariable("phone") String phone, HttpServletResponse response){
    System.out.println(phone);
    try {
        response.getWriter().write(sendNoteUtil.sendNoteMessgae(phone));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 *
 * @param phone
 * @param authCode
 * @return
 */

@RequestMapping(value = "/api/note/login/{phone}/{authCode}")
public String login(@PathVariable("phone") String phone, @PathVariable("authCode") String authCode){
    System.out.println("phonenumber--------------"+phone);
    MyX.phone1 = phone;
    MyX.authCode1 = authCode;
    //验证验证码
    if(!verifyAuthCode(authCode,phone)){
        //System.out.println("1222222222222222");
        return "redirect:/admin";
    }
    //System.out.println("123465789");
    return "admin/index";
}

/**
 * 对输入的验证码进行校验
 * 和Redis中的键值对进行比对即可
 * @param authCode
 * @param telephone
 * @return
 */
private boolean verifyAuthCode(String authCode, String telephone){
    if(StringUtils.isEmpty(authCode)){
        return false;
    }
    String realAuthCode = redisService.get(REDIS_KEY_PREFIX_AUTH_CODE + telephone);
    //System.out.println("real-------------"+realAuthCode);
    return authCode.equals(realAuthCode);
}
  • 可以在后端进行测试正常的短信发送,登陆服务,这里给出的是集成前端的完整代码

前端集成

  • 前端这里遇到了很多坑,首先,ajax异步请求在传递前端参数的时候,后端Controller层代码可以正常运行,但是无法跳转到指定的界面

  • 前端后端全都不报错,原因是因为ajax只是局部刷新,所以不能在后台接口进行页面的跳转

  • 所以我们只能在前端进行跳转 window.location.href="http://localhost:8080/admin/blogs";

  • 但是在本项目中,有拦截权限验证,我们无法通过前端直接跳转到管理界面,会显示没有权限

  • 那么我们该如何做?在这里博主给出一种方法

    • 定义一个全局类Myx,这里有公共变量供所有类使用

    • 在后端接收到前端的参数,在redis中完成验证,发现参数都对应正确后,直接把对应的值赋给Myx中的变量

    • 对拦截器类中判断的条件做出更改,本来判断条件为用户为空,现在改成用户为空并且两个参数都为空(如果不为空说明通过了Redis的验证)

    • package com.star.util;
      
      public class MyX {
          public static String phone1;
          public static String authCode1;
      
          public static String getPhone1() {
              return phone1;
          }
      
          public static void setPhone1(String phone1) {
              MyX.phone1 = phone1;
          }
      
          public static String getAuthCode1() {
              return authCode1;
          }
      
          public static void setAuthCode1(String authCode1) {
              MyX.authCode1 = authCode1;
          }
      }
      <!--93-->
    • 这样就跳转到了后端管理

  • 前端代码:

  • <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>手机号登录</title>
        <link href="../static/images/favicon.ico" th:href="@{/images/me.jpg}" rel="icon" type="image/x-ico">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
        <link rel="stylesheet" href="../../static/css/me.css" th:href="@{/css/me.css}">
        <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
    </head>
    <body>
    
    <br>
    <br>
    <br>
    <div class="m-container-small m-padded-tb-massive" style="max-width: 30em !important;">
        <div class="ur container">
            <div class="ui middle aligned center aligned grid">
                <div class="column">
                    <h2 class="ui teal image header">
                        <div class="content">
                            管理后台手机验证码登录
                        </div>
                    </h2>
                    <input class="layui-input" type="text" id="phone" placeholder="请输入手机号">
                    <br>
                    <input type="number" name="authCode" id="authCode"class="layui-input" placeholder="请输入验证码">
                    <br><br>
                        <input type="button" value="获取验证码" name="yzm" class="layui-btn layui-btn-radius layui-btn-warm" disabled="disabled" id="yzm">
                        <br><br>
                        <input type="submit" value="提交" name="nzm" class="layui-btn layui-btn-radius layui-btn-normal" id="nzm">
                </div>
            </div>
        </div>
    </div>
    
    <div>
    
    </div>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>
    
    
```

16. 服务器部署项目

  • 打开FinalShell连接云服务器
  • 后台运行jar包 nohup java -jar *****.jar &
  • 运行jar包所需要环境: redis , mysql , Java 8
  • Nginx反向代理开启后便可访问域名
  • [Fhawke的博客](http://81.70.168.126:8080/)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

教育平台开发所遇问题 Previous
CAP理论 Next