費開源可商用PHP萬能建站程序
增加平板排除為移動端的識別開關[6-3]
增加網站表單和模塊表單[未通過]狀態的設置[6-4]
關聯內容和關聯用戶字段增加最大數量選擇設置值[6-8]
增加自動獲取[單文件、多文件、圖片字段]的首圖作為縮略圖開關[6-9]
增加模板標簽modules支持多個模塊組合查詢[6-15]
增加開發者工具欄views選項,用于查看當前頁面的模板路徑、加載時間、模板變量[6-15]
增加后臺界面右側導航:快速菜單鏈接、訪問歷史菜單記錄[6-18]
增加html實體字符轉換函數:dr_html_code[6-21]
增加域名準確性驗證form類方法[6-23]
在開發者工具欄目views選項中加入模板引用提示信息[6-23]
系統體檢時增加移動端的常規配置檢測[6-23]
增加判斷數據庫名稱的規范性語句[6-25]
增加文件上傳后的鉤子點(upload_file)[7-2]
增加訪問網站首頁的鉤子點(cms_index)[7-3]
在刪除用戶賬號時增加確認刪除范圍按鈕,全部刪除|部分刪除[7-4]
內容發布前的基礎類_content_post_before增加錯誤返回值寫法[7-6]
增加一鍵初始化整站用戶的權限設置[7-10]
手機域名綁定界面增加測試按鈕,測試域名是否綁定成功[7-12]
關聯用戶字段Members增加設置只允許關聯的用戶組選項[7-13]
前端首頁index.html模板中增加[滾動調用示例],便于學習研究[7-13]
增加用戶組有效時長單位(天/月/年)[7-14]
增加用戶退出登錄的鉤子member_logout [7-15]
變更域名處增加測試新域名是否可用狀態檢測 [7-15]
修復每日登陸的exp經驗值會重復累加問題[6-2]
欄目目錄支持(空格 _ -)符號[6-2]
應用程序不存在時標記為404狀態[6-2]
欄目管理界面提供檢測未安裝的模塊欄目,并提示模塊未安裝[6-3]
后臺生成菜單字符串允許開發者使用自定義函數來重組變更[6-3]
獨立模塊的欄目設置單頁屬性時自動創建content字段屬性[6-5]
將模塊評論功能從框架中移除[6-7]
修復內容統計分時間段數據無效的問題[6-7]
生成規則是加入對欄目目錄的新規則識別[6-9]
模板標簽支持對field自定義別名的匹配[6-11]
關聯字段支持對主題名稱顯示值的修改[6-12]
后臺會員資料修改界面僅超管賬戶可看到全部字段數據[6-14]
上傳類字段瀏覽界面中刪除未使用的附件時新增提示刪除操作[6-15]
百度編輯器上傳視頻播放器由embed改成video[6-16]
將第三方登錄程序轉換為組件,需在應用市場下載該組件[6-17]
優化錯誤日志顯示記錄[6-18]
用戶通知設置處改為按插件分組歸類顯示[6-19]
修復后臺在編輯模板代碼時出現的異常情況[6-20]
Clink和Cbottom支持加載自身模塊的屬性規則[6-22]
修復用戶組等級管理單位值會顯示錯誤的情況[6-22]
修復ftable在編輯刪除選項后會引起存儲異常的情況[6-23]
修復CNVD-C-2020-131664代碼執行漏洞[6-23]
單頁欄目seo標簽中剔除“共享”文字[6-23]
修復后臺發布內容是選擇更新時間存儲無效的問題[6-29]
修復移除內容推薦位時報錯問題[6-30]
Textarea和Ueditor字段的默認存儲值輸入框改成多行輸入框[7-3]
后臺系統回收充值金額處增加回收次數限制判斷[7-7]
修復本地磁盤存儲策略無法獲取圖片尺寸信息問題[7-9]
在欄目管理處增加用戶權限設置功能[7-10]
模塊推薦位數量改為無限添加[7-10]
文件上傳字段File和Files進行優化JS代碼[7-13]
特別升級提示:
1、使用過快捷登錄(微信、QQ),需要在應用市場下載安裝對應的登錄插件(不影響升級數據)
2、使用過[評論功能]的,需要在應用市場下載安裝[評論系統]插件(不影響升級數據)
后臺截圖
什么是表單呢?當前端想要提交數據給后端,怎么搞?那么在前端開發中,表單是常用的手段,比如常見的場景有:登錄框、賬號注冊頁、主機信息錄入CMDB等等場景都是需要表單。那么在本篇中,筆者除了講一些基本的知識點,還會再結合后端的方式來演示如何接收表單提交的數據。希望這些小小的演示可以起到拋磚引玉的效果。
構建表單,主要是通過from元素,我們先來一個最簡單的小栗子,看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息錄入CMDB</h3>
<form>
<label for="hostname">主機名:</label><br>
<input type="text" id="hostname" name="hostname"><br>
<label for="ipaddr">IP地址:</label><br>
<input type="text" id="ipaddr" name="ipaddr"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果如下圖:
通上面的小栗子可以知道,form表單的主要通途是用于收集用戶的輸入。在from表單里面,還包含著各種不同類型的input元素,比如我們上面小栗子中用到的文本(text)、提交按鈕(submit)。
input元素是表單里最重要的元素,它有很多type屬性,下面我們來總結下:
類型描述text文本輸入radio單選按鈕checkbox提交按鈕submit提交按鈕button可單擊按鈕
在上面小栗子中,除了input元素之外,不知道大家注意label元素沒有。label元素的主要用途是為input元素定義標簽,且用for屬性和input元素的id屬性進行綁定呢。
什么是單選按鈕?就是在多個選項中,你只能選其中1個,不能多選。下面我們看個小栗子,看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>問答題1(單選):某站長,工作經驗不足1年,僅從互聯網收集學習資料制定學習路線售賣盈利,從道德層面角度分析是否有問題?</h3>
<form>
<input type="radio" id="i1" name="problem" value="yes">
<label for="i1">有</label>
<input type="radio" id="i2" name="problem" value="no">
<label for="i2">沒有</label>
<input type="radio" id="i3" name="problem" value="not_clear">
<label for="i3">不清楚</label>
</form>
<h3>問答題2(單選):實際工作經驗不足1年的人員折騰的學習資料您覺得是否對你有幫助?</h3>
<form>
<input type="radio" id="w1" name="problem" value="yes">
<label for="w1">有</label>
<input type="radio" id="w2" name="problem" value="no">
<label for="w2">沒有</label>
<input type="radio" id="w3" name="problem" value="not_clear">
<label for="w3">不清楚</label>
</form>
</body>
</html>
效果如下圖:
上面的小栗子中,出了兩道問答題,均為單選題。那么,類似的需求都是可以使用輸入類型為radio來實現需要使用單選按鈕的場景。
什么是復選框?復選框就是可以選擇多個選項,當需要多選的時候,使用復選框輸入類型就對了。看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>問答題1(單選):某站長,工作經驗不足1年,僅從互聯網收集學習資料制定學習路線售賣盈利,從道德層面角度分析是否有問題?</h3>
<form>
<input type="checkbox" id="i1" name="problem" value="yes">
<label for="i1">有</label>
<input type="checkbox" id="i2" name="problem" value="no">
<label for="i2">沒有</label>
<input type="checkbox" id="i3" name="problem" value="not_clear">
<label for="i3">不清楚</label><br>
<input type="submit" value="提交">
</form>
<h3>問答題2(單選):實際工作經驗不足1年的人員折騰的學習資料您覺得是否對你有幫助?</h3>
<form>
<input type="checkbox" id="w1" name="problem" value="yes">
<label for="w1">有</label>
<input type="checkbox" id="w2" name="problem" value="no">
<label for="w2">沒有</label>
<input type="checkbox" id="w3" name="problem" value="not_clear">
<label for="w3">不清楚</label><br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果如下圖:
上面的小栗子中,實現復選框的輸入類型是checkbox,這個是重點哦!
當有數據要提交給后端的時候怎么搞?如果后端是API服務,可以給它提交json。如果是前端頁面,就需要通過構建表單來獲取用戶的輸入。基于表單提交數據給后端,怎么提交?需要一個可以點擊的提交按鈕,那這個按鈕怎么來?先看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form>
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果如下圖:
輸入類型為submit,說明它就是提交按鈕,注意上面代碼type="submit"了嗎?
在之前的例子中,前端表單需要將數據提交給后端,除了需要一個提交按鈕外,還需要action屬性。當點擊提交按鈕后,表單的數據該發到后端的哪個url進行處理,就是定義在action屬性中。接下來,我們結合前端和后端直接來個小實戰,后端代碼用Python的Flask框架。
前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank">
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端代碼:
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=["GET", "POST"])
def ttropsstack():
args = request.args
print 'ip addr is: %s' % args.get('ip')
return 'ok'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
前端和后端的代碼寫完后,我們接下來看看效果
輸入IP地址后,點擊提交
這個ok是后端返回的
后端接收到數據后,啥也沒做,只是做了簡單打印
這個小栗子很簡單,通過這個小栗子,是不是對action屬性的用法有了進一步的理解呢?
method屬性的用途是指定提交數據的http方法,通常有2個,get和post。接下來我們在上個小栗子的基礎上做個小改造,看下面代碼
前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="get">
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端代碼:
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=["POST"])
def ttropsstack():
args = request.args
print 'ip addr is: %s' % args.get('ip')
return 'ok'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
當前端輸入數據后,提交表單時,直接告訴你這是不允許的方法
在這個例子中,是因為前端的表單了指定了method為get請求,而后端接收數據的method規定了需要post請求,故所以出現這個問題。
下面我們改造一下后端代碼:
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
print request.get_data(as_text=True)
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
前端表單中的method還是保持get請求,再次提交,后端的返回如下:
看到了嗎?后端判斷前端過來的請求是get還是post,很顯然,前端過來的請求是get,并且返回了非常友好的提示。
接下來我們繼續改造一下前端的代碼,將請求修改為post,代碼如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="post">
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端代碼也稍微改造一下,改變接收前端數據的方法
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
a = request.form
print a.get('ip')
print type(a)
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
輸入IP地址,并點擊提交
提交后,后端給前端返回了ok
接下來看下后端,后端啥也沒做,就獲取到表單的數據,然后打印了數據,并且打印了下數據類型
好了,關于前端的action屬性和Method屬性就講到這里了。為了講解action和method,還結合了后端的一丟丟知識,前端和后端的知識點以后都會慢慢講到哈!
先來個前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="post">
<label for="opslist">運維開發應掌握的技能:</label>
<select id="opslist" name="opslist">
<option value="python">Python語言</option>
<option value="go">Go語言</option>
<option value="shell">Shell語言</option>
<option value="database">數據庫</option>
<option value="frontend">前端</option>
<option value="linux">Linux</option>
<option value="network">網絡</option>
<option value="storage">存儲</option>
</select>
<input type="submit">
</form>
</body>
</html>
后端代碼:
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
a = request.form
print a.get('opslist')
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
在下拉框中選擇“Go語言”,并提交
后端啥也沒干,就只做了打印
前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="post">
<label for="opslist">運維開發應掌握的技能:</label>
<select id="opslist" name="opslist" size="6" multiple>
<option value="python">Python語言</option>
<option value="go">Go語言</option>
<option value="shell">Shell語言</option>
<option value="database">數據庫</option>
<option value="frontend">前端</option>
<option value="linux">Linux</option>
<option value="network">網絡</option>
<option value="storage">存儲</option>
</select>
<input type="submit">
</form>
</body>
</html>
上述前端代碼中,是使用multiple屬性來實現選擇多個值。
后端代碼的打印方式稍微做了些許調整:
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
data = request.get_data()
print data
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
按住ctrl或者shift鍵可進行多選
后端打印的效果圖下圖:
關于表單基礎知識點的講解就這么愉快的結束了,關于更多表單的元素、輸入屬性、輸入類型可自行查閱和實戰,筆者因時間有限就不一一演示。感謝您的閱讀,望多多關注我們、點贊、轉發!
本文轉載于(喜歡的盆友關注我們):https://mp.weixin.qq.com/s/L-Msv39JrF7AzRx4K1OLjA
景
最近小編接到一個獲取網站請求數據的需求,要求抓取網站某個頁面請求的數據。我使用Google Chrome瀏覽器查看了一下請求鏈接的傳入參數,發現需要傳入一個Token值才能獲取數據。于是我在Chrome中登錄后,通過Postman請求成功,并將Token存儲到了Cookie中。然而問題又來了,在代碼層面如何獲取這個Token呢?
解決方案
小編在網上查了一圈,發現Chrome瀏覽器的Cookie存儲在本地的sqlite數據庫中。在Chrome的安裝目錄中找到了sqlite的文件,通過讀取sqlite數據庫即可獲得了Cookie值。如下圖:
其中name是cookie的名稱, encrypted_value字段是加密的cookie值,host_key就是站點域名了。但是這個Cookie值是加密的。
存儲cookie目錄:
C:\Users\用戶名\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies
各個版本的目錄可能不同,有的在Local State文件夾下。
小編繼續搜索,發現Chrome瀏覽器是開源的,算法是公開的,用到的加密算法是aes加密算法。我們不需要知道這個算法,只需要知道此算法需要一個秘鑰,通過這個秘鑰就可以進行加密和解密,代碼如下。
實現代碼
關鍵代碼如下:
using (SqliteConnection connection = new SqliteConnection())
{
//1、獲取google Chrome瀏覽器目錄
var userprofilePath = Environment.GetEnvironmentVariable("USERPROFILE");
var sourceFile= $@"{userprofilePath}\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies";
//var sourceFile = $@"{userprofilePath}\Desktop\11111111.txt";
var targetFile = $@"{Directory.GetCurrentDirectory()}\GoogleFile\Cookies";
//2、拷貝文件到本地目錄,防止文件被占用
FileInfo file = new FileInfo(sourceFile);
if(!Directory.Exists($@"{Directory.GetCurrentDirectory()}\GoogleFile"))
Directory.CreateDirectory($@"{Directory.GetCurrentDirectory()}\GoogleFile");
if (file.Exists)
{
file.CopyTo(targetFile, true);
}
//3、鏈接sqlite數據庫
connection.ConnectionString = $@"DataSource="+ targetFile;
connection.Open();
//4、通過select查詢相關的語句
SqliteCommand command = new SqliteCommand("select host_key,name,encrypted_value from cookies where name='public-token' and host_key='.cponline.cnipa.gov.cn'", connection);
SqliteDataReader dataReader = command.ExecuteReader();
dataReader.Read();
byte[] encryptedValue = (byte[])dataReader["encrypted_value"];
//5、解密數據
int keyLength = 256 / 8;
int nonceLength = 96 / 8;
String kEncryptionVersionPrefix = "v10";
int GCM_TAG_LENGTH = 16;
//字符串內容取自C:\Users\用戶名\AppData\Local\Google\Chrome\User Data\Local State文件的encrypted_key
byte[] encryptedKeyBytes = Convert.FromBase64String("RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAACVAmSA/y7+TLs+3WWdDv1ZAAAAAAIAAAAAABBmAAAAAQAAIAAAABV7dDMB8p+vKnLEjnrhnWB4DAbB/k5XAtjWGFnci/3qAAAAAA6AAAAAAgAAIAAAAH/pnc+fF6dhG8Fpw6yQezIXtMw48xNvuyRub/cZ62XaMAAAAP1pl5QqRJmd1J4V++dhE63MEA9F4NzCHb1aOMgTnFCo1+xSHYovSTzCoYFvoDfIFUAAAAAZzDzWwwpUm6yZG9tpYu/ioRSO8V16MetQy2s7L9HHO03Q6bO8Nr05Erl1QbjCVoSgSOU4krcerUsngMwIYFyb");
encryptedKeyBytes = encryptedKeyBytes.Skip("DPAPI".Length).Take(encryptedKeyBytes.Length - "DPAPI".Length).ToArray();
var keyBytes = System.Security.Cryptography.ProtectedData.Unprotect(encryptedKeyBytes, null, System.Security.Cryptography.DataProtectionScope.CurrentUser);
var nonce = encryptedValue.Skip(kEncryptionVersionPrefix.Length).Take(nonceLength).ToArray();
encryptedValue = encryptedValue.Skip(kEncryptionVersionPrefix.Length + nonceLength).Take(encryptedValue.Length - (kEncryptionVersionPrefix.Length + nonceLength)).ToArray();
var str = System.Web.HttpUtility.UrlDecode(AesGcmDecrypt(keyBytes, nonce, encryptedValue));
//6、獲得值
Console.WriteLine($"{dataReader["host_key"]}-{dataReader["name"]}-{str}");
//7、關閉數據
connection.Close();
}
以上代碼列出了解密步驟,大家可以根據自己的項目情況調整。
AesGcmDecrypt解密的方法:
public static string AesGcmDecrypt(byte[] keyBytes, byte[] nonce, byte[] encryptedValue)
{
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
AeadParameters aeadParameters = new AeadParameters(
new KeyParameter(keyBytes),
128,
nonce);
gcmBlockCipher.Init(false, aeadParameters);
byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(encryptedValue.Length)];
int length = gcmBlockCipher.ProcessBytes(encryptedValue, 0, encryptedValue.Length, plaintext, 0);
gcmBlockCipher.DoFinal(plaintext, length);
return Encoding.UTF8.GetString(plaintext);
}
注:本案例.NET項目使用的是.NET6,Chrome瀏覽器版本是v110。
Chrome開源地址:chromium.googlesource.com/chromium/src
解密加密地址:
https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_win.cc?q=OSCrypt&dr=CSs
輸出str效果:
結語
本文介紹了用C#讀取Chrome瀏覽器cookie值的方法,并用代碼實現了功能,大家可以根據自己項目的情況使用。本案例涉及到隱私問題,建議不要用本案例做違規的操作。希望本文對你有所幫助,同時歡迎留言或吐槽。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。