first commit

This commit is contained in:
zzc
2026-01-09 11:21:29 +08:00
commit 24e6484860
29 changed files with 1930 additions and 0 deletions

BIN
util/66.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

38
util/file.js Normal file
View File

@@ -0,0 +1,38 @@
const fs = require('fs')
const getFileBuffer = (path) => fs.readFileSync(path)
const getFilePath = (prefixStr = '', postfixStr = '') => {
// 文件名称
const uuid = () => {
function s4 () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
}
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()
}
// 文件扩展名 toLowerCase 转小写
let prefix = prefixStr
if (prefixStr && prefix.substr(prefix.length - 1, 1) !== '/') { prefix += '/' }
// 后缀
let postfix = ''
if (postfixStr) { postfix = '_' + postfixStr }
return prefix + uuid() + postfix + '.jpg'
}
const uuid = () => {
function s4 () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
}
return s4() + s4() + s4()
}
module.exports = {
getFileBuffer,
getFilePath,
uuid
}

27
util/grpc-promisify.js Normal file
View File

@@ -0,0 +1,27 @@
function promisify (client) {
Object.keys(Object.getPrototypeOf(client)).forEach(functionName => {
if (functionName.startsWith('$')) {
return
}
const originalFunction = client[functionName]
client[functionName] = (request, callback) => {
if (callback && typeof callback === 'function') {
return originalFunction.call(client, request, (error, response) => {
callback(error, response)
})
}
return new Promise((resolve, reject) => {
originalFunction.call(client, request, (error, response) => {
if (error) {
reject(error)
} else {
resolve(response)
}
})
})
}
client[functionName].interceptors = originalFunction.interceptors
})
}
module.exports = promisify

120
util/image.js Normal file
View File

@@ -0,0 +1,120 @@
const { createCanvas, loadImage, registerFont } = require("canvas");
const fs = require("fs");
const path = require("path");
registerFont(path.join(process.cwd(), `/public/font/PINGFANG MEDIUM.TTF`), {
family: "pf",
});
const mergeImage = async (name) => {
const WIDTH = 1080;
const HEIGHT = 1920;
const canvas = createCanvas(WIDTH, HEIGHT);
const context = canvas.getContext("2d");
const bg = await loadImage(
path.join(process.cwd(), "/public/merge/base_card1.jpg")
);
context.drawImage(bg, 0, 0, 1080, 1920);
context.font = "48px px";
context.fillStyle = "#282828";
context.textAlign = "center"; //文字水平居中
context.fillText(name, 566 / 2, 422);
context.font = `400 80px pf`;
context.textAlign = "center";
context.fillStyle = "red";
context.fillText(`龙马精神`, WIDTH / 2, 366 + 4);
// return canvas.toBuffer();
const out = fs.createWriteStream(__dirname + "/test2.png");
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on("finish", () => console.log("The PNG file was created."));
};
// const avatarOrnament = async (avatarPath, ornamentPath, size = 512) => {
// // 1⃣ 加载图片
// const avatarImg = await loadImage(avatarPath);
// const ornamentImg = await loadImage(ornamentPath);
// const width = avatarImg.width;
// const height = avatarImg.height;
// const canvas = createCanvas(width, height);
// const ctx = canvas.getContext("2d");
// // 3⃣ 先画头像
// ctx.drawImage(avatarImg, 0, 0, width, height);
// // 4⃣ 计算挂饰尺寸(取短边的 25%
// const baseSize = Math.min(width, height);
// const ornamentSize = Math.floor(baseSize * 0.25);
// const ornamentRatio = ornamentImg.width / ornamentImg.height;
// const ornamentWidth =
// ornamentRatio >= 1 ? ornamentSize : ornamentSize * ornamentRatio;
// const ornamentHeight =
// ornamentRatio >= 1 ? ornamentSize / ornamentRatio : ornamentSize;
// // 5⃣ 位置:右下角 + 内边距
// const padding = Math.floor(baseSize * 0.05);
// const x = width - ornamentWidth - padding;
// const y = height - ornamentHeight - padding;
// // 6⃣ 阴影(非常关键:让合成“自然”)
// ctx.save();
// ctx.shadowColor = "rgba(0, 0, 0, 0.25)";
// ctx.shadowBlur = Math.floor(baseSize * 0.03);
// ctx.shadowOffsetX = Math.floor(baseSize * 0.01);
// ctx.shadowOffsetY = Math.floor(baseSize * 0.01);
// ctx.drawImage(ornamentImg, x, y, ornamentWidth, ornamentHeight);
// ctx.restore();
// // 6. 输出文件
// // const buffer = canvas.toBuffer("image/png");
// // fs.writeFileSync(outputPath, buffer);
// const out = fs.createWriteStream(__dirname + "/test3.png");
// const stream = canvas.createPNGStream();
// stream.pipe(out);
// out.on("finish", () => console.log("The PNG file was created."));
// return "success";
// };
const avatarOrnament = async (avatarPath, ornamentPath, size = 512) => {
// 1. 读取头像
const avatar = await loadImage(avatarPath);
// 2. Canvas 尺寸 = 头像尺寸(不裁剪)
const width = 500;
const height = 500;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
// 3. 绘制头像
ctx.drawImage(avatar, 0, 0, width, height);
// 4. 读取装饰外框PNG透明
// const frame = await loadImage(ornamentPath);
const framePath = path.resolve(__dirname + "/66.png");
const frame = await loadImage(framePath);
// 5. 覆盖绘制外框
ctx.drawImage(frame, 0, 0, width, height);
// 6. 输出文件
// const buffer = canvas.toBuffer("image/png");
// fs.writeFileSync(outputPath, buffer);
const out = fs.createWriteStream(__dirname + "/test6.png");
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on("finish", () => console.log("The PNG file was created."));
return "success";
};
module.exports = {
mergeImage,
avatarOrnament,
};

12
util/index.js Normal file
View File

@@ -0,0 +1,12 @@
const promisify = require('./grpc-promisify')
const imageUtil = require('./image')
// const fileUtil = require('./file')
const qiniuUtil = require('./qiniu')
const getLogger = require('./logger')
module.exports = {
imageUtil,
promisify,
qiniuUtil,
getLogger
}

25
util/logger.js Normal file
View File

@@ -0,0 +1,25 @@
const pino = require('pino')
let log
if (process.env.NODE_ENV === 'production') {
log = pino()
} else if (process.env.NODE_ENV === 'development') {
const transport = pino.transport({
target: 'pino-pretty',
options: { colorize: true }
})
log = pino({ level: 'trace'}, transport)
} else {
const transport = pino.transport({
target: 'pino-pretty',
options: { colorize: true }
})
log = pino({level: 'debug'},transport)
}
module.exports = getLogger = name => {
const logger = log.child({ name })
return logger
}

26
util/pg.js Normal file
View File

@@ -0,0 +1,26 @@
const Pool = require('pg-pool');
const pgConnection = new Pool({
user: process.env.POSTGRESQL_USERNAME,
password: process.env.POSTGRESQL_PASSWORD,
host: process.env.POSTGRESQL_HOST,
port: 5432,
database: process.env.POSTGRESQL_DATABASE
})
module.exports = {
pgQuery: async (sql, values) => {
let connection = false
try {
connection = await pgConnection.connect()
let results = await connection.query(sql, values)
return results
} catch (error) {
throw error
} finally {
if (connection) {
connection.release()
}
}
},
}

135
util/qiniu.js Normal file
View File

@@ -0,0 +1,135 @@
const qiniu = require('qiniu');
const { unlinkSync } = require('fs');
const { resolve } = require('path');
const getFileKey = (file, prefix) => {
const uuid = () => {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4();
};
const splitted = file ? file.originalname.split('.') : '';
const extension = file ? splitted[splitted.length - 1] : 'png';
return {
fileName: uuid() + '.' + extension,
key: prefix
? `${prefix}/` + uuid() + '.' + extension
: 'resource/' + uuid() + '.' + extension,
};
};
const getFileName = (file, prefix) => {
const uuid = () => {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4();
};
return uuid() + '.png'
};
const deleteFile = (name) => {
const path = resolve(__dirname, `../public/${name}`);
unlinkSync(path);
};
const saveToQiNIu = (fileName) => {
const mac = new qiniu.auth.digest.Mac(
process.env.ACCESS_KEY,
process.env.SECRET_KEY,
);
const options = { scope: process.env.BUCKET };
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);
const config = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z2;
const localFile = resolve(__dirname, `../public/${fileName}`);
const putExtra = new qiniu.form_up.PutExtra();
const formUploader = new qiniu.form_up.FormUploader(config);
return new Promise((resolve, reject) => {
formUploader.putFile(
uploadToken,
fileName,
localFile,
putExtra,
function (respErr, respBody, respInfo) {
if (respErr) {
reject(respErr);
}
if (respInfo.statusCode == 200) {
resolve(respBody);
}
},
);
});
};
const deleteQiNiuSource = (key) => {
const mac = new qiniu.auth.digest.Mac(
process.env.ACCESS_KEY,
process.env.SECRET_KEY,
);
const config = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z2;
const bucketManager = new qiniu.rs.BucketManager(mac, config);
return new Promise((resolve, reject) => {
bucketManager.delete(
process.env.BUCKET,
key,
function (err, respBody, respInfo) {
if (err) {
reject(err);
} else {
resolve(respInfo);
}
},
);
});
};
const uploadFileByBuffer = (buffer, prefix) => {
const { key } = getFileKey('', prefix);
const mac = new qiniu.auth.digest.Mac(
process.env.ACCESS_KEY,
process.env.SECRET_KEY,
);
const options = { scope: process.env.BUCKET };
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);
const config = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z2;
const putExtra = new qiniu.form_up.PutExtra();
const formUploader = new qiniu.form_up.FormUploader(config);
return new Promise((resolve, reject) => {
formUploader.put(
uploadToken,
key,
buffer,
putExtra,
function (respErr, respBody, respInfo) {
if (respErr) {
reject(respErr);
}
if (respInfo.statusCode == 200) {
resolve(key);
}
},
);
});
};
module.exports = {
saveToQiNIu,
deleteQiNiuSource,
getFileName,
deleteFile,
uploadFileByBuffer
}

BIN
util/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
util/test2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
util/test3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

BIN
util/test5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

BIN
util/test6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB