What's the deal with recent wave of 'Go is not fit for web apps'?

blov · · 1302 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>This specially comes from people to want to move to Elixir. Do you find their reasons for this valid? Have you had the same negative experience in your big Go based web app development?</p> <hr/>**评论:**<br/><br/>jerf: <pre><p>The Erlang crew is fairly pissed off at Go&#39;s popularity. They feel like they&#39;ve been doing it for 15+ years without anybody noticing, and along comes this upstart Go that is missing two-thirds of what makes Erlang special and steals most of their thunder.</p> <p>Lest I sound like I&#39;m being too harsh on them, they&#39;re largely correct. I was using Erlang for years before Go even existed, and at the time it was something special a lot of people overlooked.</p> <p>A lot of the reason why it was overlooked was its syntax, which is <a href="https://news.ycombinator.com/item?id=7277957">not Algol-derived</a>. Elixir takes it back into a domain where, even if it has a couple of differences, looks more like a language people are used to again.</p> <p>That said, it does not particularly seem to me that Elixir/Erlang&#39;s and Go&#39;s web stories are all that different.</p> <p>My personal feeling is that the relationship between the communities ought to be a great deal more friendly. Erlang ought to see Go&#39;s existence as validation rather than competition, because there is no universe in which Erlang is lauded and loved but somehow nobody ever copies any of its features and it is forever unique. Languages showing up with similarities to it is an inevitable aspect of success, so why not interpret it as success? And there are things it could profitably learn from Go, for instance, it could really use a &#34;channel&#34;-like abstraction because it turns out to be impossible to write natively in Erlang. And, correspondingly, the Go community can learn things from Erlang; certain semantic differences will always stand between the two languages (preferring channels vs. preferring PIDs being a big one, they are fundamentally different things) but there&#39;s still lessons that can be learned about reliability and convenient clustering and such, and structuring large, reliable programs out of thousands of microthreads.</p> <p>But instead, we seem to get a lot of rancor and sneering dismissals. It&#39;s not really useful for two language communities that, however much they might like to think otherwise, are <em>both</em> quite small compared to the Big Hitters like Java or C++ or even mature dynamic language communities like Python. It&#39;s a real waste of energy.</p></pre>Ironballs: <pre><p>Go is fine, or rather, excellent, for creating web APIs. The standard library is more than sufficient and HTTP testing is easy (the httptest package is quite useful).</p> <p>The problem lies in the fact that people aren&#39;t yet used to writing web apps using APIs. The current growing trend is to have a web API and then have your frontend use the API for business logic. This is a great design, but it isn&#39;t as easy to develop on compared to traditional MVC frameworks.</p> <p>What is more, most of the industry isn&#39;t using this method: people still rely on Rails-like MVC frameworks where such a separation doesn&#39;t exist. In Elixir the Phoenix framework is popular and more or less works the same way, in fact, one of its developers is a core Rails contributor.</p> <p>To say &#34;people want to move to Elixir&#34; because Go isn&#39;t suited for it is, I think, a bit of a fallacy, since people will always flock to <em>frameworks</em> first. Java is popular because Spring and the likes are great systems to begin with (and the JVM is a solid piece of software engineering). Ruby wasn&#39;t really that great to begin with, Rails on the other hand, when it came out, was new and revolutionary. It wasn&#39;t the first to implement its MVC design but it made it popular. </p> <p>In the end, it&#39;s always a good idea to learn new languages, Elixir after all is different from Go and embraces a different concurrency model (actors).</p></pre>1010011010: <pre><p>I&#39;ve never heard spring referred to as a great system before.</p></pre>tehbilly: <pre><p>Come to the enterprise world, gaze upon the burnt landscape with the smoking (but still kicking) corpses of spring projects.</p> <p>&#34;It&#39;s so easy!&#34;, they said. And then bolted before having to maintain their atrocities.</p> <p>tl;dr, enterprise is why spring is a thing</p></pre>coreb: <pre><p>Can anyone provide links to best practices on using a Web API for business logic? Does this mean having a server side app call the API, or are the frontends written in a Javascript framework? How do you secure an API key in a javascript app?</p></pre>Ironballs: <pre><p>With that paradigm, the frontends are usually in JavaScript.</p> <p>To secure an API key, <a href="http://billpatrianakos.me/blog/2013/09/12/securing-api-keys-in-a-client-side-javascript-app/%5D%20is%20a%20good%20tutorial.%20See%20also%20%5Bthis%5D(https://www.reddit.com/r/webdev/comments/2cceth/what_are_best_practices_on_storing_api_keys_in/" rel="nofollow">this</a> and <a href="https://www.reddit.com/r/javascript/comments/2a4qyi/api_keys_how_to_hide_them/" rel="nofollow">this</a>. The short version is that you don&#39;t: you login with some other method and then the server provides a token that you can use on subsequent requests. </p> <p>The API key identification is attached to the login details (so when user Bob is registered, it is added to the &#34;Frontend&#34; client list -&gt; generate &#34;Frontend&#34; keys for Bob). Another solution is to put a proxy in between that handles this.</p></pre>strident-octo-spork: <pre><p>Beyond a certain scale of application, there is an API server and an App server. The API server contains the business logic/database connections. The App server servers the front-end application, templates html pages, and routes requests from the front-end application through the API. The JavaScript frontend talks to the app server, and the app server routes requests to the various business APIs. Internet security policies prevent websites from talking to pages that aren&#39;t their origin domain, and Database/API security means not letting in arbitrary connections. Thus the app server acts as an intermediary.</p> <p>Of course for smaller applications, these two are often combined. If all you are doing is updating a single field in a database, anything you throw together will work fine.</p></pre>gophers4life: <pre><blockquote> <p>The problem lies in the fact that people aren&#39;t yet used to writing web apps using APIs. The current growing trend is to have a web API and then have your frontend use the API for business logic.</p> </blockquote> <p>Forgive me for my stupidity, but I don&#39;t follow. Could you please elaborate, perhaps with an example?</p> <p>Are you saying there&#39;s a client and server process running for such a mobile app?</p></pre>Ironballs: <pre><p>In a MVC framework, you have a controller that accepts <code>POST</code> requests to <code>/users</code> to create a new user. There&#39;s a form on your web page with the appropriate fields and then a button (a link) that directly does that: the browser <code>POST</code>s to that URL, and then the controller returns the appropriate view, the list of users.</p> <p>With an API/SPA (single page application) solution, you have a simple REST API that listens to <code>POST</code> requests on users, but contains no real web page logic, no models, no controllers, just a request and a response to be returned. Your web application frontend is actually a JavaScript application that does <em>not</em> POST directly using the browser, but sends the response asynchronously via an ajax request, and the frontend logic decides what you shall see thereafter.</p> <p>So basically the frontend becomes just another API client. Your API doesn&#39;t really know anything about web pages, templates or CSS styles, it just receives requests and sends back responses. A clear separation of concerns.</p> <p>This separation allows one to write software that scales well, because you could one day bolt a GUI application or a mobile application on top of it. Just swap the frontend with Qt or Android, anything you like, as long as you talk back in HTTP.</p> <p>Shameless plug: to this end, I&#39;ve outlined a <a href="http://github.com/ane/ebi">program architecture model</a> for creating the architecture of the backend, with examples in Go. The core idea is from Robert C. Martin&#39;s <a href="https://www.youtube.com/watch?v=HhNIttd87xs">Clean Architecture</a> (and <a href="https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html">here</a>) principles.</p> <p>The single page application concept is well explained <a href="https://en.wikipedia.org/wiki/Single-page_application">here</a>.</p></pre>gophers4life: <pre><p>Thank you very much! I&#39;ve bookmarked the links for my train-ride home :)</p></pre>ihsw: <pre><p>I think it requires expansion -- web apps != APIs.</p> <p>Go had no analogue to Rails, at best it has handy middlewares, routing, and terse database access.</p> <p>In this vein I&#39;d compare it more closely to the NodeJS stack with heavy focus on program correctness on large codebases.</p> <p>You&#39;d have to have a lot of experience under your belt to make such an inflammatory statement, and it isn&#39;t completely unfounded.</p></pre>fungussa: <pre><p>Go&#39;s rapid adoption is being seen as somewhat of a threat by those who&#39;ve invested significant time and effort in other technologies.<br/> I wish Go had arrived aclot sooner, as it would saved me much time and effort in getting a whole class of projects off the ground.</p></pre>mekanikal_keyboard: <pre><p>this. also, there is always an &#34;obligatory contrarian&#34; who simply must show up in any discussion predicated on the supposition that a nay-sayer musdt have some insight</p></pre>oefig: <pre><p>I&#39;ve been building an API layer with Go and it&#39;s been extremely productive — especially coming from Node.</p> <p>As I&#39;m building I hardly ever feel the need to go refactor or simplify in the same way I do with Javascript. The type system is really simple and eliminates common bugs at compile time. Embedding feels much more pragmatic than the OOP I&#39;m used to with PHP and slightly more similar to Javascript (without prototype weirdness). </p> <p>I personally haven&#39;t considered <em>jumping ship</em> to Elixir because I feel like Go works well for me. I do think functional programming is interesting though...</p></pre>nerdwaller: <pre><p>The only hook I have found with using golang for more is I don&#39;t love the database api, but it&#39;s probably mostly out of laze. Any tips for using dbs in go effectively?</p></pre>very-little-gravitas: <pre><p>Which bits didn&#39;t you like, marshalling to objects, building queries, or something else?</p></pre>nerdwaller: <pre><p>I think the main thing was how manual statements have to be. I don&#39;t mind raw SQL, but data models change and I don&#39;t love the idea of having to manually go into all my source to check/update my queries.</p></pre>very-little-gravitas: <pre><p>Not sure I understand, when I change models queries don&#39;t have to change, just a field in the model, and a change to the model creation function from the db row, so should be a 2 line change. There are plenty of query builders and/or ORMs (I just use a query builder personally, and let the model be in charge of creating itself). </p></pre>nerdwaller: <pre><p>It&#39;s mostly in migrations in changing my data models (which means a DB change many times), and my query is <code>select id, name from users where id = ?</code> and <code>name</code> is now <code>full_name</code>, I have to go into any queries on the users table to change <code>name</code> to <code>full_name</code>. Granted, this is a really poor example. I just imagine this with more complicated joins and such (and a better example). Maybe I am just crazy and need to play more in the sql package to understand it better in a larger usage.</p></pre>very-little-gravitas: <pre><p>Ah I see, you&#39;re selecting just a few columns. I usually use select * from users, rather than naming the specific columns (unless specifically needing a subset, in which case you don&#39;t need to add the new column). It might be worth taking a look at a query builder/ORM to see how it works or whether you can use it, as it tends to simplify things like this and joins. It shouldn&#39;t be necc. to do much manual SQL or change lots of queries every time you add a column. </p></pre>nerdwaller: <pre><p>Roger that, do you have favorite packages to recommend? Thanks for the discourse :)</p></pre>oefig: <pre><p>I&#39;ve been using <a href="https://github.com/jinzhu/gorm" rel="nofollow">Gorm</a> which definitely feels familiar to database APIs seen in larger frameworks like ActiveRecord.</p></pre>010a: <pre><p>Asking this on <a href="/r/golang">/r/golang</a> isn&#39;t really the best place to get a truly objective point of view. But then again, what really is objective in this industry.</p> <p>I&#39;ve held the contrarian view for a while that Golang is on the &#34;lower-end&#34; of my ordered list of languages I think are suitable for productive web API development. Certainly not the worst. But, at best, average. </p> <p>Now, because I have voiced these opinions before and gotten terribly cookie-cutter responses, let me be perfectly clear here: The word &#34;frustrating&#34; is not a synonym for &#34;impossible&#34;. </p> <p>Parsing user data is frustrating. The boilerplate attached to any route which converts the body of a request into some specific struct is both significant and yet unnecessary if the type system were only a little more powerful. There is no performant and typesafe way to abstract that functionality out into pre-route middleware. </p> <p>JSON tags on structs are also frustrating in the same way. Most structs would want to export fields, but doing so requires you to define a <code>json</code> tag on every field so that you don&#39;t start creating JSON payloads with capital first letters (which is generally accepted to be best JSON practice). This could easily be solved if they added some shortcut where I can set <code>encoding/json</code>&#39;s rule to say, like, <code>default</code> (<code>MyField</code> -&gt; <code>MyField</code>), <code>CamelCase</code> (<code>MyField</code> -&gt; <code>myField</code>), <code>underscores</code> (<code>MyField</code> -&gt; <code>my_field</code>). Obviously there are i18n/unicode concerns with doing this automatically, but its worth exploring.</p> <p>And on the flip side of the stack, database access is frustrating. This is one of the few areas of Go where I feel like the golang stdlib package is grossly inadequate for normal programmer use. I&#39;ve used gorp for the latest project of mine, and it works well, but obviously relies on lots of reflection and struct tags to get the job done. This hurts performance <em>and</em> makes me less productive (than competing languages, not when compared to <code>database/sql</code>). No win at all. </p> <p>Finally, consider the overall performance of Go. This is one of its primarily touted benefits, yet I strongly argue that it is overblown. For web applications which <strong>inherently</strong> have a lot of asyncio, I&#39;ve seen real world benchmarks which place Node as being faster once <code>cluster</code> is enabled (which is the only way to run a fair benchmark). Pypy is maybe 5x slower. Java is competitive. Every modern stack is within the same league as Go. </p> <p>When a web app is built to be horizontally scalable, the cost of deploying <strong>1</strong> more server might be $30/mo. Considering a developer&#39;s time is, oh, lets say $60/hr, if a language can save a developer literally <strong>30</strong> minutes of time every month than that language has a net higher economic value. Generally speaking, I don&#39;t have the data to suggest Go makes developers more or less productive than any competing language. I can only speak for myself, but I personally know that I have spent at least an hour a month writing <code>echo</code> middleware which <code>express</code> or <code>twisted</code> does for me, or repeating myself on four-line database functions which would be ten characters in <code>knex</code>, or hunting down goroutine leaks because everyone told me &#34;this is how you design Go apps. dont just throw servers at it, parallelize your work on one server&#34;. </p> <p>And as I end these talks each time I say them: There are plenty of things Go is great at, but anything which involves communication over the wire is a pain point for me.</p></pre>m3wm3wm3wm: <pre><p>I understand these frustrations. They do exist. But there is another way of looking at it which I&#39;m trying to exercise:</p> <ul> <li>All those frustrations originate from manual code.</li> <li>A typical web project does not have 1e3 models. It might have 5, 10 or 50.</li> <li>You write this frustrating code <strong>once</strong> and it works forever.</li> </ul> <p>I see the salesmanship of web frameworks which are advertised as <em>one-liners</em> as a way to get the developers into a technical debt.</p> <p>The truth is that programming is not easy. Any framework salesman who claims that it&#39;s easy is selling you a sub prime mortgage. Sooner or later you will have to pay the technical debt.</p> <p>Of course, if you are a freelancer, the debt won&#39;t be your problem. It makes sense to make more profit using those magic frameworks which save time, and let the project owner to pay the debt. But if it&#39;s your own project, why won&#39;t you sit down and type those extra characters once, and be debt free forever?</p> <p>Again, this is my view of looking at this problem from a different angle.</p></pre>slrz: <pre><blockquote> <p>JSON tags on structs are also frustrating in the same way.</p> </blockquote> <p>I don&#39;t think adding some heavy mechanism for specifying the conversion of field names to JSON keys is worth it. I mean, adding the struct tags is a single trivial operation in your text editor if you just want to lowercase the names. Is it that you worry about forgetting to add the tags?</p> <p>For your other points, you seem to solve them mostly by throwing libraries at it, when programming in another language. What about their code? Are they all well-maintained and offer a stable API? Or do you need to track their changes going forward? Enter technical debt, as m3wm3wm3wm already wrote about.</p></pre>storm14k: <pre><p>I attempted to start a business on one Go web app and I&#39;m thinking about another in my spare time. It has fit just fine for me and I wish it had developed to this point earlier. Some of these twisted up mangled things you can do in dynamic languages just aren&#39;t good practice. I eventually learned that and its why my Go web apps work without half the problems in the end even though they might take a little more thought and planning up front. To make things more solid I often ended up skipping the benefits of the dynamic languages anyway.</p></pre>no1youknowz: <pre><p>What is missing from Golang that is stopping developers from building web applications?</p> <p>Are beego, revel, gin, etc not enough? Or do we need something more consistent with MVC frameworks? </p> <p>What about something basic like codeigniter 3 for PHP? If at a minimum something like that was ported to Go and allowed developers a base to rapidly develop from. Would this help?</p> <p>I wrote a huge monolithic application with CI2. I know it gets a lot of complaints, but it allowed me to forget lots of issues and concentrate on the business logic and getting the project completed.</p> <p>I&#39;m considering building something akin to CI3 in terms of an MVC framework as a learning exercise. I still have a lot to learn with Go and it will be a fun project for me.</p> <p>Next step would be to port that monolithic app to Go. I&#39;ll then have to support just 2 languages. Golang and javascript/jquery. For me would be ideal.</p></pre>cathalgarvey: <pre><p>For bonus points, develop in pure go using gopherjs and the gopherjs/goquery bindings, and ship pure-Go front and backend. :)</p></pre>no1youknowz: <pre><p>Whilst I love developing in go. I&#39;ll probably be moving to react eventually.</p></pre>boomshroom: <pre><p>If only we had a native go react api.</p></pre>Neitanod: <pre><p>do we?</p></pre>boomshroom: <pre><p>Not at the moment as far as I&#39;m aware of, the doesn&#39;t mean someone can&#39;t make one.</p></pre>-Nii-: <pre><p>That would be pretty cool, is there an example of this somewhere?</p></pre>cathalgarvey: <pre><p>I don&#39;t know, sorry. GopherJS and its jquery bindings are there on Github, I assume <em>someone</em> has made a pure-Go webapp with them!</p></pre>very-little-gravitas: <pre><blockquote> <p>Have you had the same negative experience in your big Go based web app development?</p> </blockquote> <p>I&#39;ve built a few smaller sites, and one larger site, and haven&#39;t had any negative experiences, on the contrary it&#39;s been a really pleasant experience to build new sites in Go.</p> <p>I disagree with those below (soon to be above) saying it is good for APIs but not good for full stack web apps. There&#39;s a built in server, templates with XSS protection, router and handler examples in the std lib and there are now some good options for routers, auth, csrf, minification, image manipulation, migrations etc. The only possible hurdles are getting used to the type system and relating objects to databases in a clean way but personally I&#39;ve come to a solution I&#39;m quite comfortable with there, and there are a few options available for query builders or ORMs (or roll your own solution if you don&#39;t believe in them). The only place I&#39;ve felt a lack was having to convert arrays of an Interface to a type by hand, which I ended up just avoiding doing. I&#39;m not a huge fan of the standard handler either (lack of request context or error returns), but again that&#39;s easily fixed. </p> <p>So I think Go is now able to offer most of what someone coming from another language popular for web dev like python, ruby, PHP or Java. What it doesn&#39;t have yet is a large ecosystem of useful large apps written in the language, but that is coming surprisingly quickly (see gogs, mattermost, caddy etc). So I&#39;d say the outlook for writing web apps in Go is looking pretty good, and from my experience I&#39;m really happy with it for full stack websites, not just for APIs. </p></pre>lbrtdj: <pre><p>I always see Go as closer to systems programming, aka C, than it is to higher level languages like Python/Ruby/Javascript. Despite its popularity, writing code in Go feels restrictive in &#39;expressiveness&#39;. Want to create a hash? Well you do hash := make(map[string]interface{}. What about arrays of random objects? []interface{}.</p> <p>So you end up with interface{} all over, and having had to type check before you read. In Ruby/Python, this is taken away from you. Create a hash? hash = {}. Done.</p> <p>There is nothing wrong with that, but it&#39;s just not suitable for business logic, which where web apps are typically for. Try doing this in Go. Given an arbitrary JSON, read key &#34;abc&#34;, and if key &#34;abc&#34; is nil, read key &#34;def&#34;, which should contain an array of could be strings could be integers, go to element #10, then go read key &#34;ghi&#34;. If &#34;ghi&#34; is not nil, go deeper and look for &#34;jkl&#34;, and if that&#39;s not nil, go deeper and look for &#34;xyz&#34;, and grab the Nth index (As pointed by key &#34;def&#34; earlier), and return that value.</p> <p>You will see how many if statements and error checking you need to get that working. This is where dynamically-typed language shine, and Go isn&#39;t just for that. Sure, Go can do it just fine, but your code will end up very long, while if you write that in JS or Ruby, you can probably do all that in 2-3 lines of code.</p></pre>stone_henge: <pre><p>To be fair, if your business logic houses &#34;random objects&#34; and arbitrary json in its definition, the language probably isn&#39;t your greatest problem. Define all your types and give them relevant methods for dealing with their content and you&#39;ll avoid having to traverse deeply nested arbitrary json.</p> <p>Sure, it isn&#39;t as flexible as Python or Ruby, but I see that as one of its strengths. More than once have I written Python code where I had to defensively brace for several input types because there was no formal definition of the couplings. You can pobably blame that on our process to some extent, but I&#39;ve found that the team writes better software with explicit errors and types.</p></pre>lbrtdj: <pre><p>I am not saying there is anything wrong with Go. Go is perfectly fine the way it is now. But sometimes real world isn&#39;t that perfect and nice the way we want to, and when engineers are presented with a challenge that wasn&#39;t their fault to begin with (e.g. some lame DB admins who aren&#39;t doing their job sanitizing their data), we were forced to solve problems in creative ways.</p></pre>calebdoxsey: <pre><p>It can be short in Go too:</p> <pre><code>func get(str string, n int) interface{} { obj, _ := simplejson.NewJson([]byte(str)) for _, k := range []string{&#34;abc&#34;, &#34;def&#34;} { if o, ok := obj.CheckGet(k); ok { return o.GetIndex(n).GetPath(&#34;ghi&#34;, &#34;jkl&#34;, &#34;xyz&#34;).Interface() } } return nil } </code></pre> <p>Using github.com/bitly/go-simplejson. </p></pre>akarl: <pre><p>That does sound like a problem Go isn&#39;t ideally suited for, but it also sounds like a problem that may be more an artifact of your app design than an artifact of your underlying business requirement? For example, an array of mixed arbitrary types, while easy to do in JS, is bad practice in every case: it&#39;s not an array, you&#39;re just using an array as a numerically keyed key-value store, and probably suffering poor memory and time performance for it.</p></pre>Yojihito: <pre><blockquote> <p>if you write that in JS or Ruby, you can probably do all that in 2-3 lines of code.</p> </blockquote> <p>o.O</p> <p>I would like to see the same for that in Ruby/JS/Python, can&#39;t imagine 2-3 lines of code for your task, that&#39;s a lot of if/else in my mind (but I have no big knowledge in Ruby/JS so there may be a magic syntax trick).</p></pre>lbrtdj: <pre><p>In Ruby (untested). Not a single if-statement:</p> <pre><code>id = json[&#39;abc&#39;] || json[&#39;def&#39;] &amp;&amp; json[&#39;def&#39;][10].to_i and json[&#39;ghi&#39;] &amp;&amp; json[&#39;ghi&#39;][&#39;jkl&#39;] &amp;&amp; json[&#39;ghi&#39;][&#39;jkl&#39;][&#39;xyz&#39;][id] or nil </code></pre></pre>Yojihito: <pre><p>Interesting, I would have used 20 if/else blocks because I don&#39;t know any better I guess, thanks for the answer :).</p></pre>SwellJoe: <pre><p>I don&#39;t know Go beyond the basics, but if Go has map, reduce, filter type constructs for lists I dunno why it would have to be dramatically more verbose than a dynamic language. I&#39;m most familiar with Perl for stuff like this, and the List::Util and List::MoreUtils modules provide the meat of any kind of implementation for that sort of thing. Does Go have nothing similar?</p></pre>diegobernardes: <pre><p>shiny new things are always better, isnt? joke, apart, never did something big on web with go, but there are plenty of examples of good web apps done in go: mattermost, gogs, camlistore,...</p> <p>the thing is, dinamic and in case of elixir, fucional languages, are more produtive, im my opnion, maybe this is way people are jumping to elixir.</p> <p>ahh and phoenix framework that is very inspired in rails.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1302 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传