image.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. //云端图片操作工具
  2. const EventEmitter = require('events')
  3. const api = require('./api.js')
  4. const env = require('./env.js')
  5. const config = require('./config.js')
  6. const logger = require('./logger.js')('image.js')
  7. const downloadLogger = require('./logger.js')('download')
  8. const upyun = require('./upyun.js')
  9. const fs = require('fs')
  10. const path = require('path')
  11. const readline = require('readline')
  12. const sizeOf = require('image-size')
  13. const mustache = require('mustache')
  14. const mkdirp = require('mkdirp')
  15. const gm = config.imagemagick != undefined ? require('gm').subClass({
  16. imageMagick: true,
  17. appPath: config.imagemagick
  18. }) : require('gm')
  19. class executor extends EventEmitter {
  20. async readFile(file) {
  21. return new Promise((resolve, reject) => {
  22. var data = []
  23. if (fs.existsSync(file)) {
  24. let reader = readline.createInterface({
  25. input: fs.createReadStream(file)
  26. })
  27. reader.on('line', line => {
  28. data.push(line)
  29. })
  30. reader.on('close', () => {
  31. resolve(data)
  32. })
  33. } else {
  34. resolve(data)
  35. }
  36. })
  37. }
  38. async addWatermark(image, file, student, index) {
  39. let fontSize = config.watermark.fontSize
  40. let fontFile = config.watermark.fontFile
  41. let color = config.watermark.color
  42. let imgData = gm(image)
  43. let size = sizeOf(image)
  44. //添加第一页的得分明细
  45. if (index == 1) {
  46. //初始坐标
  47. let x = 30
  48. let y = 10
  49. //最大宽/高限制
  50. let fontSize = 30
  51. let maxX = size.width / 2 - x * 2
  52. let height = fontSize + 10
  53. //计算总分
  54. let totalScore = (parseFloat(student.objectiveScore) || 0) + (parseFloat(student.subjectiveScore) || 0)
  55. //显示总分明细
  56. imgData.font(fontFile, fontSize).fill(color)
  57. imgData.drawText(x, y += height, '成绩明细')
  58. imgData.drawText(x, y += height, '总分=(客观+主观) | ' + totalScore + '=' + student.objectiveScore + '+' + student.subjectiveScore)
  59. //显示客观题明细
  60. if (student.objectiveScoreDetail && student.objectiveScoreDetail.length > 0) {
  61. let lines = []
  62. let array = []
  63. //前置提示文字的字符数
  64. let count = 10
  65. lines.push(array)
  66. for (let i = 0; i < student.objectiveScoreDetail.length; i++) {
  67. let detail = student.objectiveScoreDetail[i]
  68. let content = detail.answer + ':' + detail.score
  69. //超长后另起一行显示客观题
  70. if ((count + content.length) * fontSize * 0.7 > maxX) {
  71. array = []
  72. lines.push(array)
  73. count = 10
  74. }
  75. array.push(content)
  76. count += content.length
  77. }
  78. //显示所有行的客观题明细
  79. for (let l = 0; l < lines.length; l++) {
  80. imgData.drawText(x, y += height, '客观题识别结果 | ' + lines[l].join(';'))
  81. }
  82. }
  83. //显示主观题明细
  84. if (student.subjectiveScoreDetail && student.subjectiveScoreDetail.length > 0) {
  85. let startY = y
  86. imgData.drawText(x, y += height, '题号 | 分数')
  87. for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
  88. let detail = student.subjectiveScoreDetail[i]
  89. //超过最大高度了则另起一列
  90. if ((y + height + 15) > size.height) {
  91. y = startY
  92. x += 300
  93. imgData.drawText(x, y += height, '题号 | 分数')
  94. }
  95. let content = detail.mainNumber + '-' + detail.subNumber + ' : ' + detail.score
  96. if (detail.marker != undefined) {
  97. content = content + ' ' + detail.marker.join(',')
  98. }
  99. imgData.drawText(x, y += height, content)
  100. }
  101. }
  102. }
  103. //显示评卷标记
  104. if (student.tags != undefined && student.tags[index] != undefined) {
  105. let fontSize = 60
  106. let height = fontSize + 10
  107. imgData.font(fontFile, fontSize).fill(color)
  108. let tags = student.tags[index]
  109. for (let i = 0; i < tags.length; i++) {
  110. let tag = tags[i]
  111. if (tag.content != undefined) {
  112. let top = tag.top
  113. for (let j = 0; j < tag.content.length; j++) {
  114. imgData.drawText(tag.left, top, tag.content[j])
  115. top += height
  116. }
  117. }
  118. }
  119. }
  120. return new Promise((resolve, reject) => {
  121. imgData.write(file, error => {
  122. if (error) {
  123. reject(error)
  124. } else {
  125. resolve()
  126. }
  127. })
  128. })
  129. }
  130. async downloadFile(remoteTemplate, localTemplate, data, dir, client, bucket, index, watermark) {
  131. data.index = index
  132. let remote = mustache.render(remoteTemplate, data)
  133. let local = path.join(dir, mustache.render(localTemplate, data))
  134. mkdirp.sync(path.dirname(local))
  135. let imgData
  136. if (config.localStore != undefined && config.localStore.length > 0) {
  137. let cache = path.join(config.localStore, bucket, remote)
  138. if (fs.existsSync(cache)) {
  139. imgData = fs.readFileSync(cache)
  140. }
  141. }
  142. if (imgData == undefined) {
  143. try {
  144. imgData = await client.download(remote)
  145. } catch (err) {
  146. if (err.code === 404) {
  147. //文件不存在,记录日志并跳过
  148. downloadLogger.error('404 ' + bucket + ' ' + remote)
  149. return Promise.resolve()
  150. } else {
  151. logger.error(err)
  152. return Promise.reject(err)
  153. }
  154. }
  155. }
  156. //是否需要添加分数水印
  157. if (watermark) {
  158. await this.addWatermark(imgData, local, data, index)
  159. } else {
  160. await fs.writeFileSync(local, imgData)
  161. }
  162. }
  163. async downloadSheet(dir, template, watermark) {
  164. //从文件中读取已成功下载的准考证号列表
  165. let doneFile = path.join(__dirname, '../../logs/sheet.log')
  166. let finish = await this.readFile(doneFile)
  167. //logger.info(finish)
  168. let success = {}
  169. for (let i = 0; i < finish.length; i++) {
  170. success[finish[i]] = true
  171. }
  172. let bucket = env.server.bucketPrefix + '-sheet'
  173. let client = upyun(bucket, config.upyun.operator, config.upyun.password)
  174. if (env.server.upyunDomain && env.server.upyunDomain != '') {
  175. //局域网模式修改图片服务器地址
  176. client.setDomain(env.server.upyunDomain)
  177. }
  178. try {
  179. let totalCount = await api.countStudents(env.examId, true, undefined)
  180. this.emit('total', totalCount)
  181. let count = 0
  182. let pageNumber = 0
  183. this.emit('count', 0)
  184. while (true) {
  185. pageNumber++
  186. let array = await api.getStudents(env.examId, pageNumber, 10, true, undefined, watermark === true, watermark === true)
  187. if (array == undefined || array.length == 0) {
  188. break
  189. }
  190. for (let i = 0; i < array.length; i++) {
  191. let promises = []
  192. let student = array[i]
  193. let examNumber = student['examNumber']
  194. if (success[examNumber]) {
  195. //在已完成列表中则跳过,不用重复下载
  196. } else {
  197. student.examId = env.examId
  198. for (let i = 1; i <= student.sheetCount; i++) {
  199. promises.push(this.downloadFile(config.imageUrl.sheet, template, student, dir, client, bucket, i, watermark))
  200. }
  201. //等待所有图片下载完毕
  202. await Promise.all(promises)
  203. //finish.push(examNumber)
  204. success[examNumber] = true
  205. //实时回写到记录文件中去
  206. fs.appendFileSync(doneFile, examNumber + '\r\n')
  207. }
  208. count++
  209. this.emit('count', count)
  210. //实时回写到记录文件中去
  211. //fs.writeFileSync(doneFile, finish.join('\r\n'))
  212. }
  213. }
  214. this.emit('finish')
  215. } catch (error) {
  216. logger.error('download sheet error:' + error)
  217. this.emit('error', error)
  218. } finally {
  219. //回写到记录文件中去
  220. //fs.writeFileSync(doneFile, finish.join('\r\n'))
  221. }
  222. }
  223. async downloadPackage(dir, template) {
  224. let bucket = env.server.bucketPrefix + '-package'
  225. let client = upyun(bucket, config.upyun.operator, config.upyun.password)
  226. if (env.server.upyunDomain && env.server.upyunDomain != '') {
  227. client.setDomain(env.server.upyunDomain)
  228. }
  229. try {
  230. let array = await api.getPackages(env.examId, true)
  231. this.emit('total', array.length)
  232. let count = 0
  233. this.emit('count', 0)
  234. for (let i = 0; i < array.length; i++) {
  235. let p = array[i]
  236. p.examId = env.examId
  237. for (let i = 1; i <= p.picCount; i++) {
  238. p.index = i
  239. await this.downloadFile(config.imageUrl.package, template, p, dir, client, bucket)
  240. }
  241. count++
  242. this.emit('count', count)
  243. }
  244. this.emit('finish')
  245. } catch (err) {
  246. logger.error('download package error: ' + err)
  247. this.emit('error', err)
  248. }
  249. }
  250. }
  251. module.exports = function() {
  252. return new executor()
  253. }