forked from opentiny/tiny-engine
438 lines
13 KiB
JavaScript
438 lines
13 KiB
JavaScript
import mysql from 'mysql'
|
||
import Logger from './logger.mjs'
|
||
import fs from 'node:fs'
|
||
import path from 'node:path'
|
||
import dotenv from 'dotenv'
|
||
|
||
const logger = new Logger('buildMaterials')
|
||
|
||
// 先构造出.env*文件的绝对路径
|
||
const appDirectory = fs.realpathSync(process.cwd())
|
||
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath)
|
||
const pathsDotenv = resolveApp('.env')
|
||
// 加载.env.local
|
||
dotenv.config({ path: `${pathsDotenv}.local` })
|
||
const { SQL_HOST, SQL_PORT, SQL_USER, SQL_PASSWORD, SQL_DATABASE } = process.env
|
||
|
||
// 组件表名称
|
||
const componentsTableName = 'user_components'
|
||
// 组件关联到物料资产包的id
|
||
const materialHistoryId = 639
|
||
// 数据库配置
|
||
const mysqlConfig = {
|
||
host: SQL_HOST, // 主机名(服务器地址)
|
||
port: SQL_PORT, // 端口号
|
||
user: SQL_USER, // 用户名
|
||
password: SQL_PASSWORD, // 密码
|
||
database: SQL_DATABASE // 数据库名称
|
||
}
|
||
class MysqlConnection {
|
||
constructor(config) {
|
||
this.config = config || mysqlConfig
|
||
// 是否连接上了数据库
|
||
this.connected = false
|
||
this.connection = mysql.createConnection(this.config)
|
||
}
|
||
|
||
connect() {
|
||
return new Promise((resolve, reject) => {
|
||
this.connection.connect((error) => {
|
||
if (error) {
|
||
logger.warn('未能连接到数据库,请查看数据库配置是否正确')
|
||
reject()
|
||
} else {
|
||
logger.success('数据库连接成功')
|
||
this.connected = true
|
||
resolve()
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 执行sql语句,更新数据库
|
||
* @param {string} sql sql语句
|
||
* @param {string} componentName 组件名称
|
||
*/
|
||
query(sql) {
|
||
return new Promise((resolve, reject) => {
|
||
this.connection.query(sql, (error, result) => {
|
||
if (error) {
|
||
reject(error)
|
||
} else {
|
||
resolve(result)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 组件字段映射
|
||
* @param {string} field 字段名
|
||
* @returns 映射后的字段名
|
||
*/
|
||
fieldTransform(field) {
|
||
const fieldMap = {
|
||
docUrl: 'doc_url',
|
||
devMode: 'dev_mode',
|
||
schema: 'schema_fragment'
|
||
}
|
||
|
||
return fieldMap[field] || field
|
||
}
|
||
|
||
/**
|
||
* 格式化单引号
|
||
* @param {string} str 待格式化的字符串
|
||
* @returns 格式化后的字符串
|
||
*/
|
||
formatSingleQuoteValue(str) {
|
||
if (typeof str !== 'string') {
|
||
return str
|
||
}
|
||
|
||
return str.replace(/'/g, "\\'")
|
||
}
|
||
|
||
/**
|
||
* 校验组件数据是否有效
|
||
* @param {object} component 组件数据
|
||
* @returns boolean 校验组件字段是否失败,false-有字段出错
|
||
*/
|
||
isValid(component, file) {
|
||
const longtextFields = ['name', 'npm', 'snippets', 'schema_fragment', 'configure', 'component_metadata']
|
||
|
||
return Object.entries(component).every(([key, value]) => {
|
||
if (longtextFields.includes(key) && value !== null && typeof value !== 'object') {
|
||
logger.error(`"${key}" 的值不是有效的JSON (${file})`)
|
||
|
||
return false
|
||
}
|
||
|
||
return true
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 生成更新组件的sql语句
|
||
* @param {object} component 组件数据
|
||
* @returns 更新组件的sql语句
|
||
*/
|
||
updateComponent(component, file) {
|
||
const valid = this.isValid(component, file)
|
||
|
||
if (!valid) {
|
||
return
|
||
}
|
||
|
||
const values = []
|
||
let sqlContent = `update ${componentsTableName} set `
|
||
|
||
Object.keys(component).forEach((key) => {
|
||
const { [key]: value } = component
|
||
const fields = [
|
||
'version',
|
||
'name',
|
||
'component',
|
||
'icon',
|
||
'description',
|
||
'docUrl',
|
||
'screenshot',
|
||
'tags',
|
||
'keywords',
|
||
'devMode',
|
||
'npm',
|
||
'group',
|
||
'category',
|
||
'priority',
|
||
'snippets',
|
||
'schema',
|
||
'configure',
|
||
'public',
|
||
'framework',
|
||
'isOfficial',
|
||
'isDefault',
|
||
'tiny_reserved',
|
||
'tenant',
|
||
'createBy',
|
||
'updatedBy'
|
||
]
|
||
|
||
if (!fields.includes(key)) {
|
||
return
|
||
}
|
||
|
||
const field = this.fieldTransform(key)
|
||
let updateContent = ''
|
||
|
||
if (['id', 'component'].includes(field)) {
|
||
return
|
||
}
|
||
|
||
if (value === void 0) {
|
||
return
|
||
}
|
||
|
||
if (typeof value === 'string') {
|
||
const formatValue = this.formatSingleQuoteValue(value)
|
||
|
||
updateContent = `\`${field}\` = '${formatValue}'`
|
||
} else if (typeof field === 'number' || field === null) {
|
||
updateContent = `\`${field}\` = ${value}`
|
||
} else {
|
||
const formatValue = this.formatSingleQuoteValue(JSON.stringify(value))
|
||
|
||
updateContent = `\`${field}\` = '${formatValue}'`
|
||
}
|
||
|
||
values.push(updateContent)
|
||
})
|
||
|
||
sqlContent += values.join()
|
||
sqlContent += ` where component = '${component.component}';`
|
||
|
||
this.query(sqlContent, component.component)
|
||
.then(() => {
|
||
logger.success(`组件 ${component.component} 数据更新成功`)
|
||
})
|
||
.catch((error) => {
|
||
logger.error(`组件 ${component.component} 数据更新失败 ${error}`)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 新建的组件关联物料资产包
|
||
* @param {number} id 新建的组件id
|
||
*/
|
||
relationMaterialHistory(id) {
|
||
const uniqSql = `SELECT * FROM \`material_histories_components__user_components_mhs\` WHERE \`material-history_id\`=${materialHistoryId} AND \`user-component_id\`=${id}`
|
||
this.query(uniqSql).then((result) => {
|
||
if (!result.length) {
|
||
const sqlContent = `INSERT INTO \`material_histories_components__user_components_mhs\` (\`material-history_id\`, \`user-component_id\`) VALUES (${materialHistoryId}, ${id})`
|
||
|
||
this.query(sqlContent)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 生成新增组件的sql语句
|
||
* @param {object} component 组件数据
|
||
* @returns 新增组件的sql语句
|
||
*/
|
||
insertComponent(component, file) {
|
||
const valid = this.isValid(component, file)
|
||
|
||
if (!valid) {
|
||
return
|
||
}
|
||
|
||
const defaultName = {
|
||
zh_CN: component.component
|
||
}
|
||
const defaultNpm = {
|
||
package: '',
|
||
exportName: '',
|
||
version: '1.0.0',
|
||
destructuring: true
|
||
}
|
||
const defaultConfigure = {
|
||
loop: true,
|
||
condition: true,
|
||
styles: true,
|
||
isContainer: true,
|
||
isModal: false,
|
||
nestingRule: {
|
||
childWhiteList: '',
|
||
parentWhiteList: '',
|
||
descendantBlacklist: '',
|
||
ancestorWhitelist: ''
|
||
},
|
||
isNullNode: false,
|
||
isLayout: false,
|
||
rootSelector: '',
|
||
shortcuts: {
|
||
properties: ['value', 'disabled']
|
||
},
|
||
contextMenu: {
|
||
actions: ['create symbol'],
|
||
disable: ['copy', 'remove']
|
||
}
|
||
}
|
||
const {
|
||
version = '1.0.0',
|
||
name = defaultName,
|
||
component: componentName,
|
||
icon,
|
||
description,
|
||
docUrl,
|
||
screenshot,
|
||
tags,
|
||
keywords,
|
||
devMode = 'proCode',
|
||
npm = defaultNpm,
|
||
group,
|
||
category = 'general',
|
||
priority = 1,
|
||
snippets = [{}],
|
||
schema = {},
|
||
configure = defaultConfigure,
|
||
public: publicRight = 0,
|
||
framework = 'vue',
|
||
isOfficial = 0,
|
||
isDefault = 0,
|
||
tiny_reserved = 0,
|
||
tenant = 1,
|
||
createBy = 86,
|
||
updatedBy = 86
|
||
} = component
|
||
const values = `('${version}',
|
||
'${this.formatSingleQuoteValue(JSON.stringify(name))}',
|
||
'${componentName}',
|
||
'${icon}',
|
||
'${this.formatSingleQuoteValue(description)}',
|
||
'${docUrl}',
|
||
'${screenshot}',
|
||
'${tags}',
|
||
'${keywords}',
|
||
'${devMode}',
|
||
'${this.formatSingleQuoteValue(JSON.stringify(npm))}',
|
||
'${group}',
|
||
'${category}',
|
||
'${priority}',
|
||
'${this.formatSingleQuoteValue(JSON.stringify(snippets))}',
|
||
'${this.formatSingleQuoteValue(JSON.stringify(schema))}',
|
||
'${this.formatSingleQuoteValue(JSON.stringify(configure))}',
|
||
'${publicRight}',
|
||
'${framework}',
|
||
'${isOfficial}',
|
||
'${isDefault}',
|
||
'${tiny_reserved}',
|
||
'${tenant}',
|
||
'${createBy}',
|
||
'${updatedBy}'
|
||
);`
|
||
|
||
const sqlContent = `INSERT INTO ${componentsTableName} (version, name, component, icon, description, doc_url,
|
||
screenshot, tags, keywords, dev_mode, npm, \`group\`, \`category\`, priority, snippets,
|
||
schema_fragment, configure, \`public\`, framework, isOfficial, isDefault, tiny_reserved,
|
||
tenant, createdBy, updatedBy) VALUES ${values}`.replace(/\n/g, '')
|
||
|
||
this.query(sqlContent, componentName)
|
||
.then((result) => {
|
||
const id = result.insertId
|
||
|
||
logger.success(`组件 ${component.component} 数据新增成功`)
|
||
this.relationMaterialHistory(id)
|
||
})
|
||
.catch((error) => {
|
||
logger.success(`组件 ${component.component} 数据新增失败:${error}`)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 初始化数据库数据,判断是否已存在组件,不存在时执行新增组件
|
||
* @param {object} component 组件数据
|
||
*/
|
||
initDB(component) {
|
||
const selectSqlContent = `SELECT * FROM ${this.config.database}.${componentsTableName} WHERE component = '${component.component}'`
|
||
|
||
this.query(selectSqlContent)
|
||
.then((result) => {
|
||
if (!result.length) {
|
||
this.insertComponent(component)
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
logger.success(`查询组件 ${component.component} 失败:${error}`)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 创建组件表
|
||
* @returns promise
|
||
*/
|
||
createUserComponentsTable() {
|
||
const sqlContent = `
|
||
CREATE TABLE ${componentsTableName} (
|
||
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
version varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||
name longtext CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||
component varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||
icon varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
description varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
doc_url varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
screenshot varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
tags varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
keywords varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
dev_mode varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||
npm longtext CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||
\`group\` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
category varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||
priority int(11) NULL DEFAULT NULL,
|
||
snippets longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
|
||
schema_fragment longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
|
||
configure longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
|
||
createdBy int(11) NULL DEFAULT NULL,
|
||
updatedBy int(11) NULL DEFAULT NULL,
|
||
created_by int(11) NULL DEFAULT NULL,
|
||
updated_by int(11) NULL DEFAULT NULL,
|
||
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||
public int(11) NULL DEFAULT NULL,
|
||
framework varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||
isOfficial tinyint(1) NULL DEFAULT NULL,
|
||
isDefault tinyint(1) NULL DEFAULT NULL,
|
||
tiny_reserved tinyint(1) NULL DEFAULT NULL,
|
||
tenant int(11) NULL DEFAULT NULL,
|
||
component_metadata longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
|
||
library int(11) NULL DEFAULT NULL,
|
||
PRIMARY KEY (id) USING BTREE,
|
||
UNIQUE INDEX unique_component(createdBy, framework, component, version) USING BTREE
|
||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
|
||
`.replace(/\n/g, '')
|
||
|
||
return new Promise((resolve, reject) => {
|
||
this.query(sqlContent)
|
||
.then((result) => {
|
||
logger.success(`表 ${componentsTableName} 创建成功`)
|
||
resolve(result)
|
||
})
|
||
.catch((error) => {
|
||
logger.success(`表 ${componentsTableName} 创建失败:${error}`)
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 初始化数据库的组件表
|
||
* @returns promise
|
||
*/
|
||
initUserComponentsTable() {
|
||
return new Promise((resolve, reject) => {
|
||
// 查询是否已存在表
|
||
this.query(`SHOW TABLES LIKE '${componentsTableName}'`)
|
||
.then((result) => {
|
||
if (result.length) {
|
||
// 已存在
|
||
resolve()
|
||
} else {
|
||
this.createUserComponentsTable()
|
||
.then(() => {
|
||
resolve()
|
||
})
|
||
.catch((err) => {
|
||
reject(err)
|
||
})
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
}
|
||
|
||
export default MysqlConnection
|