需求:
對於任何一個用戶量極大的系統來說做好系統保護是非常有必要的,系統保護可以用服務降級、限流、緩存等方式實現。在最近做的一個需求中需要對一些短時間內訪問量很大的用戶(主要是針對爬蟲)做限流。在一定時間內聚合一次用戶訪問次數,超過閾值則需要啓動限流措施了。限流的實現方式用很多種,我爲什麼要用storm進行實時計算用戶訪問次數做限流呢?其實是處於以後需求考慮,做限流只是其中一部分功能。後面還會使用storm用於用戶行爲分析。
基本概念(需要了解strom的同學可以查看相關教程,本次只講實踐):
Storm 分佈式計算結構稱爲 topology(拓撲),由 stream(數據流), spout(數據流的生成者), bolt(運算)組成。
Storm 的核心數據結構是 tuple。 tuple是 包 含 了 一 個 或 者 多 個 鍵 值 對 的 列 表,Stream 是 由 無 限 制 的 tuple 組 成 的 序 列。
spout 代表了一個 Storm topology 的主要數據入口,充當採集器的角色,連接到數據源,將數據轉化爲一個個 tuple,並將 tuple 作爲數據流進行發射。
bolt 可以理解爲計算程序中的運算或者函數,將一個或者多個數據流作爲輸入,對數據實施運算後,選擇性地輸出一個或者多個數據流。 bolt 可以訂閱多個由 spout 或者其他bolt 發射的數據流,這樣就可以建立複雜的數據流轉換網絡。
整體流程示意圖:
strom top圖:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kpy.storm</groupId>
<artifactId>kpy.storm</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.storm/storm-core -->
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources/</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
spout用於從QMQ中消費用戶數據,QMQ是去哪兒開源的一款MQ,有興趣的同學可以瞭解下。由於公司部分項目沒有開源,此處demo用沒有貼出實現消費QMQ的部分,只用固定字符串數據用於模擬,以供學習交流。
UserInfoSpout
package com.kpy.storm.spout;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;
import java.util.Map;
/**
* @Package: com.kpy.storm.spout
* @ClassName: UserInfoSpout
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class UserInfoSpout extends BaseRichSpout {
/**
* BaseRichSpout是ISpout接口和IComponent接口的簡單實現,接口對用不到的方法提供了默認的實現
*/
private SpoutOutputCollector collector;
private String[] sentences = {
"{\"allianceId\":\"\",\"allianceName\":\"\",\"clientID\":\"09031141310261798371\",\"cticket\":\"9E41E8C6812C4F597F18BF2330E796BA54D709F9F39588029E950F1098FC59C1\",\"id\":\"2d1b86be-0cc0-41a0-96de-399959d84741\",\"interval\":0,\"login\":true,\"loginType\":\"MemberLogin\",\"ouid\":\"\",\"pageId\":\"212094\",\"referer\":\"http://m.ctrip.fat0.qa.nt.ctripcorp.com:15389/webapp/hotel/hoteldetail/387233.html?atime=20190115&daylater=0&days=1&contrl=0&pay=0&discount=&latlon=&listindex=3&userLocationSearch=false&ucity=2\",\"remoteIPAddress\":\"10.32.151.27\",\"requestMethod\":\"POST\",\"requestURL\":\"http://m.ctrip.fat0.qa.nt.ctripcorp.com:15389/webapp/hotel/api/static/abresult\",\"serviceCode\":\"ABTestController.abresultGet\",\"sessionId\":\"219\",\"sid\":\"\",\"sourceId\":\"\",\"startTime\":1547553924585,\"systemCode\":\"09\",\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"userCityId\":2,\"userId\":\"18100000000\",\"vid\":\"1545130759191.dnb0yy\"}"
};
private int index=0;
private int count=1;
/**
* open()方法中是ISpout接口中定義,在Spout組件初始化時被調用。
* open()接受三個參數:一個包含Storm配置的Map,一個TopologyContext對象,提供了topology中組件的信息,SpoutOutputCollector對象提供發射tuple的方法。
* 在這個例子中,我們不需要執行初始化,只是簡單的存儲在一個SpoutOutputCollector實例變量。
*/
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
}
/**
* nextTuple()方法是任何Spout實現的核心。
* Storm調用這個方法,向輸出的collector發出tuple。
* 在這裏,我們只是發出當前索引的句子,並增加該索引準備發射下一個句子。
*/
public void nextTuple() {
this.collector.emit(new Values(sentences[0]));
// index++;
// if (index>=sentences.length) {
// index=0;
// }
System.out.println("第"+count+"次發射");
count ++;
Utils.sleep(1000);
}
/**
* declareOutputFields是在IComponent接口中定義的,所有Storm的組件(spout和bolt)都必須實現這個接口
* 用於告訴Storm流組件將會發出那些數據流,每個流的tuple將包含的字段
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//告訴組件發出數據流包含sentence字段
declarer.declare(new Fields("userInfo"));
}
}
splitbot主要用於解析切分用戶數據,聚合維度有clientID、userId、ip、serviceCode,分別表示用戶客戶端ID \用戶id、ip、用戶訪問接口。
UserInfoSplitBot
package com.kpy.storm.bolt;
import com.alibaba.fastjson.JSON;
import com.kpy.storm.model.RequestContextUserInfo;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import java.util.Map;
import static com.kpy.storm.model.CommonConstant.*;
/**
* @Package: com.kpy.storm.bolt
* @ClassName: UserInfoSplitBot
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class UserInfoSplitBot extends BaseRichBolt {
//BaseRichBolt是IComponent和IBolt接口的實現
//繼承這個類,就不用去實現本例不關心的方法
private OutputCollector collector;
/**
* prepare()方法類似於ISpout 的open()方法。
* 這個方法在blot初始化時調用,可以用來準備bolt用到的資源,比如數據庫連接。
* 本例子和SentenceSpout類一樣,SplitSentenceBolt類不需要太多額外的初始化,
* 所以prepare()方法只保存OutputCollector對象的引用。
*/
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector=collector;
}
/**
* SplitSentenceBolt核心功能是在類IBolt定義execute()方法,這個方法是IBolt接口中定義。
* 每次Bolt從流接收一個訂閱的tuple,都會調用這個方法。
* 本例中,收到的元組中查找“sentence”的值,
* 並將該值拆分成單個的詞,然後按單詞發出新的tuple。
*/
public void execute(Tuple input) {
String userInfo = input.getStringByField("userInfo");
RequestContextUserInfo contextUserInfo = JSON.parseObject(userInfo, RequestContextUserInfo.class);
//向下一個bolt發射數據
if (contextUserInfo != null) {
collector.emit(new Values(contextUserInfo.getClientID(), contextUserInfo.getUserId(),contextUserInfo.getRemoteIPAddress(),contextUserInfo.getServiceCode() ));
}
}
/**
* plitSentenceBolt類定義一個元組流,每個包含一個字段(“clientID”)。
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields(FILED_CLIENT_ID, FILED_USER_ID, FILED_IP,FILED_SERVICE_CODE));
}
}
countbot用戶統計用戶瀏覽次數。
UserInfoCountBot
package com.kpy.storm.bolt;
import org.apache.storm.Config;
import org.apache.storm.Constants;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import static com.kpy.storm.model.CommonConstant.*;
/**
* @Package: com.kpy.storm.bolt
* @ClassName: UserInfoCountBot
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class UserInfoCountBot extends BaseRichBolt {
private OutputCollector collector;
private static final Logger logger = LoggerFactory.getLogger(UserInfoCountBot.class);
private volatile boolean CHANGE_MAP = false;
private Map<String, Long> COUNT_MAP_0 = new HashMap<>();
private Map<String, Long> COUNT_MAP_1 = new HashMap<>();
/**Bolt定時時間 單位秒*/
private long tickTupleTime = 600;
/**
* 大部分實例變量通常是在prepare()中進行實例化,這個設計模式是由topology的部署方式決定的
* 因爲在部署拓撲時,組件spout和bolt是在網絡上發送的序列化的實例變量。
* 如果spout或bolt有任何non-serializable實例變量在序列化之前被實例化(例如,在構造函數中創建)
* 會拋出NotSerializableException並且拓撲將無法發佈。
* 本例中因爲HashMap 是可序列化的,所以可以安全地在構造函數中實例化。
* 但是,通常情況下最好是在構造函數中對基本數據類型和可序列化的對象進行復制和實例化
* 而在prepare()方法中對不可序列化的對象進行實例化。
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
/**
* 在execute()方法中,我們查找的收到的單詞的計數(如果不存在,初始化爲0)
* 然後增加計數並存儲,發出一個新的詞和當前計數組成的二元組。
* 發射計數作爲流允許拓撲的其他bolt訂閱和執行額外的處理。
*/
@Override
public void execute(Tuple input) {
try {
//該處定義了兩個Map用於存放count數據
//如果是系統發射的時鐘tuple, 則切換map然後發射到下一個節點,再清空當前map
if (isTickTuple(input)) {
if (!CHANGE_MAP) {
logger.debug("clearMapAndSendTuple-COUNT_MAP_0--"+"CHANGE_MAP="+CHANGE_MAP);
CHANGE_MAP = true;
clearMapAndSendTuple(COUNT_MAP_0);
} else {
logger.debug("clearMapAndSendTuple-COUNT_MAP_1--"+"CHANGE_MAP="+CHANGE_MAP);
CHANGE_MAP = false;
clearMapAndSendTuple(COUNT_MAP_1);
}
return;
}
String clientID = input.getStringByField(FILED_CLIENT_ID);
String userId = input.getStringByField(FILED_USER_ID);
String ip = input.getStringByField(FILED_IP);
String serviceCode = input.getStringByField(FILED_SERVICE_CODE);
String unKey = clientID + FILED_SPLIT + userId + FILED_SPLIT + ip+ FILED_SPLIT + serviceCode;
if (CHANGE_MAP) {
countNum(COUNT_MAP_1, unKey);
} else {
countNum(COUNT_MAP_0, unKey);
}
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
}
private void clearMapAndSendTuple(Map<String, Long> map) {
try {
map.forEach((key, value) -> collector.emit(new Values(key, value)));
map.clear();
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
}
private void countNum(Map<String, Long> map, String unKey) {
Long count = map.get(unKey);
if (count == null) {
//如果不存在,初始化爲0
count = 0L;
}
//增加計數
count++;
//存儲計數
map.put(unKey, count);
}
@Override
public Map<String, Object> getComponentConfiguration() {
Map<String, Object> conf = new HashMap<String, Object>(1);
// 設置本Bolt定時發射數據(所以這個地方我們可以偷偷地進行某些定時處理)
conf.put(Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS, tickTupleTime);
return conf;
}
/**
* 根據傳送過來的Tuple,判斷本Tuple是否是tickTuple 如果是tickTuple,則觸發動作
*
* @param tuple
* @return
*/
public boolean isTickTuple(Tuple tuple) {
return tuple.getSourceComponent().equals(Constants.SYSTEM_COMPONENT_ID)
&& tuple.getSourceStreamId().equals(Constants.SYSTEM_TICK_STREAM_ID);
}
/**
*
*/
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//聲明一個輸出流,其中tuple包括了單詞和對應的計數,向後發射
//其他bolt可以訂閱這個數據流進一步處理
declarer.declare(new Fields(FILED_UN_KEY, FILED_COUNT));
}
}
輸出bot
UserReportBolt
package com.kpy.storm.bolt;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import static com.kpy.storm.model.CommonConstant.*;
/**
* @Package: com.kpy.storm.bolt
* @ClassName: UserReportBolt
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class UserReportBolt extends BaseRichBolt {
private static final Logger logger = LoggerFactory.getLogger(UserReportBolt.class);
private boolean needSendMessage = NEED_SEND_MESSAGE;
private long maxCountNum = MAX_COUNT_NUM;
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
}
/**
* 這裏作主要業務處理,如將數據存入DB hive HBASE 或者REDIS都可以
* 本需求是將消息發送QMQ
* @param input
*/
@Override
public void execute(Tuple input) {
try {
String unKey = input.getStringByField(FILED_UN_KEY);
Long count = input.getLongByField(FILED_COUNT);
if (needSendMessage && count != null && count >= maxCountNum) {
//超過閾值 發送警戒消息
// QmqUtil.sendMessage(SEND_QMQ_MESSAGE_SUBJECT, unKey + FILED_SPLIT + count, USER_UN_MESSAGE_TAG);
}
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//這裏是末端bolt,不需要發射數據流,這裏無需定義
}
}
userinfoapp 是系統啓動類,在這裏定義top圖。
UserInfoApp
package com.kpy.storm;
import com.kpy.storm.bolt.UserInfoCountBot;
import com.kpy.storm.bolt.UserInfoSplitBot;
import com.kpy.storm.bolt.UserReportBolt;
import com.kpy.storm.spout.UserInfoSpout;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
import org.apache.storm.utils.Utils;
import static com.kpy.storm.model.CommonConstant.*;
/**
* @Package: com.kpy.storm
* @ClassName: UserInfoApp
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class UserInfoApp {
private static final String SENTENCE_SPOUT_ID = "sentence-spout";
private static final String SPLIT_BOLT_ID = "split-bolt";
private static final String COUNT_BOLT_ID = "count-bolt";
private static final String REPORT_BOLT_ID = "report-bolt";
private static final String TOPOLOGY_NAME = "user-count-topology";
public static void main(String[] args) throws Exception{
//實例化spout和bolt
UserInfoSpout spout = new UserInfoSpout();
UserInfoSplitBot splitBolt = new UserInfoSplitBot();
UserInfoCountBot countBolt = new UserInfoCountBot();
UserReportBolt reportBolt = new UserReportBolt();
//創建了一個TopologyBuilder實例
TopologyBuilder builder = new TopologyBuilder();
//TopologyBuilder提供流式風格的API來定義topology組件之間的數據流
//設置兩個Executeor(線程),默認一個
builder.setSpout(SENTENCE_SPOUT_ID, spout,1);
//註冊一個bolt並訂閱sentence發射出的數據流,shuffleGrouping方法告訴Storm要將spout發射的tuple隨機均勻的分發給splitBolt的實例
//SplitBolt分割器設置4個Task,4個Executeor(線程)
builder.setBolt(SPLIT_BOLT_ID, splitBolt, 4).setNumTasks(4).shuffleGrouping(SENTENCE_SPOUT_ID);
//fieldsGrouping將含有特定數據的tuple路由到特殊的bolt實例中
//這裏fieldsGrouping()方法保證所有“FILED_CLIENT_ID, FILED_USER_ID, FILED_IP, FILED_SERVICE_CODE”字段相同的tuuple會被路由到同一個CountBolt實例中
//CountBolt單詞計數器設置4個Executeor(線程)
builder.setBolt(COUNT_BOLT_ID, countBolt, 4).fieldsGrouping(SPLIT_BOLT_ID, new Fields(FILED_CLIENT_ID, FILED_USER_ID, FILED_IP, FILED_SERVICE_CODE));
//globalGrouping是把UserInfoCountBot發射的所有tuple路由到唯一的UserReportBolt
builder.setBolt(REPORT_BOLT_ID, reportBolt).globalGrouping(COUNT_BOLT_ID);
//Config類是一個HashMap<String,Object>的子類,用來配置topology運行時的行爲
Config config = new Config();
//設置worker數量
////config.setNumWorkers(2);
// if (args != null && args.length > 0 && "local".equalsIgnoreCase(args[0])) {
LocalCluster cluster = new LocalCluster();
//本地提交
cluster.submitTopology(TOPOLOGY_NAME, config, builder.createTopology());
Utils.sleep(10000);
cluster.killTopology(TOPOLOGY_NAME);
cluster.shutdown();
// } else {
// StormSubmitter.submitTopology(TOPOLOGY_NAME, config, builder.createTopology());
// }
}
}
常量類
CommonConstant
package com.kpy.storm.model;
/**
* @Package: com.ctrip.muise.jstorm.constant
* @ClassName: CommonConstant
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class CommonConstant {
public static final String LOCAL = "local";
/**發送限流信息subject**/
public static final String SEND_QMQ_MESSAGE_SUBJECT = "hotel.wireless.h5.visit.statistic";
/**消費subject**/
public static final String TOPIC_NAME = "hotel.wireless.h5.visit";
public static final String USER_UN_MESSAGE_TAG = "userInfoTag";
public static final String FILED_SPLIT = ":";
public static final String FILED_MESSAGE = "message";
public static final String FILED_USER_INFO= "userInfo";
public static final String FILED_CLIENT_ID = "clientID";
public static final String FILED_USER_ID = "userId";
public static final String FILED_UN_KEY = "unKey";
public static final String FILED_COUNT= "count";
public static final String FILED_IP = "ip";
public static final String FILED_SERVICE_CODE = "serviceCode";
/****************************TOP配置項*********************************************************/
public static final String MESSAGE_SPOUT = "hotel_wireless_h5_message_spout";
public static final String SPLIT_BOLT_ID = "hotel_wireless_h5_split_bolt";
public static final String QMQ_BOLT_ID = "hotel_wireless_h5_qmq_bolt";
public static final String COUNT_BOLT_ID = "hotel_wireless_h5_count_bolt";
public static final String REPORT_BOLT_ID = "hotel_wireless_h5_message_bolt";
public static final String TOPOLOGY_NAME = "hotel_wireless_h5_user_topology";
public static final String SPOUT_ID = TOPOLOGY_NAME + "_" + MESSAGE_SPOUT;
/****************************TOP配置項*********************************************************/
/****************************平臺自定義配置項*********************************************************/
/**Bolt定時時間 單位秒*/
public static final long TOPOLOGY_TICK_TUPLE_TIME = 6L;
public static final String H5_TOPOLOGY_TICK_TUPLE_TIME = "h5.topology.tick.tuple.time";
/*** 限流次數**/
public static final long MAX_COUNT_NUM = 1000L;
public static final String MAX_COUNT_NUM_ = "max.count.num";
/**是否需要發送限流信息開關*/
public static final boolean NEED_SEND_MESSAGE = true;
public static final String NEED_SEND_MESSAGE_SWITCH = "need.send.message.switch";
/****************************平臺自定義配置項*********************************************************/
}
RequestContextUserInfo
package com.kpy.storm.model;
import java.io.Serializable;
/**
* @Package:
* @ClassName: UserRequestInfo
* @Description:
* @Author: yekeping
* @Version: 1.0
*/
public class RequestContextUserInfo implements Serializable{
private String clientID;
private String vid;
private String sessionId;
private String systemCode;
private long interval;
private String serviceCode;
private String requestMethod;
private String requestURL;
private String remoteIPAddress;
private String id;
private String userAgent;
private String referer;
private String sourceId;
private String allianceId;
private String allianceName;
private String sid;
private String ouid;
private String pageId;
private String result;
private String cticket;
private long startTime;
private String userId;
private int userCityId;
private boolean login;
private String loginType;
public String getClientID() {
return clientID;
}
public void setClientID(String clientID) {
this.clientID = clientID;
}
public String getVid() {
return vid;
}
public void setVid(String vid) {
this.vid = vid;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getSystemCode() {
return systemCode;
}
public void setSystemCode(String systemCode) {
this.systemCode = systemCode;
}
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
public String getServiceCode() {
return serviceCode;
}
public void setServiceCode(String serviceCode) {
this.serviceCode = serviceCode;
}
public String getRequestMethod() {
return requestMethod;
}
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
public String getRequestURL() {
return requestURL;
}
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public String getRemoteIPAddress() {
return remoteIPAddress;
}
public void setRemoteIPAddress(String remoteIPAddress) {
this.remoteIPAddress = remoteIPAddress;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public String getReferer() {
return referer;
}
public void setReferer(String referer) {
this.referer = referer;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public String getAllianceId() {
return allianceId;
}
public void setAllianceId(String allianceId) {
this.allianceId = allianceId;
}
public String getAllianceName() {
return allianceName;
}
public void setAllianceName(String allianceName) {
this.allianceName = allianceName;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getOuid() {
return ouid;
}
public void setOuid(String ouid) {
this.ouid = ouid;
}
public String getPageId() {
return pageId;
}
public void setPageId(String pageId) {
this.pageId = pageId;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getCticket() {
return cticket;
}
public void setCticket(String cticket) {
this.cticket = cticket;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public int getUserCityId() {
return userCityId;
}
public void setUserCityId(int userCityId) {
this.userCityId = userCityId;
}
public boolean isLogin() {
return login;
}
public void setLogin(boolean login) {
this.login = login;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}