プロジェクト分析 NiterForum (3) サードパーティ サービスとキャッシュ

サードパーティサービス

アリ

主に情報入手のため

以下を使用してリクエストOkHttpClientを作成します: OkhttpClient_workingman_li のブログの詳細な使用法 - CSDN ブログ

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.14.1</version>
</dependency>

百度クラウド

主に Baidu Cloud のコンテンツ レビュー サービス:

// 初始化一个AipImageCensor
AipContentCensor client = new AipContentCensor(APP_ID, API_KEY, SECRET_KEY);
JSONObject response = client.textCensorUserDefined(text);

サインインするには

AuthorizeController設定ファイルを読み込み、コールバック関数を記述します。

百度

Baidu サービスを呼び出すには、Baidu アクセス トークンが必要です。

同様に、次を使用してリクエストOkHttpClientを送信します

MediaType mediaType = MediaType.get("application/x-www-form-urlencoded; charset=utf-8"); // 媒体类型,决定浏览器将以什么形式、什么编码对资源进行解析
OkHttpClient client = new OkHttpClient();
String s = "grant_type="+baiduAccessTokenDTO.getGrant_type()+"&code="+baiduAccessTokenDTO.getCode()+"&client_id="+baiduAccessTokenDTO.getClient_id()+"&client_secret="+baiduAccessTokenDTO.getClient_secret()+"&redirect_uri="+baiduAccessTokenDTO.getRedirect_uri(); // baiduAccessTokenDTO含有配置文件中的百度登录API配置信息
RequestBody body = RequestBody.create(mediaType, s); // 准备请求体
Request request = new Request.Builder()
    .url("https://openapi.baidu.com/oauth/2.0/token")
    .post(body)
    .build();
Response response = client.newCall(request).execute(); // 发送请求,返回带有access token的json

次に、取得したアクセス トークンを使用してリクエストを送信します。

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
	.url("https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token=" + accessToken)
	.build();
Response response = client.newCall(request).execute(); // 返回带有个人信息的BaiduUserDTO对象

ギットハブ

Baidu と似ていますが、取得したアクセス トークンはリクエストを送信するヘッダーに配置されます。

OkHttpClient client = new OkHttpClient();
/*
*使用参数的方式明文传输,并不推荐,即将被Github废弃
Request request = new Request.Builder()
    .url("https://api.github.com/user?access_token=" + accessToken)
    .build();
*/

//作为header中的参数传输,强烈推荐
Request request = new Request.Builder()
    .url("https://api.github.com/user")
    .header("Authorization","token "+accessToken)
    .build();

Qq

最初にアクセス トークンを取得し、次に openID を取得し、最後に qq で接続されたアクセス トークン、openID、clientId を通じてユーザー情報を取得します。

開発者はopenIDを通じてユーザーの基本情報を取得できます。特に、開発者が複数のモバイルアプリケーションとウェブサイトアプリケーションを持っている場合、同じQQインターネットプラットフォーム上の異なるアプリケーションである限り、unionIDは同じであるため、ユーザーのunionIDを取得することによってユーザーの一意性を区別できます。言い換えると、同じユーザーの UnionID は、同じ QQ インターネット プラットフォーム上の異なるアプリケーションでも同じです。

public String getOpenID(String accessToken) {
    
    
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://graph.qq.com/oauth2.0/me?access_token=" + accessToken)
                .build();
        try {
    
    
            Response response = client.newCall(request).execute();
            String string = response.body().string();
            String jsonString = string.split(" ")[1].split(" ")[0];
            //System.out.println(jsonString);
            JSONObject obj = JSONObject.parseObject(jsonString);
            String openid = obj.getString("openid");
            //System.out.println(openid);
            return openid;
        } catch (IOException e) {
    
    
        }
        return null;
    }


    public String getUnionId(String accessToken) {
    
    
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://graph.qq.com/oauth2.0/me?access_token="+accessToken+"&unionid=1")
                .build();
        try {
    
    
            Response response = client.newCall(request).execute();
            String string = response.body().string();
            System.out.println("rs:"+string);
            String jsonString = string.split(" ")[1].split(" ")[0];
            System.out.println(jsonString);
            JSONObject obj = JSONObject.parseObject(jsonString);
            String unionid = obj.getString("unionid");
            System.out.println(unionid);
            return unionid;
        } catch (IOException e) {
    
    
        }
        return null;
    }

微博

百度と同じ

吉光

主にJiguang SMSサービス

ユーザーが携帯電話番号を入力すると、携帯電話番号、IP、ログイン トークンがバックエンドPhoneController#getPhoneCodeメソッド

まず認証コードSMSJiGuangProvider#testSendSMSCodeを送信し(設定が必要です)、次に送信結果を処理します(送信が成功したかどうかを判断します)。

その後、ユーザーは検証コードを送信し、PhoneController#validCodeメソッド、JiGuangProvider#testSendValidSMSCodeメソッドを呼び出し、検証リクエストを送信しsmsclient.sendValidSMSCode(msg_id, code)、結果を取得した後、ユーザーが送信した検証コードのステータスを判断して処理します(携帯電話をバインドするか、登録してログに記録します)の)

ユーザーが Jiguang ワンキー ログインを選択すると、PhoneController#loginTokenVerifyメソッドをJiGuangProvider#loginTokenVerify入力して呼び出します。このメソッドは、Jiguang のワンキー ログイン API を呼び出すための http リクエストを送信し、フロントエンドから渡されたログイン トークンを送信します。API は暗号化された携帯電話を返します。検証後に番号を取得し、JiGuangProvider#decryptPKCS8EncodedKeySpec を使用してそれを復号化する権限を呼び出します。

JAVA——RSA 暗号化 [X509EncodedKeySpec、PKCS8EncodedKeySpec、RSAPublicKeySpec、RSAPrivateKeySpec] - Starzkg のブログ - CSDN ブログ

Qクラウド

テンセントクラウドサービス

COS

画像がアップロードされるときに呼び出されます。

String url = qCloudProvider.upload(inputStream,contentType,user,contentLength);

アップロードでは、uploadtoBucket が呼び出され、ファイルをバケットに保存します。

public String uploadtoBucket(InputStream inputStream, String fileType, String contentType, UserDTO user, String fileName, Long contentLength){
    
    

    // 1 初始化用户身份信息(secretId, secretKey)
    COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
    // 2 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224
    ClientConfig clientConfig = new ClientConfig(new Region(region));
    // 3 生成cos客户端
    COSClient cosclient = new COSClient(cred, clientConfig);

    String key = "upload/user/"+user.getId()+"/"+fileType+"/"+fileName; // bucket中路径

    ObjectMetadata objectMetadata = new ObjectMetadata();
    // 从输入流上传必须制定content length, 否则http客户端可能会缓存所有数据,存在内存OOM的情况
    objectMetadata.setContentLength(contentLength);
    // 默认下载时根据cos路径key的后缀返回响应的contenttype, 上传时设置contenttype会覆盖默认值
    objectMetadata.setContentType("contentType");

    PutObjectRequest putObjectRequest =
    	new PutObjectRequest(bucketName, key, inputStream, objectMetadata);
    // 设置存储类型, 默认是标准(Standard), 低频(standard_ia)
    putObjectRequest.setStorageClass(StorageClass.Standard);
    try {
    
    
        PutObjectResult putObjectResult = cosclient.putObject(putObjectRequest);
        // putobjectResult会返回文件的etag
        String etag = putObjectResult.getETag();
        //System.out.println(etag);
    } catch (CosServiceException e) {
    
    
        //e.printStackTrace();
        log.error("upload error,{}", key, e);
        throw new CustomizeException(CustomizeErrorCode.FILE_UPLOAD_FAIL);
    } catch (CosClientException e) {
    
    
        //e.printStackTrace();
        log.error("upload error,{}", key, e);
        throw new CustomizeException(CustomizeErrorCode.FILE_UPLOAD_FAIL);
    }
    // 关闭客户端
    cosclient.shutdown();
    return objecturl+key;
}

テンセントSMS

public String sendSms(String session, String phone){
    
    

    try {
    
    
        /* 必要步骤:
        * 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId 和 secretKey
        * 本示例采用从环境变量读取的方式,需要预先在环境变量中设置这两个值
        * CAM 密钥查询:https://console.cloud.tencent.com/cam/capi*/
        Credential cred = new Credential(secretId, secretKey);

        /* 非必要步骤:
        * 实例化一个客户端配置对象,可以指定超时时间等配置 */
        HttpProfile httpProfile = new HttpProfile();
        ClientProfile clientProfile = new ClientProfile();
        
        /* SDK 默认用 TC3-HMAC-SHA256 进行签名
        * 非必要请不要修改该字段 */
        clientProfile.setSignMethod("HmacSHA256");
        clientProfile.setHttpProfile(httpProfile);
        
        /* 实例化 SMS 的 client 对象
        * 第二个参数是地域信息,可以直接填写字符串 ap-guangzhou,或者引用预设的常量 */
        SmsClient client = new SmsClient(cred, "",clientProfile);
        
        /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
        * 您可以直接查询 SDK 源码确定接口有哪些属性可以设置
        * 属性可能是基本类型,也可能引用了另一个数据结构*/
        SendSmsRequest req = new SendSmsRequest();

        /* 填充请求参数,这里 request 对象的成员变量即对应接口的入参
        * 您可以通过官网接口文档或跳转到 request 对象的定义处查看请求参数的定义
        * 基本类型的设置:
        * 帮助链接:
        * 短信控制台:https://console.cloud.tencent.com/smsv2
        * sms helper:https://cloud.tencent.com/document/product/382/3773 */

        /* 短信应用 ID: 在 [短信控制台] 添加应用后生成的实际 SDKAppID,例如1400006666 */
        //String appid = "1400009099";
        req.setSmsSdkAppid(smsAppId);

        /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,可登录 [短信控制台] 查看签名信息 */
        //String sign = "签名内容";
        req.setSign(smsSign);

        /* 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
        //String session = "xxx";
        req.setSessionContext(session);

        /* 模板 ID: 必须填写已审核通过的模板 ID,可登录 [短信控制台] 查看模板 ID */
        //String templateID = "400000";
        req.setTemplateID(smsTempId);

        /* 下发手机号码,采用 e.164 标准,+[国家或地区码][手机号]
        * 例如+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/
        String[] phoneNumbers = {
    
    "+86"+phone};
        req.setPhoneNumberSet(phoneNumbers);

        /* 模板参数: 若无模板参数,则设置为空*/
        String code = String.valueOf(new Random().nextInt(899999) + 100000);
        String[] templateParams = {
    
    smsSign+session,code,"5"};
        req.setTemplateParamSet(templateParams);

        /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
        * 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */
        SendSmsResponse res = client.SendSms(req);

        // 输出 JSON 格式的字符串回包
        System.out.println(SendSmsResponse.toJsonString(res));
        SendStatus[] sendStatusSet = res.getSendStatusSet();
        for (SendStatus sendStatus : sendStatusSet) {
    
    
            if("Ok".equals(sendStatus.getCode())){
    
    
                temporaryCache.putPhoneCode(
                    sendStatus.getSerialNo(), sendStatus.getPhoneNumber()+code);
            }
        return SendSmsResponse.toJsonString(sendStatus);
        }

        // 可以取出单个值,您可以通过官网接口文档或跳转到 response 对象的定义处查看返回字段的定义
        //System.out.println(res.getRequestId());

    } catch (TencentCloudSDKException e) {
    
    
    	e.printStackTrace();
    }
    return null;
}

ティニファイ

<dependency>
  <groupId>com.tinify</groupId>
  <artifactId>tinify</artifactId>
  <version>RELEASE</version>
</dependency>

Tinify API を使用すると、JPEG および PNG 画像を圧縮して最適化できます。REST サービス (軽量 Web サービス) に基づいて設計されています。私たちはさまざまな言語でクライアント ライブラリを維持しているため、Tinify API とのやり取りが非常に簡単になります。

Tinify Java API 中国語リファレンス ドキュメント - プログラマー募集

ここでは、に要素があるかどうかをTinifyPngTasks#tinifyPngSchedule定期的にTinifyPngCacheチェックするスケジュールされたタスクが定義されています。List<TinifyPngDTO> images

についてはTinifyPngCache、画像圧縮を有効にしないと意味がありませんが、有効にすると、各画像が COS バケットにアップロードされた後images

TinifyPngTasks#tinifyPngSchedule時々、imagesその中の要素 (画像 URL) が圧縮され、Tinify API を通じて InputStream に変換されます。

@Value("${tinify.key}")
private String tinifyKey;

public InputStream getStreamfromUrl(String url){
    
    
    Tinify.setKey(tinifyKey);
    InputStream inputStream=null;
    try {
    
    
        byte[] resultData = Tinify.fromUrl(url).toBuffer(); // toBuffer()压缩图像
        inputStream = new ByteArrayInputStream(resultData);
    } catch (IOException e) {
    
    
    	e.printStackTrace();
    }
    return inputStream;
}

次に、uploadtoBucket を呼び出して圧縮画像ファイルを COS に保存し、imagesその中のすべてのファイルがアップロードされたらファイルをクリアします。

バプチャ

人による検査

ValidateController#postメソッドを呼び出してvaptcha サーバー (OkHttpClient) にリクエストVaptchaProvider.getValidateResult(token,scene,ip)送信し、リクエストが成功したかどうかを含む json 文字列を返します。

public static String getValidateResult(String token,int scene,String ip){
    
    
    MediaType mediaType = MediaType.get("application/x-www-form-urlencoded; charset=utf-8");
    OkHttpClient client = new OkHttpClient();
    String s = "id="+vid+"&secretkey="+key+"&token="+token+"&scene="+scene+"&ip="+ip;
    RequestBody body = RequestBody.create(mediaType, s);
    Request request = new Request.Builder()
        .url("http://0.vaptcha.com/verify")
        .post(body)
        .build();
    try (Response response = client.newCall(request).execute()) {
    
    
        String string = response.body().string();
        return string;
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return null;
}

キャッシュ

アプリユーザーキャッシュ

ExpiringMap<String,String> map = ExpiringMap.builder()
    .maxSize(100)//最大容量,防止恶意注入
    .expiration(1, TimeUnit.MINUTES)//过期时间1分钟
    .expirationPolicy(ExpirationPolicy.ACCESSED)
    .variableExpiration()
    .build();

これは、携帯電話にログインするユーザーをキャッシュし、コードをスキャンしてログインするプロセス中にログイン検証を実行するために使用されます。

ホットタグキャッシュ

private List<String> hots = new ArrayList<>();

クラスには、優先キューを使用してキャッシュを更新するメソッドがありますPriorityQueue

Java PriorityQueue (優先キュー) - 2Tree のブログ - CSDN ブログ

  • PriorityQueue は無制限のキューであり、動的に増加します。デフォルトの初期容量は、'11'対応するコンストラクターのinitialCapacityパラメーターを使用してオーバーライドできます。
  • NULL オブジェクトは許可されません。
  • PriorityQueue に追加されるオブジェクトは同等である必要があります。
  • デフォルトでは、優先キュー オブジェクトは自然な順序で並べ替えられます
  • コンパレータは、キュー内のオブジェクトのカスタム順序付けに使用できます。
  • 優先キューの先頭は、自然順序付けまたはコンパレータベースの順序付けに基づく最小の要素です。キューをポーリングすると、キューから先頭オブジェクトが返されます。
  • 同じ優先度を持つオブジェクトが複数ある場合、それらのいずれかをランダムにポーリングできます。
  • PriorityQueueはスレッドセーフではありませんPriorityBlockingQueue同時環境で使用されます。
  • 追加メソッドとポーリングメソッドに **O(log(n))** の時間がかかります。

DTO はComparableインターフェイスを実装しており、それを優先キューに入れるときに比較戦略を指定できます。

@Data
public class HotTagDTO implements Comparable {
    
    
    private String name;
    private Integer priority;

    @Override
    public int compareTo(Object o) {
    
    
        return this.getPriority() - ((HotTagDTO) o).getPriority();
    }
}

まず Map タグを優先キューに格納し、次にそれらを 1 つずつ取り出して新しい List に追加し、次に新しい List を指すホットを指定します (書き込み時にコピーするという考えがここで使用されています)。

同様に、時間指定タスクを使用してキャッシュを更新するメソッドを呼び出します。

IpLimitキャッシュ

ExpiringMap<String,String> interval = ExpiringMap.builder()
    .maxSize(20)//设置最大容量,增大攻击难度,值越大存储的可疑ip越多,过大会占用额外资源
    .expiration(30, TimeUnit.SECONDS)//过期时间30秒
    .expirationPolicy(ExpirationPolicy.CREATED)//每次访问重置过期时间
    .variableExpiration()
    .build();

ExpiringMap<String,Integer> ipScores = ExpiringMap.builder()
    .maxSize(100)//设置最大容量,增大攻击难度,值越大存储的可疑ip越多,过大会占用额外资源
    .expiration(1, TimeUnit.DAYS)//过期时间1天
    .expirationPolicy(ExpirationPolicy.CREATED)//每次更新重置过期时间
    .variableExpiration()
    .build();

interval<ip, token>IP ブラックリストです。もちろん、このブラックリストには有効期限があります。指定された時間 (30 秒) 以内は、同じ IP を繰り返し確認することはできません

ipScores<ip, score>これは IP スコアリング テーブルです。ユーザーが操作を送信するたびに、バックエンドはそのテーブルinterval内でその操作を検出するか、IP のスコアが 100 を超える場合は、ポイントを追加して例外を返します。

ログインユーザーキャッシュ

private List<User> loginUsers = new ArrayList<>();

ExpiringMap<Long,Long> loginUserMap = ExpiringMap.builder()
    .maxSize(16)//最大容量,防止恶意注入
    .expiration(11, TimeUnit.MINUTES)//过期时间10分钟
    .expirationPolicy(ExpirationPolicy.CREATED)
    .variableExpiration()
    .build();

講演には関連する分析があります

タグキャッシュ

静的メソッドは、すべてのプリセットタグを含むリストを生成します。

一時キャッシュ

2 つはExpiringMapそれぞれ電話と電子メールの確認コードをキャッシュするために使用されます

TinifyPngCache

サードパーティ サービスの章「Tinify」を参照してください。

おすすめ

転載: blog.csdn.net/Falling_Asteroid/article/details/130121488