说起测试GA,真是一件枯燥乏味,重复性很高的工作,那么为什么我们不使用自动化测试代替它呢,显然,很多公司的产品迭代太快,ga也变化的比较频繁,但是确保ga工作正常,对于其他部门的工作是有很大帮助的,由于公司对于这块比较注重,而且曾经出现过ga被前端修复bug而影响,所以抽空倒腾了下如何对ga进行自动化测试,由于自身比较习惯使用ruby,所以本帖都是ruby的代码,思路是一样的,喜欢的童鞋可以用其他语言去实现。

首先说说开始考虑的实现方案:

1. 使用selenium+firefox的插件抓取request生成har文件,尝试过后发现不可行,点看此文章 http://www.cnblogs.com/timsheng/p/7209964.html

2. 使用proxy,讲浏览器请求server的request转发到本地,proxy的库有很多,ruby内置的webrick就很好用,但是尝试过后发现依然不行,webrick只能抓取http的request,我们网站是https协议的,抓取不到。

3. 使用evil-proxy, https://github.com/bbtfr/evil-proxy 这个库很强大,可以结合selenium使用,原理是运行时会生成自己的签名文件,然后将生成的签名文件import到浏览器就行了,具体如何操作请参考wiki,但是问题又来了,我们网站https的request都可以抓到,除了google的https request无法抓取,会提示无效签名。

4. ok,这些简单的方式都无法成功抓取ga的request,只能出绝招了,可能大家都知道,phantomjs是一个很强大的工具,它可以结合其他框架做headless网站测试,可以截图,不同于selenium截取当前页面图,它可以截取全屏截图,另外它可以做网页测试,最关键的是它可以进行网络监控。 传送门在此,http://phantomjs.org/

所以我们需要使用的是phantomjs, 去进行页面自动化,并且抓取生成的ga requests,用ruby去分析日志,并且进行校验pageview和event的事件是否触发,参数是否正确

不多说上代码:

首先看一下目录结构

我们先来看一下student_ga.js文件

/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/ "use strict"; function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis || 8000, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 250); //< repeat check every 250ms
}; // initialise various variables
var page = require('webpage').create(),
system = require('system'),
address; page.viewportSize = {
width: 1280,
height: 800
}; // how long should we wait for the page to load before we exit
// in ms
var WAIT_TIME = 30000; // if the page hasn't loaded after this long, something is probably wrong
// in ms
var MAX_EXECUTION_TIME = 30000; // output error messages
var DEBUG = true // a list of regular expressions of resources (urls) to log when we load them
var resources_to_log = [
new RegExp('^http(s)?://(www|ssl)\.google-analytics\.com.*'),
new RegExp('^http(s)?://stats\.g\.doubleclick\.net.*')
]; // page.settings.resourceTimeout = 10000; // check we have a url, if not exit
if (system.args.length === 1) {
console.log('Usage: get_ga_resources.js http://www.yoururl.com');
phantom.exit(1);
} else {
// address is the url passed
address = system.args[1]; // create a function that is called every time a resource is requested
// http://phantomjs.org/api/webpage/handler/on-resource-requested.html
page.onResourceRequested = function (res) {
// loop round all our regexs to see if this url matches any of them
var length = resources_to_log.length;
while(length--) {
if (resources_to_log[length].test(res.url)){
// we have a match, log it
console.log(res.url);
}
}
}; // if debug is true, log errors, else ignore them
page.onError = function(msg, trace){
if (DEBUG) {
console.log('ERROR: ' + msg);
console.log(trace);
}
}; // output console message
// page.onConsoleMessage = function(msg) {
// console.log(msg);
// }; // page.onResourceTimeout = function(request) {
// console.log(request.errorCode);
// console.log(request.errorString);
// console.log(request.url);
// // console.log('Response (#' + request.id + '): ' + JSON.stringify(request));
// }; // page.onResourceError = function(resourceError) {
// console.log('Unable to load resource (#' + resourceError.id + 'URL:' + resourceError.url + ')');
// console.log('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
// }; // now all we have to do is open the page, wait WAIT_TIME ms and exit
try {
page.open(address, function (status) {
console.log("Starting to open --" + address);
if (status !== 'success') {
console.log("FAILED: to load " + system.args[1]);
console.log(page.reason_url);
console.log(page.reason);
phantom.exit();
} else {
// page is loaded!
if(address != page.url){
console.log('Redirected: ' + page.url)
}
// start to do actions on website
console.log("Success to open --" + address);
page.render("screenshot/homepage.png"); // select top city london to click
page.evaluate(function(s) {
console.log("Click top city -> London");
document.querySelector(".top-cities__city:nth-child(1)>a>img").click();
console.log("click it done!!!!");
}); // submit enquiry until srp load completely
setTimeout(function(){
waitFor(function() {
// Check in the page if a specific element is now visible
return page.evaluate(function() {
console.log("determine if contact an expert element is visible? ")
cae_element = document.querySelector(".advicebar__btn.button.button--keppel.js-account-modal-enquire");
if (cae_element.offsetWidth > 0 && cae_element.offsetHeight > 0) {
return true;
} else {
return false;
};
});
}, function() {
console.log("Page_Url:" + page.url);
console.log("--- Get into search result page ---");
page.render("screenshot/search_result_page.png");
page.evaluate(function() {
console.log(document.querySelector(".advicebar__btn.button.button--keppel.js-account-modal-enquire").innerHTML);
document.querySelector(".advicebar__btn.button.button--keppel.js-account-modal-enquire").click();
console.log("Click contact an expert button");
});
console.log("--- Get into entry screen ---");
page.render("screenshot/entry_screen.png"); page.evaluate(function() {
document.querySelector("#js-account-modal-enquiry .account-modal__step--visible .button.button--secondary").click();
console.log("--- Get into sign up screen ---");
document.querySelector("#js-account-modal-enquiry input[name='full_name']").value = 'tim sheng';
document.querySelector("#js-account-modal-enquiry input[name='telephone']").value = '123123123';
document.querySelector("#js-account-modal-enquiry input[name='email']").value = ("tim.sheng+" + (new Date().getTime()) + "@student.com");
document.querySelector("#js-account-modal-enquiry input[name='password']").value = 'abc20052614';
});
page.render("screenshot/sign_up_screen.png");
page.evaluate(function() {
console.log('Click sign up button');
document.querySelector("#set-password-button").click();
});
waitFor(function() {
return page.evaluate(function() {
console.log("determine if confirm button is visible? on about you screen");
confirm_element = document.querySelector("#js-account-modal-enquiry #submit-about-you-button");
if (confirm_element.offsetWidth > 0 && confirm_element.offsetHeight > 0) {
return true;
} else {
return false;
};
});
}, function() {
console.log("--- Get into about you screen ---");
page.render("screenshot/about_you_screen.png");
page.evaluate(function() {
document.querySelector("#js-account-modal-enquiry #submit-about-you-button").click();
});
waitFor(function() {
return page.evaluate(function() {
console.log("determine if budget field is visible? on about listing screen");
budget_element = document.querySelector("#js-account-modal-enquiry input[name='budget']");
if (budget_element.offsetWidth > 0 && budget_element.offsetHeight > 0) {
return true;
} else {
return false;
};
});
}, function() {
console.log("--- Get into about listing screen ---");
page.render("screenshot/about_listig_screen_unfilled.png");
page.evaluate(function() {
// click date picker plugin
document.querySelector("#js-account-modal-enquiry .date-picker").click();
// select move in date
document.querySelectorAll("#js-account-modal-enquiry .js-date-picker-move-in-fieldset input[class='js-date-picker-move-in-month']:not(:disabled)+label")[0].click();
// select move out date
document.querySelectorAll("#js-account-modal-enquiry .js-date-picker-move-out-fieldset input[class='js-date-picker-move-out-month']:not(:disabled)+label")[0].click();
// input budget value
document.querySelector("#js-account-modal-enquiry input[name='budget']").value = '1234';
// input university value
document.querySelector("#js-account-modal-enquiry .account-modal__step--visible input[name='university']").value = 'london';
// dispatch inputing event to elem
var event = new Event('inputing');
input_elem = document.querySelector("#js-account-modal-enquiry .account-modal__step--visible input[name='university']");
input_elem.focus();
input_elem.dispatchEvent(event);
});
waitFor(function() {
return page.evaluate(function() {
console.log("determine if university is visible? on autocomplete list");
uni_element = document.querySelector('#js-account-modal-enquiry .autocomplete__item:first-child .autocomplete__item__link');
if (uni_element.offsetWidth > 0 && uni_element.offsetHeight > 0) {
return true;
} else {
return false;
};
});
}, function() {
console.log("--- University is visible on autocomplete list");
page.render("screenshot/about_listing_university.png");
page.evaluate(function() {
document.querySelector('#js-account-modal-enquiry .autocomplete__item:first-child .autocomplete__item__link').click();
});
page.render("screenshot/about_listing_screen_filled.png");
page.evaluate(function() {
console.log("Click submit enquiry button");
document.querySelector("#js-account-modal-enquiry .account-modal__step--visible #submit-about-stay-button").click();
});
waitFor(function() {
return page.evaluate(function() {
console.log("determine if success button is visible? on leads process screen");
success_element = document.querySelector("#js-account-modal-enquiry .account-modal__step--visible .button.button--primary");
if (success_element.offsetWidth > 0 && success_element.offsetHeight > 0) {
return true;
} else {
return false;
};
});
}, function() {
console.log("submit enquiry from srp cae successfully!");
page.render("screenshot/enquiry_success_screen.png");
});
});
});
});
});
},5000); setTimeout(function () {
phantom.exit();
}, WAIT_TIME);
}
});
} finally {
// if we are still running after MAX_EXECUTION_TIME ms exit
setTimeout(function() {
console.log("FAILED: Max execution time " + Math.round(MAX_EXECUTION_TIME) + " seconds exceeded");
phantom.exit(1);
}, MAX_EXECUTION_TIME);
}
}

  

然后写个ruby类去解析这个log, cop.rb

require 'uri'

module Cop
class Logger
attr_accessor :ga_requests def initialize path
@ga_requests = open_log_file path
end # fetch pageview ga
def pageview
Gas.new ga_requests, 'pageview'
end # fetch event ga
def event
Gas.new ga_requests, 'event'
end # get all google analytics request records
def open_log_file path
all_ga_requests = []
File.open("#{path}").each do |line|
all_ga_requests << line if line.include? 'google'
end
all_ga_requests
end
end # Gas is a class which is consisted of a list of specific ga requests
class Gas
attr_accessor :gas, :type VALID_KEYS = ['dl','dp','ul','dt','ec','ea','el','cd17','cd15','cd5','cd1','cd14','cg1','cg2','cd18','cd2'] def initialize all_gas, type
h_gas = handle_gas all_gas, type
@gas = get_expected_gas h_gas
@type = type
end # return the count of gas
def count
gas.count
end # use the value of key to get corresponding pageview record
# it is better for pageview ga using dp to get value
# use the value of key to get corresponding event record
# it is better for event ga using el to get value
def get_gas_by value
gas.each do |ga|
ga.each do |k,v|
if v == value
return ga
end
end
end
end private # fetch ga requests by type
def handle_gas all_gas, type
new_gas_arr = []
all_gas.each do |all_ga|
if all_ga.include? type
decoded_all_ga = URI.decode(all_ga)
new_gas_arr << qs_to_hash(decoded_all_ga)
end
end
new_gas_arr
end # get expected gas
def get_expected_gas gas
expected_gas = []
gas.each do |ga|
expected_ga = {}
ga.each do |k,v|
if VALID_KEYS.include? k
expected_ga[k] = v
else
next
end
end
expected_gas << expected_ga
end
expected_gas
end # decode url
def qs_to_hash query
keyvals = query.split('&').inject({}) do |result, q|
k,v = q.split('=')
if !v.nil?
result.merge({k => v})
elsif !result.key?(k)
result.merge({k => true})
else
result
end
end
keyvals
end
end
end

Gas类返回的是hash,我希望取hash的value根据object.xx  的形式,而不是hash[] 的方式,所以重新打开hash类,根据ga的常用参数使用define_method动态定义一些方法

hash.rb

class Hash
VALID_KEYS = ['dl','dp','ul','dt','ec','ea','el','cd17','cd15','cd5','cd1','cd14','cg1','cg2','cd18','cd2'] def self.ga name
define_method "#{name}" do
self["#{name}"]
end
end VALID_KEYS.each do |e|
ga "#{e}"
end end

基本准备工作差不多了,现在我们用rspec去管理测试用例,在执行case前,我们需要去清洗一下环境,删除log文件,截图,然后执行phantomjs脚本,

bridge.rb

module Cop

  def clear_env
`rm -rf screenshot/*.png`
`rm -rf log/*.log`
end def submit_lead_from_srp_cae
`phantomjs js/student_ga.js https://hurricane-cn.dandythrust.com > log/ga.log`
end
end

让我们在根目录下rspec --init一下,生成spec_helper.rb 文件,在此文件中,引入cop.rb, hash.rb, bridge.rb以便于在_spec文件中使用

spec_helper.rb

require 'cop'
require 'hash'
require 'bridge'
include Cop

新建个ga_spec.rb文件,开始编写case

require "spec_helper"

describe "GA Checking" do
describe "New user submit lead from srp cae" do before(:all) do
clear_env
submit_lead_from_srp_cae
end let(:logger) { logger= Cop::Logger.new "log/ga.log" } context "Pageview" do
let(:pageview_gas) { logger.pageview } it "should be correct on homepage" do
result = pageview_gas.get_gas_by "/"
expect(result.dp).to eql "/"
expect(result.ul).to eql "en-us"
expect(result.cd17).to eql "3rd Party Login" unless result.cd17.nil?
expect(result.cd15).to eql "zh-cn"
expect(result.cd5).to eql "home"
expect(result.cd1).to eql "desktop"
expect(result.cd14).to eql "Special Offers"
expect(result.cg1).to eql "Home Page"
end it "should be correct on search result page" do
result = pageview_gas.get_gas_by "/uk/london"
expect(result.dp).to eql "/uk/london"
expect(result.ul).to eql "en-us"
expect(result.cd17).to eql "3rd Party Login" unless result.cd17.nil?
expect(result.cd18).to eql ""
expect(result.cd15).to eql "zh-cn"
expect(result.cd5).to eql "search"
expect(result.cd2).to eql "London"
expect(result.cd1).to eql "desktop"
expect(result.cg1).to eql "Search"
expect(result.cg2).to eql "City"
end it "should be correct on entry screen" do
result = pageview_gas.get_gas_by "/modal/enquiry/cae_srp/signup"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup"
expect(result.ul).to eql "en-us"
end it "should be correct on sign up screen" do
result = pageview_gas.get_gas_by "/modal/enquiry/cae_srp/signup/email"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email"
expect(result.ul).to eql "en-us"
end it "should be correct on about you screen" do
result = pageview_gas.get_gas_by "/modal/enquiry/cae_srp/signup/email/confirm_contact_info"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/confirm_contact_info"
expect(result.ul).to eql "en-us"
end it "should be correct on about stay screen" do
result = pageview_gas.get_gas_by "/modal/enquiry/cae_srp/signup/email/about_stay"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/about_stay"
expect(result.ul).to eql "en-us"
end it "should be correct on enquiry success screen" do
result = pageview_gas.get_gas_by "/modal/enquiry/cae_srp/signup/email/enq_submitted"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/enq_submitted"
expect(result.ul).to eql "en-us"
end
end context "Event" do
let(:event_gas) { logger.event } it "click top city on homepage is correct" do
result = event_gas.get_gas_by "topCities"
expect(result.dp).to eql "/"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "topCities"
expect(result.ec).to eql "homePage"
expect(result.el).to eql "city:231004024"
expect(result.cd17).to eql "3rd Party Login"
expect(result.cd15).to eql "zh-cn"
expect(result.cd5).to eql "home"
expect(result.cd1).to eql "desktop"
expect(result.cd14).to eql "Special Offers"
expect(result.cg1).to eql "Home Page"
end it "click contact an expert on srp is correct" do
result = event_gas.get_gas_by "cae > button:getInTouch"
expect(result.dp).to eql "/uk/london"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "cae > button:getInTouch"
expect(result.ec).to eql "searchClick"
expect(result.el).to eql "231004024-London"
expect(result.cd17).to eql "3rd Party Login"
expect(result.cd15).to eql "zh-cn"
expect(result.cd5).to eql "search"
expect(result.cd2).to eql "London"
expect(result.cd1).to eql "desktop"
expect(result.cg1).to eql "Search"
expect(result.cg2).to eql "City"
end it "click sign up with email on entry screen is correct" do
result = event_gas.get_gas_by "continueWithEmail"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "signupScreen"
expect(result.ec).to eql "enquiryFlow"
expect(result.el).to eql "continueWithEmail"
end it "click continue button on sign up screen is correct" do
result = event_gas.get_gas_by "continueBtnClicked"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "signupWithEmailScreen"
expect(result.ec).to eql "enquiryFlow"
expect(result.el).to eql "continueBtnClicked"
end it "click request details button on about you screen is correct" do
result = event_gas.get_gas_by "requestDetailsBtnClicked"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/confirm_contact_info"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "confirmContactInfo:email"
expect(result.ec).to eql "enquiryFlow"
expect(result.el).to eql "requestDetailsBtnClicked"
end it "focus destination uni on about stay screen is correct" do
result = event_gas.get_gas_by "focus:destinationUniversity"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/about_stay"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "aboutStay"
expect(result.ec).to eql "enquiryFlow"
expect(result.el).to eql "focus:destinationUniversity"
end it "click submit form button on about stay screen is correct" do
result = event_gas.get_gas_by "submitBtnClicked"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/about_stay"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "aboutStay"
expect(result.ec).to eql "enquiryFlow"
expect(result.el).to eql "submitBtnClicked"
end it "enquiry submitted is correct" do
result = event_gas.get_gas_by "cae_srp"
expect(result.dp).to eql "/modal/enquiry/cae_srp/signup/email/enq_submitted"
expect(result.ul).to eql "en-us"
expect(result.ea).to eql "success"
expect(result.ec).to eql "enquiry"
expect(result.el).to eql "cae_srp"
end
end
end end

The End

ruby + phantomjs 自动化测试 - GA的更多相关文章

  1. phantomjs 自动化测试

    最近网站的质量检查越来越严格,原来开发过程中很多隐蔽的问题,逐渐暴露出来,为提高前端的工作效率,就想到是不是可以在开发过程中能自动的对页面的中一些规范化的东西进行自动监测,这个就可以省去不少麻烦. 整 ...

  2. python selenium+phantomJS自动化测试环境

    0x00配置phantomJS 1. 在windows平台下 此种方法是弹浏览器进行自动化测试的. 1.下载谷歌的驱动 https://chromedriver.storage.googleapis. ...

  3. dubbo服务自动化测试搭建

    java实现dubbo的消费者服务编写:ruby实现消费者服务的接口测试:通过消费者间接测试dubbo服务接口的逻辑 内容包括:dubbo服务本地调用环境搭建,dubbo服务启动,消费者部署,脚本编写 ...

  4. web自动化测试(2):选择selenium优势?与PhantomJS/QTP/Monkey对比

    上篇 <web自动化测试(1):再谈UI发展史与UI.功能自动化测试>,自动化测试工具众多, PC端常用的功能自动化测试工具 Selenium:开源工具集,用于回归功能测试或者系统用例说明 ...

  5. 基于Ruby的watir-webdriver自动化测试方案与实施(五)

    接着基于Ruby的watir-webdriver自动化测试方案与实施(四) http://www.cnblogs.com/Javame/p/4164570.html 继续 ... ... 关于特殊控件 ...

  6. 基于Ruby的watir-webdriver自动化测试方案与实施(四)

    接着基于Ruby的watir-webdriver自动化测试方案与实施(三) http://www.cnblogs.com/Javame/p/4159468.html 继续 ... ...   首先回忆 ...

  7. 基于Ruby的watir-webdriver自动化测试方案与实施(三)

    接着基于Ruby的watir-webdriver自动化测试方案与实施(二) http://www.cnblogs.com/Javame/p/4159468.html 继续 ... ...   编写脚本 ...

  8. 基于Ruby的watir-webdriver自动化测试方案与实施(二)

    接着基于Ruby的watir-webdriver自动化测试方案与实施(一) http://www.cnblogs.com/Javame/p/4159360.html 继续 ... ...   回顾 软 ...

  9. 基于Ruby的watir-webdriver自动化测试方案与实施(一)

    基于Ruby的watir-webdriver自动化测试方案与实施(五)   基于Ruby的watir-webdriver自动化测试方案与实施(四)   基于Ruby的watir-webdriver自动 ...

随机推荐

  1. 使用SignalR 2进行服务器广播

    概述 在本教程中,您将创建一个股票代码应用程序,该应用程序代表您希望定期“推送”或广播从服务器到所有连接客户端的通知的实时应用程序.在本教程的第一部分中,您将从头开始创建该应用程序的简化版本.在本教程 ...

  2. NET 集合分页查询

    参数: var list = new List<int>(); // 集合 ; // 总数量 ; // 每页查询数量 第一种: ? totalCount / pageSize : tota ...

  3. .net Framework使用之 MongoDB

    新建Helper using MongoDB.Bson; using MongoDB.Driver; using System; using System.Collections.Generic; u ...

  4. Keil下Debug随笔

    很多时候我们需要通过硬件仿真来调试程序,在仿真时有时候会遇到这样的情况,那就是选择全速运行时,我们的全局变量无法随时更新,而在那设一个断点后发现值是变化的,那么为什么会出现这种情况呢,那就是可能是我们 ...

  5. redis的常用公共方法(2)

    之前已经写过一篇redis公共方法的使用(https://www.cnblogs.com/jhy55/p/7681626.html),可是发现在高并发的时候出现 Unknown reply on in ...

  6. 解决win10不显示coreldraw x4/5/6菜单栏

    1.下载插件 https://pan.baidu.com/s/1nQ1kgUNSD7-9sjUbv5M6XA 复制链接下载插件 2.导入插件到工作区 3.设置工具栏显示 4.把工具栏拖动到菜单栏位置, ...

  7. JEECG(一) 如何配置自己的业务包

    使用自己的业务包开发,不在com.jeecg业务包下 请首先阅读这篇文章[官网] http://www.jeecg.org/forum.php?mod=viewthread&tid=1832& ...

  8. 如何使用socket进行java网络编程(四)

    在上一篇的结尾,提到过用来处理每一个服务端accept到的socket,我们由原来最开始的单线程改成了多线程去处理,但是对每一个接收到的socket都new一个thread去处理,这样效率太低,我们需 ...

  9. JQuery Mobile - 解决动态更新页面内容,CSS失效问题!

    今天编写JQuery Mobile程序,需要对数组数据动态创建,并且每条数据对应一个复选框,于是我很顺利写了一个Demo,当我运行时候发现,和我期望的不一样!复选框确实创建出来了,但是却没有CSS效果 ...

  10. 【文文殿下】【CF724C】Ray Tracing (中国剩余定理)

    题解 我们考虑将棋盘扩大一倍,这样相当于取膜.然后,我们只要对x,y,的位置分类讨论,做四次crt就行.具体细节看文文代码. #include<cstdio> #include<al ...