【转载】spring-security-oauth2(十三 ) 微信登录

微信登陆和QQ登陆大致流程一致,只是有些api不一样,主要是QQ的getUserInfo微信多了一个参数openId。这是因为微信互联文档中在OAuth2.0的认证流程示意图第五步时,微信的openid 同access_token一起返回。而Spring Social获取access_token的类AccessGrant.java中没有openid

微信暂时没有申请测试账户,先上代码后续网站备案下来再自行测试

这里也分三个模块进行开发  api  connect config

api


 
 
  1. package com.rui.tiger.auth.core.social.wechat.api;
  2. /**
  3. * 微信用户api接口
  4. * @author CaiRui
  5. * @Date 2019-01-12 12:08
  6. */
  7. public interface WechatApi {
  8. /**
  9. * 获取微信用户信息
  10. * @param openId
  11. * @return
  12. */
  13. WechatUserInfo getUserInfo(String openId);
  14. }

 
 
  1. package com.rui.tiger.auth.core.social.wechat.api;
  2. import com.alibaba.fastjson.JSON;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.commons.lang.StringUtils;
  5. import org.springframework.http.converter.HttpMessageConverter;
  6. import org.springframework.http.converter.StringHttpMessageConverter;
  7. import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
  8. import org.springframework.social.oauth2.TokenStrategy;
  9. import java.nio.charset.Charset;
  10. import java.util.List;
  11. /**
  12. * @author CaiRui
  13. * @Date 2019-01-12 12:24
  14. */
  15. @Slf4j
  16. public class WechatApiImpl extends AbstractOAuth2ApiBinding implements WechatApi {
  17. /**
  18. * 获取用户信息的url
  19. * https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
  20. * access_token 父类会帮我们拼接
  21. */
  22. private static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";
  23. /**
  24. * @param accessToken
  25. */
  26. public WechatApiImpl(String accessToken) {
  27. super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
  28. }
  29. /**
  30. * 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。
  31. */
  32. protected List<HttpMessageConverter<?>> getMessageConverters() {
  33. List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
  34. messageConverters.remove( 0);
  35. messageConverters.add( new StringHttpMessageConverter(Charset.forName( "UTF-8")));
  36. return messageConverters;
  37. }
  38. @Override
  39. public WechatUserInfo getUserInfo(String openId) {
  40. String url = URL_GET_USER_INFO + openId;
  41. String response = getRestTemplate().getForObject(url, String.class);
  42. if(StringUtils.contains(response, "errcode")) {
  43. log.info( "微信用户信息获取失败:"+response);
  44. return null;
  45. }
  46. WechatUserInfo profile = null;
  47. try {
  48. profile = JSON.parseObject(response, WechatUserInfo.class);
  49. } catch (Exception e) {
  50. log.info( "微信用户信息json转换异常",e);
  51. }
  52. return profile;
  53. }
  54. }

微信社交用户信息封装


 
 
  1. package com.rui.tiger.auth.core.social.wechat.api;
  2. import lombok.Data;
  3. /**
  4. * 微信用户信息
  5. * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316518&token=&lang=zh_CN}
  6. * @author CaiRui
  7. * @Date 2019-01-12 12:13
  8. */
  9. @Data
  10. public class WechatUserInfo {
  11. /**
  12. * 普通用户的标识,对当前开发者帐号唯一
  13. */
  14. private String openid;
  15. /**
  16. * 普通用户昵称
  17. */
  18. private String nickname;
  19. /**
  20. * 语言
  21. */
  22. private String language;
  23. /**
  24. * 普通用户性别,1为男性,2为女性
  25. */
  26. private String sex;
  27. /**
  28. * 普通用户个人资料填写的省份
  29. */
  30. private String province;
  31. /**
  32. * 普通用户个人资料填写的城市
  33. */
  34. private String city;
  35. /**
  36. * 国家,如中国为CN
  37. */
  38. private String country;
  39. /**
  40. * 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
  41. */
  42. private String headimgurl;
  43. /**
  44. * 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
  45. */
  46. private String[] privilege;
  47. /**
  48. * 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
  49. */
  50. private String unionid;
  51. }

connect 

微信的返回比标准的多了个openId


 
 
  1. package com.rui.tiger.auth.core.social.wechat.connect;
  2. import org.springframework.social.oauth2.AccessGrant;
  3. /**
  4. * 微信的access_token信息。与标准OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToke换取openId的服务
  5. * 所以在这里继承了标准AccessGrant,添加了openId字段,作为对微信access_token信息的封装。
  6. * @author CaiRui
  7. * @Date 2019-01-12 13:08
  8. */
  9. public class WechatAccessGrant extends AccessGrant {
  10. private String openId;
  11. public WechatAccessGrant() {
  12. super( "");
  13. }
  14. public WechatAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
  15. super(accessToken, scope, refreshToken, expiresIn);
  16. }
  17. /**
  18. * @return the openId
  19. */
  20. public String getOpenId() {
  21. return openId;
  22. }
  23. /**
  24. * @param openId the openId to set
  25. */
  26. public void setOpenId(String openId) {
  27. this.openId = openId;
  28. }
  29. }

api适配器


 
 
  1. package com.rui.tiger.auth.core.social.wechat.connect;
  2. import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
  3. import com.rui.tiger.auth.core.social.wechat.api.WechatUserInfo;
  4. import org.springframework.social.connect.ApiAdapter;
  5. import org.springframework.social.connect.ConnectionValues;
  6. import org.springframework.social.connect.UserProfile;
  7. /**
  8. * 微信 api适配器,将微信 api的数据模型转为spring social的标准模型。
  9. * @author CaiRui
  10. * @Date 2019-01-12 13:34
  11. */
  12. public class WechatApiAdapter implements ApiAdapter<WechatApi> {
  13. private String openId;
  14. public WechatApiAdapter() {}
  15. public WechatApiAdapter(String openId){
  16. this.openId = openId;
  17. }
  18. /**
  19. * @param api
  20. * @return
  21. */
  22. @Override
  23. public boolean test(WechatApi api) {
  24. return true;
  25. }
  26. /**
  27. * @param api
  28. * @param values
  29. */
  30. @Override
  31. public void setConnectionValues(WechatApi api, ConnectionValues values) {
  32. WechatUserInfo profile = api.getUserInfo(openId);
  33. values.setProviderUserId(profile.getOpenid());
  34. values.setDisplayName(profile.getNickname());
  35. values.setImageUrl(profile.getHeadimgurl());
  36. }
  37. /**
  38. * @param api
  39. * @return
  40. */
  41. @Override
  42. public UserProfile fetchUserProfile(WechatApi api) {
  43. return null;
  44. }
  45. /**
  46. * @param api
  47. * @param message
  48. */
  49. @Override
  50. public void updateStatus(WechatApi api, String message) {
  51. //do nothing
  52. }
  53. }

 
 
  1. package com.rui.tiger.auth.core.social.wechat.connect;
  2. import com.alibaba.fastjson.JSON;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.commons.collections.MapUtils;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.springframework.http.converter.StringHttpMessageConverter;
  7. import org.springframework.social.oauth2.AccessGrant;
  8. import org.springframework.social.oauth2.OAuth2Parameters;
  9. import org.springframework.social.oauth2.OAuth2Template;
  10. import org.springframework.util.MultiValueMap;
  11. import org.springframework.web.client.RestTemplate;
  12. import java.nio.charset.Charset;
  13. import java.util.Map;
  14. /**
  15. * 完成微信的OAuth2认证流程的模板类。国内厂商实现的OAuth2每个都不同,
  16. * spring默认提供的OAuth2Template适应不了,只能针对每个厂商自己微调。
  17. * @author CaiRui
  18. * @Date 2019-01-12 13:23
  19. */
  20. @Slf4j
  21. public class WechatOAuth2Template extends OAuth2Template {
  22. private String clientId;
  23. private String clientSecret;
  24. private String accessTokenUrl;
  25. /**
  26. * 获取token
  27. * https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
  28. */
  29. private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
  30. public WechatOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
  31. super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
  32. setUseParametersForClientAuthentication( true);
  33. this.clientId = clientId;
  34. this.clientSecret = clientSecret;
  35. this.accessTokenUrl = accessTokenUrl;
  36. }
  37. /* (non-Javadoc)
  38. * @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap)
  39. */
  40. @Override
  41. public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
  42. MultiValueMap<String, String> parameters) {
  43. StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
  44. accessTokenRequestUrl.append( "?appid="+clientId);
  45. accessTokenRequestUrl.append( "&secret="+clientSecret);
  46. accessTokenRequestUrl.append( "&code="+authorizationCode);
  47. accessTokenRequestUrl.append( "&grant_type=authorization_code");
  48. accessTokenRequestUrl.append( "&redirect_uri="+redirectUri);
  49. return getAccessToken(accessTokenRequestUrl);
  50. }
  51. public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
  52. StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
  53. refreshTokenUrl.append( "?appid="+clientId);
  54. refreshTokenUrl.append( "&grant_type=refresh_token");
  55. refreshTokenUrl.append( "&refresh_token="+refreshToken);
  56. return getAccessToken(refreshTokenUrl);
  57. }
  58. @SuppressWarnings( "unchecked")
  59. private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
  60. log.info( "获取access_token, 请求URL: "+accessTokenRequestUrl.toString());
  61. String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
  62. log.info( "获取access_token, 响应内容: "+response);
  63. Map<String, Object> result = null;
  64. try {
  65. result = JSON.parseObject(response, Map.class);
  66. } catch (Exception e) {
  67. log.error( "微信获取token解析json异常",e);
  68. }
  69. //返回错误码时直接返回空
  70. if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){
  71. String errcode = MapUtils.getString(result, "errcode");
  72. String errmsg = MapUtils.getString(result, "errmsg");
  73. throw new RuntimeException( "获取access token失败, errcode:"+errcode+ ", errmsg:"+errmsg);
  74. }
  75. WechatAccessGrant accessToken = new WechatAccessGrant(
  76. MapUtils.getString(result, "access_token"),
  77. MapUtils.getString(result, "scope"),
  78. MapUtils.getString(result, "refresh_token"),
  79. MapUtils.getLong(result, "expires_in"));
  80. accessToken.setOpenId(MapUtils.getString(result, "openid"));
  81. return accessToken;
  82. }
  83. /**
  84. * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
  85. * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN
  86. */
  87. public String buildAuthenticateUrl(OAuth2Parameters parameters) {
  88. String url = super.buildAuthenticateUrl(parameters);
  89. url=url.replace( "client_id", "appid");
  90. //url = url + "&appid="+clientId+"&scope=snsapi_login";
  91. url = url + "&scope=snsapi_login";
  92. log.info( "微信获取授权码地址url:"+url);
  93. return url;
  94. }
  95. public String buildAuthorizeUrl(OAuth2Parameters parameters) {
  96. return buildAuthenticateUrl(parameters);
  97. }
  98. /**
  99. * 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
  100. */
  101. protected RestTemplate createRestTemplate() {
  102. RestTemplate restTemplate = super.createRestTemplate();
  103. restTemplate.getMessageConverters().add( new StringHttpMessageConverter(Charset.forName( "UTF-8")));
  104. return restTemplate;
  105. }
  106. }

 
 
  1. package com.rui.tiger.auth.core.social.wechat.connect;
  2. import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
  3. import com.rui.tiger.auth.core.social.wechat.api.WechatApiImpl;
  4. import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
  5. /**
  6. * 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用
  7. * @author CaiRui
  8. * @Date 2019-01-12 13:40
  9. */
  10. public class WechatServiceProvider extends AbstractOAuth2ServiceProvider<WechatApi> {
  11. /**
  12. * 微信获取授权码的url
  13. *
  14. * https://open.weixin.qq.com/connect/qrconnect?
  15. * appid=APPID&
  16. * redirect_uri=REDIRECT_URI&
  17. * response_type=code&
  18. * scope=SCOPE&
  19. * state=STATE#wechat_redirect
  20. */
  21. private static final String URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect";
  22. /**
  23. * 微信获取accessToken的url
  24. */
  25. private static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
  26. /**
  27. * @param appId
  28. * @param appSecret
  29. */
  30. public WechatServiceProvider(String appId, String appSecret) {
  31. super( new WechatOAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
  32. }
  33. /* (non-Javadoc)
  34. * @see org.springframework.social.oauth2.AbstractOAuth2ServiceProvider#getApi(java.lang.String)
  35. */
  36. @Override
  37. public WechatApi getApi(String accessToken) {
  38. return new WechatApiImpl(accessToken);
  39. }
  40. }

 
 
  1. package com.rui.tiger.auth.core.social.wechat.connect;
  2. import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
  3. import org.springframework.social.connect.ApiAdapter;
  4. import org.springframework.social.connect.Connection;
  5. import org.springframework.social.connect.ConnectionData;
  6. import org.springframework.social.connect.support.OAuth2Connection;
  7. import org.springframework.social.connect.support.OAuth2ConnectionFactory;
  8. import org.springframework.social.oauth2.AccessGrant;
  9. import org.springframework.social.oauth2.OAuth2ServiceProvider;
  10. /**
  11. * @author CaiRui
  12. * @Date 2019-01-12 13:32
  13. */
  14. public class WechatConnectionFactory extends OAuth2ConnectionFactory<WechatApi> {
  15. /**
  16. * @param appId
  17. * @param appSecret
  18. */
  19. public WechatConnectionFactory(String providerId, String appId, String appSecret) {
  20. super(providerId, new WechatServiceProvider(appId, appSecret), new WechatApiAdapter());
  21. }
  22. /**
  23. * 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
  24. */
  25. @Override
  26. protected String extractProviderUserId(AccessGrant accessGrant) {
  27. if(accessGrant instanceof WechatAccessGrant) {
  28. return ((WechatAccessGrant)accessGrant).getOpenId();
  29. }
  30. return null;
  31. }
  32. /* (non-Javadoc)
  33. * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.oauth2.AccessGrant)
  34. */
  35. public Connection<WechatApi> createConnection(AccessGrant accessGrant) {
  36. return new OAuth2Connection<WechatApi>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),
  37. accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
  38. }
  39. /* (non-Javadoc)
  40. * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.connect.ConnectionData)
  41. */
  42. public Connection<WechatApi> createConnection(ConnectionData data) {
  43. return new OAuth2Connection<WechatApi>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
  44. }
  45. private ApiAdapter<WechatApi> getApiAdapter(String providerUserId) {
  46. return new WechatApiAdapter(providerUserId);
  47. }
  48. private OAuth2ServiceProvider<WechatApi> getOAuth2ServiceProvider() {
  49. return (OAuth2ServiceProvider<WechatApi>) getServiceProvider();
  50. }
  51. }

config


 
 
  1. package com.rui.tiger.auth.core.social.wechat.config;
  2. import com.rui.tiger.auth.core.properties.SecurityProperties;
  3. import com.rui.tiger.auth.core.properties.WechatProperties;
  4. import com.rui.tiger.auth.core.social.wechat.connect.WechatConnectionFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.core.env.Environment;
  9. import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
  10. import org.springframework.social.config.annotation.SocialConfigurerAdapter;
  11. import org.springframework.social.connect.ConnectionFactory;
  12. import org.springframework.social.connect.ConnectionFactoryLocator;
  13. import org.springframework.social.connect.UsersConnectionRepository;
  14. /**
  15. * 微信登陆配置
  16. * @author CaiRui extends SocialConfigurerAdapter
  17. * @Date 2019-01-12 13:57
  18. */
  19. //@Configuration
  20. //@ConditionalOnProperty(prefix = "tiger.auth.social.wechat", name = "app-id")
  21. public class WechatAutoConfiguration extends SocialConfigurerAdapter {
  22. /* @Autowired
  23. private SecurityProperties securityProperties;
  24. @Override
  25. public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
  26. connectionFactoryConfigurer.addConnectionFactory(createConnectionFactory());
  27. }
  28. private ConnectionFactory<?> createConnectionFactory() {
  29. WechatProperties weixinConfig = securityProperties.getSocial().getWechat();
  30. return new WechatConnectionFactory(weixinConfig.getProviderId(), weixinConfig.getAppId(),
  31. weixinConfig.getAppSecret());
  32. }
  33. // 后补:做到处理注册逻辑的时候发现的一个bug:登录完成后,数据库没有数据,但是再次登录却不用注册了
  34. // 就怀疑是否是在内存中存储了。结果果然发现这里父类的内存ConnectionRepository覆盖了SocialConfig中配置的jdbcConnectionRepository
  35. // 这里需要返回null,否则会返回内存的 ConnectionRepository
  36. @Override
  37. public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
  38. return null;
  39. }*/
  40. }

配置文件调整


 
 
  1. package com.rui.tiger.auth.core.properties;
  2. /**
  3. * 微信配置文件
  4. * @author CaiRui
  5. * @Date 2019-01-12 14:00
  6. */
  7. public class WechatProperties {
  8. /**
  9. * 第三方id,用来决定发起第三方登录的url,默认是 weixin。
  10. */
  11. private String providerId = "wechat";
  12. private String appId; //应用id
  13. private String appSecret; //应用密匙
  14. public String getProviderId() {
  15. return providerId;
  16. }
  17. public void setProviderId(String providerId) {
  18. this.providerId = providerId;
  19. }
  20. public String getAppId() {
  21. return appId;
  22. }
  23. public void setAppId(String appId) {
  24. this.appId = appId;
  25. }
  26. public String getAppSecret() {
  27. return appSecret;
  28. }
  29. public void setAppSecret(String appSecret) {
  30. this.appSecret = appSecret;
  31. }
  32. }

放到社交配置中

 添加微信相关配置


 
 
  1. social:
  2. filterProcessesUrl: /auth
  3. qq:
  4. app-id: ***
  5. app-secret: ***
  6. wechat:
  7. app-id: ***
  8. app-secret: ***

前台界面添加微信登陆 tiger-login.html


 
 
  1. <html>
  2. <head>
  3. <meta charset="UTF-8">
  4. <title>登录 </title>
  5. </head>
  6. <body>
  7. <h2>标准登录页面 </h2>
  8. <h3>表单登录 </h3>
  9. <form action="/authentication/form" method="post">
  10. <table>
  11. <tr>
  12. <td>用户名: </td>
  13. <td> <input type="text" name="username"> </td>
  14. </tr>
  15. <tr>
  16. <td>密码: </td>
  17. <td> <input type="password" name="password"> </td>
  18. </tr>
  19. <tr>
  20. <td>图形验证码: </td>
  21. <td>
  22. <input type="text" name="imageCode">
  23. <img src="/captcha/image">
  24. </td>
  25. </tr>
  26. <tr>
  27. <td colspan="2"> <input type="checkbox" name="remember-me" value="true"/>记住我 </td>
  28. </tr>
  29. <tr>
  30. <td colspan="2">
  31. <button type="submit">登录 </button>
  32. </td>
  33. </tr>
  34. </table>
  35. </form>
  36. <h3>短信登录 </h3>
  37. <form action="/authentication/mobile" method="post">
  38. <table>
  39. <tr>
  40. <td>手机号: </td>
  41. <td> <input type="text" name="mobile" value="15026929536"> </td>
  42. </tr>
  43. <tr>
  44. <td>短信验证码: </td>
  45. <td>
  46. <input type="text" name="smsCode">
  47. <a href="/captcha/sms?mobile=13012345678">发送验证码 </a>
  48. </td>
  49. </tr>
  50. <tr>
  51. <td colspan="2"> <button type="submit">登录 </button> </td>
  52. </tr>
  53. </table>
  54. </form>
  55. <!--<h3>社交登录</h3>
  56. &lt;!&ndash;不支持get请求 /auth/qq 默认是这个请求 /login/qq &ndash;&gt;
  57. <form action="/auth/qq" method="post">
  58. <button type="submit">QQ登录</button>
  59. &nbsp;&nbsp;&nbsp;&nbsp;
  60. <a href="/qqLogin/weixin">微信登录</a>
  61. </form>-->
  62. <h3>社交登录 </h3>
  63. <a href="/auth/qq">QQ登录 </a>
  64. &nbsp;&nbsp;&nbsp;&nbsp;
  65. <a href="/auth/wechat">微信登录 </a>
  66. </body>
  67. </html>

ok 微信登陆的代码都写完了 ,等待后续测试

文章转载至:https://blog.csdn.net/ahcr1026212/article/details/86247943

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值