This commit is contained in:
dengqn 2024-07-23 00:03:08 +08:00
parent ff3e0de135
commit 798e01b43f
3 changed files with 163 additions and 53 deletions

View File

@ -1,37 +1,45 @@
package com.dengqn.igps2xingzhe.controller; package com.dengqn.igps2xingzhe.controller;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA; import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.dengqn.igps2xingzhe.config.IGPSport; import com.dengqn.igps2xingzhe.config.IGPSport;
import com.dengqn.igps2xingzhe.config.XingZhe; import com.dengqn.igps2xingzhe.config.XingZhe;
import com.dengqn.igps2xingzhe.vo.IGPSActivity;
import com.dengqn.igps2xingzhe.vo.IGPSActivityResp;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.security.SecurityUtil; import org.apache.catalina.security.SecurityUtil;
import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.cookie.Cookie; import org.apache.hc.client5.http.cookie.Cookie;
import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.FileBody;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.entity.mime.StringBody;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.io.entity.BasicHttpEntity; import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.ByteArrayInputStream; import java.io.*;
import java.io.IOException; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -53,64 +61,106 @@ import java.util.Map;
@RequestMapping("/api/trigger") @RequestMapping("/api/trigger")
public class Trigger { public class Trigger {
@Autowired @Autowired
private HttpClient httpClient; private HttpClient httpClient;
@Autowired @Autowired
private CookieStore cookieStore; private CookieStore cookieStore;
@Value("${dataDir:/data}")
private String fileDir;
@Autowired @Autowired
private XingZhe xingZhe; private XingZhe xingZhe;
@Autowired @Autowired
private IGPSport igpSport; private IGPSport igpSport;
@GetMapping("/sync/igps2xingzhe") @GetMapping("/sync/igps2xingzhe")
public ResponseEntity<String> onSyncIGPS2XingZhe() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { public ResponseEntity<String> onSyncIGPS2XingZhe() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
// 1. 登录行者 // 1. 登录行者
// xingzheLogin(); xingzheLogin();
// 2. 登录igps // 2. 登录igps
igpsLogin(); igpsLogin();
// 3. 获取igps活动
syncIgpsActicities();
return ResponseEntity.ok("ok"); return ResponseEntity.ok("ok");
} }
private void igpsLogin() throws IOException { private void syncIgpsActicities() throws IOException {
// 3. igps 登录 formData CloseableHttpResponse activityResp = (CloseableHttpResponse) httpClient.execute(ClassicRequestBuilder.get("https://my.igpsport.com/Activity/MyActivityList").build());
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(CollUtil.toList( String activityRespStr = new String(IoUtil.readBytes(activityResp.getEntity().getContent()));
new BasicNameValuePair("username", igpSport.getUsername()), IGPSActivityResp activityRespData = JSONUtil.toBean(activityRespStr, IGPSActivityResp.class, JSONConfig.create()
new BasicNameValuePair("password", igpSport.getPassword()) .setIgnoreCase(true)
)); .isIgnoreError());
for (IGPSActivity activity : activityRespData.getItem()) {
// 1. 判断是否本地有文件
String filePath = fileDir + File.separator + activity.getFileName();
ClassicRequestBuilder igpsLoginReq = ClassicRequestBuilder if (FileUtil.exist(filePath)) {
.post("https://my.igpsport.com/Auth/Login") log.info("文件已存在" + filePath);
.setEntity(formEntity); continue;
}
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(igpsLoginReq.build()); // 下载文件
log.info("igps login: {}", IoUtil.read(response.getEntity().getContent())); String downloadUrl = "https://my.igpsport.com/fit/activity?type=0&rideid=" + activity.getRideId();
} log.info("尝试下载" + downloadUrl);
private void xingzheLogin() throws IOException { CloseableHttpResponse downloadResp = (CloseableHttpResponse) httpClient.execute(ClassicRequestBuilder.get(downloadUrl).build());
// 1获取行者平台的cookie InputStream downloadInputStream = downloadResp.getEntity().getContent();
HttpResponse executed = httpClient.execute(ClassicRequestBuilder.get("https://www.imxingzhe.com/user/login").build()); long copied = IoUtil.copy(downloadInputStream, new FileOutputStream(filePath));
System.out.println(executed.getCode()); log.info("{} bytes copied", copied);
// 2计算加密后的密码参数
RSA rsa = new RSA(AsymmetricAlgorithm.RSA_ECB_PKCS1.getValue(), null, xingZhe.getPubKey());
String rd = cookieStore.getCookies()
.stream().filter(a -> a.getName().equals("rd")).findFirst()
.map(Cookie::getValue).orElse("");
String encryptBase64 = rsa.encryptBase64(xingZhe.getPassword() + ";" + rd, KeyType.PublicKey);
Map<String, Object> loginData = new HashMap<>(); // 上传到xingzhe
loginData.put("account", xingZhe.getUsername()); String uploadUrl = "https://www.imxingzhe.com/api/v4/upload_fits";
loginData.put("password", encryptBase64); HttpEntity uploadForm = MultipartEntityBuilder.create()
loginData.put("source", "web"); .addPart("upload_file_name", new FileBody(new File(filePath)))
.addPart("title", new StringBody(activity.getFileName(), ContentType.create("text/plain", Charset.defaultCharset())))
.addPart("device", new StringBody("3", ContentType.create("text/plain", Charset.defaultCharset())))
.addPart("sport", new StringBody("3", ContentType.create("text/plain", Charset.defaultCharset())))
.build();
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(ClassicRequestBuilder CloseableHttpResponse uploadResponse = (CloseableHttpResponse) httpClient.execute(ClassicRequestBuilder.post(uploadUrl).setEntity(uploadForm).build());
.post("https://www.imxingzhe.com/api/v4/account/login") log.info("upload result: {}", new String(IoUtil.readBytes(uploadResponse.getEntity().getContent())));
.setEntity(JSONUtil.toJsonPrettyStr(loginData), ContentType.APPLICATION_JSON) }
.build()); }
log.info("xingzhe login: {}", IoUtil.read(response.getEntity().getContent()));
} private void igpsLogin() throws IOException {
// 3. igps 登录 formData
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(CollUtil.toList(
new BasicNameValuePair("username", igpSport.getUsername()),
new BasicNameValuePair("password", igpSport.getPassword())
));
ClassicRequestBuilder igpsLoginReq = ClassicRequestBuilder
.post("https://my.igpsport.com/Auth/Login")
.setEntity(formEntity);
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(igpsLoginReq.build());
log.info("igps login: {}", IoUtil.read(response.getEntity().getContent()));
}
private void xingzheLogin() throws IOException {
// 1获取行者平台的cookie
HttpResponse executed = httpClient.execute(ClassicRequestBuilder.get("https://www.imxingzhe.com/user/login").build());
System.out.println(executed.getCode());
// 2计算加密后的密码参数
RSA rsa = new RSA(AsymmetricAlgorithm.RSA_ECB_PKCS1.getValue(), null, xingZhe.getPubKey());
String rd = cookieStore.getCookies()
.stream().filter(a -> a.getName().equals("rd")).findFirst()
.map(Cookie::getValue).orElse("");
String encryptBase64 = rsa.encryptBase64(xingZhe.getPassword() + ";" + rd, KeyType.PublicKey);
Map<String, Object> loginData = new HashMap<>();
loginData.put("account", xingZhe.getUsername());
loginData.put("password", encryptBase64);
loginData.put("source", "web");
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(ClassicRequestBuilder
.post("https://www.imxingzhe.com/api/v4/account/login")
.setEntity(JSONUtil.toJsonPrettyStr(loginData), ContentType.APPLICATION_JSON)
.build());
log.info("xingzhe login: {}", IoUtil.read(response.getEntity().getContent()));
}
} }

View File

@ -0,0 +1,35 @@
package com.dengqn.igps2xingzhe.vo;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IGPSActivity implements Serializable {
@Serial
private static final long serialVersionUID = 2974104022106192054L;
private String RideId;
private String MemberId;
private String Title;
private String StartTime;
private String StartTimeString;
private String RideDistance;
private String TotalAscent;
private String MovingTime;
private String OpenStatus;
private String Status;
private String SportType;
public String getFileName() {
return StrUtil.format("igpsport-{}-{}-{}.fit", RideId, StartTimeString, RideDistance);
}
}

View File

@ -0,0 +1,25 @@
package com.dengqn.igps2xingzhe.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IGPSActivityResp implements Serializable {
@Serial
private static final long serialVersionUID = -2545669204414828896L;
private Integer total;
private List<IGPSActivity> item;
private Integer unit;
}