当前位置: 首页 > news >正文

西安网站制作资源软件开发前景2021

西安网站制作资源,软件开发前景2021,golang建设网站,自己搭建服务器访问国外网站自定义基座实时采集uniapp日志 打测试包给远端现场(测试/客户)实际测试时也能实时看到日志了#xff0c;也有代码行数显示。 流程设计 #mermaid-svg-1I5W9r1DU4xUsaTF {font-family:trebuchet ms,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid…自定义基座实时采集uniapp日志 打测试包给远端现场(测试/客户)实际测试时也能实时看到日志了也有代码行数显示。 流程设计 #mermaid-svg-1I5W9r1DU4xUsaTF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF .error-icon{fill:#552222;}#mermaid-svg-1I5W9r1DU4xUsaTF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1I5W9r1DU4xUsaTF .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-1I5W9r1DU4xUsaTF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1I5W9r1DU4xUsaTF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1I5W9r1DU4xUsaTF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1I5W9r1DU4xUsaTF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1I5W9r1DU4xUsaTF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1I5W9r1DU4xUsaTF .marker.cross{stroke:#333333;}#mermaid-svg-1I5W9r1DU4xUsaTF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1I5W9r1DU4xUsaTF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF .cluster-label text{fill:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF .cluster-label span{color:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF .label text,#mermaid-svg-1I5W9r1DU4xUsaTF span{fill:#333;color:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF .node rect,#mermaid-svg-1I5W9r1DU4xUsaTF .node circle,#mermaid-svg-1I5W9r1DU4xUsaTF .node ellipse,#mermaid-svg-1I5W9r1DU4xUsaTF .node polygon,#mermaid-svg-1I5W9r1DU4xUsaTF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1I5W9r1DU4xUsaTF .node .label{text-align:center;}#mermaid-svg-1I5W9r1DU4xUsaTF .node.clickable{cursor:pointer;}#mermaid-svg-1I5W9r1DU4xUsaTF .arrowheadPath{fill:#333333;}#mermaid-svg-1I5W9r1DU4xUsaTF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1I5W9r1DU4xUsaTF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1I5W9r1DU4xUsaTF .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-1I5W9r1DU4xUsaTF .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-1I5W9r1DU4xUsaTF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1I5W9r1DU4xUsaTF .cluster text{fill:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF .cluster span{color:#333;}#mermaid-svg-1I5W9r1DU4xUsaTF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1I5W9r1DU4xUsaTF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 重写console.log方法 Websocket Websocket uniapp收集日志 通过插件或Native.js传输 安卓基座接收 后台服务接收保存 前端html页面渲染 uniapp收集代码 重写console方法 通过条件编译在app使用环境重写日志打印方法 // #ifdef APP-PLUSfunction(...args){console.log function (...args) {try {_this.$plugins.getUtils(consoleLog, {level: log, args: args})} catch (e) {console.info(console.log 打印失败, e);}}console.error function (...args) {try {_this.$plugins.getUtils(consoleLog, {level: error, args: args})} catch (e) {console.info(console.error 打印失败, e);}}console.warn function (...args) {try {_this.$plugins.getUtils(consoleLog, {level: warn, args: args})} catch (e) {console.info(console.warn 打印失败, e);}}// #endif发送给安卓层 /*** 快捷调用安卓工具类方法* this.$plugins.getUtils(method,{userId:test})* param {Object} method* param {Object} jsonObject* param {Object} successCallback* param {Object} errorCallback* return {String} 原始字符串如果是json化返回的就是一个json字符串 不是对象!!!*/ getUtils: function(method, jsonObject, successCallback, errorCallback) {try {var success typeof successCallback ! function ? null : function(args) {successCallback(args);},fail typeof errorCallback ! function ? null : function(code) {errorCallback(code);};var callbackID plus.bridge.callbackId(success, fail);return plus.bridge.exec(_BARCODE, getUtils, [callbackID, method, jsonObject]);} catch (e) {console.error(e)errorCallback(e)}},//初始化方法一般是登录后调用 _this.$plugins.getUtils(initConsoleLog, {userId: _this.GLOBAL.$USER_INFO.user_iidd})安卓自定义基座收集日志 跳转方法 /*** 工具类获取** param pWebview* param array* return*/public void getUtils(IWebview pWebview, JSONArray array) {Log.i(getUtils, 工具类获取 array.toString());String result null;String CallBackID array.optString(0);try {//方法String method array.optString(1);JSONObject json new JSONObject(array.optString(2));result this.utilMethood(method, json, pWebview);} catch (Exception e) {e.printStackTrace();JSUtil.execCallback(pWebview, CallBackID, e.getMessage(), JSUtil.ERROR, false);}Log.i(getUtils, 工具类返回信息:\n result);JSUtil.execCallback(pWebview, CallBackID, result, JSUtil.OK, true);}初始化日志信息方法 /*** WebSocket调试信息推送客户端*/ private PushConsoleWebSocketClient pushConsoleWebSocketClient null;/*** 初始化推送*/ public static boolean pushLogInit false;/*** 调试日志地址*/ public static String LOG_WS_URL ws://127.0.0.1:5080/weblog/uniapplogv2/;/*** 调试id*/ public static String LOG_WS_USERID null;/*** 初始化日志信息** param params* param pWebview* return*/ private String initConsoleLog(JSONObject params, IWebview pWebview) {LOG_WS_USERID params.optString(userId);Log.i(TAG, uniapp层初始化日志信息: LOG_WS_USERID);if (null ! LOG_WS_USERID !.equals(LOG_WS_USERID)) {try {new Thread(new Runnable() {Overridepublic void run() {try {pushConsoleWebSocketClient PushConsoleWebSocketClient.builder(LOG_WS_URL, 系统名称, LOG_WS_USERID);pushConsoleWebSocketClient.connect();pushLogInit true;} catch (Exception e) {e.printStackTrace();}}}).start();} catch (Exception e) {Log.e(TAG, initConsoleLog: 初始化调试信息推送服务异常, e);}}return ResultUtil.ok(日志初始化完毕); }推送日志调试信息方法 /*** 推送日志信息到调试页面** param log 日志内容* param level 日志等级*/ private void pushLogToCache(String level, JSONArray log) {Thread thread new Thread(new Runnable() {Overridepublic void run() {try {com.alibaba.fastjson.JSONObject params new com.alibaba.fastjson.JSONObject();params.put(code, push);params.put(sys, pushConsoleWebSocketClient.getSys());params.put(userId, pushConsoleWebSocketClient.getUserId());params.put(level, level);params.put(timestamp, System.currentTimeMillis());params.put(time, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss.SSS).format(new Date()));try {params.put(log, parseUniappConsoleLog(log));} catch (Exception e) {params.put(log, log);}pushConsoleWebSocketClient.send(params.toString());} catch (Exception e) {e.printStackTrace();}}});thread.start(); // executorService.submit(thread); } 安装自定义基座 安卓WebSocket客户端 安卓WebSocket客户端推送负责将调试日志推送给后端 gradle依赖 //WebSocket连接 implementation org.java-websocket:Java-WebSocket:1.5.3import android.util.Log;import com.inspur.mobilefsp.plugins.WfmPlugin;import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import org.json.JSONObject;import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.Charset;/*** WebSocket客户端类用于与服务器建立WebSocket连接并处理消息。* 该类实现了WebSocketClient接口并提供了连接、消息处理和错误处理的功能。** author 淡梦如烟* date 20250211*/ public class PushConsoleWebSocketClient extends WebSocketClient {/*** 日志标签用于标识日志输出的来源*/public final static String TAG PushLogClient;/*** WebSocket服务器的URL*/private String url;/*** 系统名称*/private String sys;/*** 用户ID*/private String userId;/*** 构造函数初始化WebSocket客户端** param serverUrl WebSocket服务器的URL* param sys 系统名称* param userId 用户ID* throws URISyntaxException 如果提供的URL格式不正确*/public PushConsoleWebSocketClient(String serverUrl, String urlParams, String sys, String userId) throws URISyntaxException {super(new URI(serverUrl urlParams));this.url serverUrl;this.sys sys;this.userId userId;}/*** 建造者生成客户端** param serverUrl* param sys* param userId* return*/public static PushConsoleWebSocketClient builder(String serverUrl, String sys, String userId) {try {//自定义参数自行实现JSONObject json new JSONObject();json.put(code, pushStart);json.put(userId, userId);json.put(sys, sys);JSONObject password new JSONObject();password.put(userId, userId);password.put(timestamp, System.currentTimeMillis());//aes加密 自行实现或者用第三方包String encode QEncodeUtil.aesEncrypt(json.toString(), aes秘钥2);encode URLEncoder.encode(encode, UTF-8);//百分号不能作为参数encode encode.replaceAll(%, BaiFenHao);String url serverUrl encode;Log.e(TAG, builder: websocket地址: url);PushConsoleWebSocketClient pushConsoleWebSocketClient new PushConsoleWebSocketClient(serverUrl, encode, sys, userId);return pushConsoleWebSocketClient;} catch (Exception e) {throw new RuntimeException(e);}}/*** 获取WebSocket服务器的URL** return WebSocket服务器的URL*/public String getUrl() {return url;}/*** 获取系统名称** return 系统名称*/public String getSys() {return sys;}/*** 获取用户ID** return 用户ID*/public String getUserId() {return userId;}/*** 当WebSocket连接成功建立时调用** param handshake 握手信息*/Overridepublic void onOpen(ServerHandshake handshake) {// WebSocket连接已成功建立// 在此执行任何必要的操作Log.i(TAG, onOpen: handshake.getHttpStatus());WfmPlugin.pushLogInit true;}/*** 当接收到来自服务器的消息时调用** param message 收到的消息内容*/Overridepublic void onMessage(String message) {// 处理来自服务器的传入消息Log.i(TAG, onMessage: message);}/*** 当WebSocket连接关闭时调用** param code 关闭状态码* param reason 关闭原因* param remote 是否由远程服务器关闭*/Overridepublic void onClose(int code, String reason, boolean remote) {Log.e(TAG, onClose: code[ code ];remote[ remote ];url[ this.url ];reason: reason);// WebSocket连接已关闭// 在此执行任何必要的清理操作 // this.reconnectAfterMillis(100L);}/*** 重连锁*/private static boolean reConnectLock false;/*** 延迟重连** param millis*/public void reconnectAfterMillis(Long millis) {try {if (reConnectLock) {return;}reConnectLock true;new Thread(new Runnable() {Overridepublic void run() {try {// 尝试在5秒后重新连接Thread.sleep(millis);reconnect();} catch (Exception e) {e.printStackTrace();} finally {reConnectLock false;}}}).start();} catch (Exception e) {e.printStackTrace();reConnectLock false;} finally {}}/*** 当WebSocket连接期间发生错误时调用** param ex 发生的异常*/Overridepublic void onError(Exception ex) {Log.e(TAG, onError: , ex);// 处理WebSocket连接期间发生的任何错误 // this.reconnectAfterMillis(5000L);} } 后台代码 springboot接受日志和推送日志 package com.faker.weblog.websocket;import cn.hutool.core.net.URLDecoder; import com.alibaba.fastjson2.JSONObject; import com.faker.weblog.model.dto.PushUniappLogDto; import com.faker.weblog.util.Toolkit; import com.faker.weblog.wrapper.WrapMapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiModelProperty; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component;import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap;Lazy Component Slf4j ServerEndpoint(/uniapplogv2/{id}) Api(value websocket日志接受和推送uniapp日志工具) public class UniappLogWebHandleV2 {ApiModelProperty(value 客户端id)private String id;ApiModelProperty(value 是否初始化, example true)private boolean initialized false;ApiModelProperty(value 是否接受日志消息, example true)private boolean isPullLogs false;ApiModelProperty(value 系统名称, example fakerSys)private String sys;ApiModelProperty(value 用户id, example test)private String userId;/*** 日志列表*/private static ConcurrentHashMapString, ConcurrentHashMapString, ListString logListMap new ConcurrentHashMap();/*** 获取日志列表** return*/public ConcurrentHashMapString, ConcurrentHashMapString, ListString getlogListMap() {return logListMap;}/*** 清理日志列表*/public static void cleanLogListMap() {logListMap.clear();}/*** concurrent包的线程安全Map用来存放每个客户端对应的MyWebSocket对象。*/private static ConcurrentHashMapString, UniappLogWebHandleV2 webSocketMap new ConcurrentHashMapString, UniappLogWebHandleV2();/*** websocket的session*/private Session session;/*** 获取session** return*/public Session getSession() {return this.session;}/*** 新的WebSocket请求开启*/OnOpenpublic void onOpen(Session session, PathParam(id) String id) {log.info(新的WebSocket请求开启: id);try {String decode id.replaceAll(BaiFenHao, %);decode URLDecoder.decode(decode, Charset.forName(UTF-8));String aesJson com.faker.dba.util.QEncodeUtil.aesDecrypt(decode, aes秘钥2);JSONObject jsonObject JSONObject.parseObject(aesJson);String userId jsonObject.getString(userId);String password jsonObject.getString(password);String sign jsonObject.getString(sign);if (jsonObject.get(isPullLogs) ! null) {this.isPullLogs jsonObject.getBoolean(isPullLogs);}this.sys jsonObject.getString(sys);this.userId userId;this.session session;//鉴权方法自行实现this.validate(userId, sign, password);this.id id;webSocketMap.put(id, this);String code jsonObject.getString(code);if (pushStart.equalsIgnoreCase(code)) {//app推送方法if (thisLististMap null) {thisLististMap new ConcurrentHashMap();logListMap.put(this.sys, thisLististMap);}ListString logList thisLististMap.get(this.userId);if (logList null) {logList new ArrayList();thisLististMap.put(this.userId, logList);}} else if (webStart.equalsIgnoreCase(code)) {//pc端查看日志方法this.isPullLogs true;this.sys jsonObject.getString(watchSys);this.userId jsonObject.getString(watchUserId);ConcurrentHashMapString, ListString thisLististMap logListMap.get(this.sys);if (thisLististMap ! null) {ListString logList thisLististMap.get(this.userId);if (logList ! null) {for (String log : logList) {try {session.getBasicRemote().sendText(log);} catch (IOException e) {e.printStackTrace();}}}}}} catch (Exception e) {log.error(鉴权错误: id, e);}}/*** WebSocket 请求关闭*/OnClosepublic void onClose() {// 从set中删除log.info(WebSocket请求关闭: id);webSocketMap.remove(id);}/*** 发生异常*/OnErrorpublic void onErro(Throwable throwable) {throwable.printStackTrace();}/*** 收到客户端消息后调用的方法** param message 客户端发送过来的消息*/OnMessagepublic void onMessage(String message, Session session) throws IOException {log.debug(websocket来自客户端的消息:{}, message);JSONObject jsonObject JSONObject.parseObject(message);String code jsonObject.getString(code);if (this.initialized) {if (push.equalsIgnoreCase(code)) {PushUniappLogDto params JSONObject.parseObject(message, PushUniappLogDto.class);if (Toolkit.isNullOrEmpty(params.getSys())) {log.warn(系统名称不能为空);return;}if (Toolkit.isNullOrEmpty(params.getUserId())) {log.warn(用户id不能为空);return;}if (Toolkit.isNullOrEmpty(params.getLevel())) {log.warn(日志等级不能为空);return;}if (Toolkit.isNullOrEmpty(params.getLog()) || [].equals(params.getLog())) {log.warn(日志信息不能为空);return;}this.sendLogs(JSONObject.toJSONString(params));}} else {log.warn([ this.sys ][ this.userId ]未初始化 this.initialized);}}/*** token鉴权** param userId* param sign* param password* throws IOException*/public void validate(String userId, String sign, String password) throws IOException {if (Toolkit.isNotNull(userId) Toolkit.isNotNull(sign)) {//校验userId和密码 这里简化为校验userId和时间戳的aes加密信息校验通过初始化连接try {String aesJson com.faker.dba.util.QEncodeUtil.aesDecrypt(sign, aes秘钥1);JSONObject aesJsonObject JSONObject.parseObject(aesJson);if (aesJsonObject.get(userId) null || aesJsonObject.get(timestamp) null) {session.getBasicRemote().sendText(加密信息校验错误已记录! br aesJson br);session.close();}if (userId.equals(aesJsonObject.getString(userId))) {if (aesJsonObject.getLong(timestamp) System.currentTimeMillis() - 1000 * 60 * 5|| aesJsonObject.getLong(timestamp) System.currentTimeMillis() 1000 * 60 * 5) {this.initialized true;session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.ok(签名[ sign ]正确已记录!)));} else {session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error(签名[ sign ]已过期已记录!)));session.close();}} else {session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error(签名[ sign ]错误已记录!)));session.close();}} catch (Exception e) {log.error(加密信息[ password ]校验错误, e);session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error(加密信息校验错误已记录! br e.getMessage())));session.close();}} else if (Toolkit.isNotNull(userId) Toolkit.isNotNull(password)) {//todo 校验登录密码} else {log.error(登录信息错误[ userId ][ password ][ sign ]);session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error(登录信息错误已记录!)));session.close();}}/*** 向客户端发送消息** param message*/public void sendLogs(String message) {ConcurrentHashMapString, ListString thisLististMap logListMap.get(this.sys);if (thisLististMap null) {thisLististMap new ConcurrentHashMap();}ListString logList thisLististMap.get(this.userId);if (logList null) {logList new ArrayList();}logList.add(message);//日志暂存最新的100条if (logList.size() 100) {logList.remove(0);}this.sendToUser(message);}/*** 向指定客户端发送消息** param message*/private void sendToUser(String message) {for (UniappLogWebHandleV2 webSocket : webSocketMap.values()) {if (webSocket.isInitialized() webSocket.isPullLogs() webSocket.getSys().equals(this.sys) webSocket.getUserId().equals(this.userId)) {log.debug(【websocket消息】广播消息, message{}, message);try {Session session webSocket.getSession();session.getBasicRemote().sendText(message);} catch (Exception e) {log.error(【websocket消息】广播消息, message{}, message);}}}}/*** 向所有客户端发送消息** param message*/public void sendToAll(String message) {for (UniappLogWebHandleV2 webSocket : webSocketMap.values()) {if (!webSocket.isInitialized()) {continue;}log.debug(【websocket消息】广播消息, message{}, message);try {Session session webSocket.getSession();session.getBasicRemote().sendText(message);} catch (Exception e) {log.error(【websocket消息】广播消息, message{}, message);}}}public String getId() {return id;}public String getSys() {return sys;}public String getUserId() {return userId;}public boolean isInitialized() {return initialized;}public boolean isPullLogs() {return isPullLogs;} }html页面渲染日志 const id JSON.stringify({code: webStart,userId: userToken.userId,sign: userToken.token,isPullLogs: true,watchSys: $(#sys).val(),watchUserId: $(#userId).val() }) //aes加密 const aes aesEncrypt(id) //替换百分号 const url encodeURIComponent(aes).replaceAll(%, BaiFenHao) console.log([信息]传输协议秘钥, id, aes, url) // 指定websocket路径 var wsUrl ws:// location.host /weblog/uniapplogv2/ url;try {if (null ! websocket undefined ! websocket) {websocket.close();} } catch (e) {console.warn(e) } websocket new WebSocket(wsUrl); websocket.onmessage function (event) {// 接收服务端的实时日志并添加到HTML页面中$(#log-container div).append(showColorLog(event.data));$(#log-container div).append(br/)if (localStorage.autoJump 是) {// 滚动条滚动到最低部$(#log-container).scrollTop($(#log-container div).height() - $(#log-container).height());} }; websocket.onopen function (event) {reloadLock false; }websocket.onerror function (error) {console.log(onerror, error)// $(#log-container div).append(br/br/连接已断开... 5秒后尝试重新连接........ br/br/);// setTimeout(reloadWebSocket(), 5000) } websocket.onclose function (event) {console.log(onclose, event)$(#log-container div).append(br/br/连接已关闭... 5秒后尝试重新连接........ br/br/);setTimeout(reloadWebSocket, 5000) }总结 给远程调试提供方便websocket推送消耗较少也是有序推送完善好重连机制比post提交更方便查看。
http://www.w-s-a.com/news/785232/

相关文章:

  • 济南建设网站哪里好网站色哦优化8888
  • 什么网站做简历最好外贸公司网站大全
  • 衡水网站托管企业二级网站怎么做
  • 丹阳网站建设公司旅游类网站开发开题报告范文
  • 地方门户网站建设苏州网站优化建设
  • 谁用fun域名做网站了网络营销的三种方式
  • 织梦网站上传天津网站建设电话咨询
  • 论坛网站搭建深圳网
  • 天津建立网站营销设计window7用jsp做的网站要什么工具
  • 英文网站wordpress所有图片
  • 我做的网站怎么打开很慢网络营销典型企业
  • 新增备案网站python3网站开发
  • 诊断网站seo现状的方法与通信工程专业做项目的网站
  • 南京 微网站 建站alexa排名查询统计
  • 天津网站建设企业系统wordpress已发布不显示不出来
  • 大连网站前端制作公司局域网视频网站建设
  • 张家界建设局网站电话wordpress网站怎么建
  • 淄博网站建设有实力装修培训机构哪家最好
  • 彩票网站建设seo优化师是什么
  • 怎么做英文网站网站建设基本费用
  • dede网站名称不能保存wordpress运费设置
  • 出口网站制作好一点的网站建设
  • 在小说网站做编辑怎么找韶关市建设局网站
  • 网站策划怎么做内容旅游型网站建设
  • 东莞百度网站推广ppt模板免费下载的网站
  • 网站建设项目管理基本要求网站空间到期影响
  • 做奖杯的企业网站谁有推荐的网址
  • wordpress能做企业站吗wordpress收发邮件
  • 电子产品网站建设策划方案腾讯企业邮箱注册申请免费
  • 哪些网站可以免费做代码自己电脑做网站服务器广域网访问