文章目录
第三方服务
Ali
主要是获取资讯
通过使用 OkHttpClient
进行请求:OkhttpClient的使用详解_workingman_li的博客-CSDN博客
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.1</version>
</dependency>
BaiduCloud
主要是百度云的内容审核服务:
// 初始化一个AipImageCensor
AipContentCensor client = new AipContentCensor(APP_ID, API_KEY, SECRET_KEY);
JSONObject response = client.textCensorUserDefined(text);
第三方登录
在 AuthorizeController
里进行配置文件的读取和回调函数的编写
Baidu
要想调用百度服务需要一个百度的access token:
同理,使用 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
之后带着获取的access token去发请求即可:
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对象
Github
与百度类似,但是获取的access token是放在header里去发请求的:
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();
先获取access token,然后再获得openID,最终通过access token、openID和qq互联的clientId获取用户信息
开发者可通过openID来获取用户的基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用,可通过获取用户的unionID来区分用户的唯一性,因为只要是同一QQ互联平台下的不同应用,unionID是相同的。换句话说,同一用户,对同一个QQ互联平台下的不同应用,unionID是相同的
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
主要是极光短信服务
用户输入手机号后,手机号和ip、登录token一起送至后端 PhoneController#getPhoneCode
方法
首先 JiGuangProvider#testSendSMSCode
发一个验证码短信(需有配置),然后对发送后的结果进行处理(判定是否发送成功)
之后用户提交验证码,进入 PhoneController#validCode
方法,其调用 JiGuangProvider#testSendValidSMSCode
方法,发送验证的请求 smsclient.sendValidSMSCode(msg_id, code)
,获得结果后判断、处理用户提交验证码状态的状态(绑定手机或注册登录)
当用户选择极光一键登录时,进入 PhoneController#loginTokenVerify
方法,其调用 JiGuangProvider#loginTokenVerify
,该方法发送http请求调用极光的一键登录api,将前端传入的login token发过去,api验证后返回加密的手机号码,然后调用JiGuangProvider#decrypt
对其解密,其使用PKCS8EncodedKeySpec
QCloud
腾讯云服务
COS
上传图片时调用:
String url = qCloudProvider.upload(inputStream,contentType,user,contentLength);
upload 内又调用 uploadtoBucket 把文件存入 Bucket :
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;
}
腾讯短信
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;
}
Tinify
<dependency>
<groupId>com.tinify</groupId>
<artifactId>tinify</artifactId>
<version>RELEASE</version>
</dependency>
Tinify API允许您压缩和优化JPEG与PNG图像。 它是基于Rest服务设计的(轻量级web服务)。我们维护各种语言的客户端库,使其与Tinify API进行交互变得非常简单。
Tinify Java API中文参考文档_弃天笑的博客-CSDN博客
在这里,定义了一个定时任务TinifyPngTasks#tinifyPngSchedule
用于定时检查 TinifyPngCache
中的 List<TinifyPngDTO> images
里是否有元素。
关于 TinifyPngCache
,如果不开启图片压缩,那就没用,开启时会在每次把图片上传到COS桶后把图片url存入 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
里的全部上传,就清除
Vaptcha
人机检验
在ValidateController#post
方法中调用VaptchaProvider.getValidateResult(token,scene,ip)
向vaptcha服务器发请求(OkHttpClient),返回的是包含是否通过的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;
}
缓存
AppUserCache
ExpiringMap<String,String> map = ExpiringMap.builder()
.maxSize(100)//最大容量,防止恶意注入
.expiration(1, TimeUnit.MINUTES)//过期时间1分钟
.expirationPolicy(ExpirationPolicy.ACCESSED)
.variableExpiration()
.build();
用于缓存在手机端登录的用户,进而在扫码登录的过程中进行登录检验。
HotTagCache
private List<String> hots = new ArrayList<>();
类里有一个方法用于更新缓存,用到了优先队列PriorityQueue
:
Java PriorityQueue(优先队列)_2Tree的博客-CSDN博客
- PriorityQueue是一个无限制的队列,并且动态增长。默认初始容量
'11'
可以使用相应构造函数中的initialCapacity参数覆盖。- 它不允许NULL对象。
- 添加到PriorityQueue的对象必须具有可比性。
- 默认情况下,优先级队列的对象按自然顺序排序。
- 比较器可用于队列中对象的自定义排序。
- 优先级队列的头部是基于自然排序或基于比较器的排序的最小元素。当我们轮询队列时,它从队列中返回头对象。
- 如果存在多个具有相同优先级的对象,则它可以随机轮询其中任何一个。
- PriorityQueue 不是线程安全的。
PriorityBlockingQueue
在并发环境中使用。- 它为add和poll方法提供了**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 tags存入优先队列,然后依次取出加入一个新List,之后指定hots指向新List(此处运用了写入时拷贝的思想)。
同样,通过定时任务的方式调用更新缓存的方法
IpLimitCache
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的黑名单,当然这个黑名单是有过期时间的,在规定时间内(30s),同一ip不可重复验证
ipScores<ip, score>
是一个ip的计分表,每次用户发来操作,后端都在interval
里找到了它,或者一个ip的分数超过了100,那么会加分并且返回异常
LoginUserCache
private List<User> loginUsers = new ArrayList<>();
ExpiringMap<Long,Long> loginUserMap = ExpiringMap.builder()
.maxSize(16)//最大容量,防止恶意注入
.expiration(11, TimeUnit.MINUTES)//过期时间10分钟
.expirationPolicy(ExpirationPolicy.CREATED)
.variableExpiration()
.build();
在说说篇有相关分析
TagCache
静态方法生成一个包含所有预设tag的List
TemporaryCache
两个ExpiringMap
分别用于缓存电话、邮件验证码
TinifyPngCache
见第三方服务篇Tinify