中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

asp.net 網(wǎng)站管理工具 安全營銷軟件

asp.net 網(wǎng)站管理工具 安全,營銷軟件,珠海建設(shè)網(wǎng)站官網(wǎng),公司做網(wǎng)站需要哪些費用由于安卓資源管理器展示的路徑不盡相同,各種軟件保存文件的位置也不一定一樣.對于普通用戶上傳文件時,查找文件可能是一個麻煩的事情.后來想到了一個辦法,使用pc端進行輔助上傳. 文章目錄 實現(xiàn)思路1.0 實現(xiàn)定義web與客戶端通信數(shù)據(jù)類型和數(shù)據(jù)格式web端websocket實現(xiàn)web端對客戶…

由于安卓資源管理器展示的路徑不盡相同,各種軟件保存文件的位置也不一定一樣.對于普通用戶上傳文件時,查找文件可能是一個麻煩的事情.后來想到了一個辦法,使用pc端進行輔助上傳.

文章目錄

  • 實現(xiàn)思路
  • 1.0 實現(xiàn)
    • 定義web與客戶端通信數(shù)據(jù)類型和數(shù)據(jù)格式
    • web端websocket實現(xiàn)
      • web端對客戶端數(shù)據(jù)的管理
    • pc端實現(xiàn)
    • OkHttp3建立websocket連接
  • 2.0版本
    • spring-boot放到nginx后面
    • spring-boot 放到gateway后面
    • spring-boot 放到nginx gateway后面
    • ws升級為wss
  • 其他
    • springboot打包

實現(xiàn)思路

  1. pc端與服務(wù)器建立websocket連接;
  2. 服務(wù)器將sessionId傳遞到pc端;
  3. pc端生成二維碼;
  4. 手機端掃描二維碼,讀取pc端sessionId;
  5. 手機端與服務(wù)器建立websocket連接;
  6. 手機端將fileId(后面再解釋)、pc端sessionId、token等參數(shù)傳遞給服務(wù)器;
  7. 服務(wù)器更新pc端session 對應(yīng)的fileId;
  8. 服務(wù)器將fileId、token等發(fā)送到pc端;
  9. pc使用token、fileId等請求文件列表并進行展示;
  10. 手機端、pc端進行文件修改后,向服務(wù)器發(fā)送給更新信號,服務(wù)器將更新信號轉(zhuǎn)發(fā)到對端。

1.0 實現(xiàn)

定義web與客戶端通信數(shù)據(jù)類型和數(shù)據(jù)格式

  1. 定義web與客戶端通信數(shù)據(jù)類型
public class MsgType {public static final int UPDATE = 0; //提示客戶端數(shù)據(jù)發(fā)生更新public static final int REQ = 1; //發(fā)送/接受fileId等字段public static final int SELF = 3; //建立連接后,web端發(fā)送client其sessionIdpublic static final int ERR_SESSION = 4; //提示session不存在或已close public static final int HEART_BEAT = 100; //心跳包
}
  1. 定義web與客戶端通信數(shù)據(jù)格式
@Data
public class MsgData {private int type;  //對應(yīng) MsgTypeprivate String sessionId; //SELF 對應(yīng)自身sessionId; REQ 對應(yīng)pc端sessionId;private String fileId; //建立連接后,向pc端發(fā)送fileId等字段

web端websocket實現(xiàn)

創(chuàng)建spring-boot項目,添加web\websocket相關(guān)依賴

使用maven引入websocket依賴;

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

配置websocket和訪問路徑的映射關(guān)系

@Configuration   //配置websocket和訪問路徑的映射關(guān)系
@EnableWebSocket // 全局開啟WebSocket支持
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new WebSocketServer(), "/websocket").setAllowedOrigins("*");}
}

web端對客戶端數(shù)據(jù)的管理

  1. 定義web管理session的數(shù)據(jù)結(jié)構(gòu)
@Data
public class SessionData {private int sessionType; // 1 master(app). 0 pcprivate String fileId; //pc會話IDprivate WebSocketSession session;private String sessionId;//雖然可以通過session.getId()獲取到sessionId,但session關(guān)閉后,讀取就會報錯

web端對session的管理邏輯

  1. 新創(chuàng)建的連接添加到鏈表上,web向客戶端發(fā)送SELF,告知其對應(yīng)的sessionId;

  2. 斷開連接時,如果是pc端session直接從鏈表中刪除,如果是app端session,將其他相同fileId的session全部關(guān)閉并從鏈表刪除;

  3. 接收到新消息后,根據(jù)消息類型進行分類處理:

    1. 心跳包,則直接返回;
    2. REQ app發(fā)送的fileId\pc端sessionId等字段,修改sessions上app連接和pc端SessionData內(nèi)的fileId字段;
      并將fileId等字段發(fā)送給pc端;
    3. UPDATE 給所有相同fileId的session發(fā)送更新信號;

注意: sessions遍歷\刪除\添加必須添加synchronized,否則ConcurrentModificationException

package com.example.im.ws;import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;/*** @ClassName WebSocketServer* @Description 處理websocket 連接* @Author guchuanhang* @date 2025/1/25 14:01* @Version 1.0**/@Slf4j
public class WebSocketServer extends TextWebSocketHandler {private final Object syncObject = new Object();private final List<SessionData> sessions =new ArrayList<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {log.info("New connection established: " + session.getId());SessionData sessionData = new SessionData(session);synchronized (syncObject) {sessions.add(sessionData);}MsgData msgData = new MsgData();msgData.setType(MsgType.SELF);msgData.setSessionId(session.getId());session.sendMessage(new TextMessage(new Gson().toJson(msgData)));}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message)throws Exception {String payload = message.getPayload();log.info("handleTextMessage: " + session.getId());log.info("Received message: " + payload);final MsgData msgData = new Gson().fromJson(payload, MsgData.class);//master 發(fā)來的需求.switch (msgData.getType()) {case MsgType.HEART_BEAT: {//heart beatbreak;}case MsgType.REQ: {//set master{SessionData sessionData = null;synchronized (syncObject) {final Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(session.getId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null != sessionData) {//set master.sessionData.setSessionType(ClientType.MASTER);sessionData.setFileId(msgData.getFileId());}}//set slave{SessionData sessionData = null;synchronized (syncObject) {final Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(msgData.getSessionId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null != sessionData) {sessionData.setSessionType(ClientType.SALVER);sessionData.setFileId(msgData.getFileId());MsgData msgData1 = new MsgData();msgData1.setType(MsgType.REQ);msgData1.setFileId(msgData.getFileId());sessionData.getSession().sendMessage(new TextMessage(new Gson().toJson(msgData1)));} else {//pc session error.MsgData msgData1 = new MsgData();msgData1.setType(MsgType.ERR_SESSION);session.sendMessage(new TextMessage(new Gson().toJson(msgData1)));}}break;}case MsgType.UPDATE: {//slfSessionData sessionData = null;synchronized (syncObject) {final Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(session.getId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null != sessionData) {final String fileId = sessionData.getFileId();List<SessionData> collect;synchronized (syncObject) {collect =sessions.stream().filter(s -> (null != s.getFileId() && s.getFileId().equals(fileId)) || (null == s.getSession() || !s.getSession().isOpen())).collect(Collectors.toList());}if (collect.isEmpty()) {return;}List<SessionData> errList = new ArrayList<>();for (SessionData s : collect) {if (null == s.getSession() || !s.getSession().isOpen()) {errList.add(s);continue;}//不需要給自己發(fā)送了if (s.getSessionId().equals(session.getId())) {continue;}MsgData msgData1 = new MsgData();msgData1.setType(MsgType.UPDATE);try {s.getSession().sendMessage(new TextMessage(new Gson().toJson(msgData1)));} catch (Exception e) {e.printStackTrace();errList.add(s);}}synchronized (syncObject) {sessions.removeAll(errList);}}break;}}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {log.info("Connection closed: " + session.getId() + ", Status: " + status);SessionData sessionData = null;synchronized (syncObject) {Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(session.getId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null == sessionData) {return;}final String fileId = sessionData.getFileId();//slave just ignore and delete.if (ClientType.SALVER == sessionData.getSessionType()) {sessions.remove(sessionData);return;}if (ClientType.MASTER == sessionData.getSessionType()) {List<SessionData> collect;synchronized (syncObject) {collect =sessions.stream().filter(s ->(null != s.getFileId()&& s.getFileId().equals(fileId)) ||(null == s.getSession() || !s.getSession().isOpen())).collect(Collectors.toList());}if (collect.isEmpty()) {return;}for (SessionData s : collect) {final WebSocketSession session1 = s.getSession();if (null == session1 || !session1.isOpen()) {continue;}session1.close();}synchronized (syncObject) {sessions.removeAll(collect);}}}
}

pc端實現(xiàn)

  1. 頁面創(chuàng)建時創(chuàng)建websocket,銷毀時關(guān)閉websocket
  2. 根據(jù)和服務(wù)器約定的消息格式 在websocket回調(diào)函數(shù)onmessage接受數(shù)據(jù)類型進行二維碼生成\文件列表查詢等操作
  3. 添加心跳機制,讓websocket更健壯

fileId是一個key,通過fileId可以查詢最新的數(shù)據(jù). pc端接受到刷新信號后,請求獲取最新數(shù)據(jù); pc端更新數(shù)據(jù)后,發(fā)送數(shù)據(jù)已更新信號.

<template><div v-if="fileId"><div>{{ fileId }}</div><el-button @click="updateData" type="primary">更新數(shù)據(jù)</el-button><div>發(fā)送給服務(wù)端更新信號時間: {{ sndUpdateSignalTime }}</div><div>收到服務(wù)端更新信號時間: {{ rcvUpdateSignalTime }}</div><div>心跳最新時間: {{ heartBeatSignalTime }}</div><div>服務(wù)器返回最新內(nèi)容: {{ serverContent }}</div></div><div v-else-if="sessionId"><div>sessionId:{{ sessionId }}</div><img width="200px" height="200px" :src="qrCode" alt="QR Code"/></div></template><script>import QRCode from "qrcode";export default {name: "HelloWorld",data() {return {wsuri: "ws://192.168.0.110:7890/websocket",ws: null,sessionId: '',qrCode: null,fileId: '',rcvUpdateSignalTime: '',sndUpdateSignalTime: '',heartBeatSignalTime: '',serverContent: '',heartbeatInterval: null,heartbeatIntervalTime: 3000, // 心跳間隔時間,單位為毫秒}},created() {//頁面打開時,初始化WebSocket連接this.initWebSocket()},beforeDestroy() {// 頁面銷毀時,關(guān)閉WebSocket連接this.stopHeartbeat()this.fileId = ''try {this.ws.close()} catch (e) {}this.ws = null;this.sessionId = ''},methods: {// pc端更新附件數(shù)據(jù)后,向服務(wù)器端發(fā)送更新信號updateData() {console.error('snd update signal')this.ws.send(JSON.stringify({type: 0}))//格式化為  yyyy-MM-dd HH:mm:ssthis.sndUpdateSignalTime = new Date().toLocaleTimeString()this.resetHeartbeat();},async generateQRCode() {try {this.qrCode = await QRCode.toDataURL(this.sessionId);} catch (error) {console.error('生成二維碼時出錯:', error);}},// 周期性發(fā)送心跳包startHeartbeat() {this.heartbeatInterval = setInterval(() => {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify({type: 100}))this.heartBeatSignalTime = new Date().toLocaleTimeString()console.error('snd heartbeat signal')} else {this.stopHeartbeat();}}, this.heartbeatIntervalTime);},//在發(fā)送或接受數(shù)據(jù)后,重置下一次發(fā)送心跳包的時間resetHeartbeat() {clearInterval(this.heartbeatInterval);this.startHeartbeat();},// 停止發(fā)送心跳包stopHeartbeat() {clearInterval(this.heartbeatInterval);},initWebSocket() {let that = this;this.ws = new WebSocket(this.wsuri);this.ws.onopen = () => {this.startHeartbeat();};// 接收后端消息this.ws.onmessage = function (event) {console.error('RECV:' + event.data)that.serverContent = event.data;let parse = JSON.parse(event.data);that.resetHeartbeat();switch (parse.type) {case 0: {console.error('update')that.rcvUpdateSignalTime = new Date().toLocaleTimeString()//TODO. 請求最新數(shù)據(jù)break;}case 1: {   //fileId list. 接受數(shù)據(jù),進行路徑跳轉(zhuǎn)console.error('REQ:' + event.data)that.fileId = parse.fileId;//記錄并請求最新數(shù)據(jù)break;}case 3: {that.sessionId = parse.sessionId;that.generateQRCode();break;}}};// 關(guān)閉連接時調(diào)用this.ws.onclose = function (event) {alert('連接已關(guān)閉');that.stopHeartbeat()// 強制刷新頁面(created 會調(diào)用)location.reload(true)};}}
}
</script><style scoped></style>

OkHttp3建立websocket連接

  1. 使用okhttp3建立websocket連接,監(jiān)聽onMessage根據(jù)消息類型進行不同的處理;
  2. 使用handler 管理心跳包

掃碼后, 如果已經(jīng)建立連接了

package com.example.im.ws;import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.example.im.R;
import com.google.gson.Gson;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;public class HelloActivity extends AppCompatActivity {public static final int MSG_HEART = 0x123;public static final int MSG_INTERVAL = 3000;private WebSocket webSocket;public static final String URL = "ws://192.168.0.110:7890/websocket";private TextView msgView;private List<String> sessionIds = new ArrayList<>();Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (MSG_HEART == msg.what) {MsgData msgData = new MsgData();msgData.setType(MsgType.HEART_BEAT);webSocket.send(new Gson().toJson(msgData));msgView.append(getNowDate() + ":發(fā)送消息 heart beat\n");mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);}}};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);msgView = findViewById(R.id.tv_msg);findViewById(R.id.btn_scan).setOnClickListener(v -> {scanQRCode();});findViewById(R.id.btn_update).setOnClickListener(v -> {MsgData msgData = new MsgData();msgData.setType(MsgType.UPDATE);webSocket.send(new Gson().toJson(msgData));});}@Overrideprotected void onDestroy() {mHandler.removeCallbacksAndMessages(null);super.onDestroy();}private void scanQRCode() {IntentIntegrator integrator = new IntentIntegrator(this);integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE);integrator.setPrompt("提示");integrator.setCameraId(0);  // 使用后置攝像頭integrator.setBeepEnabled(false);integrator.setBarcodeImageEnabled(true);integrator.initiateScan();}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);if (result != null && !TextUtils.isEmpty(result.getContents())) {String sessionId = result.getContents();if (sessionIds.contains(sessionId)) {return;}sessionIds.add(sessionId);//startif (null == webSocket) {OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(URL).build();webSocket = client.newWebSocket(request, new MyWebSocketListener());} else {//這樣可以實現(xiàn)掃多個pc端. 同時與多個pc端通信MsgData msgData = new MsgData();msgData.setSessionId(sessionId);msgData.setType(MsgType.REQ);msgData.setFileId("123");webSocket.send(new Gson().toJson(msgData));}}super.onActivityResult(requestCode, resultCode, data);}private String getNowDate() {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.getDefault());return simpleDateFormat.format(new java.util.Date());}private class MyWebSocketListener extends WebSocketListener {@Overridepublic void onOpen(WebSocket webSocket, okhttp3.Response response) {// 連接成功msgView.append(getNowDate() + ":連接成功\n");MsgData msgData = new MsgData();msgData.setSessionId(sessionIds.get(sessionIds.size() - 1));msgData.setType(MsgType.REQ);msgData.setFileId("123");webSocket.send(new Gson().toJson(msgData));mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);}@Overridepublic void onMessage(WebSocket webSocket, String text) {msgView.append(getNowDate() + ":接受消息" + text + "\n");mHandler.removeMessages(MSG_HEART);mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, okhttp3.Response response) {// 連接失敗msgView.append(getNowDate() + ":失敗" + t.getMessage() + "\n");}}
}

2.0版本

上面的實現(xiàn)確實簡單.下面結(jié)合實際的系統(tǒng)架構(gòu)進行適配一下.

spring-boot放到nginx后面

nginx常用來進行負載均衡\防火墻\反向代理等等,這種情況比較常見.

	map $http_upgrade $connection_upgrade { default upgrade; '' close; } server {listen       7777;server_name  localhost;location / {proxy_pass http://127.0.0.1:7890;proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; }}
}

設(shè)置Upgrade\Connection請求頭,將訪問地址修改為nginx的地址,即可實現(xiàn)nginx代理到spring-boot.

spring-boot 放到gateway后面

也就是所謂的spring-cloud 微服務(wù)架構(gòu).

gateway添加ws協(xié)議的路由

        # IM- id: imuri: ws://localhost:7890predicates:- Path=/im/**filters:- StripPrefix=1

訪問gateway代理之后的地址,即可實現(xiàn)nginx代理到spring-boot.

spring-boot 放到nginx gateway后面

將前面兩者進行結(jié)合, nginx保證可以代理到gateway, gateway再路由到spring-boot.

ws升級為wss

網(wǎng)上的做法是, 給gateway\spring-boot都配置證書.

簡單才能高效,既然gateway有防火墻驗證證書等功能,應(yīng)用不需要管理才對. nginx要屏蔽這種差異.

配置nginx 直接將wss的請求重寫為ws.

nginx重寫協(xié)議

map $http_upgrade $connection_upgrade { 
default upgrade; 
'' close; 
} 
server
{listen 443 ssl http2;server_name #SSL-START SSL相關(guān)配置,請勿刪除或修改下一行帶注釋的404規(guī)則ssl on;ssl_certificate   ssl_certificate_key add_header Strict-Transport-Security "max-age=31536000";error_page 497  https://$host$request_uri;location /im/ { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_pass http://127.0.0.1:18080/im/;rewrite ^(.*)wss://(.*)$ $1ws://$2 permanent;}
}

這樣 wss://域名/im/websocket就可以進行訪問了.

其他

源碼下載地址: https://gitee.com/guchuanhang/imapplication.git

springboot打包

  1. 注釋掉skip
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.example.im.ImApplication</mainClass><!-- 注釋掉,否則不能打包-->
<!--                    <skip>true</skip>--></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin>
  1. springboot日志
    spring-boot 默認支持logback

@Slf4j
public class WebSocketServer extends TextWebSocketHandler {log.info("New connection established: " + session.getId());
  1. bootstrap.yml

bootstrap.yml 是 spring-cloud 配置文件.
application.yml applicaition.properties 是 spring-boot 的配置文件.

  1. wss測試工具 wscat
npm install -g wscat  # 安裝方式wscat -c wss://www.baidu.com/im/websocket   

圖片

http://www.risenshineclean.com/news/28680.html

相關(guān)文章:

  • 安徽網(wǎng)站開發(fā)費用做谷歌推廣比較好的公司
  • wordpress建的網(wǎng)站打開太慢優(yōu)化大師win10能用嗎
  • 域名怎么和網(wǎng)站綁定深圳網(wǎng)站快速排名優(yōu)化
  • 動態(tài)網(wǎng)站開發(fā)在線測試第5章策劃方案
  • 城建設(shè)投資公司網(wǎng)站最近國內(nèi)新聞
  • 導(dǎo)航網(wǎng)站 php煙臺網(wǎng)絡(luò)推廣
  • 做跨境網(wǎng)站注意事項小白如何學(xué)電商運營
  • 武漢網(wǎng)站設(shè)計制作公司哪家好搜索引擎優(yōu)化時營銷關(guān)鍵詞
  • 神碼ai智能寫作網(wǎng)站百度怎么發(fā)帖做推廣
  • 什么樣的網(wǎng)站需要icp經(jīng)營性備案產(chǎn)品市場營銷策劃書
  • 自己做網(wǎng)站需要買什么手機怎么建自己的網(wǎng)站
  • 企業(yè)網(wǎng)站建設(shè)定制南寧求介紹seo軟件
  • 做網(wǎng)站關(guān)鍵詞網(wǎng)絡(luò)營銷的效果是什么
  • 做網(wǎng)站的注意什么國內(nèi)最新消息新聞
  • 蚌埠哪里做網(wǎng)站站長權(quán)重
  • 南寧中小企業(yè)網(wǎng)站制作許昌seo公司
  • 管理網(wǎng)站開發(fā)教程semseo是什么意思
  • 百度網(wǎng)頁版在線使用網(wǎng)站整站優(yōu)化公司
  • 淘寶客怎么建網(wǎng)站網(wǎng)站后臺管理系統(tǒng)
  • 廣西城鄉(xiāng)和住房建設(shè)廳網(wǎng)站黃頁推廣
  • 網(wǎng)站主機和空間360搜索引擎地址
  • 洛陽網(wǎng)站建設(shè)首選洛陽銘信科技佛山seo按效果付費
  • 用帝國cms做網(wǎng)站2022年新聞?wù)畻l
  • 加工鋼球網(wǎng)架公司西安seo教程
  • wordpress站點logo設(shè)置河北疫情最新情況
  • 建設(shè)門戶網(wǎng)站的申請網(wǎng)站推廣是做什么的
  • 直播教育網(wǎng)站建設(shè)注冊網(wǎng)站平臺要多少錢
  • 什么網(wǎng)站可以做投票愛站查詢工具
  • 付費推廣網(wǎng)站網(wǎng)絡(luò)營銷論文題目
  • 建設(shè)銀行曲江支行網(wǎng)站優(yōu)化分析