|
@@ -1,364 +0,0 @@
|
|
-//云端图片操作工具
|
|
|
|
-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 fs = require('fs')
|
|
|
|
-const path = require('path')
|
|
|
|
-const readline = require('readline')
|
|
|
|
-const request_util = require('requestretry')
|
|
|
|
-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 => {
|
|
|
|
- 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, index, trackMode) {
|
|
|
|
- let fontFile = config.watermark.fontFile
|
|
|
|
- let color = config.watermark.color
|
|
|
|
- let size = sizeOf(image)
|
|
|
|
- let imgData = gm(image)
|
|
|
|
- //添加第一页的得分明细
|
|
|
|
- if (index == 1) {
|
|
|
|
- //初始坐标
|
|
|
|
- let x = 30
|
|
|
|
- let y = 10
|
|
|
|
- //最大宽/高限制
|
|
|
|
- let fontSize = config.watermark.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, '成绩明细')
|
|
|
|
- //普通考试模式,按客观+主观模式显示总分
|
|
|
|
- if (trackMode === '1') {
|
|
|
|
- imgData.drawText(x, y += height, '总分=(客观+主观) | ' + totalScore + '=' + student.objectiveScore + '+' + student.subjectiveScore)
|
|
|
|
- }
|
|
|
|
- //研究生考试模式,只显示总分
|
|
|
|
- else if (trackMode === '2') {
|
|
|
|
- imgData.drawText(x, y += height, '总分=' + totalScore + '分')
|
|
|
|
- }
|
|
|
|
- //显示客观题明细
|
|
|
|
- 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.inspector) {
|
|
|
|
- imgData.drawText(x, y += height, '复核人: ' + student.inspector.loginName)
|
|
|
|
- }
|
|
|
|
- //显示主观题明细
|
|
|
|
- if (student.subjectiveScoreDetail && student.subjectiveScoreDetail.length > 0) {
|
|
|
|
- //普通考试模式,按小题显示明细
|
|
|
|
- if (trackMode === '1') {
|
|
|
|
- let 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 +
|
|
|
|
- ' ' + (detail.marker || '') +
|
|
|
|
- ' ' + (detail.header || '')
|
|
|
|
- width = Math.max(width, content.length * fontSize)
|
|
|
|
- imgData.drawText(x, y += height, content)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- //研究生考试模式,按分组显示明细
|
|
|
|
- else if (trackMode === '2') {
|
|
|
|
- let title = '评卷分组 | 总分 | 评卷员 | 仲裁员'
|
|
|
|
- let startY = y
|
|
|
|
- let width = title.length * fontSize
|
|
|
|
- imgData.drawText(x, y += height, title)
|
|
|
|
- //所有小题得分按评卷分组聚合
|
|
|
|
- let maxGroupNumber = 0
|
|
|
|
- let groups = {}
|
|
|
|
- for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
|
|
|
|
- let detail = student.subjectiveScoreDetail[i]
|
|
|
|
- let group = groups[detail.groupNumber]
|
|
|
|
- if (group == undefined) {
|
|
|
|
- group = {
|
|
|
|
- number: detail.groupNumber,
|
|
|
|
- score: 0,
|
|
|
|
- title: {},
|
|
|
|
- titleString: [],
|
|
|
|
- marker: {},
|
|
|
|
- markerString: [],
|
|
|
|
- header: {},
|
|
|
|
- headerString: []
|
|
|
|
- }
|
|
|
|
- groups[detail.groupNumber] = group
|
|
|
|
- maxGroupNumber = Math.max(maxGroupNumber, group.number)
|
|
|
|
- }
|
|
|
|
- group.score = group.score + detail.score
|
|
|
|
- if (detail.mainTitle && !group.title[detail.mainTitle]) {
|
|
|
|
- group.titleString.push(detail.mainTitle)
|
|
|
|
- group.title[detail.mainTitle] = true
|
|
|
|
- }
|
|
|
|
- if (detail.marker && !group.marker[detail.marker]) {
|
|
|
|
- group.markerString.push(detail.marker)
|
|
|
|
- group.marker[detail.marker] = true
|
|
|
|
- }
|
|
|
|
- if (detail.header && !group.header[detail.header]) {
|
|
|
|
- group.headerString.push(detail.header)
|
|
|
|
- group.header[detail.header] = true
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- for (let i = 1; i <= maxGroupNumber; i++) {
|
|
|
|
- let group = groups[i]
|
|
|
|
- if (group != undefined) {
|
|
|
|
- //超过最大高度了则另起一列
|
|
|
|
- if ((y + height + 15) > size.height) {
|
|
|
|
- y = startY
|
|
|
|
- x += width
|
|
|
|
- imgData.drawText(x, y += height, title)
|
|
|
|
- }
|
|
|
|
- let content = group.number + '(' + group.titleString.join(',') + ')' +
|
|
|
|
- ' ' + group.score +
|
|
|
|
- ' ' + group.markerString.join(',') +
|
|
|
|
- ' ' + group.headerString.join(',')
|
|
|
|
- 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 downloadUrl(url) {
|
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
|
- request_util({
|
|
|
|
- url: url,
|
|
|
|
- method: 'GET',
|
|
|
|
- encoding: null,
|
|
|
|
- timeout: 3000,
|
|
|
|
- maxAttempts: 3,
|
|
|
|
- retryDelay: 500,
|
|
|
|
- retryStrategy: request_util.RetryStrategies.HTTPOrNetworkError
|
|
|
|
- }, function (error, response, body) {
|
|
|
|
- if (!error && response.statusCode == 200) {
|
|
|
|
- resolve(body)
|
|
|
|
- } else {
|
|
|
|
- logger.error(error || (url + ' download error'))
|
|
|
|
- error = error || {}
|
|
|
|
- error.code = response ? response.statusCode : 500
|
|
|
|
- reject(error)
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- async downloadFile(type, append, url, localTemplate, data, dir, index, watermark, trackMode) {
|
|
|
|
- data.index = index
|
|
|
|
- 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
|
|
|
|
- try {
|
|
|
|
- imgData = await this.downloadUrl(url)
|
|
|
|
- } catch (err) {
|
|
|
|
- if (err.code === 404) {
|
|
|
|
- //文件不存在,记录日志并跳过
|
|
|
|
- downloadLogger.error('404 ' + type + ' ' + url)
|
|
|
|
- return Promise.resolve()
|
|
|
|
- } else {
|
|
|
|
- logger.error(err)
|
|
|
|
- return Promise.reject(err)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //是否需要添加分数水印
|
|
|
|
- if (watermark) {
|
|
|
|
- return this.addWatermark(imgData, local, data, index, trackMode)
|
|
|
|
- } 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, trackMode, params) {
|
|
|
|
- params.upload = true
|
|
|
|
- params.withSheetUrl = true
|
|
|
|
- params.withScoreDetail = watermark === true
|
|
|
|
- params.withMarkTrack = watermark === true
|
|
|
|
- params.withGroupScoreTrack = watermark === true && trackMode === '1'
|
|
|
|
-
|
|
|
|
- try {
|
|
|
|
- let totalCount = await api.countStudents(env.examId, params)
|
|
|
|
- this.emit('total', totalCount)
|
|
|
|
-
|
|
|
|
- let count = 0
|
|
|
|
- let pageNumber = 0
|
|
|
|
- this.emit('count', 0)
|
|
|
|
- for (; ;) {
|
|
|
|
- pageNumber++
|
|
|
|
- let array = await api.getStudents(env.examId, pageNumber, 10, params)
|
|
|
|
- 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 = 0; i < student.sheetUrls.length; i++) {
|
|
|
|
- promises.push(this.downloadFile('sheet', append, student.sheetUrls[i], template, student, dir, i + 1, watermark, trackMode))
|
|
|
|
- }
|
|
|
|
- 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) {
|
|
|
|
- try {
|
|
|
|
- let array = await api.getPackages(env.examId, true, 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 = 0; i < p.urls.length; i++) {
|
|
|
|
- try {
|
|
|
|
- await this.downloadFile('package', append, p.urls[i], template, p, dir, i + 1)
|
|
|
|
- } 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)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-module.exports = function () {
|
|
|
|
- return new executor()
|
|
|
|
-}
|
|
|