articles.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. const express = require('express');
  2. const router = express.Router();
  3. const {Article} = require('../../models')
  4. const {Op} = require('sequelize')
  5. /*
  6. 查询文章列表
  7. GET /admin/articles
  8. */
  9. router.get('/', async function(req, res, next) {
  10. try {
  11. const query = req.query
  12. //当前是第几页,如果不传,那就是第一页
  13. const currentPage = Math.abs(Number(query.currentPage)) || 1
  14. //每页显示多少条数据,如果不传,那就显示10条
  15. const pageSize = Math.abs(Number(query.pageSize)) || 10
  16. //计算 offset
  17. const offset = (currentPage - 1) * pageSize
  18. const condition = {
  19. order:[['id','DESC']],
  20. limit:pageSize,
  21. offset
  22. }
  23. if(query.title){
  24. condition.where = {
  25. title:{
  26. [Op.like]:`%${query.title}%`
  27. }
  28. }
  29. }
  30. const {count ,rows} = await Article.findAndCountAll(condition)
  31. res.json({
  32. status:true,
  33. message:'成功',
  34. data:{
  35. articles:rows,
  36. pagination:{
  37. total:count,
  38. currentPage,
  39. pageSize
  40. }
  41. }
  42. });
  43. }catch(error){
  44. res.status(500).json({
  45. status:false,
  46. message:'失败',
  47. errors:[error.message]
  48. });
  49. }
  50. });
  51. /*
  52. 查询文章详情
  53. GET /admin/articles/:id
  54. */
  55. router.get('/:id', async function(req, res, next) {
  56. try {
  57. //获取文章id
  58. const {id} = req.params
  59. //查询文章
  60. const article = await Article.findByPk(id)
  61. if(article){
  62. res.json({
  63. status:true,
  64. message:'成功',
  65. data:article
  66. });
  67. }else{
  68. res.status(404).json({
  69. status:false,
  70. message:'文章未找到',
  71. });
  72. }
  73. }catch(error){
  74. res.status(500).json({
  75. status:false,
  76. message:'失败',
  77. errors:[error.message]
  78. });
  79. }
  80. });
  81. /*
  82. 创建文章
  83. POST /admin/articles/
  84. */
  85. router.post('/', async function(req, res, next) {
  86. try {
  87. // 添加请求日志
  88. console.log('=== 创建文章请求开始 ===');
  89. console.log('请求体大小:', JSON.stringify(req.body).length);
  90. console.log('Content字段长度:', req.body.content ? req.body.content.length : 0);
  91. console.log('Title字段长度:', req.body.title ? req.body.title.length : 0);
  92. //白名单过滤
  93. const body = filterBody(req)
  94. console.log('过滤后的数据:', {
  95. titleLength: body.title ? body.title.length : 0,
  96. contentLength: body.content ? body.content.length : 0,
  97. hasImage: !!body.img,
  98. type: body.type
  99. });
  100. const article = await Article.create(body)
  101. console.log('文章创建成功, ID:', article.id);
  102. console.log('=== 创建文章请求结束 ===');
  103. res.status(201).json({
  104. status:true,
  105. message:'成功',
  106. data:article
  107. });
  108. }catch(error){
  109. // 添加详细的错误日志
  110. console.error('=== 创建文章错误 ===');
  111. console.error('错误名称:', error.name);
  112. console.error('错误消息:', error.message);
  113. console.error('错误堆栈:', error.stack);
  114. console.error('请求体大小:', JSON.stringify(req.body).length);
  115. console.error('请求体:', JSON.stringify(req.body, null, 2));
  116. if(error.message === '标题不能为空' || error.message === '内容不能为空' ||
  117. error.message.includes('长度不能超过') || error.message.includes('不允许的脚本标签')){
  118. res.status(400).json({
  119. status:false,
  120. message:'请求参数错误',
  121. errors:[error.message]
  122. });
  123. }else if(error.name === 'SequelizeValidationError'){
  124. const errors = error.errors.map(e => e.message)
  125. res.status(400).json({
  126. status:false,
  127. message:'数据验证失败',
  128. errors
  129. });
  130. }else if(error.name === 'SequelizeDatabaseError'){
  131. console.error('数据库错误详情:', error.original);
  132. res.status(500).json({
  133. status:false,
  134. message:'数据库错误',
  135. errors:['数据库操作失败,请稍后重试']
  136. });
  137. }else if(error.name === 'SequelizeConnectionError'){
  138. res.status(500).json({
  139. status:false,
  140. message:'数据库连接错误',
  141. errors:['数据库连接失败,请稍后重试']
  142. });
  143. }else{
  144. res.status(500).json({
  145. status:false,
  146. message:'服务器内部错误',
  147. errors:['服务器处理请求时发生错误,请稍后重试']
  148. });
  149. }
  150. }
  151. });
  152. /*
  153. 删除文章
  154. GET /admin/articles/:id
  155. */
  156. router.delete('/:id', async function(req, res, next) {
  157. try {
  158. //获取文章id
  159. const {id} = req.params
  160. //查询文章
  161. const article = await Article.findByPk(id)
  162. if(article){
  163. await article.destroy()
  164. res.json({
  165. status:true,
  166. message:'成功',
  167. });
  168. }else{
  169. res.status(404).json({
  170. status:false,
  171. message:'文章未找到',
  172. });
  173. }
  174. }catch(error){
  175. res.status(500).json({
  176. status:false,
  177. message:'失败',
  178. errors:[error.message]
  179. });
  180. }
  181. });
  182. /*
  183. 更新文章
  184. GET /admin/articles/:id
  185. */
  186. router.put('/:id', async function(req, res, next) {
  187. try {
  188. //获取文章id
  189. const {id} = req.params
  190. //查询文章
  191. const article = await Article.findByPk(id)
  192. //白名单过滤
  193. const body = filterBody(req)
  194. if(article){
  195. await article.update(body)
  196. res.json({
  197. status:true,
  198. message:'成功',
  199. data:article
  200. });
  201. }else{
  202. res.status(404).json({
  203. status:false,
  204. message:'文章未找到',
  205. });
  206. }
  207. }catch(error){
  208. res.status(500).json({
  209. status:false,
  210. message:'失败',
  211. errors:[error.message]
  212. });
  213. }
  214. });
  215. function filterBody(req){
  216. try {
  217. // 数据清理和验证
  218. const body = {
  219. title: req.body.title ? String(req.body.title).trim() : null,
  220. content: req.body.content ? String(req.body.content) : null,
  221. type: req.body.type ? parseInt(req.body.type) : null,
  222. img: req.body.img ? String(req.body.img).trim() : null,
  223. date: req.body.date ? new Date(req.body.date) : null,
  224. author: req.body.author ? String(req.body.author).trim() : null,
  225. category: req.body.category ? parseInt(req.body.category) : null,
  226. crop: req.body.crop ? parseInt(req.body.crop) : null,
  227. seoKeyword: req.body.seoKeyword ? String(req.body.seoKeyword).trim() : null,
  228. seoDescription: req.body.seoDescription ? String(req.body.seoDescription).trim() : null
  229. };
  230. // 验证必填字段
  231. if (!body.title) {
  232. throw new Error('标题不能为空');
  233. }
  234. if (!body.content) {
  235. throw new Error('内容不能为空');
  236. }
  237. // 验证标题长度 - 放宽限制以适应富文本编辑器
  238. if (body.title.length > 500) {
  239. throw new Error('标题长度不能超过500个字符');
  240. }
  241. // 验证内容长度 - 防止过大的内容
  242. if (body.content.length > 5000000) { // 5MB限制
  243. throw new Error('内容过长,请减少内容长度');
  244. }
  245. // 检查富文本内容是否包含危险标签或脚本
  246. const dangerousTags = /<script[^>]*>.*?<\/script>/gi;
  247. if (dangerousTags.test(body.content)) {
  248. throw new Error('内容包含不允许的脚本标签');
  249. }
  250. return body;
  251. } catch (error) {
  252. console.error('filterBody错误:', error);
  253. throw error;
  254. }
  255. }
  256. module.exports = router;