fix: 修复skopeo Windows路径 + 回写阶段无效表过滤 + 清理调试代码

- HarborService: skopeo push 改用工作目录+相对路径,规避 Windows 盘符路径问题
- OssSyncService: 扫描阶段跳过已回写路径避免重复处理
- OssSyncService: 回写阶段先过滤有效映射表,避免加载无效数据
- app.yml: 修复 server.request.maxBodySize 配置格式,补充 maxFileSize/maxRequestSize
- App.java: 删除注释掉的 Choerodon 登录调试代码

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 10:54:30 +08:00
co-authored by Claude Opus 4.7
parent 036d7488ad
commit 1bfda7bbe9
4 changed files with 31 additions and 159 deletions
-145
View File
@@ -21,149 +21,4 @@ public class App {
public static void main(String[] args) {
Solon.start(App.class, args);
}
// private static final String BASE_URL = "http://api.dev.xjyt.petrochina/oauth/choerodon/login"; // 替换为实际域名
// private static final String PUBLIC_KEY = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJL0JkqsUoK6kt3JyogsgqNp9VDGDp+t3ZAGMbVoMPdHNT2nfiIVh9ZMNHF7g2XiAa8O8AQWyh2PjMR0NiUSVQMCAwEAAQ==";
//
// /**
// * RSA 加密密码
// */
// public static String encryptPassword(String password) throws Exception {
// byte[] keyBytes = Base64.getDecoder().decode(PUBLIC_KEY);
// X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
//
// Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// byte[] encrypted = cipher.doFinal(password.getBytes("UTF-8"));
// return Base64.getEncoder().encodeToString(encrypted);
// }
//
// /**
// * 登录,返回 Cookie(后续请求需携带)
// */
// public static String login(String username, String password) throws Exception {
// String encryptedPassword = encryptPassword(password);
//
// // 构建 form 表单体
// String formBody = "username=" + java.net.URLEncoder.encode(username, "UTF-8")
// + "&password=" + java.net.URLEncoder.encode(encryptedPassword, "UTF-8");
//
// // 打印等效的 curl 命令(Windows 格式)
// System.out.println("curl \"" + BASE_URL + "\" ^");
// System.out.println(" -H \"Origin: http://api.dev.xjyt.petrochina\" ^");
// System.out.println(" -H \"Content-Type: application/x-www-form-urlencoded\" ^");
// System.out.println(" -H \"User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36\" ^");
// System.out.println(" -H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\" ^");
// System.out.println(" -H \"Referer: http://api.dev.xjyt.petrochina/oauth/choerodon/login\" ^");
// System.out.println(" -H \"Accept-Language: zh-CN,zh;q=0.9\" ^");
// System.out.println(" -H \"Cookie: SESSION=M2MwYTZjM2ItNzk4Ni00NTM0LWJlYzYtZDAwMDA5Zjc2ZTE1; route=289a995e776d44c58b544d574adb34a0; _hatt=26a898962c71fb9c70f01348ee9e32bb\" ^");
// System.out.println(" --data-raw \"" + formBody + "\"");
//
// HttpClient client = HttpClient.newBuilder()
// .followRedirects(HttpClient.Redirect.NEVER)
// .build();
//
// HttpRequest request = HttpRequest.newBuilder()
// .uri(URI.create(BASE_URL))
// .header("Origin", "http://api.dev.xjyt.petrochina")
// .header("Content-Type", "application/x-www-form-urlencoded")
// .header("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36")
// .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
// .header("Referer", "http://api.dev.xjyt.petrochina/oauth/choerodon/login")
// .header("Accept-Language", "zh-CN,zh;q=0.9")
// .header("Cookie", "SESSION=M2MwYTZjM2ItNzk4Ni00NTM0LWJlYzYtZDAwMDA5Zjc2ZTE1; route=289a995e776d44c58b544d574adb34a0; _hatt=26a898962c71fb9c70f01348ee9e32bb")
// .POST(HttpRequest.BodyPublishers.ofString(formBody))
// .build();
//
// HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
//
// System.out.println("状态码: " + response.statusCode());
// System.out.println("响应体: " + response.body());
//
// // 登录成功后服务器返回 302Set-Cookie 中携带新 Session
// String cookie = response.headers().firstValue("Set-Cookie").orElse(null);
// System.out.println("Cookie: " + cookie);
//
// return cookie;
// }
//
// /**
// * OAuth 授权,返回重定向地址(含 access_token
// */
// public static String authorize(String cookie) throws Exception {
// String url = "http://api.dev.xjyt.petrochina/oauth/oauth/authorize" +
// "?response_type=token&client_id=choerodon&state=&redirect_uri=" +
// java.net.URLEncoder.encode("http://dev.xjyt.petrochina/#/devops/resource" +
// "?activeKey=instance&category=AGILE&id=748209321615708160" +
// "&name=%E5%85%A5%E5%BA%B7%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0" +
// "&organizationId=1&type=project&redirectFlag", "UTF-8");
//
// // 打印等效的 curl 命令(Windows 格式)
// System.out.println("curl -v \"" + url + "\" ^");
// System.out.println(" -H \"User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36\" ^");
// System.out.println(" -H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\" ^");
// System.out.println(" -H \"Referer: http://dev.xjyt.petrochina/\" ^");
// System.out.println(" -H \"Accept-Language: zh-CN,zh;q=0.9\" ^");
// System.out.println(" -H \"Cookie: " + cookie + "\" ^");
// System.out.println(" -L");
//
// HttpClient client = HttpClient.newBuilder()
// .followRedirects(HttpClient.Redirect.NEVER)
// .build();
//
// HttpRequest request = HttpRequest.newBuilder()
// .uri(URI.create(url))
// .header("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36")
// .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
// .header("Referer", "http://dev.xjyt.petrochina/")
// .header("Accept-Language", "zh-CN,zh;q=0.9")
// .header("Cookie", cookie)
// .GET()
// .build();
//
// HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
//
// System.out.println("authorize 状态码: " + response.statusCode());
// System.out.println("authorize 响应体: " + response.body());
//
// // 授权成功返回 302Location 中含 access_token
// String location = response.headers().firstValue("Location").orElse(null);
// System.out.println("Location: " + location);
//
// return location;
// }
//
// public static void main(String[] args) throws Exception {
// String cookie = login("jy-lianlonggang", "UserPwdLlgA@123!");
// System.out.println("登录成功,Cookie: " + cookie);
//
// if (cookie != null) {
// String location = authorize(cookie);
// System.out.println("授权跳转地址: " + location);
//
// String token = extractToken(location);
// System.out.println("access_token: " + token);
// }
// }
//
// /**
// * 从 Location URL 的 fragment# 后)中提取 access_token
// */
// public static String extractToken(String location) {
// if (location == null) return null;
// int hashIdx = location.indexOf('#');
// if (hashIdx < 0) return null;
// String fragment = location.substring(hashIdx + 1);
// // fragment 格式:/path?key=val&access_token=xxx&...
// int queryIdx = fragment.indexOf('?');
// String query = queryIdx >= 0 ? fragment.substring(queryIdx + 1) : fragment;
// for (String param : query.split("&")) {
// if (param.startsWith("access_token=")) {
// return param.substring("access_token=".length());
// }
// }
// return null;
// }
}
@@ -13,6 +13,7 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.io.File;
import java.security.cert.X509Certificate;
import java.util.Base64;
import javax.net.ssl.SSLContext;
@@ -111,26 +112,27 @@ public class HarborService {
* @param tag 目标 tag
*/
public String pushImage(String tarRelPath, String repoName, String tag) throws Exception {
String tarAbsPath = skopeoConfig.getImagesPath().replace("\\", "/") + "/" + tarRelPath;
String dest = harborConfig.getUrl().replace("https://", "")
.replace("http://", "");
dest = harborConfig.getUrl().startsWith("https") ? dest : dest;
String destImage = "docker://" + dest.replace("https://", "").replace("http://", "")
+ "/" + harborConfig.getProject() + "/" + repoName + ":" + tag;
// 修正:直接拼完整 harbor host
// tarAbsPath 使用系统原生路径分隔符,供 File 对象使用
String tarAbsPath = skopeoConfig.getImagesPath() + File.separator + tarRelPath.replace("/", File.separator);
File tarFile = new File(tarAbsPath);
// skopeo 在 Windows 下无法处理带盘符的绝对路径,改用工作目录+文件名相对路径规避
String tarFileName = tarFile.getName();
File workDir = tarFile.getParentFile();
String harborHost = harborConfig.getUrl().replaceFirst("https?://", "");
destImage = "docker://" + harborHost + "/" + harborConfig.getProject() + "/" + repoName + ":" + tag;
String destImage = "docker://" + harborHost + "/" + harborConfig.getProject() + "/" + repoName + ":" + tag;
String[] cmd = {
skopeoConfig.getExePath(),
"copy",
"--dest-creds", harborConfig.getUsername() + ":" + harborConfig.getPassword(),
"--dest-tls-verify=false",
"docker-archive:" + tarAbsPath,
"docker-archive:" + tarFileName,
destImage
};
log.info("[Harbor] skopeo push: {} -> {}", tarAbsPath, destImage);
log.info("[Harbor] skopeo push: workDir={} cmd={}", workDir.getAbsolutePath(), String.join(" ", cmd));
Process process = new ProcessBuilder(cmd)
.directory(workDir) // 工作目录设为 tar 所在目录
.redirectErrorStream(true)
.start();
String output = new String(process.getInputStream().readAllBytes());
@@ -171,6 +171,10 @@ public class OssSyncService {
for (Map<String, String> row : rows) {
String bizId = row.get("id");
String filePath = row.get("filePath");
// 已回写的 OSS 路径包含 @/jkgl 关键字,跳过避免重复处理
if (filePath != null && filePath.contains("@/jkgl")) {
continue;
}
QhOssFileLog existing = ossFileLogMapper.selectOne(new LambdaQueryWrapper<QhOssFileLog>()
.eq(QhOssFileLog::getBizTabName, mapping.getTableName())
.eq(QhOssFileLog::getBizTabId, bizId)
@@ -272,17 +276,26 @@ public class OssSyncService {
* 回写已上传成功(状态=1)的记录到业务表
*/
private int writeBack(SyncTask task) {
// 先取有效映射,再按表名范围查日志,避免加载无效表的数据
List<BizFileMappingEntity> mappings = bizFileMappingMapper.selectList(
new LambdaQueryWrapper<BizFileMappingEntity>()
.eq(BizFileMappingEntity::getEnabled, BizFileMappingEntity.ENABLED_YES));
if (mappings == null || mappings.isEmpty()) {
log.warn("[OssSyncService] 无有效的业务表映射配置,跳过回写");
return 0;
}
List<String> validTableNames = mappings.stream()
.map(BizFileMappingEntity::getTableName).toList();
List<QhOssFileLog> uploaded = ossFileLogMapper.selectList(
new LambdaQueryWrapper<QhOssFileLog>()
.eq(QhOssFileLog::getBizHandleStatus, QhOssFileLog.STATUS_UPLOADED));
.eq(QhOssFileLog::getBizHandleStatus, QhOssFileLog.STATUS_UPLOADED)
.in(QhOssFileLog::getBizTabName, validTableNames));
syncTaskMapper.update(null, new LambdaUpdateWrapper<SyncTask>()
.eq(SyncTask::getId, task.getId())
.set(SyncTask::getTotal, uploaded.size())
.set(SyncTask::getSuccess, 0)
.set(SyncTask::getFail, 0)
.set(SyncTask::getUpdateTime, LocalDateTime.now()));
// 从数据库读取所有映射配置(回写不限有效/无效,按表名匹配)
List<BizFileMappingEntity> mappings = bizFileMappingMapper.selectList(null);
int success = 0;
for (QhOssFileLog record : uploaded) {
String dbName = mappings.stream()
+3 -1
View File
@@ -1,5 +1,7 @@
server.port: 8080
server.request-maxBodySize: 1073741824
server.request.maxBodySize: 1073741824
server.request.maxFileSize: 1073741824
server.request.maxRequestSize: 1073741824
solon.config.load:
- "app-env.yml" #可以是环境相关的