接入层
简单时序图表示下
1-1 ajax事件
auth.do?method=registerByPhoneForOneshop
在web.xml中找到RxdaiInvokeServlet,
//1. new holder
HttpInvokeHolder invokeHolder = new HttpInvokeHolder(subtime, httpRequest, httpResponse) ;
//2. do invoke
doInvoke(methodName, invokeHolder) ;
// logger.info("response status={}", httpResponse.getStatus()) ;
if(httpResponse.getStatus()==200){
}
1-2 RxdaiInvokeServlet.doInvoke
//1. found service holder
ServiceHolder serviceHolder = InvokeManager.foundServiceHolder(serviceName) ;
if(serviceHolder==null) throw new InvokeNotFoundException("unfound service=["+serviceName+"] @ "+invokerName) ;
//2. found method holder
methodHolder = serviceHolder.getMethodHolder(methodName) ;
if(methodHolder==null) throw new InvokeNotFoundException("unfound method=["+methodName+"] from service=["+serviceName+"] @ "+invokerName) ;
ServiceApiStat.stat(serviceName, methodName);
//check state.
checkState(methodHolder, invokeHolder) ;
if(!invokeHolder.isOk()) return ;
//3. extract invoke args
invokeArgs = HttpInvokeHelper.extractInvokeParameters(methodHolder.getArgsNames(), methodHolder.getArgsMarkers(), invokeHolder.getHttpRequest()) ;
//4. register
InvokeManager.registerInvokeHolder(invokeHolder) ;
//5. do invoke.
methodHolder.getMethod().invoke(serviceHolder.getService(), invokeArgs) ;
1-3 查看对应方法AuthService.registerByPhoneForOneshop
目的是通过远程调用获取RegisterResp (registerDispatch(req, phone);),
注:推荐人id :UCode
//新版一站式理财注册的功能
@Override
public User registerByPhoneForOneshop(String md5Pwd, String phone, String phoneCode, String smsRandom, String channel,String inviteUsername,String inviteUCode) {
try {
// 验证码校验
SMSCodeSupport.checkSMSCode2(SMSCodeType.SMS_REGISTER, phoneCode, smsRandom, InvokeChannel.PC);
//SMSmanages.getSMSInstance().checkValidCode(phoneCode, SMSmanages.SMS_REGISTER, smsRandom);
phone = CommonUtils.emptyIfNull(phone);
if (!ParameterCheckUtils.checkPhone(phone)) {
throw MessageException.newMessageException("手机号码格式错误");
}
int invUserid=getInviteUserid(inviteUCode);
RegisterReq req = new RegisterReq();
{
req.setPhone(phone);
req.setPassword(md5Pwd);
req.setRegisterType(RegisterReq.REGISTER_TYPE_ONESHOP_PHONE);
req.setChannel(channel);
req.setInviteUserName(inviteUsername);
req.setInviteUserid(invUserid);
}
RegisterResp resp = registerDispatch(req, phone);
User user = resp.getUser();
String ip = HttpInvokeHelper.getRequestIP();
LoginSupport.loginForRegister(phone, md5Pwd, ip);
return user;
} catch (Exception e) {
DBClient.getDBClient().rollback();
throw CommonUtils.convertRuntimeException(e);
}
}
1-4 之后registerDispatch远程调用
//新版注册远程调用
private static RegisterResp registerDispatch(RegisterReq req, String lock) {
synchronized (lock.intern()) {
HttpServletRequest request = HttpInvokeHelper.getHttpServletRequest();
String addip = HttpInvokeHelper.getRequestIP() ;
String cpsLable = CookieHelper.getCookie(Constant.ADS_FOLLOW_KEY, request);// 从cookie中获取广告追踪
{
req.setCpsLable(cpsLable);
req.setIp(addip);
req.setEntryChannel(InvokeChannel.PC.name());
}
Map<String, String> map;
try {
map = BeanUtils.describe(req);
} catch (Exception e) {
throw CommonUtils.convertRuntimeException(e);
}
ApiResult<RegisterResp> ar = KeystoneServiceFactory.getService(com.touna.user.api.AuthService.class).register(map);
if (!ar.isOK()) {
throw MessageException.newMessageException(ar.getMessage());
} else if (ar.getResult().getUser() != null && ar.getResult().getUser().getUserid() > 0) {// 注册成功,且已经生成了user记录清掉相关cookie
CookieHelper.clearCookie(Constant.ADS_FOLLOW_KEY, request, HttpInvokeHelper.getHttpServletResponse());
CookieHelper.clearCookie(Constant.CHARGE_CARD_KEY, request, HttpInvokeHelper.getHttpServletResponse());
}
return ar.getResult();
}
}
1-5 具体的还是交给Keystone,调用AuthService.register返回ApiResult
如果成功清掉cookie,不浪费cookie空间,好习惯
KeystoneServiceFactory.getService(com.touna.user.api.AuthService.class).register(map);
1-6 如果成功,之后跳转自动登录到用户中心(LoginSupport.loginForRegister(phone, md5Pwd, ip);)。
//注册时进行登录
public static void loginForRegister(String loginName, String loginPwd, String ip) {
Map<String, Object> holder = new HashMap<>(4, 1.0f) ;
login(loginName, loginPwd, ip, holder) ;
LoginTocken tocken = CommonUtils.get(holder, LoginTocken.LOGIN_TOCKEN_TAG) ;
HttpInvokeHelper.setInvokeResult(tocken);
}
service层
服务层时序示意图
com.*.user.api(对应server是user-server)包下AuthServiceImpl.register
AuthServiceImpl
public ApiResult<RegisterResp> register(Map<String, String> map)
{
final RegisterReq req = (RegisterReq)Bean2MapUtils.populate(map, RegisterReq.class);
return NoTX.call(new TxCallable()
{
public RegisterResp call(DBClient dbClient) throws Exception {
RegisterResp resp = RegisterLogicProvider.getLogic(req).register(dbClient, req);
if ((resp.getUser() != null) && (resp.getUser().getUserid() > 0L))
{
if (StringUtil.isNotEmpty(resp.getUser().getPhone())) {
UserAreaWriter.getInstance().write(resp.getUser());
}
UserLogHelper.logRegister(resp.getUser().getUserid(), req.getRegisterType(), req.toString(), req.getIp());
}
return resp;
}
});
}
2-1 代码简化,目的是返回ApiResult
final RegisterReq req = (RegisterReq)Bean2MapUtils.populate(map, RegisterReq.class);
return NoTX.call(new TxCallable() resp);
2-1-1 NoTX.call方法如下
public static <T> ApiResult<T> call(TxCallable<T> handler)
{
ApiResult result = new ApiResult();
DBClient dbClient = DBClientFactory.getDefaultDBClient();
try {
Object t = handler.call(dbClient);
result.setResult(t);
} catch (MessageException e) {
return result.error(e.getMessage());
} catch (Throwable e) {
logger.info("NoTx call() with:", e);
throw CommonUtils.convertRuntimeException(e);
}
return result.ok();
}
2-1-2 重点还是handler.call。
TxCallable里也没有实现,只是接口,handler.call这里写法没见过,回调? handler.call在AuthServiceImpl.register里;
public RegisterResp call(DBClient dbClient) throws Exception {
RegisterResp resp = RegisterLogicProvider.getLogic(req).register(dbClient, req);
if ((resp.getUser() != null) && (resp.getUser().getUserid() > 0L))
{
if (StringUtil.isNotEmpty(resp.getUser().getPhone())) {
UserAreaWriter.getInstance().write(resp.getUser());
}
UserLogHelper.logRegister(resp.getUser().getUserid(), req.getRegisterType(), req.toString(), req.getIp());
}
return resp;
}
RegisterLogic依次用到如下几个类
2-1-2-1 RegisterLogicProvider
RegisterLogicProvider.getLogic(req).register(dbClient, req);
先看看RegisterLogicProvider类中的getLogic方法,是从req中取出注册类型,这里是手机注册,
public class RegisterLogicProvider {
public static IRegisterLogic getLogic(RegisterReq req) {
IRegisterLogic logic = map.get(req.getRegisterType());
if (logic == null) {
throw CommonUtils.illegalStateException("Can not found suitable register logic for register type=" + req.getRegisterType());
}
return logic;
}
}
2-1-2-1-2 看看IRegisterLogic,
是抽象接口,没有register的实现,继续找发现AbstractRegisterLogic里实现了register,负责对RegisterResp 的处理,其他具体regiserLogic继承AbstractRegisterLogic
regiserLogic类的关系
继续看AbstractRegisterLogic类
public abstract class AbstractRegisterLogic
implements IRegisterLogic{
protected Logger logger = LoggerFactory.getLogger(getClass());
public RegisterResp register(DBClient dbClient, RegisterReq req)
{
this.logger.debug("Begin register user! RegisterReq={}", req);
Date now = SystemTime.getNow();
User u = returnUser(dbClient, now, req);
UserCheckField[] fields = getCheckFields(req);
UserCheckHelper.checkParameter(fields, u);
checkUserMore(u);
UserCheckHelper.checkDuplicate(dbClient, fields, u);
RegisterResp resp = doRegister(dbClient, req, u, now);
this.logger.debug("End register user! RegisterResp={}", resp.getUser());
return resp;
}
private User returnUser(DBClient dbClient, Date now, RegisterReq req)
{
//见后文
}
protected void checkUserMore(User u)
{
}
protected void assemble(DBClient dbClient, RegisterReq req, User user)
{
//见后文
}
protected RegisterResp doRegister(DBClient dbClient, RegisterReq req, User u, Date now)
{
//见后文
}
}
2-1-2-1-2-1 关于AbstractRegisterLogic.register方法,简化下代码分几步处理的
//1 先得到user对象
User u = returnUser(dbClient, now, req);
//2 检查user对象
UserCheckHelper.*(dbClient, fields, u);
//3 进行注册得到RegisterResp 对象
RegisterResp resp = doRegister(dbClient, req, u, now);
看下AbstractRegisterLogic.returnUser,返回值为user
private User returnUser(DBClient dbClient, Date now, RegisterReq req)
{
User user = new User();
user.setChannel(req.getChannel());
user.setPassword(req.getPassword());
assemble(dbClient, req, user);
Pair pair = InviteHelper.getInviteInfo(dbClient, req);
if (((Integer)pair.getFirst()).intValue() == 0) {
pair = InviteHelper.getInviteInfoWithPreReg(dbClient, req.getPhone());
}
user.setInviteUserid(((Integer)pair.getFirst()).intValue());
req.setInviteUserName((String)pair.getSecond());
if (user.getChannel() == null) {
user.setChannel("");
}
String time = TimeUtils.stringOfUnixtimestamp(now);
String ip = req.getIp();
user.setTypeid(2);
user.setCreditRating(3);
user.setAddtime(time);
user.setAddip(ip);
user.setUptime(time);
user.setUpip(ip);
user.setLasttime(time);
user.setLastip(ip);
user.setQq(req.getQq() == null ? "" : req.getQq());
return user;
}
调用了AbstractRegisterLogic.assemble(dbClient, req, user);这里未用到dbClient
protected void assemble(DBClient dbClient, RegisterReq req, User user)
{
for (UserCheckField field : getCheckFields(req))
UserFieldProcessorProvider.getProcessor(field).setValueToUser(req, user);
}
其中UserFieldProcessorProvider:用户字段校验器提供类 ,看下UserFieldProcessorProvider.getProcessor方法。
public static UserFieldProcessor getProcessor(UserCheckField field) {
UserFieldProcessor processor = map.get(field);
if (processor == null) {
throw CommonUtils.illegalStateException("Can not found suitable user field processor for field type=" + field);
}
return processor;
}
UserFieldProcessor:用户字段类,抽象接口中有setValueToUser方法,实现往下找
public abstract interface UserFieldProcessor
{
//其他方法略,见文末
public abstract void setValueToUser(RegisterReq paramRegisterReq, User paramUser);
}
参考loginType的设计,AbstractUserFieldProcessor实现抽象接口,其他具体用户字段如PhoneProcessor 再继承AbstractUserFieldProcessor并分别重写setValueToUser,代码举例如下。
public abstract class AbstractUserFieldProcessor
implements UserFieldProcessor
public class PhoneProcessor extends AbstractUserFieldProcessor(){
public void setValueToUser(RegisterReq req, User u)
{
u.setPhone(req.getPhone());
u.setPhoneStatus(1);
}
}
UserField类的关系
assemble操作是把req中的参数填充到user中,继续
Pair pair = InviteHelper.getInviteInfo(dbClient, req);
其中InviteHelper:注册时,邀请信息处理帮助类,这里操作是对有邀请码的用户,我这里没有邀请码,具体InviteHelper.getInviteInfo的code见文末。
2-1-2-3-1-2 检查用户,略
2-1-2-3-1-3 注册AbstractRegisterLogic.doRegister,
protected RegisterResp doRegister(DBClient dbClient, RegisterReq req, User u, Date now)
{
int uid = UserPersistHelper.persistUser(dbClient, u);
UserPersistHelper.persistUserExtend(dbClient, uid, u.getInviteUserid(), req.getInviteUserName(), req.getEntryChannel());
UserPersistHelper.persistAccount(dbClient, uid);
UserPersistHelper.persistNewbie(dbClient, uid);
AuthActive active = UserPersistHelper.dealEmailAuth(dbClient, u, now);
UserPersistHelper.dealAdsFollow(dbClient, u, req.getCpsLable());
UserPersistHelper.activeChargeCard(dbClient, uid, req.getChargeCard());
SinglesDayUtils.registerSuccess(dbClient, u, req.getIp());
return new RegisterResp(u, active, now);
}
关于UserPersistHelper:用户持久化帮助类
方法简述如下,例UserPersistHelper.persistUser,其他持久化操作间文末。
//持久化用户记录 见dw_user表
public static int persistUser(DBClient dbClient, User u) {
//存在尚未认证的邮箱
boolean ignorePersitEmail = existUnauthorizedEmail(u);
//持久化前,把用户的邮箱信息置空
String email = u.getEmail();
if (ignorePersitEmail) {
u.setEmail(null);
}
//持久化
int uid = (int) dbClient.insertObject(u);
u.setUserid(uid);
//持久化后,把用户的邮箱信息加上
if (ignorePersitEmail) {
u.setEmail(email);
}
return uid;
}
上述是注册用户的持久化操作,继续看
AuthActive active = UserPersistHelper.dealEmailAuth(dbClient, u, now);
AuthActive 是认证激活类,判断邮箱是否需要激活,更新持久化。
继续UserPersistHelper.dealAdsFollow(dbClient, u, req.getCpsLable());删除广告
UserPersistHelper.activeChargeCard(dbClient, uid, req.getChargeCard());激活充值卡,活动结束后屏蔽掉。
马上要注册成功了!SinglesDayUtils.registerSuccess(dbClient, u, req.getIp());
关于SinglesDayUtils:各种优惠和奖励在这个里面操作。节日过后就删除。这种做法耦合较高,目前做法是直接更新接口,少改user-server包。
registerSuccess方法会做一些过滤,最后添加未过期的活动给用户。
public static void registerSuccess(DBClient dbClient, User user, String ip) {
if (user != null) {
long addtime = Long.parseLong(user.getAddtime());
// 活动期间,手机注册用户,送1万体验金
boolean organBlacklist = GrayManager.isGray(Constants.GRAY_KEY_ORGANIZATION_BLACKLIST, user.getUserid());//资金机构黑名单
if (isInactiviyTime(addtime) && user.getPhoneStatus() == User.USER_AUDIT_STATUS_NOW && !organBlacklist) {
if (filterUserByUtmSource(dbClient, user.getUserid(), addtime)) {
logger.info("filterUserByUtmSource uid={}, addtime={}", new Object[]{user.getUserid(), addtime});
return;
}
addVirtualCash(dbClient, user.getUserid(), 9, 10000, addtime, ip);
}
}
}
addVirtualCash:查询那些活动适用,写入数据库。
2-1-2-2 TxCallable.call UserAreaWriter
UserAreaWriter 没有write方法,继续找其父类AutoWriter,可以看到write是将resp.getUser()插入队列未,这里用队列是为了提高速度(用户体验):把不需要立即执行的动作放到消息队列,优先完成其他操作,在高并发模式下可以提高速度
private final Queue<T> queue = new ConcurrentLinkedQueue();
public abstract class AutoWriter<T>
{
public void write(T t) {
logger.info("Queue offer: " + t.toString());
this.queue.offer(t);
}
}
2-1-2-3 UserLogHelper
UserLogHelper是记录日志操作类,logRegister方法是记录注册操作日志,不做代码展示。
最后LoginSupport.loginForRegister
HttpInvokeHelper.setInvokeResult(tocken);
续-如何判断新注册用户的?
后台暂时没找到,前端对比发现如下请求
Request URL:http://oneshop.touna.cn/account.oneshop.do?method=loadBasicUserInfo&subtime=1461584120258
web.xml中找下account.oneshop.do对应servlet没变,好吧
找到OneshopAccountSupport中有loadBasicUserInfo方法
account.oneshop.do有2次,第一次没有参数,第二次有新手引导的信息,推测是ajax判断。
持久层
3-1 DBClient 只是封装,继续追
public abstract interface DBClient extends MapperClientApi
{
}
3-2 MapperClientApi
public abstract interface MapperClientApi extends BaseClientApiExt
{
public abstract <T> T findObjectValue(Class<?> paramClass, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract <T> T findObjectFields(Class<?> paramClass, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract <T> T findObject(Class<?> paramClass, String paramString, Object[] paramArrayOfObject);
public abstract <T> T lockObjectFields(Class<T> paramClass, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract <T> T lockObject(Class<T> paramClass, String paramString, Object[] paramArrayOfObject);
public abstract <E> List<E> queryObjectField(Class<?> paramClass, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract <T> List<T> queryObjectFields(Class<T> paramClass, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract <T> List<T> queryObject(Class<T> paramClass, String paramString, Object[] paramArrayOfObject);
public abstract <T> long countObject(Class<T> paramClass, String paramString, Object[] paramArrayOfObject);
public abstract <T> int updateObjectFields(Class<T> paramClass, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract int updateObjectFields(Object paramObject, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract int updateObject(Object paramObject, String paramString, Object[] paramArrayOfObject);
public abstract long insertObject(Object paramObject);
public abstract long insertObjectFields(Object paramObject, String paramString);
public abstract int deleteObject(Class<?> paramClass, String paramString, Object[] paramArrayOfObject);
public abstract <T> int[] batchUpdateObjects(String paramString1, String paramString2, List<T> paramList);
public abstract <T> int[] batchUpdateClasses(Class<T> paramClass, String paramString1, String paramString2, List<Object[]> paramList);
public abstract <T> int[] batchInsertObjects(List<T> paramList);
public abstract <T> int[] batchInsertObjectFields(String paramString, List<T> paramList);
public abstract <T> int insertOnDuplicateKey(Object paramObject, String paramString1, String paramString2, Object[] paramArrayOfObject);
public abstract <T> int[] batchOnDuplicateKey(String paramString1, String paramString2, List<T> paramList);
public abstract <T> int[] batchOnDuplicateKeyUpdate(String paramString1, String paramString2, List<T> paramList, List<Object[]> paramList1);
}
未用到的代码块在这里
TxCallable
public abstract interface TxCallable<T>
{
public abstract T call(DBClient paramDBClient)
throws Exception;
}
UserAreaWriter
/**
* 根据手机号获取省市区,并写入user表
*/
public class UserAreaWriter extends AutoWriter<User> {
private static UserAreaWriter INSTANCE = new UserAreaWriter();
public static UserAreaWriter getInstance() {
return INSTANCE;
}
public UserAreaWriter() {
super();
}
@Override
public void doWrite() {
List<User> users = pollAll();
if (!users.isEmpty()) {
for (User user : users) {
//根据手机号更新省市县信息
if (user != null && StringUtils.isNotBlank(user.getPhone()) && user.getUserid() > 0) {//手机号不为空
try {
UserLocaleHelper.updateLocaleInfoByPhone(DBClientAccessor.getTounaClient(), (int) user.getUserid(), user.getPhone());
} finally {
DBClientFactory.releaseClient();
}
}
}
}
}
UserFieldProcessor
public abstract interface UserFieldProcessor
{
public abstract void checkParameter(User paramUser);
public abstract void checkDuplicate(DBClient paramDBClient, User paramUser);
public abstract void checkParameter(String paramString);
public abstract void checkDuplicate(DBClient paramDBClient, String paramString);
public abstract void checkDuplicateExceptUid(DBClient paramDBClient, String paramString, int paramInt);
public abstract UserCheckField getCheckFiled();
public abstract String getValueFromUser(User paramUser);
public abstract void setValueToUser(RegisterReq paramRegisterReq, User paramUser);
}
InviteHelper
public class InviteHelper
{
private static final Pair<Integer, String> EMPTY = new Pair(Integer.valueOf(0), "");
//获取当前邀请人的信息
public static Pair<Integer, String> getInviteInfo(DBClient dbClient, RegisterReq req)
{
int uid = req.getInviteUserid();
if (uid > 0) {
User u = (User)dbClient.findObjectFields(User.class, "userid,username", "userid=?", new Object[] { Integer.valueOf(uid) });
if (u != null) {
return fromInviteUser(u);
}
}
String uname = req.getInviteUserName();
if (StringUtil.isEmpty(uname)) {
return EMPTY;
}
User u = UserHelper.findUserByUsername(dbClient, uname, "userid");
if (u != null) {
return fromInviteUser(u);
}
if (ParameterCheckUtils.checkPhone(uname)) {
u = UserHelper.findUserByPhone(dbClient, uname, "userid");
if (u != null) {
return fromInviteUser(u);
}
}
if (ParameterCheckUtils.checkEmail(uname)) {
u = UserHelper.findUserByEmail(dbClient, uname, "userid");
if (u != null) {
return fromInviteUser(u);
}
}
return EMPTY;
}
}
UserPersistHelper.persistUserExtend
// 持久化UserExtend记录,如果存在邀请人,更新邀请人已邀请的人数统计
public static void persistUserExtend(DBClient dbClient, int uid, int inviteUid, String inviteUname) {
UserExtend userExtend = new UserExtend();
userExtend.setUserid(uid);
if (inviteUid > 0) {
userExtend.setInviteUserid(inviteUid);
userExtend.setInviteUserName(inviteUname);
}
dbClient.insertObject(userExtend);
//更新推荐人的总推荐人数(+1)
if (inviteUid > 0) {
dbClient.update("update dw_user_extend set total_invite_user = total_invite_user + 1 where user_id =?", inviteUid);
}
}
UserPersistHelper.persistAccount
public static void persistAccount(DBClient dbClient, int uid) {
Account account = new Account();
account.setUserid(uid);
dbClient.insertObject(account);
}
UserPersistHelper.persistNewbie
public static void persistNewbie(DBClient dbClient, int uid) {
Newbie newbie = new Newbie();
newbie.setUserid(uid);
dbClient.insertObject(newbie);
}
IRegisterLogic
public abstract interface IRegisterLogic
{
public abstract RegisterResp register(DBClient paramDBClient, RegisterReq paramRegisterReq);
public abstract UserCheckField[] getCheckFields(RegisterReq paramRegisterReq);
public abstract int getRegisterType();
}