diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml deleted file mode 100644 index b25cbb5..0000000 --- a/.github/workflows/android.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Android CI - -# 触发器 -on: - push: - tags: - - *-beta - pull_request: - tags: - - *-beta - -jobs: - build: - - runs-on: ubuntu-latest - - # 设置 JDK 环境 - steps: - - uses: actions/checkout@v3 - - name: set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - cache: gradle - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - # 获取应用打包秘钥库 - - name: Checkout Android Keystore - uses: actions/checkout@v3 - with: - repository: zhangsken/keystore # 存储应用打包用的 keystore 的仓库(格式:用户名/仓库名) - token: ${{ secrets.APP_SECRET_TOKEN_1 }} # 连接仓库的 token , 需要单独配置 - path: keystore # 仓库的根目录名 - - # 打包 Stage Release 版本应用 - - name: Build with Gradle - run: bash ./gradlew assembleBetaRelease - # 创建release - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.APP_SECRET_TOKEN_1 }} - # GitHub 会自动创建 GITHUB_TOKEN 密码以在工作流程中使用。 - # 您可以使用 GITHUB_TOKEN 在工作流程运行中进行身份验证。 - # 当您启用 GitHub Actions 时,GitHub 在您的仓库中安装 GitHub 应用程序。 - # GITHUB_TOKEN 密码是一种 GitHub 应用程序 安装访问令牌。 - # 您可以使用安装访问令牌代表仓库中安装的 GitHub 应用程序 进行身份验证。 - # 令牌的权限仅限于包含您的工作流程的仓库。 更多信息请参阅“GITHUB_TOKEN 的权限”。 - # 在每个作业开始之前, GitHub 将为作业提取安装访问令牌。 令牌在作业完成后过期。 - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - # 获取 APK 版本号 - - name: Get Version Name - uses: actions/github-script@v3 - id: get-version - with: - script: | - const str=process.env.GITHUB_REF; - return str.substring(str.indexOf("v")); - result-encoding: string - # 上传至 Release 的资源 - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.APP_SECRET_TOKEN_1 }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} # 上传网址,无需改动 - #asset_path: app/build/outputs/apk/release/app-release.apk # 上传路径(Release) - asset_path: app/build/outputs/apk/beta/release/app-beta-release.apk # 上传路径(WinBoll Stage Release) - asset_name: WinBoll-${{steps.get-version.outputs.result}}0.apk # 资源名 - asset_content_type: application/vnd.android.package-archiv # 资源类型 - - # 存档打包的文件 - - name: Archive production artifacts - uses: actions/upload-artifact@v2 - with: - name: build - path: app/build/outputs # 将打包之后的文件全部上传(里面会有混淆的 map 文件) diff --git a/.winboll/bashPublishAPKAddTag.sh b/.winboll/bashPublishAPKAddTag.sh index d210d28..082dc7e 100644 --- a/.winboll/bashPublishAPKAddTag.sh +++ b/.winboll/bashPublishAPKAddTag.sh @@ -1,166 +1,223 @@ #!/usr/bin/bash +# ============================================================================== +# WinBoLL 应用发布脚本 +# 功能:检查Git源码状态 → 编译Stage Release包 → 添加WinBoLL标签 → 提交并推送源码 +# 依赖:build.properties、app_update_description.txt(项目根目录下) +# 使用:./script_name.sh +# 作者:豆包&ZhanGSKen +# ============================================================================== -# 检查是否指定了将要发布的应用名称 -# 使用 `-z` 命令检查变量是否为空 +# ==================== 常量定义 ==================== +# 脚本退出码 +EXIT_CODE_SUCCESS=0 +EXIT_CODE_ERR_NO_APP_NAME=2 +EXIT_CODE_ERR_WORK_DIR=1 +EXIT_CODE_ERR_GIT_CHECK=1 +EXIT_CODE_ERR_ADD_WINBOLL_TAG=1 + +# Gradle 任务(正式发布) +GRADLE_TASK_PUBLISH="assembleStageRelease" +# Gradle 任务(调试用,注释备用) +# GRADLE_TASK_DEBUG="assembleBetaDebug" + +# ==================== 函数定义 ==================== +# 检查Git源码是否已完全提交(无未提交变更) +# 返回值:0=已完全提交,1=存在未提交变更 +function checkGitSources() { + # 配置Git安全目录(解决权限问题) + git config --global --add safe.directory "$(pwd)" + + # 检查是否有未提交的变更 + if [[ -n $(git diff --stat) ]]; then + echo "[ERROR] Git源码存在未提交变更,请先提交所有修改!" + return 1 + fi + + echo "[INFO] Git源码检查通过:所有变更已提交。" + return 0 +} + +# 询问是否添加GitHub Workflows标签(当前逻辑注释,保留扩展能力) +# 返回值:1=用户选择是,0=用户选择否 +function askAddWorkflowsTag() { + read -p "是否添加GitHub Workflows标签?(Y/n) " answer + if [[ $answer =~ ^[Yy]$ ]]; then + return 1 + else + return 0 + fi +} + +# 添加WinBoLL正式标签 +# 参数:$1=应用名称(项目根目录名) +# 返回值:0=标签添加成功,1=标签已存在/添加失败 +function addWinBoLLTag() { + local app_name=$1 + local build_prop_path="${app_name}/build.properties" + + # 从build.properties中提取publishVersion + local publish_version=$(grep -o "publishVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}') + if [[ -z ${publish_version} ]]; then + echo "[ERROR] 未从${build_prop_path}中提取到publishVersion配置!" + return 1 + fi + echo "[INFO] 从${build_prop_path}读取到publishVersion:${publish_version}" + + # 构造WinBoLL标签(格式:-v) + local tag="${app_name}-v${publish_version}" + echo "[INFO] 准备添加WinBoLL标签:${tag}" + + # 检查标签是否已存在 + if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then + echo "[ERROR] WinBoLL标签${tag}已存在!" + return 1 + fi + + # 添加带注释的标签(注释来自app_update_description.txt) + git tag -a "${tag}" -F "${app_name}/app_update_description.txt" + echo "[INFO] WinBoLL标签${tag}添加成功!" + return 0 +} + +# 添加GitHub Workflows Beta标签(当前逻辑注释,保留扩展能力) +# 参数:$1=应用名称(项目根目录名) +# 返回值:0=标签添加成功,1=标签已存在/添加失败 +function addWorkflowsTag() { + local app_name=$1 + local build_prop_path="${app_name}/build.properties" + + # 从build.properties中提取baseBetaVersion + local base_beta_version=$(grep -o "baseBetaVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}') + if [[ -z ${base_beta_version} ]]; then + echo "[ERROR] 未从${build_prop_path}中提取到baseBetaVersion配置!" + return 1 + fi + echo "[INFO] 从${build_prop_path}读取到baseBetaVersion:${base_beta_version}" + + # 构造Workflows标签(格式:-v-beta) + local tag="${app_name}-v${base_beta_version}-beta" + echo "[INFO] 准备添加Workflows标签:${tag}" + + # 检查标签是否已存在 + if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then + echo "[ERROR] Workflows标签${tag}已存在!" + return 1 + fi + + # 添加带注释的标签(注释来自app_update_description.txt) + git tag -a "${tag}" -F "${app_name}/app_update_description.txt" + echo "[INFO] Workflows标签${tag}添加成功!" + return 0 +} + +# ==================== 主流程开始 ==================== +echo "=============================================" +echo " WinBoLL 应用发布脚本" +echo "=============================================" + +# 1. 检查应用名称参数是否指定 if [ -z "$1" ]; then - echo "No APP name specified : $0" - exit 2 + echo "[ERROR] 未指定应用名称!使用方式:${0} " + exit ${EXIT_CODE_ERR_NO_APP_NAME} fi +APP_NAME=$1 +echo "[INFO] 待发布应用名称:${APP_NAME}" -## 定义相关函数 -## 检查 Git 源码是否完全提交了,完全提交就返回0 -function checkGitSources { - #local input="$1" - #echo "The string is: $input" - git config --global --add safe.directory `pwd` - if [[ -n $(git diff --stat) ]] - then - local result="Source is no commit completely." - echo $result - # 脚本调试时使用 - #return 0 - # 正式检查源码时使用 - return 1 - fi - local result="Git Source Check OK." - echo $result - return 0 -} - -function askAddWorkflowsTag { - read answer - if [[ $answer =~ ^[Yy]$ ]]; then - #echo "You chose yes." - return 1 - else - #echo "You chose no." - return 0 - fi -} - -function addWinBoLLTag { - # 就读取脚本 .winboll/winboll_app_build.gradle 生成的 publishVersion。 - # 如果文件中有 publishVersion 这一项, - # 使用grep找到包含"publishVersion="的那一行,然后用awk提取其后的值 - PUBLISH_VERSION=$(grep -o "publishVersion=.*" $1/build.properties | awk -F '=' '{print $2}') - echo "< $1/build.properties publishVersion : ${PUBLISH_VERSION} >" - ## 设新的 WinBoLL 标签 - # 脚本调试时使用 - #tag="projectname-v7.6.4-test1" - # 正式设置标签时使用 - tag=$1"-v"${PUBLISH_VERSION} - echo "< WinBoLL Tag To: $tag >"; - # 检查是否已经添加了 WinBoLL Tag - if [ "$(git tag -l ${tag})" == "${tag}" ]; then - echo -e "< WinBoLL Tag ${tag} exist! >" - return 1 # WinBoLL标签重复 - fi - # 添加WinBoLL标签 - git tag -a ${tag} -F $1/app_update_description.txt - return 0 -} - -function addWorkflowsTag { - # 就读取脚本 .winboll/winboll_app_build.gradle 生成的 baseBetaVersion。 - # 如果文件中有 baseBetaVersion 这一项, - # 使用grep找到包含"baseBetaVersion="的那一行,然后用awk提取其后的值 - BASE_BETA_VERSION=$(grep -o "baseBetaVersion=.*" $1/build.properties | awk -F '=' '{print $2}') - echo "< $1/build.properties baseBetaVersion : ${BASE_BETA_VERSION} >" - ## 设新的 workflows 标签 - # 脚本调试时使用 - #tag="projectname-v7.6.4-beta" - # 正式设置标签时使用 - tag=$1"-v"${BASE_BETA_VERSION}-beta - echo "< Workflows Tag To: $tag >"; - # 检查是否已经添加了工作流 Tag - if [ "$(git tag -l ${tag})" == "${tag}" ]; then - echo -e "< Github Workflows Tag ${tag} exist! >" - return 1 # 工作流标签重复 - fi - # 添加工作流标签 - git tag -a ${tag} -F $1/app_update_description.txt - return 0 -} - -## 开始执行脚本 -echo -e "Current dir : \n"`pwd` -# 检查当前目录是否是项目根目录 -if [[ -e $1/build.properties ]]; then - echo "The $1/build.properties file exists." - echo -e "Work dir correctly." -else - echo "The $1/build.properties file does not exist." - echo "尝试进入根目录" - # 进入项目根目录 +# 2. 检查并切换到项目根目录(确保build.properties存在) +echo "[INFO] 当前工作目录:$(pwd)" +if [[ ! -e "${APP_NAME}/build.properties" ]]; then + echo "[WARNING] 当前目录不存在${APP_NAME}/build.properties,尝试切换到上级目录..." cd .. + echo "[INFO] 切换后工作目录:$(pwd)" fi -## 本脚本需要在项目根目录下执行 -echo -e "Current dir : \n"`pwd` -# 检查当前目录是否是项目根目录 -if [[ -e $1/build.properties ]]; then - echo "The $1/build.properties file exists." - echo -e "Work dir correctly." + +# 验证最终工作目录是否正确 +if [[ ! -e "${APP_NAME}/build.properties" ]]; then + echo "[ERROR] 工作目录错误!${APP_NAME}/build.properties 文件不存在。" + exit ${EXIT_CODE_ERR_WORK_DIR} +fi +echo "[INFO] 工作目录验证通过:${APP_NAME}/build.properties 存在。" + +# 3. 检查Git源码状态 +echo "---------------------------------------------" +echo " 步骤1:检查Git源码状态" +echo "---------------------------------------------" +checkGitSources +if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then + echo "[ERROR] Git源码检查失败,脚本终止!" + exit ${EXIT_CODE_ERR_GIT_CHECK} +fi + +# 4. 编译Stage Release版本APK +echo "---------------------------------------------" +echo " 步骤2:编译Stage Release APK" +echo "---------------------------------------------" +echo "[INFO] 开始执行Gradle任务:${GRADLE_TASK_PUBLISH}" +# 调试用(注释正式任务,启用调试任务) +# bash gradlew :${APP_NAME}:${GRADLE_TASK_DEBUG} +bash gradlew :${APP_NAME}:${GRADLE_TASK_PUBLISH} + +if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then + echo "[ERROR] Gradle编译任务失败!" + exit 1 +fi +echo "[INFO] Stage Release APK编译成功!" + +# 5. 添加WinBoLL正式标签 +echo "---------------------------------------------" +echo " 步骤3:添加WinBoLL标签" +echo "---------------------------------------------" +addWinBoLLTag ${APP_NAME} +if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then + echo "[ERROR] WinBoLL标签添加失败,脚本终止!" + exit ${EXIT_CODE_ERR_ADD_WINBOLL_TAG} +fi + +# 6. (可选)添加GitHub Workflows标签(当前逻辑注释,保留扩展能力) +# echo "---------------------------------------------" +# echo " 步骤4:添加Workflows标签(可选)" +# echo "---------------------------------------------" +# echo "是否添加GitHub Workflows Beta标签?(Y/n) " +# askAddWorkflowsTag +# nAskAddWorkflowsTag=$? +# if [[ ${nAskAddWorkflowsTag} -eq 1 ]]; then +# addWorkflowsTag ${APP_NAME} +# if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then +# echo "[ERROR] Workflows标签添加失败,脚本终止!" +# exit 1 +# fi +# fi + +# 7. 清理更新描述文件 +echo "---------------------------------------------" +echo " 步骤5:清理更新描述文件" +echo "---------------------------------------------" +echo "" > "${APP_NAME}/app_update_description.txt" +echo "[INFO] 已清空${APP_NAME}/app_update_description.txt" + +# 8. 提交并推送源码与标签 +echo "---------------------------------------------" +echo " 步骤6:提交并推送源码" +echo "---------------------------------------------" +git add . +git commit -m "<${APP_NAME}> 开始新的Stage版本开发。" +echo "[INFO] 源码提交成功,开始推送..." + +# 推送源码到远程仓库 +git push origin +# 推送标签到远程仓库 +git push origin --tags + +if [[ $? -eq ${EXIT_CODE_SUCCESS} ]]; then + echo "[INFO] 源码与标签推送成功!" else - echo "The $1/build.properties file does not exist." - echo -e "Work dir error." + echo "[ERROR] 源码与标签推送失败!" exit 1 fi -# 检查源码状态 -result=$(checkGitSources) -if [[ $? -eq 0 ]]; then - echo $result - # 如果Git已经提交了所有代码就执行标签和应用发布操作 +# ==================== 主流程结束 ==================== +echo "=============================================" +echo " WinBoLL 应用发布完成!" +echo "=============================================" +exit ${EXIT_CODE_SUCCESS} - # 预先询问是否添加工作流标签 - #echo "Add Github Workflows Tag? (yes/No)" - #result=$(askAddWorkflowsTag) - #nAskAddWorkflowsTag=$? - #echo $result - - # 发布应用 - echo "Publishing WinBoLL APK ..." - # 脚本调试时使用 - #bash gradlew :$1:assembleBetaDebug - # 正式发布 - bash gradlew :$1:assembleStageRelease - echo "Publishing WinBoLL APK OK." - - # 添加 WinBoLL 标签 - result=$(addWinBoLLTag $1) - echo $result - if [[ $? -eq 0 ]]; then - echo $result - # WinBoLL 标签添加成功 - else - echo -e "${0}: addWinBoLLTag $1\n${result}\nAdd WinBoLL tag cancel." - exit 1 # addWinBoLLTag 异常 - fi - - # 添加 GitHub 工作流标签 - #if [[ $nAskAddWorkflowsTag -eq 1 ]]; then - # 如果用户选择添加工作流标签 - #result=$(addWorkflowsTag $1) - #if [[ $? -eq 0 ]]; then - # echo $result - # 工作流标签添加成功 - #else - #echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel." - #exit 1 # addWorkflowsTag 异常 - #fi - #fi - - ## 清理更新描述文件内容 - echo "" > $1/app_update_description.txt - - # 设置新版本开发参数配置 - # 提交配置 - git add . - git commit -m "<$1>Start New Stage Version." - echo "Push sources to git repositories ..." - # 推送源码到所有仓库 - git push origin && git push origin --tags -else - echo -e "${0}: checkGitSources\n${result}\nShell cancel." - exit 1 # checkGitSources 异常 -fi diff --git a/.winboll/winboll_app_build.gradle b/.winboll/winboll_app_build.gradle index 9c932b8..3522fd3 100644 --- a/.winboll/winboll_app_build.gradle +++ b/.winboll/winboll_app_build.gradle @@ -64,6 +64,11 @@ android { dimension "WinBoLLApp" } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } // 应用包输出配置 // diff --git a/README.md b/README.md index 0ddef51..1eac983 100644 --- a/README.md +++ b/README.md @@ -155,3 +155,11 @@ $ bash gradlew assembleBetaDebug $ bash gradlew assembleStageDebug ### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。 + +# 应用版本号命名方式 +## statge 渠道 +V<应用开发环境编号><应用功能变更号><应用调试阶段号> +如:APPBase_15.7.0 +## beta 渠道 +V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> +如:APPBase_15.9.6-beta8_5413 diff --git a/build.gradle b/build.gradle index f3a5f4e..858671d 100644 --- a/build.gradle +++ b/build.gradle @@ -100,12 +100,15 @@ allprojects { } subprojects { + // 1. 对纯 Java 模块的 JavaCompile 任务配置(升级为 Java 11) tasks.withType(JavaCompile) { options.compilerArgs << "-parameters" - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + // 可选:确保编码一致 + options.encoding = "UTF-8" } - } + } } task clean(type: Delete) { diff --git a/winboll/build.gradle b/winboll/build.gradle index 548cc8b..95f112f 100644 --- a/winboll/build.gradle +++ b/winboll/build.gradle @@ -19,11 +19,11 @@ def genVersionName(def versionName){ android { - // 1. compileSdkVersion:必须 ≥ targetSdkVersion,建议直接等于 targetSdkVersion(30) - compileSdkVersion 30 + // 关键:改为你已安装的 SDK 32(≥ targetSdkVersion 30,兼容已安装环境) + compileSdkVersion 32 - // 2. buildToolsVersion:需匹配 compileSdkVersion,建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion) - buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版 + // 直接使用已安装的构建工具 33.0.3(无需修改) + buildToolsVersion "33.0.3" defaultConfig { applicationId "cc.winboll.studio.winboll" @@ -43,6 +43,12 @@ android { packagingOptions { doNotStrip "*/*/libmimo_1011.so" } + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下 + } + } } dependencies { @@ -63,6 +69,10 @@ dependencies { api 'io.github.medyo:android-about-page:2.0.0' // 网络连接类库 api 'com.squareup.okhttp3:okhttp:4.4.1' + // OkHttp网络请求 + implementation 'com.squareup.okhttp3:okhttp:3.14.9' + // FastJSON解析 + implementation 'com.alibaba:fastjson:1.2.76' // AndroidX 类库 api 'androidx.appcompat:appcompat:1.1.0' diff --git a/winboll/build.properties b/winboll/build.properties index d46ac26..d2a4938 100644 --- a/winboll/build.properties +++ b/winboll/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Dec 07 04:17:43 GMT 2025 -stageCount=8 +#Tue Jan 06 06:07:46 GMT 2026 +stageCount=9 libraryProject= baseVersion=15.11 -publishVersion=15.11.7 -buildCount=1 -baseBetaVersion=15.11.8 +publishVersion=15.11.8 +buildCount=10 +baseBetaVersion=15.11.9 diff --git a/winboll/libs/libWeWorkSpecSDK.so b/winboll/libs/libWeWorkSpecSDK.so new file mode 100644 index 0000000..d9f592b Binary files /dev/null and b/winboll/libs/libWeWorkSpecSDK.so differ diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml index cfcf3fb..8236f00 100644 --- a/winboll/src/main/AndroidManifest.xml +++ b/winboll/src/main/AndroidManifest.xml @@ -278,6 +278,10 @@ + + + + - + \ No newline at end of file diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java index 24f4d2a..935b86f 100644 --- a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java +++ b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java @@ -1,6 +1,6 @@ package cc.winboll.studio.winboll; -import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -8,20 +8,17 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; +import cc.winboll.studio.libaes.activitys.AboutActivity; import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity; -import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; import cc.winboll.studio.libaes.models.APPInfo; import cc.winboll.studio.libaes.models.DrawerMenuBean; -import cc.winboll.studio.libaes.unittests.TestAButtonFragment; -import cc.winboll.studio.libaes.unittests.TestViewPageFragment; import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.winboll.R; import cc.winboll.studio.winboll.activities.SettingsActivity; +import cc.winboll.studio.winboll.activities.WXPayActivity; import cc.winboll.studio.winboll.fragments.BrowserFragment; import java.util.ArrayList; -import android.content.Intent; -import cc.winboll.studio.libaes.activitys.AboutActivity; public class MainActivity extends DrawerFragmentActivity { @@ -152,6 +149,8 @@ public class MainActivity extends DrawerFragmentActivity { } } else if (nItemId == R.id.item_settings) { WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), SettingsActivity.class); + } else if (nItemId == R.id.item_wxpayactivity) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), WXPayActivity.class); } else if (nItemId == cc.winboll.studio.libaes.R.id.item_about) { Intent intent = new Intent(getApplicationContext(), AboutActivity.class); APPInfo appInfo = genDefaultAPPInfo(); diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java b/winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java new file mode 100644 index 0000000..69c7493 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java @@ -0,0 +1,28 @@ +package cc.winboll.studio.winboll; + +/** + * 微信支付配置类 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class WxPayConfig { + // ========== 核心修改点:替换为你的服务端地址 ========== + // 服务端IP/域名 + 端口(Docker部署的服务端,需确保安卓端可访问) + public static final String BASE_URL = "https://wxpay.winboll.cc"; + + // 统一下单接口路径(对应服务端的测试接口) + public static final String CREATE_ORDER_URL = BASE_URL + "/pay/createOrder"; + + // 订单查询接口路径 + public static final String QUERY_ORDER_URL = BASE_URL + "/pay/queryOrder"; + + // ========== 固定支付配置 ========== + public static final String ORDER_BODY = "定额测试支付"; // 商品描述 + public static final int TOTAL_FEE = 1; // 固定金额:1分(沙箱环境推荐) + public static final String TRADE_TYPE = "NATIVE"; // 支付类型:二维码 + + // ========== 轮询配置 ========== + public static final long POLL_INTERVAL = 10000; // 轮询间隔:10秒 + public static final long POLL_TIMEOUT = 45000; // 轮询超时:45秒 +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java new file mode 100644 index 0000000..e881993 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java @@ -0,0 +1,211 @@ +package cc.winboll.studio.winboll.activities; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.WxPayConfig; +import cc.winboll.studio.winboll.utils.SpecUtil; +import cc.winboll.studio.winboll.utils.WxPayApi; +import cc.winboll.studio.winboll.utils.ZXingUtils; +import java.util.Timer; +import java.util.TimerTask; + + + +/** + * 主界面:生成二维码 + 轮询查询支付结果 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class WXPayActivity extends WinBoLLActivity implements IWinBoLLActivity { + + private static final String TAG = "WXPayActivity"; + + // Handler消息标识 + private static final int MSG_POLL_TIMEOUT = 1001; + private static final int MSG_POLL_SUCCESS = 1002; + private static final int MSG_POLL_FAILED = 1003; + + private ImageView mIvQrCode; + private TextView mTvOrderNo; + private TextView mTvPayStatus; + private Button mBtnCreateOrder; + + private String mOutTradeNo; // 商户订单号 + private Timer mPollTimer; // 轮询定时器 + private long mPollStartTime; // 轮询开始时间 + + + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + private Handler mPollHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case MSG_POLL_TIMEOUT: + stopPoll(); + mTvPayStatus.setText("支付状态:轮询超时"); + mTvPayStatus.setTextColor(getResources().getColor(android.R.color.darker_gray)); + Toast.makeText(WXPayActivity.this, "轮询超时,请手动查询", Toast.LENGTH_SHORT).show(); + break; + case MSG_POLL_SUCCESS: + boolean isPaySuccess = (boolean) msg.obj; + String tradeState = (String) msg.getData().getString("tradeState"); + stopPoll(); + if (isPaySuccess) { + mTvPayStatus.setText("支付状态:支付成功 ✅"); + mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark)); + Toast.makeText(WXPayActivity.this, "支付成功!", Toast.LENGTH_SHORT).show(); + } else { + mTvPayStatus.setText("支付状态:" + tradeState); + mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark)); + } + break; + case MSG_POLL_FAILED: + String errorMsg = (String) msg.obj; + mTvPayStatus.setText("查询失败:" + errorMsg); + break; + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_wxpay); + + initView(); + initListener(); + } + + private void initView() { + mIvQrCode = findViewById(R.id.iv_qrcode); + mTvOrderNo = findViewById(R.id.tv_order_no); + mTvPayStatus = findViewById(R.id.tv_pay_status); + mBtnCreateOrder = findViewById(R.id.btn_create_order); + } + + private void initListener() { + mBtnCreateOrder.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createOrder(); + } + }); + } + + /** + * 统一下单,生成二维码 + */ + private void createOrder() { + mBtnCreateOrder.setEnabled(false); + mTvPayStatus.setText("支付状态:生成订单中..."); + mIvQrCode.setImageBitmap(null); + + WxPayApi.createOrder(new WxPayApi.OnCreateOrderCallback() { + @Override + public void onSuccess(String outTradeNo, String codeUrl) { + mOutTradeNo = outTradeNo; + mTvOrderNo.setText("商户订单号:" + outTradeNo); + mTvPayStatus.setText("支付状态:未支付,请扫码"); + + // 生成二维码 + Bitmap qrCodeBitmap = ZXingUtils.createQRCodeBitmap(codeUrl, 250, 250); + if (qrCodeBitmap != null) { + mIvQrCode.setImageBitmap(qrCodeBitmap); + // 开始轮询查询支付结果 + startPoll(); + } else { + mTvPayStatus.setText("支付状态:生成二维码失败"); + mBtnCreateOrder.setEnabled(true); + } + } + + @Override + public void onFailure(String errorMsg) { + SpecUtil.WWSpecLogError(TAG, "统一下单失败:" + errorMsg); + mTvPayStatus.setText("生成订单失败:" + errorMsg); + mBtnCreateOrder.setEnabled(true); + Toast.makeText(WXPayActivity.this, errorMsg, Toast.LENGTH_SHORT).show(); + } + }); + } + + /** + * 开始轮询查询支付结果 + */ + private void startPoll() { + stopPoll(); // 先停止之前的轮询 + mPollStartTime = System.currentTimeMillis(); + mPollTimer = new Timer(); + mPollTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + // 检查是否超时 + long elapsedTime = System.currentTimeMillis() - mPollStartTime; + if (elapsedTime >= WxPayConfig.POLL_TIMEOUT) { + mPollHandler.sendEmptyMessage(MSG_POLL_TIMEOUT); + return; + } + + // 查询订单状态 + WxPayApi.queryOrder(mOutTradeNo, new WxPayApi.OnQueryOrderCallback() { + @Override + public void onSuccess(boolean isPaySuccess, String tradeState) { + Message msg = Message.obtain(); + msg.what = MSG_POLL_SUCCESS; + msg.obj = isPaySuccess; + Bundle bundle = new Bundle(); + bundle.putString("tradeState", tradeState); + msg.setData(bundle); + mPollHandler.sendMessage(msg); + } + + @Override + public void onFailure(String errorMsg) { + Message msg = Message.obtain(); + msg.what = MSG_POLL_FAILED; + msg.obj = errorMsg; + mPollHandler.sendMessage(msg); + } + }); + } + }, 0, WxPayConfig.POLL_INTERVAL); + } + + /** + * 停止轮询 + */ + private void stopPoll() { + if (mPollTimer != null) { + mPollTimer.cancel(); + mPollTimer = null; + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + stopPoll(); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java b/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java new file mode 100644 index 0000000..5fdac27 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java @@ -0,0 +1,311 @@ +package cc.winboll.studio.winboll.unittest; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.activities.WinBoLLActivity; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/01/03 10:52 + * @Describe 企业微信SDK接口测试(基础调试版) + * 包含:SDK初始化、基础接口调用、日志输出、主线程回调处理 + */ +public class TestWeWorkSpecSDK extends WinBoLLActivity implements IWinBoLLActivity, View.OnClickListener { + + public static final String TAG = "TestWeWorkSpecSDK"; + + // ------------------- 企业微信SDK配置常量(需替换为实际项目参数) ------------------- + // 企业微信 CorpID(从企业微信管理后台获取) + private static final String CORP_ID = "wwb37c73f34c722852"; + // 应用 AgentID(从企业微信应用管理后台获取) + private static final String AGENT_ID = "your_agent_id_here"; + // 应用 Secret(从企业微信应用管理后台获取,注意保密) + private static final String APP_SECRET = "your_app_secret_here"; + + // ------------------- Handler消息标识(主线程处理SDK回调) ------------------- + private static final int MSG_SDK_INIT_SUCCESS = 1001; + private static final int MSG_SDK_INIT_FAILED = 1002; + private static final int MSG_GET_CORP_INFO_SUCCESS = 1003; + private static final int MSG_GET_CORP_INFO_FAILED = 1004; + + // ------------------- 控件声明 ------------------- + private Button mBtnInitSDK; + private Button mBtnGetCorpInfo; + private Button mBtnCheckAuth; + + // ------------------- 主线程Handler(处理SDK异步回调) ------------------- + private Handler mWeWorkHandler; + + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_test_weworkspecsdk); + + // 初始化控件 + initViews(); + // 绑定点击事件 + initEvents(); + // 初始化Handler(主线程处理回调,更新UI) + initHandler(); + // 初始化SDK(可选:启动时自动初始化,或点击按钮初始化) + // initWeWorkSDK(); + } + + /** + * 初始化控件(Java 7 显式绑定) + */ + private void initViews() { + mBtnInitSDK = (Button) findViewById(R.id.btn_init_sdk); + mBtnGetCorpInfo = (Button) findViewById(R.id.btn_get_corp_info); + mBtnCheckAuth = (Button) findViewById(R.id.btn_check_auth); + } + + /** + * 绑定点击事件(Java 7 匿名内部类) + */ + private void initEvents() { + mBtnInitSDK.setOnClickListener(this); + mBtnGetCorpInfo.setOnClickListener(this); + mBtnCheckAuth.setOnClickListener(this); + } + + /** + * 初始化主线程Handler(处理SDK异步回调,安全更新UI) + */ + private void initHandler() { + mWeWorkHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case MSG_SDK_INIT_SUCCESS: + showToast("企业微信SDK初始化成功"); + LogUtils.d(TAG, "SDK初始化成功"); + break; + case MSG_SDK_INIT_FAILED: + String initError = (String) msg.obj; + showToast("SDK初始化失败:" + initError); + LogUtils.e(TAG, "SDK初始化失败:" + initError); + break; + case MSG_GET_CORP_INFO_SUCCESS: + String corpInfo = (String) msg.obj; + showToast("获取企业信息成功"); + LogUtils.d(TAG, "企业信息:" + corpInfo); + break; + case MSG_GET_CORP_INFO_FAILED: + String corpError = (String) msg.obj; + showToast("获取企业信息失败:" + corpError); + LogUtils.e(TAG, "获取企业信息失败:" + corpError); + break; + default: + break; + } + } + }; + } + + // ------------------- 企业微信SDK核心接口调用 ------------------- + + /** + * 初始化企业微信SDK(异步操作,通过Handler回调结果) + */ + private void initWeWorkSDK() { + showToast("开始初始化企业微信SDK..."); + // 模拟SDK异步初始化(实际项目中替换为企业微信SDK的真实初始化接口) + new Thread(new Runnable() { + @Override + public void run() { + try { + // 真实SDK初始化逻辑示例: + // WeWorkSDK.init(TestWeWorkSpecSDK.this, CORP_ID, AGENT_ID, new WeWorkSDKCallback() { + // @Override + // public void onSuccess() { + // mWeWorkHandler.sendEmptyMessage(MSG_SDK_INIT_SUCCESS); + // } + // + // @Override + // public void onFailure(String errorMsg) { + // Message msg = Message.obtain(); + // msg.what = MSG_SDK_INIT_FAILED; + // msg.obj = errorMsg; + // mWeWorkHandler.sendMessage(msg); + // } + // }); + + // 调试模拟:休眠1秒,模拟异步初始化 + Thread.sleep(1000); + // 模拟初始化成功(如需测试失败,替换为发送MSG_SDK_INIT_FAILED) + mWeWorkHandler.sendEmptyMessage(MSG_SDK_INIT_SUCCESS); + // 模拟初始化失败 + // Message msg = Message.obtain(); + // msg.what = MSG_SDK_INIT_FAILED; + // msg.obj = "CorpID或AgentID错误"; + // mWeWorkHandler.sendMessage(msg); + } catch (InterruptedException e) { + e.printStackTrace(); + Message msg = Message.obtain(); + msg.what = MSG_SDK_INIT_FAILED; + msg.obj = "线程中断:" + e.getMessage(); + mWeWorkHandler.sendMessage(msg); + } + } + }).start(); + } + + /** + * 获取企业基本信息(异步操作,需先初始化SDK) + */ + private void getCorpInfo() { + if (!isSDKInitialized()) { + showToast("请先初始化SDK"); + return; + } + showToast("开始获取企业信息..."); + // 模拟SDK异步获取企业信息(实际项目中替换为真实接口) + new Thread(new Runnable() { + @Override + public void run() { + try { + // 真实SDK接口示例: + // WeWorkSDK.getCorpInfo(APP_SECRET, new CorpInfoCallback() { + // @Override + // public void onSuccess(CorpInfo info) { + // Message msg = Message.obtain(); + // msg.what = MSG_GET_CORP_INFO_SUCCESS; + // msg.obj = "企业名称:" + info.getCorpName() + ",企业ID:" + info.getCorpId(); + // mWeWorkHandler.sendMessage(msg); + // } + // + // @Override + // public void onFailure(String errorMsg) { + // Message msg = Message.obtain(); + // msg.what = MSG_GET_CORP_INFO_FAILED; + // msg.obj = errorMsg; + // mWeWorkHandler.sendMessage(msg); + // } + // }); + + // 调试模拟:休眠1秒,模拟异步获取 + Thread.sleep(1000); + // 模拟获取成功 + Message successMsg = Message.obtain(); + successMsg.what = MSG_GET_CORP_INFO_SUCCESS; + successMsg.obj = "企业名称:WinBoLL Studio,企业ID:" + CORP_ID; + mWeWorkHandler.sendMessage(successMsg); + // 模拟获取失败 + // Message failMsg = Message.obtain(); + // failMsg.what = MSG_GET_CORP_INFO_FAILED; + // failMsg.obj = "AppSecret错误或权限不足"; + // mWeWorkHandler.sendMessage(failMsg); + } catch (InterruptedException e) { + e.printStackTrace(); + Message msg = Message.obtain(); + msg.what = MSG_GET_CORP_INFO_FAILED; + msg.obj = "线程中断:" + e.getMessage(); + mWeWorkHandler.sendMessage(msg); + } + } + }).start(); + } + + /** + * 检查当前用户是否已授权(同步操作,示例) + */ + private void checkAuthStatus() { + if (!isSDKInitialized()) { + showToast("请先初始化SDK"); + return; + } + // 真实SDK接口示例: + // boolean isAuthorized = WeWorkSDK.isAuthorized(); + // 调试模拟:默认返回true + boolean isAuthorized = true; + + if (isAuthorized) { + showToast("用户已授权"); + LogUtils.d(TAG, "当前用户已授权企业微信应用"); + } else { + showToast("用户未授权,请先授权"); + LogUtils.d(TAG, "当前用户未授权企业微信应用"); + // 真实项目中可调用授权接口: + // WeWorkSDK.requestAuth(TestWeWorkSpecSDK.this, new AuthCallback() { + // @Override + // public void onSuccess(String code) { + // showToast("授权成功,code:" + code); + // } + // + // @Override + // public void onFailure(String errorMsg) { + // showToast("授权失败:" + errorMsg); + // } + // }); + } + } + + // ------------------- 工具方法 ------------------- + + /** + * 检查SDK是否已初始化(模拟方法,实际项目中替换为SDK的真实状态检查) + */ + private boolean isSDKInitialized() { + // 真实SDK可通过静态方法检查状态: + // return WeWorkSDK.isInitialized(); + // 调试模拟:假设Handler不为空即表示已初始化 + return mWeWorkHandler != null; + } + + /** + * 显示Toast提示(Java 7 简化封装) + */ + private void showToast(String msg) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + } + + // ------------------- 点击事件处理 ------------------- + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btn_init_sdk) { + initWeWorkSDK(); + } else if (id == R.id.btn_get_corp_info) { + getCorpInfo(); + } else if (id == R.id.btn_check_auth) { + checkAuthStatus(); + } + } + + // ------------------- 生命周期管理 ------------------- + + @Override + protected void onDestroy() { + super.onDestroy(); + // 释放Handler资源,避免内存泄漏 + if (mWeWorkHandler != null) { + mWeWorkHandler.removeCallbacksAndMessages(null); + mWeWorkHandler = null; + } + // 真实SDK需调用销毁方法: + // WeWorkSDK.destroy(); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java new file mode 100644 index 0000000..4e23dcb --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java @@ -0,0 +1,83 @@ +package cc.winboll.studio.winboll.utils; + +import android.os.Handler; +import android.os.Looper; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * OkHttp网络请求工具类 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class OkHttpUtil { + + private static OkHttpClient sOkHttpClient; + private static Handler sMainHandler = new Handler(Looper.getMainLooper()); + + static { + sOkHttpClient = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .build(); + } + + /** + * GET请求 + * @param url 请求地址 + * @param callback 回调 + */ + public static void get(String url, final OnResultCallback callback) { + Request request = new Request.Builder() + .url(url) + .get() + .build(); + + sOkHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, final IOException e) { + sMainHandler.post(new Runnable() { + @Override + public void run() { + if (callback != null) { + callback.onFailure(e.getMessage()); + } + } + }); + } + + @Override + public void onResponse(Call call, final Response response) throws IOException { + final String result = response.body().string(); + sMainHandler.post(new Runnable() { + @Override + public void run() { + if (callback != null) { + if (response.isSuccessful()) { + callback.onSuccess(result); + } else { + callback.onFailure("请求失败:" + response.code()); + } + } + } + }); + } + }); + } + + /** + * 回调接口 + */ + public interface OnResultCallback { + void onSuccess(String result); + void onFailure(String errorMsg); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java new file mode 100644 index 0000000..279f286 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java @@ -0,0 +1,26 @@ +package cc.winboll.studio.winboll.utils; + +import cc.winboll.studio.libappbase.LogUtils; + +/** + * 日志工具类(适配项目规范) + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class SpecUtil { + + private static final boolean isDebug = true; + + public static void WWSpecLogInfo(String tag, String msg) { + if (isDebug) { + LogUtils.i(tag, msg); + } + } + + public static void WWSpecLogError(String tag, String msg) { + if (isDebug) { + LogUtils.e(tag, msg); + } + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java new file mode 100644 index 0000000..dd0b508 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java @@ -0,0 +1,100 @@ +package cc.winboll.studio.winboll.utils; + +import cc.winboll.studio.winboll.WxPayConfig; +import com.alibaba.fastjson.JSONObject; + +/** + * 微信支付服务端接口封装 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class WxPayApi { + + /** + * 统一下单(生成二维码) + * @param callback 回调 + */ + public static void createOrder(final OnCreateOrderCallback callback) { + // 拼接请求参数(服务端测试接口需支持GET传参,若为POST需修改为表单/JSON) + String url = WxPayConfig.CREATE_ORDER_URL + + "?body=" + WxPayConfig.ORDER_BODY + + "&totalFee=" + WxPayConfig.TOTAL_FEE + + "&tradeType=" + WxPayConfig.TRADE_TYPE; + + OkHttpUtil.get(url, new OkHttpUtil.OnResultCallback() { + @Override + public void onSuccess(String result) { + try { + JSONObject jsonObject = JSONObject.parseObject(result); + String outTradeNo = jsonObject.getString("out_trade_no"); + String codeUrl = jsonObject.getString("code_url"); + if (callback != null) { + callback.onSuccess(outTradeNo, codeUrl); + } + } catch (Exception e) { + if (callback != null) { + callback.onFailure("解析统一下单结果失败:" + e.getMessage()); + } + } + } + + @Override + public void onFailure(String errorMsg) { + if (callback != null) { + callback.onFailure("统一下单请求失败:" + errorMsg); + } + } + }); + } + + /** + * 订单查询 + * @param outTradeNo 商户订单号 + * @param callback 回调 + */ + public static void queryOrder(String outTradeNo, final OnQueryOrderCallback callback) { + String url = WxPayConfig.QUERY_ORDER_URL + "?outTradeNo=" + outTradeNo; + + OkHttpUtil.get(url, new OkHttpUtil.OnResultCallback() { + @Override + public void onSuccess(String result) { + try { + JSONObject jsonObject = JSONObject.parseObject(result); + String tradeState = jsonObject.getString("trade_state"); + boolean isSuccess = "SUCCESS".equals(tradeState); + if (callback != null) { + callback.onSuccess(isSuccess, tradeState); + } + } catch (Exception e) { + if (callback != null) { + callback.onFailure("解析订单查询结果失败:" + e.getMessage()); + } + } + } + + @Override + public void onFailure(String errorMsg) { + if (callback != null) { + callback.onFailure("订单查询请求失败:" + errorMsg); + } + } + }); + } + + /** + * 统一下单回调接口 + */ + public interface OnCreateOrderCallback { + void onSuccess(String outTradeNo, String codeUrl); + void onFailure(String errorMsg); + } + + /** + * 订单查询回调接口 + */ + public interface OnQueryOrderCallback { + void onSuccess(boolean isPaySuccess, String tradeState); + void onFailure(String errorMsg); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java new file mode 100644 index 0000000..389298d --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java @@ -0,0 +1,75 @@ +package cc.winboll.studio.winboll.utils; + +import android.graphics.Bitmap; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.journeyapps.barcodescanner.BarcodeEncoder; +import java.util.HashMap; +import java.util.Map; + +/** + * ZXing二维码生成工具类 + * 依赖:com.google.zxing:core:3.4.1 + com.journeyapps:zxing-android-embedded:3.6.0 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class ZXingUtils { + + /** + * 生成二维码Bitmap(核心方法,使用journeyapps工具类) + * @param content 内容(如微信支付的code_url) + * @param width 二维码宽度(px) + * @param height 二维码高度(px) + * @return 二维码Bitmap,失败返回null + */ + public static Bitmap createQRCodeBitmap(String content, int width, int height) { + // 1. 入参合法性校验 + if (content == null || content.trim().isEmpty()) { + return null; + } + if (width <= 0 || height <= 0) { + return null; + } + + // 2. 配置二维码参数 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 字符编码 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 高容错级别(H级可容忍30%遮挡) + hints.put(EncodeHintType.MARGIN, 1); // 边距(值越小,二维码越紧凑,建议1-4) + + try { + // 3. 生成BitMatrix(二维码矩阵) + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix bitMatrix = qrCodeWriter.encode( + content, + BarcodeFormat.QR_CODE, + width, + height, + hints + ); + + // 4. 转换BitMatrix为Bitmap(关键:使用journeyapps的BarcodeEncoder) + BarcodeEncoder barcodeEncoder = new BarcodeEncoder(); + return barcodeEncoder.createBitmap(bitMatrix); + + } catch (WriterException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 重载方法:生成正方形二维码(宽度=高度) + * @param content 内容 + * @param size 二维码边长(px) + * @return 二维码Bitmap + */ + public static Bitmap createQRCodeBitmap(String content, int size) { + return createQRCodeBitmap(content, size, size); + } +} + diff --git a/winboll/src/main/java/com/tencent/wework/SpecCallbackSDK.java b/winboll/src/main/java/com/tencent/wework/SpecCallbackSDK.java new file mode 100644 index 0000000..18faee5 --- /dev/null +++ b/winboll/src/main/java/com/tencent/wework/SpecCallbackSDK.java @@ -0,0 +1,210 @@ +package com.tencent.wework; + +import java.util.Map; +import java.util.HashMap;; + +/** + * @warning: 1. 不要修改成员变量名,native方法内有反射调用 + * 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内 + * 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件 + */ +public final class SpecCallbackSDK { + + /** + * @description 调用本地方法后实例化的对象指针 + */ + private long specCallbackSDKptr = 0; + + public long GetPtr() { return specCallbackSDKptr; } + + /** + * @description: 回包的headers + */ + private Map responseHeaders; + + public Map GetResponseHeaders() { return responseHeaders; } + + /** + * @description: 回包的加密后的body + */ + private String responseBody; + + public String GetResponseBody() { return responseBody; } + + /** + * @description: 每个请求构造一个SpecCallbackSDK示例, + * SpecCallbackSDK仅持有headers和body的引用, + * 因此需保证headers和body的生存期比SpecCallbackSDK长 + * @param method: 请求方法GET/POST + * @param headers: 请求header + * @param body: 请求body + * @example: + * SpecCallbackSDK sdk = new SpecCallbackSDK(method, headers, body); + * if (sdk.IsOk()) { + * String corpid = sdk.GetCorpId(); + * String agentid = sdk.GetAgentId(); + * String call_type = sdk.GetCallType(); + * String data = sdk.GetData(); + * //do something... + * } + * String response = ...; + * sdk.BuildResponseHeaderBody(response); + * Map responseHeaders = sdk.GetResponseHeaders(); + * String body = sdk.GetResponseBody(); + * //do response + * + * @return errorcode 示例如下: + * -920001: 未设置请求方法 + * -920002: 未设置请求header + * -920003: 未设置请求body + * */ + public SpecCallbackSDK(String method, Map headers, String body) { + try { + specCallbackSDKptr = NewCallbackSDK(method, headers, body); + } catch (Exception e) { + SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage()); + } + } + + private native long NewCallbackSDK(String method, Map headers, String body); + + /** + * @usage 在Java对象的内存回收前析构C++对象 + */ + @Override + protected void finalize() throws Throwable { + DeleteCPPInstance(specCallbackSDKptr); + super.finalize(); + } + + private native void DeleteCPPInstance(long specCallbackSDKptr); + + /** + * @description: 判断构造函数中传入的请求是否解析成功 + * @return: 成功与否 + * */ + public boolean IsOk() { + return IsOk(specCallbackSDKptr); + } + + private native boolean IsOk(long specCallbackSDKptr); + + /** + * @description: 获取请求的企业 + * @require: 仅当IsOk() == true可调用 + * @return: corpid + * */ + public String GetCorpId() { + return GetCorpId(specCallbackSDKptr); + } + + private native String GetCorpId(long specCallbackSDKptr); + + /** + * @description: 获取请求的应用 + * @require: 仅当IsOk() == true可调用 + * @return: agentid + * */ + public long GetAgentId() { + return GetAgentId(specCallbackSDKptr); + } + + private native long GetAgentId(long specCallbackSDKptr); + + /** + * @description: 获取请求的类型 + * @require: 仅当IsOk() == true可调用 + * @return: 1 - 来自[应用调用专区]的请求 + * 2 - 来自企业微信的回调事件 + * */ + public long GetCallType() { + return GetCallType(specCallbackSDKptr); + } + + private native long GetCallType(long specCallbackSDKptr); + + /** + * @description: 获取请求数据 + * @require: 仅当IsOk() == true可调用 + * @return: 请求数据,根据call_type可能是: + * - 企业微信回调事件 + * - [应用调用专区]接口中的request_data + * */ + public String GetData() { + return GetData(specCallbackSDKptr); + } + + private native String GetData(long specCallbackSDKptr); + + /** + * @description: 是否异步请求 + * @require: 仅当IsOk() == true可调用 + * @return: 是否异步请求 + * */ + public boolean GetIsAsync() { + return GetIsAsync(specCallbackSDKptr); + } + + private native boolean GetIsAsync(long specCallbackSDKptr); + + /** + * @description: 获取请求的job_info, + * @require: 仅当IsOk() == true可调用 + * @return: job_info,无需理解内容, + * 在同一个请求上下文中使用SpecSDK的时候传入 + * */ + public String GetJobInfo() { + return GetJobInfo(specCallbackSDKptr); + } + + private native String GetJobInfo(long specCallbackSDKptr); + + /** + * @description: 获取请求的ability_id,[应用调用专区]接口时指定 + * @require: 仅当IsOk() == true可调用 + * @return: ability_id + * */ + public String GetAbilityId() { + return GetAbilityId(specCallbackSDKptr); + } + + private native String GetAbilityId(long specCallbackSDKptr); + + /** + * @description: 获取请求的notify_id,用于[应用同步调用专区程序]接口 + * @require: 仅当IsOk() == true可调用 + * @return: notify_id + * */ + public String GetNotifyId() { + return GetNotifyId(specCallbackSDKptr); + } + + private native String GetNotifyId(long specCallbackSDKptr); + + /** + * @description: 对返回包计算签名&加密 + * @param response: 待加密的回包明文.如果IsOk()==false,传入空串即可 + * @note 本接口的执行问题可查看日志 + * */ + public void BuildResponseHeaderBody(String response) { + try { + responseHeaders = new HashMap(); + responseBody = ""; + BuildResponseHeaderBody(specCallbackSDKptr, response); + } catch (Exception e) { + SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage()); + } + } + + private native void BuildResponseHeaderBody(long specCallbackSDKptr, String response); + + // 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询 + static { + try { + Class.forName("com.tencent.wework.SpecUtil"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/winboll/src/main/java/com/tencent/wework/SpecSDK.java b/winboll/src/main/java/com/tencent/wework/SpecSDK.java new file mode 100644 index 0000000..8cf555a --- /dev/null +++ b/winboll/src/main/java/com/tencent/wework/SpecSDK.java @@ -0,0 +1,163 @@ +package com.tencent.wework; + +/** + * @warning: 1. 不要修改成员变量名,native方法内有反射调用 + * 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内 + * 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件 + */ +public final class SpecSDK { + + /** + * @description 调用本地方法后实例化的对象指针 + */ + private long specSDKptr = 0; + + /** + * @usage invoke的请求 + * @example "{\"limit\":1} + */ + private String request; + + public void SetRequest(String request) { + this.request = request; + } + + /** + * @usage 访问上一次invoke的结果 + */ + private String response; + + public String GetResponse() { + return response; + } + + /** + * @param corpid: 企业corpid,必选参数 + * @param agentid: 应用id,必选参数 + * @param ability_id: 能力ID,可选参数 + * @param job_info: job_info,可选参数 + * */ + public SpecSDK(String corpId, long agentId) { + specSDKptr = NewSDK1(corpId, agentId); + } + + private native long NewSDK1(String corpId, long agentId); + + public SpecSDK(String corpId, long agentId, String abilityId) { + specSDKptr = NewSDK2(corpId, agentId, abilityId); + } + + private native long NewSDK2(String corpId, long agentId, String abilityId); + + public SpecSDK(String corpId, long agentId, String abilityId, String jobInfo) { + specSDKptr = NewSDK3(corpId, agentId, abilityId, jobInfo); + } + + private native long NewSDK3(String corpId, long agentId, String abilityId, String jobInfo); + + /** + * @description 使用callback的请求来初始化 + * @param callback_sdk: 要求IsOk()==true + * @return C++内部指针,创建失败时指针仍为0,并输出错误日志 + * */ + public SpecSDK(SpecCallbackSDK callbackSDK) { + specSDKptr = NewSDK4(callbackSDK.GetPtr()); + } + + private native long NewSDK4(long callbackSDK); + + /** + * @usage 在Java对象的内存回收前析构C++对象 + */ + @Override + protected void finalize() throws Throwable { + DeleteCPPInstance(specSDKptr); + super.finalize(); + } + + private native void DeleteCPPInstance(long specSDKptr); + + /** + * @description 用于在专区内调用企业微信接口 + * @param api_name 接口名 + * @param request json格式的请求数据 + * @param response json格式的返回数据 + * @return errorcode 参考如下: + * 0: 成功 + * -910001: SDK没有初始化 + * -910002: 没有设置请求体 + * -910003: 没有设置请求的API + * -910004: 在SDK成员内找不到成员"response",注意lib内有反射机制,不要修改成员变量名 + * -910005: 使用未初始化的callback初始化SDK + * -910006: invoke调用失败,应检查日志查看具体原因 + * -910007: 响应体为空 + * @note 当返回0时,表示没有网络或请求协议层面或调用方法的失败, + * 调用方需继续检查response中的errcode字段确保业务层面的成功 + * + * @usage 当前版本sdk支持的接口列表,每个接口的具体协议请查看企业微信文档: + * https://developer.work.weixin.qq.com/document/path/91201 + * + * +--------------------------------+--------------------------------+ + * |接口名 |描述 | + * |--------------------------------|--------------------------------| + * |program_async_job_call_back |上报异步任务结果 | + * |sync_msg |获取会话记录 | + * |get_group_chat |获取内部群信息 | + * |get_agree_status_single |获取单聊会话同意情况 | + * |get_agree_status_room |获取群聊会话同意情况 | + * |set_hide_sensitiveinfo_config |设置成员会话组件敏感信息隐藏配置| + * |get_hide_sensitiveinfo_config |获取成员会话组件敏感信息隐藏配置| + * |search_chat |会话名称搜索 | + * |search_msg |会话消息搜索 | + * |create_rule |新增关键词规则 | + * |get_rule_list |获取关键词列表 | + * |get_rule_detail |获取关键词规则详情 | + * |update_rule |修改关键词规则 | + * |delete_rule |删除关键词规则 | + * |get_hit_msg_list |获取命中关键词规则的会话记录 | + * |create_sentiment_task |创建情感分析任务 | + * |get_sentiment_result |获取情感分析结果 | + * |create_summary_task |创建摘要提取任务 | + * |get_summary_result |获取摘要提取结果 | + * |create_customer_tag_task |创建标签匹配任务 | + * |get_customer_tag_result |获取标签任务结果 | + * |create_recommend_dialog_task |创建话术推荐任务 | + * |get_recommend_dialog_result |获取话术推荐结果 | + * |create_private_task |创建自定义模型任务 | + * |get_private_task_result |获取自定义模型结果 | + * |(废弃)document_list |获取知识集列表 | + * |create_spam_task |会话反垃圾创建分析任务 | + * |get_spam_result |会话反垃圾获取任务结果 | + * |create_chatdata_export_job |创建会话内容导出任务 | + * |get_chatdata_export_job_status |获取会话内容导出任务结果 | + * |spec_notify_app |专区通知应用 | + * |create_program_task |创建自定义程序任务 | + * |get_program_task_result |获取自定义程序结果 | + * |knowledge_base_list |获取企业授权给应用的知识集列表 | + * |knowledge_base_create |创建知识集 | + * |knowledge_base_detail |获取知识集详情 | + * |knowledge_base_add_doc |添加知识集內容 | + * |knowledge_base_remove_doc |删除知识集內容 | + * |knowledge_base_modify_name |修改知识集名称 | + * |knowledge_base_delete |删除知识集 | + * |search_contact_or_customer |员工或者客户名称搜索 | + * |create_ww_model_task |创建企微通用模型任务 | + * |get_ww_model_result |获取企微通用模型结果 | + * |get_msg_list_by_page_id |page_id获取消息列表 | + * +-----------------------------------------------------------------+ + * */ + public int Invoke(String apiName) { + return Invoke(specSDKptr, apiName, request); + } + + private native int Invoke(long sdk, String apiName, String request); + + // 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询 + static { + try { + Class.forName("com.tencent.wework.SpecUtil"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/winboll/src/main/java/com/tencent/wework/SpecUtil.java b/winboll/src/main/java/com/tencent/wework/SpecUtil.java new file mode 100644 index 0000000..85ea23b --- /dev/null +++ b/winboll/src/main/java/com/tencent/wework/SpecUtil.java @@ -0,0 +1,171 @@ +package com.tencent.wework; + +//import java.lang.management.ManagementFactory; +//import java.lang.management.RuntimeMXBean; + +/** + * @warning: 1. 不要修改成员变量名,native方法内有反射调用 + * 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内 + * 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件 + * 4. 使用其他工具打印的日志将无法被查询,如需使用SLF4j风格的日志或性能更好的日志框架, + * 请自行封装SpecUtil.SpecLog或SpecUtil.SpecLogNative方法 + * + * @usage: 1. 获取SDK的版本号 + * 2. 打印三个级别的日志 + * 3. 开启调试模式 + */ +public final class SpecUtil { + + /** + * @description SDK版本号 + * @usage 可用于校对不同SDK版本,或后续针对不同的SDK版本添加业务逻辑 + */ + private static final String SDK_VERSION = "1.4.0"; + + public static String GetSDKVersion() { + return SDK_VERSION; + } + + /** + * @description 正确的包名,SDK必须存放在"com.tencent.wework"下,否则会影响本地方法的调用 + */ + private static final String EXPECTED_PACKAGE_NAME = "com.tencent.wework"; + + public static String GetExpectedPackageName() { + return EXPECTED_PACKAGE_NAME; + } + + private static final String LINE_SEPERATOR = System.getProperty("line.separator"); + + public static void WWSpecLogInfo(String... args) { + SpecLog('I', args); + } + + public static void WWSpecLogError(String... args) { + SpecLog('E', args); + } + + public static void WWSpecLogDebug(String... args) { + SpecLog('D', args); + } + + public static void WWSpecLogInfoWithReqId(String reqId, String... args) { + SpecLogWithReqId(reqId, 'I', args); + } + + public static void WWSpecLogErrorWithReqId(String reqId, String... args) { + SpecLogWithReqId(reqId, 'E', args); + } + + public static void WWSpecLogDebugWithReqId(String reqId, String... args) { + SpecLogWithReqId(reqId, 'D', args); + } + + /** + * @usage 打印标准日志 + * @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看 + * @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG + * @param args 自定义参数 + */ + public static void SpecLog(char logLevel, String... args) { + StackTraceElement element = Thread.currentThread().getStackTrace()[3]; + SpecLogNative( + logLevel, + element.getFileName(), + element.getLineNumber(), + String.join(",", args).replace(LINE_SEPERATOR, " ") + ); + } + + /** + * @usage 打印标准日志 + * @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看 + * @param reqid 请求id + * @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG + * @param args 自定义参数 + */ + public static void SpecLogWithReqId(String reqId, char logLevel, String... args) { + StackTraceElement element = Thread.currentThread().getStackTrace()[3]; + SpecLogNativeWithReqId( + reqId, + logLevel, + element.getFileName(), + element.getLineNumber(), + String.join(",", args).replace(LINE_SEPERATOR, " ") + ); + } + + /** + * @usage 打印标准日志 + * @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看 + * 如需SLF4J风格的接口或对日志性能有进一步需求,开发者可以自行封装该函数 + * @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG + * @param fileName 文件名(类名) + * @param lineNumber 行号 + * @param argsString 自定义参数 + */ + public static native void SpecLogNative(char logLevel, String fileName, int lineNumber, String argsString); + + /** + * @usage 打印标准日志 + * @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看 + * 如需SLF4J风格的接口或对日志性能有进一步需求,开发者可以自行封装该函数 + * @param reqid 请求id + * @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG + * @param fileName 文件名(类名) + * @param lineNumber 行号 + * @param argsString 自定义参数 + */ + public static native void SpecLogNativeWithReqId(String reqId, char logLevel, String fileName, int lineNumber, String argsString); + + + + /** + * @usage 开启调试模式,进程级别开关 + * @param debugToken 调试凭证,在管理端获取 + * @param accessToken 应用access token + * @return 是否开启成功 + */ + public static boolean SpecOpenDebugMode(String debugToken, String accessToken) { + return SpecOpenDebugModeNative(debugToken, accessToken); + } + + private static native boolean SpecOpenDebugModeNative(String debugToken, String accessToken); + + /** + * @usage 生成notify id。用户可调用本接口生成notify id,也可完全自定义生成 + * @return 新的notify id,支持纳秒级隔离,内部异常时会输出日志并返回空串 + * @note 1. 用户可先生成notify id,将其与回调数据关联存储后,再使用该notify id通知应用, + * 从而保证回调数据被请求时已存储完毕 + */ + public static String GenerateNotifyId() { + return GenerateNotifyIdNative(); + } + + private static native String GenerateNotifyIdNative(); + + static { + // 检查包名 + String packageName = SpecUtil.class.getPackage().getName(); + if (!EXPECTED_PACKAGE_NAME.equals(packageName)) { + // 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询 + System.out.println("SpecUtil class must be in package com.tencent.wework"); + System.exit(1); + } + + // 加载so库 + try { + System.loadLibrary("WeWorkSpecSDK"); + } catch (UnsatisfiedLinkError e) { + System.out.println("libWeWorkSpecSDK.so not found in java.library.path"); + e.printStackTrace(); + System.exit(1); + } catch (Exception e) { + System.out.println("unexpected exception: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + + SpecUtil.WWSpecLogInfo("SDK init done", "packageName=" + packageName, "SDK_VERSION=" + SDK_VERSION); + } +} diff --git a/winboll/src/main/res/layout/activity_test_weworkspecsdk.xml b/winboll/src/main/res/layout/activity_test_weworkspecsdk.xml new file mode 100644 index 0000000..b1448e8 --- /dev/null +++ b/winboll/src/main/res/layout/activity_test_weworkspecsdk.xml @@ -0,0 +1,30 @@ + + + +