diff --git a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java index a4f5a85..4dd82b8 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java @@ -1493,6 +1493,36 @@ public String userInfo() { public Class getTargetClass() { return null; } + }, + + /** + * 小红书商业开放平台授权登录 + */ + XHS { + @Override + public String authorize() { + return " https://ad-market.xiaohongshu.com/auth"; + } + + @Override + public String accessToken() { + return "https://adapi.xiaohongshu.com/api/open/oauth2/access_token"; + } + + @Override + public String userInfo() { + throw new UnsupportedOperationException("不支持获取用户信息 url"); + } + + @Override + public String refresh() { + return "https://adapi.xiaohongshu.com/api/open/oauth2/refresh_token"; + } + + @Override + public Class getTargetClass() { + return AuthXiaohongshuMarketingRequest.class; + } } } diff --git a/src/main/java/me/zhyd/oauth/enums/scope/AuthXiaohongshuMarketingScope.java b/src/main/java/me/zhyd/oauth/enums/scope/AuthXiaohongshuMarketingScope.java new file mode 100644 index 0000000..345ddb2 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/enums/scope/AuthXiaohongshuMarketingScope.java @@ -0,0 +1,29 @@ +package me.zhyd.oauth.enums.scope; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 小红书商业平台 OAuth 授权范围 + * + * @author yangjiahao + * @version 1.0.0 + * @since 1.0.0 + */ +@Getter +@AllArgsConstructor +public enum AuthXiaohongshuMarketingScope implements AuthScope { + + /** + * {@code scope} 含义,以{@code description} 为准 + */ + report_service("report_service", "获取账户报表信息", true), + ad_query("ad_query", "获取推广计划、推广单元、推广创意信息", false), + ad_manage("ad_manage", "创建&修改推广计划、推广单元、推广创意", false), + account_manage("account_manage", "账户管理", false); + + private final String scope; + private final String description; + private final boolean isDefault; + +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthXiaohongshuMarketingRequest.java b/src/main/java/me/zhyd/oauth/request/AuthXiaohongshuMarketingRequest.java new file mode 100644 index 0000000..a6b75fb --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthXiaohongshuMarketingRequest.java @@ -0,0 +1,116 @@ +package me.zhyd.oauth.request; + +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.enums.AuthResponseStatus; +import me.zhyd.oauth.enums.scope.AuthXiaohongshuMarketingScope; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.AuthScopeUtils; +import me.zhyd.oauth.utils.HttpUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +import java.util.HashMap; +import java.util.Map; + +/** + * 小红书商业开放平台登录 + *

+ * 小红书商业开放平台 + * + * @author yangjiahao + * @since 2024-10-08 + */ +public class AuthXiaohongshuMarketingRequest extends AuthDefaultRequest { + public AuthXiaohongshuMarketingRequest(AuthConfig config) { + super(config, AuthDefaultSource.XHS); + } + + public AuthXiaohongshuMarketingRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthDefaultSource.XHS, authStateCache); + } + + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + * @since 1.9.3 + */ + @Override + public String authorize(String state) { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("appId", config.getClientId()) + .queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthXiaohongshuMarketingScope.values()))) + .queryParam("redirectUri", config.getRedirectUri()) + .queryParam("state", getRealState(state)) + .build(); + } + + @Override + public AuthToken getAccessToken(AuthCallback authCallback) { + + Map params = new HashMap<>(7); + params.put("app_id", config.getClientId()); + params.put("secret", config.getClientSecret()); + params.put("code", authCallback.getCode()); + String response = new HttpUtils(config.getHttpConfig()).post(source.accessToken(), params, false).getBody(); + JSONObject object = JSONObject.parseObject(response); + + this.checkResponse(object); + + return AuthToken.builder() + .accessToken(object.getString("access_token")) + .expireIn(object.getIntValue("access_token_expires_in")) + .refreshToken(object.getString("refresh_token")) + .scope(object.getString("scope")) + .build(); + } + + @Override + public AuthUser getUserInfo(AuthToken authToken) { + throw new UnsupportedOperationException("不支持获取用户信息 url"); + } + + @Override + public AuthResponse refresh(AuthToken oldToken) { + Map form = new HashMap<>(7); + form.put("app_id", config.getClientId()); + form.put("secret", config.getClientSecret()); + form.put("refresh_token", oldToken.getRefreshToken()); + + String response = new HttpUtils(config.getHttpConfig()).post(source.refresh(), form, false).getBody(); + JSONObject object = JSONObject.parseObject(response); + + this.checkResponse(object); + + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(AuthToken.builder() + .accessToken(object.getString("access_token")) + .refreshToken(object.getString("refresh_token")) + .scope(object.getString("scope")) + .expireIn(object.getIntValue("expires_in")) + .build()) + .build(); + } + + + /** + * 检查响应内容是否正确 + * + * @param + */ + private void checkResponse(JSONObject object) { + if (object.getIntValue("code") != 0) { + throw new AuthException(object.getString("msg")); + } + } + + +}