matchRegistration.controller.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
  2. import { Validate } from '@midwayjs/validate';
  3. import { Controller, Inject, Get, Param, Post, Body, Del, Query, Config } from '@midwayjs/core';
  4. import { cloneDeep, get, head, isArray, omit, pick } from 'lodash';
  5. import { ServiceError, ErrorCode } from '../../error/service.error';
  6. import { BaseController } from '../../frame/BaseController';
  7. import { MatchRegistrationService } from '../../service/match/matchRegistration.service';
  8. import { UserService } from '../../service/system/user.service';
  9. import * as bcrypt from 'bcryptjs';
  10. import * as Path from 'path'
  11. import dayjs = require('dayjs');
  12. import { MatchService } from '../../service/platform/match.service';
  13. import { ServiceUtilService } from '../../service/serviceUtil.service';
  14. import * as Excel from 'exceljs';
  15. import { UtilService } from '../../service/util.service';
  16. import { AliyunSmsService } from '../../service/thirdParty/aliyunSms.service';
  17. const namePrefix = '创新大赛-赛事报名';
  18. @ApiTags(['创新大赛-赛事报名'])
  19. @Controller('/matchReg', { tagName: namePrefix })
  20. export class MatchRegistrationController implements BaseController {
  21. @Inject()
  22. service: MatchRegistrationService;
  23. @Inject()
  24. userService: UserService;
  25. @Inject()
  26. matchService: MatchService
  27. @Inject()
  28. serviceUtil: ServiceUtilService
  29. @Inject()
  30. utilService: UtilService;
  31. @Inject()
  32. smsService: AliyunSmsService
  33. @Config('PathConfig.path')
  34. path;
  35. @Get('/')
  36. @ApiTags('列表查询')
  37. @ApiQuery({ name: 'query' })
  38. async index(@Query() query: object) {
  39. const qobj = omit(query, ['skip', 'limit']);
  40. const others: any = pick(query, ['skip', 'limit']);
  41. others.order = { time: 'ASC' };
  42. const { data, total } = await this.service.query(qobj, others);
  43. const fillList = []
  44. for (const i of data) {
  45. let newItem = cloneDeep(i)
  46. const user_id = get(i, 'user_id')
  47. const user = await this.userService.fetch({ id: user_id })
  48. if (user) newItem.user_name = get(user, 'nick_name')
  49. const match_id = get(i, 'match_id')
  50. const match = await this.matchService.fetch({ id: match_id })
  51. if (match) newItem.match_name = get(match, 'name')
  52. fillList.push(newItem)
  53. }
  54. return { data: fillList, total };
  55. }
  56. @Get('/:id')
  57. @ApiTags('单查询')
  58. async fetch(@Param('id') id: number) {
  59. const data = await this.service.fetch({ id });
  60. return data;
  61. }
  62. @Post('/', { routerName: `创建${namePrefix}` })
  63. @ApiTags('创建数据')
  64. @Validate()
  65. async create(@Body() data: object) {
  66. const userColumns = ['user', 'user_id'];
  67. let regData = omit(data, userColumns);
  68. let returnUser = {};
  69. if (get(data, 'user_id')) {
  70. regData = { user_id: get(data, 'user_id'), ...regData };
  71. } else {
  72. const user = get(data, 'user');
  73. if (!user) throw new ServiceError(ErrorCode.MATCH_NEED_USER_INFO);
  74. // 注册用户
  75. await this.userService.createExamine(user);
  76. await this.userService.checkPhone(user);
  77. await this.userService.checkEmail(user);
  78. // 处理密码, TODO:可能会要自动生成
  79. const password = get(user, 'password');
  80. if (password) {
  81. const salt = bcrypt.genSaltSync(10);
  82. const hash = bcrypt.hashSync(password, salt);
  83. Object.assign(user, { password: hash });
  84. }
  85. returnUser = { password, account: get(user, 'account') }
  86. // 发送短信
  87. try {
  88. const phone = get(user, 'phone')
  89. const msg = { "账号": get(user, 'account'), "密码": password }
  90. await this.smsService.send(phone, msg)
  91. } catch (error) {
  92. console.error('matchReg - create:发送短信发生错误')
  93. }
  94. const dbData = await this.userService.create(user);
  95. regData = { user_id: dbData.id, ...regData };
  96. }
  97. // 检查是否报名
  98. const query = { match_id: get(data, 'match_id'), user_id: get(regData, 'user_id') };
  99. const { total } = await this.service.query(query);
  100. if (total > 0) throw new ServiceError(ErrorCode.MATCH_USER_HAS_REGISTED);
  101. // 制作项目编号
  102. const { total: matchRegTotal = 0 } = await this.service.query({ match_id: get(data, 'match_id') });
  103. const no = `${get(data, 'match_id')}-${get(regData, 'user_id')}-${matchRegTotal + 1}`;
  104. regData = { ...regData, no, time: dayjs().format('YYYY-MM-DD HH:mm:ss') };
  105. const result = await this.service.create(regData);
  106. if (Object.keys(returnUser).length > 0) return returnUser
  107. else return result;
  108. }
  109. @Post('/:id', { routerName: `修改${namePrefix}` })
  110. @ApiTags('修改数据')
  111. @Validate()
  112. async update(@Param('id') id: number, @Body() data: object) {
  113. if (!id) throw new ServiceError(ErrorCode.ID_NOT_FOUND);
  114. const result = await this.service.update({ id }, data);
  115. if (get(result, 'stauts') === '-1') {
  116. //被退回,发送短信
  117. const user_id = get(result, 'user_id')
  118. const user = await this.userService.fetch({ id: user_id })
  119. if (!user) throw new ServiceError(ErrorCode.USER_NOT_FOUND)
  120. const match_id = get(result, 'match_id')
  121. const match = await this.matchService.fetch({ id: match_id })
  122. if (!match) throw new ServiceError(ErrorCode.MATCH_NOT_FOUND)
  123. const match_name = get(match, 'match_name')
  124. const phone = get(user, 'phone')
  125. const msg = `您提交的的 ${match_name} 赛事申请已被退回,请登录平台进行修改后提交`
  126. try {
  127. await this.smsService.send(phone, msg)
  128. } catch (error) {
  129. console.error('matchReg - update:发送短信发生错误')
  130. }
  131. }
  132. return result;
  133. }
  134. @Del('/:id', { routerName: `删除${namePrefix}` })
  135. @ApiTags('删除数据')
  136. @Validate()
  137. async delete(@Param('id') id: number) {
  138. if (!id) throw new ServiceError(ErrorCode.ID_NOT_FOUND);
  139. const result = await this.service.delete({ id });
  140. return result;
  141. }
  142. @Get('/detail/:id')
  143. async detail(@Param('id') id: string) {
  144. const data = await this.service.fetch({ id });
  145. return data;
  146. }
  147. @Get('/view/:match_id', { routerName: '查看初赛名单结果' })
  148. async viewOrderByScore(@Param('match_id') match_id: string) {
  149. const query = { match_id, status: '0' }
  150. const others = { order: { score: 'DESC' } }
  151. const { data: list } = await this.service.query(query, others)
  152. const match = await this.matchService.fetch({ id: match_id })
  153. const match_name = get(match, 'name')
  154. for (const i of list) {
  155. const user_id = get(i, 'user_id')
  156. const user = await this.userService.fetch({ id: user_id })
  157. if (user) i.user_name = get(user, 'nick_name')
  158. if (match_name) i.match_name = match_name
  159. delete i.info;
  160. }
  161. return list
  162. }
  163. @Get('/export/:match_id', { routerName: `导出初赛名单` })
  164. @ApiTags('导出初赛名单')
  165. async exportList(@Param('match_id') match_id: string) {
  166. // 查询所有未被退回的报名信息
  167. const { data } = await this.service.query({ match_id, status: '0' })
  168. // 没有人,提示该赛事没有报名人员,无法导出
  169. if (data.length <= 0) throw new ServiceError(ErrorCode.MATCH_NO_PERSON_TO_EXPORT)
  170. const match = await this.matchService.fetch({ id: match_id })
  171. // 没有找到赛事信息
  172. if (!match) throw new ServiceError(ErrorCode.MATCH_NOT_FOUND)
  173. // 获取赛事名称, 赛事名称放首行
  174. const match_name = get(match, 'name')
  175. const fir = ['赛事名称', match_name]
  176. // 第二行是表头: 项目编号,用户名称,报名时间, ...报名信息,分数
  177. const sec = ['项目编号', '用户名称', '报名时间']
  178. // 将报名信息的问题塞进来, 并整理出文件列,计算出文件列的字母
  179. const firstData = head(data);
  180. const info = get(firstData, 'info', [])
  181. const fileColumns = []
  182. for (const i of info) {
  183. const problem = get(i, 'problem')
  184. sec.push(problem)
  185. const type = get(i, 'type')
  186. if (type === '5' || type === '7') {
  187. const length = sec.length
  188. const col = this.utilService.ToExcelColumn(length - 1)
  189. const sid = get(i, 'sid')
  190. fileColumns.push({ sid, col })
  191. }
  192. }
  193. // 填充分数列
  194. sec.push('分数')
  195. const workbook = new Excel.Workbook();
  196. const sheet = workbook.addWorksheet('sheet1');
  197. sheet.addRow(fir)
  198. sheet.addRow(sec);
  199. // 整理数据
  200. for (let index = 0; index < data.length; index++) {
  201. const i = data[index];
  202. const item = [];
  203. // 项目编号
  204. const no = get(i, 'no', "")
  205. item.push(no)
  206. // 用户信息
  207. const user = await this.userService.fetch({ id: get(i, 'user_id') });
  208. if (user) item.push(get(user, 'nick_name'))
  209. else item.push("")
  210. // 报名时间
  211. const time = get(i, 'time', "")
  212. item.push(time)
  213. // 处理报名信息
  214. const info = get(i, 'info', [])
  215. for (const is of info) {
  216. const type = get(is, 'type')
  217. const val = get(is, 'reply', '')
  218. // 0:单选;2:下拉;3:单行文本;4:多行文本
  219. if (type === '0' || type === '2' || type === '3' || type === '4') {
  220. item.push(val)
  221. } else if (type === '1') {
  222. // 1:多选
  223. item.push(val.join(';'))
  224. } else if (type === '5' || type === '7') {
  225. // 5:图片/文件;7:附件模板
  226. // 1.需要确定单元格位置,生成超链接格式,1个单元格只能有一个超链接
  227. const sid = get(is, 'sid')
  228. const headFile = head(val)
  229. const link = {
  230. text: get(headFile, 'name'),
  231. hyperlink: get(headFile, 'uri'),
  232. toolTip: get(headFile, 'uri'),
  233. }
  234. const posObj = fileColumns.find(f => f.sid === sid)
  235. if (!posObj) continue;
  236. posObj.value = link;
  237. posObj.line = index + 1 + 2; // +1 是索引加1为行数; +2 是表格从第三行开始的
  238. // for (const valI of val) {
  239. // const fileName = get(valI, 'name')
  240. // // TODO:需要加上域名
  241. // const uri = get(valI, 'uri')
  242. // const link = {
  243. // text: fileName,
  244. // hyperlink: uri,
  245. // tooltip: uri
  246. // }
  247. // }
  248. } else if (type === '6') {
  249. // 6:数组,将sid外的 key,value拼成字符串作为内容
  250. if (!isArray(val)) continue;
  251. const midArr = []
  252. for (const valI of val) {
  253. const obj = omit(valI, ['sid'])
  254. for (const key in obj) {
  255. midArr.push(`${key}: ${obj[key]}`)
  256. }
  257. }
  258. // 将键值对拼成字符串
  259. item.push(midArr.join(';'))
  260. }
  261. }
  262. sheet.addRow(item)
  263. }
  264. // 处理超链接
  265. for (const i of fileColumns) {
  266. const col = get(i, 'col')
  267. const line = get(i, 'line')
  268. const pos = `${col}${line}`
  269. const value = get(i, 'value')
  270. if (!value) continue;
  271. sheet.getCell(pos).value = value
  272. }
  273. const nowDate = new Date().getTime();
  274. const filename = `${match_name}初赛名单-${nowDate}.xlsx`
  275. const path = this.path;
  276. if (!path) {
  277. throw new ServiceError('服务端没有设置存储路径');
  278. }
  279. const filepath = Path.resolve(path, filename);
  280. await workbook.xlsx.writeFile(filepath);
  281. return `/files/cxyy/export/${filename}`;
  282. }
  283. }