Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
Bucket有點(diǎn)像電腦里面的盤(pán)符或者目錄,我們文件的上傳,必須指定上傳到哪個(gè)Bucket里面。因此,在上傳之前必須創(chuàng)建它。
在阿里云控制臺(tái)點(diǎn)擊OSS服務(wù),然后點(diǎn)擊【Bucket列表】就可以看到如下界面:
然后點(diǎn)擊【創(chuàng)建Bucket】按鈕,按下圖填寫(xiě):
Bucket名稱(chēng):需要自己要進(jìn)行命名,相當(dāng)于自己文件的存儲(chǔ)目錄。
區(qū)域:這個(gè)選擇很重要,因?yàn)楹竺嬲{(diào)用api接口上傳文件的時(shí)候需要使用。
Endpoint:這個(gè)也很重要,因?yàn)楹竺嬲{(diào)用api接口上傳文件的時(shí)候也需要使用。
上述選擇完成后,就可以點(diǎn)擊【確定】按鈕進(jìn)行創(chuàng)建了。
創(chuàng)建成功后,進(jìn)入Bucket信息頁(yè)面,可以點(diǎn)擊左側(cè)的【文件管理】按鈕,出現(xiàn)如下界面:
好了,到此為止,阿里云的OSS配置就算完成了,新手對(duì)于這個(gè)過(guò)程感覺(jué)十分困惑,因此我這里說(shuō)的比較詳細(xì)。
下面是官網(wǎng)文檔地址以及官方代碼,我摘抄了幾個(gè)入門(mén)級(jí)的代碼。
https://help.aliyun.com/document_detail/84781.html?spm=a2c4g.11186623.6.787.101245dcJzaFge
(1)上傳字符串
// Endpoint以杭州為例,其它Region請(qǐng)按實(shí)際情況填寫(xiě)。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主賬號(hào)AccessKey擁有所有API的訪(fǎng)問(wèn)權(quán)限,風(fēng)險(xiǎn)很高。強(qiáng)烈建議您創(chuàng)建并使用RAM賬號(hào)進(jìn)行API訪(fǎng)問(wèn)或日常運(yùn)維,請(qǐng)登錄 https://ram.console.aliyun.com 創(chuàng)建RAM賬號(hào)。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";
// 創(chuàng)建OSSClient實(shí)例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 創(chuàng)建PutObjectRequest對(duì)象。
String content="Hello OSS";
// <yourObjectName>表示上傳文件到OSS時(shí)需要指定包含文件后綴在內(nèi)的完整路徑,例如abc/efg/123.jpg。
PutObjectRequest putObjectRequest=new PutObjectRequest("<yourBucketName>", "<yourObjectName>", new ByteArrayInputStream(content.getBytes()));
// 如果需要上傳時(shí)設(shè)置存儲(chǔ)類(lèi)型與訪(fǎng)問(wèn)權(quán)限,請(qǐng)參考以下示例代碼。
// ObjectMetadata metadata=new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);
// 上傳字符串。
ossClient.putObject(putObjectRequest);
// 關(guān)閉OSSClient。
ossClient.shutdown();
(2)上傳Byte數(shù)組
// Endpoint以杭州為例,其它Region請(qǐng)按實(shí)際情況填寫(xiě)。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主賬號(hào)AccessKey擁有所有API的訪(fǎng)問(wèn)權(quán)限,風(fēng)險(xiǎn)很高。強(qiáng)烈建議您創(chuàng)建并使用RAM賬號(hào)進(jìn)行API訪(fǎng)問(wèn)或日常運(yùn)維,請(qǐng)登錄 https://ram.console.aliyun.com 創(chuàng)建RAM賬號(hào)。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";
// 創(chuàng)建OSSClient實(shí)例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
// 上傳Byte數(shù)組。
byte[] content="Hello OSS".getBytes();
ossClient.putObject("<yourBucketName>", "<yourObjectName>", new ByteArrayInputStream(content));
// 關(guān)閉OSSClient。
ossClient.shutdown();
(3)上傳網(wǎng)絡(luò)流
// Endpoint以杭州為例,其它Region請(qǐng)按實(shí)際情況填寫(xiě)。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主賬號(hào)AccessKey擁有所有API的訪(fǎng)問(wèn)權(quán)限,風(fēng)險(xiǎn)很高。強(qiáng)烈建議您創(chuàng)建并使用RAM賬號(hào)進(jìn)行API訪(fǎng)問(wèn)或日常運(yùn)維,請(qǐng)登錄 https://ram.console.aliyun.com 創(chuàng)建RAM賬號(hào)。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";
// 創(chuàng)建OSSClient實(shí)例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上傳網(wǎng)絡(luò)流。
InputStream inputStream=new URL("https://www.aliyun.com/").openStream();
ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);
// 關(guān)閉OSSClient。
ossClient.shutdown();
(4)上傳文件流
// Endpoint以杭州為例,其它Region請(qǐng)按實(shí)際情況填寫(xiě)。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 云賬號(hào)AccessKey有所有API訪(fǎng)問(wèn)權(quán)限,建議遵循阿里云安全最佳實(shí)踐,創(chuàng)建并使用RAM子賬號(hào)進(jìn)行API訪(fǎng)問(wèn)或日常運(yùn)維,請(qǐng)登錄 https://ram.console.aliyun.com 創(chuàng)建。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";
// 創(chuàng)建OSSClient實(shí)例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上傳文件流。
InputStream inputStream=new FileInputStream("<yourlocalFile>");
ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);
// 關(guān)閉OSSClient。
ossClient.shutdown();
// Endpoint以杭州為例,其它Region請(qǐng)按實(shí)際情況填寫(xiě)。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主賬號(hào)AccessKey擁有所有API的訪(fǎng)問(wèn)權(quán)限,風(fēng)險(xiǎn)很高。強(qiáng)烈建議您創(chuàng)建并使用RAM賬號(hào)進(jìn)行API訪(fǎng)問(wèn)或日常運(yùn)維,請(qǐng)登錄 https://ram.console.aliyun.com 創(chuàng)建RAM賬號(hào)。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";
// 創(chuàng)建OSSClient實(shí)例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 創(chuàng)建PutObjectRequest對(duì)象。
PutObjectRequest putObjectRequest=new PutObjectRequest("<yourBucketName>", "<yourObjectName>", new File("<yourLocalFile>"));
// 如果需要上傳時(shí)設(shè)置存儲(chǔ)類(lèi)型與訪(fǎng)問(wèn)權(quán)限,請(qǐng)參考以下示例代碼。
// ObjectMetadata metadata=new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);
// 上傳文件。
ossClient.putObject(putObjectRequest);
// 關(guān)閉OSSClient。
ossClient.shutdown();
首先我們來(lái)定一個(gè)阿里云OSS文件上傳的一個(gè)工具類(lèi),具體代碼如下:
package com.shenmazong.utils;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
public class UploadFileUtils {
/**
* TODO uploadAliyunOss 阿里云OSS文件上傳
* @param file
* @param fileName
* @return
*/
public static String uploadAliyunOss(MultipartFile file, String fileName) {
String url=null;
// Endpoint以杭州為例,其它Region請(qǐng)按實(shí)際情況填寫(xiě)。
String endpoint="http://oss-cn-beijing.aliyuncs.com";
// 阿里云主賬號(hào)AccessKey擁有所有API的訪(fǎng)問(wèn)權(quán)限,風(fēng)險(xiǎn)很高。強(qiáng)烈建議您創(chuàng)建并使用RAM賬號(hào)進(jìn)行API訪(fǎng)問(wèn)或日常運(yùn)維,請(qǐng)登錄 https://ram.console.aliyun.com 創(chuàng)建RAM賬號(hào)。
// shenmazong@1642590691341298.onaliyun.com
String accessKeyId="XXXX";
String accessKeySecret="XXXX";
// 創(chuàng)建OSSClient實(shí)例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 創(chuàng)建PutObjectRequest對(duì)象。
// 上傳文件流。
PutObjectResult result=null;
String newName=UUID.randomUUID().toString();
try {
result=ossClient.putObject("shenmazong", fileName, file.getInputStream());
url="https://shenmazong.oss-cn-beijing.aliyuncs.com/" + fileName;
} catch (IOException e) {
e.printStackTrace();
return null;
}
// 關(guān)閉OSSClient。
ossClient.shutdown();
return url;
}
}
上面的工具類(lèi),是傳輸web上傳的文件流,然后返回能夠訪(fǎng)問(wèn)的url,這樣我們web網(wǎng)上實(shí)現(xiàn)圖片文件上傳,就很容易了。
下面我們就可以編寫(xiě)service來(lái)實(shí)現(xiàn)我們的文件上傳接口了。
/**
* TODO 實(shí)現(xiàn)用戶(hù)頭像的上傳
* @param userId
* @param file
* @return
*/
@Override
public ResponseResult uploadHead(Integer userId, MultipartFile file) {
ResponseResult result=ResponseResult.SUCCESS();
//--1 驗(yàn)證用戶(hù)是否存在
TbUser user=iTbUserMapper.selectById(userId);
if(user==null) {
result.setCode(-1);
result.setMessage("獲取用戶(hù)信息失敗");
return result;
}
//--2 上傳文件
if(file.isEmpty()){
log.info("上傳文件為空");
result.setCode(-1);
result.setMessage("上傳文件為空");
return result;
}
String fileName=file.getOriginalFilename();
int size=(int) file.getSize();
log.info(fileName + "-->" + size);
UUID uuid=UUID.randomUUID();
String suffix=fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
String newName=uuid.toString() + suffix;
String headUrl=UploadFileUtils.uploadAliyunOss(file, newName);
if(headUrl==null) {
result.setCode(-1);
result.setMessage("上傳文件到OSS失敗");
return result;
}
//--3 修改用戶(hù)頭像鏈接
user.setHeadurl(headUrl);
iTbUserMapper.updateById(user);
return result;
}
service寫(xiě)好以后,就需要編寫(xiě)controller了,具體代碼如下:
/**
* TODO uploadHead 上傳頭像圖片
* @param userId
* @param file
* @return
*/
@ApiOperation(value="頭像上傳", notes="用戶(hù)上傳更新自己的頭像")
@ApiImplicitParams(value={
@ApiImplicitParam(name="userId", value="用戶(hù)ID", required=true, dataType="int", example="0")
})
@PostMapping(value="/uploadHead", headers="content-type=multipart/form-data")
public ResponseResult uploadHead(@RequestParam("userId") Integer userId,
@RequestParam("uploadFile") MultipartFile file) {
return userService.uploadHead(userId, file);
}
最后是html頁(yè)面的示例代碼,也一并貼上來(lái):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 可選的 Bootstrap 主題文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="row">
<form action="upload" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="uploadFile">選擇文件</label>
<input type="file" id="uploadFile" name="uploadFile">
<p class="help-block">Example block-level help text here.</p>
</div>
<button type="submit" class="btn btn-primary">上傳</button>
</form>
</div>
</div>
</body>
</html>
最后的實(shí)例演示,是我在一個(gè)演示項(xiàng)目中拷貝過(guò)來(lái)的代碼,沒(méi)有針對(duì)咱們這個(gè)教程進(jìn)行整理,但是作為參考資料,這部分代碼是合格的。
018年初,一個(gè)物理專(zhuān)業(yè)的學(xué)生Jan B?hmer創(chuàng)建了一個(gè)網(wǎng)站,用來(lái)跟蹤和記錄用戶(hù)的點(diǎn)擊、鼠標(biāo)移動(dòng)、瀏覽器類(lèi)型和操作系統(tǒng)等數(shù)據(jù)。雖然用戶(hù)跟蹤并不新鮮,但他的方法不需要JavaScript、插件或外部庫(kù)。實(shí)際上,它只使用了普通HTML文本和一點(diǎn)CSS。
它是如何工作的
B?hmer的概念利用了CSS的兩個(gè)特性:將內(nèi)容注入HTML元素的能力,以及在用戶(hù)執(zhí)行操作后更改樣式的能力。網(wǎng)站的工作方式是在執(zhí)行操作時(shí)使用content屬性設(shè)置URL。此URL調(diào)用一個(gè)腳本,該腳本記錄有關(guān)操作的詳細(xì)信息,這些操作作為URL參數(shù)進(jìn)行傳遞。使用::before和::after CSS選擇器設(shè)置這個(gè)URL可以確保只在執(zhí)行操作時(shí)調(diào)用URL,而不是在頁(yè)面首次加載時(shí)調(diào)用URL。
例如,下面的CSS在每次單擊#link元素時(shí)調(diào)用一次URL:
跟蹤腳本包含記錄事件和操作執(zhí)行次數(shù)的代碼。它還可以用于提取用戶(hù)的IP地址、用戶(hù)代理和其他標(biāo)識(shí)信息。
下面是這樣一個(gè)腳本在PHP中的示例:
檢測(cè)瀏覽器類(lèi)型
用戶(hù)可以欺騙瀏覽器的用戶(hù)代理,但是 B?hmer繞過(guò)了這個(gè)問(wèn)題,他使用@supports at-rule(at-rule 是CSS樣式聲明,以@開(kāi)頭,緊跟著是標(biāo)識(shí)符(charset),最后以分號(hào)(;)結(jié)尾。)測(cè)試瀏覽器特定的CSS屬性。例如,下面的操作通過(guò)檢測(cè)--webkit-appearance是可用的,而-ms-ime-align是不可用的來(lái)檢測(cè)Chrome瀏覽器:
檢測(cè)操作系統(tǒng)
B?hmer甚至使用字體檢測(cè)來(lái)識(shí)別用戶(hù)的操作系統(tǒng)。例如,通過(guò)檢測(cè)瀏覽器是否支持Calibri字體家族,我們可以假定瀏覽器運(yùn)行在Windows中:
B?hmer關(guān)于此概念的驗(yàn)證可以識(shí)別其他數(shù)據(jù)點(diǎn),包括瀏覽器窗口的大小和方向、用戶(hù)是否單擊了鏈接以及用戶(hù)在一個(gè)元素上停留的時(shí)間。
這種攻擊在瀏覽器中非常難以預(yù)防。完全防止它的唯一方法就是禁用CSS,這會(huì)使網(wǎng)站無(wú)法使用。然而,通過(guò)使用內(nèi)容安全策略(CSP),可以減少攻擊者利用此漏洞的機(jī)會(huì)。
使用內(nèi)容安全策略減少CSS泄漏
CSP是一組規(guī)則,它決定瀏覽器可以執(zhí)行哪些操作,不能執(zhí)行哪些操作。CSP通常用于防止跨站腳本攻擊(XSS)和由瀏覽器加載不信任腳本導(dǎo)致的其他攻擊。雖然CSP通常用于JavaScript文件,但它也可以應(yīng)用于CSS樣式和樣式表。
考慮一個(gè)使用第三方提供商托管的樣式表的網(wǎng)站。攻擊者破壞樣式表并將用戶(hù)跟蹤代碼添加到頁(yè)面上的鏈接:
當(dāng)用戶(hù)點(diǎn)擊該鏈接時(shí),他們的瀏覽器調(diào)用evil.com上托管的跟蹤腳本。由于這完全是通過(guò)瀏覽器完成的,網(wǎng)站所有者完全不知道這個(gè)漏洞。
Content-Security-Policy通過(guò)設(shè)置允許哪些樣式以及樣式來(lái)源等規(guī)則來(lái)防止這種情況。
禁用內(nèi)聯(lián)樣式
禁用內(nèi)聯(lián)樣式是CSP提供的最大安全好處之一。內(nèi)聯(lián)樣式是直接在HTML文檔中聲明的樣式(或使用JavaScript設(shè)置的樣式),而不是從樣式表加載的樣式。內(nèi)聯(lián)樣式——尤其是動(dòng)態(tài)生成的樣式或用戶(hù)創(chuàng)建的樣式——非常難以保護(hù)。這就是為什么CSP通常會(huì)鎖定所有內(nèi)聯(lián)腳本和樣式,并將那些已被特別批準(zhǔn)的內(nèi)聯(lián)腳本和樣式列入白名單。
以下規(guī)則將阻止所有內(nèi)聯(lián)樣式以及外部托管的樣式表:
使用Hash和Nonce驗(yàn)證樣式
如果阻塞內(nèi)聯(lián)樣式是不可行的,你仍然可以使用hash和nonce來(lái)確保CSS的完整性。
Hash是由一個(gè)文件或字符串的內(nèi)容生成的單向字符串。在樣式表或內(nèi)聯(lián)樣式上執(zhí)行哈希函數(shù)時(shí),除非樣式發(fā)生改變,否則它總是返回相同的結(jié)果。這對(duì)于將某些內(nèi)聯(lián)樣式和樣式表加入白名單是很有用的,只需要同時(shí)驗(yàn)證樣式?jīng)]有被修改或篡改。
Nonce的功能與hash類(lèi)似。使用nonce,將為每個(gè)請(qǐng)求生成一個(gè)新的隨機(jī)數(shù),這使得攻擊者更難猜測(cè)它的值。這避免了hash的一個(gè)關(guān)鍵缺點(diǎn),即多個(gè)輸入可能生成相同的hash值(稱(chēng)為沖突)。
驗(yàn)證外部托管的樣式表
樣式表通常托管在第三方服務(wù)器上,如內(nèi)容交付網(wǎng)絡(luò)(content delivery networks, CDNs),但這帶來(lái)了新的攻擊方向。如果CDN受到威脅,如何阻止攻擊者用自己修改過(guò)的版本替換樣式表?子資源完整性,也叫SRI,試圖解決這個(gè)問(wèn)題。
SRI使用hash值來(lái)驗(yàn)證腳本和樣式表的內(nèi)容。計(jì)算每個(gè)文件的hash值,并將其附加到HTML元素的integrity屬性中。當(dāng)瀏覽器下載腳本或樣式表時(shí),計(jì)算其hash值并將其與存儲(chǔ)在屬性中的值進(jìn)行比較。如果匹配,瀏覽器將加載腳本或樣式。
這是在假設(shè)web頁(yè)面是從受信任的源(如源服務(wù)器)提交的情況下運(yùn)行的,而當(dāng)資源是從不受信任的源(如第三方)提交的時(shí)候,就無(wú)法正常運(yùn)行。如果web頁(yè)面和資源都由第三方托管,攻擊者只需要簡(jiǎn)單地修改web頁(yè)面來(lái)匹配其CSS替換文件的hash值即可。
結(jié)論
雖然通過(guò)CSS跟蹤用戶(hù)的能力并不新鮮,但它確實(shí)要求我們以不同的方式考慮網(wǎng)頁(yè)上的隱私和安全性。CSS是現(xiàn)代網(wǎng)頁(yè)的基本語(yǔ)言之一,禁用網(wǎng)站的CSS將使網(wǎng)頁(yè)的大部分內(nèi)容無(wú)法使用。內(nèi)容安全策略是阻止XSS攻擊和CSS泄漏的最佳方法。Templarbit創(chuàng)建了一個(gè)“靈活的內(nèi)容-安全-策略工作流”,以便于維護(hù)CSP頭文件。如果你的團(tuán)隊(duì)正在努力為你的應(yīng)用程序推出CSP,請(qǐng)立即注冊(cè)一個(gè)免費(fèi)試用版,并學(xué)習(xí)更多關(guān)于Templarbit如何解決CSS泄露的方法.
你可以在GitHub上找到B?hmer的概念驗(yàn)證的源代碼。
相關(guān)連接:
網(wǎng)站——http://crookedss.bplaced.net/
最大的安全好處——https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles
通常鎖定所有內(nèi)聯(lián)腳本——https://developers.google.com/web/fundamentals/security/csp/
加入白名單——https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
并不新鮮——https://www.smashingmagazine.com/2014/10/css-only-solution-for-ui-tracking/
現(xiàn)在就注冊(cè)獲取免費(fèi)版——https://www.templarbit.com/signup
GitHub——https://github.com/jbtronics/CrookedStyleSheets
相關(guān)知識(shí):
SRI,Subresource Integrity 的縮寫(xiě),中文:子資源完整性,由 Web 應(yīng)用安全工作組(Web Application Security Working Group)發(fā)布。
英文原文:https://www.templarbit.com/blog/2018/03/20/tracking-users-with-css/
譯者:憂(yōu)郁的紅秋褲
互聯(lián)網(wǎng)上有很多資源可以找到關(guān)于機(jī)器學(xué)習(xí)數(shù)據(jù)集的見(jiàn)解和訓(xùn)練模型,但是關(guān)于如何使用這些模型構(gòu)建實(shí)際應(yīng)用程序的文章很少。
因此,今天我們將通過(guò)首先使用hackathon中的數(shù)據(jù)集來(lái)訓(xùn)練視頻游戲銷(xiāo)售預(yù)測(cè)模型,然后使用經(jīng)過(guò)訓(xùn)練的模型來(lái)創(chuàng)建一個(gè)基本應(yīng)用程序,根據(jù)用戶(hù)輸入為我們提供銷(xiāo)售預(yù)測(cè)來(lái)學(xué)習(xí)此過(guò)程。
本文分為多個(gè)部分,你可以一個(gè)接一個(gè)地學(xué)習(xí),而不必一口氣完成它。從我第一次選擇數(shù)據(jù)集以來(lái),我花了整整一周的時(shí)間來(lái)完成應(yīng)用程序。因此,請(qǐng)花時(shí)間專(zhuān)注于學(xué)習(xí)構(gòu)建應(yīng)用程序的各個(gè)方面,而不是只注意最終產(chǎn)品。
我們將使用在Machine Hack網(wǎng)站上運(yùn)行的視頻游戲銷(xiāo)售預(yù)測(cè)hackathon中的數(shù)據(jù)集。首先,在MachineHack上創(chuàng)建一個(gè)帳戶(hù),然后在此鏈接上注冊(cè)hackathon。
注冊(cè)后,轉(zhuǎn)到數(shù)據(jù)標(biāo)簽并下載zip文件,該文件將包含三個(gè)文件,即訓(xùn)練,測(cè)試和樣品提交。
下一步將在Google Colab Notebook中介紹,你可以從以下鏈接打開(kāi)和克隆該Notebook:
或者如果你想在本地或其他平臺(tái)上下載并運(yùn)行該Notebook,請(qǐng)從以下GitHub鏈接下載該Notebook:Jupyter Notebook鏈接
Notebook電腦的第一部分簡(jiǎn)要介紹了問(wèn)題陳述。通過(guò)運(yùn)行下面顯示的下一個(gè)代碼單元,上傳我們收到的文件
from google.colab import files
uploaded=files.upload()
for fn in uploaded.keys():
print('User uploaded file "{name}" with length {length} bytes'.format(
name=fn, length=len(uploaded[fn])))
在下一個(gè)代碼單元中,我們導(dǎo)入所需的python包。它們中的大多數(shù)已預(yù)先安裝在Google Colab中,因此無(wú)需安裝它們中的任何一個(gè)。
因?yàn)槲覀儫o(wú)法在hackathon結(jié)束后提交測(cè)試數(shù)據(jù)進(jìn)行評(píng)估,因此本文其余部分僅將數(shù)據(jù)用于Train.csv。請(qǐng)記住,Train.csv的行數(shù)少于通常用于正確訓(xùn)練模型的行數(shù)。但是,出于學(xué)習(xí)目的,我們可以使用行數(shù)較少的數(shù)據(jù)集。
現(xiàn)在讓我們深入研究解決此機(jī)器學(xué)習(xí)問(wèn)題…
步驟1:識(shí)別目標(biāo)和獨(dú)立特征
首先,讓我們將Train.csv導(dǎo)入pandas數(shù)據(jù)框中,然后運(yùn)行df.head()以查看數(shù)據(jù)集中的列。
從數(shù)據(jù)框中,我們可以看到目標(biāo)列是SalesInMillions,其余列是獨(dú)立特征
步驟2:清理資料集
首先,我們通過(guò)運(yùn)行input.isnull().sum()命令檢查null值。
input.isnull().sum()
#Output:
ID 0
CONSOLE 0
YEAR 0
CATEGORY 0
PUBLISHER 0
RATING 0
CRITICS_POINTS 0
USER_POINTS 0
SalesInMillions 0
dtype: int64
我們可以看到數(shù)據(jù)集中沒(méi)有空值。接下來(lái),我們可以通過(guò)運(yùn)行以下命令刪除不必要的列ID,因?yàn)樗谀繕?biāo)銷(xiāo)售中不起作用:input=input.drop(columns=['ID'])
接下來(lái),我們可以使用train_test_split命令將數(shù)據(jù)框分為訓(xùn)練和測(cè)試數(shù)據(jù)集:
train, test=train_test_split(input, test_size=0.2, random_state=42, shuffle=True)
步驟3:探索性數(shù)據(jù)分析
描述性統(tǒng)計(jì)信息
使用df.shape命令我們可以找到數(shù)據(jù)集中的總行數(shù),并且可以使用命令df.nunique()在每個(gè)列中查找唯一值。
CONSOLE 17
YEAR 23
CATEGORY 12
PUBLISHER 184
RATING 6
CRITICS_POINTS 1499
USER_POINTS 1877
SalesInMillions 2804
在EDA部分中,我們使用pandas profiling和matplotlib包生成各種列的圖形,并觀察它們與目標(biāo)列的關(guān)系。
從EDA獲得的一些見(jiàn)解是:
PS3平臺(tái)的銷(xiāo)售額最高。之后是Xbox360:
動(dòng)作類(lèi)別的銷(xiāo)售額最高,難題類(lèi)別的銷(xiāo)售額最低
2007年到2011年的銷(xiāo)售額最高:
通常,我們?cè)贓DA之后進(jìn)行特征工程或特征選擇步驟。但是,我們的功能較少,著重于實(shí)際使用模型。因此,我們正在朝著下一步邁進(jìn)。但是,請(qǐng)記住,USER_POINTS和CRITICS_POINTS列可用于派生其他功能。
步驟4:建立模型
由于我們具有許多分類(lèi)功能,因此我們將對(duì)數(shù)據(jù)集使用catboost回歸模型。由于catboost可以直接作用于分類(lèi)特征,因此跳過(guò)了對(duì)分類(lèi)特征進(jìn)行標(biāo)簽編碼的步驟。
首先,我們使用pip install命令安裝catboost軟件包。
然后,我們創(chuàng)建一個(gè)分類(lèi)特征列表,將其傳遞給模型,然后將模型擬合到訓(xùn)練數(shù)據(jù)集上:
import catboost as cat
cat_feat=['CONSOLE','CATEGORY', 'PUBLISHER', 'RATING']
features=list(set(train.columns)-set(['SalesInMillions']))
target='SalesInMillions'
model=cat.CatBoostRegressor(random_state=100,cat_features=cat_feat,verbose=0)
model.fit(train[features],train[target])
步驟5:檢查模型的準(zhǔn)確性
首先,我們根據(jù)測(cè)試數(shù)據(jù)集創(chuàng)建真實(shí)的預(yù)測(cè):
y_true=pd.DataFrame(data=test[target], columns=['SalesInMillions'])
test_temp=test.drop(columns=[target])
接下來(lái),我們?cè)跍y(cè)試數(shù)據(jù)集上運(yùn)行訓(xùn)練良好的模型以獲取模型預(yù)測(cè)并檢查模型準(zhǔn)確性
y_pred=model.predict(test_temp[features])
from sklearn.metrics import mean_squared_error
from math import sqrt
rmse=sqrt(mean_squared_error(y_true, y_pred))
print(rmse)
#Output: 1.5555409360901584
我們的RMSE值為1.5,這相當(dāng)不錯(cuò)。有關(guān)在出現(xiàn)回歸問(wèn)題時(shí)準(zhǔn)確性指標(biāo)的更多信息,可以參考本文。
如果你想進(jìn)一步改善模型或嘗試組合各種模型,可以在本文中參考本次hackathon獲勝者的方法:
步驟6:將模型保存到pickle文件中
現(xiàn)在,我們可以將模型保存到pickle文件中,然后將其保存在本地:
import pickle
filename='finalized_model.sav'
pickle.dump(model, open(filename, 'wb'))
保存pickle文件后,你可以從Google Colab Notebook文件部分的左側(cè)邊欄中下載并保存在本地
額外提示
我們可以通過(guò)向模型添加更多數(shù)據(jù)來(lái)改善模型預(yù)測(cè)。我們可以使用一些在Kaggle上的相關(guān)的數(shù)據(jù)集。Kaggle:https://www.kaggle.com/gregorut/videogamesales
我們可以使用組合模型的堆棧來(lái)進(jìn)一步提高模型效率。
如果你已完成此步驟,請(qǐng)輕拍一下自己的背,因?yàn)槲覀儎倓偼瓿闪隧?xiàng)目的第一個(gè)主要部分。休息一會(huì)兒,拉伸一下,然后開(kāi)始本文的下一部分。
我們將使用Python Flask創(chuàng)建后端API。
因此,首先在本地創(chuàng)建一個(gè)名為server的文件夾。另外,如果還沒(méi)有,請(qǐng)?jiān)谀愕挠?jì)算機(jī)上安裝Python和pip軟件包管理器。
接下來(lái),我們需要在文件夾中創(chuàng)建一個(gè)虛擬環(huán)境。我在Linux上使用python3,因此我創(chuàng)建虛擬環(huán)境的命令為:python3 -m venv server。
你可以在本文中查看適用于你的OS和Python版本的命令:Python venv:https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/
接下來(lái),我們將通過(guò)運(yùn)行以下命令激活虛擬環(huán)境: source server/bin/activate
完成后,我們需要安裝Flask pip軟件包: pip install -U Flask
接下來(lái),使用你喜歡的文本編輯器在服務(wù)器文件夾中創(chuàng)建一個(gè)名為“app.py”的文件,并添加以下代碼以創(chuàng)建基本的API:
from flask import Flask, jsonify, make_response, request, abort
app=Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__=="__main__":
app.run()
現(xiàn)在打開(kāi)終端并運(yùn)行python3 app.py以啟動(dòng)服務(wù)器。這將主要在5000端口上啟動(dòng)服務(wù)器。為了測(cè)試API,請(qǐng)?jiān)跒g覽器中打開(kāi)此鏈接:http://localhost:5000/
你應(yīng)該在瀏覽器中打印出Hello World。如果不是,則在啟動(dòng)API時(shí)檢查API是否在其他端口上運(yùn)行或在終端上打印錯(cuò)誤。
我們將調(diào)用POST API,因此最好在繼續(xù)進(jìn)行之前安裝Postman工具。使用此工具將向服務(wù)器發(fā)送POST請(qǐng)求。
接下來(lái),我們需要使用以下命令安裝catboost,pandas和Flask-Cors pip軟件包:
pip install catboost pandas Flask-Cors
接下來(lái),將我們?cè)诘?部分末尾下載的經(jīng)過(guò)訓(xùn)練的模型的pickle文件(finalized_model.sav)復(fù)制到服務(wù)器文件夾中。
現(xiàn)在,使用以下代碼更新 app.py:
from flask import Flask, jsonify, make_response, request, abort
import pandas as pd
import catboost
import pickle
from flask_cors import CORS,cross_origin
model=pickle.load(open( "finalized_model.sav", "rb"))
app=Flask(__name__)
app.config['CORS_HEADERS']='Content-Type'
cors=CORS(app)
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
@app.route("/")
def hello():
return "Hello World!"
@app.route("/get_prediction", methods=['POST','OPTIONS'])
@cross_origin()
def get_prediction():
if not request.json:
abort(400)
df=pd.DataFrame(request.json, index=[0])
cols=["CONSOLE","RATING","CRITICS_POINTS","CATEGORY","YEAR","PUBLISHER","USER_POINTS"]
df=df[cols]
return jsonify({'result': model.predict(df)[0]}), 201
if __name__=="__main__":
app.run()
在第6行中,我們將訓(xùn)練后的模型導(dǎo)入到我們的python文件中。
在第10行,我們初始化CORS模塊以允許來(lái)自客戶(hù)端API調(diào)用的請(qǐng)求。
在第11行,我們定義了一個(gè)錯(cuò)誤處理程序,如果從服務(wù)器訪(fǎng)問(wèn)了任何未處理的異常或未定義的路徑,它將發(fā)送錯(cuò)誤響應(yīng)。
對(duì)我們來(lái)說(shuō),主要的興趣點(diǎn)是從第19行定義的get_prediction POST API。get_prediction方法是我們從客戶(hù)端獲取數(shù)據(jù)并提供響應(yīng)的銷(xiāo)售預(yù)測(cè)。
在第24行,我們將來(lái)自API請(qǐng)求的數(shù)據(jù)轉(zhuǎn)換為pandas數(shù)據(jù)框。現(xiàn)在,我們的模型期望列以特定順序提供正確的響應(yīng)。因此,在第25行中,我們指定列順序。在接下來(lái)的步驟中,以所需順序重新排列列。
在第27行,model.predict用于從模型中獲取預(yù)測(cè),并將其作為響應(yīng)傳遞給客戶(hù)端。在這一步,我們準(zhǔn)備好在本地使用該API。我們可以通過(guò)發(fā)送POST-API調(diào)用來(lái)測(cè)試Postman客戶(hù)端中的API,如截圖所示:
你可以在上述請(qǐng)求的正文部分中添加一個(gè)JSON示例,可以在代碼Github-gist中找到。
確保在主體和主體類(lèi)型中選擇raw和JSON選項(xiàng)(如屏幕截圖所示),并在請(qǐng)求類(lèi)型中選擇POST。
如果在此步驟之前一切正常,那么恭喜你,你現(xiàn)在有了一個(gè)后端API,該API可根據(jù)輸入?yún)?shù)根據(jù)經(jīng)過(guò)訓(xùn)練的模型進(jìn)行預(yù)測(cè)。
額外提示
在后端設(shè)計(jì)中不建議在單個(gè)文件中編寫(xiě)API,我們可以將路徑和模型導(dǎo)入分隔到不同的文件夾中,以使代碼更具模塊化。如果將來(lái)引入其他API,這也將使我們能夠以可管理的方式擴(kuò)展代碼。
在這一點(diǎn)上,我們可以再次休息一下,確保收藏了本文,以便輕松地重新開(kāi)始本項(xiàng)目的下一部分。
到目前為止,我們的API在本地工作,但我們需要將其部署在遠(yuǎn)程服務(wù)器上,以供其他地方使用。為此,我們將使用Heroku作為我們的API托管平臺(tái)。
我主要參考了來(lái)自stackabuse 的文章,將其部署到Heroku。我們將簡(jiǎn)要介紹這些步驟,但是如果你卡在了其中某個(gè)步驟,請(qǐng)?jiān)诖颂巺㈤喸嘉恼拢?/p>
首先,我們使用terminal命令安裝gunicorn:
pip install gunicorn
接下來(lái),運(yùn)行下面的命令將所有已安裝的pip包,存儲(chǔ)到require.txt文件:
pip freeze > requirements.txt
你可以參考此處上傳的requirements.txt文件以供參考:
接下來(lái),Procfile使用以下代碼在服務(wù)器文件夾中創(chuàng)建一個(gè)名稱(chēng)為Procfile的文件:
web: gunicorn app:app
現(xiàn)在,在Heroku網(wǎng)站上注冊(cè),在該網(wǎng)站上創(chuàng)建一個(gè)應(yīng)用,然后按照原始文章中的說(shuō)明安裝Heroku CLI 。
接下來(lái),通過(guò)運(yùn)行以下命令從本地終端登錄Heroku: heroku login -i
使用以下命令添加你的Heroku應(yīng)用git參考:
heroku git:remote -a {your-project-name}
現(xiàn)在,使用以下命令將代碼推送到Heroku:
git push heroku master
在運(yùn)行上述命令的最后,你將在終端輸出中獲得API URL,現(xiàn)在我們可以使用該URL從客戶(hù)端進(jìn)行調(diào)用。此時(shí),我們還可以從PostMan應(yīng)用程序發(fā)送API請(qǐng)求,以查看我們是否正確收到了與步驟2末尾所述類(lèi)似的響應(yīng)。
到目前為止的代碼庫(kù)可以在以下Github存儲(chǔ)庫(kù)中找到
現(xiàn)在,我們?cè)诜?wù)器上托管了一個(gè)正常工作的API。如果一切正常,那么我們可以繼續(xù)開(kāi)發(fā)客戶(hù)端應(yīng)用程序。如果遇到任何問(wèn)題,請(qǐng)?jiān)谠u(píng)論部分中提及你的問(wèn)題。
我們將需要在我們的計(jì)算機(jī)上正確安裝和設(shè)置Node.js。因此,請(qǐng)先下載并安裝適用于你相關(guān)操作系統(tǒng)和系統(tǒng)的Node.js,然后再繼續(xù)進(jìn)行操作。另外,建議安裝yarn管理器:yarn 安裝
現(xiàn)在,在上一步中創(chuàng)建frontend的服務(wù)器文件夾外部創(chuàng)建一個(gè)新文件夾,并從終端進(jìn)入該frontend文件夾內(nèi)部。
接下來(lái),我們將創(chuàng)建一個(gè)新的react應(yīng)用程序,并通過(guò)在終端中運(yùn)行以下命令來(lái)啟動(dòng)它:
npx create-react-app sales-prediction-app
cd sales-prediction-app
npm start
你應(yīng)該在瀏覽器中看到默認(rèn)打開(kāi)的瀏覽器選項(xiàng)卡,以及react.js默認(rèn)模板應(yīng)用程序。現(xiàn)在我們需要在我們最喜歡的編輯器中打開(kāi)該項(xiàng)目(我正在使用VSCode),并開(kāi)始進(jìn)行更改以構(gòu)建前端應(yīng)用程序。
首先,我們需要在應(yīng)用程序公共文件夾中的index.html文件中導(dǎo)入相關(guān)的引導(dǎo)程序文件。
我們需要按照bootstrap文檔提供的說(shuō)明在index.html文件中添加文件,如下所示:
<head>
...
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
...
</head>
<body>
...
<div id="root"></div>
...
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
...
</body>
我們的最終用戶(hù)界面是集合下拉菜單項(xiàng),其中單個(gè)項(xiàng)如下所示:
我們將在src文件夾中創(chuàng)建一個(gè)名稱(chēng)為optionsSources.json的JSON文件。JSON文件中的每個(gè)條目都包含以下對(duì)象:
{
"CONSOLE": {
"options": [
"ps2","x360","ps3","pc"
],
"icon": "?",
"dropDownPlaceholder": "Select Console"
}
}
下拉菜單中顯示的選項(xiàng)位于options數(shù)組中,下拉菜單選項(xiàng)左側(cè)顯示的圖標(biāo)和標(biāo)簽位于icon和dropDownPlaceholder項(xiàng)。我們需要?jiǎng)?chuàng)建多個(gè)這樣的下拉列表,因此要添加的完整JSON文件如以下文件所示:
接下來(lái),我們需要在我們的應(yīng)用程序中實(shí)現(xiàn)下拉組件。在src文件夾中創(chuàng)建一個(gè)名為components的文件夾,并在components文件夾中創(chuàng)建一個(gè)名為OptionSelection.js的文件
我們將編寫(xiě)一個(gè)功能組件,該組件返回一個(gè)下拉項(xiàng),如下所示:
import React,{ useState } from 'react';
import optionSources from '../optionsSources.json';
function OptionSelection({itemKey, setOptionInObject}) {
const title=optionSources[itemKey].dropDownPlaceholder;
const icon=optionSources[itemKey].icon;
return(
<div className="d-flex justify-content-start align-items-center mt-2 selection-item">
<div className="option-label">
<b><span role="img" aria-label="label-icon">{icon}</span>{` ${title}`}</b>
</div>
<div className="dropdown ml-4">
<button className="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{title}
</button>
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
{renderOptionsDropdown()}
</div>
</div>
</div>
)
}
export default OptionSelection;
在上面的組件中,我們itemKey從第3行的父組件中獲得prop(param)值。我們假設(shè)itemKey從父組件接收的是CONSOLE。在第4行和第5行,我們首先提取顯示在下拉菜單左側(cè)的標(biāo)題和圖標(biāo)。然后,根據(jù)Boostrap文檔在創(chuàng)建下拉列表時(shí),在第6行的返回函數(shù)中使用HTML標(biāo)簽。
接下來(lái),我們需要實(shí)現(xiàn)renderOptionsDrop在返回函數(shù)中定義的函數(shù),如下所示:
import optionSources from '../optionsSources.json';
function OptionSelection({itemKey, setOptionInObject}) {
...
const renderOptionsDropdown=()=> {
const selectionOptions=optionSources[itemKey].options;
return selectionOptions.map((selectionOption, index)=>{
return (
<div className="dropdown-item pointer"
key={`${index}${selectionOption}`}
onClick={()=> handleDropDownSelection(selectionOption)}
>
{selectionOption}
</div>
);
})
}
...
}
在第5行,我們從optionSources JSON對(duì)象獲取特定項(xiàng)的options數(shù)組,并將其存儲(chǔ)在selectionOptions變量中。
然后在第6行,我們使用map函數(shù)迭代數(shù)組并顯示下拉選擇項(xiàng)。我們必須在第10行使用onClick函數(shù)更新下拉項(xiàng)的選定值。
然后實(shí)現(xiàn)onClick處理程序中的函數(shù)handleDropDownSelection,如下所示:
import React,{ useState } from 'react';
...
function OptionSelection({itemKey, setOptionInObject}) {
const [currentSelectedOption, setSelectedOption]=useState(null);
const handleDropDownSelection=(consoleOption)=> {
setSelectedOption(consoleOption)
setOptionInObject(itemKey, consoleOption)
}
...
}
我們?cè)诘?行輸入了useState hook。它是一個(gè)內(nèi)部函數(shù),允許我們使用狀態(tài)變量的概念動(dòng)態(tài)更新值。關(guān)于這個(gè)函數(shù)的更多信息可以在這里找到:
在第7行,我們更新下拉列表的選定選項(xiàng)。在第8行中,我們將選擇的值傳遞回父函數(shù)以進(jìn)行進(jìn)一步處理。
這個(gè)組件的完整代碼可以在這里找到:https://github.com/codeclassifiers/video-salesprediction-frontend/blob/master/src/components/ConsoleSelection.js
然后我們?cè)趕rc文件夾中導(dǎo)入此選項(xiàng)并對(duì)服務(wù)器進(jìn)行API調(diào)用。完整的代碼可以在這里找到:
然后在handleInputSubmission函數(shù)中對(duì)后端進(jìn)行API調(diào)用,如下所示:
import React, {useState} from 'react';
import axios from 'axios';
function App() {
...
const handleInputSubmission=()=> {
if(selectedObject && Object.keys(selectedObject).length===7) {
...
axios.post(process.env.REACT_APP_HEROKU_SERVER_URL, selectedObject)
.then(function (response) {
setPredictionLoading(false)
setModelPrediction(response.data.result)
})
.catch(function (error) {
setPredictionLoading(false)
setRequestFailed("Some error ocurred while fetching prediction")
});
} else {
setRequestFailed("Please select all fields before submitting request")
}
}
}
我們正在使用Axios npm模塊對(duì)后端Heroku服務(wù)器進(jìn)行POST API調(diào)用。確保在process.env.REACT_APP_HEROKU_SERVER_URL占位符的第8行上添加自己的Heroku服務(wù)器URL,以接收來(lái)自服務(wù)器API的響應(yīng)。
最好將API URL變量保存在.env文件中,然后在部署環(huán)境中進(jìn)行設(shè)置。可以在這里找到更多詳細(xì)信息:
在此處找到Github上的前端應(yīng)用程序的完整資源:
這使我們有了在線(xiàn)部署Web應(yīng)用程序的最后一步。因此,請(qǐng)耐心一些,讓我們開(kāi)始項(xiàng)目的最后一步。
Netlify是一個(gè)可以輕松在線(xiàn)部署靜態(tài)網(wǎng)站的平臺(tái)。在部署使用createreact app模塊生成的應(yīng)用程序時(shí),它有一個(gè)非常簡(jiǎn)單的過(guò)程。我們將利用此服務(wù)在線(xiàn)托管我們的web應(yīng)用程序。
首先,我們需要在Github上創(chuàng)建一個(gè)帳戶(hù)。
然后,我們需要將前端文件夾上傳到Github存儲(chǔ)庫(kù)。我們可以按照官方文檔中顯示的步驟將項(xiàng)目部署到Github:官方文檔(https://docs.github.com/en/github/importing-your-projects-to-github/adding-an-existing-project-to-github-using-the-command-line)
一旦該項(xiàng)目在GitHub上進(jìn)行部署,通過(guò)遵循以下官方文檔即可簡(jiǎn)單,直接地完成netlify的過(guò)程:Netlify Deploy(https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/)
如果你已在上一步中將環(huán)境變量用于服務(wù)器URL,請(qǐng)確保如本文檔所示將其添加到netlify dashboard中。
最后,我們將提供一個(gè)如下所示的網(wǎng)絡(luò)應(yīng)用程序:
額外提示
老實(shí)說(shuō),上面的UI非常簡(jiǎn)單。它沒(méi)有很好的配色方案(主要是因?yàn)橄裎疫@樣的開(kāi)發(fā)人員不是優(yōu)秀的設(shè)計(jì)師)。你可以改善設(shè)計(jì)并調(diào)整CSS,以更好地查看網(wǎng)頁(yè)。
這樣就完成了從機(jī)器學(xué)習(xí)hackathon數(shù)據(jù)集創(chuàng)建銷(xiāo)售預(yù)測(cè)Web應(yīng)用程序的過(guò)程。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。