使用SpringSocial开发QQ登录
⒈编写QQ用户对应的数据结构
1 package cn.coreqi.social.qq.entities; 2 3 /** 4 * 封装QQ的用户信息 5 */ 6 public class QQUserInfo { 7 8 /** 9 * 返回码 10 */ 11 private String ret; 12 /** 13 * 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。 14 */ 15 private String msg; 16 /** 17 * 18 */ 19 private String openId; 20 /** 21 * 不知道什么东西,文档上没写,但是实际api返回里有。 22 */ 23 private String is_lost; 24 /** 25 * 省(直辖市) 26 */ 27 private String province; 28 /** 29 * 市(直辖市区) 30 */ 31 private String city; 32 /** 33 * 出生年月 34 */ 35 private String year; 36 /** 37 * 用户在QQ空间的昵称。 38 */ 39 private String nickname; 40 /** 41 * 大小为30×30像素的QQ空间头像URL。 42 */ 43 private String figureurl; 44 /** 45 * 大小为50×50像素的QQ空间头像URL。 46 */ 47 private String figureurl_1; 48 /** 49 * 大小为100×100像素的QQ空间头像URL。 50 */ 51 private String figureurl_2; 52 /** 53 * 大小为40×40像素的QQ头像URL。 54 */ 55 private String figureurl_qq_1; 56 /** 57 * 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100×100的头像,但40×40像素则是一定会有。 58 */ 59 private String figureurl_qq_2; 60 /** 61 * 性别。 如果获取不到则默认返回”男” 62 */ 63 private String gender; 64 /** 65 * 标识用户是否为黄钻用户(0:不是;1:是)。 66 */ 67 private String is_yellow_vip; 68 /** 69 * 标识用户是否为黄钻用户(0:不是;1:是) 70 */ 71 private String vip; 72 /** 73 * 黄钻等级 74 */ 75 private String yellow_vip_level; 76 /** 77 * 黄钻等级 78 */ 79 private String level; 80 /** 81 * 标识是否为年费黄钻用户(0:不是; 1:是) 82 */ 83 private String is_yellow_year_vip; 84 85 86 public String getRet() { 87 return ret; 88 } 89 90 public void setRet(String ret) { 91 this.ret = ret; 92 } 93 94 public String getMsg() { 95 return msg; 96 } 97 98 public void setMsg(String msg) { 99 this.msg = msg; 100 } 101 102 public String getOpenId() { 103 return openId; 104 } 105 106 public void setOpenId(String openId) { 107 this.openId = openId; 108 } 109 110 public String getIs_lost() { 111 return is_lost; 112 } 113 114 public void setIs_lost(String is_lost) { 115 this.is_lost = is_lost; 116 } 117 118 public String getProvince() { 119 return province; 120 } 121 122 public void setProvince(String province) { 123 this.province = province; 124 } 125 126 public String getCity() { 127 return city; 128 } 129 130 public void setCity(String city) { 131 this.city = city; 132 } 133 134 public String getYear() { 135 return year; 136 } 137 138 public void setYear(String year) { 139 this.year = year; 140 } 141 142 public String getNickname() { 143 return nickname; 144 } 145 146 public void setNickname(String nickname) { 147 this.nickname = nickname; 148 } 149 150 public String getFigureurl() { 151 return figureurl; 152 } 153 154 public void setFigureurl(String figureurl) { 155 this.figureurl = figureurl; 156 } 157 158 public String getFigureurl_1() { 159 return figureurl_1; 160 } 161 162 public void setFigureurl_1(String figureurl_1) { 163 this.figureurl_1 = figureurl_1; 164 } 165 166 public String getFigureurl_2() { 167 return figureurl_2; 168 } 169 170 public void setFigureurl_2(String figureurl_2) { 171 this.figureurl_2 = figureurl_2; 172 } 173 174 public String getFigureurl_qq_1() { 175 return figureurl_qq_1; 176 } 177 178 public void setFigureurl_qq_1(String figureurl_qq_1) { 179 this.figureurl_qq_1 = figureurl_qq_1; 180 } 181 182 public String getFigureurl_qq_2() { 183 return figureurl_qq_2; 184 } 185 186 public void setFigureurl_qq_2(String figureurl_qq_2) { 187 this.figureurl_qq_2 = figureurl_qq_2; 188 } 189 190 public String getGender() { 191 return gender; 192 } 193 194 public void setGender(String gender) { 195 this.gender = gender; 196 } 197 198 public String getIs_yellow_vip() { 199 return is_yellow_vip; 200 } 201 202 public void setIs_yellow_vip(String is_yellow_vip) { 203 this.is_yellow_vip = is_yellow_vip; 204 } 205 206 public String getVip() { 207 return vip; 208 } 209 210 public void setVip(String vip) { 211 this.vip = vip; 212 } 213 214 public String getYellow_vip_level() { 215 return yellow_vip_level; 216 } 217 218 public void setYellow_vip_level(String yellow_vip_level) { 219 this.yellow_vip_level = yellow_vip_level; 220 } 221 222 public String getLevel() { 223 return level; 224 } 225 226 public void setLevel(String level) { 227 this.level = level; 228 } 229 230 public String getIs_yellow_year_vip() { 231 return is_yellow_year_vip; 232 } 233 234 public void setIs_yellow_year_vip(String is_yellow_year_vip) { 235 this.is_yellow_year_vip = is_yellow_year_vip; 236 } 237 }
⒉编写一个QQ API接口用于获取QQ用户信息
1 package cn.coreqi.social.qq.api; 2 3 import cn.coreqi.social.qq.entities.QQUserInfo; 4 5 public interface QQ { 6 /** 7 * 返回QQ中的用户信息 8 * @return 9 */ 10 QQUserInfo getUserInfo(); 11 }
⒊编写一个QQ API接口实现
1 package cn.coreqi.social.qq.api.impl; 2 3 import cn.coreqi.social.qq.api.QQ; 4 import cn.coreqi.social.qq.entities.QQUserInfo; 5 import com.fasterxml.jackson.databind.ObjectMapper; 6 import org.apache.commons.lang.StringUtils; 7 import org.springframework.social.oauth2.AbstractOAuth2ApiBinding; 8 import org.springframework.social.oauth2.TokenStrategy; 9 10 import java.io.IOException; 11 12 /** 13 * 获取用户信息 14 * 不能声明为单例,因为每个用户的验证是不同的 15 */ 16 public class QQImpl extends AbstractOAuth2ApiBinding implements QQ { 17 18 private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s"; //获取openid的请求地址 19 private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s"; //获取用户信息的请求地址 20 21 private String appid; //申请QQ登录成功后,分配给应用的appid 22 private String openid; //用户的ID,与QQ号码一一对应。 23 24 private ObjectMapper objectMapper = new ObjectMapper(); //用于序列化Json数据 25 26 public QQImpl(String accessToken,String appid){ 27 super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER); //将token作为查询参数 28 this.appid = appid; 29 30 String url = String.format(URL_GET_OPENID,accessToken); //拼接成最终的openid的请求地址 31 String result = getRestTemplate().getForObject(url,String.class); 32 33 System.out.println(result); 34 35 this.openid = StringUtils.substringBetween(result,"\"openid\":\"","\"}"); 36 37 } 38 39 @Override 40 public QQUserInfo getUserInfo() { 41 String url = String.format(URL_GET_USERINFO,appid,openid); 拼接成最终的获取用户信息的请求地址 42 String result = getRestTemplate().getForObject(url,String.class); 43 System.out.println(result); 44 QQUserInfo userInfo = null; 45 try { 46 userInfo = objectMapper.readValue(result,QQUserInfo.class); 47 userInfo.setOpenId(openid); 48 return userInfo; 49 } catch (Exception e) { 50 throw new RuntimeException("获取用户信息失败",e); 51 } 52 } 53 }
⒋编写QQ OAuth2认证流程模板类。
1 package cn.coreqi.social.qq.connect; 2 3 import org.apache.commons.lang.StringUtils; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.http.converter.StringHttpMessageConverter; 7 import org.springframework.social.oauth2.AccessGrant; 8 import org.springframework.social.oauth2.OAuth2Template; 9 import org.springframework.util.MultiValueMap; 10 import org.springframework.web.client.RestTemplate; 11 import java.nio.charset.Charset; 12 13 public class QQOAuth2Template extends OAuth2Template { 14 15 private Logger logger = LoggerFactory.getLogger(getClass()); 16 17 public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) { 18 super(clientId, clientSecret, authorizeUrl, accessTokenUrl); 19 setUseParametersForClientAuthentication(true); 20 } 21 22 @Override 23 protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) { 24 String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class); 25 26 logger.info("获取accessToke的响应:"+responseStr); 27 28 String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&"); 29 30 String accessToken = StringUtils.substringAfterLast(items[0], "="); 31 Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "=")); 32 String refreshToken = StringUtils.substringAfterLast(items[2], "="); 33 34 return new AccessGrant(accessToken, null, refreshToken, expiresIn); 35 } 36 37 @Override 38 protected RestTemplate createRestTemplate() { 39 RestTemplate restTemplate = super.createRestTemplate(); 40 restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); 41 return restTemplate; 42 } 43 }
⒌编写QQ的OAuth2流程处理器的提供器
1 package cn.coreqi.social.qq.connect; 2 3 import cn.coreqi.social.qq.api.QQ; 4 import cn.coreqi.social.qq.api.impl.QQImpl; 5 import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider; 6 7 /** 8 * 泛型是API接口的类型 9 */ 10 public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> { 11 12 private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize"; //获取授权码地址 13 private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token"; //获取用户令牌地址 14 15 private String appId; 16 17 18 public QQServiceProvider(String appId,String appSecret) { 19 super(new QQOAuth2Template(appId,appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN)); 20 this.appId = appId; 21 } 22 23 @Override 24 public QQ getApi(String accessToken) { 25 return new QQImpl(accessToken,appId); 26 } 27 }
⒍编写QQ API适配器,将从QQ API拿到的用户数据模型转换为Spring Social的标准用户数据模型。
1 package cn.coreqi.social.qq.connect; 2 3 import cn.coreqi.social.qq.api.QQ; 4 import cn.coreqi.social.qq.entities.QQUserInfo; 5 import org.springframework.social.connect.ApiAdapter; 6 import org.springframework.social.connect.ConnectionValues; 7 import org.springframework.social.connect.UserProfile; 8 9 import java.io.IOException; 10 11 /** 12 * 泛型是指当前API适配器适配API的类型是什么 13 */ 14 public class QQAdapter implements ApiAdapter<QQ> { 15 16 /** 17 * 用来测试当前的API是否可用 18 * @param qq 19 * @return 20 */ 21 @Override 22 public boolean test(QQ qq) { 23 return true; 24 } 25 26 /** 27 * 将服务提供商个性化的用户信息映射到ConnectionValues标准的数据化结构上 28 * @param qq 29 * @param connectionValues 30 */ 31 @Override 32 public void setConnectionValues(QQ qq, ConnectionValues connectionValues) { 33 QQUserInfo userInfo = qq.getUserInfo(); 34 connectionValues.setDisplayName(userInfo.getNickname()); //显示的用户名称 35 connectionValues.setImageUrl(userInfo.getFigureurl_qq_1()); //用户的头像 36 connectionValues.setProfileUrl(null); //个人主页 37 connectionValues.setProviderUserId(userInfo.getOpenId()); //QQ的唯一标识 38 } 39 40 /** 41 * 和上面的方法类似 42 * @param qq 43 * @return 44 */ 45 @Override 46 public UserProfile fetchUserProfile(QQ qq) { 47 return null; 48 } 49 50 /** 51 * 52 * @param qq 53 * @param s 54 */ 55 @Override 56 public void updateStatus(QQ qq, String s) { 57 58 } 59 }
⒎创建QQ连接工厂
1 package cn.coreqi.social.qq.connect; 2 3 import cn.coreqi.social.qq.api.QQ; 4 import org.springframework.social.connect.support.OAuth2ConnectionFactory; 5 6 public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> { 7 8 /** 9 * 10 * @param providerId 我们给服务提供商的唯一标识 11 * @param appId 服务提供商给的AppId 12 * @param appSecret 服务提供商给的App密码 13 */ 14 public QQConnectionFactory(String providerId,String appId,String appSecret) { 15 super(providerId, new QQServiceProvider(appId,appSecret), new QQAdapter()); 16 } 17 }
⒏创建UserConnection数据表
1 create table UserConnection (userId varchar(255) not null, 2 providerId varchar(255) not null, 3 providerUserId varchar(255), 4 `rank` int not null, 5 displayName varchar(255), 6 profileUrl varchar(512), 7 imageUrl varchar(512), 8 accessToken varchar(512) not null, 9 secret varchar(512), 10 refreshToken varchar(512), 11 expireTime bigint, 12 primary key (userId, providerId, providerUserId)); 13 create unique index UserConnectionRank on UserConnection(userId, providerId, `rank`);
⒐为用户服务类实现SocialUserDetailsService ,用于从数据库中通过QQ Id 拿到业务系统用户
1 /** 2 * 3 */ 4 package cn.coreqi.security; 5 6 import org.slf4j.Logger; 7 import org.slf4j.LoggerFactory; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.security.core.authority.AuthorityUtils; 10 import org.springframework.security.core.userdetails.UserDetails; 11 import org.springframework.security.core.userdetails.UserDetailsService; 12 import org.springframework.security.core.userdetails.UsernameNotFoundException; 13 import org.springframework.security.crypto.password.PasswordEncoder; 14 import org.springframework.social.security.SocialUser; 15 import org.springframework.social.security.SocialUserDetails; 16 import org.springframework.social.security.SocialUserDetailsService; 17 import org.springframework.stereotype.Component; 18 19 /** 20 * @author fanqi 21 * 22 */ 23 @Component 24 public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService { 25 26 private Logger logger = LoggerFactory.getLogger(getClass()); 27 28 @Autowired 29 private PasswordEncoder passwordEncoder; 30 31 /* 32 * (non-Javadoc) 33 * 34 * @see org.springframework.security.core.userdetails.UserDetailsService# 35 * loadUserByUsername(java.lang.String) 36 */ 37 @Override 38 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 39 logger.info("表单登录用户名:" + username); 40 return buildUser(username); 41 } 42 43 @Override 44 public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException { 45 logger.info("设计登录用户Id:" + userId); 46 return buildUser(userId); 47 } 48 49 private SocialUserDetails buildUser(String userId) { 50 // 根据用户名查找用户信息 51 //根据查找到的用户信息判断用户是否被冻结 52 String password = passwordEncoder.encode("123456"); 53 logger.info("数据库密码是:"+password); 54 return new SocialUser(userId, password, 55 true, true, true, true, 56 AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); 57 } 58 59 }
⒑创建QQ登陆配置类
1 package cn.coreqi.social.qq.connect; 2 3 import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.social.connect.ConnectionFactory; 6 7 /** 8 * QQ登录配置 9 */ 10 @Configuration 11 public class QQAutoConfig extends SocialAutoConfigurerAdapter { 12 @Override 13 protected ConnectionFactory<?> createConnectionFactory() { 14 String providerId = "qq"; //第三方id,用来决定发起第三方登录的url,默认是weixin 15 String appId = ""; 16 String appSecret = ""; 17 return new QQConnectionFactory(providerId, appId, appSecret); 18 } 19 }
⒒自定义我们自己的SpringSocial配置
1 package cn.coreqi.social.config; 2 3 import org.springframework.social.security.SocialAuthenticationFilter; 4 import org.springframework.social.security.SpringSocialConfigurer; 5 6 public class CoreqiSpringSocialConfig extends SpringSocialConfigurer { 7 8 /** 9 * 10 * @param object 11 * @param <T> 12 * @return 13 */ 14 @Override 15 protected <T> T postProcess(T object) { 16 SocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object); 17 filter.setFilterProcessesUrl("/coreqi/auth"); 18 return (T) filter; 19 } 20 }
SpringSocialConfigurer 会在 configure方法中声明一个 SocialAuthenticationFilter,我们可以继承SpringSocialConfigurer达到自定义我们的SpringSocial配置需求。 ⒓声明一个SpringSocial的配置类
1 package cn.coreqi.social.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.security.crypto.encrypt.Encryptors; 7 import org.springframework.social.config.annotation.EnableSocial; 8 import org.springframework.social.config.annotation.SocialConfigurerAdapter; 9 import org.springframework.social.connect.ConnectionFactoryLocator; 10 import org.springframework.social.connect.ConnectionSignUp; 11 import org.springframework.social.connect.UsersConnectionRepository; 12 import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository; 13 import org.springframework.social.connect.web.ProviderSignInUtils; 14 import org.springframework.social.security.SpringSocialConfigurer; 15 16 import javax.sql.DataSource; 17 18 @Configuration 19 @EnableSocial 20 public class SocialConfig extends SocialConfigurerAdapter { 21 22 @Autowired 23 private DataSource dataSource; 24 25 @Autowired(required = false) 26 private ConnectionSignUp connectionSignUp; 27 28 /** 29 * 30 * @param connectionFactoryLocator 作用是去根据条件去查找应该用那个connectionFactory,因为系统中可能有很多的connectionFactory。 31 * @return 32 */ 33 @Override 34 public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { 35 //第三个参数的作用是把插入到数据库的数据进行加解密 36 JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText()); 37 //jdbcUsersConnectionRepository.setTablePrefix(); //设置数据表的前缀 38 if(connectionSignUp != null){ 39 jdbcUsersConnectionRepository.setConnectionSignUp(connectionSignUp); 40 } 41 return jdbcUsersConnectionRepository; 42 } 43 44 /** 45 * 声明后还需要加在SpringSecurity过滤器链上 46 * @return 47 */ 48 @Bean 49 public SpringSocialConfigurer coreqiSocialSecurityConfig(){ 50 CoreqiSpringSocialConfig config = new CoreqiSpringSocialConfig(); 51 config.signupUrl("/registry"); //当从业务系统中无法找到OAuth快捷登陆的用户,那么将用户引导到注册页面中 52 return config; 53 } 54 55 //1.注册过程中如何拿到SpringSocial信息 56 //2.注册完成后如何把业务系统的用户ID传给SpringSocial 57 @Bean 58 public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator){ 59 return new ProviderSignInUtils(connectionFactoryLocator,getUsersConnectionRepository(connectionFactoryLocator)); 60 } 61 }
⒔应用我们的过滤器配置
1 package cn.coreqi.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 import org.springframework.social.security.SpringSocialConfigurer; 7 8 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 9 @Autowired 10 private SpringSocialConfigurer coreqiSocialSecurityConfig; 11 @Override 12 protected void configure(HttpSecurity http) throws Exception { 13 http.apply(coreqiSocialSecurityConfig); 14 } 15 }
⒕
1 package cn.coreqi.social.qq.connect; 2 3 import org.springframework.social.connect.Connection; 4 import org.springframework.social.connect.ConnectionSignUp; 5 import org.springframework.stereotype.Component; 6 7 /** 8 * 当没有从数据库中查找到第三方登录的用户,那么将执行ConnectionSignUp的execute方法生成新的用户id并存储到数据库中 9 */ 10 @Component 11 public class CoreqiConnectionSignUp implements ConnectionSignUp { 12 @Override 13 public String execute(Connection<?> connection) { 14 return connection.getDisplayName(); 15 } 16 }
转载于:https://www.cnblogs.com/fanqisoft/p/10691234.html
使用SpringSocial开发QQ登录相关推荐
- SpringSocial 开发 QQ 登录
本篇文章带着大家在自己的系统中集成 QQ 第三方登录 不管是做什么操作,官方文档与手册绝对是最靠谱的.所以我们先来看一下 QQ 互联官网的文档 QQ 互联介绍 QQ 互联官网 在 QQ 互联官网上,我 ...
- SpringSecurity和SpringSocial实现QQ登录
siki学院课程: http://www.sikiedu.com/course/366 (SpringSecurity和SpringSocial认证授权) http://www.sikiedu.com ...
- SpringSocial之QQ登录
编写顺序为: Api 获取用户信息 Oauth2Operations 代表用户与服务提供者进行Oauth认证 ServiceProvider 服务提供商 ApiAdapter 连接统一的{@link ...
- SpringSocial之微信登录
编写顺序同SpringSocial之QQ登录 创建用户信息类WeiXinUserInfo: package com.cong.security.core.social.weixin.api;impor ...
- spring-security学习(七)——QQ登录(上篇)
文章目录 OAuth简介 OAuth协议要解决的问题 spring-social简介 QQ登录(实例) 1.实现Api与ServiceProvider 2.获取到用户信息之后的处理 3.一些需要的配置 ...
- php跳转到qq界面,PHP实现QQ登录的开原理和实现过程
第三方登录,就是使用大家比较熟悉的比如QQ.微信.微博等第三方软件登录自己的网站,这可以免去注册账号.快速留住用户的目的,免去了相对复杂的注册流程.下边就给大家讲一下怎么使用PHP开发QQ登录的功能. ...
- php mysql登录实现原理_PHP实现QQ登录的开原理和实现过程
第三方登录,就是使用大家比较熟悉的比如QQ.微信.微博等第三方软件登录自己的网站,这可以免去注册账号.快速留住用户的目的,免去了相对复杂的注册流程.下边就给大家讲一下怎么使用PHP开发QQ登录的功能. ...
- 使用 apifm 插件进行 Flutter 云开发——QQ一键登录/注册
在你的 App 中集成手机QQ一键授权(注册)登录功能,达到快速注册.快速登录功能 按照本教程的操作指引,预计5分钟即可帮你实现并掌握QQ登录的实现 申请开通QQ互联 https://connect. ...
- Laravel第三方登录开发之实现QQ登录
在我们的项目开发中,第三方登录可以很好的为用户提供便捷,比如微信.QQ.微博登录等等. Laravel,作为一个优雅的PHP框架,已经集成了诸多第三方登录插件. 本文,将详细论述如何在Laravel框 ...
最新文章
- 策略模式,状态模式,监听模式之间的区分。
- html input image 尺寸,HTML DOM Input Image 对象
- python中的及||
- 205. jetcache:你需要知道的小技巧
- 使用ML.NET模型生成器来完成图片性别识别
- 亿佰特电源模块:无线通信模块电平转换指南
- python 函数参数传递机制_Python函数参数传递机制(超级详细)
- Apache ab并发负载压力测试
- 《SQL Server企业级平台管理实践》读书笔记——SQL Server如何设置自动增长和自动收缩项...
- 【Zookeeper学习】Apache Zookeeper项目简介
- 简要说明python的缩进规则_关于python的缩进规则的知识点详解
- springboot 集成 log4j,log4j配置不同包不同日志输出级别(按包输出不同级别日志)
- Webpack中 CDN加速
- 论文撰写八大技巧与八大心得,一文读懂
- ppt学习07——动画
- 基于springboot的课堂考勤签到打卡小程序
- java植物大战僵尸小游戏
- 欢迎Edrp开发组第一个成员Zhuang Liu的加入!
- Python数据结构——二叉树排序
- 【ctf】ret2text