//云端图片操作工具 const EventEmitter = require('events') const api = require('./api.js') const env = require('./env.js') const config = require('./config.js') const logger = require('./logger.js')('image.js') const upyun = require('./upyun.js') const fs = require('fs') const path = require('path') const readline = require('readline') const sizeOf = require('image-size') const mustache = require('mustache') const mkdirp = require('mkdirp') const gm = config.imagemagick != undefined ? require('gm').subClass({ imageMagick: true, appPath: config.imagemagick }) : require('gm') class executor extends EventEmitter { async readFile(file) { return new Promise((resolve, reject) => { var data = [] if (fs.existsSync(file)) { let reader = readline.createInterface({ input: fs.createReadStream(file) }) reader.on('line', line => { data.push(line) }) reader.on('close', () => { resolve(data) }) } else { resolve(data) } }) } async addWatermark(image, file, student) { let fontSize = config.watermark.fontSize let fontFile = config.watermark.fontFile let color = config.watermark.color let imgData = gm(image).font(fontFile, fontSize).fill(color) let size = sizeOf(image) //初始坐标 let x = 30 let y = 10 let height = fontSize + 10 //最大宽/高限制 let maxX = size.width - x * 2 //计算总分 let totalScore = (parseFloat(student.objectiveScore) || 0) + (parseFloat(student.subjectiveScore) || 0) //显示总分明细 imgData.drawText(x, y += height, '成绩明细') imgData.drawText(x, y += height, '总分=(客观+主观) | ' + totalScore + '=' + student.objectiveScore + '+' + student.subjectiveScore) //显示客观题明细 if (student.objectiveScoreDetail && student.objectiveScoreDetail.length > 0) { let lines = [] let array = [] //前置提示文字的字符数 let count = 10 lines.push(array) for (let i = 0; i < student.objectiveScoreDetail.length; i++) { let detail = student.objectiveScoreDetail[i] let content = detail.answer + ':' + detail.score //超长后另起一行显示客观题 if ((count + content.length) * fontSize * 0.7 > maxX) { array = [] lines.push(array) count = 10 } array.push(content) count += content.length } //显示所有行的客观题明细 for (let l = 0; l < lines.length; l++) { imgData.drawText(x, y += height, '客观题识别结果 | ' + lines[l].join(';')) } } //显示主观题明细 if (student.subjectiveScoreDetail && student.subjectiveScoreDetail.length > 0) { let startY = y imgData.drawText(x, y += height, '题号 | 分数') for (let i = 0; i < student.subjectiveScoreDetail.length; i++) { let detail = student.subjectiveScoreDetail[i] //超过最大高度了则另起一列 if ((y + height + 15) > size.height) { y = startY x += 200 imgData.drawText(x, y += height, '题号 | 分数') } imgData.drawText(x, y += height, detail.mainNumber + '-' + detail.subNumber + ' : ' + detail.score) } } return new Promise((resolve, reject) => { imgData.write(file, error => { if (error) { reject(error) } else { resolve() } }) }) } async downloadFile(remoteTemplate, localTemplate, data, dir, client, bucket, watermark) { let remote = mustache.render(remoteTemplate, data) let local = path.join(dir, mustache.render(localTemplate, data)) mkdirp.sync(path.dirname(local)) let imgData if (config.localStore != undefined && config.localStore.length > 0) { let cache = path.join(config.localStore, bucket, remote) if (fs.existsSync(cache)) { imgData = fs.readFileSync(cache) } else { imgData = await client.download(remote) } } else { imgData = await client.download(remote) } //是否需要添加分数水印 if (watermark) { await this.addWatermark(imgData, local, data) } else { await fs.writeFileSync(local, imgData) } } async downloadSheet(dir, template, watermark) { //从文件中读取已成功下载的准考证号列表 let doneFile = path.join(__dirname, '../../logs/sheet.log') let finish = await this.readFile(doneFile) logger.info(finish) let success = {} for (let i = 0; i < finish.length; i++) { success[finish[i]] = true } let bucket = env.server.bucketPrefix + '-sheet' let client = upyun(bucket, config.upyun.operator, config.upyun.password) if (env.server.upyunDomain && env.server.upyunDomain != '') { //局域网模式修改图片服务器地址 client.setDomain(env.server.upyunDomain) } try { let totalCount = await api.countStudents(env.examId, true, undefined) this.emit('total', totalCount) let count = 0 let pageNumber = 0 this.emit('count', 0) while (true) { pageNumber++ let array = await api.getStudents(env.examId, pageNumber, 1000, true, undefined, watermark === true) if (array == undefined || array.length == 0) { break } for (let i = 0; i < array.length; i++) { let promises = [] let student = array[i] let examNumber = student['examNumber'] if (success[examNumber]) { //在已完成列表中则跳过,不用重复下载 } else { student.examId = env.examId for (let i = 1; i <= student.sheetCount; i++) { student.index = i promises.push(this.downloadFile(config.imageUrl.sheet, template, student, dir, client, bucket, (i == 1 && watermark))) } //等待所有图片下载完毕 await Promise.all(promises) finish.push(examNumber) success[examNumber] = true } count++ this.emit('count', count) //实时回写到记录文件中去 fs.writeFileSync(doneFile, finish.join('\r\n')) } } this.emit('finish') } catch (error) { logger.error(error) this.emit('error', error) } finally { //回写到记录文件中去 fs.writeFileSync(doneFile, finish.join('\r\n')) } } async downloadPackage(dir, template) { let bucket = env.server.bucketPrefix + '-package' let client = upyun(bucket, config.upyun.operator, config.upyun.password) if (env.server.upyunDomain && env.server.upyunDomain != '') { client.setDomain(env.server.upyunDomain) } try { let array = await api.getPackages(env.examId, true) this.emit('total', array.length) let count = 0 this.emit('count', 0) for (let i = 0; i < array.length; i++) { let p = array[i] p.examId = env.examId for (let i = 1; i <= p.picCount; i++) { p.index = i await this.downloadFile(config.imageUrl.package, template, p, dir, client, bucket) } count++ this.emit('count', count) } this.emit('finish') } catch (err) { logger.error(err) this.emit('error', err) } } } module.exports = function () { return new executor() }