在之前的一个文章中,我大致的介绍如果利用vue构建一个前端的成果物,然后在后端的springboot中部署。
在这个例子中,我们的前端代码和后端代码被打成了统一的jar,然后使用java命令启动,这就uhi导致一个问题。当我们只需要更新前端代码时,会需要连同后端一起打包,并从新部署和重启。这种情况下是由风险的,完全比不上nginx作为代理的直接更新。
所以想实现一个既可以像前后端不分离部署那样使用相同的上下文访问,又可以像nignx部署那样可单独更新。虽然这在现实情况下,机会没有应用的地方。但是谁让我想到了,那我就想试试可不可以。
基于这个要求,就想如果可以实时更新且不影响后端代码,那就不能打成java包,又要同样的上下文访问。那就只能java自己返回前端的文件了。所以,只要指定好前端成果物文件的路径,然后通过指定的url就可以返回给前端这些文件。在需要跟新时,只需要更新这个文件夹即可。
这样就得到这样的代码:
获取前端文件的url:/context/web/**
@RestController
@RequestMapping("/web")
public class IndexController {
private static final CoreLogger LOGGER = CoreLoggerFactory.getLogger(IndexController.class);
/**
* 首页的访问
* @param response 首页的html的流
*/
@GetMapping({"/", "/index", "index.html"})
public ResponseEntity index(HttpServletResponse response){
response.setHeader("Content-Type", "text/html;charset=UTF-8");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
//headers.add("Transfer-Encoding", "chunked");
String name = "D:\\code\\self\\gitee\\core-manage-test-front\\dist\\index.html";
File file = new File(name);
try {
ResponseEntity result = new ResponseEntity(FileUtil.readAsByteArray(file), headers, HttpStatus.OK);
return result;
} catch (IOException e) {
LOGGER.error("访问首页错误", e);
}
return null;
}
/**
* 文件的访问路径
* @return 文件流
*/
@GetMapping("/static/**")
public ResponseEntity getJsResource(HttpServletRequest request){
String uri = request.getServletPath();
LOGGER.info("获取文件:{}", uri);
//截取/web/static后面的部分
String filePath = uri.substring(11);
// 创建File对象,表示要下载的文件
File file = new File("D:\\code\\self\\gitee\\core-manage-test-front\\dist" + filePath);
String fileType = uri.substring(uri.lastIndexOf(".") + 1);
// 将File转换为InputStream
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 创建InputStreamResource对象,将其作为文件流返回
InputStreamResource resource = new InputStreamResource(inputStream);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, WebFileUtils.getContentType(fileType));
headers.add("Vary","Access-Control-Request-Headers");
headers.add("Vary","Access-Control-Request-Method");
headers.add("Vary","Origin");
// 创建ResponseEntity对象,将InputStreamResource作为响应体返回
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.body(resource);
}
}
由于我们返回的是文件流,且浏览器要知道是什么的文件,所以我们要指定好返回接口的Content-Type:如果发现由其他的没有被定义的,需要持续的补充。
/**
* 前端文件类型,对应的返回的 Content-Type
*/
public class WebFileUtils {
private static Map webContentType = new HashMap<>();
static {
//html文件
webContentType.put("html", MediaType.TEXT_HTML_VALUE);
//css文件
webContentType.put("css", "text/css");
//js文件
webContentType.put("js", "application/javascript");
//图片类型
webContentType.put("jpg", "image/jpeg");
webContentType.put("tiff", "image/tiff");
webContentType.put("gif", "image/gif");
webContentType.put("jfif", "image/jpeg");
webContentType.put("png", "image/png");
webContentType.put("tif", "image/tiff");
webContentType.put("ico", "image/x-icon");
webContentType.put("jpeg", "image/jpeg");
webContentType.put("wbmp", "image/vnd.wap.wbmp");
webContentType.put("fax", "image/fax");
webContentType.put("net", "image/pnetvue");
webContentType.put("jpe", "image/jpeg");
webContentType.put("rp", "image/vnd.rn-realpix");
}
public static String getContentType(String fileType){
return webContentType.get(fileType);
}
}
通过以上的代码,只要是通过 /context/web/**发送过来的接口都当作是前端文件的请求。
前端按照正常新建一个vue项目,需要注意的是以下几个方面:
1. publicPath的配置,由于我们是访问后端拿的前端文件,假设我们后端项目的上下文是 /test1,则publicPath应该为:
publicPath: process.env.NODE_ENV === 'production' ? '/test1/web/static' : '/',
如果,没有上下文,就应该是:
publicPath: process.env.NODE_ENV === 'production' ? '/web/static' : '/',
其实经过以上配置,基本上就可以访问到后端页面了,但是也有一个不足的地方,就是我们浏览器的标签页没有展示我们设置的图片信息。
public/index.html的配置了这个图片:
虽然我也不知知道为什么没有去后端获取这个图片信息。但是既然你不取,那我就把你改成可以去获取的就行。 所以我在src下面创建了一个static文件,这里存了图片的地址。这里不可以放到asserts下,至于原因后面说明。然后在项目build的时候,将static中的文件复制到成果物的img下方。
复制的方法是利用插件:copy-webpack-plugin
在vue.config.js中做配置:
const {defineConfig} = require('@vue/cli-service')
const path = require('path');
//静态文件的复制操作
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = defineConfig({
//...忽略原来的其他配置
//复制static下面的文件
configureWebpack: {
plugins: [
new CopyWebpackPlugin([
// ./src/static是一个静态的文件,我们直接使用copy-webpack-plugin插件将文件复制到我们打包后的路径中,from为需要复制的文件的路径,还有一个to属性是复制到哪里的路径,不写则默认复制到打包后文件的根目录。
{
from: path.resolve(__dirname, './src/static'),
to: path.resolve(__dirname, './dist/img')
}
])
]
}
})
public/index.html修改如下:加上了img的路径
如此大功告成。只需要i将这个成果物放到指定的文件夹下,就能实时更新了。也嫩通过后端访问了。
这里附上vueconfig.js的完整代码:
const {defineConfig} = require('@vue/cli-service')
const path = require('path');
//静态文件的复制操作
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = defineConfig({
transpileDependencies: true,
publicPath: process.env.NODE_ENV === 'production' ? '/test1/web/static' : '/',
devServer: {
//以上的ip和端口是我们本机的;下面为需要跨域的
proxy: {
//由于前后端会部署到一台机器上,所以只要端口确定了就能保证可以访问到后端,IP用localhost就行
'/': {
ws: false,
target: 'http://localhost:9998',
changeOrigin: true,
pathRewrite: {
'/^': '/'
}
}
}
},
configureWebpack: {
plugins: [
new CopyWebpackPlugin([
// ./src/static是一个静态的文件,我们直接使用copy-webpack-plugin插件将文件复制到我们打包后的路径中,from为需要复制的文件的路径,还有一个to属性是复制到哪里的路径,不写则默认复制到打包后文件的根目录。
{
from: path.resolve(__dirname, './src/static'),
to: path.resolve(__dirname, './dist/img')
}
])
]
}
})
之前说了为什么这个图片不能放到asserts下面。原因是,asserts下方的文件会被压缩,精简,筛选。只有在代码中使用了的才会被打包进去。而且名称也是不一样的。public/index.html不被认为是使用了图片。具体原因我(后端)不知道。 所以只能是通过复制的方式原封不动的复制过去。
下一篇文章说一下怎么使用nginx来部署。嗯,希望我能搞懂先。