博客系统开发流程
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) 注解表示资源找不到的状态码,标识了状态码的时候就不拦截 */ (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、调用的方法、传递的参数、返回的内容,并记录到日志 */ public class LogAspect { //获取日志信息 private final Logger logger = LoggerFactory.getLogger(this.getClass()); //定义切面 ("execution(* com.star.controller.*.*(..))") public void log(){} //在切面之前执行 ("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文件
<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; public class UserServiceImpl implements UserService { private UserDao userDao; 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 { 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
<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-->
这样就跳转到了后端管理
前端代码:
<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 协议 ,转载请注明出处!