From dae39b43d622a2b9c89f3ffcfe504a268817e92b Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sat, 31 Jan 2026 21:03:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0FTP=E5=A4=87=E4=BB=BD?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E4=BF=9D=E5=AD=98=E8=B7=AF=E5=BE=84=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appbase/build.properties | 4 +- .../winboll/studio/appbase/MainActivity.java | 11 +- libappbase/build.properties | 4 +- .../activities/FTPBackupsActivity.java | 165 ++++++++++++------ 4 files changed, 123 insertions(+), 61 deletions(-) diff --git a/appbase/build.properties b/appbase/build.properties index 5a763fb..feaaf1c 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Jan 31 12:06:15 GMT 2026 +#Sat Jan 31 13:02:06 GMT 2026 stageCount=12 libraryProject=libappbase baseVersion=15.15 publishVersion=15.15.11 -buildCount=27 +buildCount=29 baseBetaVersion=15.15.12 diff --git a/appbase/src/main/java/cc/winboll/studio/appbase/MainActivity.java b/appbase/src/main/java/cc/winboll/studio/appbase/MainActivity.java index 6ce939b..76f82c9 100644 --- a/appbase/src/main/java/cc/winboll/studio/appbase/MainActivity.java +++ b/appbase/src/main/java/cc/winboll/studio/appbase/MainActivity.java @@ -168,18 +168,18 @@ public class MainActivity extends Activity { public void onFTPBackupsActivity(View view) { LogUtils.d(TAG, "onFTPBackupsActivity() 调用"); SFTPBackupsSettingsDialog dlg = new SFTPBackupsSettingsDialog(this); - SFTPAuthModel authModel = dlg.getSFTPAuthModelFromSP(this); + SFTPAuthModel authModel = SFTPBackupsSettingsDialog.getSFTPAuthModelFromSP(this); if (authModel == null) { dlg.show(); } else { // 1. 构建应用Data目录待备份文件Map HashMap dataFileMap = new HashMap<>(); - // 存入文件:key=唯一标识,value=应用外部文件目录下的相对路径(与原addSdcardFile参数一致) + // 存入文件:key=唯一标识,value=应用Data目录下的相对路径 dataFileMap.put(TestBean.class.getName() + ".json", getTestBeanRelativePath()); - // 1. 构建SDCard目录待备份文件Map(与BackupUtils的SdcardMap泛型一致:String-String) + // 构建SDCard目录待备份文件Map(与BackupUtils的SdcardMap泛型一致:String-String) HashMap sdcardFileMap = new HashMap<>(); - // 存入文件:key=唯一标识,value=应用外部文件目录下的相对路径(与原addSdcardFile参数一致) + // 存入文件:key=唯一标识,value=应用专属外部文件目录下的相对路径 sdcardFileMap.put(TestBean.class.getName() + ".json", getTestBeanRelativePath()); @@ -188,7 +188,8 @@ public class MainActivity extends Activity { // 3. 序列化传递Map参数(使用FTPBackupsActivity中定义的常量,避免硬编码) ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_DATA_DIR_FILE_MAP, dataFileMap); ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_SDCARD_DIR_FILE_MAP, sdcardFileMap); - // 若需要传Data目录的Map,同理:ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_DATA_DIR_FILE_MAP, dataFileMap); + // 传递FTP上传目标目录参数,路径为/WinBoLLStudio/APPBackups/WinBoLL + ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_FTP_TARGET_DIR, "/WinBoLLStudio/APPBackups/WinBoLL"); // 4. 启动Activity,参数自动透传 startActivity(ftpBackupsIntent); diff --git a/libappbase/build.properties b/libappbase/build.properties index 5a763fb..feaaf1c 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Jan 31 12:06:15 GMT 2026 +#Sat Jan 31 13:02:06 GMT 2026 stageCount=12 libraryProject=libappbase baseVersion=15.15 publishVersion=15.15.11 -buildCount=27 +buildCount=29 baseBetaVersion=15.15.12 diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/activities/FTPBackupsActivity.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/activities/FTPBackupsActivity.java index 30d1bde..b451d25 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/activities/FTPBackupsActivity.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/activities/FTPBackupsActivity.java @@ -21,18 +21,19 @@ import cc.winboll.studio.libappbase.dialogs.SFTPBackupsSettingsDialog; /** * BackupUtils 调用实例 - * 支持Intent传入双Map参数,初始化BackupUtils的待备份文件列表 + * 支持Intent传入双Map参数+FTP上传目标目录,初始化BackupUtils的待备份文件列表和上传路径 + * 强制校验FTP上传目录参数,未传参时直接提示不执行备份 * @Author 豆包&ZhanGSKen * @Date 2026/01/30 20:55 - * @LastEditTime 2026/02/01 04:00 + * @LastEditTime 2026/02/01 11:00 */ public class FTPBackupsActivity extends Activity { public static final String TAG = "FTPBackupsActivity"; // 主线程Handler:子线程更新UI专用 private Handler mMainHandler; - // FTP服务器上传目标目录(可根据业务自定义) - private static final String FTP_TARGET_DIR = "/WinBoLLStudio/APPBackups/WinBoLL/"; + // FTP服务器上传目标目录-默认值(移除,改为强制传参,无默认值) + private static final String DEFAULT_FTP_TARGET_DIR = null; // ==================== Intent传参常量(规范外部调用)==================== /** @@ -45,10 +46,16 @@ public class FTPBackupsActivity extends Activity { * 类型:HashMap 实现Serializable接口 */ public static final String EXTRA_SDCARD_DIR_FILE_MAP = "extra_sdcard_dir_file_map"; + /** + * Intent传入参数-FTP/SFTP服务器上传目标目录【强制传参】 + * 类型:String 示例:/backup/app/ ,必须传参否则不执行备份 + */ + public static final String EXTRA_FTP_TARGET_DIR = "extra_ftp_target_dir"; - // 解析后的双Map参数(用于初始化BackupUtils) + // 解析后的参数(用于初始化BackupUtils) private Map mDataDirFileMap; private Map mSdcardDirFileMap; + private String mFtpTargetDir; // 解析后的FTP上传目标目录(强制非空) @Override protected void onCreate(Bundle savedInstanceState) { @@ -56,38 +63,60 @@ public class FTPBackupsActivity extends Activity { setContentView(R.layout.activity_ftp_backups); // 初始化主线程Handler,避免子线程更新UI崩溃 mMainHandler = new Handler(Looper.getMainLooper()); - // 解析Intent中的双Map参数 + // 解析Intent中的所有参数(双Map+FTP上传目录) parseIntentParams(); } /** - * 解析Intent传入的序列化Map参数 - * 兜底处理:参数为空/类型错误时,赋值为null(兼容BackupUtils初始化逻辑) + * 解析Intent传入的所有序列化参数 + * 兜底处理:双Map解析失败初始化空Map,FTP目录**未传/空则直接赋值null,不兜底默认值** */ private void parseIntentParams() { Intent intent = getIntent(); - if (intent != null) { - // 解析Data目录Map:强转为HashMap(实现Serializable,可序列化传递) - Serializable dataMapSer = intent.getSerializableExtra(EXTRA_DATA_DIR_FILE_MAP); - if (dataMapSer instanceof HashMap) { - mDataDirFileMap = (HashMap) dataMapSer; - LogUtils.d(TAG, "Intent解析Data目录Map成功,共" + mDataDirFileMap.size() + "个文件"); - } else { - mDataDirFileMap = null; - LogUtils.d(TAG, "Intent未解析到有效Data目录Map"); - } - - // 解析SDCard目录Map:强转为HashMap(实现Serializable,可序列化传递) - Serializable sdcardMapSer = intent.getSerializableExtra(EXTRA_SDCARD_DIR_FILE_MAP); - if (sdcardMapSer instanceof HashMap) { - mSdcardDirFileMap = (HashMap) sdcardMapSer; - LogUtils.d(TAG, "Intent解析SDCard目录Map成功,共" + mSdcardDirFileMap.size() + "个文件"); - } else { - mSdcardDirFileMap = null; - LogUtils.d(TAG, "Intent未解析到有效SDCard目录Map"); - } + if (intent == null) { + LogUtils.w(TAG, "parseIntentParams:Intent为null,参数解析失败"); + initEmptyParams(); + return; } - // 未解析到参数时,保持null即可,BackupUtils内部会默认初始化空Map + + // 1. 解析Data目录Map:强转为HashMap,失败则初始化空Map + Serializable dataMapSer = intent.getSerializableExtra(EXTRA_DATA_DIR_FILE_MAP); + if (dataMapSer instanceof HashMap) { + mDataDirFileMap = (HashMap) dataMapSer; + LogUtils.d(TAG, "Intent解析Data目录Map成功,共" + mDataDirFileMap.size() + "个文件"); + } else { + mDataDirFileMap = new HashMap<>(); + LogUtils.d(TAG, "Intent未解析到有效Data目录Map,初始化空Map"); + } + + // 2. 解析SDCard目录Map:强转为HashMap,失败则初始化空Map + Serializable sdcardMapSer = intent.getSerializableExtra(EXTRA_SDCARD_DIR_FILE_MAP); + if (sdcardMapSer instanceof HashMap) { + mSdcardDirFileMap = (HashMap) sdcardMapSer; + LogUtils.d(TAG, "Intent解析SDCard目录Map成功,共" + mSdcardDirFileMap.size() + "个文件"); + } else { + mSdcardDirFileMap = new HashMap<>(); + LogUtils.d(TAG, "Intent未解析到有效SDCard目录Map,初始化空Map"); + } + + // 3. 解析FTP上传目标目录:**强制校验,未传/空/空格则直接赋值null,不兜底** + String inputFtpDir = intent.getStringExtra(EXTRA_FTP_TARGET_DIR); + if (inputFtpDir != null && !inputFtpDir.trim().isEmpty()) { + mFtpTargetDir = inputFtpDir.trim().endsWith("/") ? inputFtpDir.trim() : inputFtpDir.trim() + "/"; + LogUtils.d(TAG, "Intent解析FTP上传目录成功:" + mFtpTargetDir); + } else { + mFtpTargetDir = null; + LogUtils.w(TAG, "Intent未传递/传递空的FTP上传目录参数,禁止执行备份"); + } + } + + /** + * 初始化空参数:Intent为null时使用,FTP目录直接赋值null + */ + private void initEmptyParams() { + mDataDirFileMap = new HashMap<>(); + mSdcardDirFileMap = new HashMap<>(); + mFtpTargetDir = null; } public void onSFTPSettings(View view) { @@ -97,57 +126,68 @@ public class FTPBackupsActivity extends Activity { /** * 点击事件:执行FTP备份(核心调用方法) - * 主线程仅做UI触发,所有核心逻辑在子线程执行 - * 每次点击都重新解析Intent参数,保证获取最新的文件列表 + * 【强制校验】未传递FTP_TARGET_DIR参数时,仅吐司提示,不执行任何操作 + * 每次点击都重新解析Intent参数,保证参数最新 */ public void onBackups(View view) { - ToastUtils.show("开始执行FTP备份,请勿退出页面..."); - LogUtils.d(TAG, "触发FTP备份操作,开启子线程执行核心逻辑"); - // 每次点击都重新解析Intent参数,避免Activity复用导致参数失效 + // 1. 重新解析参数,保证最新 parseIntentParams(); - SFTPBackupsSettingsDialog dlg = new SFTPBackupsSettingsDialog(this); - SFTPAuthModel authModel = dlg.getSFTPAuthModelFromSP(this); - if (authModel == null) { - dlg.show(); - } else { - // 传入解析后的双Map参数 - doBackups(authModel, mDataDirFileMap, mSdcardDirFileMap); + // 2. 强制核心校验:FTP上传目录未传参,直接吐司提示,不执行后续操作 + if (mFtpTargetDir == null) { + ToastUtils.show("备份失败:未设置FTP上传目标目录参数"); + LogUtils.e(TAG, "触发备份操作失败:Intent未传递EXTRA_FTP_TARGET_DIR参数"); + return; } + // 3. 校验SFTP配置,未配置则弹框 + SFTPAuthModel authModel = SFTPBackupsSettingsDialog.getSFTPAuthModelFromSP(this); + if (authModel == null) { + ToastUtils.show("备份失败:请先配置SFTP服务器信息"); + new SFTPBackupsSettingsDialog(this).show(); + LogUtils.w(TAG, "触发备份操作失败:未配置SFTP服务器信息"); + return; + } + // 4. 所有校验通过,提示并执行备份 + ToastUtils.show("开始执行FTP备份,请勿退出页面..."); + LogUtils.d(TAG, "所有校验通过,开启子线程执行备份核心逻辑"); + doBackups(authModel, mDataDirFileMap, mSdcardDirFileMap, mFtpTargetDir); } /** - * 核心备份逻辑:子线程执行 + * 核心备份逻辑:子线程执行(仅在校验通过后调用) * @param authModel SFTP认证模型 * @param dataDirFileMap Intent解析的Data目录文件Map * @param sdcardDirFileMap Intent解析的SDCard目录文件Map + * @param ftpTargetDir Intent解析的FTP上传目标目录(已标准化、非空) */ - void doBackups(final SFTPAuthModel authModel, final Map dataDirFileMap, final Map sdcardDirFileMap) { + void doBackups(final SFTPAuthModel authModel, final Map dataDirFileMap, + final Map sdcardDirFileMap, final String ftpTargetDir) { // 所有BackupUtils操作放入子线程,规避网络/IO主线程异常 new Thread(new Runnable() { @Override public void run() { try { - // 初始化BackupUtils单例,透传解析后的双Map参数 + // 初始化BackupUtils单例,透传动态解析的FTP上传目录 BackupUtils backupUtils = BackupUtils.getInstance( getApplicationContext(), authModel, - FTP_TARGET_DIR, - dataDirFileMap, - sdcardDirFileMap + ftpTargetDir ); - LogUtils.d(TAG, "待备份文件初始化完成 → Data目录:" + backupUtils.getAllDataDirFiles().size() + "个 | SDCard目录:" + backupUtils.getAllSdcardFiles().size() + "个"); + // 向BackupUtils添加解析后的待备份文件(双Map) + addBackupFilesToUtils(backupUtils, dataDirFileMap, sdcardDirFileMap); + + LogUtils.d(TAG, "待备份文件初始化完成 → Data目录:" + backupUtils.getAllDataDirFiles().size() + "个 | SDCard目录:" + backupUtils.getAllSdcardFiles().size() + "个 | 上传目录:" + ftpTargetDir); // 核心执行:打包为ZIP + SFTP上传(内部已实现登录→打包→上传→登出) boolean isSuccess = backupUtils.packAndUploadByFtp(); // 主线程反馈执行结果 if (isSuccess) { - updateUi("FTP备份成功!文件已打包为ZIP上传至服务器:" + FTP_TARGET_DIR); - LogUtils.i(TAG, "FTP备份全流程执行成功"); + updateUi("FTP备份成功!文件已打包为ZIP上传至服务器:" + ftpTargetDir); + LogUtils.i(TAG, "FTP备份全流程执行成功,上传目录:" + ftpTargetDir); } else { updateUi("FTP备份失败!请检查服务器配置或文件路径"); - LogUtils.e(TAG, "FTP备份全流程执行失败"); + LogUtils.e(TAG, "FTP备份全流程执行失败,上传目录:" + ftpTargetDir); } } catch (IllegalArgumentException e) { @@ -168,6 +208,26 @@ public class FTPBackupsActivity extends Activity { }).start(); } + /** + * 向BackupUtils添加待备份文件:遍历双Map,调用add方法逐个添加 + * 先清空原有文件,避免Activity复用导致文件重复 + * @param backupUtils BackupUtils实例 + * @param dataMap Data目录待备份文件Map + * @param sdcardMap SDCard目录待备份文件Map + */ + private void addBackupFilesToUtils(BackupUtils backupUtils, Map dataMap, Map sdcardMap) { + backupUtils.clearDataDirFiles(); + backupUtils.clearSdcardFiles(); + // 添加Data目录文件 + for (Map.Entry entry : dataMap.entrySet()) { + backupUtils.addDataDirFile(entry.getKey(), entry.getValue()); + } + // 添加SDCard目录文件 + for (Map.Entry entry : sdcardMap.entrySet()) { + backupUtils.addSdcardFile(entry.getKey(), entry.getValue()); + } + } + /** * 子线程更新UI工具方法(Toast提示) * @param msg 提示信息 @@ -192,9 +252,10 @@ public class FTPBackupsActivity extends Activity { if (mMainHandler != null) { mMainHandler.removeCallbacksAndMessages(null); } - // 清空本地Map引用,协助GC(不影响BackupUtils内部逻辑) + // 清空本地引用,协助GC mDataDirFileMap = null; mSdcardDirFileMap = null; + mFtpTargetDir = null; LogUtils.d(TAG, "FTPBackupsActivity销毁:已释放本地资源,不影响二次备份初始化"); } }