使用openresty的lua-resty-upload实现文件上传

一、实现方式

1、前端:前端使用jquery+webuploader来实现图片上传和预览

使用webuploader来实现模拟表单上传,将上传请求发给服务端,待服务端上传处理完成后将处理结果显示在页面上

2、服务端:使用openresty的lua-resty-upload模块来实现文件上传

接受前端的上传请求,将处理好的图片保存起来,将图片地址返回给前端

二、项目目录结构

  • 2016为图片上传目录
  • conf为nginx.conf配置文件目录
  • html为前端文件目录(包括js和css等)
  • logs为日志目录
  • upload.lua为处理上传的lua代码

三、实现细节:

1、前端

前端使用jquery+webuploader来实现图片上传和预览

(1)、html代码:

要加载jquery库文件和webuploader文件

  1. <div id="wrapper">
  2. <div class="page-body">
  3. <div id="post-container" class="container">
  4. <div class="page-container">
  5. <h1 id="demo">Demo</h1>
  6. <p>您可以尝试文件拖拽,使用QQ截屏工具,然后激活窗口后粘贴,或者点击添加图片按钮,来体验此demo.</p>
  7. <div id="uploader" class="wu-example">
  8. <div class="queueList">
  9. <div id="dndArea" class="placeholder">
  10. <div id="filePicker"><!--按钮位置--></div>
  11. <p>或将照片拖到这里,单次最多可选300张</p>
  12. </div>
  13. </div>
  14. </div>
  15. <div id="fileList">
  16. <!--图片展示位置-->
  17. </div>
  18. </div>
  19. </div>
  20. </div>
  21. </div>
(2)、js代码
  1. // 初始化Web Uploader
  2. var uploader = WebUploader.create({
  3. // 选完文件后,是否自动上传。
  4. auto: true,
  5. // swf文件路径
  6. swf: '/webuplader/images/Uploader.swf',
  7. // 文件接收服务端。
  8. server: '/upload',
  9. // 选择文件的按钮。可选。
  10. // 内部根据当前运行是创建,可能是input元素,也可能是flash.
  11. pick: {
  12. id: '#filePicker',
  13. label: '点击选择图片'
  14. },
  15. dnd: '#uploader .queueList',
  16. paste: document.body,
  17. // 只允许选择图片文件。
  18. accept: {
  19. title: 'Images',
  20. extensions: 'gif,jpg,jpeg,bmp,png',
  21. mimeTypes: 'image/*'
  22. }
  23. });
  24. uploader.on( 'uploadAccept', function( file, response ) {
  25. console.log(response)
  26. if (typeof response != 'undefined' && typeof response.code != 'undefined') {
  27. if (response.code == 200) {
  28. $('#fileList').append('<img src="http://openfile.shixinke.com/images/posts/2016/05/span>+response.imgurl+'">');
  29. } else {
  30. alert(response.msg)
  31. }
  32. } else {
  33. console.log(response);
  34. }
  35. });
  36. uploader.on( 'uploadError', function( file ) {
  37. $( '#'+file.id ).find('p.state').text('上传出错');
  38. });
  39. uploader.on( 'uploadComplete', function( file ) {
  40. $( '#'+file.id ).find('.progress').fadeOut();
  41. });
2、nginx配置
  1. lua_package_path '/data/www/openrestyproject/upload/lib/?.lua;/data/www/openrestyproject/upload/?.lua;;';
  2. lua_code_cache off;
  3. server {
  4. server_name localhost;
  5. listen 8000;
  6. charset utf-8;
  7. set $UPLOAD_ROOT /data/www/openrestyproject/upload;
  8. error_log /data/www/openrestyproject/upload/logs/error.log;
  9. access_log /data/www/openrestyproject/upload/logs/access.log main;
  10. location /upload {
  11. default_type text/html;
  12. content_by_lua_file $UPLOAD_ROOT/upload.lua;
  13. }
  14. }
3、服务端

使用openresty的lua-resty-upload模块来实现文件上传

  1. local upload = require "resty.upload"
  2. local cjson = require "cjson"
  3. local chunk_size = 4096
  4. local form = upload:new(chunk_size)
  5. local conf = {max_size=1000000, allow_exts={'jpg', 'png', 'gif'}}
  6. local file
  7. local file_name
  8. --获取文件扩展名
  9. function get_ext(res)
  10. local ext = 'jpg'
  11. if res == 'image/png' then
  12. ext = 'png'
  13. elseif res == 'image/jpg' or res == 'image/jpeg' then
  14. ext = 'jpg'
  15. elseif res == 'image/gif' then
  16. ext = 'gif'
  17. end
  18. return ext
  19. end
  20. --判断某个值是否在数组中
  21. function in_array(v, tab)
  22. local i = false
  23. for _, val in ipairs(tab) do
  24. if val == v then
  25. i = true
  26. break
  27. end
  28. end
  29. return i
  30. end
  31. while true do
  32. local typ, res, err = form:read()
  33. if typ == "header" then
  34. if res[1] ~= "Content-Disposition" then
  35. local file_id = ngx.md5('upload'..os.time())
  36. local extension = get_ext(res[2])
  37. if not extension then
  38. ngx.say(cjson.encode({code=501, msg='未获取文件后缀', data=res}))
  39. return
  40. end
  41. if not in_array(extension, conf.allow_exts) then
  42. ngx.say(cjson.encode({code=501, msg='不支持这种文件格式', data=res}))
  43. return
  44. end
  45. -- 注:这个地方请将/data/www/openrestyproject/upload/换成你的项目目录
  46. local dir = '/data/www/openrestyproject/uploadhttp://openfile.shixinke.com/images/posts/2016/05/span>..os.date('%Y')..'http://openfile.shixinke.com/images/posts/2016/05/span>..os.date('%m')..'http://openfile.shixinke.com/images/posts/2016/05/span>..os.date('%d')..'http://openfile.shixinke.com/images/posts/2016/05/span>
  47. local status = os.execute('mkdir -p '..dir)
  48. if status ~= 0 then
  49. ngx.say(cjson.encode({code=501, msg='创建目录失败'}))
  50. return
  51. end
  52. file_name = dir..file_id.."."..extension
  53. if file_name then
  54. file = io.open(file_name, "w+")
  55. if not file then
  56. ngx.say(cjson.encode({code=500, msg='failed to open file',imgurl=''}))
  57. return
  58. end
  59. end
  60. end
  61. elseif typ == "body" then
  62. if type(tonumber(res)) == 'number' and tonumber(res) > conf.max_size then
  63. ngx.say(cjson.encode({code=501, msg='文件超过规定大小', data=res}))
  64. return
  65. end
  66. if file then
  67. file:write(res)
  68. end
  69. elseif typ == "part_end" then
  70. if file then
  71. file:close()
  72. file = nil
  73. end
  74. elseif typ == "eof" then
  75. file_name = string.gsub(file_name, '/data/www/openrestyproject/uploadhttp://openfile.shixinke.com/images/posts/2016/05/span>, '')
  76. -- 注:这个地方请将/data/www/openrestyproject/upload/换成你的项目目录
  77. ngx.say(cjson.encode({code=200, msg='上传成功!',imgurl= file_name}))
  78. break
  79. else
  80. end
  81. end

四、项目效果

项目源码下载地址:https://github.com/shixinke/openresty-practices/tree/master/upload

1、上传前:

2、上传后: