uni-app路由管理神器:vue-router风格体验
@meng-xi/uni-router
为 uni-app 提供类似 vue-router 风格的路由管理系统(uni_modules 版本)。
特性
- vue-router 风格 API - 熟悉的
push/replace/back导航方式,零学习成本 - 路由守卫 - 全局前置守卫
beforeEach、解析守卫beforeResolve、后置钩子afterEach、路由独享守卫beforeEnter - 守卫超时保护 - 守卫未调用
next()时自动中止导航,超时时间可配置(guardTimeout) - 命名路由 - 通过
name进行导航,无需硬编码路径字符串 - 路由元信息 -
meta字段支持页面标题、权限标记、TabBar 标识等自定义数据 - uni API 拦截 - 拦截
uni.navigateTo等原生导航 API,确保守卫始终生效(interceptUniApi) - 路由状态同步 -
syncRoute()将路由状态与实际页面栈同步,处理物理返回键等非路由器导航 - 路由变化监听 -
onRouteChange()订阅路由状态变化,包括导航完成和状态同步 - RouterLink 组件 - 声明式导航组件,支持
push/replace模式和@error事件 - TypeScript 类型提示 - 通过模块增强为路由名称和路径提供自动补全和类型检查
- 错误处理 - 完整的
RouterError/NavigationFailure体系,支持onError全局捕获 - 组合式 API -
useRouter()/useRoute()在组件中便捷访问路由器 - uni_modules 集成 - 通过 uni_modules 方式安装,无需 npm,开箱即用
📖 完整文档:https://mengxi-studio.github.io/uni-router/
安装
uni_modules(推荐)
将 mxuni-router 目录复制到项目的 uni_modules 目录下:
src/
└── uni_modules/
└── mxuni-router/
├── js_sdk/
│ ├── index.js
│ ├── index.cjs
│ ├── index.d.ts
│ └── index.d.cts
├── components/
│ └── mxuni-router/
│ └── mxuni-router.vue
├── package.json
└── readme.md
npm
pnpm add @meng-xi/uni-router
npm 方式需将导入路径改为
@meng-xi/uni-router。
快速开始
1. 创建路由器
// main.ts
import { createSSRApp } from 'vue'
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'
import App from './App.vue'
const router = createRouter({
routes: [
{ path: 'pages/index/index', name: 'home', meta: { title: '首页' } },
{ path: 'pages/about/about', name: 'about', meta: { title: '关于', requireAuth: true } },
{ path: 'pages/user/user', name: 'user', meta: { title: '我的', isTab: true } }
],
strict: true
})
export function createApp() {
const app = createSSRApp(App)
app.use(router)
return { app }
}
2. 路由导航
import { useRouter, useRoute } from './uni_modules/mxuni-router/js_sdk/index.js'
// 在组件 setup 中使用
const router = useRouter()
const route = useRoute()
// 路径导航
await router.push('/pages/about/about')
await router.push({ path: '/pages/about/about', query: { id: '1' } })
// 命名导航
await router.push({ name: 'about' })
// 返回
await router.back()
await router.back(2) // 返回两级
3. 路由守卫
// 全局前置守卫 - 登录验证
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !isLoggedIn()) {
next({ name: 'login', query: { redirect: to.fullPath } })
} else {
next()
}
})
// 全局后置钩子
router.afterEach((to, from) => {
console.log(`导航完成: ${from.path} → ${to.path}`)
})
4. 自动生成路由配置(推荐)
配合 @meng-xi/vite-plugin 的 generateRouter 插件,可从 pages.json 自动生成路由配置和类型声明:
pnpm add @meng-xi/vite-plugin -D
// vite.config.ts
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { generateRouter } from '@meng-xi/vite-plugin'
export default defineConfig({
plugins: [
uni(),
generateRouter({
pagesJsonPath: 'src/pages.json',
outputPath: 'src/router.config.ts',
dts: true,
metaMapping: {
navigationBarTitleText: 'title',
requireAuth: 'requireAuth'
}
})
]
})
然后在 main.ts 中导入生成的路由配置:
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'
import routes from './router.config'
const router = createRouter({ routes })
API 概览
核心
| API | 说明 |
|---|---|
createRouter(options) |
创建路由器实例 |
useRouter() |
获取路由器实例(组合式 API) |
useRoute() |
获取当前路由位置(组合式 API) |
Router 实例方法
| 方法 | 说明 |
|---|---|
router.push(location) |
导航到新页面 |
router.replace(location) |
替换当前页面 |
router.back(delta?) |
返回上一页或多级页面 |
router.beforeEach(guard) |
注册全局前置守卫 |
router.beforeResolve(guard) |
注册全局解析守卫 |
router.afterEach(guard) |
注册全局后置钩子 |
router.onError(handler) |
注册错误处理回调 |
router.resolve(location) |
解析路由位置(不导航) |
router.getRoutes() |
获取所有路由配置 |
router.hasRoute(name) |
检查路由是否存在 |
router.isReady() |
等待路由器初始化完成 |
router.onRouteChange(listener) |
注册路由变化监听器 |
router.syncRoute() |
同步路由状态与实际页面栈 |
错误码
| 错误码 | 说明 |
|---|---|
NAVIGATION_ABORTED |
导航被守卫中止 |
NAVIGATION_CANCELLED |
导航被取消(守卫异常或重定向超限) |
NAVIGATION_DUPLICATED |
重复导航到当前位置 |
ROUTE_NOT_FOUND |
未找到匹配的路由 |
NAVIGATION_API_ERROR |
uni 导航 API 调用失败 |
SETUP_ERROR |
路由器初始化或使用方式错误 |
RouterOptions 配置项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
routes |
RouteConfig[] |
- | 路由配置列表,需与 pages.json 中的页面声明保持一致 |
strict |
boolean |
true |
是否启用严格模式,启用后未匹配的命名路由将抛出异常 |
interceptUniApi |
boolean |
false |
是否拦截 uni.navigateTo 等原生导航 API,启用后直接调用 uni API 将转由路由器处理,确保守卫生效 |
guardTimeout |
number |
10000 |
守卫超时时间(毫秒),超时后自动中止导航并输出警告,设为 0 可禁用 |
RouterLink 组件
声明式导航组件,对应 uni-app 的 <navigator>,自动通过路由器执行导航。
<!-- 路径导航 -->
<mxuni-router to="/pages/about/about">
<view>跳转到关于页</view>
</mxuni-router>
<!-- replace 模式 -->
<mxuni-router to="/pages/about/about" replace>
<view>替换当前页</view>
</mxuni-router>
<!-- 捕获导航失败 -->
<mxuni-router to="/pages/about/about" @error="onNavError">
<view>跳转</view>
</mxuni-router>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
to |
RouteLocationRaw |
- | 目标路由位置 |
replace |
boolean |
false |
是否使用替换模式导航 |
hoverClass |
string |
'navigator-hover' |
按下时的样式类 |
hoverStopPropagation |
boolean |
false |
是否阻止祖先节点的点击态 |
hoverStartTime |
number |
50 |
按住后多久出现点击态(ms) |
hoverStayTime |
number |
600 |
手指松开后点击态保留时间(ms) |
| 事件 | 参数 | 说明 |
|---|---|---|
error |
NavigationFailure |
导航失败时触发 |
TypeScript 类型提示
启用 dts: true 后,generateRouter 插件自动生成类型声明文件,为路由导航提供类型安全:
// 路由名称自动补全
router.push({ name: 'pagesIndexIndex' }) // ✅ 自动补全
router.push({ name: 'invalidName' }) // ❌ 类型错误
// 路径自动补全
router.push({ path: '/pages/index/index' }) // ✅ 自动补全
router.push({ path: '/invalid/path' }) // ❌ 类型错误
与 pages.json 的关系
Uni Router 不替代 pages.json,而是与之配合使用:
| 职责 | pages.json | Uni Router |
|---|---|---|
| 页面注册 | 必须声明 | 不负责 |
| 路由导航 | uni.navigateTo 等 | push / replace / back |
| 路由守卫 | 不支持 | beforeEach 等 |
| 路由元信息 | 不支持 | meta 字段 |
| 命名路由 | 不支持 | name 字段 |
License
@meng-xi/uni-router
为 uni-app 提供类似 vue-router 风格的路由管理系统(uni_modules 版本)。
特性
- vue-router 风格 API - 熟悉的
push/replace/back导航方式,零学习成本 - 路由守卫 - 全局前置守卫
beforeEach、解析守卫beforeResolve、后置钩子afterEach、路由独享守卫beforeEnter - 守卫超时保护 - 守卫未调用
next()时自动中止导航,超时时间可配置(guardTimeout) - 命名路由 - 通过
name进行导航,无需硬编码路径字符串 - 路由元信息 -
meta字段支持页面标题、权限标记、TabBar 标识等自定义数据 - uni API 拦截 - 拦截
uni.navigateTo等原生导航 API,确保守卫始终生效(interceptUniApi) - 路由状态同步 -
syncRoute()将路由状态与实际页面栈同步,处理物理返回键等非路由器导航 - 路由变化监听 -
onRouteChange()订阅路由状态变化,包括导航完成和状态同步 - RouterLink 组件 - 声明式导航组件,支持
push/replace模式和@error事件 - TypeScript 类型提示 - 通过模块增强为路由名称和路径提供自动补全和类型检查
- 错误处理 - 完整的
RouterError/NavigationFailure体系,支持onError全局捕获 - 组合式 API -
useRouter()/useRoute()在组件中便捷访问路由器 - uni_modules 集成 - 通过 uni_modules 方式安装,无需 npm,开箱即用
📖 完整文档:https://mengxi-studio.github.io/uni-router/
安装
uni_modules(推荐)
将 mxuni-router 目录复制到项目的 uni_modules 目录下:
src/
└── uni_modules/
└── mxuni-router/
├── js_sdk/
│ ├── index.js
│ ├── index.cjs
│ ├── index.d.ts
│ └── index.d.cts
├── components/
│ └── mxuni-router/
│ └── mxuni-router.vue
├── package.json
└── readme.md
npm
pnpm add @meng-xi/uni-router
npm 方式需将导入路径改为
@meng-xi/uni-router。
快速开始
1. 创建路由器
// main.ts
import { createSSRApp } from 'vue'
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'
import App from './App.vue'
const router = createRouter({
routes: [
{ path: 'pages/index/index', name: 'home', meta: { title: '首页' } },
{ path: 'pages/about/about', name: 'about', meta: { title: '关于', requireAuth: true } },
{ path: 'pages/user/user', name: 'user', meta: { title: '我的', isTab: true } }
],
strict: true
})
export function createApp() {
const app = createSSRApp(App)
app.use(router)
return { app }
}
2. 路由导航
import { useRouter, useRoute } from './uni_modules/mxuni-router/js_sdk/index.js'
// 在组件 setup 中使用
const router = useRouter()
const route = useRoute()
// 路径导航
await router.push('/pages/about/about')
await router.push({ path: '/pages/about/about', query: { id: '1' } })
// 命名导航
await router.push({ name: 'about' })
// 返回
await router.back()
await router.back(2) // 返回两级
3. 路由守卫
// 全局前置守卫 - 登录验证
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !isLoggedIn()) {
next({ name: 'login', query: { redirect: to.fullPath } })
} else {
next()
}
})
// 全局后置钩子
router.afterEach((to, from) => {
console.log(`导航完成: ${from.path} → ${to.path}`)
})
4. 自动生成路由配置(推荐)
配合 @meng-xi/vite-plugin 的 generateRouter 插件,可从 pages.json 自动生成路由配置和类型声明:
pnpm add @meng-xi/vite-plugin -D
// vite.config.ts
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { generateRouter } from '@meng-xi/vite-plugin'
export default defineConfig({
plugins: [
uni(),
generateRouter({
pagesJsonPath: 'src/pages.json',
outputPath: 'src/router.config.ts',
dts: true,
metaMapping: {
navigationBarTitleText: 'title',
requireAuth: 'requireAuth'
}
})
]
})
然后在 main.ts 中导入生成的路由配置:
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'
import routes from './router.config'
const router = createRouter({ routes })
API 概览
核心
| API | 说明 |
|---|---|
createRouter(options) |
创建路由器实例 |
useRouter() |
获取路由器实例(组合式 API) |
useRoute() |
获取当前路由位置(组合式 API) |
Router 实例方法
| 方法 | 说明 |
|---|---|
router.push(location) |
导航到新页面 |
router.replace(location) |
替换当前页面 |
router.back(delta?) |
返回上一页或多级页面 |
router.beforeEach(guard) |
注册全局前置守卫 |
router.beforeResolve(guard) |
注册全局解析守卫 |
router.afterEach(guard) |
注册全局后置钩子 |
router.onError(handler) |
注册错误处理回调 |
router.resolve(location) |
解析路由位置(不导航) |
router.getRoutes() |
获取所有路由配置 |
router.hasRoute(name) |
检查路由是否存在 |
router.isReady() |
等待路由器初始化完成 |
router.onRouteChange(listener) |
注册路由变化监听器 |
router.syncRoute() |
同步路由状态与实际页面栈 |
错误码
| 错误码 | 说明 |
|---|---|
NAVIGATION_ABORTED |
导航被守卫中止 |
NAVIGATION_CANCELLED |
导航被取消(守卫异常或重定向超限) |
NAVIGATION_DUPLICATED |
重复导航到当前位置 |
ROUTE_NOT_FOUND |
未找到匹配的路由 |
NAVIGATION_API_ERROR |
uni 导航 API 调用失败 |
SETUP_ERROR |
路由器初始化或使用方式错误 |
RouterOptions 配置项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
routes |
RouteConfig[] |
- | 路由配置列表,需与 pages.json 中的页面声明保持一致 |
strict |
boolean |
true |
是否启用严格模式,启用后未匹配的命名路由将抛出异常 |
interceptUniApi |
boolean |
false |
是否拦截 uni.navigateTo 等原生导航 API,启用后直接调用 uni API 将转由路由器处理,确保守卫生效 |
guardTimeout |
number |
10000 |
守卫超时时间(毫秒),超时后自动中止导航并输出警告,设为 0 可禁用 |
RouterLink 组件
声明式导航组件,对应 uni-app 的 <navigator>,自动通过路由器执行导航。
<!-- 路径导航 -->
<mxuni-router to="/pages/about/about">
<view>跳转到关于页</view>
</mxuni-router>
<!-- replace 模式 -->
<mxuni-router to="/pages/about/about" replace>
<view>替换当前页</view>
</mxuni-router>
<!-- 捕获导航失败 -->
<mxuni-router to="/pages/about/about" @error="onNavError">
<view>跳转</view>
</mxuni-router>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
to |
RouteLocationRaw |
- | 目标路由位置 |
replace |
boolean |
false |
是否使用替换模式导航 |
hoverClass |
string |
'navigator-hover' |
按下时的样式类 |
hoverStopPropagation |
boolean |
false |
是否阻止祖先节点的点击态 |
hoverStartTime |
number |
50 |
按住后多久出现点击态(ms) |
hoverStayTime |
number |
600 |
手指松开后点击态保留时间(ms) |
| 事件 | 参数 | 说明 |
|---|---|---|
error |
NavigationFailure |
导航失败时触发 |
TypeScript 类型提示
启用 dts: true 后,generateRouter 插件自动生成类型声明文件,为路由导航提供类型安全:
// 路由名称自动补全
router.push({ name: 'pagesIndexIndex' }) // ✅ 自动补全
router.push({ name: 'invalidName' }) // ❌ 类型错误
// 路径自动补全
router.push({ path: '/pages/index/index' }) // ✅ 自动补全
router.push({ path: '/invalid/path' }) // ❌ 类型错误
与 pages.json 的关系
Uni Router 不替代 pages.json,而是与之配合使用:
| 职责 | pages.json | Uni Router |
|---|---|---|
| 页面注册 | 必须声明 | 不负责 |
| 路由导航 | uni.navigateTo 等 | push / replace / back |
| 路由守卫 | 不支持 | beforeEach 等 |
| 路由元信息 | 不支持 | meta 字段 |
| 命名路由 | 不支持 | name 字段 |
License
收起阅读 »PC端微信小程序,切换tabBar卡死问题
初始排查方向:
- 微信API兼容性问题
- 组件兼容性问题
- 数据更新机制问题
排查过程:
在初步测试中,我们发现该小程序仅能在体验版环境下通过电脑端进行查看,而开发版本则无法正常打开,或使用自动预览功能,点击 预览->自动预览,可以选择启动 PC 自动预览,点击编译并预览,成功的话将在微信 PC 版上自动拉起小程序。
进一步的诊断显示,即使移除了微信API相关的更新代码,应用仍然出现卡顿现象。为精确定位问题源头,我们采取了逐页注释与标签级注释的方法逐步排除,最终确认问题是由于某公共状态管理中的数据引起。
具体而言,所有使用TabBar导航模式的页面以及登录页面均频繁调用同一接口,并基于此更新上述提及的公共状态管理中的数据,导致了性能瓶颈。
解决方案:
● 在短时间内减少对特定接口的请求频率。
● 在更新公共状态管理的数据时引入一致性检查逻辑,即只有当新获取的数据与现有数据存在差异时才执行更新操作。
● 同时该公共状态管理数据appConfig嵌套过深,如需使用appConfig中某个单一数据,请在store文件中的getters声明再引用。
初始排查方向:
- 微信API兼容性问题
- 组件兼容性问题
- 数据更新机制问题
排查过程:
在初步测试中,我们发现该小程序仅能在体验版环境下通过电脑端进行查看,而开发版本则无法正常打开,或使用自动预览功能,点击 预览->自动预览,可以选择启动 PC 自动预览,点击编译并预览,成功的话将在微信 PC 版上自动拉起小程序。
进一步的诊断显示,即使移除了微信API相关的更新代码,应用仍然出现卡顿现象。为精确定位问题源头,我们采取了逐页注释与标签级注释的方法逐步排除,最终确认问题是由于某公共状态管理中的数据引起。
具体而言,所有使用TabBar导航模式的页面以及登录页面均频繁调用同一接口,并基于此更新上述提及的公共状态管理中的数据,导致了性能瓶颈。
解决方案:
● 在短时间内减少对特定接口的请求频率。
● 在更新公共状态管理的数据时引入一致性检查逻辑,即只有当新获取的数据与现有数据存在差异时才执行更新操作。
● 同时该公共状态管理数据appConfig嵌套过深,如需使用appConfig中某个单一数据,请在store文件中的getters声明再引用。
【解决】oppo上架应用市场提示套用马甲,相似度过高的问题
一、开发者困境:遭遇“代码相似度过高”上架应用商店驳回
最近,我遇到了一件非常棘手的事情。自己精心开发的一款Android APP在提交到某主流应用商店进行审核时,被无情驳回了。审核反馈的理由非常刺眼:“代码相似度过高”或“代码相似度极高”、“疑似马甲”。
二、解决办法:使用“问顶安全”的Android应用加固顺利过审
在寻求解决方案的过程中,我接触到了深圳问顶安全科技有限公司(asktopsec.com)的APP加固服务。抱着试一试的心态,我使用了他们的Android APP加固产品对APK进行了加固处理,随后重新申请上架。
结果令人惊喜,再次提交审核后,应用商店顺利通过了审核,之前的“代码相似度过高”提示彻底消失。APP成功上架!
后面我咨询客服才了解到,这家公司的团队成员数十年安全经验,其独有的2大核心技术成功帮我上架成功:
1.随机生成加固特征
根据每个APP/版本 随机生成独一无二的加固特征。有效防止加固检测、自动化脱壳工具、病毒误报,为阻断特征识别场景而生。
2.APP矩阵防护
多维度、矩阵式对APP进行增强型加密或混淆,增加逆向分析难度。可以达到千人千面的防护效果。
三、总结
面对应用市场上架的严苛审核,选择专业的安全服务至关重要。如果你也面临代码相似度高、被误判定为马甲包或需要高等级的安全防护,深圳问顶安全科技有限公司是一个值得信赖的选择。他们的官网:asktopsec.com ,进去后就能看到醒目的“Android应用加固与安全检测平台”了,点进去开始加固你的App吧~
一、开发者困境:遭遇“代码相似度过高”上架应用商店驳回
最近,我遇到了一件非常棘手的事情。自己精心开发的一款Android APP在提交到某主流应用商店进行审核时,被无情驳回了。审核反馈的理由非常刺眼:“代码相似度过高”或“代码相似度极高”、“疑似马甲”。
二、解决办法:使用“问顶安全”的Android应用加固顺利过审
在寻求解决方案的过程中,我接触到了深圳问顶安全科技有限公司(asktopsec.com)的APP加固服务。抱着试一试的心态,我使用了他们的Android APP加固产品对APK进行了加固处理,随后重新申请上架。
结果令人惊喜,再次提交审核后,应用商店顺利通过了审核,之前的“代码相似度过高”提示彻底消失。APP成功上架!
后面我咨询客服才了解到,这家公司的团队成员数十年安全经验,其独有的2大核心技术成功帮我上架成功:
1.随机生成加固特征
根据每个APP/版本 随机生成独一无二的加固特征。有效防止加固检测、自动化脱壳工具、病毒误报,为阻断特征识别场景而生。
2.APP矩阵防护
多维度、矩阵式对APP进行增强型加密或混淆,增加逆向分析难度。可以达到千人千面的防护效果。
三、总结
面对应用市场上架的严苛审核,选择专业的安全服务至关重要。如果你也面临代码相似度高、被误判定为马甲包或需要高等级的安全防护,深圳问顶安全科技有限公司是一个值得信赖的选择。他们的官网:asktopsec.com ,进去后就能看到醒目的“Android应用加固与安全检测平台”了,点进去开始加固你的App吧~
收起阅读 »【开源】windows上传ipa、管理证书和描述文件,数据无需上传云端
Github:https://github.com/friend-nicen/appuploader
App Store Connect GUI
一款基于 Wails 和 Vue 3 开发的跨平台(macOS / Windows / Linux)App Store Connect 桌面可视化工具。
它通过直接调用 App Store Connect API 的底层机制,实现了本地化、图形化的证书、设备、Bundle ID 和配置文件的管理,提供了现代化的 SaaS Dashboard 体验。
特性
- 现代 UI: 基于 Vue 3 + TailwindCSS 实现的流畅响应式桌面端界面。
- 本地存储: 使用无 CGO 依赖的纯 Go SQLite 驱动
github.com/glebarez/sqlite本地加密存储 API Keys 等配置信息。 - 多账户管理: 支持配置多个 Issuer ID、Key ID 和 Private Key,支持无缝切换。
- 核心功能:
- Bundle ID 管理: 查看现有的 App IDs
- 证书管理 (Certificates): 查看各类证书信息
- 描述文件管理 (Profiles): 浏览 Provisioning Profiles
- 设备管理 (Devices): 查看和管理测试设备
技术栈
- 后端 (Go): Go 1.21+, Wails v2, GORM,
golang-jwt/jwt/v5 - 前端 (Web): Vue 3 (Composition API), Vue Router, TailwindCSS 3
- 数据库: SQLite (
github.com/glebarez/sqlite)
API 密钥申请指南
使用本工具前,需要先在 Apple App Store Connect 中创建 API 密钥。
1. 前置条件
- 拥有有效的 Apple Developer 账号($99/年)
- 登录 App Store Connect
2. 创建 API 密钥
- 打开 App Store Connect → 右上角 "我的账户" → "API 密钥"
- 点击 "生成 API 密钥"
- 勾选 "开发人员" 权限(
Developer Role),这是管理证书、描述文件等所需的最低权限 - 立即下载
.p8私钥文件(页面关闭后将无法再次下载,只能重新生成)
3. 获取三个关键信息
| 信息 | 位置 | 说明 |
|---|---|---|
| Issuer ID (发行者 ID) | API 密钥页面顶部 发行者 ID 字段 |
同一账户下所有密钥共享,格式为 xxxx-xxxx-xxxx-xxxx-xxxx |
| Key ID (密钥 ID) | 密钥列表中的 密钥 ID 列 |
每个密钥唯一,格式为 XXXXXXXXXX |
| Private Key (私钥) | 下载的 .p8 文件内容 |
以 -----BEGIN PRIVATE KEY----- 开头,-----END PRIVATE KEY----- 结尾 |
4. 权限说明
API 密钥支持以下角色,本工具需要 至少 开发人员 角色:
| 角色 | 可用功能 |
|---|---|
| 开发人员 | 查看和管理证书、Bundle ID、设备、描述文件 |
| 管理员 | 上述全部 + 管理 App、用户、财务等 |
| 财务 | 仅查看财务报告 |
5. 安全注意事项
- 私钥文件(.p8)仅下载一次,请妥善保管
- 建议为不同环境创建不同的 API 密钥(如开发 / 生产)
- 可在 App Store Connect 中随时 撤销 泄露的密钥
- 本工具将私钥存储在本地 SQLite 数据库中,不会上传到任何远程服务器
- 导出数据时导出的 JSON 文件包含完整私钥,请勿将导出的 JSON 暴露给他人
开发指南
本项目代码包含详尽的中英文注释,方便进行二次开发和功能扩展。
1. 环境准备
- 安装 Go 1.26+
- 安装 Node.js 18+
- 安装 Wails CLI:
go install github.com/wailsapp/wails/v2/cmd/wails@latest
2. 本地开发 (Dev Mode)
开发模式下,Wails 会启动一个本地 Web 服务器,并提供热重载 (Hot Reload) 支持。
# 确保在项目根目录运行
wails dev
前端代码位于 frontend/ 目录中,所有的 Go 暴露方法在 frontend/wailsjs/go/main/App.js 中自动生成,可以直接在 Vue 组件中以 Promise 的方式调用。
3. 编译打包 (Build)
编译生产版本时,Wails 会将前端代码打包并嵌入到最终的二进制执行文件中。
# 编译当前平台的应用
wails build
# 交叉编译 macOS (如果你在 Windows/Linux 上)
wails build -platform darwin/amd64,darwin/arm64
# 交叉编译 Windows
wails build -platform windows/amd64
编译成功后,产物会输出到 build/bin/ 目录下。
项目结构
/backend: Go 后端逻辑,包含 API 客户端和 SQLite 数据库模型。/frontend: Vue 3 前端代码。src/views: 各大功能模块的 Vue 页面组件。src/router: 页面路由配置。
app.go: Wails 的主生命周期文件,包含了绑定到前端的 Go 方法。main.go: Wails 应用启动入口。
License
MIT License
Github:https://github.com/friend-nicen/appuploader
App Store Connect GUI
一款基于 Wails 和 Vue 3 开发的跨平台(macOS / Windows / Linux)App Store Connect 桌面可视化工具。
它通过直接调用 App Store Connect API 的底层机制,实现了本地化、图形化的证书、设备、Bundle ID 和配置文件的管理,提供了现代化的 SaaS Dashboard 体验。
特性
- 现代 UI: 基于 Vue 3 + TailwindCSS 实现的流畅响应式桌面端界面。
- 本地存储: 使用无 CGO 依赖的纯 Go SQLite 驱动
github.com/glebarez/sqlite本地加密存储 API Keys 等配置信息。 - 多账户管理: 支持配置多个 Issuer ID、Key ID 和 Private Key,支持无缝切换。
- 核心功能:
- Bundle ID 管理: 查看现有的 App IDs
- 证书管理 (Certificates): 查看各类证书信息
- 描述文件管理 (Profiles): 浏览 Provisioning Profiles
- 设备管理 (Devices): 查看和管理测试设备
技术栈
- 后端 (Go): Go 1.21+, Wails v2, GORM,
golang-jwt/jwt/v5 - 前端 (Web): Vue 3 (Composition API), Vue Router, TailwindCSS 3
- 数据库: SQLite (
github.com/glebarez/sqlite)
API 密钥申请指南
使用本工具前,需要先在 Apple App Store Connect 中创建 API 密钥。
1. 前置条件
- 拥有有效的 Apple Developer 账号($99/年)
- 登录 App Store Connect
2. 创建 API 密钥
- 打开 App Store Connect → 右上角 "我的账户" → "API 密钥"
- 点击 "生成 API 密钥"
- 勾选 "开发人员" 权限(
Developer Role),这是管理证书、描述文件等所需的最低权限 - 立即下载
.p8私钥文件(页面关闭后将无法再次下载,只能重新生成)
3. 获取三个关键信息
| 信息 | 位置 | 说明 |
|---|---|---|
| Issuer ID (发行者 ID) | API 密钥页面顶部 发行者 ID 字段 |
同一账户下所有密钥共享,格式为 xxxx-xxxx-xxxx-xxxx-xxxx |
| Key ID (密钥 ID) | 密钥列表中的 密钥 ID 列 |
每个密钥唯一,格式为 XXXXXXXXXX |
| Private Key (私钥) | 下载的 .p8 文件内容 |
以 -----BEGIN PRIVATE KEY----- 开头,-----END PRIVATE KEY----- 结尾 |
4. 权限说明
API 密钥支持以下角色,本工具需要 至少 开发人员 角色:
| 角色 | 可用功能 |
|---|---|
| 开发人员 | 查看和管理证书、Bundle ID、设备、描述文件 |
| 管理员 | 上述全部 + 管理 App、用户、财务等 |
| 财务 | 仅查看财务报告 |
5. 安全注意事项
- 私钥文件(.p8)仅下载一次,请妥善保管
- 建议为不同环境创建不同的 API 密钥(如开发 / 生产)
- 可在 App Store Connect 中随时 撤销 泄露的密钥
- 本工具将私钥存储在本地 SQLite 数据库中,不会上传到任何远程服务器
- 导出数据时导出的 JSON 文件包含完整私钥,请勿将导出的 JSON 暴露给他人
开发指南
本项目代码包含详尽的中英文注释,方便进行二次开发和功能扩展。
1. 环境准备
- 安装 Go 1.26+
- 安装 Node.js 18+
- 安装 Wails CLI:
go install github.com/wailsapp/wails/v2/cmd/wails@latest
2. 本地开发 (Dev Mode)
开发模式下,Wails 会启动一个本地 Web 服务器,并提供热重载 (Hot Reload) 支持。
# 确保在项目根目录运行
wails dev
前端代码位于 frontend/ 目录中,所有的 Go 暴露方法在 frontend/wailsjs/go/main/App.js 中自动生成,可以直接在 Vue 组件中以 Promise 的方式调用。
3. 编译打包 (Build)
编译生产版本时,Wails 会将前端代码打包并嵌入到最终的二进制执行文件中。
# 编译当前平台的应用
wails build
# 交叉编译 macOS (如果你在 Windows/Linux 上)
wails build -platform darwin/amd64,darwin/arm64
# 交叉编译 Windows
wails build -platform windows/amd64
编译成功后,产物会输出到 build/bin/ 目录下。
项目结构
/backend: Go 后端逻辑,包含 API 客户端和 SQLite 数据库模型。/frontend: Vue 3 前端代码。src/views: 各大功能模块的 Vue 页面组件。src/router: 页面路由配置。
app.go: Wails 的主生命周期文件,包含了绑定到前端的 Go 方法。main.go: Wails 应用启动入口。
License
MIT License
收起阅读 »我是怎么实现ios内购后恢复购买的
// 在客户端app,定义一个获取历史苹果收据的方法如下:
function getIapOrders() {
return new Promise(async (resolve, reject) => {
try {
// 1. 导入 iOS 原生类
const NSBundle = plus.ios.importClass("NSBundle");
const NSData = plus.ios.importClass("NSData");
// 2. 获取收据 URL
const url = NSBundle.mainBundle().appStoreReceiptURL();
if (!url) {
uni.hideLoading();
uni.showToast({
title: '未找到收据路径',
icon: 'none'
});
reject()
return;
}
// 3. 读取收据数据
const receiptData = NSData.dataWithContentsOfURL(url);
if (receiptData) {
// 4. 【核心修改】使用 plus.ios.invoke 显式调用 base64EncodedStringWithOptions: 方法
// 注意:方法名后面的冒号 ":" 必须保留,代表这是一个带参数的方法
const base64Receipt = plus.ios.invoke(receiptData, "base64EncodedStringWithOptions:", 0);
if (base64Receipt) {
//加密过的,它是苹果官方为你在这个 App 里发生的所有成功交易出具的“电子发票”和“资产清单”。
console.log("成功获取到本地收据 Base64 数据", base64Receipt);
// 5. 发送给您的服务器,解密取出数据
uni.vk.callFunction({
url: 'client/order/pub/verifyReceipt',
data: {
transaction_receipt: base64Receipt
},
success(res) {
// 返回苹果给的交易记录
resolve(res.rows)
},
complete(res) {
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '收据转换 Base64 失败',
icon: 'none'
});
reject();
}
} else {
uni.hideLoading();
uni.showToast({
title: '收据内容为空,请重试',
icon: 'none'
});
reject()
}
} catch (e) {
uni.hideLoading();
console.error("读取收据失败: ", e);
uni.showToast({
title: '读取凭证失败: ' + e.message,
icon: 'none'
});
reject()
}
})
}
'use strict';
const uniPay = require("uni-pay");
module.exports = {
/**
* 加密数据,它是苹果官方为你在这个 App 里发生的所有成功交易出具的“电子发票”和“资产清单”。
* 通过verifyReceipt数据校验后返回json交易数据
* @url client/order/pub/verifyReceipt 前端调用的url参数地址
* data 请求参数
* @param {String} params1 参数1
*/
main: async (event) => {
let { data = {}, userInfo, util, filterResponse, originalParam } = event;
let { customUtil, uniID, config, pubFun, vk, db, _, $ } = util;
let { uid } = data;
let res = { code: 0, msg: "" };
// 业务逻辑开始-----------------------------------------------------------
// 测式时的沙箱模式下
let uniPayInstance = uniPay.initAppleIapPayment({ provider: "appleiap", provider_pay_type: "app" ,sandbox:true});
let tradeRes = await uniPayInstance.verifyReceipt({
receiptData: data.transaction_receipt
});
// console.log("************ uniPayInstance **************",JSON.stringify(tradeRes))
res.rows=[];
if(tradeRes.tradeState == "SUCCESS"){
/**
* original_purchase_date、 original_purchase_date_ms、 original_transaction_id
* product_id、purchase_date、purchase_date_ms
* quantity
* transaction_id
*/
res.rows = tradeRes.receipt.in_app
}
debugger
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
// 在客户端app,定义一个获取历史苹果收据的方法如下:
function getIapOrders() {
return new Promise(async (resolve, reject) => {
try {
// 1. 导入 iOS 原生类
const NSBundle = plus.ios.importClass("NSBundle");
const NSData = plus.ios.importClass("NSData");
// 2. 获取收据 URL
const url = NSBundle.mainBundle().appStoreReceiptURL();
if (!url) {
uni.hideLoading();
uni.showToast({
title: '未找到收据路径',
icon: 'none'
});
reject()
return;
}
// 3. 读取收据数据
const receiptData = NSData.dataWithContentsOfURL(url);
if (receiptData) {
// 4. 【核心修改】使用 plus.ios.invoke 显式调用 base64EncodedStringWithOptions: 方法
// 注意:方法名后面的冒号 ":" 必须保留,代表这是一个带参数的方法
const base64Receipt = plus.ios.invoke(receiptData, "base64EncodedStringWithOptions:", 0);
if (base64Receipt) {
//加密过的,它是苹果官方为你在这个 App 里发生的所有成功交易出具的“电子发票”和“资产清单”。
console.log("成功获取到本地收据 Base64 数据", base64Receipt);
// 5. 发送给您的服务器,解密取出数据
uni.vk.callFunction({
url: 'client/order/pub/verifyReceipt',
data: {
transaction_receipt: base64Receipt
},
success(res) {
// 返回苹果给的交易记录
resolve(res.rows)
},
complete(res) {
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '收据转换 Base64 失败',
icon: 'none'
});
reject();
}
} else {
uni.hideLoading();
uni.showToast({
title: '收据内容为空,请重试',
icon: 'none'
});
reject()
}
} catch (e) {
uni.hideLoading();
console.error("读取收据失败: ", e);
uni.showToast({
title: '读取凭证失败: ' + e.message,
icon: 'none'
});
reject()
}
})
}
'use strict';
const uniPay = require("uni-pay");
module.exports = {
/**
* 加密数据,它是苹果官方为你在这个 App 里发生的所有成功交易出具的“电子发票”和“资产清单”。
* 通过verifyReceipt数据校验后返回json交易数据
* @url client/order/pub/verifyReceipt 前端调用的url参数地址
* data 请求参数
* @param {String} params1 参数1
*/
main: async (event) => {
let { data = {}, userInfo, util, filterResponse, originalParam } = event;
let { customUtil, uniID, config, pubFun, vk, db, _, $ } = util;
let { uid } = data;
let res = { code: 0, msg: "" };
// 业务逻辑开始-----------------------------------------------------------
// 测式时的沙箱模式下
let uniPayInstance = uniPay.initAppleIapPayment({ provider: "appleiap", provider_pay_type: "app" ,sandbox:true});
let tradeRes = await uniPayInstance.verifyReceipt({
receiptData: data.transaction_receipt
});
// console.log("************ uniPayInstance **************",JSON.stringify(tradeRes))
res.rows=[];
if(tradeRes.tradeState == "SUCCESS"){
/**
* original_purchase_date、 original_purchase_date_ms、 original_transaction_id
* product_id、purchase_date、purchase_date_ms
* quantity
* transaction_id
*/
res.rows = tradeRes.receipt.in_app
}
debugger
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
收起阅读 »
【需求反馈】关于鸿蒙平台支持页面透明背景的需求反馈
关于 uni-app 鸿蒙平台支持透明窗口页面的需求反馈
一、当前问题
我们在多个项目中,通过 pages\.json 配置透明背景实现透明弹窗页面,该方案在 APP-PLUS(安卓 /iOS)端可正常运行,但在鸿蒙平台不支持,导致原有功能失效。
现有可用配置(APP-PLUS IOS和安卓端)
{
"path": "pages/dialog/copy",
"style": {
"navigationStyle": "custom",
// #ifdef APP-PLUS
"backgroundColor": "transparent",
"backgroundColorTop": "transparent",
"backgroundColorBottom": "transparent",
// #endif
"app-plus": {
"animationType": "fade-in",
"background": "transparent",
"popGesture": "none"
}
}
}
鸿蒙平台存在的缺陷
-
不支持
pages.json中配置backgroundColor: transparent实现透明窗口; -
官方提供的鸿蒙专属 API
uni.setBackgroundColor(OBJECT)不支持设置透明值,无法替代原有方案; -
无官方等效方案,只能将所有页面弹窗改为自定义组件,改造工作量极大。
二、业务影响
-
多个存量项目大量页面使用透明页面做弹窗,无法直接兼容鸿蒙端;
-
若全部改用组件实现,需要大量修改老代码,耗时且容易产生兼容问题;
-
跨端一致性被破坏,APP 端正常运行、鸿蒙端无法使用。
三、需求建议
-
优先兼容原有配置
鸿蒙平台支持pages\.json中backgroundColor: transparent透明配置,与 APP 端保持一致,实现零成本兼容。 -
新增官方透明页面 API
参考 uni-app x 的uni.openDialogPage(options),在 uni-app 标准版鸿蒙平台提供官方透明弹窗页面方法。 -
完善现有 API
升级uni.setBackgroundColor,支持透明色值,补全基础能力。
四、核心诉求
透明窗口是弹窗类核心功能,恳请官方尽快适配鸿蒙平台透明页面能力,保持跨端统一,大幅降低老项目改造工作量。
关于 uni-app 鸿蒙平台支持透明窗口页面的需求反馈
一、当前问题
我们在多个项目中,通过 pages\.json 配置透明背景实现透明弹窗页面,该方案在 APP-PLUS(安卓 /iOS)端可正常运行,但在鸿蒙平台不支持,导致原有功能失效。
现有可用配置(APP-PLUS IOS和安卓端)
{
"path": "pages/dialog/copy",
"style": {
"navigationStyle": "custom",
// #ifdef APP-PLUS
"backgroundColor": "transparent",
"backgroundColorTop": "transparent",
"backgroundColorBottom": "transparent",
// #endif
"app-plus": {
"animationType": "fade-in",
"background": "transparent",
"popGesture": "none"
}
}
}
鸿蒙平台存在的缺陷
-
不支持
pages.json中配置backgroundColor: transparent实现透明窗口; -
官方提供的鸿蒙专属 API
uni.setBackgroundColor(OBJECT)不支持设置透明值,无法替代原有方案; -
无官方等效方案,只能将所有页面弹窗改为自定义组件,改造工作量极大。
二、业务影响
-
多个存量项目大量页面使用透明页面做弹窗,无法直接兼容鸿蒙端;
-
若全部改用组件实现,需要大量修改老代码,耗时且容易产生兼容问题;
-
跨端一致性被破坏,APP 端正常运行、鸿蒙端无法使用。
三、需求建议
-
优先兼容原有配置
鸿蒙平台支持pages\.json中backgroundColor: transparent透明配置,与 APP 端保持一致,实现零成本兼容。 -
新增官方透明页面 API
参考 uni-app x 的uni.openDialogPage(options),在 uni-app 标准版鸿蒙平台提供官方透明弹窗页面方法。 -
完善现有 API
升级uni.setBackgroundColor,支持透明色值,补全基础能力。
四、核心诉求
透明窗口是弹窗类核心功能,恳请官方尽快适配鸿蒙平台透明页面能力,保持跨端统一,大幅降低老项目改造工作量。
收起阅读 »终于解决了 uniapp X 的表情输入框的问题
准备写一个类似微信的输入框发现uniappx 使用 input 没法输入表情图片
使用富文本编辑框又出现了多一个 \n 换行,不适合聊天输入框
还好现在解决了,用AI写了个可以插入图片的输入框
准备写一个类似微信的输入框发现uniappx 使用 input 没法输入表情图片
使用富文本编辑框又出现了多一个 \n 换行,不适合聊天输入框
还好现在解决了,用AI写了个可以插入图片的输入框
猫拽uniapp跨平台低代码结合mimo2.5-pro模型
猫拽低代码平台是一个面向跨平台应用开发的低代码解决方案,它允许开发者通过可视化拖拽和配置的方式,快速构建和发布 Web、移动端(H5)及小程序应用。其核心价值在于大幅降低了应用开发的技术门槛和周期。
平台主要功能包括:可视化页面设计器、数据模型构建、逻辑流编排、API 集成以及一键多端发布。
引入 小米 MIMO 2.5 Pro 模型测试,为应用赋予了先进的智能交互与决策能力。
官网:猫拽低代码平台
小米 MIMO 2.5 Pro 模型是小米公司推出的新一代多模态智能模型(Multi-modal Intelligent Model),是其 MIMO 系列模型的重要升级版本。该模型深度融合了视觉、语言、语音等多种模态的理解与生成能力,旨在为智能设备、人机交互和内容创作等领域提供强大的 AI 驱动核心。
强大的多模态理解与生成
- 视觉理解:能够精准识别图像/视频中的物体、场景、文字、动作乃至情感元素。
- 语言处理:支持超长上下文对话、复杂指令跟随、多语言翻译与创作。
- 语音交互:具备高保真语音合成、实时语音识别与情感化语音交互能力。
- 跨模态关联:实现“图文互生”、“语音控图”、“以文生视频”等跨模态任务。
- 混合专家(MoE)架构:采用稀疏激活的专家网络,在保持庞大参数规模(据传达千亿级别)的同时,大幅降低推理计算成本。
- 动态自适应推理:根据任务复杂度动态分配计算资源,简单任务快速响应,复杂任务深度处理。
- 端云协同:支持模型部分能力下沉至设备端(如手机、IoT设备),实现低延迟、高隐私的本地智能,同时与云端大模型协同完成复杂任务。
将 小米 MIMO 2.5 Pro 模型 集成到 猫拽低代码平台,实质上是为可视化开发注入了“AI大脑”,创造了“1+1>2”的效应:
- 开发流程智能化:从“手动拖拽配置”迈向“描述即生成”。用户可以用自然语言提出需求,由 MIMO 2.5 Pro 理解并转化为平台可执行的构件,极大提升原型搭建速度。
- 交互体验个性化:生成的页面或应用内置了模型的智能交互能力。例如,一个电商详情页可以集成能“看懂”商品图片并回答问题的客服机器人,其能力直接来源于 MIMO 2.5 Pro。
- 多端体验一致化:模型的能力通过猫拽平台的多端发布引擎,可以无缝部署到 Web、小程序、App(通过 UniApp)等各个终端,确保智能体验的全渠道覆盖。
- 降低高级功能门槛:一些原本需要复杂编码实现的 AI 功能(如图像识别分类、智能推荐、内容审核),现在可以通过调用模型 API 并以低代码方式配置,让更多开发者能够触及 AI 能力。
猫拽低代码平台是一个面向跨平台应用开发的低代码解决方案,它允许开发者通过可视化拖拽和配置的方式,快速构建和发布 Web、移动端(H5)及小程序应用。其核心价值在于大幅降低了应用开发的技术门槛和周期。
平台主要功能包括:可视化页面设计器、数据模型构建、逻辑流编排、API 集成以及一键多端发布。
引入 小米 MIMO 2.5 Pro 模型测试,为应用赋予了先进的智能交互与决策能力。
官网:猫拽低代码平台
小米 MIMO 2.5 Pro 模型是小米公司推出的新一代多模态智能模型(Multi-modal Intelligent Model),是其 MIMO 系列模型的重要升级版本。该模型深度融合了视觉、语言、语音等多种模态的理解与生成能力,旨在为智能设备、人机交互和内容创作等领域提供强大的 AI 驱动核心。
强大的多模态理解与生成
- 视觉理解:能够精准识别图像/视频中的物体、场景、文字、动作乃至情感元素。
- 语言处理:支持超长上下文对话、复杂指令跟随、多语言翻译与创作。
- 语音交互:具备高保真语音合成、实时语音识别与情感化语音交互能力。
- 跨模态关联:实现“图文互生”、“语音控图”、“以文生视频”等跨模态任务。
- 混合专家(MoE)架构:采用稀疏激活的专家网络,在保持庞大参数规模(据传达千亿级别)的同时,大幅降低推理计算成本。
- 动态自适应推理:根据任务复杂度动态分配计算资源,简单任务快速响应,复杂任务深度处理。
- 端云协同:支持模型部分能力下沉至设备端(如手机、IoT设备),实现低延迟、高隐私的本地智能,同时与云端大模型协同完成复杂任务。
将 小米 MIMO 2.5 Pro 模型 集成到 猫拽低代码平台,实质上是为可视化开发注入了“AI大脑”,创造了“1+1>2”的效应:
- 开发流程智能化:从“手动拖拽配置”迈向“描述即生成”。用户可以用自然语言提出需求,由 MIMO 2.5 Pro 理解并转化为平台可执行的构件,极大提升原型搭建速度。
- 交互体验个性化:生成的页面或应用内置了模型的智能交互能力。例如,一个电商详情页可以集成能“看懂”商品图片并回答问题的客服机器人,其能力直接来源于 MIMO 2.5 Pro。
- 多端体验一致化:模型的能力通过猫拽平台的多端发布引擎,可以无缝部署到 Web、小程序、App(通过 UniApp)等各个终端,确保智能体验的全渠道覆盖。
- 降低高级功能门槛:一些原本需要复杂编码实现的 AI 功能(如图像识别分类、智能推荐、内容审核),现在可以通过调用模型 API 并以低代码方式配置,让更多开发者能够触及 AI 能力。
仁跃平台直充业务对接与使用指南
1. 功能概述
本系统已集成仁跃电子商务(Renyue)的虚拟商品直充接口。通过此功能,您可以实现:
- 自动同步商品:一键获取仁跃平台的会员/充值类商品(如爱奇艺、腾讯视频会员等)。
- 自动发货:用户下单并支付后,系统自动调用仁跃接口进行充值,无需人工干预。
- 余额管理:实时同步仁跃账户余额,防止因余额不足导致充值失败。
- 状态回调:接收仁跃平台的充值结果回调,自动更新订单状态。
适用场景:积分兑换商城、会员权益赠送、虚拟商品零售等。
2. 前置准备
在启用本功能前,请确保您已完成以下步骤:
- 注册仁跃账号:访问 仁跃官网 注册商户账号。
- 获取密钥信息:在仁跃后台获取以下关键信息:
MerchantNo(商户号)MerchantKey(商户密钥/签名Key)My Private Key(您的RSA私钥,用于签名生成)RenYue Public Key(仁跃公钥,用于验签,通常由仁跃提供或固定)
- 充值余额确保仁跃账户内有足够的预付款余额以支持自动充值。
3. 系统配置
请在后台 系统配置 -> 商城(仁跃) 中填写以下参数:
| 配置项键名 (Key) | 说明 | 示例/备注 |
|---|---|---|
renyuejt_url |
仁跃API接口地址 | 通常为 https://api.renyuejt.com (请以官方文档为准) |
renyuejt_merchant_no |
商户号 | 从仁跃后台获取 |
renyuejt_merchant_sign |
商户密钥 (MerchantKey) | 用于生成签名 |
renyuejt_my_private_key |
本地RSA私钥 | 注意:需包含 -----BEGIN PRIVATE KEY----- 头尾,格式需正确 |
renyuejt_freight_id |
默认运费模板ID | 虚拟商品通常设置为“免运费”模板ID |
renyuejt_category_id |
默认商品分类ID | 同步的商品将归入此分类 |
4. 核心功能操作
4.1 同步商品列表
系统提供了自动同步仁跃商品的功能。
- 操作方式:
- 手动同步:在后台商品管理页面点击“同步仁跃商品”按钮(如有)。
- 定时任务:建议设置 Cron 定时任务,定期执行
\addons\smplive\library\renyuejtService::getCardDiscountList()。
- 同步逻辑:
- 调用仁跃
getCardDiscountList接口获取最新产品列表。 - 系统将当前商户下所有已有商品状态设为
hidden(隐藏)。 - 遍历返回的产品列表:
- 若商品已存在(通过
third_id匹配),则更新价格、库存等信息。 - 若商品不存在,则创建新商品。
- 若商品已存在(通过
- 未在本次同步列表中出现的原有商品将保持隐藏状态(相当于下架)。
- 调用仁跃
- 图片匹配:
- 系统会根据商品名称,在
ShopThirdRenyuejtImg表中查找匹配的图片。 - 建议:在后台预先维护好常见会员(如“爱奇艺”、“腾讯”)的关键字与图片对应关系,以便同步时自动关联精美封面图。
- 系统会根据商品名称,在
4.2 查询账户余额
- 方法:
\addons\smplive\library\renyuejtService::getCustomerDetail() - 用途:获取当前仁跃账户的剩余余额。
- 展示:余额数据会更新至系统配置
renyuejt_money,可在后台仪表盘查看。 - 建议:设置定时任务每小时同步一次余额,以便及时充值。
4.3 自动充值流程
当用户在平台购买虚拟商品并支付成功后,系统将自动触发充值:
- 订单校验:
- 检查订单是否属于仁跃商户 (
shops_id = 11)。 - 检查订单是否只包含1个商品,且数量为1。
- 检查商品是否有有效的
third_id(仁跃产品编码)。
- 检查订单是否属于仁跃商户 (
- 提交充值:
- 调用仁跃
getOrderRechargeInfo接口。 - 传入参数:用户手机号 (
mobile) 作为充值账号、产品编码、商户订单号等。
- 调用仁跃
- 状态更新:
- 若接口返回成功 (
CodeRes == 1000),系统将订单状态更新为 已发货 (shippingstate = 1),并记录仁跃返回的交易单号 (StoreNo)。 - 若失败,记录错误日志到
ShopOrderAction,方便排查。
- 若接口返回成功 (
4.4 异步回调处理
仁跃平台会在充值完成后(成功或失败)向系统发送回调通知。
- 回调地址:
/api/smplive/pay_notify/renyuejt - 处理逻辑:
- 验签:使用
verify()方法验证回调数据的签名,确保请求来自仁跃。 - 状态判断:
RechargeStatus == 2:充值成功。系统将订单状态更新为 已完成 (shippingstate = 2)。- 其他状态:记录错误日志,便于后续人工介入或重试。
- 响应:向仁跃返回
success字符串,确认收到通知。
- 验签:使用
5. 常见问题排查 (FAQ)
Q1: 同步商品时提示“解析失败”或无反应?
- 检查网络:确保服务器能访问仁跃 API 地址。
- 检查配置:确认
renyuejt_url、merchant_no、merchant_sign填写正确。 - 查看日志:检查
runtime/log/下的日志文件,搜索renyuejtService-curlRequest,查看具体的请求参数和返回结果。
Q2: 用户下单后未自动充值?
- 检查商品编码:确认该商品在数据库中的
third_id字段不为空,且与仁跃平台的产品编码一致。 - 检查订单结构:仁跃直充接口目前仅支持单商品、单数量的订单。如果用户购物车中有多个商品或同一商品买了2个,系统将拒绝调用接口并在订单操作中记录原因。
- 检查余额:确认仁跃账户余额充足。
- 查看日志:搜索
renyuejt关键词,查看orderSubmit方法的执行日志。
Q3: 回调验签失败?
- 检查公钥/私钥:确认配置中的
renyuejt_my_private_key格式正确(通常验签需要用到仁跃的公钥,请确认代码中verify方法使用的密钥是否与仁跃文档要求一致。注:当前代码使用的是本地私钥解密ReturnSign,请核对仁跃最新文档是否变更了验签方式)。 - 检查时间戳:确保服务器时间与标准时间同步,避免
TimeStamp偏差过大导致签名失效。
Q4: 如何自定义商品图片?
- 系统通过
findImg方法模糊匹配商品名称和图片标题。 - 请在后台管理
ShopThirdRenyuejtImg表(或通过专门的管理页面),添加关键字(如“爱奇艺”)对应的图片URL。 - 例如:添加一条记录,
title为 "爱奇艺",image为 "https://example.com/iqiyi.jpg"。当同步到“爱奇艺黄金会员月卡”时,系统会自动使用该图片。
6. 开发参考 (For Developers)
如果您需要二次开发或调试,可以参考以下核心方法:
use addons\smplive\library\renyuejtService;
// 1. 同步商品
$result = renyuejtService::getCardDiscountList();
// 2. 查询余额
$balanceInfo = renyuejtService::getCustomerDetail();
// 3. 手动触发订单充值 (通常由支付成功回调自动触发)
$order = ShopOrder::get($orderId);
$success = renyuejtService::orderSubmit($order);
签名算法说明:
- 参数按 ASCII 码字典序升序排序。
- 拼接成
key=value&key=value...格式。 - 末尾拼接
&MerchantKey=YOUR_KEY。 - 使用 SHA256WithRSA 算法,利用本地私钥对字符串进行签名。
- Base64 编码得到
SignSecret。
温馨提示:虚拟商品交易涉及资金安全,请务必妥善保管商户密钥和私钥,不要泄露给他人。建议在生产环境开启详细的日志记录,以便追踪每一笔充值请求。
更多技术介绍在https://smplive.wpygo.com/
DCloud 插件市场**
便于直接在 HBuilderX 中导入使用:
点击查看插件详情https://ext.dcloud.net.cn/plugin?id=26606
Gitee 代码仓库**
获取完整源代码及提交历史:
访问仓库地址 https://gitee.com/mldxmy/simplelive
1. 功能概述
本系统已集成仁跃电子商务(Renyue)的虚拟商品直充接口。通过此功能,您可以实现:
- 自动同步商品:一键获取仁跃平台的会员/充值类商品(如爱奇艺、腾讯视频会员等)。
- 自动发货:用户下单并支付后,系统自动调用仁跃接口进行充值,无需人工干预。
- 余额管理:实时同步仁跃账户余额,防止因余额不足导致充值失败。
- 状态回调:接收仁跃平台的充值结果回调,自动更新订单状态。
适用场景:积分兑换商城、会员权益赠送、虚拟商品零售等。
2. 前置准备
在启用本功能前,请确保您已完成以下步骤:
- 注册仁跃账号:访问 仁跃官网 注册商户账号。
- 获取密钥信息:在仁跃后台获取以下关键信息:
MerchantNo(商户号)MerchantKey(商户密钥/签名Key)My Private Key(您的RSA私钥,用于签名生成)RenYue Public Key(仁跃公钥,用于验签,通常由仁跃提供或固定)
- 充值余额确保仁跃账户内有足够的预付款余额以支持自动充值。
3. 系统配置
请在后台 系统配置 -> 商城(仁跃) 中填写以下参数:
| 配置项键名 (Key) | 说明 | 示例/备注 |
|---|---|---|
renyuejt_url |
仁跃API接口地址 | 通常为 https://api.renyuejt.com (请以官方文档为准) |
renyuejt_merchant_no |
商户号 | 从仁跃后台获取 |
renyuejt_merchant_sign |
商户密钥 (MerchantKey) | 用于生成签名 |
renyuejt_my_private_key |
本地RSA私钥 | 注意:需包含 -----BEGIN PRIVATE KEY----- 头尾,格式需正确 |
renyuejt_freight_id |
默认运费模板ID | 虚拟商品通常设置为“免运费”模板ID |
renyuejt_category_id |
默认商品分类ID | 同步的商品将归入此分类 |
4. 核心功能操作
4.1 同步商品列表
系统提供了自动同步仁跃商品的功能。
- 操作方式:
- 手动同步:在后台商品管理页面点击“同步仁跃商品”按钮(如有)。
- 定时任务:建议设置 Cron 定时任务,定期执行
\addons\smplive\library\renyuejtService::getCardDiscountList()。
- 同步逻辑:
- 调用仁跃
getCardDiscountList接口获取最新产品列表。 - 系统将当前商户下所有已有商品状态设为
hidden(隐藏)。 - 遍历返回的产品列表:
- 若商品已存在(通过
third_id匹配),则更新价格、库存等信息。 - 若商品不存在,则创建新商品。
- 若商品已存在(通过
- 未在本次同步列表中出现的原有商品将保持隐藏状态(相当于下架)。
- 调用仁跃
- 图片匹配:
- 系统会根据商品名称,在
ShopThirdRenyuejtImg表中查找匹配的图片。 - 建议:在后台预先维护好常见会员(如“爱奇艺”、“腾讯”)的关键字与图片对应关系,以便同步时自动关联精美封面图。
- 系统会根据商品名称,在
4.2 查询账户余额
- 方法:
\addons\smplive\library\renyuejtService::getCustomerDetail() - 用途:获取当前仁跃账户的剩余余额。
- 展示:余额数据会更新至系统配置
renyuejt_money,可在后台仪表盘查看。 - 建议:设置定时任务每小时同步一次余额,以便及时充值。
4.3 自动充值流程
当用户在平台购买虚拟商品并支付成功后,系统将自动触发充值:
- 订单校验:
- 检查订单是否属于仁跃商户 (
shops_id = 11)。 - 检查订单是否只包含1个商品,且数量为1。
- 检查商品是否有有效的
third_id(仁跃产品编码)。
- 检查订单是否属于仁跃商户 (
- 提交充值:
- 调用仁跃
getOrderRechargeInfo接口。 - 传入参数:用户手机号 (
mobile) 作为充值账号、产品编码、商户订单号等。
- 调用仁跃
- 状态更新:
- 若接口返回成功 (
CodeRes == 1000),系统将订单状态更新为 已发货 (shippingstate = 1),并记录仁跃返回的交易单号 (StoreNo)。 - 若失败,记录错误日志到
ShopOrderAction,方便排查。
- 若接口返回成功 (
4.4 异步回调处理
仁跃平台会在充值完成后(成功或失败)向系统发送回调通知。
- 回调地址:
/api/smplive/pay_notify/renyuejt - 处理逻辑:
- 验签:使用
verify()方法验证回调数据的签名,确保请求来自仁跃。 - 状态判断:
RechargeStatus == 2:充值成功。系统将订单状态更新为 已完成 (shippingstate = 2)。- 其他状态:记录错误日志,便于后续人工介入或重试。
- 响应:向仁跃返回
success字符串,确认收到通知。
- 验签:使用
5. 常见问题排查 (FAQ)
Q1: 同步商品时提示“解析失败”或无反应?
- 检查网络:确保服务器能访问仁跃 API 地址。
- 检查配置:确认
renyuejt_url、merchant_no、merchant_sign填写正确。 - 查看日志:检查
runtime/log/下的日志文件,搜索renyuejtService-curlRequest,查看具体的请求参数和返回结果。
Q2: 用户下单后未自动充值?
- 检查商品编码:确认该商品在数据库中的
third_id字段不为空,且与仁跃平台的产品编码一致。 - 检查订单结构:仁跃直充接口目前仅支持单商品、单数量的订单。如果用户购物车中有多个商品或同一商品买了2个,系统将拒绝调用接口并在订单操作中记录原因。
- 检查余额:确认仁跃账户余额充足。
- 查看日志:搜索
renyuejt关键词,查看orderSubmit方法的执行日志。
Q3: 回调验签失败?
- 检查公钥/私钥:确认配置中的
renyuejt_my_private_key格式正确(通常验签需要用到仁跃的公钥,请确认代码中verify方法使用的密钥是否与仁跃文档要求一致。注:当前代码使用的是本地私钥解密ReturnSign,请核对仁跃最新文档是否变更了验签方式)。 - 检查时间戳:确保服务器时间与标准时间同步,避免
TimeStamp偏差过大导致签名失效。
Q4: 如何自定义商品图片?
- 系统通过
findImg方法模糊匹配商品名称和图片标题。 - 请在后台管理
ShopThirdRenyuejtImg表(或通过专门的管理页面),添加关键字(如“爱奇艺”)对应的图片URL。 - 例如:添加一条记录,
title为 "爱奇艺",image为 "https://example.com/iqiyi.jpg"。当同步到“爱奇艺黄金会员月卡”时,系统会自动使用该图片。
6. 开发参考 (For Developers)
如果您需要二次开发或调试,可以参考以下核心方法:
use addons\smplive\library\renyuejtService;
// 1. 同步商品
$result = renyuejtService::getCardDiscountList();
// 2. 查询余额
$balanceInfo = renyuejtService::getCustomerDetail();
// 3. 手动触发订单充值 (通常由支付成功回调自动触发)
$order = ShopOrder::get($orderId);
$success = renyuejtService::orderSubmit($order);
签名算法说明:
- 参数按 ASCII 码字典序升序排序。
- 拼接成
key=value&key=value...格式。 - 末尾拼接
&MerchantKey=YOUR_KEY。 - 使用 SHA256WithRSA 算法,利用本地私钥对字符串进行签名。
- Base64 编码得到
SignSecret。
温馨提示:虚拟商品交易涉及资金安全,请务必妥善保管商户密钥和私钥,不要泄露给他人。建议在生产环境开启详细的日志记录,以便追踪每一笔充值请求。
更多技术介绍在https://smplive.wpygo.com/
DCloud 插件市场**
便于直接在 HBuilderX 中导入使用:
点击查看插件详情https://ext.dcloud.net.cn/plugin?id=26606
Gitee 代码仓库**
获取完整源代码及提交历史:
访问仓库地址 https://gitee.com/mldxmy/simplelive









