openresty实现图片(文件)服务器

匿名 (未验证) 提交于 2019-12-03 00:37:01

点击打开链接

该功能是利用openresty的lua脚本实现的图片(文件)保存功能,文件上传使用java代码开发的

上传数据和文件信息不分前后,但系统只会保存最后一对信息

  • 数据格式:
{"fileDir":"文件保存的目录","fileName":"文件名"}
  • 1
  • 返回结果
{"status":"是否成功","result":"返回结果","msg":"异常原因"} enum status:["success","failed"]


  • 所保存到那个文件夹下,在nginx的perfix变量中定义

如下:

server {     listen       80;     server_name  localhost; # 配置保存的文件夹     set $prefix "/data";      location /uploadimage { # 配置是否每次lua更改都生效,适合调试时使用 #       lua_code_cache off; # 配置lua脚本         content_by_lua_file /openresty-web/luascript/luascript;     } # 用来配合理解传入到nginx的报文结构     location /uploadtest{ #       lua_code_cache off;         content_by_lua_file /openresty-web/luascript/luauploadtest;     }     error_page   500 502 503 504  /50x.html;     location = /50x.html {         root   html;     } }

luascript:

package.path = '/openresty-web/lualib/resty/?.lua;' local upload = require "upload" local cjson = require("cjson")  Result={status="success",result="",msg=""} Result.__index=Result function Result.conSuccess(ret)     ret["status"]="success"     ret["result"]="upload success"     return ret end  function Result.conFailed(ret,err)     ret["status"]="failed"     ret["msg"]=err     ret["result"]="upload failed"     return ret end  function Result:new()     local ret={}     setmetatable({},Result)     return ret end  -- lua-resty-upload local chunk_size = 4096 local form = upload:new(chunk_size) if not form then     ngx.say(cjson.encode(Result.conFailed(Result:new(),"plase upload right info")))     return  end local file local filelen=0 form:set_timeout(0) -- 1 sec local filename local prefix=ngx.var.prefix  -- 匹配文件名,当前案例用于判断是否是文件模块 function get_filename(res)     local filename = ngx.re.match(res,'(.+)filename="(.+)"(.*)')     if filename then          return filename[2]     end end   -- 用来开启输入流,当文件夹不存在时自动创建 function openstream(fileinfo,opt)     local file,err=io.open(prefix..fileinfo["fileDir"],"r")     if not file then         local start=string.find(err,"No such file or directory")         if start then             local exeret=os.execute("mkdir -p "..prefix..fileinfo["fileDir"])             if exeret ~= 0 then                 return nil,"Make directory failed"             end         else             return nil,err         end     end     file,err=io.open(prefix..fileinfo["fileDir"]..fileinfo["fileName"],opt)     return file,err end  local osfilepath local tmpfiletbl local hasFile=false local loopfile=false local fileinfostr local fileinfo local result=Result:new() -- 循环读取文件和文件信息 while true do     local typ, res, err = form:read()     if not typ then         break     end     if typ == "header" then         if res[1] ~= "Content-Type" then             filename = get_filename(res[2])             if filename then                 loopfile=true                 hasFile=true                 -- 判断是否有文件信息                 -- 如果没有记录内存                 if fileinfo then                     file,err=openstream(fileinfo,"w")                     if not file then                         break                     end                 else                     tmpfiletbl={}                 end             else                 loopfile = false                 fileinfostr = ""             end         end     end     if loopfile then         if typ == "body" then             if file then                 filelen= filelen + tonumber(string.len(res))                     file:write(res)             else                 table.insert(tmpfiletbl,res)             end         elseif typ == "part_end" then             if file then                 file:close()                 file = nil             end         end     else         if typ == "body" then             fileinfostr=fileinfostr .. res         elseif typ == "part_end" then             fileinfo = cjson.decode(fileinfostr)         end     end     if typ == "eof" then         break     end end  if not hasFile then     err="plase upload file" elseif not fileinfo or not fileinfo["fileDir"] or not fileinfo["fileName"] then     err="plase offer file info" end  if err then     ngx.log(ngx.ERR,err)     Result.conFailed(result,err)     ngx.say(cjson.encode(result))     return  end  -- 因为有文件信息在文件之后传送的 -- 所以需要将输入到内存中的文件信息打印到磁盘 if tmpfiletbl and table.getn(tmpfiletbl) > 0 then     file,err=openstream(fileinfo,"w")     if not file then         ngx.log(ngx.ERR,err)         Result.conFailed(result,err)         ngx.say(cjson.encode(result))         return      else         for index,value in ipairs(tmpfiletbl)         do             filelen= filelen + tonumber(string.len(value))              file:write(value)         end         file:close()         file=nil     end end   Result.conSuccess(result) ngx.say(cjson.encode(result)) 

luauploadtest:

local upload = require "resty.upload" local cjson = require "cjson"  local chunk_size = 5 -- should be set to 4096 or 8192                      -- for real-world settings  local form, err = upload:new(chunk_size) if not form then     ngx.log(ngx.ERR, "failed to new upload: ", err)     ngx.exit(500) end  form:set_timeout(1000) -- 1 sec  while true do     local typ, res, err = form:read()     if not typ then         ngx.say("failed to read: ", err)         return     end      ngx.say("read: ", cjson.encode({typ, res}))      if typ == "eof" then         break     end end  local typ, res, err = form:read() ngx.say("read: ", cjson.encode({typ, res})) 

luauploadtest代码是官方提供代码

Java

ImageServer:

package cn.com.cgbchina.image;  import cn.com.cgbchina.image.exception.ImageDeleteException; import cn.com.cgbchina.image.exception.ImageUploadException; import org.springframework.web.multipart.MultipartFile;  /**  * Created by 11140721050130 on 16-3-22.  */ public interface ImageServer {     /**      * 删除文件      *      * @param fileName 文件名      * @return 是否删除成功      */     boolean delete(String fileName) throws ImageDeleteException;      /**      *      * @param originalName 原始文件名      * @param file 文件      * @return 文件上传后的相对路径      */     String upload(String originalName, MultipartFile file) throws ImageUploadException; } 

LuaResult:

package cn.com.cgbchina.image.nginx;  import lombok.Getter; import lombok.Setter;  /**  * Comment: 用来保存返回结果,  * 原本想放入到LuaImageServiceImpl的内部类中,  * 但是Jackson不支持,没法反序列化  * Created by ldaokun2006 on 2017/10/24.  */ @Setter @Getter public class LuaResult{     private LuaResultStatus status;     private String result;     private String msg;     private String httpUrl;     public LuaResult(){}      public void setStatus(String result){         status=LuaResultStatus.valueOf(result.toUpperCase());     }     public enum LuaResultStatus{         SUCCESS,FAILED;     } } 

ImageServerImpl:

package cn.com.cgbchina.image.nginx;  import cn.com.cgbchina.common.utils.DateHelper; import cn.com.cgbchina.image.ImageServer; import cn.com.cgbchina.image.exception.ImageDeleteException; import cn.com.cgbchina.image.exception.ImageUploadException; import com.github.kevinsawicki.http.HttpRequest; import com.google.common.base.Splitter; import com.spirit.util.JsonMapper; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile;  import java.io.*; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger;  /**  * Comment: 实现文件上传功能  * Created by ldaokun2006 on 2017/10/16.  */ @Service @Slf4j public class LuaImageServiceImpl implements ImageServer{     // 存放nginx服务器url的,某些架构会有多个放置图片的地方     private List<String> httpUrls;     private ExecutorService fixedThreadPool ;     private Integer timeout;     private int threadSize=50;      public LuaImageServiceImpl(String httpUrls){         this(httpUrls,30000);     }      /**      *      * @param httpUrls 存放nginx服务器url      * @param timeout http超时时间      */     public LuaImageServiceImpl(String httpUrls,int timeout){         this.httpUrls=Splitter.on(";").splitToList(httpUrls);         // 没啥看得,就是想让线程池的名字易懂些         this.fixedThreadPool= new ThreadPoolExecutor(threadSize, threadSize,                 0L, TimeUnit.MILLISECONDS,                 new LinkedBlockingQueue<Runnable>(),new ThreadFactory(){                     private final AtomicInteger poolNumber = new AtomicInteger(1);                     private final ThreadGroup group;                     private final AtomicInteger threadNumber = new AtomicInteger(1);                     private final String namePrefix;                      {                         SecurityManager s = System.getSecurityManager();                         group = (s != null) ? s.getThreadGroup() :                                 Thread.currentThread().getThreadGroup();                         namePrefix = "LuaUploadPool-" +                                 poolNumber.getAndIncrement() +                                 "-thread-";                     }                      public Thread newThread(Runnable r) {                         Thread t = new Thread(group, r,                                 namePrefix + threadNumber.getAndIncrement(),                                 0);                         if (t.isDaemon())                             t.setDaemon(false);                         if (t.getPriority() != Thread.NORM_PRIORITY)                             t.setPriority(Thread.NORM_PRIORITY);                         return t;                     }                 });         this.timeout=timeout;     }      /**      * Comment: 没必要开发删除功能      * @param fileName 文件名      * @return      * @throws ImageDeleteException      */     @Override     public boolean delete(String fileName) throws ImageDeleteException {         return true;     }      /**      * Commont: 用来给SpringMVC用      * @param originalName 原始文件名      * @param file 文件      * @return      * @throws ImageUploadException      */     @Override     public String upload(String originalName, MultipartFile file) throws ImageUploadException {         try {             return this.upload(originalName,file.getInputStream());         } catch (IOException e) {             log.error("upload fail : " + e.getMessage(), e);             throw new ImageUploadException("upload fail : "+e.getMessage(),e);         }     }      /**      * Commont: 上传图片核心代码      * @param originalName 原始文件名      * @param inputStream 要上传文件的文件流      * @return      * @throws ImageUploadException      */     private String upload(String originalName,InputStream inputStream) throws ImageUploadException {         ByteArrayOutputStream byteOutStream = null;         try {             //准备数据             byte[] tmpData=new byte[1024];             byte[] inputData;             byteOutStream = new ByteArrayOutputStream();             int len=0;             while((len=inputStream.read(tmpData,0,tmpData.length))!=-1){                 byteOutStream.write(tmpData,0,len);             }             inputData=byteOutStream.toByteArray();             LuaSend sendInfo = new LuaSend(generateFileDir(),generateFileName(originalName));             List<Future<LuaResult>> resultList=new ArrayList<>(httpUrls.size());              //发送图片             for(String httpUrl:httpUrls) {                 SendImg sendImg = new SendImg(httpUrl,sendInfo, inputData,this.timeout);                 resultList.add(fixedThreadPool.submit(sendImg));             }             for(Future<LuaResult> future:resultList) {                 // 线程池异常在这里抛出                 LuaResult resultLuaResult = future.get();                 if (LuaResult.LuaResultStatus.SUCCESS != resultLuaResult.getStatus()) {                     throw new ImageUploadException("lua result url:"+resultLuaResult.getHttpUrl()+" msg : " + resultLuaResult.getMsg());                 }             }              return sendInfo.toString();         }catch (Exception e){             log.error("upload fail : "+e.getMessage(),e);             throw new ImageUploadException("upload fail : "+e.getMessage(),e);         }finally {             try {                 if(byteOutStream!=null) {                     byteOutStream.close();                 }                 if(inputStream!=null) {                     inputStream.close();                 }             } catch (IOException e) {                 throw new ImageUploadException("upload fail : "+e.getMessage(),e);             }         }     }     String separator=File.separator;     String dateFormat=separator+"yyyy"+separator+"MM"+separator+"dd"+ separator;      /**      * Comment:根据时间做路径,防止某一个文件夹东西太多      * @return 返回要保存的路径      */     private String generateFileDir(){         return DateHelper.date2string(new Date(),dateFormat);     }      /**      * Comment: 用UUID防止文件名重复      * @param originalName 源文件名字      * @return 要保存的文件名      */     private String generateFileName(String originalName){         return UUID.randomUUID().toString();     }      /**      * Comment: 用来发送图片的      */     @AllArgsConstructor     class SendImg implements  Callable<LuaResult>{          private String httpUrl;         private LuaSend sendInfo;         private byte[] inputStream;         private Integer timeout;           @Override         public LuaResult call() throws Exception {             try {                 String resultStr = HttpRequest                         .post(httpUrl, false)                         .part("fileInfo", JsonMapper.JSON_NON_EMPTY_MAPPER.toJson(sendInfo))                         // 这个地方有个坑,part上传图片必须要用这个方式,                         // 不能用没有Content-Type和fileName的                         .part("file", sendInfo.getFileName(), "multipart/form-data; boundary=00content0boundary00", new ByteArrayInputStream(inputStream))                         .connectTimeout(timeout).body();                 log.info("result:"+resultStr);                 LuaResult result = JsonMapper.JSON_NON_DEFAULT_MAPPER.fromJson(resultStr, LuaResult.class);                 result.setHttpUrl(httpUrl);                 return result;             }catch(Exception e){                 throw new ImageUploadException("upload failed url:"+httpUrl+" info:"+sendInfo.toString(),e);             }         }     }      /**      * Comment:文件数据      */     @Setter     @Getter     @AllArgsConstructor     class LuaSend {         // 文件目录         private String fileDir;         // 文件名         private String fileName;         @Override         public String toString(){             return fileDir+fileName;         }     }       /**      * Comment:测试用      * @param args      * @throws ImageUploadException      * @throws FileNotFoundException      */     public static void main(String[] args) throws ImageUploadException, FileNotFoundException {         LuaImageServiceImpl service=new LuaImageServiceImpl("http://192.168.99.102/uploadimage");         try {             System.out.println(service.upload("qqqqq", new FileInputStream("D:\\shsh.txt")));         }finally {             service.fixedThreadPool.shutdown();         }     } } 

  1. 上传两个图片或图片信息时系统只保留最后一个信息
  2. 图片和图片信息可以随意放置,但是这两个必须成对发送,建议先发送图片信息后发送图片,这样图片不用在lua处保存到内存中
  3. 上传大图片时会出现文件太大的提示,需要在nginx配置文件中添加client_max_body_size 100M;
  4. Http Header的Content-Type必须使用,boundary必须存在不然不好用
  5. 传送图片HttpRequest.part上传图片必须写明Content-type和fileName,不然不好用但是Content-type不用非的用例子上的方式
  6. 图片信息必须拷贝成byte型,因为多线程使用时需要各自发送
  1. 传送图片HttpRequest.part上传图片必须写明Content-type,不然不好用
  2. Jackson和fastjson对于需要反序列化的类,必须有无参构造函数,并且不能是内部类
  3. lua的string.find如果没有找到,返回结果为nil
  4. CSDN的编辑器,无需功能不好用
  1. HttpRequest.part用来上传Content-type:multipart/form-data;
  2. lua的使用:http://www.runoob.com/lua/lua-tutorial.html
  3. openresty的api:http://openresty.org/cn/components.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!