【第2815期】前端本地化部署
温馨提示:这篇文章已超过599天没有更新,请注意相关的内容是否还可用!
前言
有些盲点可以适当理解。 今天的前端早读课文章由@无一分享,公众号:正财云前端授权。
正文从这里开始~~
现在成熟的前端团队都有自己的内部构建平台,云畅是我们CI/CD效率的助推器。 先简单介绍一下我们公司的运昌。 这个运昌不是另一个运昌。 云畅主要做的是:获取部署的项目、分支、环境基本信息后无法验证服务器身份,开始拉取代码、安装依赖、打包,以及将项目的一些资源静态文件上传到CDN,然后将生成的代码打包成镜像文件,然后将镜像上传到镜像仓库,最后调用K8S的镜像部署服务根据环境部署镜像。 这是我们云很长一段时间要做的事情。
场景分析
为了网络安全,客户会要求我们的应用全部部署在内网,那么我们需要做什么呢? 首先我们要考虑前端代码中是否有一些直接访问外部网络资源的地方? 二、后端是否返回静态资源地址,在特定情况下访问? 第三方CDN资源有哪些具体类型? 前端直接访问的CDN资源太常见了,比如at.alicdn.com,我们自己内部的静态资源luban.zcycdn.com,sitecdn.zcycdn.com等,下面是我们使用的静态资源地址代码。
<link rel='stylesheet' href='//at.alicdn.com/t/fontnm.css' />
<img src="https://sitecdn.zcycdn.com/f2e/8.png"alt="收货人"/>
<img src="https://luban.zcycdn.com/f2e/8.png"alt="收货人"/>
//css中字体文件
src:url(https://sitecdn.zcycdn.com/t/font_148178j4i.eot);
src:url(https://sitecdn.zcycdn.com/t/font1_4i.woff);
为了保证我们的内网可以访问,我们讨论了以下两种解决方案
方案一 DNS解析转发
我们使用DNS服务层来处理。 具体来说,DNS如何解析二级域名和三级域名,如何使用DNS缓存,13台根服务器有哪些。 这次我们不深入讨论。 我们只需要 DNS。 进行域名解析,解析到指定的IP服务。
那我们是不是可以想一想,是不是可以在代码中拦截访问的静态资源的域名,把DNS解析到本地服务的地址呢? 为了更清楚的理解,我打个例子如下:
我们代码中需要访问一张图片,CDN地址:
提前上传a.js文件,提前放到本地服务器,访问地址:
代码运行时,访问代码时,将DNS直接地址解析为IP地址,达到访问静态资源的目的
貌似这个挺简单的,不用每个业务负责人去检查和修改自己代码中的静态资源,胜利在望,跑到运维童鞋那里建议是否可以这样做是可以的,但是运维没说服我。 温顺的帖子。 运维童鞋说:静态资源放在对象存储或者服务器上无法验证服务器身份,可以通过IP或者域名来请求,但是IP只支持HTTP,域名+SSL证书支持HTTPS,还可以做一些加密so您的资源或请求内容已加密,不易被破解。 域名证书原来是3到5年的,3年前换了。 目前申请的证书都是一年期的,这说明用户不仅要配置我们提供的DNS规则,还需要配合我们每年更新证书。 客户以这种方式合作并不容易。 如下所示:
DNS只是帮我们把域名解析成IP。 HTTPS 也需要证书来验证服务器的身份。 DNS 拦截和解析是不够的。 模拟实现了一波大体思路:自己开启一个静态资源服务和DNS本地解析服务,访问juejin.cn域名时,IP解析为本地IP,静态资源访问成功,如下。
自己写一个DNS服务step1:在本地启动一个服务
在服务器上临时存放静态资源和模拟资源
启动服务访问静态资源
我们的目的:如果您访问:3000/zcy.png,您可以访问我们本地服务的静态资源::3000/zcy.png
step2:启动一个本地的DNS服务,拦截所有的请求,转发给你启动的IP
点击查看源码()
step3:配置本地DNS解析
step4:测试HTTP和HTTPS的访问
访问::3000/zcy.png
如果是:3000/zcy.png
如果访问的是HTTP请求,可以访问,但是不能访问HTTPS,证明HTTPS的证书问题。 HTTPS对称加密的秘钥使用非对称加密进行传输,数据传输采用对称加密,保证了数据的加密传输。 为了防止冒充,CA(Certificate Authority)签发的证书称为数字证书(Digital Certificate),在非对称加密阶段,服务器会将证书连同非对称加密的公钥返回,以证明服务器的身份对浏览器来说 HTTPS 相比 HTTP 多了一层 SSL/TLS(安全层),如下图所示。
方案二
项目构建时,扫描项目中的静态资源地址,将我们公网的CDN服务放在客户自己的服务器上,修改源文件中的静态资源地址为客户的本地服务。
优缺点一目了然。 方案一不需要修改代码,但需要充分取得客户的信任和支持,配置DNS转发。 解决方案2不需要打扰客户。 即使以后有新的域名,也不需要和客户沟通。 代码有侵入性,会替换掉静态资源的地址
统一封装runCommand执行命令
function runCommand(cmd, args, options, before, end) {
return new Promise((resolve, reject) => {
log(before, blue)
const spawn = childProcess.spawn(
cmd,
args,
Object.assign(
{
cwd: global.WORKSPACE,
stdio: 'inherit',
shell: true,
},
options
)
)
spawn.on('error', (error) => {
log(error, chalk.red)
reject(error)
});
spawn.on('close', (code) => {
if (code !== 0) {
return reject(`sh: ${cmd} ${args.join(' ')}`)
}
end && log(end, green)
resolve()
});
})
}
1. 前期环境检查
切换公司号码
runCommand('nrm', ['use', 'zcy-server'], {}, 'switch nrm registry to zcy', 'switch nrm registry to zcy success')
下载依赖
runCommand('npm', ['i', '--unsafe-perm'], {}, 'npm install', 'npm install success')
2.编译编译
不同的环境需要上传不同的地址,所以需要动态修改webpack的publicPath
const cdnConfigStr = `assetsPublicPath: 'http://dev.com',`
replaceFileContent(configPath, /assetsPublicPath:.+,/g, cdnConfigStr)
exports.replaceFileContent = function(filePath, source, target) {
const fileContent = fs.readFileSync(filePath, 'utf-8')
let targetFileContent = fileContent
if (Array.isArray(source)) {
source.forEach(([s, target]) => {
if (target) {
targetFileContent = targetFileContent.replace(s, target)
}
})
} else {
targetFileContent = fileContent.replace(source, target)
}
fs.writeFileSync(filePath, targetFileContent, 'utf-8')
}
编译项目
runCommand('npm', ['run', 'build'], {}, `webpack build`, `webpack build success`)
3.静态资源替换
替换url源码地址
const replaceWebpackDistContent =
async function(options = {},collectionAssets,folder) {
const fileContent = fs.readFileSync(filePath, 'utf-8');
let targetFileContent=fileContent;
[
[/(https\:)?\/\/g.alicdn.com\/[-a-zA-Z0-9@:%_\+.~#?&//=]+\.[-a-zA-Z0-9@:%_\+.~#?&//=]+/g, cdn],
[/(https?\:)?\/\/sitecdn.zcycdn.com\/[-a-zA-Z0-9@:%_\+.~#?&//=]+\.[-a-zA-Z0-9@:%_\+.~#?&//=]+/g, cdn],
[/(https\:)?\/\/cdn.zcycdn.com\/[-a-zA-Z0-9@:%_\+.~#?&//=]+\.[-a-zA-Z0-9@:%_\+.~#?&//=]+/g, cdn],
].forEach(([reg,uri])=>{
targetFileContent=targetFileContent.replace(reg,function(match){
let basename = '';
let uriMath = match;
basename = path.basename(uriMath);
if(uriMath.slice(0,4)!='http'){
uriMath='https:'+uriMath;
}
const parseUrl = url.parse(uriMath);
collectionAssets({src:uriMath,fileName:path.basename(parseUrl.pathname)});
console.log('替换前',match);
const myURL= new URL(projectName, uri);
const replacedUrl = uri+'/'+projectName+parseUrl.path+(parseUrl.hash||'');
console.log('替换后', replacedUrl);
return replacedUrl;
})
})
fs.writeFileSync(filePath, targetFileContent, 'utf-8')
}
获取前端代码中硬写的静态资源
const downloadAssetsFiles= async function(img,forder){
const staticAssets='staticAssets';
let assetsUrl=getPwdPath(`${forder||''}${path.sep}${staticAssets}`);
if(!fs.existsSync(assetsUrl)){
fs.mkdirSync(assetsUrl);
}
return Promise.all(img.objUnique('src').map(({src,fileName})=>{
if(fileName){
return new Promise(function(resolve,reject){
const originFileDir = path.join(assetsUrl,path.dirname(url.parse(src).pathname));
fs.mkdirSync(originFileDir,{recursive:true});
const uri = path.join(originFileDir,fileName);
download(uri,src,resolve,reject);
}).catch(err=>{
console.log(err)
throw new Error(err);
})
}
}))
}
function download(loadedUrl,src){
const writeStream = fs.createWriteStream(loadedUrl);
const readStream = request(src);
readStream.pipe(writeStream);
readStream.on('end', function() {
console.log(fileName,'文件下载成功');
});
writeStream.on("finish", function() {
console.log(fileName,"文件写入成功");
writeStream.end();
});
}
downloadAssetsFiles(assetsArr,'dist');
// 发现替换资源里还有cdn,因此替换下载后的cdn里面的cdn
const assetsArr=[];
await replaceWebpackDistContent(options,collectionAssets,'staticAssets');
await downloadAssetsFiles(assetsArr,'dist');
4、OSS将静态资源推送给客户资源服务
const ossEndpoint = process.env.OSS_ENDPOINT;
const commonOptions = {
accessKeyId: process.env.OSS_ACCESSKEYID ,
accessKeySecret: process.env.OSS_ACCESSKEYSECRET,
bucket: process.env.OSS_BUCKET,
timeout: '120s',
}
const extraOptions = ossEndpoint
? {
endpoint: ossEndpoint, // 从全局数据获取,没有会依赖 region
cname: true,
} : {
region: process.env.OSS_REGION,
}
const ossOptions = Object.assign({}, commonOptions, extraOptions);
const client = new OSS(ossOptions);
//onlinePath 访问的文件地址
//curPath 上传的文件地址
result = await client.put(onlinePath, curPath);
参考文件
关于这篇文章