關注「WeiyiGeek」公眾號
設為「特別關注」每天帶你玩轉網路安全運維、應用開發、物聯網IOT學習!
希望各位看友【關注、點贊、評論、收藏、投幣】,助力每一個夢想,

本章目錄
目錄- 0x00 Gitalk - 基于Github的評論系統
- 1.快速介紹
- 2.安裝部署
- 3.使用實踐
- n.入坑出坑
- 1.使用Gitalk進行Github的Oauth認證無法跨域獲取Token問題解決辦法
作者: WeiyiGeek [唯一極客]
首發地址: https://mp.weixin.qq.com/s/2LLVDf7Fj4cX3IRZUtUfnA
原文地址: https://blog.weiyigeek.top/2022/5-28-588.html
0x00 Gitalk - 基于Github的評論系統
描述: 我想對于所有使用hexo、Hugo或者WordPress自建博客的博主來說GitTalk應該不陌生,GitTalk通過Github的OpenAPI以及issues功能實作社區評論確實還是很方便的,除開對國內訪問速度較慢就沒啥毛病,但是考慮到新手朋友此處還是簡單介紹一下,
1.快速介紹
描述: Gitalk 是一個基于 Github Issue 和 Preact 的現代評論組件,
功能:
- 使用 github 帳號進行身份驗證
- 無服務器,所有評論將存盤為 github 問題
- 個人和組織的github專案都可以用來存盤評論
- 本地化,支持多國語言 [en, zh-CN, zh-TW, es-ES, fr, ru, de, pl, ko, fa, ja]
- 類似 Facebook 的無干擾模式(可以通過 DistentionFreeMode 選項啟用)
- 熱鍵提交評論(cmd|ctrl + enter)
專案地址:https://github.com/gitalk/gitalk
幫助檔案:https://github.com/gitalk/gitalk/blob/master/readme-cn.md
溫馨提示: 當前 Gitalk 最新版本為 1.7.2 (Mar 3, 2021), 如后續隨著時間推移,可能會有些許變化,建議參考官網(https://github.com/gitalk/gitalk/tags)
2.安裝部署
描述:安裝參考Gitalk評論系統的兩種方式,
安裝實踐
- 方式1.在你的HTML頁面中使用 link 與 script 標簽引入,
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<!-- or -->
<link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
- 方式2.使用 npm 安裝 gital 模塊,
# 模塊安裝
npm i --save gitalk
# 專案匯入
import 'gitalk/dist/gitalk.css'
import Gitalk from 'gitalk'
配置實踐
首先,您需要為商店評論選擇一個公共 github 存盤庫(已存在或創建一個新存盤庫),然后創建一個 GitHub 應用程式,如果你沒有,點擊這里 (https://github.com/settings/applications/new) 注冊一個新的,
Application name : BlogTalk
Homepage URL : https://blog.weiyigeek.top
Application description : 歡迎訪問 WeiyiGeek blog\'s [blog.weiyigeek.top] talk about , 歡迎留言騷擾喲,親!
Authorization callback URL : https://blog.weiyigeek.top

注意:您必須在授權回呼 URL 欄位中指定網站域 url,
然后,創建完成后你將獲取Client ID 與 Client Secret,如下所示:

注意:后續更新修改可以進行訪問 Settings/Developer settings ( https://github.com/settings/developers )
最后,創建一個公共倉庫此處我創建的是blogtalk,創建完后在專案的(https://github.com/WeiyiGeek/blogtalk/settings)中啟用 issue 即可

使用方式1.將如下代碼添加到您的頁面:
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
</head>
<body>
<div id="gitalk-container"></div>
<script>
const gitalk = new Gitalk({
clientID: 'GitHub Application Client ID', // 8d8e96********797026d3
clientSecret: 'GitHub Application Client Secret', // secrets**********secrets
repo: 'GitHub repo', // blogtalk
owner: 'GitHub repo owner', // WeiyiGeek
admin: ['GitHub repo owner and collaborators, only these guys can initialize github issues'], // ['WeiyiGeek']
id: location.pathname, // Ensure uniqueness and length less than 50
distractionFreeMode: false // Facebook-like distraction free mode
})
gitalk.render('gitalk-container')
</script>
</body>
使用方式2.在React中使用
import GitalkComponent from "gitalk/dist/gitalk-component";
// 并使用類似的組件
<GitalkComponent options={{
clientID: "...",
// ...
// options below
}} />
溫馨提示: Gitalk 物件實體化引數參考 (https://github.com/gitalk/gitalk#options)
3.使用實踐
在 Hexo 中使用
描述: 此處以我的博客[https://blog.weiyigeek.top] 為例進行演示配置,此處筆者使用的是 hexo + mellow 主題 , 已經經過二次魔改(有需要該博客主題請在公眾號回復【mellow博客主題】或者訪問 https://weiyigeek.top/wechat.html?key=mellow博客主題 ),
- Step 1.在 Hexo 主題中的 _config.yaml 配置加入如下配置片段,
# gittalk 評論系統
gitalk:
enable: true
owner: WeiyiGeek # github賬號
repo: blogtalk # 留言倉庫
proxy: /github/login/oauth/access_token # 反向代理解決跨域問題,后續將會主要講解哦,
oauth:
client_id: 8d8e965c******97026d3 # Github App Auth ID
client_secret: e9c6141cb1f02f721********d01cb4d7a8f069 # Github App Auth secret
perPage: 15
- Step 2.在主題檔案引擎中加入如下片段,
<div id="gitalk-container"></div>
<!-- 實際上是將 <script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script> js 下載到了本地 -->
<script type="text/javascript" src="https://www.cnblogs.com/WeiyiGeek/p/<%- url_for(theme_js('/js/plugins/gitalk.min', cache)) %>"></script>
<script type="text/javascript" src="https://www.cnblogs.com/WeiyiGeek/p/<%- url_for(theme_js('/js/custom/gitalk.init', cache)) %>"></script>
<!-- themes\mellow\source\js\custom\gitalk.init.js -->
var gitalk = new Gitalk({
clientID: '<%- theme.gitalk.oauth.client_id %>',
clientSecret: '<%- theme.gitalk.oauth.client_secret %>',
repo: '<%- theme.gitalk.repo %>',
owner: '<%- theme.gitalk.owner %>',
admin: ['<%- theme.gitalk.owner %>'],
id: location.pathname,
proxy: '<%- theme.gitalk.proxy %>',
distractionFreeMode: true
})
# hexo g 生成靜態檔案后的樣子
# var gitalk = new Gitalk({
# clientID: '8d8e965c******97026d3',
# clientSecret: 'e9c6141cb1f02f721********d01cb4d7a8f069',
# repo: 'blogtalk',
# owner: 'WeiyiGeek',
# admin: ['WeiyiGeek'],
# id: location.pathname,
# proxy: '/github/login/oauth/access_token',
# distractionFreeMode: false
# })
# 創建 gitalk-container
gitalk.render('gitalk-container')
溫馨提示: 建議將distractionFreeMode設定為false,因為True真心難看,
溫馨提示: 為了 Github Apps ID 與 Secrets 的安全,我們需要針對上面 new Gitalk 實體化引數進行js加密混淆 (http://www.esjson.com/jsEncrypt.html)
n.入坑出坑
1.使用Gitalk進行Github的Oauth認證無法跨域獲取Token問題解決辦法
描述: 在最開始之初我們也是使用官方演示代碼中,使用的第三方提供的CORS代理服務,他會默認放行所有CORS請求,但是隨著而來的問題是登陸會出現網路錯誤 Error: Network Error 或者在使用時出現 Forbidden 錯誤 (https://github.com/gitalk/gitalk/issues/514) ,
目前由于該CORS代理服務遭到濫用,因此做了限制,導致GitTalk失效,在實踐中發現如下CORS代理服務其要么有限制要么根本不能使用,所以實踐的朋友們就不要像使用如下CORS代理服務:
# 限流
https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token
# 被墻
https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token
溫馨提示: CORS Anywhere 是一個 NodeJS 代理,它將 CORS 標頭添加到代理請求中, 專案地址 (https://github.com/Rob--W/cors-anywhere)
在 百度 CSDN 中撿了一圈垃圾之后,還是沒有最好的解決方案,然后通過某種方式Google了一下,找到兩種替代的方式利用cloudflare worker (不幸得是默認的cf worker的域名workers.dev被墻了)或者 Vercel 搭建在線代理(無vps推薦使用Vercel) 或者 使用VPS中的nginx服務器來反代 https://github.com (比較推薦-當前博主正在使用),
方式1.沒有VPS或者自己的服務器(想白嫖的)
描述: 在 cloudflare (https://dash.cloudflare.com/login/) 上創建一個免費的在線代理來解決gitalk授權登錄跨域問題,利用CloudFlare Worker創建在線代理,不需要我們有服務器,也不需要搭建Node.js服務,只需要注冊一個CloudFlare賬號,創建一個Worker,部署一個JS腳本就可以了,簡單方便,下面我們就來看看如何創建吧,

創建好之后我們便可編輯其 Worker 服務代碼,如下代碼也可通過 https://github.com/WeiyiGeek/SecOpsDev/tree/master/Application/Blog/Hexo/Gitalk 獲得,
const exclude = []; // Regexp for blacklisted urls
const include = [/^https?:\/\/.*weiyigeek\.top$/, /^https?:\/\/localhost/]; // Regexp for whitelisted origins e.g.
const apiKeys = {
EZWTLwVEqFnaycMzdhBz: {
name: 'Test App',
expired: false,
expiresAt: new Date('2023-01-01'),
exclude: [], // Regexp for blacklisted urls
include: ["^http.?://www.weiyigeek.top$", "weiyigeek.top$", "^https?://localhost/"], // Regexp for whitelisted origins
},
};
// Config is all above this line.
// It should not be necessary to change anything below.
function verifyCredentials(request) {
// Throws exception on verification failure.
const requestApiKey = request.headers.get('x-cors-proxy-api-key');
if (!Object.keys(apiKeys).includes(requestApiKey)) {
throw new UnauthorizedException('Invalid authorization key.');
}
if (apiKeys[requestApiKey].expired) {
throw new UnauthorizedException('Expired authorization key.');
}
if (apiKeys[requestApiKey].expiresAt && apiKeys[requestApiKey].expiresAt.getTime() < Date.now()) {
throw new UnauthorizedException(`Expired authorization key.\nKey was valid until: ${apiKeys[requestApiKey].expiresAt}`);
}
return apiKeys[requestApiKey];
}
function checkRequiredHeadersPresent(request) {
// Throws exception on verification failure.
if (!request.headers.get('Origin') && !request.headers.get('x-requested-with')) {
throw new BadRequestException('Missing required request header. Must specify one of: origin,x-requested-with');
}
}
function UnauthorizedException(reason) {
this.status = 401;
this.statusText = 'Unauthorized';
this.reason = reason;
}
function BadRequestException(reason) {
this.status = 400;
this.statusText = 'Bad Request';
this.reason = reason;
}
function isListed(uri, listing) {
let returnValue = https://www.cnblogs.com/WeiyiGeek/p/false;
console.log(uri);
if (typeof uri ==='string') {
for (const m of listing) {
if (uri.match(m) !== null) {
returnValue = https://www.cnblogs.com/WeiyiGeek/p/true;
}
}
} else { // Decide what to do when Origin is null
returnValue = true; // True accepts null origins false rejects them.
}
return returnValue;
}
function fix(myHeaders, request, isOPTIONS) {
myHeaders.set('Access-Control-Allow-Origin', request.headers.get('Origin'));
if (isOPTIONS) {
myHeaders.set('Access-Control-Allow-Methods', request.headers.get('access-control-request-method'));
const acrh = request.headers.get('access-control-request-headers');
if (acrh) {
myHeaders.set('Access-Control-Allow-Headers', acrh);
}
myHeaders.delete('X-Content-Type-Options');
}
return myHeaders;
}
function parseURL(requestUrl) {
const match = requestUrl.match(/^(?:(https?:)?\/\/)?(([^/?]+?)(?::(\d{0,5})(?=[/?]|$))?)([/?][\S\s]*|$)/i);
// ^^^^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^
// 1:protocol 3:hostname 4:port 5:path + query string
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 2:host
if (!match) {
console.log('no match');
throw new BadRequestException('Invalid URL for proxy request.');
}
console.log('parseURL:match:', match);
if (!match[1]) {
console.log('nothing in match group 1');
if (/^https?:/i.test(requestUrl)) {
console.log('The pattern at top could mistakenly parse "http:///" as host="http:" and path=///.');
throw new BadRequestException('Invalid URL for proxy request.');
}
// Scheme is omitted.
if (requestUrl.lastIndexOf('//', 0) === -1) {
console.log('"//" is omitted');
requestUrl = '//' + requestUrl;
}
requestUrl = (match[4] === '443' ? 'https:' : 'http:') + requestUrl;
}
const parsed = new URL(requestUrl);
if (!parsed.hostname) {
console.log('"http://:1/" and "http:/notenoughslashes" could end up here.');
throw new BadRequestException('Invalid URL for proxy request.');
}
return parsed;
}
async function proxyRequest(request, activeApiKey) {
const isOPTIONS = (request.method === 'OPTIONS');
const originUrl = new URL(request.url);
const origin = request.headers.get('Origin');
// ParseURL throws when the url is invalid
const fetchUrl = parseURL(request.url.replace(originUrl.origin, '').slice(1));
// Throws if it fails the check
checkRequiredHeadersPresent(request);
// Excluding urls which are not allowed as destination urls
// Exclude origins which are not int he included ones
if (isListed(fetchUrl.toString(), [...exclude, ...(activeApiKey?.exclude || [])]) || !isListed(origin, [...include, ...(activeApiKey?.include || [])])) {
throw new BadRequestException('Origin or Destination URL is not allowed.');
}
let corsHeaders = request.headers.get('x-cors-headers');
if (corsHeaders !== null) {
try {
corsHeaders = JSON.parse(corsHeaders);
} catch {}
}
if (!originUrl.pathname.startsWith('/')) {
throw new BadRequestException('Pathname does not start with "/"');
}
const recvHpaireaders = {};
for (const pair of request.headers.entries()) {
if ((pair[0].match('^origin') === null)
&& (pair[0].match('eferer') === null)
&& (pair[0].match('^cf-') === null)
&& (pair[0].match('^x-forw') === null)
&& (pair[0].match('^x-cors-headers') === null)
) {
recvHpaireaders[pair[0]] = pair[1];
}
}
if (corsHeaders !== null) {
for (const c of Object.entries(corsHeaders)) {
recvHpaireaders[c[0]] = c[1];
}
}
const newRequest = new Request(request, {
headers: recvHpaireaders,
});
const response = await fetch(fetchUrl, newRequest);
let myHeaders = new Headers(response.headers);
const newCorsHeaders = [];
const allh = {};
for (const pair of response.headers.entries()) {
newCorsHeaders.push(pair[0]);
allh[pair[0]] = pair[1];
}
newCorsHeaders.push('cors-received-headers');
myHeaders = fix(myHeaders, request, isOPTIONS);
myHeaders.set('Access-Control-Expose-Headers', newCorsHeaders.join(','));
myHeaders.set('cors-received-headers', JSON.stringify(allh));
const body = isOPTIONS ? null : await response.arrayBuffer();
return new Response(body, {
headers: myHeaders,
status: (isOPTIONS ? 200 : response.status),
statusText: (isOPTIONS ? 'OK' : response.statusText),
});
}
function homeRequest(request) {
const isOPTIONS = (request.method === 'OPTIONS');
const originUrl = new URL(request.url);
const origin = request.headers.get('Origin');
const remIp = request.headers.get('CF-Connecting-IP');
const corsHeaders = request.headers.get('x-cors-headers');
let myHeaders = new Headers();
myHeaders = fix(myHeaders, request, isOPTIONS);
let country = false;
let colo = false;
if (typeof request.cf !== 'undefined') {
country = typeof request.cf.country === 'undefined' ? false : request.cf.country;
colo = typeof request.cf.colo === 'undefined' ? false : request.cf.colo;
}
return new Response(
'CLOUDFLARE-CORS-ANYWHERE\n\n'
+ 'Source:\nhttps://github.com/chrisspiegl/cloudflare-cors-anywhere\n\n'
+ 'Usage:\n'
+ originUrl.origin + '/{uri}\n'
+ 'Header x-cors-proxy-api-key must be set with valid api key\n'
+ 'Header origin or x-requested-with must be set\n\n'
// + 'Limits: 100,000 requests/day\n'
// + ' 1,000 requests/10 minutes\n\n'
+ (origin === null ? '' : 'Origin: ' + origin + '\n')
+ 'Ip: ' + remIp + '\n'
+ (country ? 'Country: ' + country + '\n' : '')
+ (colo ? 'Datacenter: ' + colo + '\n' : '') + '\n'
+ ((corsHeaders === null) ? '' : '\nx-cors-headers: ' + JSON.stringify(corsHeaders)),
{status: 200, headers: myHeaders},
);
}
async function handleRequest(request) {
const {protocol, pathname} = new URL(request.url);
// In the case of a "Basic" authentication, the exchange MUST happen over an HTTPS (TLS) connection to be secure.
if (protocol !== 'https:' || request.headers.get('x-forwarded-proto') !== 'https') {
throw new BadRequestException('Must use a HTTPS connection.');
}
switch (pathname) {
case '/favicon.ico':
case '/robots.txt':
return new Response(null, {status: 204});
case '/':
return homeRequest(request);
default: {
// Not 100% sure if this is a good idea…
// Right now all OPTIONS requests are just simply replied to because otherwise they fail.
// This is necessary because apparently, OPTIONS requests do not carry the `x-cors-proxy-api-key` header so this can not be authorized.
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: fix(new Headers(), request, true),
status: 200,
statusText: 'OK',
});
}
// The "x-cors-proxy-api-key" header is sent when authenticated.
//if (request.headers.has('x-cors-proxy-api-key')) {
// Throws exception when authorization fails.
//const activeApiKey = verifyCredentials(request);
// Only returns this response when no exception is thrown.
return proxyRequest(request);
//}
// Not authenticated.
//throw new UnauthorizedException('Valid x-cors-proxy-api-key header has to be provided.');
}
}
}
addEventListener('fetch', async event => {
event.respondWith(
handleRequest(event.request).catch(error => {
const message = error.reason || error.stack || 'Unknown Error';
return new Response(message, {
status: error.status || 500,
statusText: error.statusText || null,
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
// Disables caching by default.
'Cache-Control': 'no-store',
// Returns the "Content-Length" header for HTTP HEAD requests.
'Content-Length': message.length,
},
});
}),
);
});
部署結果: https://cors-anywhere.weiyigeek.workers.dev/

溫馨提示: cloudflare 構建無服務器應用程式免費版本每天限額10萬次請求,所有為了避免其它 people 惡意使用,請在使用時設定訪問白名單, 上述原始碼來源于 (https://github.com/chrisspiegl/cloudflare-cors-anywhere),
溫馨提示: 除了使用 cloudflare 還可以使用 Vercel 免費部署node.js專案解決跨域問題,你可參考該專案 (https://github.com/Dedicatus546/cors-server) ,此處就不在累述,
方式2.有公網VPS、服務器
描述: 由于我自己有VPS所以就不借用 cloudflare 與 Vercel,因為其國內網路原因,時而通暢時而有緩慢 , 此處我將使用Nginx服務在blog.conf配置Nginx檔案中加入如下location指令片段
# https - www.weiyigeek.top
server {
listen 80;
listen 443 ssl http2;
server_name blog.weiyigeek.top;
# CORS
add_header Access-Control-Allow-Origin '*.weiyigeek.top';
add_header Access-Control-Allow-Methods 'GET,POST,OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
...
# Gitalk Auth Use
location /github {
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass https://github.com/; # 注意尾部斜杠不能少
}
...
error_page 403 /warn/403.html;
error_page 404 /warn/404.html;
error_page 500 502 503 /warn/500.html;
error_page 504 /warn/504.html;
}
配置完成后檢測blog.conf配置以及多載nginx服務 nginx -t && nginx -s reload, 然后修改Hexo 主題中的 _config.yaml 將 Gitalk 的 proxy 配置為 proxy: /github/login/oauth/access_token 即可,
# gittalk 評論系統
gitalk:
enable: true
owner: WeiyiGeek # github賬號
repo: blogtalk # 留言倉庫
proxy: /github/login/oauth/access_token # 關鍵點 (前臺請求是https://blog.weiyigeek.top/github/login/oauth/access_token, 而實際請求是https://github.com/login/oauth/access_token,所以解決了跨域問題)
oauth:
client_id: 8d8e965c******97026d3 # Github App Auth ID
client_secret: e9c6141cb1f02f721********d01cb4d7a8f069 # Github App Auth secret
perPage: 15
之后,我們需要批量初始每篇文章issue根據其路徑/2020/3-20-658.html,此處采用了gitalk-auto-init.js腳本進行批量初始化文章issue,
溫馨提示: 下述 gitalk-auto-init.js 腳本可以通過如下連接( https://github.com/WeiyiGeek/SecOpsDev/tree/master/Application/Blog/Hexo/Gitalk )進行獲取
腳本依賴:
$ npm i -S hexo-generator-sitemap
$ npm i -D md5 moment request xml-parser
+ [email protected]
+ [email protected]
+ [email protected]
+ [email protected]
added 55 packages from 70 contributors in 8.467s
配置運行:
// gitalk-auto-init.js 腳本部分片段
// 配置資訊
const config = {
username: 'weiyigeek', // GitHub repository 所有者,可以是個人或者組織,對應Gitalk配置中的owner
repo: "blogtalk", // 儲存評論issue的github倉庫名,僅需要倉庫名字即可,對應 Gitalk配置中的repo
token: 'ghp_wnpWqL********6RIf0NR5iD', // 前面在Github中的 personal access token
sitemap: path.join(__dirname, './public/sitemap.xml'), // 自己站點的 sitemap 檔案地址
cache: true, // 是否啟用快取,啟用快取會將已經初始化的資料寫入配置的 gitalkCacheFile 檔案,下一次直接通過快取檔案判斷
gitalkCacheFile: path.join(__dirname, './gitalk-init-cache.json'), // 用于保存 gitalk 已經初始化的 id 串列
gitalkErrorFile: path.join(__dirname, './gitalk-init-error.json'), // 用于保存 gitalk 初始化報錯的資料
};
// sitemap.xml 示例
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://blog.weiyigeek.top/2020/5-28-588.html</loc>
<lastmod>2022-08-15T14:39:08.638Z</lastmod>
<title>Ingress-Nginx進階學習實踐擴充配置記錄</title>
</url>
....
</urlset>

執行結果:
--------- 運行結果 ---------
報錯資料: 1 條,參考檔案 /mnt/e/githubProject/blog/gitalk-init-error.json,
本次成功: 27 條,
寫入快取: 90 條,已初始化 63 條,本次成功: 27 條,參考檔案 /mnt/e/githubProject/blog/gitalk-init-cache.json,
我們也可以通過 blogtalk 專案中 issue (https://github.com/WeiyiGeek/blogtalk/issues) 查看初始化結果以及最新評論,

在初始化issue完成之后,我們可以找到一篇 https://blog.weiyigeek.top/about/ 文章進行留言驗證,

原文地址: https://blog.weiyigeek.top/2022/5-28-588.html
本文至此完畢,更多技術文章,盡情期待下一章節!
【WeiyiGeek Blog 個人博客 - 為了能到遠方,腳下的每一步都不能少 】
歡迎各位志同道合的朋友一起學習交流【點擊加入交流群】,如文章有誤請在下方留下您寶貴的經驗知識!
作者主頁: 【 https://weiyigeek.top】
博客地址: 【 https://blog.weiyigeek.top 】

專欄書寫不易,如果您覺得這個專欄還不錯的,請給這篇專欄 【點個贊、投個幣、收個藏、關個注,轉個發,留個言】(人間六大情),這將對我的肯定,謝謝!,
-
echo "【點個贊】,動動你那粗壯的拇指或者芊芊玉手,親!"
-
printf("%s", "【投個幣】,萬水千山總是情,投個硬幣行不行,親!")
-
fmt.Printf("【收個藏】,閱后即焚不吃灰,親!")
-
console.info("【轉個發】,讓更多的志同道合的朋友一起學習交流,親!")
-
System.out.println("【關個注】,后續瀏覽查看不迷路喲,親!")
-
cout << "【留個言】,文章寫得好不好、有沒有錯誤,一定要留言喲,親! " << endl;

更多網路安全、系統運維、應用開發、物聯網實踐、網路工程、全堆疊文章,盡在 https://blog.weiyigeek.top 之中,謝謝各位看又支持!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/502181.html
標籤:其他
上一篇:第二講 Python編程基礎
下一篇:Bert不完全手冊7. 為Bert注入知識的力量 Baidu-ERNIE & THU-ERNIE & KBert
