思路

主要就是引入的了qq互联的api
要想调用qq登录的api首先要2个条件

  1. 有阿里云服务器
  2. 要申请一个域名,并且备案

有了这两个就可以在qq互联申请一个网站,会得到一个回调方法,然后qqAppId和qqAppSecret相当于qq的认证账户和密码
前端获取到一个qq登录的地址,然后登录成功后调用前面申请的回调域

1.步骤一

引入首先qq登录需要用的的jar包

<!-- 以下是 qq wx 联合登陆需要的相关依赖工具 commons-io, commons-lang3,httpclient,还有以json返回给后端的fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.54</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>

步骤二

把申请好的qqAppId,qqAppSecret,qqRedirectUrl放到yml文件中,方便以后的调用

# qq登录接口配置
constants:qqAppId: 你自己的qqAppSecret: 你自己的qqRedirectUrl: http://你自己的

注册一个bean方便以后通过访问

@Component
@ConfigurationProperties(prefix = "constants")
public class Constants {private String qqAppId;private String qqAppSecret;private String qqRedirectUrl;private String weCatAppId;private String weCatAppSecret;private String weCatRedirectUrl;public String getQqAppId() {return qqAppId;}public void setQqAppId(String qqAppId) {this.qqAppId = qqAppId;}public String getQqAppSecret() {return qqAppSecret;}public void setQqAppSecret(String qqAppSecret) {this.qqAppSecret = qqAppSecret;}public String getQqRedirectUrl() {return qqRedirectUrl;}public void setQqRedirectUrl(String qqRedirectUrl) {this.qqRedirectUrl = qqRedirectUrl;}public String getWeCatAppId() {return weCatAppId;}public void setWeCatAppId(String weCatAppId) {this.weCatAppId = weCatAppId;}public String getWeCatAppSecret() {return weCatAppSecret;}public void setWeCatAppSecret(String weCatAppSecret) {this.weCatAppSecret = weCatAppSecret;}public String getWeCatRedirectUrl() {return weCatRedirectUrl;}public void setWeCatRedirectUrl(String weCatRedirectUrl) {this.weCatRedirectUrl = weCatRedirectUrl;}@Overridepublic String toString() {return "Constants{" +"qqAppId='" + qqAppId + '\'' +", qqAppSecret='" + qqAppSecret + '\'' +", qqRedirectUrl='" + qqRedirectUrl + '\'' +", weCatAppId='" + weCatAppId + '\'' +", weCatAppSecret='" + weCatAppSecret + '\'' +", weCatRedirectUrl='" + weCatRedirectUrl + '\'' +'}';}
}

步骤三

这里使用了两个写好的工具类
1.用于http请求的
2.用于url转码的

1.HttpClientConfigUtils

//清除所有类型的警告
@SuppressWarnings("all")
public class HttpClientConfigUtils {public static final int connTimeout = 10000;public static final int readTimeout = 10000;public static final String charset = "UTF-8";private static HttpClient client = null;static {PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();cm.setMaxTotal(128);cm.setDefaultMaxPerRoute(128);client = HttpClients.custom().setConnectionManager(cm).build();}public static String postParameters(String url, String parameterStr)throws ConnectTimeoutException, SocketTimeoutException, Exception {return post(url, parameterStr, "application/x-www-form-urlencoded", charset, connTimeout, readTimeout);}public static String postParameters(String url, String parameterStr, String charset, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception {return post(url, parameterStr, "application/x-www-form-urlencoded", charset, connTimeout, readTimeout);}public static String postParameters(String url, Map<String, String> params)throws ConnectTimeoutException, SocketTimeoutException, Exception {return postForm(url, params, null, connTimeout, readTimeout);}public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception {return postForm(url, params, null, connTimeout, readTimeout);}public static String get(String url) throws Exception {return get(url, charset, null, null);}public static String get(String url, String charset) throws Exception {return get(url, charset, connTimeout, readTimeout);}/*** 发送一个 Post 请求, 使用指定的字符集编码.** @param url* @param body        RequestBody* @param mimeType    例如 application/xml "application/x-www-form-urlencoded"*                    a=1&b=2&c=3* @param charset     编码* @param connTimeout 建立链接超时时间,毫秒.* @param readTimeout 响应超时时间,毫秒.* @return ResponseBody, 使用指定的字符集编码.* @throws ConnectTimeoutException 建立链接超时异常* @throws SocketTimeoutException  响应超时* @throws Exception*/public static String post(String url, String body, String mimeType, String charset, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception {HttpClient client = null;HttpPost post = new HttpPost(url);String result = "";try {if (StringUtils.isNotBlank(body)) {HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));post.setEntity(entity);}// 设置参数Builder customReqConf = RequestConfig.custom();if (connTimeout != null) {customReqConf.setConnectTimeout(connTimeout);}if (readTimeout != null) {customReqConf.setSocketTimeout(readTimeout);}post.setConfig(customReqConf.build());HttpResponse res;if (url.startsWith("https")) {// 执行 Https 请求.client = createSSLInsecureClient();res = client.execute(post);} else {// 执行 Http 请求.client = HttpClientConfigUtils.client;res = client.execute(post);}result = IOUtils.toString(res.getEntity().getContent(), charset);} finally {post.releaseConnection();if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {((CloseableHttpClient) client).close();}}return result;}/*** 提交form表单** @param url* @param params* @param connTimeout* @param readTimeout* @return* @throws ConnectTimeoutException* @throws SocketTimeoutException* @throws Exception*/public static String postForm(String url, Map<String, String> params, Map<String, String> headers,Integer connTimeout, Integer readTimeout)throws ConnectTimeoutException, SocketTimeoutException, Exception {HttpClient client = null;HttpPost post = new HttpPost(url);try {if (params != null && !params.isEmpty()) {List<NameValuePair> formParams = new ArrayList<NameValuePair>();Set<Entry<String, String>> entrySet = params.entrySet();for (Entry<String, String> entry : entrySet) {formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));}UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);post.setEntity(entity);}if (headers != null && !headers.isEmpty()) {for (Entry<String, String> entry : headers.entrySet()) {post.addHeader(entry.getKey(), entry.getValue());}}// 设置参数Builder customReqConf = RequestConfig.custom();if (connTimeout != null) {customReqConf.setConnectTimeout(connTimeout);}if (readTimeout != null) {customReqConf.setSocketTimeout(readTimeout);}post.setConfig(customReqConf.build());HttpResponse res = null;if (url.startsWith("https")) {// 执行 Https 请求.client = createSSLInsecureClient();res = client.execute(post);} else {// 执行 Http 请求.client = HttpClientConfigUtils.client;res = client.execute(post);}return IOUtils.toString(res.getEntity().getContent(), "UTF-8");} finally {post.releaseConnection();if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {((CloseableHttpClient) client).close();}}}/*** 发送一个 GET 请求** @param url* @param charset* @param connTimeout 建立链接超时时间,毫秒.* @param readTimeout 响应超时时间,毫秒.* @return* @throws ConnectTimeoutException 建立链接超时* @throws SocketTimeoutException  响应超时* @throws Exception*/public static String get(String url, String charset, Integer connTimeout, Integer readTimeout)throws ConnectTimeoutException, SocketTimeoutException, Exception {HttpClient client = null;HttpGet get = new HttpGet(url);String result = "";try {// 设置参数Builder customReqConf = RequestConfig.custom();if (connTimeout != null) {customReqConf.setConnectTimeout(connTimeout);}if (readTimeout != null) {customReqConf.setSocketTimeout(readTimeout);}get.setConfig(customReqConf.build());HttpResponse res = null;if (url.startsWith("https")) {// 执行 Https 请求.client = createSSLInsecureClient();res = client.execute(get);} else {// 执行 Http 请求.client = HttpClientConfigUtils.client;res = client.execute(get);}result = IOUtils.toString(res.getEntity().getContent(), charset);} finally {get.releaseConnection();if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {((CloseableHttpClient) client).close();}}return result;}/*** 从 response 里获取 charset** @param ressponse* @return*/@SuppressWarnings("unused")private static String getCharsetFromResponse(HttpResponse ressponse) {// Content-Type:text/html; charset=GBKif (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null&& ressponse.getEntity().getContentType().getValue() != null) {String contentType = ressponse.getEntity().getContentType().getValue();if (contentType.contains("charset=")) {return contentType.substring(contentType.indexOf("charset=") + 8);}}return null;}/*** 创建 SSL连接** @return* @throws GeneralSecurityException*/private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {try {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {@Overridepublic boolean verify(String arg0, SSLSession arg1) {return true;}@Overridepublic void verify(String host, SSLSocket ssl) throws IOException {}@Overridepublic void verify(String host, X509Certificate cert) throws SSLException {}@Overridepublic void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {}});return HttpClients.custom().setSSLSocketFactory(sslsf).build();} catch (GeneralSecurityException e) {throw e;}}public static void main(String[] args) {try {String str = post("https://localhost:443/ssl/test.shtml", "name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);// String str=// get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");/** Map<String,String> map = new HashMap<String,String>(); map.put("name",* "111"); map.put("page", "222"); String str=* postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/System.out.println(str);} catch (ConnectTimeoutException e) {e.printStackTrace();} catch (SocketTimeoutException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}}

2.URLEncodeUtil

public class URLEncodeUtil {private final static String ENCODE = "UTF-8";/*** URL 解码*/public static String getURLDecoderString(String str) {String result = "";if (null == str) {return "";}try {result = java.net.URLDecoder.decode(str, ENCODE);} catch (UnsupportedEncodingException e) {e.printStackTrace();}return result;}/*** URL 转码*/public static String getURLEncoderString(String str) {String result = "";if (null == str) {return "";}try {result = java.net.URLEncoder.encode(str, ENCODE);} catch (UnsupportedEncodingException e) {e.printStackTrace();}return result;}
}

步骤四

获取到的qq信息实体类QQUserInfo

public class QQUserInfo {private Integer ret;private String msg;private Integer is_lost;private String nickname;private String gender;private String province;private String city;private String year;private String constellation;private String figureurl;private String figureurl_1;private String figureurl_2;private String figureurl_qq;private String figureurl_qq_1;private String figureurl_qq_2;private String is_yellow_vip;private String vip;private String yellow_vip_level;private String level;private String is_yellow_year_vip;public Integer getRet() {return ret;}public void setRet(Integer ret) {this.ret = ret;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Integer getIs_lost() {return is_lost;}public void setIs_lost(Integer is_lost) {this.is_lost = is_lost;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public String getProvince() {return province;}public void setProvince(String province) {this.province = province;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public String getYear() {return year;}public void setYear(String year) {this.year = year;}public String getConstellation() {return constellation;}public void setConstellation(String constellation) {this.constellation = constellation;}public String getFigureurl() {return figureurl;}public void setFigureurl(String figureurl) {this.figureurl = figureurl;}public String getFigureurl_1() {return figureurl_1;}public void setFigureurl_1(String figureurl_1) {this.figureurl_1 = figureurl_1;}public String getFigureurl_2() {return figureurl_2;}public void setFigureurl_2(String figureurl_2) {this.figureurl_2 = figureurl_2;}public String getFigureurl_qq() {return figureurl_qq;}public void setFigureurl_qq(String figureurl_qq) {this.figureurl_qq = figureurl_qq;}public String getFigureurl_qq_1() {return figureurl_qq_1;}public void setFigureurl_qq_1(String figureurl_qq_1) {this.figureurl_qq_1 = figureurl_qq_1;}public String getFigureurl_qq_2() {return figureurl_qq_2;}public void setFigureurl_qq_2(String figureurl_qq_2) {this.figureurl_qq_2 = figureurl_qq_2;}public String getIs_yellow_vip() {return is_yellow_vip;}public void setIs_yellow_vip(String is_yellow_vip) {this.is_yellow_vip = is_yellow_vip;}public String getVip() {return vip;}public void setVip(String vip) {this.vip = vip;}public String getYellow_vip_level() {return yellow_vip_level;}public void setYellow_vip_level(String yellow_vip_level) {this.yellow_vip_level = yellow_vip_level;}public String getLevel() {return level;}public void setLevel(String level) {this.level = level;}public String getIs_yellow_year_vip() {return is_yellow_year_vip;}public void setIs_yellow_year_vip(String is_yellow_year_vip) {this.is_yellow_year_vip = is_yellow_year_vip;}
}

返回url的类

public class QQUrl implements Serializable {private String url;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}@Overridepublic String toString() {return "QQUrl{" +"url='" + url + '\'' +'}';}
}

步骤五

最后就是controller的编写了
得到qq登录的地址

/*** 得到一个地址*/@GetMapping("/getQQUrl")public QQUrl getQQUrl() {// 拼接urlStringBuilder url = new StringBuilder();url.append("https://graph.qq.com/oauth2.0/authorize?");url.append("response_type=code");url.append("&client_id=" + constants.getQqAppId());// 回调地址 ,回调地址要进行Encode转码String redirect_uri = constants.getQqRedirectUrl();// 转码url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirect_uri));url.append("&state=ok");// HttpClientUtils.get(url.toString(), "UTF-8");QQUrl qqUrl = new QQUrl();qqUrl.setUrl(url.toString());return qqUrl;}

2.回调函数的工具方法

/*** 获得token信息(授权,每个用户的都不一致) --> 获得token信息该步骤返回的token期限为一个月* <p>* //     * @param (保存到Map<String,String> qqProperties)** @return* @throws Exception* @author wangsong* @date 2019年6月18日 下午8:56:45*/public Map<String, Object> getToken(String code) throws Exception {StringBuilder url = new StringBuilder();url.append("https://graph.qq.com/oauth2.0/token?");url.append("grant_type=authorization_code");url.append("&client_id=" + constants.getQqAppId());url.append("&client_secret=" + constants.getQqAppSecret());url.append("&code=" + code);// 回调地址String redirect_uri = constants.getQqRedirectUrl();// 转码url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirect_uri));// 获得tokenString result = HttpClientConfigUtils.get(url.toString(), "UTF-8");System.out.println("url:" + url.toString());// 把token保存String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");String accessToken = StringUtils.substringAfterLast(items[0], "=");Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));String refreshToken = StringUtils.substringAfterLast(items[2], "=");//token信息Map<String, Object> qqProperties = new HashMap<String, Object>();qqProperties.put("accessToken", accessToken);qqProperties.put("expiresIn", String.valueOf(expiresIn));qqProperties.put("refreshToken", refreshToken);return qqProperties;}/*** 获取用户openId(根据token)* <p>* //     * @param 把openId存到map中** @return* @throws Exception*/public String getOpenId(Map<String, Object> qqProperties) throws Exception {// 获取保存的用户的tokenString accessToken = (String) qqProperties.get("accessToken");if (!StringUtils.isNotEmpty(accessToken)) {// return "未授权";}StringBuilder url = new StringBuilder("https://graph.qq.com/oauth2.0/me?");url.append("access_token=" + accessToken);String result = HttpClientConfigUtils.get(url.toString(), "UTF-8");String openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");return openId;}/*** 根据token,openId获取用户信息*/public QQUserInfo getUserInfo(Map<String, Object> qqProperties) throws Exception {// 取tokenString accessToken = (String) qqProperties.get("accessToken");String openId = (String) qqProperties.get("openId");if (!StringUtils.isNotEmpty(accessToken) || !StringUtils.isNotEmpty(openId)) {return null;}//拼接urlStringBuilder url = new StringBuilder("https://graph.qq.com/user/get_user_info?");url.append("access_token=" + accessToken);url.append("&oauth_consumer_key=" + constants.getQqAppId());url.append("&openid=" + openId);// 获取qq相关数据String result = HttpClientConfigUtils.get(url.toString(), "UTF-8");Object json = JSON.parseObject(result, QQUserInfo.class);QQUserInfo userInfo = (QQUserInfo) json;return userInfo;}/*** 刷新token 信息(token过期,重新授权)** @return* @throws Exception*/@GetMapping("/refreshToken")public Map<String, Object> refreshToken(Map<String, Object> qqProperties) throws Exception {// 获取refreshTokenString refreshToken = (String) qqProperties.get("refreshToken");StringBuilder url = new StringBuilder("https://graph.qq.com/oauth2.0/token?");url.append("grant_type=refresh_token");url.append("&client_id=" + constants.getQqAppId());url.append("&client_secret=" + constants.getQqAppSecret());url.append("&refresh_token=" + refreshToken);System.out.println("url:" + url.toString());String result = HttpClientConfigUtils.get(url.toString(), "UTF-8");// 把新获取的token存到map中String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");String accessToken = StringUtils.substringAfterLast(items[0], "=");Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));String newRefreshToken = StringUtils.substringAfterLast(items[2], "=");//重置信息qqProperties.put("accessToken", accessToken);qqProperties.put("expiresIn", String.valueOf(expiresIn));qqProperties.put("refreshToken", newRefreshToken);return qqProperties;}

3.回调方法

@GetMapping("/qqLogin")public void qqLogin(String code,HttpServletRequest request) throws Exception {//获取tocketMap<String, Object> qqProperties = getToken(code);//获取openId(每个用户的openId都是唯一不变的)String openId = getOpenId(qqProperties);/tocket过期刷新tokenMap<String, Object> refreshToken = refreshToken(qqProperties);//获取数据QQUserInfo userInfo = getUserInfo(qqProperties);}

qqLogin回调域可以很灵活的使用,用户主要就是用户登录后可以获取到唯一的id和qq用户的昵称头像等方法,进行操作

前后端分离的回调,可以通过redis和session进行监听用户是否登录成功,从而跳转到登录成功的页面

希望对于不会qq接口调用的同学有帮助
完结撒花~

springboot项目功能之qq登录的实现相关推荐

  1. SpringBoot项目中集成第三方登录功能

    SpringBoot项目中集成第三方登录功能 引言 1 环境准备 2 代码实现 3 第三方平台认证申请 4 打包和部署项目 5 第三方平台登录认证测试 6 参考文章 引言 最近想把自己在公众号上介绍过 ...

  2. SpringBoot+Vue实现第三方QQ登录(一)

    1. QQ登录介绍(测试网站:地址) QQ互联开放平台为第三方网站提供了丰富的API(官网地址: QQ互联官网首页).第三方网站接入QQ互联开放平台后,即可通过调用平台提供的API实现用户使用QQ帐号 ...

  3. SpringBoot+Vue实现第三方QQ登录(二)

    1. 准备工作_OAuth2.0(官网地址:开发流程) 本步骤的作用: 接入QQ登录前,网站需首先进行申请,获得对应的appid与appkey,以保证后续流程中可正确对网站与用户进行验证与授权. 1. ...

  4. django项目学习之QQ登录

    最近在用django框架写一个商城项目(前后端分离),里面用到的一些技术其他项目也可以借鉴,于是就想写一些博客记录,以防自己忘记,今天先写一个关于登录接口中引入QQ登录接口的流程. 关于QQ登录接口的 ...

  5. springboot网站应用使用第三方qq登录

    使用第三方qq登录需要在QQ互联官网https://connect.qq.com/中申请成功才可以使用. 1.登录成功后进入个人设置中心设置个人信息 2.选择公司或个人接入,这里我选择个人接入  3. ...

  6. java mirai-2.15 qq机器人扫码登录或springboot项目进行扫码登录

    请下载我的jar包在项目中引用 [java-mirai-qrcode-0.1.jar] jar包有参考 mirai-console-dev-qrlogin-0.1.7在java中整合 适应mirai版 ...

  7. SpringBoot项目中Druid自动登录

    Druid是什么 2019最受欢迎中国开源软件评选投票https://www.oschina.net/project/top_cn_2019 请参与投票. Druid是Java语言中最好的数据库连接池 ...

  8. SpringBoot项目接入支付宝第三方登录

    今年大四毕业,由于新冠肺炎疫情的原因,开学和毕业答辩的时间都进行了推迟,因此有较多的时间对毕设项目做修改和调整,便在项目中接入微信和支付宝的第三方登录.在此需要说明,微信和支付宝的开发者平台大体一致, ...

  9. QQ登录的那些坑(如何开发qq登陆功能)

    这几天在项目上面实现qq登录的功能,当功能做好后发现,同一个qq号登录之后腾讯返回的openid并不一样....(天啦噜啊~)然后查询文档以及咨询客服才知道注册申请时是有一个固定的套路的(不得不说,如 ...

最新文章

  1. 计算机背景象怎样改变,我的页面背景总是黑色,以前改变过现在好象改变不了?我该怎么改变呢? 爱问知识人...
  2. 自学python还是报班-学习Python报培训班真的有必要吗?
  3. 我感觉这是目前讲得最明白的线性回归的文章了
  4. Windows平台九点提升权限终极技巧
  5. saml java实现_java-saml
  6. mysql无序id怎么优化limit_MYSQL分页limit速度太慢优化方法
  7. Flink on Yarn运行机制
  8. qq代理服务器哪里获取_哪里可以下载小学英语课件?这3个渠道,英语老师得赶快收藏...
  9. easyUI不同版本的combotree控件clear方法的区别
  10. 【Android】PA4D_CH6 使用Internat资源
  11. 创建和使用约束Constraint
  12. 卸载linux订阅包
  13. java---多线程及线程的概念
  14. 影响大数据的数据质量因素
  15. awvs 中文手册详细版
  16. 08cms房产门户系统源码V8.6.1多城市版
  17. Android 开发的五大开源网站
  18. gom及gee小白架设黑屏的原因以及个别装备地图不显示怎么办?
  19. 代码:吃货联盟订餐系统
  20. cvCvtColor 颜色空间转换函数

热门文章

  1. Redis缓存应用场景解析
  2. 最难的22个汉字(转载)
  3. 计算机无限代码出招表,《FATE/无限代码 携带版》系统详解 人物性能研究及连续技视频...
  4. JS常用脚本+html代码大全+对联广告代码效果大全
  5. android的银联支付,android 银联支付Demo
  6. 洛谷3393 逃离僵尸岛
  7. 模仿爱奇艺账号登录限制人数,SpringBoot 并发登录人数控制,踢人功能
  8. 腾讯云校园计划10元1月云主机
  9. 肥鼠兑换记(c++)
  10. 微商城saas系统之新版架构设计