-
2022-03-01 20:41:24
nodejs+php 实现 websocket 实时推送数据
首先安装node.js环境网上都有教程基本上都是傻瓜安装。
然后新建一个文件夹 socket
创建js文件 channel.js// channel.js /* * 根据分组频道来进行消息的分发 * 1. 主要实现服务端对客户端消息的传递 * 2. 通过redis消息队列来实现多语言的交互 */ // 初始化 socket io var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); var host = '127.0.0.1'; var port = 6379; //监听redis消息队列, 获取服务端需要传递给客户端的数据 var redis = require("redis"), subscriber = redis.createClient(port, host); //定义全局的对象, 频道名作为key对应该频道客户端的监听事件 channels[channelName] = receiveEvent var channels = {} //建立连接 io.on('connection', function (socket) { //console.log('public channel conneted sid-->>' + socket.id); //监听join事件 socket.on('join', function (data) { socket.join(data.channelName); //订阅频道 subscriber.subscribe(data.channelName); channels[data.channelName] = data.receive; }); //监听leave事件 socket.on('leave', function (roomName) { socket.leave(roomName); }); //断开连接 socket.on('disconnect', function () { //socket.leave('user_public'); }); }); //监听redis消息队列 subscriber.on("message", function (channel, message) { io.to(channel).emit(channels[channel], message); }); // 启动监听端口 http.listen(3001, function () { console.log('websocket server listening on *:3001'); });
在socket文件下打开命令窗口
npm install
安装forever:
npm install forever -g
启动守护进程
forever start channel.js
关闭守护进程:
forever stop server.js
如果需要记录输出日志和错误:
forever start -l forever.log -o out.log -e err.log server.js
安装redis
创建服务端php文件 server.php$channel = 'send';//推送的分组也可以示唯一标识 $data =['msg'=>'推送数据','data'=>[]] $host = '127.0.0.1'; $port = 6379; $redis = new \Redis(); try { $redis->connect($host, $port); } catch (\Exception $e) { echo "Error: redis connet failed" . PHP_EOL; exit; } $redis->publish($channel, json_encode($data));
创建客户端html 文件 index.html或者vue.js
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="format-detection" content="telephone=no"/> <meta name="format-detection" content="email=no"/> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" name="viewport"> <title>PHP+node.js推送消息至前台页面</title> <!--引入socket.io.js--> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script> </head> <body> <div>websocket消息推送演示,消息内容输出如下:</div> <div id="content"></div> <script type="text/javascript"> var wsHost = 'http://127.0.0.1:3001'; //建立websocket连接 --- var socket = io.connect(wsHost); // 频道名 和 接收消息方法声明 var sub = {} sub.channelName = 'send'; sub.receive = 'receive_function'; //订阅加入频道 socket.emit('join', sub); //监听消息处理 socket.on(sub.receive, function (result) { console.log(result); document.getElementById('content').innerHTML = result; }); </script> </body>
这样一个简单的实时推送数据消息的socket就写好了,对于大部分场景应该够用了,如有更多的扩展欢迎留言讨论
更多相关内容 -
websocket实时推送数据
2022-04-25 11:51:09基于node的前端websocket简单实时推送数据基本用法 含有goEasy基础版本 -
基于WebSocket的车辆GPS信息实时推送系统的实现
2021-01-27 06:53:11文中针对车辆管理系统中如何实时获取车辆定位信息问题,采用缓存数据库实现对终端实时反馈的GPS信息进行存储,推送服务器利用Websocket将缓存数据库中数据的变动情况进行处理并将处理结果推送至Web应用,从而实现... -
Python实现博客站点url实时推送至百度与Linux定时任务
2021-01-09 13:21:30之前写过一篇关于【Hexo】maupassant 主题设置百度站点自动推送 的博客 这种自动推送的方式也只有在页面受访时才会被提交至百度,只可惜百度对个人博客url的收录速度确实无法跟Google比呀,因此编写Python脚本通过... -
百度POST实时推送工具V3.0.zip
2016-03-19 23:08:04使用方法: 1.前往站长工具——链接提交——推送接口 获取接口调用地址 2.配置规则,打开自己网站——右键...(本程序可以全自动化,如需挂在服务器上全自动提交,请勾选“自动获取URL并实时推送,然后点击开启推送”) -
百度链接提交实时推送工具 v0.2
2019-11-04 21:56:57百度链接提交实时推送工具主要帮助大家便捷的实时推送站点更新的URL给百度。同时考虑到了部分同学的需求特别添加了“自动监控文件变化并推送”的功能,启用该功能后百度链接提交实时推送工具会按照指定的时间间隔... -
百度POST实时推送V3.0
2018-07-17 12:18:22百度POST实时推送V3.0工具,百度POST实时推送V3.0,百度POST实时推送V3.0 -
2021年最新百度实时推送工具.rar
2021-07-10 23:38:49网站收录链接提交辅助工具助手,可以通过多种方式提交页面链接。...多合一百度链接提交实时推送工具,包含主动推送,MIP提交,原创保护提交,熊掌号提交等多种提交方式。 使用教程:https://www.zhanceo.com/12473.html -
百度实时推送工具V0.3.1.zip
2020-04-24 22:12:151.界面可以自行配置,在Config.ini内修改IsBase的值配置成0即为精简Low B模式。 2.增加大家呼声较高的多渠道推送功能,目前支持百度实时推送、MIP、熊掌号、原创推送。 -
百度实时推送工具11111
2021-03-17 17:55:27百度实时推送工具11111 -
百度链接提交实时推送工具V0.2
2015-10-18 15:54:07百度 链接 提交实时 推送工具V0.2 传送收录网站工具 -
百度链接提交实时推送工具 v0.1.zip
2019-07-15 02:04:15本软件主要帮助大家便捷的实时推送站点更新的URL给百度。同时考虑到了部门同学的需求特别添加了“自动监控文件变化并推送”的功能,启用该功能后程序会按照指定的时间间隔自动去检查对应的txt文件是否有新的变化,... -
PHP实现的消息实时推送功能【基于反ajax推送】
2020-10-18 16:06:59主要介绍了PHP实现的消息实时推送功能,结合实例形式分析了php基于反ajax推送实现的消息实时推送前台ajax提交、后台数据处理等相关操作技巧,需要的朋友可以参考下 -
python获取地震信息 微信实时推送
2021-01-01 02:19:41本文实例为大家分享了python获取地震信息微信实时推送的具体代码,供大家参考,具体内容如下 import requests,time from lxml import etree from wxpy import * # 微信登陆 bot = Bot() # 查找好友 group = bot.... -
一种基于JMS的实时推送技术
2021-04-16 15:27:24由于出现了基于web的服务器反向推送技术,这也是本文的创新之处所在,本文在传统研究的基础之上,提出了一种基于消息总线的实时推送技术,该技术采用JMS消息服务、结合DWR框架来实现模块间信息的实时传递,在文中... -
应急通信网络大容量信息实时推送系统设计
2021-01-13 01:23:40为稳定信息网络的通信地址路由矢量,实现扩充中继推送端口流量的目的,设计应急通信网络大容量信息实时推送系统。以自定义应急协议通信模块、网络端信息负载均衡模块的良性交互作为基础条件,对实时消息推送管理模块... -
织梦sitemap地图实时推送给百度的教程
2020-10-23 20:10:26主要介绍了织梦sitemap地图实时推送给百度的教程,需要的朋友可以参考下 -
百度实时推送api接口应用示例
2020-10-25 08:20:55主要介绍了百度实时推送api接口应用示例,非常的实用,有需要的朋友可以参考下 -
pushlet java 消息实时推送
2016-05-12 14:51:52简单的消息推送实例,解决了中文错误问题! -
百度实时推送助手 v1.17_百度_百度推送_实时_百度时时推送助手_
2021-10-02 10:06:43批量生成链接,主动推送到百度进行收录,速度快,稳定,长期有效 -
百度实时推送助手
2019-03-05 11:53:19百度主动推送助手是一款seo利器,根据百度官方规则定制的主动推送工具.软件推送效率高,为站群站长量身定制.每秒可以推送2000条数据,还可以挂机.挂机模式可以不停的循环推送! 使用说明: 1.先配置接口文件,支持多行 2.... -
使用webSocket实现对数据的实时推送
2021-12-15 14:34:06实现的一个简单的向智慧大屏实时推送数据的案例,页面无刷新,无过多请求,优化用户体验,代码及实现方式详解。使用webSocket实现对数据的实时推送详解
1.什么是webSocket?
相对于 HTTP 这种非持久的协议来说,websocket是 HTML5 出的一个持久化的协议。
2.实时推送数据的实现方式以及应用场景
实现方式
1.轮询:客户端通过代码定时向服务器发送AJAX请求,服务器接收请求并返回响应信息。
优点:代码相对简单,适用于小型应用。
缺点:在服务器数据没有更新时,会造成请求重复数据,请求无用,浪费带宽和服务器资源。2.长连接:在页面中嵌入一个隐藏的iframe,将这个隐藏的iframe的属性设置为一个长连接的请求或者xrh请求,服务器通过这种方式往客户端输入数据。
优点:数据实时刷新,请求不会浪费,管理较简洁。
缺点:长时间维护保持一个长连接会增加服务器开销。3.webSocket:websocket是HTML5开始提供的一种客户端与服务器之间进行通讯的网络技术,通过这种方式可以实现客户端和服务器的长连接,双向实时通讯。
优点:减少资源消耗;实时推送不用等待客户端的请求;减少通信量;
缺点:少部分浏览器不支持,不同浏览器支持的程度和方式都不同应用场景:聊天室、智慧大屏、消息提醒、股票k线图监控等。
3.代码详解
(1)使用Visual Studio开发工具创建控制台项目,(这里只做数据实时推送演示效果,中间的实际业务代码可根据需求场景来开发)
(2)使用NuGet包管理器搜索Fleck包,并安装到我们上一步所创建的项目。
(3)在Main方法下面贴上如下代码,有注释Console.WriteLine("(1)输入1更新图表数据;"); Console.WriteLine("(2)输入exit退出系统"); //先使用Nuget导入Fleck包 版本要求.net Framework 4.5及以上 FleckLog.Level = LogLevel.Debug; var allSockets = new List<IWebSocketConnection>(); //监听所有的的地址 var server = new WebSocketServer("ws://0.0.0.0:30000"); server.Start(socket => { // onopen:当WebSocket建立网络连接的时候触发该事件 socket.OnOpen = () => { Console.WriteLine("连接已打开!"); allSockets.Add(socket); }; // onclose:当WebSocket被关闭的时候触发该事件 socket.OnClose = () => { Console.WriteLine("连接已关闭!"); allSockets.Remove(socket); }; // onmessage:当WebSocket接受到远程服务器的数据的时候触发该事件 socket.OnMessage = message => { Console.WriteLine(message); allSockets.ToList().ForEach(s => s.Send("路人甲: " + message)); }; }); var input = Console.ReadLine(); while (input != "exit") { foreach (var socket in allSockets.ToList()) { //send():向远程服务器发送数据 //socket.Send("路人乙:"+input); if (input == "1") { string jsonData = @"{""teamData"":[25,33,15,0,5,10,20],""salesData"":[35,25,18,0,32,22,13]}"; socket.Send(jsonData); Console.WriteLine("发送成功"); } } input = Console.ReadLine(); }
(4)构建前端页面,在前端JS配置代码如下,可以在ws.onmessage事件中获取服务器实时推送的数据,可根据业务场景设计页面样式,我这里模拟的是一个智慧大屏的实时推送。
<script type="text/javascript"> var start = function () { var wsImpl = window.WebSocket || window.MozWebSocket; window.ws = new wsImpl('ws://localhost:30000/'); //接收到消息的回调方法 ws.onmessage = function (evt) { //evt.data为后台推送数据 console.log(evt.data); }; //连接成功建立的回调方法 ws.onopen = function () { //连接已打开 }; //连接关闭的回调方法 ws.onclose = function () { //连接已关闭 } } window.onload = start; </script>
4.小结
从上面的案例可以看到,websocket采取的方式是让客户端连接服务器,只要两端进行连接之后,就可以避免多次请求直接发送端对端的数据,不需要经过第三方的转发,只需要客户端和浏览器通过http协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。
文章案例由于前端页面代码太多,就没有全部贴到文章里,有需要的可以在下面留言
-
百度惠:实时推送优惠-crx插件
2021-03-10 19:15:32- 支持实时推送,以及音效、类型、频率的个性化 - 永久缓存最新的优惠列表,保障您优惠到底的买买买权利 - 支持百度VIP跳转,保障返利权利 - 支持一键清空缓存、还原所有默认配置 - 支持勿扰模式,关键时段给你一片... -
百度实时推送助手 v1.07
2020-10-03 12:50:07百度主动推送助手是一款seo利器,根据百度官方规则定制的主动推送工具.软件推送效率高,为站群量身定制.每秒可以推送2000条数据,还可以挂机.挂机模式可以不停的循环推送! 使用说 -
springboot+vue+Nginx实现消息实时推送
2022-02-21 14:17:28springboot+vue+Nginx实现消息实时推送引入websocket的依赖向spring注入websocket的bean开始写websocket的接收推送消息的方法就行了测试本地websocket是否连通前端vue 业务场景是这样的,公司要将业务员的业绩实时...springboot+vue+Nginx实现消息实时推送
业务场景是这样的,公司要将业务员的业绩实时公布在大屏上,于是我就做了一套大屏页面,现在只剩实时更新数据了;vue前端也可以不断轮询请求数据,但是这样你打开F12会发现很多网络请求,很不好看,也不安全,于是我选择了websocket来实时推送引入websocket的依赖
<!--websocket--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
向spring注入websocket的bean
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.server.standard.ServerEndpointExporter; //注意注解 @EnableWebSocket @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
开始写websocket的接收推送消息的方法就行了
import com.alibaba.fastjson.JSONArray; import com.zbkj.crmeb.tool.service.DateScricpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArraySet; /** * @author zihao * * @date 2022-02-20 */ @Controller @ServerEndpoint("/websocket/{tableId}") public class WebSocket { @Autowired private DateScricpService dateScricpService; private Session session; private String uid; public static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>(); private static Map<String,Session> sessionPool = new HashMap<String,Session>(); //session集合 private static List<Map<String,Object>> sessionList = new ArrayList<>(); //信息集合 private static List<Map<String,String>> MessageList = new ArrayList<>(); @OnOpen public void onOpen(Session session, @PathParam(value="tableId")String code) { Map<String,Object> map = new HashMap<String,Object>(); this.session = session; this.uid = code; webSockets.add(this); sessionPool.put(code, session); map.put("uid", code); map.put("session", session); sessionList.add(map); // Constants.WEBSOCKET = true;//定义常量 是否开启websocket连接 System.out.println("【websocket消息】有新的连接,总数为:"+webSockets.size()); } @OnClose public void onClose() { webSockets.remove(this); //Constants.WEBSOCKET = false; System.out.println("【websocket消息】连接断开,总数为:"+webSockets.size()); for (int i = 0; i < MessageList.size(); i++) { if(this.uid.equals(MessageList.get(i).get("uid"))){ MessageList.remove(i); break; } } for (int i = 0; i < sessionList.size(); i++) { if(this.uid.equals(sessionList.get(i).get("uid"))){ sessionList.remove(i); break; } } } @OnMessage public void onMessage(String message) { System.out.println("【websocket消息】收到客户端消息:"+message); if(MessageList != null && MessageList.size() > 0){ for (Map<String, String> messageMap : MessageList) { if(this.uid.equals(messageMap.get("uid"))){ messageMap.put("message",message); return; } } } Map<String,String> map = new HashMap<String,String>(); map.put("message",message); map.put("uid",this.uid); MessageList.add(map); } // 此为广播消息 public void sendAllMessage(String message) { for(WebSocket webSocket : webSockets) { try { webSocket.session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } // 此为单点消息 public void sendOneMessage(String code, String message) { Session session = sessionPool.get(code); /*在发送数据之前先确认 session是否已经打开 使用session.isOpen() 为true 则发送消息 * 不然会报错:The WebSocket session [0] has been closed and no method (apart from close()) may be called on a closed session */ if (session != null && session.isOpen()) { try { session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } // 此为对应的广播消息 public void sendAllMessageBySelf() { //查询大屏的基础数据 Map<String, String> TotalDate = dateScricpService.selectTotalDate(); //查询客户概况的基础数据 Map<String, Object> customerDate = dateScricpService.selectCustomerDate(); //查询收入概况的基础数据 Map<String, Object> receiveDate = dateScricpService.selectReceiveDate(); //查询月收入概况的基础数据 Map<String, Object> monthReceiveMoneyY = dateScricpService.selectMonthReceiveMoneyY(); //查询业务分配数据 Map<String, Object> businessDistribute = dateScricpService.selectBusinessDistribute(); if(MessageList != null && MessageList.size() > 0){ for (Map<String, String> messageMap : MessageList) { if(sessionList != null && sessionList.size() > 0){ for (Map<String, Object> sessionMap : sessionList) { if(String.valueOf(sessionMap.get("uid")).equals(messageMap.get("uid"))){ Map<String,Object> message = (Map<String, Object>)JSONArray.parse(messageMap.get("message")); int employeeNum = Integer.parseInt(String.valueOf(message.get("employeeNum"))); //查询员工概况的基础数据 List<Map<String, String>> employeeDateList = dateScricpService.selectEmployeeDate(String.valueOf(employeeNum)); //查询月平均贡献数据 Map<String, Object> avageMonth = dateScricpService.selectAverageMonthReceiveMoneyY(String.valueOf(message.get("avageMonth"))); //查询业务员分配数据 Map<String, Object> EmployeeInfo = dateScricpService.selectEmployeeInfo(String.valueOf(message.get("employeeDate"))); //返回的数据 Map<String,Object> returnDate = new HashMap<>(); returnDate.put("TotalDate",TotalDate); returnDate.put("customerDate",customerDate); returnDate.put("receiveDate",receiveDate); returnDate.put("monthReceiveMoneyY",monthReceiveMoneyY); returnDate.put("businessDistribute",businessDistribute); returnDate.put("employeeDateList",employeeDateList); returnDate.put("avageMonth",avageMonth); returnDate.put("EmployeeInfo",EmployeeInfo); String returnStr = JSONArray.toJSONString(returnDate); //向指定客户端推送信息 Session session = (Session)sessionMap.get("session"); session.getAsyncRemote().sendText(returnStr); } } } } } } }
这里的sendAllMessageBySelf方法是我混合业务写的特定推送消息,借鉴即可。具体思路是每个客户端通过onOpen方法进来,这时候我同uid和session来存取对应客户端的标志和session对象,然后将这两个参数放到map对象里再放到客户端集合sessionList里,方便群发针对性的消息;MessageList是存放对应客户端的消息集合,具体看onMessage方法即可,写完之后我们就可以测试一下是否连通。让后端多久推送一次,自己写个定时任务就行。
import com.utils.DateUtil; import com.zbkj.crmeb.tool.service.DateScricpService; import com.zbkj.crmeb.tool.utils.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * websocket的定时推送 * * @author zihao * * @date 2022-02-20 */ @Component @Configuration //读取配置 @EnableScheduling // 2.开启定时任务 public class WebsocketTask { //日志 private static final Logger logger = LoggerFactory.getLogger(WebsocketTask.class); @Autowired private DateScricpService dateScricpService; @Autowired private WebSocket webSocket; @Scheduled(fixedDelay = 1000 * 5L) //5秒钟同步一次数据 public void init(){ logger.info("---开始推送数据 - {}", DateUtil.nowDateTime()); try { //调用websocket对应客户端推送数据推送消息 webSocket.sendAllMessageBySelf(); }catch (Exception e){ e.printStackTrace(); logger.error("websocket.task" + " | msg : " + e.getMessage()); } } }
测试本地websocket是否连通
用http://ws.douqq.com/地址测一下就行
没问题了我们就可以配置Nginx了,毕竟还是要发到服务器上的map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 888 ssl; server_name 域名; ssl_certificate 证书路径; ssl_certificate_key 证书路径; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location ~/websocket/ { proxy_pass https://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
友情提示不要忘了在服务器上把888端口放开,配完了再用刚才那个地址测一下,测通了我们就开始写前端了
前端vue
created(){ this.initWebSocket(); }, destroyed() { this.websocketclose(); }, methods: { //初始化websocket initWebSocket() { var uid = (((1+Math.random())*0x10000)|0).toString(16); var uid1 = (((1+Math.random())*0x10000)|0).toString(16); var uid2 = (((1+Math.random())*0x10000)|0).toString(16); this.uid = uid+uid1+uid2; //debugger; // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https key你自定义的key //var host = window.location.host // this.websock = new WebSocket("wss://ip:888/websocket/"+this.uid); this.websock = new WebSocket("ws://127.0.0.1:8081/websocket/"+this.uid); this.websock.onopen = this.websocketonopen; this.websock.onerror = this.websocketonerror; this.websock.onmessage = this.websocketonmessage; this.websock.onclose = this.websocketclose; }, websocketonopen() { this.websocketsend(); }, websocketonerror(e) { console.log("error") console.log(e) }, websocketonmessage(e) { //JSON.parse(e.data); //这个是收到后端主动推送的值 var receiveMessage = JSON.parse(e.data); console.log("message") console.log(receiveMessage) }, websocketsend(){ let data = {employeeNum: this.subTabIndex,employeeDate: this.monthDate,avageMonth: this.subTabMonthIndex}; //数据发送 this.websock.send(JSON.stringify(data)); }, websocketclose(e) { console.log("close") console.log(e) }, }
如果是https就用wss来链接websocket,我服务器是https,所以配的证书用的wss。到这已经基本搞完了,看看成果吧
从图上可以看出来,那个50,我后台录入了200的订单,已经实时更新成250了,成功了 -
python Django websocket 实时消息推送
2018-08-29 14:54:26Django1.9.2 websocket 实时消息推送 服务端主动推送 调用 send(username, title, data, url) username:用户名 title:消息标题 data:消息内容,ulr:消息内容 ulr -
多合一百度链接提交实时推送工具(主动推送,MIP提交,原创保护提交,熊掌号提交)
2018-03-07 17:08:55SEO研究中心内部余弦老师研发的网站收录链接提交辅助工具...多合一百度链接提交实时推送工具,包含主动推送,MIP提交,原创保护提交,熊掌号提交等多种提交方式。 使用教程:http://www.seoshipin.cn/gongju/1925.html .