全栈开发,一键生成在线文档链接的语言麻雀文档
最编程
2024-04-05 09:29:04
...
1.背景
- 公司项目多业务庞杂后都需要一个统一的文档管理工具,我们公司就是使用的语雀文档;
- 公司法务会根据应用商以及相关法律法规需求在语雀上对各应用渠道隐私政策、服务条款、用户协议……进行文档管理;
- 然后要求开发根据语雀文档输出对应的线上地址链接,给官网或者客户端使用;
- 最开始语雀支持直接导出html;我们copy到我们项目,然后推送到服务端即可;
- 后来语雀文档关闭该入口,我们只能手动选择copy,然后用富文本或者Typora工具转成html,再单独调整样式,然后在推送到服务端;
- 显然上述的操作是非常low的,完全不符合我们高级程序员的追求;
2.方案
- 根据语雀开发者文档,查看第一个进入后直接看第一个标签 overview查找对应api
- 调用接口解析获取文档的html;
- 将html进行整理,加入官方提供的css样式以及其他样式,处理表格,图片跨域等问题;
- 将整理后的html写入希望的地方;
3.实践-获取html以及整理得到完整的html
3.1查看api说明找到接口以及token
比方我们公司的空间域名为wp
,那么我们的相关变量就是
export const YUQUE_CONST = {
YUQUE_HOST: 'https://wp.yuque.com/',
API_PREFIX: 'https://wp.yuque.com/api/v2',
YUQUE_TOKEN: 'xxxx----'
}
3.1.1 语雀的token需要有空间管理员身份,在个人头像下拉-切换至个人空间-账户设置-TOKEN-新建或者找到对应要用的权限token参考
3.2 接着我们请求获取对应资源
3.2.1 封装相关请求
import axios from 'axios'
// 统一处理错误
function handleErrorStatus(status) {
// 状态码如下
// 400 请求的参数不正确,或缺少必要信息,请对比文档
// 401 需要用户认证的接口用户信息不正确
// 403 缺少对应功能的权限
// 404 数据不存在,或未开放
// 500 服务器异常
}
async function yuqueAxios(apiUrl) {
const { data } = await axios.get(apiUrl, {
headers: {
"X-Auth-Token": YUQUE_CONST.YUQUE_TOKEN,
"User-Agent": '这里可以填写应用名称'
}
})
if (data.status){
handleErrorStatus(status)
return Promise.reject(data.message);
}
return data
}
3.2.2 整理传入的语雀地址得到最终请求地址,注意这里需要兼容语雀正式地址以及分享出来的url
// 获取命名空间(仓库的唯一名称)和slug(文档唯一名称)
function getNamespaceAndSlug(link) {
const shortUrl = link.replace(YUQUE_HOST, '');
const paths = shortUrl.split('/');
const slug = paths.pop();
const namespace = paths.join('/');
return {
namespace,
slug: slug.replace(/[^a-z0-9\-].*$/, ''), // 清除后面带有的 ‘?’‘#’ 之类的
};
}
function getYuqueReqUrl({ namespace, slug}) {
return namespace === 'docs/share'
? `${YUQUE_CONST.API_PREFIX}/${namespace}/${slug}`
: `${YUQUE_CONST.API_PREFIX}/repos/${namespace}/docs/${slug}`;
}
// 示例分享链接
const link = "https://wp.yuque.com/docs/share/b0278586-d705-40a2-9f98-211b5b72716e?#"
const namespaceSlug = getNamespaceAndSlug
// {namespace: 'docs/share', slug: 'b0278586-d705-40a2-9f98-211b5b72716e'}
const url = getYuqueReqUrl(namespaceSlug)
// 最终请求url是:
https://wp.yuque.com/api/v2/docs/share/b0278586-d705-40a2-9f98-211b5b72716e
// 普通链接
const link = "https://wp.yuque.com/tcsdzz/iuxba2/fg45agbpw9a7m72w"
// {namespace: 'tcsdzz/iuxba2', slug: 'fg45agbpw9a7m72w'}
// 最终请求url是:
https://wp.yuque.com/api/v2/repos/tcsdzz/iuxba2/docs/fg45agbpw9a7m72w
3.2.3 获取文档
const result = yuqueAxios(url)
const { body_html, title } = result.data;
3.3 将获取的文档进行完整整理
3.3.1 添加内联样式
const CleanCSS = require('clean-css');
const clearCss = new CleanCSS({});
function addStyle(content) {
const html = content + '</body></html>';
// 将官方提供的CDN样式放置在项目仓库,并读取
const cssFiles = fs.readdirSync(join(__dirname, 'css'));
const cssContent = cssFiles.map(cssFile => {
const { styles } = clearCss.minify(
fs.readFileSync(join(__dirname, 'css', cssFile), 'utf-8')
);
return styles;
});
return html
.replace('\n', '<br/>')
.replace(
'html>',
`html><head><style type="text/css">${cssContent.join(
' '
)}</style></head><body>`
)
.replace(/\"/g, '"');
}
const afterAddStyle = addStyle(body_html)
3.3.2 处理里面的title
const ifShowTitle = true // 外面控制
function addTitle(afterAddStyle) {
const t = ifShowTitle ? title : ''
return afterAddStyle.replace(
/typography="classic">/,
`typography="classic"><h1 id="article-title" class="index-module_articleTitle_ed3ro doc-article-title">${t}</h1>`
)
}
const afterAddTitle = addTitle(afterAddStyle)
3.3.3 处理背景色
const bgc = 'transparent'
function handleBgc(afterAddTitle) {
if (!bgc) {
return afterAddTitle;
}
return afterAddTitle.replace(
/<\/head><body/,
`</head><body style="background:${bgc} !important"`
);
}
3.3.4 去除语雀中写死的表格宽度适配移动端
function handleTableWidth(content) {
const reg = /(<table[^>]*style="[^"]*)(width\s*:\s*\d*px;?)([^>]*>)/g;
return content.replace(reg, function (match, ...other) {
const [, x, , y] = [...arguments];
return x + y;
});
}
3.3.5 处理头部,加入图片防盗链
handleHead(content) {
// 处理图片防盗链
const reg = /src=[\'\"]https:\/\/cdn\.nlark\.com\/yuque/g;
const referrerMeta = reg.test(content)
? '<meta name="referrer" content="no-referrer"/>'
: '';
return content.replace(
/<!doctype html><head>/,
`<!DOCTYPE html><html lang="zh-cmn-Hans"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">${referrerMeta}<meta content="telephone=no" name="format-detection">`
);
}
}
3.3.6 得到处理后的html
function getIntegralHtml() {
const afterAddStyle = addStyle(this._content)
const afterAddTitle = addTitle(afterAddStyle)
const afterAddBgc = handleBgc(afterAddTitle)
const afterHandleTable = handleTableWidth(afterAddBgc)
const afterHandleHead = handleHead(afterHandleTable)
return afterHandleHead
}
const integralHtml = getIntegralHtml(body_html)
4. 将获得的html内容直接写入到指定的文件中
如果用户输入filename
为static/official/index.html 或者是static/official/index; 我们希望在根目录static文件下写入;
4.1 解析用户输入的路径
function parseFilename(filename) {
const compFilePath = /\.html$/.test(filename)
? filename
: `${filename}${values.ext}`;
return join(__dirname, compFilePath)
};
function writeYuqueFile(html, filename) {
const filepath = parseFilename(filename)
const dir = dirname(filepath);
fs.ensureDirSync(dir);
fs.writeFileSync(filepath, html, 'utf-8');
}
writeYuqueFile(integralHtml,filename)
至此整个项目就完工了; 当然语雀地址以及希望的路径,我们可以通过写配置调用,也可以通过node的process.argv在命令行工具获取;
下一步,直接输如语雀地址以及目标页面名,就可以自动生成
- 目前虽然能通过输入语雀地址直接生成文件,但是最终文件发布到线上还是需要将代码手动推到服务器进行部署;
- 我们希望后面可以直接实现一个表单,直接输入语雀地址以及目标,就能自动给出线上链接;效果如下;
推荐阅读
-
35 岁实现财务*,腾讯程序员手握2300万提前退休?-1000万房产、1000万腾讯股票、加上300万的现金,一共2300万的财产。有网友算了一笔账,假设1000万的房产用于自住,剩下1300万资产按照平均税后20-50万不等进行计算,大约花上26-60年左右的时间才能赚到这笔钱。也就是说,普通人可能奋斗一辈子,才能赚到这笔钱。在很多人还在为中年危机而惶惶不可终日的时候,有的人的35岁,就已经安全着陆,试问哪个打工人不羡慕?但问题是有这样财富积累必然有像样的实力做靠山。没有人可以不劳而获。 看到这里,肯定有人说,那么对于普通人来说,卷可能真就成了唯一的出路。但是卷也有轻松的卷,“偷懒”的卷法,对于程序员而言,刨除掉一时无法改掉的开会传统占用的大部分时间,如何把有限的时间和精力放在真正重要的架构设计、需求设计上,而不是重复的造*,编码、改bug、手动测试。因此在科技改变生活的今天,学会使用AI工具成为程序员们的必备技能。 以全栈式全自动的软件开发工具飞算SoFlu软件机器人为例,作为全球首款面向微服务架构设计和最佳实践的软件机器人,SoFlu软件机器人改变了原来手工编码的作业模式,通过可视化拖拽方式以及参数配置就能实现等同于编写复杂代码的业务逻辑,在设计业务逻辑时就完成了微服务应用开发,做到“业务即图,图即代码”,实现“软件开发,十倍提效”。 而作为飞算SoFlu软件机器人的一个重要组成部分,FuncGPT(慧函数)支持所有类型函数创建。通过自然语言描述Java函数需求,实时生成高质量、高可读性的Java函数代码。生成代码可直接复制到IDEA,或一键导入Java全自动开发工具函数库。 FuncGPT(慧函数)具有五大核心能力: ● 自然语言:通过自然语言即可生成函数,降低软件开发门槛。 ● 秒级函数开发:全自动秒级开发函数,秒级完成,效率千倍提升。 ● 一站式函数开发应用:函数生成即可直接应用,效率、安全有保障。 ● 质量可靠:生成的函数代码符合业界规范、具备良好的可读性,符合最优解。 ● 灵活生成:可以根据开发人员的具体需求生成并支持调整和修改。 以“将文件流转换为File对象”这个需求为例,以下是FuncGPT(慧函数)秒级生成的代码: / 类名:FileConverter // 函数名:convertStreamToFile // 函数功能:将文件流转换为File对象 // POM依赖包:无 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileConverter { /** * 将文件流转换为File对象 * @param inputStream 文件流 * @param filePath 文件路径 * @param fileName 文件名 * @return 转换后的File对象 * @throws IOException 如果发生I/O异常 */ public static File convertStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException { File file = new File(filePath + File.separator + fileName); // 创建File对象 try (FileOutputStream outputStream = new FileOutputStream(file)) { // 创建文件输出流 byte buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 从文件流读取数据并写入文件 outputStream.write(buffer, 0, bytesRead); } } return file; // 返回转换后的File对象 } } // 函数示例 // 将文件流转换为File对象示例 // 入参:inputStream,文件流 // 入参:filePath,文件路径 // 入参:fileName,文件名 // 出参:file,转换后的File对象 // 调用示例: // InputStream inputStream = new FileInputStream("example.txt"); // String filePath = "C:\\Users\\User\\Documents"; // String fileName = "example.txt"; // File file = FileConverter.convertStreamToFile(inputStream, filePath, fileName); // System.out.println(file.getAbsolutePath); // 输出结果:例如,将文件流转换为File对象后,文件的绝对路径为:C:\Users\User\Documents\example.txt // 则输出结果为:C:\Users\User\Documents\example.txt 通过分析,不难发现以上代码:
-
全栈开发,一键生成在线文档链接的语言麻雀文档