-
关于多设备登录的思考与实现
2019-12-16 17:23:531.需求: 不同系统(pc,Android,ios)可以同时登录,同种系统(pc与pc,Android与Android,ios与ios)形成...移动端也可以在请求头中添加指定字段确认是什么系统。 2)怎么保证单点? 需要保存已登录的系统的...1.需求:
不同系统(pc,Android,ios)可以同时登录,同种系统(pc与pc,Android与Android,ios与ios)形成登录互斥。
2.多系统单点登录存在的问题:
1)怎么区分系统?
可以通过request中User-Agent获取设备信息,判断是否存在系统关键词区分系统;移动端也可以在请求头中添加指定字段确认是什么系统。
2)怎么保证单点?
需要保存已登录的系统的信息和登录时间才能知道当前系统是否已有设备登录
3)怎么形成互斥?
在新设备登录时需要能够获取到就设备的信息,把旧设备信息删除或替换,所有token不可变。
3.现有环境:
java jkd1.8,springboot2.0,mysql5.7,redis5.0
4.初步方案:
1)token生成:根据用户使用网站过程中不会改变的信息去生成用户唯一token,用作redis中的key,用户登录时获取登录设备,将登录设备和登录时间用作value保存到redis中。
伪代码:
function String createToken(String tel,String pasword){
return md5Encoding(tel+password);
}
function String createValue(HttpservlertRequest request){
return getDeviceType(request)+(new Date().toString);
}
function boolean saveToken(String token,String value){
jedis.set(token,value,"NX","EX",1800);
}
2)鉴权:使用拦截器拦截请求,判断有无token。有token则根据token查询redis中所对应的value;获取当前操作设备,判断设备是否已登录(是否在value中),如果存在,判断是否已过期,若没有过期则鉴权通过。
伪代码:
String token = request.getParameter("token");
String value = jedis.get(token);
String deviceType = getDeviceType(request);
if(value.get(deviceType)){
if(value.get(deviceType).get("time") > new Date()){
return true;
}
}
return false;
3)token过期:在保存token时将设备的过期时间一起保存,请求时判断设备登录是否已过期,若没有过期则请求成功,同时更新设备过期时间和redis中token过期时间。
伪代码:
if(value.get(deviceType).get("time") > new Date()){
value.get(deviceType).set("time") = new Date()+30m
jedis.set(token,value,"XX","EX",1800);
return true;
}
4)退出登录:判断退出的设备是否存在登录状态,存在则删除设备,设备退出后如果已经没有设备登录,则在redis中删除token。
5.具体实现:
1)创建类保存已登录信息:
import java.util.Date; /** * @author wxm * @date 2019/9/13. * 登录信息 */ public class LoginInfo { /** * 用户id */ private String userId; /** * 安卓设备信息 */ private String android; /** * 安卓设备最后活动时间 */ private Date androidTime; /** * iphone设备登录信息 */ private String iphone; /** * iphone设备最新活动时间 */ private Date iphoneTime; /** * windows设备登录信息 */ private String windows; /** * windows设备最后活动时间 */ private Date windowsTime; /** * ipad设备登录信息 */ private String ipad; /** * ipad设备最后活动时间 */ private Date ipadTime; /** * 其它设备登录信息 */ private String other; /** * 其它设备最后活动时间 */ private Date otherTime; /** * 微信设备登录信息 */ private String wechat; /** * 微信设备登录最后活动时间 */ private Date wechatTime; public String getWechat() { return wechat; } public void setWechat(String wechat) { this.wechat = wechat; } public Date getWechatTime() { return wechatTime; } public void setWechatTime(Date wechatTime) { this.wechatTime = wechatTime; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getAndroid() { return android; } public void setAndroid(String android) { this.android = android; } public Date getAndroidTime() { return androidTime; } public void setAndroidTime(Date androidTime) { this.androidTime = androidTime; } public String getIphone() { return iphone; } public void setIphone(String iphone) { this.iphone = iphone; } public Date getIphoneTime() { return iphoneTime; } public void setIphoneTime(Date iphoneTime) { this.iphoneTime = iphoneTime; } public String getWindows() { return windows; } public void setWindows(String windows) { this.windows = windows; } public Date getWindowsTime() { return windowsTime; } public void setWindowsTime(Date windowsTime) { this.windowsTime = windowsTime; } public String getIpad() { return ipad; } public void setIpad(String ipad) { this.ipad = ipad; } public Date getIpadTime() { return ipadTime; } public void setIpadTime(Date ipadTime) { this.ipadTime = ipadTime; } public String getOther() { return other; } public void setOther(String other) { this.other = other; } public Date getOtherTime() { return otherTime; } public void setOtherTime(Date otherTime) { this.otherTime = otherTime; } @Override public String toString() { return "LoginInfo{" + "userId='" + userId + '\'' + ", android='" + android + '\'' + ", androidTime=" + androidTime + ", iphone='" + iphone + '\'' + ", iphoneTime=" + iphoneTime + ", windows='" + windows + '\'' + ", windowsTime=" + windowsTime + ", ipad='" + ipad + '\'' + ", ipadTime=" + ipadTime + ", other='" + other + '\'' + ", otherTime=" + otherTime + ", wechat='" + wechat + '\'' + ", wechatTime=" + wechatTime + '}'; } }
2)用户登录:
// token过期时间(s) public static final int LOGIN_TIMEOUT_SECOND = 1800; // Android public static final String DEVICE_TYPE_ANDROID = "Android"; // 微信 public static final String DEVICE_TYPE_MICRO_MESSENGER = "MicroMessenger"; // iPhone public static final String DEVICE_TYPE_IPHONE = "iPhone"; // iPad public static final String DEVICE_TYPE_IPAD = "iPad"; // Android public static final String DEVICE_TYPE_WINDOWS = "Windows"; // unknown public static final String DEVICE_TYPE_UNKNOWN = "unknown"; // Linux public static final String DEVICE_TYPE_LINUX = "Linux"; /** * 登录 */ public String login(String account, String password, HttpServletRequest request) { // 获取设备类型 String devType = getDevType(request.getHeader("User-Agent")); User preLogin = userMapper.getUserByAccount(account); // 序列化:protostuff 初始化 RuntimeSchema<LoginInfo> schaema = RuntimeSchema.createFrom(LoginInfo.class); LoginInfo loginInfo = schaema.newMessage(); Jedis jedis = jedisPool.getResource(); // 获取token String tokenCode = MD5Utils.MD5(preLogin.getTel() + preLogin.getPassword()); // 获取redis中数据 byte[] loginInfor = jedis.get(tokenCode.getBytes()); if(loginInfor == null || loginInfor.length == 0){ // 第一个设备登录时将userId存入 loginInfo.setUserId(preLogin.getId()); }else{ // 反序列化登录信息到loginInfo ProtostuffIOUtil.mergeFrom(loginInfor, loginInfo, schaema); } // 保存客户端信息 switch (devType){ case DEVICE_TYPE_ANDROID: loginInfo.setAndroid(request.getHeader("User-Agent")); loginInfo.setAndroidTime(new Date()); break; case DEVICE_TYPE_IPAD: loginInfo.setIpad(request.getHeader("User-Agent")); loginInfo.setIpadTime(new Date()); break; case DEVICE_TYPE_IPHONE: loginInfo.setIphone(request.getHeader("User-Agent")); loginInfo.setIphoneTime(new Date()); break; case DEVICE_TYPE_LINUX: loginInfo.setAndroid(request.getHeader("User-Agent")); loginInfo.setAndroidTime(new Date()); break; case DEVICE_TYPE_MICRO_MESSENGER: loginInfo.setWechat(request.getHeader("User-Agent")); loginInfo.setWechatTime(new Date()); break; case DEVICE_TYPE_WINDOWS: loginInfo.setWindows(request.getHeader("User-Agent")); loginInfo.setWindowsTime(new Date()); break; default: loginInfo.setOther(request.getHeader("User-Agent")); loginInfo.setOtherTime(new Date()); } try { // 序列化对象 byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); // 存入redis jedis.del(tokenCode.getBytes()); jedis.set(tokenCode.getBytes(), bytes, "NX".getBytes(), "EX".getBytes(), UserBll.LOGIN_TIMEOUT_SECOND); }finally { jedis.close(); } return tokenCode; } /** * 根据userAgent获取设备类型 */ public static String getDevType(String userAgent){ String dev_type = ""; if (userAgent.contains(DEVICE_TYPE_MICRO_MESSENGER)) { dev_type = DEVICE_TYPE_MICRO_MESSENGER; }else{ if (userAgent.contains(DEVICE_TYPE_ANDROID) || userAgent.contains(DEVICE_TYPE_LINUX)) { dev_type = DEVICE_TYPE_ANDROID; }else if (userAgent.contains(DEVICE_TYPE_IPHONE)) { dev_type = DEVICE_TYPE_IPHONE; }else if (userAgent.contains(DEVICE_TYPE_IPAD)) { dev_type = DEVICE_TYPE_IPAD; }else if(userAgent.contains(DEVICE_TYPE_WINDOWS)){ dev_type = DEVICE_TYPE_WINDOWS; }else{ dev_type = DEVICE_TYPE_UNKNOWN; } } return dev_type; }
3)拦截鉴权:
import com.alibaba.dubbo.common.utils.StringUtils; import com.alibaba.fastjson.JSON; import com.chinacarbon.api.model.ReturnModel; import com.chinacarbon.bll.UserService; import com.chinacarbon.utils.DateUtil; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.runtime.RuntimeSchema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.Date; /** * 2019-07-04 * redis登录验证 * @author wxm */ @Component public class LoginInterceptor implements HandlerInterceptor { private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); @Autowired private JedisPool jedisPool; @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取token String token = request.getParameter("token"); // 缺少权限参数 if(StringUtils.isEmpty(token)){ response.sendRedirect("/user/login"); return false; }else { // 验证权限 Jedis jedis = jedisPool.getResource(); try { String devType = UserService.getDevType(request.getHeader("User-Agent")); byte[] loginInfor = jedis.get(token.getBytes()); if(loginInfor == null || loginInfor.length == 0){ // 权限不足 response.setCharacterEncoding("utf-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(new ReturnModel(-1011, null,"登录已过期!"))); return false; }else{ // 序列化:protostuff 初始化 RuntimeSchema<LoginInfo> schaema = RuntimeSchema.createFrom(LoginInfo.class); LoginInfo loginInfo = schaema.newMessage(); // 反序列化登录信息到loginMap ProtostuffIOUtil.mergeFrom(loginInfor, loginInfo, schaema); // 判断当前种类设备是否已登录 switch (devType){ case UserService.DEVICE_TYPE_ANDROID: if(loginInfo.getAndroid() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getAndroidTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setAndroidTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; case UserService.DEVICE_TYPE_IPAD: if(loginInfo.getIpad() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getIpadTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setIpadTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; case UserService.DEVICE_TYPE_IPHONE: if(loginInfo.getIphone() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getIphoneTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setIphoneTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; case UserService.DEVICE_TYPE_LINUX: if(loginInfo.getAndroid() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getAndroidTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setAndroidTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; case UserService.DEVICE_TYPE_MICRO_MESSENGER: if(loginInfo.getWechat() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getWechatTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setWechatTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; case UserService.DEVICE_TYPE_WINDOWS: if(loginInfo.getWindows() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getWindowsTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setWindowsTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; case UserService.DEVICE_TYPE_UNKNOWN: if(loginInfo.getOther() != null){ Long secound = DateUtil.SecondsBetween(loginInfo.getOtherTime(),new Date()); if(secound < UserService.LOGIN_TIMEOUT_SECOND) { loginInfo.setOtherTime(new Date()); byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND); return true; } } break; default: response.setCharacterEncoding("utf-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(new ReturnModel(-1011, null,"未知设备!"))); return false; } } }finally { jedis.close(); } } // 权限不足 response.setCharacterEncoding("utf-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(new ReturnModel(-1011, null,"登录已过期!"))); return false; } }
4)退出登录
/** * 退出登录 */ public void loginOut(String token,HttpServletRequest request) { // 1.获取登录设备信息 Jedis jedis = jedisPool.getResource(); byte[] loginInfor = jedis.get(token.getBytes()); try { if (loginInfor == null || loginInfor.length == 0) { logger.warn("一个无效token调用了退出接口!设备信息:" + request.getHeader("User-Agent")); } else { // 序列化:protostuff 初始化 RuntimeSchema<LoginInfo> schaema = RuntimeSchema.createFrom(LoginInfo.class); LoginInfo loginInfo = schaema.newMessage(); // 反序列化登录信息到loginInfo ProtostuffIOUtil.mergeFrom(loginInfor, loginInfo, schaema); // 获取设备类型 String devType = getDevType(request.getHeader("User-Agent")); // 退出设备 switch (devType) { case DEVICE_TYPE_ANDROID: loginInfo.setAndroid(null); loginInfo.setAndroidTime(null); break; case DEVICE_TYPE_IPAD: loginInfo.setIpad(null); loginInfo.setIpadTime(null); break; case DEVICE_TYPE_IPHONE: loginInfo.setIphone(null); loginInfo.setIphoneTime(null); break; case DEVICE_TYPE_LINUX: loginInfo.setAndroid(null); loginInfo.setAndroidTime(null); break; case DEVICE_TYPE_MICRO_MESSENGER: loginInfo.setWechat(null); loginInfo.setWechatTime(null); break; case DEVICE_TYPE_WINDOWS: loginInfo.setWindows(null); loginInfo.setWindowsTime(null); break; case DEVICE_TYPE_UNKNOWN: loginInfo.setOther(null); loginInfo.setOtherTime(null); break; default: logger.warn("一个未登录的设备获取了token并执行了退出登录接口!设备信息:" + request.getHeader("User-Agent")); } if (loginInfoIsNull(loginInfo)) { // 全部设备已退出登录则删除token jedis.del(token.getBytes()); } else { // 还有设备未退出登录则更新token // 序列化对象 byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); // 存入redis jedis.del(token.getBytes()); jedis.set(token.getBytes(), bytes, "NX".getBytes(), "EX".getBytes(), LOGIN_TIMEOUT_SECOND); } } }finally { jedis.close(); } } private boolean loginInfoIsNull(LoginInfo loginInfo) { if(loginInfo.getAndroid() != null) { return false; } if(loginInfo.getIpad() != null) { return false; } if(loginInfo.getIphone() != null) { return false; } if(loginInfo.getOther() != null) { return false; } if(loginInfo.getWindows() != null) { return false; } if(loginInfo.getWechat() != null) { return false; } return true; }
6.使用的jar包:
1) protostuff 序列化和反序列化工具,性能和内存占用都优于jdk自带序列化工具。
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.8</version>
</dependency> -
最近登录日期
2020-10-14 13:59:14牛客每天有很多人登录,请你统计一下牛客每个用户最近登录是哪一天,用的是什么设备. 有一个登录(login)记录表,简况如下: 第1行表示id为2的用户在2020-10-12使用了客户端id为1的设备登录了牛客网 。。。 第4行...题目描述
牛客每天有很多人登录,请你统计一下牛客每个用户最近登录是哪一天,用的是什么设备.
有一个登录(login)记录表,简况如下:
第1行表示id为2的用户在2020-10-12使用了客户端id为1的设备登录了牛客网
。。。
第4行表示id为3的用户在2020-10-13使用了客户端id为2的设备登录了牛客网
还有一个用户(user)表,简况如下:
还有一个客户端(client)表,简况如下:
请你写出一个sql语句查询每个用户最近一天登录的日子,用户的名字,以及用户用的设备的名字,并且查询结果按照user的name升序排序,上面的例子查询结果如下:查询结果表明:
fh最近的登录日期在2020-10-13,而且是使用pc登录的
wangchao最近的登录日期也是2020-10-13,而且是使用ios登录的答案1:
select user.name as u_n, client.name as c_n, login.date from login join user on login.user_id=user.id join client on login.client_id=client.id where (login.user_id,login.date) in (select user_id,max(date) from login group by login.user_id ) order by user.name;
答案2:
select u.name,c.name,l1.date from login l1,user u,client c where l1.date=(select max(l2.date) from login l2 where l1.user_id=l2.user_id) and l1.user_id=u.id and l1.client_id=c.id order by u.name
-
是谁修改了/dev/null设备的权限?
2011-03-03 14:53:00前些天在维护服务器用普通用户登录进系统提示:-bash: /dev/...但是是什么程序修改了/dev/null的权限呢?就开始进入一段"旅程"了:1、查看/dev/null文件被哪些程序所引用:COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME -
SQL练习67:牛客每个人最近的登录日期(二)
2021-02-12 20:44:00牛客每天有很多人登录,请你统计一下牛客每个用户最近登录是哪一天,用的是什么设备. 有一个登录(login)记录表,简况如下: 第1行表示id为2的用户在2020-10-12使用了客户端id为1的设备登录了牛客网 。。。 第4行表示...SQL练习67:牛客每个人最近的登录日期2
题目链接:牛客网
题目描述
牛客每天有很多人登录,请你统计一下牛客每个用户最近登录是哪一天,用的是什么设备.
有一个登录(login)记录表,简况如下:
第1行表示id为2的用户在2020-10-12使用了客户端id为1的设备登录了牛客网
。。。
第4行表示id为3的用户在2020-10-13使用了客户端id为2的设备登录了牛客网还有一个用户(user)表,简况如下:
还有一个客户端(client)表,简况如下:
请你写出一个sql语句查询每个用户最近一天登录的日子,用户的名字,以及用户用的设备的名字,并且查询结果按照user的name升序排序,上面的例子查询结果如下:
查询结果表明:
fh最近的登录日期在2020-10-13,而且是使用pc登录的
wangchao最近的登录日期也是2020-10-13,而且是使用ios登录的
解法
连接login、user、date
三个表,通过子查询的方式获得MAX(date)
最近的日期,再添加题中条件即可,最后按照name
升序排列。SELECT u.name, c.name, l.date FROM login l, user u, client c WHERE l.date = (SELECT MAX(date) FROM login l1 WHERE l.user_id = l1.user_id) AND l.user_id = u.id AND l.client_id = c.id ORDER BY u.name
-
IOS两台手机登录同一个用户消息推送失败
2016-06-15 04:09:11我们开发了一个IOS APP,先用一台iphone手机使用用户user1登录,我们能够成功推送消息,但再用一个iphone手机用这个用户登录,结果两台手机都收到不推送消息。 我们的后台处理是:APP用户... 请问大家这是什么原因。 -
shell中spawn什么意思_[linux系统]--spawn 用法
2020-12-19 07:28:19spawn与except组合可达到远程登录设备执行命令的作用下面是登录设备的一段代码#!/usr/bin/expect -fuser=roothost=1.1.1.1password=rootspawn $user@$hostset timeout 60except {"(yes/no)?" {send "yes\n"expect "*... -
同个账号无法支持多把android手机登录!!!
2020-11-29 21:17:37单设备可以正常发送,但是同个账号在两把android手机登录,会有一方一直断开连接的情况。 我在下方打印了日志 <img alt="image" src="https://user-images.githubusercontent.... -
MySQL——SQL练习题
2021-04-07 16:37:08统计一下牛客每个用户最近登录是哪一天,用的是什么设备 两个join,连接三个表 #1.先根据用户分组,查出每个用户登录的最新日期(一) select user_id,max(date) from login group by login.user_id; #2. 然后... -
牛客SQL练习第68题
2020-09-15 12:10:28牛客每天有很多人登录,请你统计一下牛客每个用户最近登录是哪一天,用的是什么设备. 有一个登录(login)记录表,简况如下: 第1行表示id为2的用户在2020-10-12使用了客户端id为1的设备登录了牛客网 。。。 第4行表示... -
牛客在线编程练习:SQL67_较难
2021-02-08 00:22:04牛客每天有很多人登录,请你统计一下牛客每个用户最近登录是哪一天,用的是什么设备. 有一个登录(login)记录表,简况如下: 题解: # 方式1:sqlite的解法 SELECT U.name u_n,C.name c_n,MAX(date) date FROM ... -
配置console口认证(华为/思科)
2021-01-12 13:20:40默认情况下 通过console登录时无认证的,任何人都可以连接设备登录进去查看或者修改配置 为避免这样带来的风险,可以将console口配置密码认证登录(可以是明文或者密文)来降低风险 华为设备console口配置 [Huawei]... -
H3C路由配置telnet和ssh服务
2020-03-05 22:59:48拓扑结构: 配置主机的IP地址 ...至于为什么host设备在192.168.56.0/24网络? 这是因为: 我们dos控制台: 配置telnet登录 <H3C>sys System View: return to User View with Ctrl+Z. [H3C]telnet ... -
实验89答案.doc
2019-09-21 16:06:07(11)让log2具有LoanDB数据库中的全部数据的查询权,比较好的实现方法是什么? (12)如果拒绝role1查询BankT表,则log1、log2、log3是否有权查询BankT表?为什么? 2、请完成下题 新建一个数据库TGDB,然后在... -
Sa-Token是什么? Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0 等一系列权限相关问题 框架针对踢人下线、自动续签、前后台分离、分布式会话……等常见...
-
Sa-Token是什么? sa-token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0 等一系列权限相关问题 框架针对踢人下线、自动续签、前后台分离、分布式会话……等常见...
-
Unix/Linux 编程实践教程.PDF
2010-09-03 18:34:126.4.2 信号是什么 6.4.3 进程该如何处理信号 6.4.4 信号处理的例子 6.5 为处理信号做准备:play_again4.c 6.6 进程终止 6.7 为设备编程 小结 第七章 事件驱动编程:编写一个视频游戏 7.1 视频游戏和操作系统 ... -
注册表修改大全(作者:Sunny)
2009-03-14 08:51:07每个Win98特殊的东东都有一个提示,比如把鼠标停留在我的电脑上,就会有个提示你我的电脑是什么,改变这个提示并不难,到HKEY_CLASSES_ROOT\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D},修改右边的InfoTip的... -
-
winpcap驱动及开发包
2009-09-27 12:39:49什么是WinPcap WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库. 大多数网络应用程序通过被广泛使用的操作系统元件来访问网络,比如sockets。 这是一种简单的实现方式,因为操作系统已经妥善... -
Linux操作系统基础教程
2013-04-08 21:34:26什么是Linux?.................................................................................................................2 二.安装Linux的好处?..................................................... -
入门学习Linux常用必会60个命令实例详解doc/txt
2011-06-09 00:08:45这些设备名称的命名都是有规则的,可以用“推理”的方式把设备名称找出来。例如,/dev/hda1这个 IDE设备,hd是Hard Disk(硬盘)的,sd是SCSI Device,fd是Floppy Device(或是Floppy Disk?)。a代表第一个设备,通常IDE... -
计算机应用技术(实用手册)
2011-07-29 16:32:16后面是IDE设备的类型和硬件参数,TYPE用来说明硬盘设备的类型,我们可以选择AUTO、USER、NONE的工作模式,AUTO是由系统自己检测硬盘类型,在系统中存储了1-45类硬盘参数,在使用该设置值时不必再设置其它参数;... -
AHRID-黑客攻击画像分析系统.zip
2019-12-11 12:34:31题外话说的有点多了,来说说为什么开发这样一个平台:作为一个防守方光看日志固然是枯燥无味的,偶尔来几次反向打击啥的,增添防守的乐趣~所以我想到了做这样一个系统,就是想在“空暇”时间能获取点“黑客攻击者”... -
电脑高手必备 Windows系统35招实用技巧
2009-06-11 14:42:104、Win32k.sys是什么文件 现象:我刚装了Windows XP,可是接下去再装毒霸就发现病毒,位于 F:WINNT SYSTEM32里的Win32k.sys文件,删又不可删,隔离又不行,在 Windows 98下或DOS下删就会导致Windows XP不可... -
windowsnt 技术内幕
2014-04-09 20:47:17Domain Admins(域管理员)组的详细说明 赋予拨号进入权限 理解用户配置文件(User Profile) 为Windows用户创建并使用登录脚本文件(Logon Script) 创建漫游式用户配置文件(Roaming User Profile) 创建强制性用户配置... -
JMeter操作手册大全.docx
2020-03-24 21:59:094.2.我们为什么使用Jmeter 开源免费还很好用,基于Java编写,可集成到其他系统可拓展各个功能插件 支持接口测试,压力(负载和压力)测试等多种功能,支持录制回放,入门简单 相较于自己编写框架活其他开源工具,有... -
vlan学习笔记
2008-04-22 08:54:49<br> 其中基于子网的VLAN和基于用户的VLAN有可能是网络设备厂商使用独有的协议实现的,不同厂商的设备之间互联有可能出现兼容性问题;因此在选择交换机时,一定要注意事先确认。 VLAN学习笔记大全(3):实现... -
(重要)AIX command 使用总结.txt
2011-12-25 16:40:17s Subclass ->指定设备的子类名称,subclass包括什么类型可用参数P显示; t Type ->指定设备类型名称; 查看系统中所有的外置物理磁盘 lsdev -Cc pdisk -s ssar -H name status location description pdisk0 ... -
如何查杀运行状态下的EXE、DLL病毒
2008-11-15 00:13:26再单击“添加”按钮,然后再在打开窗口中单击“高级”按钮,接着单击“立即查找”按钮,找到PowerUser或User组,单击“确定”两次,将此用户添加PowerUser或User组。注销当前用户,再以新用户登录可以发现系统快很多... -
网管教程 从入门到精通软件篇.txt
2010-04-25 22:43:49以下是 ARC 设备名称的范例: multi(0)disk(0)rdisk(0)partition(1) 等价的设备名称是: DeviceHardDisk0Partition1 范例 下例将物理设备名映射为使用 ARC 设备名称的驱动器号: map arc 注意 ...