一,問題及思路
最近在用 rails + react + mysql 基本架構寫一個cms + client的項目,裡面涉及到了圖片的上傳及顯示,下面簡單說說思路,至於這個項目的配置部署,應該會在寒假結束總結分享一下。
rails中圖片上傳及顯示要解決主要問題是:
圖片存在哪?
圖片格式大小?
用戶端怎麼顯示圖片?
因為這是個小項目,估計最多1000張圖片,最多佔用空間1G,所以採取相對簡便的方法: 圖片儲存在rails的public檔案夾裡(也就是儲存在部署該項目的主機中) ,如果圖片比較多的話,還是推薦用亞馬遜雲提供的服務 AWS S3 (理解為一個硬碟,S3提供了介面給你存取東西,安全,管理方便)。
大概的思路是,前端通過 <input type="file"/> 選擇檔案,發送ajax請求到後端的controller,controller將請求的圖片資料進行大小裁剪、類型轉換後儲存到本地指定的檔案夾,同時將路徑返回,用於顯示圖片。
二,實踐
思路比較簡單,所以話不多說,直接上代碼:
前端代碼整合在react寫的一個圖片上傳組件裡,image_uploader.js.jsx代碼如下
var ImageUploader = React.createClass({
getInitialState: function() {
return {
url: this.props.url
};
},
onFileSelect: function(e) {
var that = this;
var files = e.target.files;
if (files.length <= 0) {
AlertModal.showWithProps("No file selected");
return;
}
var data = new FormData();
$.each(files, function(key, value) {
data.append('file', value);
data.append('type', that.props.type)
});
this.upload(data);
},
upload: function(data) {
var that = this;
if (!data) {
return;
} else {
this.refs.filebtn.disabled = true;
$("#loading-modal").modal('show');
$.ajax({
url: '/missions/upload_image',
type: 'post',
data: data,
processData: false,
contentType: false
}).done(function(res){
console.log(res);
that.setState({url: res.url});
}).fail(function(err){
console.log(err);
AlertModal.showWithProps("Upload Failed");
}).always(function(){
$("#loading-modal").modal('hide');
that.refs.filebtn.disabled = false;
});
}
},
handleUrlChange: function(e) {
this.setState({url: e.target.value});
},
render: function() {
var form_input_name = this.props.model + "[thumb]";
var form_input_id = this.props.model+ "_thumb";
return (
<div className="image-uploader-inputs row">
<div className="col-sm-8 image-input">
<input className="form-control" name={form_input_name} id={form_input_id} ref="urlinput" value={this.state.url || ""} onChange={this.handleUrlChange}/>
</div>
<div className="btn btn-primary btn-file image-input-btn">
<input type="file" onChange={this.onFileSelect} ref="filebtn"/>
</div>
</div>
);
}
});
上面的重點在於upload函數,源碼是最好的文檔,如果看源碼需要太多注釋的話,那肯定是我寫的代碼品質還不夠高,請批評指出。
寫這個的時候遇到兩個問題:
一,這個組件是用在_form.html.slim裡的,這個表單是用於資訊的錄入的,大家對錶單應該比較熟悉,既然要用在表單裡,就是給這個組件作標識,標明name和id,程式碼片段如下(從上面的代碼中截取):
var form_input_name = this.props.model + "[thumb]";
var form_input_id = this.props.model+ "_thumb";
<input className="form-control" name={form_input_name} id={form_input_id} ref="urlinput" value={this.state.url || ""} onChange={this.handleUrlChange}/>
二,使用 <input type="file"/> 有一個普遍的問題,內建的UI不美觀。
Google/百度“input file btn”會有許多解決方案,家裡網速差就沒細看。我的做法是把這個btn設為透明,相關的代碼及css如下:
<div className="btn btn-primary btn-file image-input-btn">
<input type="file" onChange={this.onFileSelect} ref="filebtn"/>
</div>
.image-input-btn {
float: left;
width: 15%;
background-image: image-url("upload.png");
background-size: 30px;
background-position: center;
background-repeat: no-repeat;
input {
//hide file input button
width: inherit;
opacity: 0;
}
}
10
11
12
13
接下來看看後端的代碼:
def upload_image
# => will resize later
image_relative_path = "/assets/images/#{params[:type]}/#{Time.now.to_i}.png"
image_path = File.expand_path(File.dirname(__FILE__) + '/../..') + "/public" + image_relative_path
data = File.read(params[:file].path)
img = File.new(image_path, "w+")
if img
img.syswrite(data)
end
img.close
render json: {url: image_relative_path}
end
10
11
12
13
代碼同樣簡單,構建檔案路徑,儲存檔案,返迴路徑。因為開發進度的原因這裡並沒有對圖片的大小和類型進行修改(為了減少資料轉送量,在前端進行大小的修改比較合理),後面review這部分代碼的時候會再更新這篇部落格。
值得說說的是:
圖片命名的問題,採用了時間戳記,命名不會重複,為了方便管理,將不同類型的圖片存於不同的檔案夾,但是只以時間戳記命名可擷取的資訊太少,不利於運營人員管理,後期會再仔細考慮這個問題。
存放路徑問題,在後端代碼裡,rails能將圖片存在rails專案檔夾的任何位置,但是用戶端顯示圖片的時候,只能訪問public檔案夾裡的內容,於是決定將圖片存在public檔案夾下。
三,總結及思考
完成這個功能不需要太久,但代碼外的思考不少。圖片小,甚至能用Git備份圖片,也許不是長久之計,開始想念aws的好了,容災交給aws處理最好不過了,後面試了一下AWS S3