image.js 8.5 KB

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