源码地址:
基于SpringBoot+Vue的个人博客开发-Java文档类资源-CSDN下载
项目演示地址:
基于Vue-SpringBoot的个人博客网站_哔哩哔哩_bilibili基于vue-springboot搭建的个人网站项目源码地址:https://github.com/chenalian/Blog-SpringBoot-Vue项目详解地址:https://blog.csdn.net/qq_41593124/article/details/123067458https://www.bilibili.com/video/BV1zL4y1g7oh?spm_id_from=333.999.0.0
1,自定义返回数据类型R
后端传递给前端的数据统一规范化处理
/**
* 自定义通用返回模板
* code:100为正常,非100为异常
* msg:文字描述信息
* data:携带的数据
*/
public class R extends HashMap {
public R(int code) {
this.put("code", code);
this.put("time", System.currentTimeMillis());
}
public static R success() {
return new R(100);
}
public static R success(String msg) {
R r = success();
r.put("msg", msg);
return r;
}
public static R error() {
return new R(200);
}
public static R error(String msg) {
R r = error();
r.put("msg", msg);
return r;
}
public static R error(int code, String msg) {
R r = new R(code);
r.put("msg", msg);
return r;
}
@SuppressWarnings("unchecked")
public R setAttribute(String key, Object value) {
String data = "data";
if (!this.containsKey(data)) {
this.put(data, new HashMap());
}
((HashMap) this.get(data)).put(key, value);
return this;
}
}
2,异常处理
通过@RestControllerAdvice注解设置全局异常处理来捕获springmvc抛出的异常,在通@ExceptionHandler注解具体到某个异常类。
模板
@RestControllerAdvice
public class ExceptionHandle {
@ExceptionHandler(自定义异常类.class)
public R aesException(自定义的异常 e) {
String message = e.getMessage();
return R.error(Constant.RESULT_CODE_BLOG_NOT_FOUND, message);
}
}
3,jackson的使用
jackson是json解析器,方便将对象转换成json,json转换成对象。com.fasterxml.jackson.databind.ObjectMapper实现对各种操作的封装。因为对数据的加密和解密一般是对json数据进行加密和解密,这就涉及到对象和json相互转换。
// JSON字符串转Map
@SuppressWarnings("unchecked")
public static HashMap jsonToMap(String json) throws TypeConverterException {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, HashMap.class);
} catch (JsonProcessingException e) {
throw new TypeConverterException("JsonToMap转换异常!");
}
}
// Map转JSON字符串
public static String mapToJson(Map data) throws TypeConverterException{
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(data);
} catch (JsonProcessingException e) {
throw new TypeConverterException("MapToJson转换异常!");
}
return json;
}
4,AOP实现加密和解码
对后端返回的数据进行加密,对前端传过来的数据进行解密(get和post两种不同的方式,一种不带有请求体requestbody,一种带有请求体requestbody)
加密操作
@Aspect
@Order(2)
@Component
public class AESEncryptAspect {
@Pointcut("execution(* site.hanzhe.controller..*.*(..))")// 对所有的controller方法返回的数据进行加密处理
public void pointcut() {}
@Around("pointcut()")
@SuppressWarnings("unchecked")
public Object decrypt(ProceedingJoinPoint point) throws Throwable {
// 获取到控制器返回结果
R r = (R) point.proceed(point.getArgs());
// 如果包含结果集,将结果集进行加密后返回
if (r.containsKey("data")) {
String json = BlogUtil.mapToJson((HashMap)r.get("data"));
String encrypt = BlogUtil.AESEncrypt(json);
r.remove("data");
r.setAttribute(Constant.SUCCESS_NAME, encrypt);
}
return r;
}
}
解密操作
get和delete方法,不带请求体
package site.hanzhe.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import site.hanzhe.exception.AESException;
import site.hanzhe.exception.TypeConverterException;
import site.hanzhe.utils.BlogUtil;
import java.util.HashMap;
/**
* Controller AES解密:GET、DELETE请求
* 由于AESOtherDecryptAspect实现的RequestBodyAdvice只针对拥有请求体的HTTP请求生效
* 而GET、DELETE请求不包含请求体,所以这里单独使用AOP进行解密
*/
@Aspect
@Order(1)
@Component
public class AESGetDeleteDecryptAspect {
// 切入点:只有使用了@AESDecrypt注解的GET请求才会执行解密
@Pointcut("@annotation(site.hanzhe.aspect.AESDecrypt) && " +
"(@annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping)) && " +
"execution(* site.hanzhe.controller..*.*(..))")
public void pointcut() { }
@Around("pointcut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取到请求的参数列表进行解密
Object[] args = joinPoint.getArgs();
this.decrypt(args);
// 执行将解密的结果交给控制器进行处理,并返回处理结果
return joinPoint.proceed(args);
}
// 解密方法
@SuppressWarnings("unchecked")
public void decrypt(Object[] args) throws AESException, TypeConverterException {
// 获取请求参数并转换为字符串(密文)
HashMap data = (HashMap) args[0];
String encrypt = data.get("json").toString();
// 将密文解密为JSON字符串
String json = BlogUtil.AESDecrypt(encrypt);
// 将JSON字符串转换为Map集合,并替换原本的参数
args[0] = BlogUtil.jsonToMap(json);
}
}
解密 put和post方法,带请求体
package site.hanzhe.aspect;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import site.hanzhe.exception.AESException;
import site.hanzhe.utils.BlogUtil;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.HashMap;
/**
* Controller AES解密:POST、PUT请求
* 1. 使用@ControllerAdvice注解扫描Controller所在位置
* 2. 实现RequestBodyAdvice接口来处理参数
*
* 经测试:RequestBodyAdvice执行优先级高于AOP
*/
@ControllerAdvice("site.hanzhe.controller")
public class AESPostPutDecryptAspect implements RequestBodyAdvice {
// 判断当前Controller是否需要进行参数解密
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) {
// 只有标识了AESDecrypt注解的控制器才需要解密
return methodParameter.hasMethodAnnotation(AESDecrypt.class);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) throws IOException {
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
String json = null;
try {
InputStream body = httpInputMessage.getBody();
HashMap map = BlogUtil.jsonToMap(IOUtils.toString(body));
String encrypt = map.get("json").toString();
json = BlogUtil.AESDecrypt(encrypt);
} catch (Exception e) {
// TODO 异常处理待考究
}
return IOUtils.toInputStream(json);
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
};
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) {
return o;
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) {
return o;
}
}
5,项目路径与服务器路径映射配置
相当于为一个绝对路径起一个短一点的别名,便于前后端访问。
@Configuration
public class ResourceConfigAdapter extends WebMvcConfigurerAdapter {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 添加图片映射(可以配置系统目录映射到项目目录)
// 根据不同的系统设置不同的图片存储路径
//获取文件的真实路径
String os = System.getProperty("os.name");
String winPath = System.getProperty("user.dir")+"\\src\\main\\resources\\static\\static\\blogImage\\";
//在Linux环境下的图片存放资源路径
String linuxPath = "/usr/local/alian/images/";
if (os.toLowerCase().startsWith("win")) { //windows系统
// 设置项目真实路径
registry.addResourceHandler("/static/blogImage/**").addResourceLocations("file:"+winPath);
}
else{// linux系统
registry.addResourceHandler("/images/**").
addResourceLocations("file:"+linuxPath);
}
//获取文件的真实路径
}
}
6,访问请求设置
是否支持跨域,对请求头进行设置
package site.hanzhe.filters;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
/**
* 1. 这里切记不能用@Component或者其他注解添加到IOC容器,会造成二次过滤,
* 要在配置类中使用@ServletComponentScan进行扫描
*
* 2. 过滤路径中的通配符使用单星号*即可,不可使用双星号**
*/
@Order(1)
@WebFilter("/api/*")
public class CorsFilter implements Filter {
//对每次请求拦截两次
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
resp.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
resp.setHeader("Access-Control-Allow-Headers", "Content-Type,XFILENAME,XFILECATEGORY,XFILESIZE,token");
// 允许携带cookie
resp.setHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(req, resp);
}
}
7,对管理员方法进行验证token
/*
*
*
* 自定义token验证
* 根据成功验证了的用户名和密码生成token
* token存储在redis中,且返回给前端
* 前端每次传输时候携带token和redis中进行匹配
*
*
* */
@Order(1)
@WebFilter("/api/admin/*")
public class TokenFilter implements Filter {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String token = req.getHeader("token");
// 在redis判断是否存在
Set token1 = redisTemplate.opsForSet().members("token");
boolean flag=token1.contains(token);
if(token==null||!flag)
{
R r = R.error(Constant.RESULT_CODE_TOKEN_ERROR, "请先登录");
this.refuse(resp, r);
return;
}
// 刷新token生存时间
redisTemplate.opsForSet().add("token",token,30, TimeUnit.MINUTES);
// 判断
chain.doFilter(req, resp);
}
public void refuse(HttpServletResponse resp, R r) {
resp.setCharacterEncoding("UTF-8");
try {
String json = BlogUtil.mapToJson(r);
resp.getWriter().write(json);
} catch (IOException | TypeConverterException e) {
e.printStackTrace();
}
}
}
- 1,git clone 项目源码地址
- 2,导入数据库文件
- 3,编辑application.yml文件
- 配置mysql和redis的ip和用户名密码
- 4,启动后端项目
- 5,命令行打开blog-Front
- npm install //安装项目依赖
- npm run dev // 启动前端项目
- 打开浏览器,输入:http://localhost:8100/
- 方法一:采用webpack打包的方式将vue项目打包放在springboot项目resource文件夹下面。
- 1,先要修改blog-front项目下面的constant.js配置文件,启动前后端不分离的REQUEST_BASE_URL,然后将blog-front进行打包,打开命令行输入npm run build,将会生成dist文件夹
- ,2,将dist文件夹下面的文件进行复制到springboot项目resource文件夹下面的static文件下面。
- 3,打开Idea点击package打包,并生成Blog.jar
- 4,通过Xftp将jar包传入linux系统,
- LInux中要配置好和本地一样的mysql和redis
- 5,再输入,java -jar blog.jar
- 打开本地的浏览器输入linux的IP+端口,就可以成功访问。
- 如果需要让jar在linux后台运行,输入 nohup java -jar blog.jar就行;
- 6,如何让博客系统随着linux服务器开机自启动?
- 创建自定义脚本,为自定义脚本条件执行权限,在/etc/rc.d/rc.local文件中添加脚本路径,为rc.local添加执行权限。
自定义脚本 #!/bin/sh #alianblog自启动脚本 source /etc/profile nohup java -jar /usr/local/alian/blog.jar > /usr/local/alian/sh/base.log 2>&1 & 添加权限 chmod +x blogAutoStart.sh 在rc.local添加自定义脚本路径/usr/local/alian/sh/blogAutoStart.sh 为rc.local添加执行权限 chmod +x /etc/rc.d/rc.local
(让外网用户可以访问到自己本地Linux服务器上的网站)
操作步骤
- 工具下载网址,先下载好客户端工具。
- 注册信息,进行购买隧道,有两条免费的隧道。
- 采用命令行的方式输入 ./natapp -authtoken=你的token,就可以得到一个公网链接,然后将公网链接发送给小伙伴就能看到你的个人博客了。
- 前后端数据传输采用ASE进行加密传输,前端通过修改发送请求头将明文进行加密,后端通过Aop对数据进行加密和解密;
- token令牌,通过username加密生成token,存入redis并设置生存时间;
- 图片上传存储位置,windows部署和linux部署均可以存储,需要配置路径映射;
- 跨域设置;