123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- //云端图片操作工具
- 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 downloadLogger = require('./logger.js')('download')
- const upyun = require('./upyun.js')
- const PromisePool = require('./promise-pool.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 moment = require('moment')
- 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 => {
- 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 checkFile(url, client) {
- let size = sizeOf(await client.download(url))
- if (size.width == 0 || size.height == 0) {
- throw 'invalid image data:' + url
- }
- }
- async addWatermark(image, file, student, index, showMarker) {
- let fontFile = config.watermark.fontFile
- let color = config.watermark.color
- let imgData = gm(image)
- let size = sizeOf(image)
- //添加第一页的得分明细
- if (index == 1) {
- //初始坐标
- let x = 30
- let y = 10
- //最大宽/高限制
- let fontSize = 30
- let maxX = size.width / 2 - x * 2
- let height = fontSize + 10
- //计算总分
- let totalScore = (parseFloat(student.objectiveScore) || 0) + (parseFloat(student.subjectiveScore) || 0)
- //显示总分明细
- imgData.font(fontFile, fontSize).fill(color)
- 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 title = '主观题号 | 分数'
- if (showMarker) {
- title += ' | 评卷员'
- }
- let startY = y
- let width = title.length * fontSize
- imgData.drawText(x, y += height, title)
- for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
- let detail = student.subjectiveScoreDetail[i]
- //超过最大高度了则另起一列
- if ((y + height + 15) > size.height) {
- y = startY
- x += width
- imgData.drawText(x, y += height, title)
- }
- let content = detail.mainNumber + '-' + detail.subNumber + ' : ' + detail.score
- if (showMarker) {
- content = content + ' ' + (detail.marker || detail.markerName || '')
- }
- width = Math.max(width, content.length * fontSize)
- imgData.drawText(x, y += height, content)
- }
- }
- }
- //显示评卷标记
- if (student.tags != undefined && student.tags[index] != undefined) {
- let fontSize = 60
- let height = fontSize + 10
- imgData.font(fontFile, fontSize).fill(color)
- let tags = student.tags[index]
- for (let i = 0; i < tags.length; i++) {
- let tag = tags[i]
- if (tag.content != undefined) {
- let top = tag.top
- for (let j = 0; j < tag.content.length; j++) {
- imgData.drawText(tag.left, top, tag.content[j])
- top += height
- }
- }
- }
- }
- return new Promise((resolve, reject) => {
- imgData.write(file, error => {
- if (error) {
- logger.error('add watermark error: ' + file)
- logger.error(error)
- reject(error)
- } else {
- resolve()
- }
- })
- })
- }
- async downloadFile(append, remoteTemplate, localTemplate, data, dir, client, bucket, index, watermark, showMarker) {
- data.index = index
- let remote = mustache.render(remoteTemplate, data)
- let local = path.join(dir, mustache.render(localTemplate, data))
- mkdirp.sync(path.dirname(local))
- //续传模式下,判断目标文件是否存在,存在则直接跳过
- if (append && fs.existsSync(local)) {
- return Promise.resolve()
- } else {
- 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)
- }
- }
- if (imgData == undefined) {
- try {
- imgData = await client.download(remote)
- } catch (err) {
- if (err.code === 404) {
- //文件不存在,记录日志并跳过
- downloadLogger.error('404 ' + bucket + ' ' + remote)
- return Promise.resolve()
- } else {
- logger.error(err)
- return Promise.reject(err)
- }
- }
- }
- //是否需要添加分数水印
- if (watermark) {
- return this.addWatermark(imgData, local, data, index, showMarker)
- } else {
- return new Promise((resolve, reject) => {
- fs.writeFile(local, imgData, err => {
- if (err) {
- logger.error('write image file error: ' + local)
- logger.error(err)
- reject(err)
- } else {
- resolve()
- }
- })
- })
- }
- }
- }
- async downloadSheet(dir, template, append, failover, watermark, showMarker) {
- 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)
- for (;;) {
- pageNumber++
- let array = await api.getStudents(env.examId, pageNumber, 100, true, undefined, watermark === true, watermark === true)
- if (array == undefined || array.length == 0) {
- break
- }
- for (let i = 0; i < array.length; i++) {
- let promises = []
- let student = array[i]
- student.examId = env.examId
- for (let i = 1; i <= student.sheetCount; i++) {
- promises.push(this.downloadFile(append, config.imageUrl.sheet, template, student, dir, client, bucket, i, watermark, showMarker))
- }
- try {
- //等待所有图片下载完毕
- await Promise.all(promises)
- count++
- this.emit('count', count)
- } catch (err) {
- //判断是否异常终止
- if (failover) {
- throw err
- } else {
- logger.error('download sheet error:' + err)
- logger.error(err)
- continue
- }
- }
- }
- }
- this.emit('finish')
- } catch (error) {
- logger.error('download sheet error:' + error)
- logger.error(error)
- this.emit('error', error)
- }
- }
- async downloadPackage(dir, template, append, failover) {
- 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++) {
- try {
- await this.downloadFile(append, config.imageUrl.package, template, p, dir, client, bucket, i)
- } catch (err) {
- //判断是否异常终止
- if (failover) {
- throw err
- } else {
- logger.error('download package error: ' + err)
- logger.error(err)
- continue
- }
- }
- }
- count++
- this.emit('count', count)
- }
- this.emit('finish')
- } catch (error) {
- logger.error('download package error: ' + error)
- logger.error(error)
- this.emit('error', error)
- }
- }
- async checkSlice(dir, concurrent) {
- let bucket = env.server.bucketPrefix + '-slice'
- let client = upyun(bucket, config.upyun.operator, config.upyun.password)
- if (env.server.upyunDomain && env.server.upyunDomain != '') {
- //局域网模式修改图片服务器地址
- client.setDomain(env.server.upyunDomain)
- }
- try {
- let logFile = path.join(dir, 'result.txt')
- fs.writeFileSync(logFile, moment().format('YYYY-MM-DD HH:mm:ss') + ', examId=' + env.examId + '\r\n')
- let totalCount = await api.countStudents(env.examId, true, false)
- this.emit('total', totalCount)
- let self = this
- let count = 0
- let pageNumber = 0
- let pool = PromisePool.create(concurrent, function(student) {
- student.examId = env.examId
- student.promises = []
- for (let i = 1; i <= student.sliceCount; i++) {
- student.index = i
- let url = mustache.render(config.imageUrl.slice, student)
- student.promises.push(new Promise(resolved => {
- self.checkFile(url, client).then(() => {
- //fs.appendFileSync(logFile, url + ': success\r\n')
- }).catch(() => {
- fs.appendFileSync(logFile, url + ': error\r\n')
- }).finally(() => {
- resolved()
- })
- }))
- }
- return Promise.all(student.promises)
- })
- pool.on('count', function(offset) {
- self.emit('count', count + offset)
- })
- this.emit('count', 0)
- for (;;) {
- pageNumber++
- let array = await api.getStudents(env.examId, pageNumber, 200, true, false, false, false)
- if (array == undefined || array.length == 0) {
- break
- }
- await pool.start(array)
- count += array.length
- }
- this.emit('finish')
- fs.appendFileSync(logFile, moment().format('YYYY-MM-DD HH:mm:ss') + ', examId=' + env.examId)
- } catch (error) {
- logger.error('check slice error:' + error)
- logger.error(error)
- this.emit('error', error)
- }
- }
- async checkSliceSerial(dir) {
- let bucket = env.server.bucketPrefix + '-slice'
- let client = upyun(bucket, config.upyun.operator, config.upyun.password)
- if (env.server.upyunDomain && env.server.upyunDomain != '') {
- //局域网模式修改图片服务器地址
- client.setDomain(env.server.upyunDomain)
- }
- try {
- let logFile = path.join(dir, 'result.txt')
- fs.writeFileSync(logFile, moment().format('YYYY-MM-DD HH:mm:ss') + ', examId=' + env.examId + '\r\n')
- let totalCount = await api.countStudents(env.examId, true, false)
- this.emit('total', totalCount)
- let self = this
- let count = 0
- let pageNumber = 0
- this.emit('count', 0)
- for (;;) {
- pageNumber++
- let array = await api.getStudents(env.examId, pageNumber, 100, true, false, false, false)
- if (array == undefined || array.length == 0) {
- break
- }
- for (let i = 0; i < array.length; i++) {
- let student = array[i]
- student.examId = env.examId
- student.promises = []
- for (let i = 1; i <= student.sliceCount; i++) {
- student.index = i
- let url = mustache.render(config.imageUrl.slice, student)
- student.promises.push(new Promise(resolved => {
- self.checkFile(url, client).then(() => {
- //fs.appendFileSync(logFile, url + ': success\r\n')
- }).catch(() => {
- fs.appendFileSync(logFile, url + ': error\r\n')
- }).finally(() => {
- resolved()
- })
- }))
- }
- await Promise.all(student.promises)
- count++
- this.emit('count', count)
- }
- }
- this.emit('finish')
- fs.appendFileSync(logFile, moment().format('YYYY-MM-DD HH:mm:ss') + ', examId=' + env.examId)
- } catch (error) {
- logger.error('check slice error:' + error)
- logger.error(error)
- this.emit('error', error)
- }
- }
- }
- module.exports = function() {
- return new executor()
- }
|