修复已知问题
新增app版本管理
优化uni-app android 兼容问题
This commit is contained in:
wangchen147 2025-10-16 19:34:42 +08:00
parent 3fbb2cd525
commit 7843b6ecb3
1660 changed files with 22472 additions and 29280 deletions

14
.gitignore vendored
View File

@ -34,7 +34,19 @@ build/
### VS Code ###
.vscode/
php
admin/auto-imports.d.ts
admin/components.d.ts
logs/**
webroot/runtime/upgrade
webroot/runtime/upgrade
webroot/runtime/cloud_build
webroot/runtime/weapp_build
webroot/runtime/app_build
webroot/public
webroot/resource/upload
webroot/public/admin
webroot/public/wap
webroot/public/web
webroot/runtime/uni-app
webroot/runtime/admin
webroot/runtime/wap

View File

@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents {
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
DiyPage: typeof import('./src/components/diy-page/index.vue')['default']
Editor: typeof import('./src/components/editor/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']

68
admin/src/app/api/app.ts Normal file
View File

@ -0,0 +1,68 @@
import request from '@/utils/request'
/**
* app配置
* @returns
*/
export function getAppConfig() {
return request.get('channel/app/config')
}
/**
* app配置
* @param params
* @returns
*/
export function setAppConfig(params: Record<string, any>) {
return request.put('channel/app/config', params, { showSuccessMessage: true })
}
export function getVersionList(params: Record<string, any>) {
return request.get('channel/app/version', { params })
}
export function getVersionInfo(id: number) {
return request.get(`channel/app/version/${id}`)
}
export function getAppPlatform() {
return request.get(`channel/app/platfrom`)
}
/**
*
* @param params
* @returns
*/
export function addVersion(params: Record<string, any>) {
return request.post('channel/app/version', params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function editVersion(params: Record<string, any>) {
return request.put(`channel/app/version/${ params.id }`, params, { showSuccessMessage: true })
}
/**
*
* @param siteId
*/
export function deleteVersion(params: Record<string, any>) {
return request.delete(`channel/app/version/${ params.id }`)
}
export function getBuildLog(key: string) {
return request.get(`channel/app/build/log/${ key }`)
}
export function releaseVersion(id: number) {
return request.put(`channel/app/version/${ id }/release`, {}, { showSuccessMessage: true })
}
export function generateSignCert(params: Record<string, any>) {
return request.post(`channel/app/generate_sign_cert`, params, { showSuccessMessage: true });
}

View File

@ -199,6 +199,12 @@ export function copyDiy(params: Record<string, any>) {
return request.post(`diy/copy`, params, { showSuccessMessage: true })
}
/**
*
*/
export function getPageLink(params: Record<string, any>) {
return request.get(`diy/page_link`, { params })
}
/***************************************************** 主题风格 ****************************************************/

View File

@ -151,7 +151,7 @@
</div>
<div class="text-[14px] text-[#374151] mb-[10px]">{{ t('upgrade.isNeedBackupTips') }}</div>
<div class="text-[14px] text-[#9699B6]">{{ t('上次备份时间:') }}{{ upgradeContent.last_backup.complete_time }}</div>
<div class="text-[14px] text-[#9699B6]">{{ t('上次备份时间:') }}{{ timeStampTurnTime(upgradeContent.last_backup.complete_time) }}</div>
</div>
</div>
</el-scrollbar>
@ -247,6 +247,7 @@ import { AnyObject } from '@/types/global'
import { ElNotification, ElMessage, ElMessageBox } from 'element-plus'
import Storage from '@/utils/storage'
import { useRouter } from 'vue-router'
import { timeStampTurnTime } from '@/utils/common'
const router = useRouter()
const terminalId = ref(Date.now());

View File

@ -0,0 +1,13 @@
{
"accessFlow": "接入流程",
"versionManage": "版本管理",
"title": "APP端管理",
"appInlet": "App接入流程",
"uniappApp": "uni-app应用开通",
"appAttestation1": "点击进入Dcloud官网开发者后台,创建或选择应用并维护好应用平台信息",
"clickAccess": "点击接入",
"appSetting": "App端配置",
"settingInfo": "点击配置",
"releaseVersion": "发布版本",
"toCreate": "去创建"
}

View File

@ -0,0 +1,17 @@
{
"wechatAppInfo": "微信应用信息",
"wechatAppid": "微信移动应用AppID",
"wechatAppidTips": "用于app端 微信登录 微信支付 微信分享",
"wechatAppsecret": "微信移动应用AppSecret",
"appidPlaceholder": "请输入AppID",
"appSecretPlaceholder": "请输入AppSecret",
"appInfo": "应用信息",
"uniAppId": "uniapp应用id",
"uniAppIdTips": "uniapp应用id需在Dcloud开发者中心创建",
"toCreate": "前往Dcloud官网",
"appName": "应用名称",
"applicationId": "安卓应用包名",
"applicationIdTips": "安卓应用的包名是Android系统中用于唯一标识应用的字符串采用反向域名格式如com.example.myapp。每个应用在系统中拥有唯一的包名用于区分不同应用",
"androidAppKey": "安卓离线打包Key",
"androidAppKeyTips": "安卓离线打包Key在Dcloud开发者中心 - 应用管理 - 点击应用 - 各平台信息 创建以及查看离线AppKey"
}

View File

@ -0,0 +1,55 @@
{
"accessFlow": "接入流程",
"versionManage": "版本管理",
"versionCode":"版本号",
"versionCodePlaceholder":"请输入版本号",
"versionCodeTips": "应用版本号必须是整数取值范围1~2147483647;必须高于上一版本设置的值",
"versionName":"版本名称",
"versionNamePlaceholder":"请输入版本名称",
"versionDesc":"",
"versionDescPlaceholder":"请输入",
"icon":"应用图标",
"iconPlaceholder":"请输入应用图标",
"push":"消息推送图标",
"pushPlaceholder":"请输入消息推送图标",
"splash":"应用启动页图标",
"splashPlaceholder":"请输入应用启动页的图标",
"platform":"客户端",
"packagePath":"安装包路径",
"packagePathPlaceholder":"请输入安装包路径",
"status":"状态",
"statusPlaceholder":"请输入状态",
"addAppVersion":"添加版本",
"updateAppVersion":"编辑版本",
"startDate":"请选择开始时间",
"endDate":"请选择结束时间",
"isForcedUpgrade": "强制升级",
"versionDesc": "更新内容",
"isForcedUpgradeTitle": "是否强制升级",
"releaseTime": "发布时间",
"release": "发布",
"resourceFile": "上传资源文件",
"androidResourceFileTips": "只能上传apk文件",
"iosResourceFileTips": "只能上传wgt文件",
"index": "序号",
"next": "下一步",
"prev": "上一步",
"certType": "证书类型",
"certFile": "证书文件",
"certAlias": "证书别名",
"certKeyPassword": "证书密码",
"certStorePassword": "证书库密码",
"publicCertTips": "niucloud提供的公共测试证书证书的描述信息都是测试数据任何人都可以使用仅适合应用开发期间体验测试使用",
"privateCertTips": "Android平台打包发布apk应用需要使用数字证书.keystore文件进行签名用于表明开发者身份。",
"download": "下载",
"failReason": "失败原因",
"appVersionReleaseTips": "发布后无法再对该版本进行修改,确定要发布该版本吗?",
"appVersionDeleteTips": "确定要删除该版本吗?",
"upgradeType": "升级方式",
"seeBuildLog": "查看打包日志",
"buildLog": "打包日志",
"authTips": "上传代码需先绑定授权码如果已有授权请先进行绑定没有授权可到niucloud官网购买云服务之后再进行操作",
"toBind": "绑定授权",
"toNiucloud": "去niucloud官网",
"siteAuthTips": "上传代码需先绑定授权码,请联系平台管理员进行绑定"
}

View File

@ -9,7 +9,7 @@
<el-button type="primary" class="w-[100px]" @click="addEvent">
{{ t('addMenu') }}
</el-button>
<el-button class="w-[100px]" @click="refreshMenu">
<el-button class="w-[100px]" :loading="refreshLoading" @click="refreshMenu">
{{ t('initializeMenu') }}
</el-button>
</div>
@ -82,6 +82,7 @@ const getMenuList = () => {
}
getMenuList()
//
const refreshLoading = ref(false)
const refreshMenu = () => {
ElMessageBox.confirm(h('div', null, [
h('p', null, t('initializeMenuTipsOne')),
@ -93,9 +94,12 @@ const refreshMenu = () => {
// type: 'warning'
}
).then(() => {
refreshLoading.value = true
menuRefresh({}).then(res => {
location.reload()
refreshLoading.value = false
}).catch(() => {
refreshLoading.value = false
})
}).catch(() => {
})

View File

@ -0,0 +1,130 @@
<template>
<!--微信公众号-->
<div class="main-container">
<el-card class="card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-tabs v-model="activeName" class="my-[20px]" @tab-change="handleClick">
<el-tab-pane :label="t('accessFlow')" name="/channel/app" />
<el-tab-pane :label="t('versionManage')" name="/channel/app/version" />
</el-tabs>
<div class="p-[20px]">
<h3 class="panel-title !text-sm">{{ t("appInlet") }}</h3>
<el-row>
<el-col :span="20">
<el-steps class="!mt-[10px]" :active="3" direction="vertical">
<el-step>
<template #title>
<p class="text-[14px] font-[700]">
{{ t("uniappApp") }}
</p>
</template>
<template #description>
<span class="text-[#999]">{{ t("appAttestation1") }}</span>
<div class="mt-[20px] mb-[40px] h-[32px]">
<el-button type="primary" @click="linkEvent('https://dcloud.io/')">{{ t("toCreate") }}</el-button>
</div>
</template>
</el-step>
<el-step>
<template #title>
<p class="text-[14px] font-[700]">
{{ t("appSetting") }}
</p>
</template>
<template #description>
<!-- <span class="text-[#999]">{{ t("wechatSetting1") }}</span>-->
<div class="mt-[20px] mb-[40px] h-[32px]">
<el-button type="primary" @click="router.push('/channel/app/config')">{{ t("settingInfo") }}</el-button>
</div>
</template>
</el-step>
<el-step>
<template #title>
<p class="text-[14px] font-[700]">
{{ t("versionManage") }}
</p>
</template>
<template #description>
<!-- <span class="text-[#999]">{{ t("wechatAccess") }}</span>-->
<div class="mt-[20px] mb-[40px] h-[32px]">
<el-button type="primary" plain @click="router.push('/channel/app/version')">{{ t("releaseVersion") }}</el-button>
</div>
</template>
</el-step>
</el-steps>
</el-col>
</el-row>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { getWechatConfig } from '@/app/api/wechat'
import { getAuthorizationUrl } from '@/app/api/wxoplatform'
import { getWxoplatform } from '@/app/api/sys'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const activeName = ref('/channel/app')
const qrcode = ref('')
const wechatConfig = ref({})
const oplatformConfig = ref({})
const onShowGetWechatConfig = async () => {
await getWechatConfig().then(({ data }) => {
wechatConfig.value = data
qrcode.value = data.qr_code
})
}
onMounted(async () => {
await onShowGetWechatConfig()
await getWxoplatform().then(({ data }) => {
oplatformConfig.value = data
})
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
onShowGetWechatConfig()
}
})
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', () => {
})
})
const linkEvent = (url: string) => {
window.open(url, '_blank')
}
const handleClick = (val: any) => {
router.push({ path: activeName.value })
}
const authBindWechat = () => {
getAuthorizationUrl().then(({ data }) => {
window.open(data)
})
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,286 @@
<template>
<el-dialog v-model="showDialog" :title="formData.id ? t('updateAppVersion') : t('addAppVersion')" width="60%" class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<div v-show="step == 1" class="h-[400px]">
<el-form-item :label="t('versionName')" prop="version_name">
<el-input v-model="formData.version_name" clearable :placeholder="t('versionNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('versionCode')" prop="version_code">
<el-input v-model="formData.version_code" clearable :placeholder="t('versionCodePlaceholder')" class="input-width" />
<div class="form-tip">{{ t('versionCodeTips') }}</div>
</el-form-item>
<el-form-item :label="t('versionDesc')" prop="version_desc">
<el-input v-model="formData.version_desc" type="textarea" rows="6" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('platform')" prop="platform">
<el-radio-group v-model="formData.platform">
<el-radio :label="key" size="large" v-for="(item, key) in appPlatform">{{ item }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('isForcedUpgrade')" prop="is_forced_upgrade">
<el-switch v-model="formData.is_forced_upgrade" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
</div>
<div v-show="step == 2" class="h-[400px]">
<el-scrollbar>
<el-form-item :label="t('upgradeType')">
<el-radio-group v-model="formData.upgrade_type">
<el-radio label="app" size="large">APP整包升级</el-radio>
<el-radio label="hot" size="large">wgt资源包升级</el-radio>
<el-radio label="market" size="large">应用市场升级</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('resourceFile')" v-show="formData.upgrade_type == 'app'">
<el-radio-group v-model="formData.package_type">
<el-radio label="file" size="large">上传资源包</el-radio>
<el-radio label="cloud" size="large">云打包</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="formData.package_type == 'file' && formData.upgrade_type != 'market'">
<el-form-item label="" prop="package_path">
<div class="input-width" >
<upload-file v-model="formData.package_path" :accept="accept" api="sys/document/app_package"></upload-file>
</div>
<div class="form-tip" v-if="formData.upgrade_type == 'app'">{{ t('androidResourceFileTips') }}</div>
<div class="form-tip" v-if="formData.upgrade_type == 'hot'">{{ t('iosResourceFileTips') }}</div>
</el-form-item>
</div>
<div v-show="formData.upgrade_type == 'market'">
<el-form-item label="应用市场链接" prop="package_path">
<el-input v-model="formData.package_path" clearable class="input-width" />
</el-form-item>
</div>
<div v-show="formData.package_type == 'cloud'">
<el-form-item :label="t('icon')" prop="build.icon">
<div class="input-width" >
<upload-file v-model="formData.build.icon" accept=".zip" api="sys/document/applet"></upload-file>
</div>
<div class="form-tip !leading-[1.5]">应用图标和启动界面图片 icon.png为应用的图标 push.png为推送消息的图标 splash.png为应用启动页的图标 将icon.pngpush.pngsplash.png放置到drawabledrawable-ldpidrawable-mdpidrawable-hdpidrawable-xhdpidrawable-xxhdpi文件夹下压缩成压缩包上传
具体详情可查看 <span class="text-primary cursor-pointer" @click="windowOpen('https://nativesupport.dcloud.net.cn/AppDocs/usesdk/android.html')">uniapp App离线打包</span>配置应用图标和启动界面片段</div>
<div class="form-tip !leading-[1.5]">只支持上传.zip 在drawable的根目录进行压缩</div>
</el-form-item>
<el-form-item :label="t('certType')">
<el-radio-group v-model="formData.cert.type">
<el-radio label="public" size="large">公共证书</el-radio>
<el-radio label="private" size="large">自有证书</el-radio>
</el-radio-group>
<div class="form-tip">{{ t('publicCertTips') }}</div>
<div class="form-tip !leading-[1.5]">{{ t('privateCertTips') }}<span class="text-primary cursor-pointer" @click="windowOpen('https://ask.dcloud.net.cn/article/35777')">Android平台签名证书说明</span></div>
<div class="form-tip flex items-center">证书可以自己生成也可通过niucloud提供的<span class="text-primary cursor-pointer" @click="generateSingCertRef.open()">证书生成工具生成</span></div>
</el-form-item>
<div v-show="formData.cert.type == 'private'">
<el-form-item :label="t('certFile')" prop="cert.file">
<div class="input-width" >
<upload-file v-model="formData.cert.file" accept="" api="sys/document/android_cert"></upload-file>
</div>
</el-form-item>
<el-form-item :label="t('certAlias')" prop="cert.key_alias">
<el-input v-model="formData.cert.key_alias" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('certKeyPassword')" prop="cert.key_password">
<el-input v-model="formData.cert.key_password" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('certStorePassword')" prop="cert.store_password">
<el-input v-model="formData.cert.store_password" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
</div>
</div>
</el-scrollbar>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<view v-show="step == 1">
<el-button type="primary" class="ml-3" @click="step = 2">{{
t('next')
}}</el-button>
</view>
<view v-show="step == 2">
<el-button type="primary" class="ml-3" @click="step = 1">{{
t('prev')
}}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</view>
</span>
</template>
</el-dialog>
<generate-sing-cert ref="generateSingCertRef"/>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addVersion, editVersion, getVersionInfo, getAppPlatform } from '@/app/api/app'
import GenerateSingCert from '@/app/views/channel/app/components/generate-sing-cert.vue'
const showDialog = ref(false)
const loading = ref(false)
const appPlatform = ref({})
const step = ref(1)
const generateSingCertRef = ref(null)
const getAppPlatformFn = async () => {
await getAppPlatform().then(({ data }) => {
appPlatform.value = data
initialFormData.platform = Object.keys(data)[0]
})
}
getAppPlatformFn()
const accept = computed(() => {
if (formData.upgrade_type == 'app') return '.apk'
if (formData.upgrade_type == 'hot') return '.wgt'
return ''
})
/**
* 表单数据
*/
const initialFormData = {
id: '',
version_code: '',
version_name: '',
version_desc: '',
platform: '',
is_forced_upgrade: 0,
package_path: '',
package_type: 'file',
upgrade_type: 'app',
build: {
icon: '',
},
cert: {
type: 'public',
file: '',
key_alias: '',
key_password: '',
store_password: ''
}
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
watch(() => formData.upgrade_type, () => {
if (formData.upgrade_type == 'app' || formData.upgrade_type == 'hot') {
formData.package_type = 'file'
}
formData.package_path = ''
formData.cert.type = 'public'
})
//
const formRules = computed(() => {
return {
version_code: [
{ required: true, message: t('versionCodePlaceholder'), trigger: 'blur' },
{
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (isNaN(value) || !/^\d{0,10}$/.test(value)) {
callback(new Error(t('versionCodeTips')))
} else if (value < 0) {
callback(new Error(t('versionCodeTips')))
} else {
callback()
}
}
}
],
version_name: [
{ required: true, message: t('versionNamePlaceholder'), trigger: 'blur' }
],
package_path: [
{ required: formData.upgrade_type != 'market' && formData.package_type == 'file', message: '请上传资源文件', trigger: 'blur' },
{ required: formData.upgrade_type == 'market', message: '请输入应用市场链接', trigger: 'blur' }
],
'build.icon': [
{ required: formData.package_type == 'cloud', message: '请上传图标文件', trigger: 'blur' },
],
'cert.file': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书文件', trigger: 'blur' }
],
'cert.key_alias': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请输入证书别名', trigger: 'blur' }
],
'cert.key_password': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书密码', trigger: 'blur' }
],
'cert.store_password': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书库密码', trigger: 'blur' }
]
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
const save = formData.id ? editVersion : addVersion
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
save(formData).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
})
}
})
}
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
if (row) {
const data = await (await getVersionInfo(row.id)).data
if (data) Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
loading.value = false
}
watch(() => showDialog.value, () => {
step.value = 1
})
const windowOpen = (url: string) => {
window.open(url)
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label{
height: auto !important;
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<el-dialog v-model="showDialog" title="生成Android证书" width="50%" class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item label="证书别名" prop="key_alias">
<el-input v-model="formData.key_alias" clearable placeholder="" class="input-width" />
<div class="form-tip">只支持字母从证书文件中读取证书时需要别名</div>
</el-form-item>
<el-form-item label="证书密码" prop="key_password">
<el-input v-model="formData.key_password" clearable placeholder="" class="input-width" />
<div class="form-tip">只支持字母或数字密码至少 6 设置好后请牢记密码</div>
</el-form-item>
<el-form-item label="证书库密码" prop="store_password">
<el-input v-model="formData.store_password" clearable placeholder="" class="input-width" />
</el-form-item>
<el-form-item label="有效期" prop="limit">
<div class="flex items-center">
<el-input v-model="formData.limit" clearable placeholder="" class="w-[100px]" />
<div class="form-tip ml-2"></div>
</div>
<div class="form-tip"> 1 - 100 年之间</div>
</el-form-item>
<div class="text-primary cursor-pointer pl-[120px] my-[10px]" @click="moreInfo = !moreInfo">
{{ moreInfo ? '点击收起' : '点击展开填写更多信息 '}}
</div>
<view v-show="moreInfo">
<el-form-item label="域名">
<el-input v-model="formData.cn" clearable placeholder="" class="input-width" />
</el-form-item>
<el-form-item label="组织名称">
<el-input v-model="formData.o" clearable placeholder="" class="input-width" />
<div class="form-tip">如公司名称或者其他名称</div>
</el-form-item>
<el-form-item label="部门">
<el-input v-model="formData.ou" clearable placeholder="" class="input-width" />
<div class="form-tip">部门名称 IT 研发部等</div>
</el-form-item>
<el-form-item label="国家地区">
<el-input v-model="formData.c" clearable placeholder="" class="input-width" />
<div class="form-tip">输入国家/地区代号两个字母中国为CN</div>
</el-form-item>
<el-form-item label="省份">
<el-input v-model="formData.st" clearable placeholder="" class="input-width" />
<div class="form-tip">所在省份名称如Beijing</div>
</el-form-item>
<el-form-item label="城市">
<el-input v-model="formData.l" clearable placeholder="" class="input-width" />
<div class="form-tip">所在城市名称如Beijing</div>
</el-form-item>
</view>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">生成</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import type { FormInstance } from 'element-plus'
import { generateSignCert } from '@/app/api/app'
import { img } from '@/utils/common'
const showDialog = ref(false)
const moreInfo = ref(false)
const loading = ref(false)
const initialFormData = {
key_alias: '',
key_password: '',
store_password: '',
limit: 30,
cn: '',
o: '',
ou: '',
c: '',
st: '',
l: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
key_alias: [
{ required: true, message: '请输入证书别名', trigger: 'blur' }
],
key_password: [
{ required: true, message: '请输入证书密码', trigger: 'blur' }
],
store_password: [
{ required: true, message: '请输入证书库密码', trigger: 'blur' }
],
limit: [
{ required: true, message: '请输入有效期', trigger: 'blur' },
{
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (isNaN(value) || !/^\d{0,10}$/.test(value)) {
callback(new Error('有效期必须是数字'))
} else if (value < 0) {
callback(new Error('有效期必须为 1 - 100 之间的数字'))
} else if (value > 100) {
callback(new Error('有效期必须为 1 - 100 之间的数字'))
} else {
callback()
}
}
}
],
organization_name: [
{ required: true, message: '请输入所有者', trigger: 'blur' }
]
}
})
const confirm = async (formEl: FormInstance | undefined) => {
if (formEl) {
await formEl.validate()
loading.value = true
generateSignCert(formData).then(res => {
loading.value = false
showDialog.value = false
window.open(img(res.data), '_blank')
})
}
}
const open = async (row: any = null) => {
Object.assign(formData, initialFormData)
showDialog.value = true
}
defineExpose({
open
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,129 @@
<template>
<!--小程序配置-->
<div class="main-container">
<el-card class="card !border-none" shadow="never">
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
</el-card>
<el-form class="page-form mt-[15px]" :model="formData" label-width="170px" ref="formRef" :rules="formRules" v-loading="loading">
<el-card class="box-card !border-none mt-[15px]" shadow="never">
<h3 class="panel-title !text-sm">{{ t('appInfo') }}</h3>
<el-form-item :label="t('uniAppId')" prop="uni_app_id">
<el-input v-model.trim="formData.uni_app_id" placeholder="" class="input-width" clearable/>
<div class="form-tip flex items-center">
{{ t('uniAppIdTips') }}
<el-button link type="primary" @click="windowOpen('https://www.dcloud.io/')">{{ t('toCreate') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('appName')" prop="app_name">
<el-input v-model.trim="formData.app_name" placeholder="" class="input-width" clearable/>
</el-form-item>
<el-form-item :label="t('androidAppKey')" prop="android_app_key">
<el-input v-model.trim="formData.android_app_key" placeholder="" class="input-width" clearable/>
<div class="form-tip">
{{ t('androidAppKeyTips') }}
<span class="text-primary cursor-pointer" @click="windowOpen('https://nativesupport.dcloud.net.cn/AppDocs/usesdk/appkey.html')">查看详情</span>
</div>
</el-form-item>
<el-form-item :label="t('applicationId')" prop="application_id">
<el-input v-model.trim="formData.application_id" placeholder="" class="input-width" clearable/>
<div class="form-tip">
{{ t('applicationIdTips') }}
</div>
</el-form-item>
</el-card>
<el-card class="box-card !border-none mt-[15px]" shadow="never">
<h3 class="panel-title !text-sm">{{ t('wechatAppInfo') }}</h3>
<el-form-item :label="t('wechatAppid')" prop="app_id">
<el-input v-model.trim="formData.wechat_app_id" :placeholder="t('appidPlaceholder')" class="input-width" clearable/>
<div class="form-tip">
{{ t('wechatAppidTips') }}
</div>
</el-form-item>
<el-form-item :label="t('wechatAppsecret')" prop="app_secret">
<el-input v-model.trim="formData.wechat_app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
</el-form-item>
</el-card>
</el-form>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" :loading="loading" @click="save(formRef)">{{ t('save') }}</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { getAppConfig, setAppConfig } from '@/app/api/app'
import { FormInstance } from "element-plus"
import { useRoute, useRouter } from "vue-router"
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const loading = ref(true)
const formData = reactive<Record<string, any>>({
uni_app_id: '',
app_name: '',
android_app_key: '',
application_id: '',
wechat_app_id: '',
wechat_app_secret: ''
})
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
}
})
/**
* 获取app配置
*/
getAppConfig().then(res => {
Object.assign(formData, res.data)
loading.value = false
})
/**
* 保存
*/
const save = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
setAppConfig(formData).then(() => {
loading.value = false
}).catch(() => {
loading.value = false
})
}
})
}
const windowOpen = (url: string) => {
window.open(url)
}
const back = () => {
router.push('/channel/app')
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,339 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{pageName}}</span>
</div>
<el-tabs v-model="activeName" class="my-[20px]" @tab-change="handleClick">
<el-tab-pane :label="t('accessFlow')" name="/channel/app" />
<el-tab-pane :label="t('versionManage')" name="/channel/app/version" />
</el-tabs>
<el-alert type="info">
<template #default>
<div class="flex items-center">
<div>
<p>使用云打包提交成功后请先不要离开该页面稍待几分钟等待打包结果的返回</p>
</div>
</div>
</template>
</el-alert>
<div class="mt-[20px]">
<el-button type="primary" @click="addEvent" :disabled="loading">{{ t('addAppVersion') }}</el-button>
</div>
<div class="mt-[10px]">
<el-table :data="appVersionTable.data" size="large" v-loading="appVersionTable.loading">
<template #empty>
<span>{{ !appVersionTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column type="index" width="90" :label="t('index')" />
<el-table-column prop="version_code" :label="t('versionCode')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="version_name" :label="t('versionName')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="version_desc" :label="t('versionDesc')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="platform_name" :label="t('platform')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="status_name" :label="t('status')" min-width="120" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<el-button link :loading="row.status == 'creating'">{{ row.status_name }}</el-button>
</template>
</el-table-column>
<el-table-column prop="status" :label="t('isForcedUpgradeTitle')" min-width="120" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.is_forced_upgrade ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column prop="package_path" :label="t('packagePath')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('releaseTime')" min-width="120" :show-overflow-tooltip="true">
<template #default="{ row }">
<text v-if="row.release_time != 0">
{{ row.release_time }}
</text>
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="200px">
<template #default="{ row }">
<el-button type="primary" link v-if="row.release_time == 0" @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link v-if="row.status == 'upload_success'" @click="releaseEvent(row)">{{ t('release') }}</el-button>
<el-button type="primary" link v-if="row.status == 'creating'" @click="seeBuildLog(row)">{{ t('seeBuildLog') }}</el-button>
<el-button type="primary" link v-if="row.package_path && row.upgrade_type != 'market'" @click="downloadEvent(row)">{{ t('download') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="appVersionTable.page" v-model:page-size="appVersionTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="appVersionTable.total"
@size-change="loadAppVersionList()" @current-change="loadAppVersionList" />
</div>
</div>
<edit ref="editAppVersionDialog" @complete="loadAppVersionList" />
</el-card>
<el-dialog v-model="failReasonDialogVisible" :title="t('failReason')" width="60%">
<el-scrollbar class="h-[60vh] w-full whitespace-pre-wrap p-[20px]">
<div v-html="failReason"></div>
</el-scrollbar>
</el-dialog>
</div>
<el-dialog v-model="showDialog" :title="t('buildLog')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose">
<div class="h-[370px]">
<terminal ref="terminalRef" :name="`upgrade-${terminalId}`" context="" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { img, getAppType } from '@/utils/common'
import { ElMessageBox, FormInstance } from 'element-plus'
import { getVersionList, getBuildLog, deleteVersion, releaseVersion } from '@/app/api/app'
import Edit from '@/app/views/channel/app/components/app-version-edit.vue'
import { useRoute, useRouter } from 'vue-router'
import { Terminal, TerminalFlash } from 'vue-web-terminal'
import 'vue-web-terminal/lib/theme/dark.css'
import { getAuthInfo } from '@/app/api/module'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const activeName = ref('/channel/app/version')
const terminalRef: any = ref(null)
const appVersionTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
platfrom: ''
}
})
const showDialog = ref(false)
const loading = ref(true)
const authCode = ref('')
getAuthInfo().then(res => {
if (res.data.data && res.data.data.auth_code) {
authCode.value = res.data.data.auth_code
}
loading.value = false
}).catch(() => {
loading.value = false
})
const handleClick = (val: any) => {
router.push({ path: activeName.value })
}
const searchFormRef = ref<FormInstance>()
/**
* 获取app版本管理列表
*/
const loadAppVersionList = (page: number = 1) => {
appVersionTable.loading = true
appVersionTable.page = page
getVersionList({
page: appVersionTable.page,
limit: appVersionTable.limit,
...appVersionTable.searchParam
}).then(res => {
appVersionTable.loading = false
appVersionTable.data = res.data.data
appVersionTable.total = res.data.total
if (page == 1 && appVersionTable.data.length && appVersionTable.data[0].status == 'creating') getAppBuildLogFn(appVersionTable.data[0].task_key)
}).catch(() => {
appVersionTable.loading = false
})
}
loadAppVersionList()
const editAppVersionDialog: Record<string, any> | null = ref(null)
/**
* 添加app版本管理
*/
const addEvent = () => {
if (!authCode.value) {
authElMessageBox()
return
}
editAppVersionDialog.value.setFormData()
editAppVersionDialog.value.showDialog = true
}
/**
* 编辑app版本管理
* @param data
*/
const editEvent = (data: any) => {
editAppVersionDialog.value.setFormData(data)
editAppVersionDialog.value.showDialog = true
}
/**
* 删除app版本管理
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('appVersionDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteVersion({ id }).then(() => {
loadAppVersionList()
}).catch(() => {
})
})
}
const releaseEvent = (data: any) => {
ElMessageBox.confirm(t('appVersionReleaseTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
releaseVersion(data.id).then(() => {
loadAppVersionList()
}).catch(() => {
})
})
}
let buildLog = []
const getAppBuildLogFn = (key: string) => {
getBuildLog(key).then(res => {
if (res.data) {
if (res.data.status == '') {
if (showDialog.value) {
if (!buildLog.length) {
terminalRef.value.execute('clear')
terminalRef.value.execute('开始打包')
}
res.data.build_log.data[0].forEach((item) => {
if (!buildLog.includes(item.action)) {
terminalRef.value.pushMessage({ content: `${item.action}` })
buildLog.push(item.action)
}
})
}
setTimeout(() => {
getAppBuildLogFn(key)
}, 2000)
} else {
if (res.data.status == 'fail' && showDialog.value) {
terminalRef.value.pushMessage({ content: res.data.fail_reason, class: 'error' })
} else {
showDialog.value = false
}
loadAppVersionList()
buildLog = []
}
}
})
}
const seeBuildLog = () => {
showDialog.value = true;
}
/**
* 升级进度动画
*/
let flashInterval: any = null
const terminalFlash = new TerminalFlash()
const onExecCmd = (key, command, success, failed, name) => {
if (command == '开始打包') {
success(terminalFlash)
const frames = makeIterator(['/', '——', '\\', '|'])
flashInterval = setInterval(() => {
terminalFlash.flush('> ' + frames.next().value)
}, 150)
}
}
const makeIterator = (array: string[]) => {
let nextIndex = 0
return {
next () {
if (nextIndex + 1 == array.length) {
nextIndex = 0
}
return { value: array[nextIndex++] }
}
}
}
const failReason = ref('')
const failReasonDialogVisible = ref(false)
const handleFailReason = (data: any) => {
failReason.value = data.fail_reason
failReasonDialogVisible.value = true
}
const downloadEvent = (data: any) => {
window.open(img(data.package_path), '_blank')
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadAppVersionList()
}
const authElMessageBox = () => {
if (getAppType() == 'admin') {
ElMessageBox.confirm(
t('authTips'),
t('warning'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('toBind'),
cancelButtonText: t('toNiucloud')
}
).then(() => {
router.push({ path: '/app/authorize' })
}).catch((action: string) => {
if (action === 'cancel') {
window.open('https://www.niucloud.com/app')
}
})
} else {
ElMessageBox.alert(t('siteAuthTips'), t('warning'))
}
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

View File

@ -7,7 +7,7 @@
<el-form label-width="80px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showTitleStyle">{{ diyStore.editComponent.titleStyle.title }}</span>
<el-icon>
<el-icon @click="showTitleStyle" class="cursor-pointer">
<ArrowRight />
</el-icon>
</el-form-item>
@ -57,7 +57,7 @@
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer"
@click="showBlockStyle">{{ diyStore.editComponent.blockStyle.title }}</span>
<el-icon>
<el-icon @click="showBlockStyle" class="cursor-pointer">
<ArrowRight />
</el-icon>
</el-form-item>

View File

@ -29,7 +29,7 @@
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showSearchStyle">{{ diyStore.editComponent.search.styleName }}</span>
<el-icon>
<el-icon @click="showSearchStyle" class="cursor-pointer">
<ArrowRight />
</el-icon>
</el-form-item>

View File

@ -7,7 +7,7 @@
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer"
@click="showStyle">{{ diyStore.editComponent.styleName }}</span>
<el-icon>
<el-icon @click="showStyle" class="cursor-pointer">
<ArrowRight />
</el-icon>
</el-form-item>

View File

@ -7,7 +7,7 @@
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer"
@click="showStyle">{{ diyStore.editComponent.styleName }}</span>
<el-icon>
<el-icon @click="showStyle" class="cursor-pointer">
<ArrowRight />
</el-icon>
</el-form-item>

View File

@ -808,6 +808,7 @@ const getInstallTask = (first: boolean = true) => {
restarting = false
break;
case "installing":
restarting = true
getCloudInstallLog()
setTimeout(() => {
getInstallTask(false)
@ -858,6 +859,8 @@ const formatUpgradeDuration = computed(() => {
const checkInstallTask = () => {
installShowDialog.value = true
installStep.value = 1
getInstallTask(false)
notificationEl && notificationEl.close()
}
const localInstalling = ref(false)

View File

@ -0,0 +1,202 @@
<template>
<div>
<div @click="show">
<slot>
<el-input v-model="selectData.title" :placeholder="t('请选择跳转页面')" readonly class="link-input">
<template #suffix>
<div @click.stop="clear">
<el-icon v-if="selectData.name">
<Close />
</el-icon>
<el-icon v-else>
<ArrowRight />
</el-icon>
</div>
</template>
</el-input>
</slot>
</div>
<el-dialog v-model="showDialog" :title="t('页面选择')" width="850px" :destroy-on-close="true" :close-on-click-modal="false">
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="timeListTableRef" max-height="400">
<template #empty>
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column min-width="7%">
<template #default="{ row }">
<el-checkbox v-model="row.checked" @change="handleCheckChange($event,row)" />
</template>
</el-table-column>
<el-table-column prop="page_title" :label="t('页面名称')" min-width="25%" />
<el-table-column prop="type_name" :label="t('页面类型')" min-width="25%" />
<el-table-column prop="url" :label="t('页面路径')" min-width="25%" />
</el-table>
<div class="mt-[16px] flex">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="tableData.total"
@size-change="loadList()" @current-change="loadList" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import { ref, reactive, nextTick,computed } from 'vue'
import { FormInstance, ElMessage } from "element-plus";
import { getPageLink } from '@/app/api/diy'
const prop = defineProps({
modelValue: {
type: Object,
default: () => {
return {
id: 0,
name: '',
parent: '',
title: '',
url: ''
}
}
},
ignore: {
type: Array,
default: []
}
})
const clear = () => {
selectData.value = {
id: 0,
name: '',
parent: '',
title: '',
url: ''
}
}
const emit = defineEmits(['update:modelValue', 'confirm', 'success'])
const selectData: any = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const searchFormRef = ref<FormInstance>()
const tableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
name: '',
}
})
const timeListTableRef = ref()
const showDialog = ref(false)
const show = () => {
showDialog.value = true
loadList()
}
/**
* 获取商品列表
*/
const loadList = (page: number = 1) => {
tableData.loading = true
tableData.page = page
getPageLink({
page: tableData.page,
limit: tableData.limit,
...tableData.searchParam
}).then(res => {
tableData.loading = false
tableData.data = res.data.data
tableData.data.forEach((item: any) => {
item.checked = item.id == selectData.value.id
})
tableData.total = res.data.total
setTimesSelected();
}).catch(() => {
tableData.loading = false
})
}
loadList()
const handleCheckChange = (isSelect: any, row: any) => {
if (isSelect) {
selectData.value.id = row.id
} else {
selectData.value.id = 0 //
}
setTimesSelected()
}
// //
const setTimesSelected = () => {
nextTick(() => {
for (let i = 0; i < tableData.data.length; i++) {
tableData.data[i].checked = false
if (selectData.value.id == tableData.data[i].id) {
tableData.data[i].checked = true
Object.assign(selectData.value, tableData.data[i])
}
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadList()
}
const save = () => {
if (selectData.value.id == 0) {
ElMessage({
type: 'warning',
message: `${ t('请选择页面') }`
})
return;
}
selectData.value ={
id:selectData.value.id,
name:selectData.value.name,
parent:selectData.value.parent,
title:selectData.value.title,
url:selectData.value.url
}
showDialog.value = false;
}
defineExpose({
show,
showDialog,
})
</script>
<style lang="scss" scoped>
.form-item-wrap {
margin-right: 10px !important;
margin-bottom: 10px !important;
&.last-child {
margin-right: 0 !important;
}
}
</style>

View File

@ -50,15 +50,19 @@ import { img } from '@/utils/common'
const showDialog = ref(false)
const channel = ref('h5')
// H5
const wapUrl = ref('')
const wapDomain = ref('')
const wapImage = ref('')
const wapPreview = ref('')
const pageName = ref('')
//
const weappData = reactive({
path: ''
})
//
getUrl().then((res: any) => {
wapUrl.value = res.data.wap_url
@ -66,42 +70,53 @@ getUrl().then((res: any) => {
if (import.meta.env.MODE == 'production') return
wapDomain.value = res.data.wap_domain
// envwap
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
}
const wapDomainStorage = storage.get('wap_domain')
if (wapDomainStorage) {
wapUrl.value = wapDomainStorage
}
})
// **H5**
// H5
const generateH5QRCode = () => {
wapPreview.value = `${ wapUrl.value }${ pageName.value }`
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 120 }).then(url => {
// URL
const queryStr = params.value
.map(item => `${encodeURIComponent(item.name)}=${encodeURIComponent(item.value)}`)
.join('&')
// H5
wapPreview.value = `${wapUrl.value}${pagePath.value}${queryStr ? '?' + queryStr : ''}`
//
QRCode.toDataURL(wapPreview.value, {
errorCorrectionLevel: 'L',
margin: 0,
width: 120
}).then(url => {
wapImage.value = url
})
}
// ****
//
const fetchWeAppQRCode = () => {
// page '/'
if (pagePath.value.startsWith('/')) {
pagePath.value = pagePath.value.slice(1);
// '/'
let page = pagePath.value
if (page.startsWith('/')) {
page = page.slice(1)
}
//
getQrcode({
page: pagePath.value, //
folder: folder.value, //
params: [
{
column_name: columnName.value,
column_value: columnValue.value
}
]
page: page,
folder: folder.value,
// { column_name, column_value }
params: params.value.map(item => ({
column_name: item.name,
column_value: item.value
}))
}).then((res: any) => {
if (res.data) {
weappData.path = res.data.weapp_path
@ -109,24 +124,30 @@ const fetchWeAppQRCode = () => {
})
}
//
const pagePath = ref("")
const columnName = ref("")
const columnValue = ref("")
const titleName = ref("")
const folder: any = ref("")
//
const pagePath = ref("") // "/addon/pintuan/pages/goods/detail"
const params = ref<Array<{name: string; value: string | number}>>([]) //
const titleName = ref("") //
const folder: any = ref("") //
// ****
const show = (page: string, column: string, value: string, title: string, dir: string) => {
//
const show = (
page: string,
paramsArr: Array<{name: string; value: string | number}>,
title: string,
dir: string
) => {
//
pagePath.value = page
columnName.value = column
columnValue.value = value
params.value = paramsArr
titleName.value = title
folder.value = dir
pageName.value = `${ pagePath.value }?${ columnName.value }=${ columnValue.value }`
//
generateH5QRCode()
fetchWeAppQRCode()
//
showDialog.value = true
}
@ -156,3 +177,4 @@ defineExpose({
show
})
</script>

View File

@ -3,13 +3,22 @@ package com.niu.core.common.component.base;
import java.util.HashMap;
import java.util.Map;
/**
* 线程本地变量工具类用于存储和获取线程上下文信息
*/
public final class ThreadLocalHolder {
private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal();
// 私有构造函数防止实例化
private ThreadLocalHolder() {
throw new AssertionError("工具类不允许实例化");
}
private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();
/**
* @param key
* @param value
* 存储键值对到线程本地变量
* @param key
* @param value
*/
public static void put(String key, Object value) {
Map<String, Object> map = threadLocal.get();
@ -21,8 +30,9 @@ public final class ThreadLocalHolder {
}
/**
* @param key
* @return
* 获取线程本地变量中的值
* @param key
* @return 对应的值不存在则返回null
*/
public static Object get(String key) {
Map<String, Object> map = threadLocal.get();
@ -32,65 +42,108 @@ public final class ThreadLocalHolder {
return map.get(key);
}
/**
* 获取指定类型的线程本地变量值
* @param key
* @param clazz 目标类型
* @param <T> 泛型类型
* @return 转换后的对象null或转换失败时返回null
*/
public static <T> T get(String key, Class<T> clazz) {
Object value = get(key);
if (value == null) {
return null;
}
try {
return clazz.cast(value);
} catch (ClassCastException e) {
// 类型转换失败时返回null可根据需要添加日志
return null;
}
}
/**
* 移除所有
* 移除线程本地变量中的所有
*/
public static void remove() {
threadLocal.remove();
}
/**
* @param key
* @return
* 移除线程本地变量中指定键的值
* @param key 要移除的键
*/
public static void remove(String key) {
Map<String, Object> map = threadLocal.get();
if (map != null) {
map.remove(key);
// 如果map为空可选择清除ThreadLocal以释放资源
if (map.isEmpty()) {
threadLocal.remove();
}
}
}
/**
* 获取Integer类型的值
* @param key
* @return 转换后的Integer失败则返回0
*/
public static Integer getInteger(String key){
Object result = get(key);
try{
return (Integer)result;
return (Integer) result;
}catch (Exception e){
// 防止类型错误
// 类型转换失败返回默认值
return 0;
}
return 0;
}
/**
* @param key
* @return
* 获取Long类型的值
* @param key
* @return 转换后的Long失败则返回0L
*/
public static Long getLong(String key){
Object result = get(key);
try{
return (Long)result;
return (Long) result;
}catch (Exception e){
// 防止类型错误
// 类型转换失败返回默认值
return 0L;
}
return 0L;
}
public static void putInteger(String key, Integer value) {
put(key,value);
}
/**
* @param key
* @return
* 存储Integer类型值
* @param key
* @param value
*/
public static void putInteger(String key, Integer value) {
put(key, value);
}
/**
* 获取String类型的值
* @param key
* @return 转换后的String失败则返回空字符串
*/
public static String getString(String key){
Object result = get(key);
try{
return (String)result;
return (String) result;
}catch (Exception e){
// 防止类型错误
// 类型转换失败返回默认值
return "";
}
return "";
}
/**
* 存储String类型值
* @param key
* @param value
*/
public static void putString(String key, String value) {
put(key,value);
put(key, value);
}
}

View File

@ -37,6 +37,7 @@ public class CallbackPublisher {
callbackMap = CallbackMediator.getInstance().handle(event);
}
callbackMap = CallbackMediator.getInstance().handleBySpring(event);
System.out.println("==============event==================>" + callbackMap);
if (callbackMap != null) {
return new ArrayList<>(callbackMap.values());
}

View File

@ -49,7 +49,7 @@ public class GlobalConfig {
/**
* 版本
*/
public static String version = "1.0.0";
public static String version = "1.0.1";
/**
* 应用key

View File

@ -110,12 +110,24 @@ public class PermissionAuthorizer {
if (supportUrl.endsWith("]")) {
supportUrl = supportUrl.substring(0, supportUrl.length() - 1);
}
HandlerMethod handlerMethod = methodMap.get(requestMappingInfo);
Authorizer authorizer = new Authorizer();
authorizer.setSourcePath(supportUrl);
authorizer.setMethod(supportMethods.toString());
authorizer.setHandlerMethod(handlerMethod);
authorizerMap.put(supportMethods.toString() + supportUrl, authorizer);
if (supportMethods.size() == 0) {
for (RequestMethod method : RequestMethod.values()) {
HandlerMethod handlerMethod = methodMap.get(requestMappingInfo);
Authorizer authorizer = new Authorizer();
authorizer.setSourcePath(supportUrl);
authorizer.setMethod(supportMethods.toString());
authorizer.setHandlerMethod(handlerMethod);
authorizerMap.put("[" + method.toString() + "]" + supportUrl, authorizer);
}
} else {
HandlerMethod handlerMethod = methodMap.get(requestMappingInfo);
Authorizer authorizer = new Authorizer();
authorizer.setSourcePath(supportUrl);
authorizer.setMethod(supportMethods.toString());
authorizer.setHandlerMethod(handlerMethod);
authorizerMap.put(supportMethods.toString() + supportUrl, authorizer);
}
}
}

View File

@ -37,18 +37,18 @@ public class SaTokenAdminInterceptor extends SaInterceptor {
/** 验证是否登录 */
String token= RequestUtils.adminToken();
if((ObjectUtil.isEmpty(token) || ObjectUtil.isNull(token)) && isCheckLogin){
throw new AuthException("MUST_LOGIN", 401);
throw new AuthException("请登录", 401);
}
/** 检验当前登录是否有效 */
Boolean islogin=StpUtil.isLogin();
if(!islogin && isCheckLogin){
throw new AuthException("LOGIN_EXPIRE", 401);
throw new AuthException("登录过期,请重新登录", 401);
}
if(islogin){
//设置当前登录用户id
String[] loginId = ObjectUtil.defaultIfEmpty(StpUtil.getLoginId().toString(), "").split("-");
if (loginId[1] == null) throw new AuthException("LOGIN_EXPIRE", 401);
if (loginId[1] == null) throw new AuthException("登录过期,请重新登录", 401);
Integer uid = Integer.parseInt(loginId[1]);
RequestUtils.setUid(uid);

View File

@ -44,19 +44,19 @@ public class SaTokenApiInterceptor extends SaInterceptor {
/** 验证是否登录 */
String token= RequestUtils.apiToken();
if((ObjectUtil.isEmpty(token) || ObjectUtil.isNull(token)) && isCheckLogin){
throw new AuthException("MUST_LOGIN", 401);
throw new AuthException("请登录", 401);
}
/** 检验当前登录是否有效 */
Boolean islogin=StpUtil.isLogin();
if(!islogin && isCheckLogin){
throw new AuthException("LOGIN_EXPIRE", 401);
throw new AuthException("'登录过期,请重新登录", 401);
}
/** 设置当前登录会员id */
if(islogin){
String[] loginId = ObjectUtil.defaultIfEmpty(StpUtil.getLoginId().toString(), "").split("-");
if (loginId[1] == null) throw new AuthException("LOGIN_EXPIRE", 401);
if (loginId[1] == null) throw new AuthException("登录过期,请重新登录", 401);
Integer memberId=Integer.parseInt(loginId[1]);
RequestUtils.setMemberId(memberId);

View File

@ -48,10 +48,10 @@ public class SaTokenInterceptor extends SaInterceptor {
try{
boolean checkToken = super.preHandle(request, response, handler);
if(!checkToken){
throw new AuthException("MUST_LOGIN", 401);
throw new AuthException("请登录", 401);
}
}catch (Exception e){
throw new AuthException("MUST_LOGIN", 401);
throw new AuthException("请登录", 401);
}

View File

@ -3,6 +3,7 @@ package com.niu.core.common.domain;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
@ -17,6 +18,8 @@ import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* JSON序列化
@ -30,6 +33,12 @@ public final class BeanJsonSerializer {
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 如果是Redis序列化上下文直接写入字符串
if (isRedisSerializationContext()) {
jsonGenerator.writeNumber(value);
return;
}
// 按照自定义格式转换
String formatStr = "";
if (value != null && value > 0) {
@ -38,6 +47,17 @@ public final class BeanJsonSerializer {
jsonGenerator.writeString(formatStr);
}
private boolean isRedisSerializationContext() {
// 通过堆栈信息判断是否在Redis序列化过程中
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
if (element.getClassName().contains("Redis") ||
element.getClassName().contains("Jackson2JsonRedisSerializer")) {
return true;
}
}
return false;
}
}
/**
@ -76,15 +96,48 @@ public final class BeanJsonSerializer {
@Override
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (value == null || value.trim().isEmpty()) {
jsonGenerator.writeNull();
return;
}
// 如果是Redis序列化上下文直接写入字符串
if (isRedisSerializationContext()) {
jsonGenerator.writeString(value);
return;
}
try {
jsonGenerator.writeObject(JsonViewUtils.toJsonObject(value));
JSON json = JSONUtil.parse(value);
if (json instanceof JSONObject) {
Map<String, Object> map = JSONUtil.toBean(value, Map.class);
jsonGenerator.writeObject(map);
} else if (json instanceof JSONArray) {
List<Object> list = JSONUtil.toList(value, Object.class);
jsonGenerator.writeObject(list);
} else {
jsonGenerator.writeString(value);
}
} catch (Exception e) {
// ignore
jsonGenerator.writeString(value);
}
}
private boolean isRedisSerializationContext() {
// 通过堆栈信息判断是否在Redis序列化过程中
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
if (element.getClassName().contains("Redis") ||
element.getClassName().contains("Jackson2JsonRedisSerializer")) {
return true;
}
}
return false;
}
}
/**
* BigDecimal显示为带2个小数
*/

View File

@ -15,6 +15,4 @@ public class PageParam implements Serializable {
private Integer page = 1;
// 每页数量
private Integer limit = 10;
}

View File

@ -30,6 +30,18 @@ public class PageResult<E> {
// 记录结果
private List<E> data;
private Integer lastPage;
public Integer getLastPage() {
if (total <= 0) {
return 0;
}
if (perPage <= 0) {
return 1;
}
return (int) ((total + perPage - 1) / perPage);
}
public PageResult() {
}

View File

@ -0,0 +1,80 @@
package com.niu.core.common.utils;
import com.niu.core.common.exception.CommonException;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ObjectUtils;
import org.krysalis.barcode4j.HumanReadablePlacement;
import org.krysalis.barcode4j.impl.code128.Code128Bean;
import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
import java.awt.image.BufferedImage;
import java.io.*;
@Log4j2
public class BarcodeUtils {
public static String generateBarcode(String msg, String path) {
// 校验路径是否存在不存在则创建
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
path = path + msg + ".png";
File file = new File(path);
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
generateBarCode128(msg, 10.0, 0.3, true, false, outputStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
return path;
}
/**
* 生成code128条形码
*
* @param message 要生成的文本
* @param height 条形码的高度
* @param width 条形码的宽度
* @param withQuietZone 是否两边留白
* @param hideText 隐藏可读文本
* @param outputStream 输出流
*/
public static void generateBarCode128(String message, Double height, Double width, boolean withQuietZone, boolean hideText, OutputStream outputStream) {
Code128Bean bean = new Code128Bean();
// 分辨率越大条形码就越大
int dpi = 150;
// 设置两侧是否留白
bean.doQuietZone(withQuietZone);
// 设置条形码高度和宽度
bean.setBarHeight(ObjectUtils.defaultIfNull(height, 9.0D));
if (width != null) {
bean.setModuleWidth(width);
}
// 设置文本位置包括是否显示
if (hideText) {
bean.setMsgPosition(HumanReadablePlacement.HRP_NONE);
}
// 设置图片类型
String format = "image/png";
BitmapCanvasProvider canvas = new BitmapCanvasProvider(outputStream, format, dpi,
BufferedImage.TYPE_BYTE_BINARY, false, 0);
// 生产条形码
bean.generateBarcode(canvas, message);
try {
canvas.finish();
} catch (IOException e) {
log.error("生成条形码失败:{}", e.getMessage(), e);
throw new CommonException("生成条形码失败");
}
}
}

View File

@ -3,6 +3,13 @@ package com.niu.core.common.utils;
import cn.hutool.core.util.ObjectUtil;
import com.niu.core.common.component.base.ThreadLocalHolder;
import com.niu.core.enums.common.ChannelEnum;
import com.niu.core.service.core.member.ICoreMemberService;
import com.niu.core.service.core.member.dto.MemberInfoDto;
import com.niu.core.service.core.user.ICoreUserService;
import com.niu.core.service.core.user.dto.UserInfoDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;
@ -12,8 +19,90 @@ import jakarta.servlet.http.HttpServletRequest;
/**
* 系统请求工具类
*/
@Component
public class RequestUtils {
private static ApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
RequestUtils.applicationContext = applicationContext;
}
private static ICoreUserService getUserService() {
return applicationContext.getBean(ICoreUserService.class);
}
private static ICoreMemberService getMemberService() {
return applicationContext.getBean(ICoreMemberService.class);
}
/**
* 获取当前登录用户信息带缓存
* @return 用户信息若uid无效则返回null
*/
public static UserInfoDto getCurrentUser() {
// 先从缓存获取
UserInfoDto user = ThreadLocalHolder.get("current-user", UserInfoDto.class);
if (ObjectUtil.isNotNull(user)) {
return user;
}
// 缓存不存在查询数据库
Integer uid = uid();
if (ObjectUtil.isNotNull(uid) && uid > 0) {
user = getUserService().getUserInfo(uid);
// 存入缓存
ThreadLocalHolder.put("current-user", user);
}
return user;
}
/**
* 获取当前登录会员信息带缓存
* @return 会员信息若memberId无效则返回null
*/
public static MemberInfoDto getCurrentMember() {
// 先从缓存获取
MemberInfoDto member = ThreadLocalHolder.get("current-member", MemberInfoDto.class);
if (ObjectUtil.isNotNull(member)) {
return member;
}
// 缓存不存在查询数据库
Integer memberId = memberId();
if (ObjectUtil.isNotNull(memberId) && memberId > 0) {
member = getMemberService().getMemberInfo(memberId);
// 存入缓存
ThreadLocalHolder.put("current-member", member);
}
return member;
}
/**
* 清除用户信息缓存
*/
public static void clearUserCache() {
ThreadLocalHolder.remove("current-user");
}
/**
* 清除会员信息缓存
*/
public static void clearMemberCache() {
ThreadLocalHolder.remove("current-member");
}
/**
* 清除所有用户相关缓存
*/
public static void clearAllUserRelatedCache() {
clearUserCache();
clearMemberCache();
}
/**
* 获取默认站点id
*/

View File

@ -11,6 +11,8 @@ import com.niu.core.common.exception.CommonException;
import com.niu.core.common.utils.date.DateUtils;
import com.niu.core.enums.channel.WechatEncryptionTypeEnum;
import com.niu.core.service.admin.wxoplatform.vo.OplatformConfigVo;
import com.niu.core.service.core.channel.ICoreAppService;
import com.niu.core.service.core.channel.vo.AppConfigVo;
import com.niu.core.service.core.weapp.ICoreWeappConfigService;
import com.niu.core.service.core.weapp.vo.WeappConfigVo;
import com.niu.core.service.core.wechat.ICoreWechatConfigService;
@ -131,6 +133,25 @@ public class WechatUtils {
}
}
/**
* App
* @param siteId
* @return
*/
public static WxMpService app(Integer siteId) {
ICoreAppService coreAppService = (ICoreAppService) SpringContext.getBean(ICoreAppService.class);
AppConfigVo appConfigVo = coreAppService.getConfig(siteId);
WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl();
wxMpDefaultConfig.setAppId(appConfigVo.getWechatAppId());
wxMpDefaultConfig.setSecret(appConfigVo.getWechatAppSecret());
wxMpDefaultConfig.setToken("");
WxMpService service = new WxMpServiceImpl();
service.setWxMpConfigStorage(wxMpDefaultConfig);
return service;
}
/**
* 微信开放平台接口
* @return

View File

@ -4,14 +4,34 @@ package com.niu.core.common.utils.date;
import cn.hutool.core.date.DateUtil;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* 针对hutool二次封装时间方便调用
*/
public class DateUtils {
private static final List<String> datePatterns = List.of(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss",
"yyyy-MM-dd",
"yyyy/MM/dd",
"yyyy年MM月dd日 HH时mm分ss秒",
"yyyy年MM月dd日",
"MM/dd/yyyy HH:mm:ss",
"MM-dd-yyyy HH:mm:ss",
"dd/MM/yyyy HH:mm:ss",
"dd-MM-yyyy HH:mm:ss"
);
/**
* 时间戳转化成时间格式
*
@ -162,6 +182,26 @@ public class DateUtils {
return DateUtil.format(new Date(), "yyyy-MM-dd");
}
/**
* 获取当天的开始时间00:00:00
* @return
*/
public static long getStartOfDayTimestamp() {
LocalDateTime startOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
ZonedDateTime zonedDateTime = startOfDay.atZone(ZoneId.systemDefault());
return zonedDateTime.toInstant().getEpochSecond();
}
/**
* 获取当天的结束时间23:59:59
* @return
*/
public static long getEndOfDayTimestamp() {
LocalDateTime endOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
ZonedDateTime zonedDateTime = endOfDay.atZone(ZoneId.systemDefault());
return zonedDateTime.toInstant().getEpochSecond();
}
/**
* 获取昨天的格式化时间
*
@ -172,5 +212,91 @@ public class DateUtils {
return DateUtil.format(new Date(timestamp), "yyyy-MM-dd");
}
/**
* 格式化时间戳
*/
public static String formatTimestamp(long timestamp) {
Instant instant = Instant.ofEpochSecond(timestamp);
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分");
return dateTime.format(formatter);
}
/**
* 解析时间字符串
*/
public static long parseTimeString(String timeStr) {
try {
// 先尝试作为时间戳解析
return Long.parseLong(timeStr);
} catch (NumberFormatException e) {
// 作为日期字符串解析
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse(timeStr, formatter);
return dateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
}
}
/**
* 将时间戳转为无时分秒的字符串
* @return
*/
public static String timestampToNoHMS(long timestamp) {
return DateUtil.format(new Date(timestamp * 1000), "yyyy-MM-dd");
}
/**
* 将不同类型的时间字符串转换为时间戳秒级
*/
public static Long strToTimestamp(String timeStr) {
if (timeStr == null || timeStr.trim().isEmpty()) {
return 0L;
}
String cleanedStr = timeStr.trim();
// 优先尝试解析ISO 8601格式包含时区信息
try {
Instant instant = Instant.parse(cleanedStr);
return instant.getEpochSecond();
} catch (DateTimeParseException e) {
// 继续尝试其他格式
}
// 尝试解析数字时间戳放在格式解析之前更合理
try {
long timestamp = Long.parseLong(cleanedStr);
if (timestamp > 1000000000000L) { // 毫秒级
return timestamp / 1000;
}
return timestamp;
} catch (NumberFormatException e) {
// 继续尝试日期格式
}
// 解析其他日期格式
for (String pattern : datePatterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
if (pattern.contains("HH:mm:ss") || pattern.contains("HH时mm分ss秒")) {
// 对于没有时区的时间明确指定时区策略
LocalDateTime dateTime = LocalDateTime.parse(cleanedStr, formatter);
// 使用系统时区但建议明确文档说明
return dateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
} else {
// 只有日期的使用当天的开始时间
LocalDate date = LocalDate.parse(cleanedStr, formatter);
return date.atStartOfDay(ZoneId.systemDefault()).toEpochSecond();
}
} catch (DateTimeParseException e) {
continue;
}
}
return 0L;
}
}

View File

@ -2,6 +2,9 @@ package com.niu.core.common.utils.image;
import com.niu.core.common.component.context.WebAppEnvs;
import com.niu.core.common.utils.CommonUtils;
import com.niu.core.common.utils.StringUtils;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import javax.imageio.ImageIO;
import java.awt.*;
@ -11,9 +14,12 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
*/
@Log4j2
public class ImageUtils {
/**
@ -82,4 +88,70 @@ public class ImageUtils {
}
}
}
/**
* 获取图片尺寸
* @param imagePath 图片路径或URL
* @return 包含宽度和高度的Map
*/
public static ImageSize getImageSize(String imagePath) {
ImageSize result = new ImageSize();
Map<String, Integer> size = new HashMap<>();
if (StringUtils.isEmpty(imagePath)) {
return result;
}
try {
BufferedImage bufferedImage = null;
if (CommonUtils.isUrl(imagePath)) {
// 处理网络图片
URL url = new URL(imagePath);
bufferedImage = ImageIO.read(url);
} else {
// 处理本地图片
File file = new File(WebAppEnvs.get().webRootDownResource, imagePath);
if (file.exists() && file.isFile()) {
bufferedImage = ImageIO.read(file);
}
}
if (bufferedImage != null) {
result.setWidth(bufferedImage.getWidth());
result.setHeight(bufferedImage.getHeight());
}
} catch (IOException e) {
log.error("获取图片尺寸失败:{}", e.getMessage(), e);
}
return result;
}
/**
* 获取第一张图片的尺寸处理逗号分隔的图片字符串
* @param images 逗号分隔的图片字符串
* @return 包含宽度和高度的Map
*/
public static ImageSize getFirstImageSize(String images) {
ImageSize imageSize = new ImageSize();
if (StringUtils.isEmpty(images)) {
return imageSize;
}
String[] imageArray = images.split(",");
if (imageArray.length == 0) {
return imageSize;
}
String firstImage = imageArray[0].trim();
return getImageSize(firstImage);
}
@Data
public static class ImageSize {
private Integer width = 0;
private Integer height = 0;
}
}

View File

@ -2,12 +2,10 @@ package com.niu.core.common.utils.json;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* JSON转换器
@ -66,4 +64,154 @@ public final class JsonConverterUtils {
return itemList;
}
/**
* 将JSONObject中的键从驼峰格式转换为下划线格式递归处理
* 支持处理嵌套的JSON字符串将字符串类型的JSON解析为JSONObject或JSONArray后再递归处理
*
* @param obj 待转换的JSONObject
* @return 转换后的新JSONObject
*/
public static JSONObject convertCamelToUnderline(Object obj) {
JSONObject result = new JSONObject();
if (obj == null || StrUtil.isEmpty(obj.toString())) {
return result;
}
JSONObject json = new JSONObject();
if (obj instanceof JSONObject) {
json = (JSONObject) obj;
} else if (obj instanceof Map) {
json = new JSONObject((Map) obj);
} else if (obj instanceof String) {
json = JSONUtil.parseObj(obj.toString());
}
Iterator<String> keys = json.keySet().iterator();
while (keys.hasNext()) {
String originalKey = keys.next();
Object value = json.get(originalKey);
// 转换键名驼峰转下划线
String newKey = StrUtil.toUnderlineCase(originalKey);
// 检查value是否为字符串类型的JSON
if (value instanceof String) {
String strValue = (String) value;
if (JSONUtil.isTypeJSON(strValue)) {
try {
// 尝试解析为JSONObject或JSONArray
Object parsed = JSONUtil.parse(strValue);
value = parsed; // 替换value为解析后的JSON对象
} catch (Exception e) {
// 解析失败保持原样
}
}
}
// 递归处理嵌套的JSONObject和JSONArray
if (value instanceof JSONObject) {
JSONObject nestedJson = (JSONObject) value;
result.put(newKey, convertCamelToUnderline(nestedJson));
} else if (value instanceof JSONArray) {
JSONArray originalArray = (JSONArray) value;
JSONArray newArray = new JSONArray();
for (Object item : originalArray) {
if (item instanceof JSONObject) {
newArray.add(convertCamelToUnderline((JSONObject) item));
} else if (item instanceof JSONArray) {
newArray.add(convertCamelToUnderlineForArray(item));
} else {
// 检查基本类型是否可能是JSON字符串
if (item instanceof String) {
String strItem = (String) item;
if (JSONUtil.isTypeJSON(strItem)) {
try {
Object parsed = JSONUtil.parse(strItem);
if (parsed instanceof JSONObject) {
newArray.add(convertCamelToUnderline(parsed));
} else if (parsed instanceof JSONArray) {
newArray.add(convertCamelToUnderlineForArray(parsed));
} else {
newArray.add(item);
}
} catch (Exception e) {
newArray.add(item);
}
} else {
newArray.add(item);
}
} else {
newArray.add(item);
}
}
}
result.put(newKey, newArray);
} else {
result.put(newKey, value);
}
}
return result;
}
/**
* 将JSONArray中的键从驼峰格式转换为下划线格式递归处理
* 支持处理嵌套的JSON字符串将字符串类型的JSON解析为JSONObject或JSONArray后再递归处理
*
* @param obj 待转换的对象可以是JSONArrayList数组或JSON字符串
* @return 转换后的新JSONArray
*/
public static JSONArray convertCamelToUnderlineForArray(Object obj) {
JSONArray result = new JSONArray();
if (obj == null || StrUtil.isEmpty(obj.toString())) {
return result;
}
JSONArray jsonArray = new JSONArray();
if (obj instanceof JSONArray) {
jsonArray = (JSONArray) obj;
} else if (obj instanceof List) {
jsonArray = new JSONArray((List) obj);
} else if (obj instanceof String) {
jsonArray = JSONUtil.parseArray(obj.toString());
}
for (Object item : jsonArray) {
if (item instanceof JSONObject) {
// 处理JSONObject元素
result.add(convertCamelToUnderline(item));
} else if (item instanceof JSONArray) {
// 处理嵌套的JSONArray
result.add(convertCamelToUnderlineForArray(item));
} else if (item instanceof Map) {
// 处理Map元素
JSONObject jsonObj = new JSONObject((Map) item);
result.add(convertCamelToUnderline(jsonObj));
} else if (item instanceof String) {
// 检查字符串是否是JSON对象或数组
String strValue = (String) item;
if (JSONUtil.isTypeJSON(strValue)) {
try {
Object parsed = JSONUtil.parse(strValue);
if (parsed instanceof JSONObject) {
result.add(convertCamelToUnderline(parsed));
} else if (parsed instanceof JSONArray) {
result.add(convertCamelToUnderlineForArray(parsed));
} else {
result.add(item); // 普通字符串
}
} catch (Exception e) {
result.add(item); // 普通字符串
}
} else {
result.add(item); // 普通字符串
}
} else {
// 基本类型直接添加
result.add(item);
}
}
return result;
}
}

View File

@ -4,11 +4,15 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.github.yulichang.query.MPJQueryWrapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.niu.core.common.utils.date.DateUtils;
import org.apache.poi.ss.formula.functions.T;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.List;
import java.util.function.Function;
public class QueryMapperUtils {
@ -36,38 +40,75 @@ public class QueryMapperUtils {
return queryWrapper;
}
public static LambdaQueryWrapper buildByTime(LambdaQueryWrapper queryWrapper, String fieldName, String[] stringTimes) {
Long startTime = (stringTimes[0] == null) ? 0 : DateUtils.StringToTimestamp(stringTimes[0]);
Long endTime = (stringTimes.length == 1) ? 0 : DateUtils.StringToTimestamp(stringTimes[1]);
if (startTime == 0 && endTime == 0) {
public static QueryWrapper buildByTime(QueryWrapper queryWrapper, String fieldName, List<String> stringTimes) {
Long startTime = (stringTimes.get(0) == null) ? 0 : DateUtils.StringToTimestamp(stringTimes.get(0));
Long endTime = (stringTimes.size() == 1) ? 0 : DateUtils.StringToTimestamp(stringTimes.get(1));
if(startTime == 0 && endTime == 0){
return queryWrapper;
}
if (startTime > 0 && endTime > 0) {
queryWrapper.between(getField(fieldName), startTime, endTime);
queryWrapper.between(fieldName, startTime, endTime);
} else if (startTime > 0 && endTime == 0) {
queryWrapper.ge(getField(fieldName), startTime);
queryWrapper.ge(fieldName, startTime);
} else if (startTime == 0 && endTime > 0) {
queryWrapper.le(getField(fieldName), endTime);
queryWrapper.le(fieldName, startTime);
}
return queryWrapper;
}
private static <T> Function<T, Object> getField(String fieldName) {
return t -> {
try {
Method method = t.getClass().getMethod("get" + capitalize(fieldName));
return method.invoke(t);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid field name: " + fieldName, e);
}
};
public static <T> LambdaQueryWrapper<T> buildByTime(LambdaQueryWrapper queryWrapper, SFunction<T, ?> column, List<String> stringTimes) {
Long startTime = (stringTimes.get(0) == null) ? 0 : DateUtils.StringToTimestamp(stringTimes.get(0));
Long endTime = (stringTimes.size() == 1) ? 0 : DateUtils.StringToTimestamp(stringTimes.get(1));
if(startTime == 0 && endTime == 0){
return queryWrapper;
}
if (startTime > 0 && endTime > 0) {
queryWrapper.between(column, startTime, endTime);
} else if (startTime > 0 && endTime == 0) {
queryWrapper.ge(column, startTime);
} else if (startTime == 0 && endTime > 0) {
queryWrapper.le(column, startTime);
}
return queryWrapper;
}
private static String capitalize(String s) {
if (s == null || s.length() == 0) {
return s;
public static <T> LambdaQueryWrapper<T> buildByTime(LambdaQueryWrapper<T> queryWrapper, SFunction<T, ?> column, String[] stringTimes) {
Long startTime = (stringTimes[0] == null) ? 0 : DateUtils.StringToTimestamp(stringTimes[0]);
Long endTime = (stringTimes.length == 1) ? 0 : DateUtils.StringToTimestamp(stringTimes[1]);
if (startTime == 0 && endTime == 0) {
return queryWrapper;
}
return s.substring(0, 1).toUpperCase() + s.substring(1);
if (startTime > 0 && endTime > 0) {
queryWrapper.between(column, startTime, endTime);
} else if (startTime > 0 && endTime == 0) {
queryWrapper.ge(column, startTime);
} else if (startTime == 0 && endTime > 0) {
queryWrapper.le(column, endTime);
}
return queryWrapper;
}
public static <T> MPJLambdaWrapper<T> buildByTime(MPJLambdaWrapper<T> queryWrapper, SFunction<T, ?> column, String[] stringTimes) {
Long startTime = (stringTimes[0] == null) ? 0 : DateUtils.StringToTimestamp(stringTimes[0]);
Long endTime = (stringTimes.length == 1) ? 0 : DateUtils.StringToTimestamp(stringTimes[1]);
if (startTime == 0 && endTime == 0) {
return queryWrapper;
}
if (startTime > 0 && endTime > 0) {
queryWrapper.between(column, startTime, endTime);
} else if (startTime > 0 && endTime == 0) {
queryWrapper.ge(column, startTime);
} else if (startTime == 0 && endTime > 0) {
queryWrapper.le(column, endTime);
}
return queryWrapper;
}
/**

View File

@ -0,0 +1,165 @@
package com.niu.core.controller.adminapi.channel;
import com.niu.core.common.domain.PageParam;
import com.niu.core.common.domain.PageResult;
import com.niu.core.common.domain.Result;
import com.niu.core.common.enums.HttpEnum;
import com.niu.core.enums.channel.AppDict;
import com.niu.core.service.admin.channel.IAdminAppService;
import com.niu.core.service.admin.channel.param.AppVersionAddParam;
import com.niu.core.service.admin.channel.param.AppVersionPageParam;
import com.niu.core.service.admin.channel.vo.AppVersionInfoVo;
import com.niu.core.service.admin.channel.vo.AppVersionListVo;
import com.niu.core.service.core.channel.ICoreAppCloudService;
import com.niu.core.service.core.channel.param.GenerateSignCertParam;
import com.niu.core.service.core.channel.param.SetAppParam;
import com.niu.core.service.core.channel.vo.AppCompileLogVo;
import com.niu.core.service.core.channel.vo.AppConfigVo;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jdk.jfr.Description;
import java.util.Map;
/**
* APP管理控制器
*/
@RestController
@RequestMapping("/adminapi/channel/app")
@Description("APP管理")
public class AppController {
@Resource
private IAdminAppService adminAppService;
@Resource
private ICoreAppCloudService coreAppCloudService;
/**
* 获取APP配置
* @return Result<AppConfigVo>
*/
@GetMapping("/config")
@Description("获取APP配置")
public Result<AppConfigVo> getAppConfig() {
AppConfigVo config = adminAppService.getAppConfig();
return Result.success(config);
}
/**
* 设置APP配置
* @param param APP配置参数
* @return Result<Boolean>
*/
@PutMapping("/config")
@Description("设置APP配置")
public Result setAppConfig(@Valid @RequestBody SetAppParam param) {
adminAppService.setAppConfig(param);
return Result.success();
}
/**
* 获取版本列表
* @param param 分页查询参数
* @return Result<PageResult<AppVersionListVo>>
*/
@GetMapping("/version")
@Description("获取版本列表")
public Result<PageResult<AppVersionListVo>> getVersionList(PageParam pageParam, AppVersionPageParam param) {
PageResult<AppVersionListVo> list = adminAppService.getVersionPage(pageParam, param);
return Result.success(list);
}
/**
* 获取版本详情
* @param id 版本ID
* @return Result<AppVersionInfoVo>
*/
@GetMapping("/version/{id}")
@Description("获取版本详情")
public Result<AppVersionInfoVo> getVersionInfo(@PathVariable("id") Integer id) {
AppVersionInfoVo info = adminAppService.getVersionInfo(id);
return Result.success(info);
}
/**
* 添加版本
* @param param 添加版本参数
* @return Result<Boolean>
*/
@PostMapping("/version")
@Description("添加版本")
public Result<Boolean> addVersion(@Valid @RequestBody AppVersionAddParam param) {
boolean result = adminAppService.addVersion(param);
return Result.success(result);
}
/**
* 编辑版本
* @param param 编辑版本参数
* @return Result<Boolean>
*/
@PutMapping("/version/{id}")
@Description("编辑版本")
public Result<Boolean> editVersion(@PathVariable("id") Integer id, @Valid @RequestBody AppVersionAddParam param) {
boolean result = adminAppService.editVersion(id, param);
return Result.success(result);
}
/**
* 删除版本
* @param id 版本ID
* @return Result<Boolean>
*/
@DeleteMapping("/version/{id}")
@Description("删除版本")
public Result delVersion(@PathVariable("id") Integer id) {
adminAppService.delVersion(id);
return Result.success();
}
/**
* 获取APP平台信息
* @return Result<Map<String, Object>>
*/
@GetMapping("/platfrom")
@Description("获取APP平台信息")
public Result<Map<String, Object>> getAppPlatform() {
return Result.success(AppDict.getPlatformMap());
}
/**
* 获取构建日志
* @param key 版本key
* @return Result<String>
*/
@GetMapping("/build/log/{key}")
@Description("获取构建日志")
public Result<AppCompileLogVo> getBuildLog(@PathVariable("key") String key) {
return Result.success(adminAppService.getBuildLog(key));
}
/**
* 发布版本
* @param id 版本ID
* @return Result<Boolean>
*/
@PutMapping("/version/{id}/release")
@Description("发布版本")
public Result<Boolean> release(@PathVariable("id") Integer id) {
boolean result = adminAppService.release(id);
return Result.success(result);
}
/**
* 生成签名证书
* @param param 证书生成参数
* @return Result<Map<String, Object>>
*/
@PostMapping("/generate_sign_cert")
@Description("生成签名证书")
public Result<String> generateSignCert(@RequestBody GenerateSignCertParam param) {
return Result.success(HttpEnum.SUCCESS.getCode(), HttpEnum.SUCCESS.getMsg(), coreAppCloudService.generateSignCert(param));
}
}

View File

@ -232,4 +232,14 @@ public class DiyController {
diyPageService.copy(id.getId());
return Result.success();
}
/**
* 获取自定义链接
* @param pageParam
* @return
*/
@GetMapping("/page_link")
public Result<PageResult<DiyPageListVo>> getPageLink(PageParam pageParam) {
return Result.success(diyPageService.getPageLink(pageParam));
}
}

View File

@ -324,4 +324,13 @@ public class NiuSmsController {
nuiSmsService.templateDelete(username, templateId);
return Result.success();
}
/**
* 模板信息
*/
@Description("模板信息")
@GetMapping("/template/info/{smsType}/{username}")
public Result<?> templateInfo(@PathVariable("smsType") String smsType, @PathVariable("username") String username, @RequestParam("template_key") String templateKey) {
return Result.success(nuiSmsService.templateInfo(username, smsType, templateKey));
}
}

View File

@ -214,4 +214,16 @@ public class SiteController {
CaptchaUtils.check(param.getCaptchaKey(), param.getCaptchaCode());
return Result.success(siteService.siteInit(param.getSiteId()));
}
@GetMapping("/special_menu")
@Description("生成菜单数据")
public Result<?> getSpecialMenuList() {
return Result.success(siteService.getSpecialMenuList());
}
@GetMapping("/showCustomer")
@Description("统一展示 安装的插件 应用 营销工具等。")
public Result<?> showCustomer() {
return Result.success(siteService.showCustomer(true));
}
}

View File

@ -1,6 +1,8 @@
package com.niu.core.controller.adminapi.sys;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.niu.core.common.domain.PageParam;
import com.niu.core.common.domain.PageResult;
import com.niu.core.common.domain.Result;
@ -8,10 +10,13 @@ import com.niu.core.enums.sys.ExportEnum;
import com.niu.core.service.admin.sys.ISysExportService;
import com.niu.core.service.admin.sys.param.SysExportSearchParam;
import com.niu.core.service.admin.sys.vo.SysExportListVo;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
@ -60,8 +65,17 @@ public class SysExportController {
* 报表导出数据检查
*/
@GetMapping("/export/check/{type}")
public Result<Object> check(@Validated @PathVariable("type") String type, @RequestParam Map<String, Object> objectMap) {
Boolean status = sysExportService.checkExportData(type, objectMap);
public Result<Object> check(@Validated @PathVariable("type") String type, @RequestParam MultiValueMap<String,Object> objectMap) {
JSONObject jsonObject = new JSONObject();
objectMap.forEach((key, valueList) -> {
key = key.replace("[]", "");
if (valueList.size() == 1) {
jsonObject.set(key, valueList.get(0));
} else {
jsonObject.set(key, valueList);
}
});
Boolean status = sysExportService.checkExportData(type, jsonObject);
String msg = status ? "" : "暂无可导出数据";
return Result.success(msg, status);
}
@ -70,7 +84,16 @@ public class SysExportController {
* 报表导出
*/
@GetMapping("/export/{type}")
public Result<Object> export(@Validated @PathVariable("type") String type, @RequestParam Map<String, Object> jsonObject) {
public Result<Object> export(@Validated @PathVariable("type") String type, @RequestParam MultiValueMap<String, Object> objectMap) {
JSONObject jsonObject = new JSONObject();
objectMap.forEach((key, valueList) -> {
key = key.replace("[]", "");
if (valueList.size() == 1) {
jsonObject.set(key, valueList.get(0));
} else {
jsonObject.set(key, valueList);
}
});
sysExportService.exportData(type, jsonObject);
return Result.success();
}

View File

@ -100,6 +100,7 @@ public class SysMenuController {
installSystemService.install();
return Result.success();
}
/**
* 菜单树
*
@ -126,4 +127,8 @@ public class SysMenuController {
return Result.success(sysMenuService.getAddonMenu(appKey, "all", 1, 0));
}
@GetMapping("/menu/system_menu")
public Result getSystem() {
return Result.success(sysMenuService.getSystemMenu("all", 1, 0));
}
}

View File

@ -0,0 +1,45 @@
package com.niu.core.controller.api.channel;
import cn.dev33.satoken.annotation.SaIgnore;
import com.niu.core.common.domain.Result;
import com.niu.core.common.utils.system.CaptchaUtils;
import com.niu.core.enums.member.MemberLoginTypeEnum;
import com.niu.core.service.admin.member.vo.LoginConfigVo;
import com.niu.core.service.api.channel.IAppService;
import com.niu.core.service.api.channel.param.GetNewVersionParam;
import com.niu.core.service.api.channel.vo.NewVersionVo;
import com.niu.core.service.api.login.ILoginService;
import com.niu.core.service.api.login.IRegisterService;
import com.niu.core.service.api.login.param.AccountLoginParam;
import com.niu.core.service.api.login.param.MobileLoginParam;
import com.niu.core.service.api.login.param.ResetPasswordParam;
import com.niu.core.service.api.login.param.SendMobileCodeParam;
import com.niu.core.service.api.login.vo.LoginVo;
import com.niu.core.service.api.wechat.param.AuthRegisterParam;
import com.niu.core.service.api.wechat.param.WechatAuthParam;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class AppController {
@Resource
private IAppService appService;
@PostMapping("/wxapp/login")
public Result<LoginVo> wechatLogin(@Validated @RequestBody WechatAuthParam param){
return Result.success(appService.wechatLogin(param));
}
/**
* 获取新的版本
* @param param
* @return
*/
@GetMapping("/app/newversion")
public Result<NewVersionVo> getNewVersion(GetNewVersionParam param){
return Result.success(appService.getNewVersion(param));
}
}

View File

@ -2,18 +2,29 @@ package com.niu.core.controller.api.login;
import cn.dev33.satoken.annotation.SaIgnore;
import com.niu.core.common.domain.Result;
import com.niu.core.common.utils.RequestUtils;
import com.niu.core.common.utils.system.CaptchaUtils;
import com.niu.core.enums.member.MemberLoginTypeEnum;
import com.niu.core.service.admin.member.vo.LoginConfigVo;
import com.niu.core.service.api.channel.IAppService;
import com.niu.core.service.api.login.ILoginService;
import com.niu.core.service.api.login.IRegisterService;
import com.niu.core.service.api.login.param.*;
import com.niu.core.service.api.login.vo.LoginVo;
import com.niu.core.service.api.weapp.IWeappService;
import com.niu.core.service.api.wechat.IWechatService;
import com.niu.core.service.api.wechat.param.AuthRegisterParam;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.io.IOException;
@RestController
@RequestMapping("/api")
public class LoginController {
@ -24,6 +35,15 @@ public class LoginController {
@Resource
ILoginService loginService;
@Resource
IWechatService wechatService;
@Resource
IWeappService weappService;
@Resource
IAppService appService;
/**
* 账号登录
*
@ -94,4 +114,19 @@ public class LoginController {
public Result logout() {
return Result.success();
}
@PostMapping("/bind")
public Result<LoginVo> bind(@RequestBody AuthRegisterParam param) {
switch (RequestUtils.channel()) {
case "pc":
case "wechat":
return Result.success(wechatService.register(param));
case "weapp":
return Result.success(weappService.register(param));
case "app":
return Result.success(appService.register(param));
}
return Result.success(new LoginVo());
}
}

View File

@ -7,6 +7,7 @@ import com.niu.core.common.utils.RequestUtils;
import com.niu.core.event.sys.InitWapEvent;
import com.niu.core.mapper.member.MemberMapper;
import com.niu.core.service.admin.diy.IDiyThemeService;
import com.niu.core.service.api.channel.IAppService;
import com.niu.core.service.api.diy.IDiyService;
import com.niu.core.service.api.diy.param.DiyTabbarListParam;
import com.niu.core.service.api.login.ILoginService;
@ -50,6 +51,9 @@ public class SysConfigController {
@Resource
private MemberMapper memberMapper;
@Resource
private IAppService appService;
/**
* 版权信息
*
@ -100,7 +104,7 @@ public class SysConfigController {
* @return
*/
@GetMapping("/init")
public Result init(@RequestParam(name = "url", defaultValue = "") String url) {
public Result init(@RequestParam(name = "url", defaultValue = "") String url, @RequestParam(value = "openid", defaultValue = "") String openid) {
SysInitVo vo = new SysInitVo();
vo.setTabbarList(diyService.tabbarList(new DiyTabbarListParam()));
@ -109,6 +113,7 @@ public class SysConfigController {
vo.setSiteInfo(coreSiteService.getSiteCache(RequestUtils.siteId()));
vo.setMemberLevel(memberLevelService.list(new MemberLevelParam()));
vo.setThemeList(diyThemeService.getDiyTheme());
vo.setAppConfig(appService.getAppConfig());
if (ObjectUtil.isEmpty(vo.getSiteInfo().getSiteId())) {
InitWapEvent event = new InitWapEvent();
@ -116,6 +121,10 @@ public class SysConfigController {
EventAndSubscribeOfPublisher.publishAll(event);
}
Map<String, Integer> checkRes = sysConfigService.getMemberMobileExist(openid, RequestUtils.channel());
vo.setMemberExist(checkRes.getOrDefault("member_exist", 0));
vo.setMemberMobileExist(checkRes.getOrDefault("member_mobile_exist", 0));
return Result.success(vo);
}

View File

@ -28,6 +28,13 @@ public class UploadController {
return Result.success(uploadService.image(param));
}
@PostMapping("/video")
public Result video(@RequestParam("file") MultipartFile file) {
AttachmentUploadParam param = new AttachmentUploadParam();
param.setFile(file);
return Result.success(uploadService.video(param));
}
@PostMapping("/image/fetch")
public Result imageFetch() {
return Result.success();

View File

@ -59,6 +59,8 @@ public class Member implements Serializable {
/** 微信unionid */
private String wxUnionid;
private String wxappOpenid;
/** 支付宝账户id */
private String aliOpenid;
@ -174,6 +176,9 @@ public class Member implements Serializable {
@TableField(exist = false)
private String memberLevelName;
@TableField(exist = false)
private String headimgSmall;
private String idCard;
private String remark;

View File

@ -75,5 +75,6 @@ public class Pay implements Serializable {
/** 失败原因 */
private String failReason;
private int fromMainId;
}

View File

@ -0,0 +1,105 @@
package com.niu.core.entity.sys;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* app版本管理
*/
@Data
public class AppVersion implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 站点ID
*/
@TableField("site_id")
private Integer siteId;
/**
* 版本号
*/
@TableField("version_code")
private String versionCode;
/**
* 版本名称
*/
@TableField("version_name")
private String versionName;
/**
* 版本描述
*/
@TableField("version_desc")
private String versionDesc;
/**
* 平台类型 android/ios
*/
@TableField("platform")
private String platform;
/**
* 是否强制更新
*/
@TableField("is_forced_upgrade")
private Integer isForcedUpgrade;
/**
* 安装包路径
*/
@TableField("package_path")
private String packagePath;
/**
* 任务key
*/
@TableField("task_key")
private String taskKey;
/**
* 发布时间
*/
@TableField("release_time")
private Long releaseTime;
/**
* 状态 upload_success/create_fail/published/creating
*/
@TableField("status")
private String status;
/**
* 失败原因
*/
@TableField("fail_reason")
private String failReason;
/**
* 更新类型
*/
@TableField("upgrade_type")
private String upgradeType;
/**
* 创建时间
*/
@TableField("create_time")
private Long createTime;
/**
* 更新时间
*/
@TableField("update_time")
private Long updateTime;
}

View File

@ -0,0 +1,92 @@
package com.niu.core.enums.channel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
/**
* app相关枚举类
*/
public class AppDict {
/**
* 平台类型枚举
*/
@Getter
@AllArgsConstructor
public enum PlatformEnum {
ANDROID("android", "Android");
// IOS("ios", "iOS");
private final String value;
private final String desc;
}
/**
* 版本状态枚举
*/
@Getter
@AllArgsConstructor
public enum StatusEnum {
STATUS_UPLOAD_SUCCESS("upload_success", "上传成功"),
STATUS_CREATE_FAIL("create_fail", "创建失败"),
STATUS_PUBLISHED("published", "发布成功"),
STATUS_CREATING("creating", "创建中");
private final String value;
private final String desc;
}
/**
* 获取平台类型map
*/
public static Map<String, Object> getPlatformMap() {
Map<String, Object> map = new HashMap<>();
for (PlatformEnum platform : PlatformEnum.values()) {
map.put(platform.getValue().toString(), platform.getDesc());
}
return map;
}
/**
* 根据值获取平台类型名称
*/
public static String getPlatformName(String value) {
if (value == null) {
return "";
}
for (PlatformEnum platform : PlatformEnum.values()) {
if (platform.getValue().equals(value)) {
return platform.getDesc();
}
}
return "";
}
/**
* 获取状态map
*/
public static Map<String, Object> getStatusMap() {
Map<String, Object> map = new HashMap<>();
for (StatusEnum status : StatusEnum.values()) {
map.put(status.getValue().toString(), status.getDesc());
}
return map;
}
/**
* 根据值获取状态名称
*/
public static String getStatusName(String value) {
if (value == null) {
return "";
}
for (StatusEnum status : StatusEnum.values()) {
if (status.getValue().equals(value)) {
return status.getDesc();
}
}
return "";
}
}

View File

@ -0,0 +1,209 @@
package com.niu.core.enums.common;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public enum CommonActiveEnum {
IMPULSE_BUY("impulse_buy", "顺手买", "", "#FF7700"),
GIFTCARD("gift_card", "礼品卡", "", "#F00000"),
DISCOUNT("discount", "限时折扣", "", "#FFA322"),
EXCHANGE("exchange", "积分商城", "", "#00C441"),
MANJIANSONG("manjiansong", "满减送", "满减", "#249DE9"),
NEWCOMER_DISCOUNT("newcomer_discount", "新人专享", "", "#BB27FF"),
SECKILL("seckill", "秒杀", "", "#F606CA"),
PINTUAN("pintuan", "拼团", "", "#FF1C77");
private final String code;
private final String shortName;
private final String name;
private final String bgColor;
CommonActiveEnum(String code, String name, String shortName, String bgColor) {
this.code = code;
this.name = name;
this.shortName = shortName;
this.bgColor = bgColor;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public String getShortName() {
return shortName;
}
public String getBgColor() {
return bgColor;
}
public static Map<String, Map<String, Object>> getActiveShort() {
Map<String, Map<String, Object>> data = new HashMap<>();
for (CommonActiveEnum active : CommonActiveEnum.values()) {
Map<String, Object> item = new HashMap<>();
item.put("name", active.getShortName());
item.put("active_name", active.getName());
item.put("bg_color", active.getBgColor());
data.put(active.getCode(), item);
}
return data;
}
public static Map<String, Object> getActiveShort(String code) {
for (CommonActiveEnum active : CommonActiveEnum.values()) {
if (active.getCode().equals(code)) {
Map<String, Object> item = new HashMap<>();
item.put("name", active.getShortName());
item.put("active_name", active.getName());
item.put("bg_color", active.getBgColor());
return item;
}
}
return null;
}
// 活动背景特效
public static final Map<String, Map<String, Object>> ACTIVE_BG_EFFECT = Map.of(
"normal", Map.of(
"title", "无特效",
"child_list", List.of()
),
"promotion", Map.of(
"title", "促销",
"child_list", Map.of(
"red_packet", "红包",
"gift_box", "礼盒",
"big_wing", "大元宝"
)
),
"festival", Map.of(
"title", "节庆",
"child_list", Map.of(
"blessing", "花瓣",
"firecrackers", "爆竹",
"i_love_you", "我爱你",
"kongmin_light", "孔明灯"
)
)
);
// 背景音乐模板
public static final Map<String, Map<String, Object>> ACTIVE_BG_MUSIC = Map.of(
"popular", Map.of(
"title", "流行",
"child_list", Map.of(
"quiet_guitar_soundtrack", Map.of(
"title", "安静吉他配乐",
"url", "static/resource/audio/quiet_guitar_soundtrack.mp3"
),
"city_of_hope", Map.of(
"title", "City Of Hope",
"url", "static/resource/audio/city_of_hope.mp3"
),
"intro", Map.of(
"title", "Intro",
"url", "static/resource/audio/intro.mp3"
),
"dreams_exploring_world", Map.of(
"title", "探索世界的梦想",
"url", "static/resource/audio/dreams_exploring_world.mp3"
)
)
),
"holiday", Map.of(
"title", "节日",
"child_list", Map.of(
"spring_festival_overture", Map.of(
"title", "过年春节序曲",
"url", "static/resource/audio/spring_festival_overture.mp3"
)
)
),
"classical", Map.of(
"title", "古典",
"child_list", Map.of(
"elegant_chinese_style", Map.of(
"title", "古典高雅中国风",
"url", "static/resource/audio/elegant_chinese_style.mp3"
),
"city_hope_soundtrack", Map.of(
"title", "希望之城配乐",
"url", "static/resource/audio/city_hope_soundtrack.mp3"
),
"ink_painting_chinese_style", Map.of(
"title", "墨水画中国风",
"url", "static/resource/audio/ink_painting_chinese_style.mp3"
)
)
),
"epic", Map.of(
"title", "史诗",
"child_list", Map.of(
"destiny_honor", Map.of(
"title", "Destiny & Honor",
"url", "static/resource/audio/destiny_honor.mp3"
),
"electric_romeo", Map.of(
"title", "Electric Romeo",
"url", "static/resource/audio/electric_romeo.mp3"
)
)
),
"piano", Map.of(
"title", "钢琴",
"child_list", Map.of(
"tapeworm_compilation", Map.of(
"title", "Tapeworm Compilation I",
"url", "static/resource/audio/tapeworm_compilation.mp3"
)
)
),
"happy", Map.of(
"title", "欢快",
"child_list", Map.of(
"laugh_smile_no_vox", Map.of(
"title", "A Laugh And A Smile No Vox",
"url", "static/resource/audio/laugh_smile_no_vox.mp3"
),
"easy_run_soundtrack", Map.of(
"title", "轻松奔跑配乐",
"url", "static/resource/audio/easy_run_soundtrack.mp3"
),
"theme_happy_no_1", Map.of(
"title", "Theme-Happy No.1",
"url", "static/resource/audio/theme_happy_no_1.mp3"
),
"sunny_jim", Map.of(
"title", "Sunny Jim",
"url", "static/resource/audio/sunny_jim.mp3"
),
"energetic_warm_soundtrack", Map.of(
"title", "活力温暖配乐",
"url", "static/resource/audio/energetic_warm_soundtrack.mp3"
),
"light_soothing_soundtrack", Map.of(
"title", "轻快舒缓配乐",
"url", "static/resource/audio/light_soothing_soundtrack.mp3"
),
"inspirational_bright_music", Map.of(
"title", "励志明亮配乐",
"url", "static/resource/audio/inspirational_bright_music.mp3"
),
"sharp_rhythm_whistling", Map.of(
"title", "节奏鲜明口哨",
"url", "static/resource/audio/sharp_rhythm_whistling.mp3"
),
"cheerful_korean_music", Map.of(
"title", "欢快韩风配乐",
"url", "static/resource/audio/cheerful_korean_music.mp3"
)
)
)
);
}

View File

@ -2,6 +2,7 @@ package com.niu.core.enums.diy;
import cn.hutool.json.JSONObject;
import com.niu.core.common.utils.RequestUtils;
import com.niu.core.common.utils.json.JacksonUtils;
import com.niu.core.common.utils.json.JsonModuleLoader;
public class PagesEnum {
@ -38,7 +39,6 @@ public class PagesEnum {
}
return modePages;
}
return pages;
}
}

View File

@ -73,5 +73,55 @@ public class NoticeEnum {
return noticeMap;
}
// getNotice
public static Map<String, NoticeEnumListVo> getNiuyunNotice(){
JsonModuleLoader jsonModuleLoader = new JsonModuleLoader();
JSONObject notice = jsonModuleLoader.mergeResultElement(RequestUtils.siteId(), "notice/notice.json");
Map<String, NoticeEnumListVo> noticeMap = new HashMap<>();
for (Map.Entry<String, Object> noticeItem : notice.entrySet()) {
NoticeEnumListVo vo = new NoticeEnumListVo();
BeanUtil.copyProperties(noticeItem.getValue(), vo);
noticeMap.put(noticeItem.getKey(), vo);
}
Map<String, String> noticeTypeName = NoticeTypeEnum.getMap();
for (String type : noticeTypeName.keySet()) {
JSONObject defaultConfig = jsonModuleLoader.mergeResultElement("notice/" + type + ".json");
//加载牛云配置
JSONObject niuyunConfig = jsonModuleLoader.mergeResultElement("notice/niuyun/" + type + ".json");
for (Map.Entry<String, NoticeEnumListVo> entry : noticeMap.entrySet()) {
String noticeKey = entry.getKey();
NoticeEnumListVo vo = entry.getValue();
// 优先使用niuyun目录的配置如果存在
if (niuyunConfig != null && niuyunConfig.containsKey(noticeKey)) {
addOrUpdateSupportType(vo, type, niuyunConfig.getJSONObject(noticeKey));
}
// 如果没有niuyun配置使用默认配置
else if (defaultConfig != null && defaultConfig.containsKey(noticeKey)) {
addOrUpdateSupportType(vo, type, defaultConfig.getJSONObject(noticeKey));
}
}
}
return noticeMap;
}
private static void addOrUpdateSupportType(NoticeEnumListVo vo, String type, JSONObject config) {
if (vo.getSupport_type() == null) {
vo.setSupport_type(new ArrayList<>());
}
if (!vo.getSupport_type().contains(type)) {
vo.getSupport_type().add(type);
}
if (vo.getSupport_type_map() == null) {
vo.setSupport_type_map(new HashMap<>());
}
// 设置或更新配置niuyun的配置会覆盖默认配置
vo.getSupport_type_map().put(type, config);
}
}

View File

@ -0,0 +1,41 @@
package com.niu.core.enums.site;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public enum AddonChildMenuEnum {
SYSTEM_TOOL("system_tool", "系统工具", "系统", 97),
MARKETING_TOOL("marketing_tool", "营销工具", "工具", 99),
MARKETING_ACTIVE("marketing_active", "营销活动", "活动", 100),
ADDON_TOOL("addon_tool", "应用插件", "插件", 98);
private final String key;
private final String name;
private final String shortName;
private final Integer sort;
/**
* 获取所有菜单配置
*/
public static List<MenuConfig> getAll() {
return Arrays.stream(values())
.map(item -> new MenuConfig(item.key, item.name, item.shortName, item.sort))
.collect(Collectors.toList());
}
@Data
public static class MenuConfig {
private final String key;
private final String name;
private final String shortName;
private final Integer sort;
}
}

View File

@ -2,70 +2,285 @@ package com.niu.core.enums.site;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.niu.core.common.component.context.SpringContext;
import com.niu.core.common.config.dataization.AddonModuleContext;
import com.niu.core.common.utils.RequestUtils;
import com.niu.core.entity.addon.Addon;
import com.niu.core.entity.diy.DiyPage;
import com.niu.core.entity.sys.SysMenu;
import com.niu.core.mapper.diy.DiyPageMapper;
import com.niu.core.mapper.sys.SysMenuMapper;
import com.niu.core.service.admin.site.ISiteService;
import com.niu.core.service.admin.site.vo.ShowMarketingVo;
import com.niu.core.service.core.addon.ICoreAddonService;
import com.niu.core.service.core.site.ICoreSiteService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
@Log4j2
public class ShowMarketingEnum {
public static Map<String, ShowMarketingVo> getShowMarketingTools() {
Map<String, ShowMarketingVo> result = new HashMap<>();
ShowMarketingVo showMarketingVo = new ShowMarketingVo();
showMarketingVo.setTitle("营销活动");
String path = "loader/sys/show_marketing.json";
Integer siteId = RequestUtils.siteId();
List<String> addonModules = getSiteAddonModules(siteId);
private static final String MARKETING_TITLE = "营销活动";
private static final String ADDON_TITLE = "营销工具";
private static final String TOOL_TITLE = "系统工具";
private static final String MARKETING_JSON_PATH = "loader/sys/show_marketing.json";
private static final String APP_JSON_PATH = "loader/sys/show_app.json";
List<ShowMarketingVo.MarketingTool> marketingTools = parseMarketingToolsFromModules(addonModules, path);
if (marketingTools.isEmpty()) {
showMarketingVo.setList(new ArrayList<>());
} else {
showMarketingVo.setList(marketingTools);
}
private static ApplicationContext context;
result.put("marketing", showMarketingVo);
return result;
@Autowired
private ISiteService siteService;
@Autowired
private SysMenuMapper sysMenuMapper;
@Autowired
private DiyPageMapper diyPageMapper;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
private static List<ShowMarketingVo.MarketingTool> parseMarketingToolsFromModules(List<String> addonModules, String path) {
List<ShowMarketingVo.MarketingTool> marketingTools = new ArrayList<>();
for (String addonModule : addonModules) {
String jsonString = AddonModuleContext.readResourceAsStreamToText(addonModule, path);
if (StrUtil.isNotEmpty(jsonString)) {
jsonString = jsonString.trim();
try {
if (jsonString.startsWith("{")) {
JSONObject jsonObject = JSONUtil.parseObj(jsonString);
List<ShowMarketingVo.MarketingTool> marketing = JSONUtil.toList(jsonObject.getJSONArray("marketing"), ShowMarketingVo.MarketingTool.class);
marketingTools.addAll(marketing);
public static Map<String, ShowMarketingVo> getShowMarketingTools() {
ShowMarketingEnum instance = context.getBean(ShowMarketingEnum.class);
Map<String, ShowMarketingVo> all = instance.getAllAddonAndTool();
return Map.of("marketing", all.get("marketing"), "addon", all.get("addon"));
}
public Map<String, ShowMarketingVo> getAllAddonAndTool() {
//获取所有show_marketing文件
Map<String, ShowMarketingVo> marketingData = getMarketing();
ShowMarketingVo marketingVo = marketingData.get("marketing");
// 获取营销工具列表
List<ShowMarketingVo.MarketingTool> marketingAddon = Optional.ofNullable(marketingData.get("tool"))
.map(ShowMarketingVo::getList)
.orElse(Collections.emptyList());
// 构建基础结构
Map<String, ShowMarketingVo> result = new HashMap<>();
result.put("marketing", marketingVo);
result.put("addon", createShowMarketingVo(ADDON_TITLE, marketingAddon));
result.put("tool", createShowMarketingVo(TOOL_TITLE, new ArrayList<>()));
// 处理应用数据
Integer siteId = RequestUtils.siteId();
List<String> addonModules = getSiteAddonModules(siteId);
Map<String, List<ShowMarketingVo.MarketingTool>> appData = getShowAppData(addonModules, APP_JSON_PATH);
Set<String> existingKeys = new HashSet<>();
if (marketingVo != null && CollectionUtils.isNotEmpty(marketingVo.getList())) {
marketingVo.getList().stream()
.filter(Objects::nonNull)
.map(ShowMarketingVo.MarketingTool::getKey)
.forEach(existingKeys::add);
}
// 填充应用数据到结果
for (Map.Entry<String, List<ShowMarketingVo.MarketingTool>> entry : appData.entrySet()) {
String categoryKey = entry.getKey();
List<ShowMarketingVo.MarketingTool> items = entry.getValue();
ShowMarketingVo categoryVo = result.get(categoryKey);
if (categoryVo != null && CollectionUtils.isNotEmpty(items)) {
for (ShowMarketingVo.MarketingTool item : items) {
if (item != null && !existingKeys.contains(item.getKey())) {
categoryVo.getList().add(item);
existingKeys.add(item.getKey());
}
} catch (Exception e) {
log.error("Error parsing JSON while reading resource from module: {} => path: {}", addonModule, path, e);
}
}
}
return marketingTools;
// 处理站点插件
processSiteAddons(siteId, result, existingKeys);
return result;
}
private void processSiteAddons(Integer siteId, Map<String, ShowMarketingVo> result, Set<String> existingKeys) {
List<Addon> siteAddons = siteService.getSiteAddons();
if (CollectionUtils.isEmpty(siteAddons)) {
return;
}
// 获取插件URL映射
Map<String, String> addonUrls = getAddonUrls(siteAddons);
// 过滤未存在的插件并转换为MarketingTool
List<ShowMarketingVo.MarketingTool> remainingAddons = siteAddons.stream()
.filter(Objects::nonNull)
.filter(addon -> !existingKeys.contains(addon.getKey()))
.map(addon -> {
ShowMarketingVo.MarketingTool tool = new ShowMarketingVo.MarketingTool();
tool.setTitle(addon.getTitle());
tool.setDesc(addon.getDesc());
tool.setIcon(addon.getIcon());
tool.setKey(addon.getKey());
tool.setUrl(StrUtil.isNotEmpty(addonUrls.get(addon.getKey())) ? "/" + addonUrls.get(addon.getKey()) : "");
return tool;
})
.collect(Collectors.toList());
// 添加到addon列表
ShowMarketingVo addonVo = result.get("addon");
if (addonVo != null) {
if (addonVo.getList() == null) {
addonVo.setList(new ArrayList<>());
}
addonVo.getList().addAll(remainingAddons);
// 特殊处理pintuan_diy
if (CollectionUtils.isNotEmpty(addonVo.getList())) {
for (ShowMarketingVo.MarketingTool tool : addonVo.getList()) {
if ("pintuan_diy".equals(tool.getKey())) {
DiyPage diyPage = diyPageMapper.selectOne(new LambdaQueryWrapper<DiyPage>()
.eq(DiyPage::getType, "DIY_PINTUAN_ACTIVITY_GOODS_DETAIL")
.eq(DiyPage::getIsDefault, 1)
.eq(DiyPage::getMode, "diy")
.eq(DiyPage::getSiteId, siteId)
.last("limit 1"));
if (diyPage != null && diyPage.getId() != null) {
tool.setUrl("/site/decorate/edit?id=" + diyPage.getId() + "&back=/site/app/marketing&is_target=true");
}
break;
}
}
}
}
}
private Map<String, String> getAddonUrls(List<Addon> siteAddons) {
List<String> addonKeys = siteAddons.stream()
.map(Addon::getKey)
.collect(Collectors.toList());
List<SysMenu> sysMenus = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenu>()
.select(SysMenu::getRouterPath, SysMenu::getAddon)
.in(SysMenu::getAddon, addonKeys)
.eq(SysMenu::getIsShow, 1)
.eq(SysMenu::getMenuType, 1)
.orderByAsc(SysMenu::getId)
.groupBy(SysMenu::getAddon));
return sysMenus.stream()
.collect(Collectors.toMap(SysMenu::getAddon, SysMenu::getRouterPath, (existing, replacement) -> existing));
}
public static Map<String, ShowMarketingVo> getMarketing() {
Integer siteId = RequestUtils.siteId();
List<String> addonModules = getSiteAddonModules(siteId);
return parseMarketingToolsFromModules(addonModules, MARKETING_JSON_PATH);
}
private static Map<String, List<ShowMarketingVo.MarketingTool>> getShowAppData(List<String> addonModules, String path) {
Map<String, List<ShowMarketingVo.MarketingTool>> appData = new HashMap<>();
appData.put("app", new ArrayList<>());
appData.put("tool", new ArrayList<>());
appData.put("promotion", new ArrayList<>());
for (String addonModule : addonModules) {
try {
String jsonString = AddonModuleContext.readResourceAsStreamToText(addonModule, path);
if (StrUtil.isNotEmpty(jsonString)) {
parseJsonToAppData(jsonString.trim(), appData);
}
} catch (Exception e) {
log.error("获取show_app失败: {} => path: {}", addonModule, path, e);
}
}
return appData;
}
private static void parseJsonToAppData(String jsonString, Map<String, List<ShowMarketingVo.MarketingTool>> appData) {
if (!jsonString.startsWith("{")) return;
try {
JSONObject jsonObject = JSONUtil.parseObj(jsonString);
parseJsonArrayToCategory(jsonObject, "app", appData.get("app"));
parseJsonArrayToCategory(jsonObject, "tool", appData.get("tool"));
parseJsonArrayToCategory(jsonObject, "promotion", appData.get("promotion"));
} catch (Exception e) {
log.error("解析JSON失败: {}", jsonString, e);
}
}
private static void parseJsonArrayToCategory(JSONObject jsonObject, String categoryKey, List<ShowMarketingVo.MarketingTool> targetList) {
if (jsonObject.containsKey(categoryKey)) {
JSONArray jsonArray = jsonObject.getJSONArray(categoryKey);
if (jsonArray != null && !jsonArray.isEmpty()) {
List<ShowMarketingVo.MarketingTool> tools = JSONUtil.toList(jsonArray, ShowMarketingVo.MarketingTool.class);
targetList.addAll(tools);
}
}
}
private static Map<String, ShowMarketingVo> parseMarketingToolsFromModules(List<String> addonModules, String path) {
List<ShowMarketingVo.MarketingTool> appList = new ArrayList<>();
List<ShowMarketingVo.MarketingTool> toolList = new ArrayList<>();
List<ShowMarketingVo.MarketingTool> marketingList = new ArrayList<>();
for (String addonModule : addonModules) {
try {
String jsonString = AddonModuleContext.readResourceAsStreamToText(addonModule, path);
if (StrUtil.isNotEmpty(jsonString)) {
parseMarketingJson(jsonString.trim(), appList, toolList, marketingList);
}
} catch (Exception e) {
log.error("获取marketing.json失败: {} => path: {}", addonModule, path, e);
}
}
Map<String, ShowMarketingVo> result = new HashMap<>();
result.put("app", createShowMarketingVo("应用", appList));
result.put("tool", createShowMarketingVo("工具", toolList));
result.put("marketing", createShowMarketingVo(MARKETING_TITLE, marketingList));
return result;
}
private static void parseMarketingJson(String jsonString,
List<ShowMarketingVo.MarketingTool> appList,
List<ShowMarketingVo.MarketingTool> toolList,
List<ShowMarketingVo.MarketingTool> marketingList) {
if (!jsonString.startsWith("{")) return;
try {
JSONObject jsonObject = JSONUtil.parseObj(jsonString);
parseJsonArrayToCategory(jsonObject, "app", appList);
parseJsonArrayToCategory(jsonObject, "tool", toolList);
parseJsonArrayToCategory(jsonObject, "marketing", marketingList);
} catch (Exception e) {
log.error("解析营销JSON失败: {}", jsonString, e);
}
}
private static ShowMarketingVo createShowMarketingVo(String title, List<ShowMarketingVo.MarketingTool> list) {
ShowMarketingVo vo = new ShowMarketingVo();
vo.setTitle(title);
vo.setList(list != null ? list : new ArrayList<>());
return vo;
}
private static List<String> getSiteAddonModules(Integer siteId) {
List<String> addonModules = new LinkedList<>();
addonModules.add("core");
if (ObjectUtil.isNull(siteId) || siteId == 0) {
addonModules.addAll(SpringContext.bean(ICoreAddonService.class).getInstallAddonList().keySet().stream().toList());
addonModules.addAll(SpringContext.bean(ICoreAddonService.class).getInstallAddonList().keySet());
} else {
addonModules.addAll(SpringContext.bean(ICoreSiteService.class).getAddonKeysBySiteId(siteId));
}
return addonModules;
}
}
}

View File

@ -6,6 +6,7 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ConfigKeyEnum {
PINGTUAN_ORDER_CONFIG("PINTUAN_ORDER_CONFIG"),//拼团配置
NIUCLOUD_CONFIG("NIUCLOUD_CONFIG"),//牛云配置
WECHAT("WECHAT"),//微信公众号
WEAPP("weapp"),//微信小程序
@ -21,7 +22,8 @@ public enum ConfigKeyEnum {
WXOPLATFORM("WXOPLATFORM"), // 微信开放平台
WEAPP_AUTHORIZATION_INFO("weapp_authorization_info"),
WECHAT_AUTHORIZATION_INFO("wechat_authorization_info"),
WECHAT_TRANSFER_SCENE_CONFIG("WECHAT_TRANSFER_SCENE_CONFIG");
WECHAT_TRANSFER_SCENE_CONFIG("WECHAT_TRANSFER_SCENE_CONFIG"),
APP("app");
private final String name;
}

View File

@ -27,6 +27,9 @@ public class CommonEvent extends Event {
*/
private Integer memberId;
/**
* 类型
*/
private String source;
}

View File

@ -32,6 +32,11 @@ public abstract class CommonEventDefiner extends CallbackListener<CommonEventDef
* 关联会员id
*/
private Integer memberId = 0;
/**
* 类型
*/
private String source;
}
/**

View File

@ -0,0 +1,51 @@
package com.niu.core.event.sys;
import com.niu.core.common.component.context.event.Event;
import com.niu.core.common.component.context.event.EventResult;
import com.niu.core.common.component.context.listener.CallbackListener;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
public abstract class ShowCustomerEventDefiner extends CallbackListener<ShowCustomerEventDefiner.ShowCustomerEvent> {
@Data
@Accessors(chain = true)
public static class ShowCustomerEvent extends Event {
}
/**
* 事件结果
*/
@Data
@Accessors(chain = true)
public static class ShowCustomerEventResult extends EventResult {
private Map<String, List<MenuItem>> data;
}
@Data
public static class MenuItem{
private String title;
private String desc;
private String icon;
private String key;
private String url;
public MenuItem(String title, String desc, String icon, String key, String url) {
this.title = title;
this.desc = desc;
this.icon = icon;
this.key = key;
this.url = url;
}
}
// 事件响应
public abstract ShowCustomerEventDefiner.ShowCustomerEventResult handleCallback(ShowCustomerEventDefiner.ShowCustomerEvent event);
}

View File

@ -0,0 +1,35 @@
package com.niu.core.event.sys;
import com.niu.core.common.component.context.event.Event;
import com.niu.core.common.component.context.event.EventResult;
import com.niu.core.common.component.context.listener.CallbackListener;
import com.niu.core.service.admin.verify.vo.VerifyInfoVo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
* 事件定义者 核销操作
*/
public abstract class VerifyInfoEventDefiner extends CallbackListener<VerifyInfoEventDefiner.VerifyInfoEvent> {
/**
* 事件主题 核销创建
*/
@Data
@Accessors(chain = true)
public static class VerifyInfoEvent extends Event {
private VerifyInfoVo data;
}
@Data
@Accessors(chain = true)
public static class VerifyInfoEventResult extends EventResult {
private Map<String, Object> result;
}
// 事件响应
public abstract VerifyInfoEventResult handleCallback(VerifyInfoEvent verifyEvent);
}

View File

@ -165,17 +165,17 @@ public class SiteAddAfterListener extends AbstractListener {
.addon("core")
.build());
SetDiyDataParam setParam = new SetDiyDataParam();
setParam.setSiteId(event.getSiteId());
setParam.setType("index");
setParam.setKey("DIY_INDEX");
setParam.setIsStart(app.size() > 1 ? 1 : 0);
setParam.setMainApp(app);
diyService().setDiyData(setParam);
setParam.setType("member_index");
setParam.setKey("DIY_MEMBER_INDEX");
diyService().setDiyData(setParam);
// SetDiyDataParam setParam = new SetDiyDataParam();
// setParam.setSiteId(event.getSiteId());
// setParam.setType("index");
// setParam.setKey("DIY_INDEX");
// setParam.setIsStart(app.size() > 1 ? 1 : 0);
// setParam.setMainApp(app);
// diyService().setDiyData(setParam);
//
// setParam.setType("member_index");
// setParam.setKey("DIY_MEMBER_INDEX");
// diyService().setDiyData(setParam);
//创建主题色
createThemeColor(siteId);

View File

@ -48,7 +48,7 @@ public class PosterDrawListener extends AbstractListener {
case "text":
String testStr = poster.getStr("value");
if (testStr.isEmpty() && !poster.getStr("relate").isEmpty()) {
testStr = (String) posterData.getOrDefault(poster.getStr("relate"), "");
testStr = posterData.getOrDefault(poster.getStr("relate"), "").toString();
}
int fontStyle = poster.getBool("weight") ? Font.BOLD : Font.PLAIN;

View File

@ -0,0 +1,91 @@
package com.niu.core.listener.sys;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.niu.core.common.annotation.EventCallback;
import com.niu.core.common.domain.BeanJsonSerializer;
import com.niu.core.common.domain.PageResult;
import com.niu.core.common.utils.date.DateUtils;
import com.niu.core.enums.site.AddonChildMenuEnum;
import com.niu.core.event.sys.ExportDataEventDefiner;
import com.niu.core.event.sys.ShowCustomerEventDefiner;
import com.niu.core.service.admin.member.IMemberService;
import com.niu.core.service.admin.member.param.MemberSearchParam;
import com.niu.core.service.admin.member.vo.MemberListVo;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@EventCallback("core")
@Component
public class ShowCustomerListener extends ShowCustomerEventDefiner {
private final Class<? extends MemberListVo> clazz = MemberListVo.class;
@Resource
IMemberService memberService;
@Override
public ShowCustomerEventResult handleCallback(ShowCustomerEvent exportDataEvent) {
ShowCustomerEventResult showCustomerEventResult = new ShowCustomerEventResult();
Map<String, List<MenuItem>> result = new HashMap<>();
// 系统工具菜单组
List<MenuItem> systemToolMenus = new ArrayList<>();
systemToolMenus.add(new MenuItem(
"核销管理",
"管理核销员及核销记录",
"static/resource/images/marketing/verifier.png",
"verify",
"/marketing/verify/index"
));
systemToolMenus.add(new MenuItem(
"万能表单",
"适用于各种应用场景,满足多样化的业务需求",
"static/resource/images/diy_form/icon.png",
"diy_form",
"/diy_form/list"
));
systemToolMenus.add(new MenuItem(
"小票打印",
"支持打印机添加,便捷创建小票打印模板",
"static/resource/images/tool/printer_icon.png",
"printer_management",
"/printer/list"
));
systemToolMenus.add(new MenuItem(
"数据导出",
"展示导出文件,支持删除与下载",
"static/resource/images/tool/export_icon.png",
"setting_export",
"/setting/export"
));
// 营销活动菜单组
List<MenuItem> marketingActiveMenus = new ArrayList<>();
marketingActiveMenus.add(new MenuItem(
"签到管理",
"客户每日签到发放奖励",
"static/resource/images/marketing/sign.png",
"sign",
"/marketing/sign/config"
));
// 工具菜单组
List<MenuItem> markingToolMenus = new ArrayList<>();
// 放入结果Map
result.put(AddonChildMenuEnum.SYSTEM_TOOL.getKey(), systemToolMenus);
result.put(AddonChildMenuEnum.MARKETING_TOOL.getKey(), markingToolMenus);
result.put(AddonChildMenuEnum.MARKETING_ACTIVE.getKey(), marketingActiveMenus);
showCustomerEventResult.setData(result);
return showCustomerEventResult;
}
}

View File

@ -19,7 +19,7 @@ public class WechatQrcodeListener extends GetQrcodeOfChannelDefiner {
@Override
public GetQrcodeOfChannelResult handleCallback(GetQrcodeOfChannelEvent event) {
if (event.getChannel().equals("wechat") || event.getChannel().equals("h5")) {
if (event.getChannel().equals("wechat") || event.getChannel().equals("h5") || event.getChannel().equals("app")) {
try {
String url = event.getUrl();
if (ObjectUtil.isNotEmpty(event.getPage())) url += "/" + event.getPage();

View File

@ -0,0 +1,13 @@
package com.niu.core.mapper.sys;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.niu.core.entity.sys.AppVersion;
import org.apache.ibatis.annotations.Mapper;
/**
* APP版本Mapper接口
*/
@Mapper
public interface AppVersionMapper extends BaseMapper<AppVersion> {
}

View File

@ -40,6 +40,7 @@ import org.springframework.util.Assert;
import jakarta.annotation.Resource;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
@ -357,7 +358,7 @@ public class AddonServiceImpl implements IAddonService {
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(response.bodyBytes());
ZipUtil.unzip(file.getPath(), WebAppEnvs.get().webRootDownAddon, CharsetUtil.CHARSET_UTF_8);
ZipUtil.unzip(file.getPath(), WebAppEnvs.get().webRootDownAddon, Charset.forName(System.getProperty("sun.jnu.encoding")));
} catch (Exception e) {
e.printStackTrace();
throw new CommonException(e.getMessage());

View File

@ -0,0 +1,89 @@
package com.niu.core.service.admin.channel;
import com.niu.core.common.domain.PageParam;
import com.niu.core.common.domain.PageResult;
import com.niu.core.service.admin.channel.param.AppVersionAddParam;
import com.niu.core.service.admin.channel.param.AppVersionEditParam;
import com.niu.core.service.admin.channel.param.AppVersionPageParam;
import com.niu.core.service.admin.channel.vo.AppVersionInfoVo;
import com.niu.core.service.admin.channel.vo.AppVersionListVo;
import com.niu.core.service.core.channel.param.SetAppParam;
import com.niu.core.service.core.channel.vo.AppCompileLogVo;
import com.niu.core.service.core.channel.vo.AppConfigVo;
import java.util.Map;
/**
* APP管理服务接口
*/
public interface IAdminAppService {
/**
* 获取APP配置
* @return AppConfigVo
*/
AppConfigVo getAppConfig();
/**
* 设置APP配置
* @param param APP配置参数
* @return boolean
*/
void setAppConfig(SetAppParam param);
/**
* 获取版本列表
* @param param 分页查询参数
* @return PageResult<AppVersionListVo>
*/
PageResult<AppVersionListVo> getVersionPage(PageParam pageParam, AppVersionPageParam param);
/**
* 获取版本详情
* @param id 版本ID
* @return AppVersionInfoVo
*/
AppVersionInfoVo getVersionInfo(Integer id);
/**
* 添加版本
* @param param 版本添加参数
* @return boolean
*/
boolean addVersion(AppVersionAddParam param);
/**
* 编辑版本
* @param id
* @param param 版本编辑参数
* @return boolean
*/
boolean editVersion(Integer id, AppVersionAddParam param);
/**
* 删除版本
* @param id 版本ID
* @return boolean
*/
void delVersion(Integer id);
/**
* 发布版本
* @param id 版本ID
* @return boolean
*/
boolean release(Integer id);
/**
* 获取构建日志
* @return Map<String, Object>
*/
AppCompileLogVo getBuildLog(String key);
/**
* 生成签名证书
* @param param 证书生成参数
* @return Map<String, Object>
*/
Map<String, Object> generateSingCert(Map<String, Object> param);
}

View File

@ -0,0 +1,273 @@
package com.niu.core.service.admin.channel.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.niu.core.common.domain.PageParam;
import com.niu.core.common.domain.PageResult;
import com.niu.core.common.exception.CommonException;
import com.niu.core.common.utils.RequestUtils;
import com.niu.core.common.utils.date.DateUtils;
import com.niu.core.entity.sys.AppVersion;
import com.niu.core.enums.channel.AppDict;
import com.niu.core.mapper.sys.AppVersionMapper;
import com.niu.core.service.admin.channel.IAdminAppService;
import com.niu.core.service.admin.channel.param.AppVersionAddParam;
import com.niu.core.service.admin.channel.param.AppVersionPageParam;
import com.niu.core.service.admin.channel.vo.AppVersionInfoVo;
import com.niu.core.service.admin.channel.vo.AppVersionListVo;
import com.niu.core.service.core.channel.ICoreAppCloudService;
import com.niu.core.service.core.channel.ICoreAppService;
import com.niu.core.service.core.channel.param.SetAppParam;
import com.niu.core.service.core.channel.vo.AppCompileLogVo;
import com.niu.core.service.core.channel.vo.AppConfigVo;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.util.Assert;
import java.util.*;
/**
* APP管理服务实现类
*/
@Service
public class AdminAppServiceImpl implements IAdminAppService {
@Resource
private ICoreAppService coreAppService;
@Resource
private AppVersionMapper appVersionMapper;
@Resource
private ICoreAppCloudService coreAppCloudService;
/**
* 获取APP配置
* @return AppConfigVo
*/
@Override
public AppConfigVo getAppConfig() {
return coreAppService.getConfig(RequestUtils.siteId());
}
/**
* 设置APP配置
* @param param APP配置参数
* @return boolean
*/
@Override
public void setAppConfig(SetAppParam param) {
coreAppService.setConfig(RequestUtils.siteId(), param);
}
/**
* 获取版本列表
* @param param 分页查询参数
* @return PageResult<AppVersionListVo>
*/
@Override
public PageResult<AppVersionListVo> getVersionPage(PageParam pageParam, AppVersionPageParam param) {
Page<AppVersion> page = new Page<>(pageParam.getPage(), pageParam.getLimit());
QueryWrapper<AppVersion> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("site_id", RequestUtils.siteId());
if (param.getPlatform() != null) {
queryWrapper.eq("platform", param.getPlatform());
}
queryWrapper.orderByDesc("create_time");
Page<AppVersion> resultPage = appVersionMapper.selectPage(page, queryWrapper);
List<AppVersionListVo> list = new LinkedList();
for (AppVersion item : resultPage.getRecords()) {
AppVersionListVo vo = new AppVersionListVo();
BeanUtil.copyProperties(item, vo);
list.add(vo);
}
return PageResult.build(resultPage, list);
}
/**
* 获取版本详情
* @param id 版本ID
* @return AppVersionInfoVo
*/
@Override
public AppVersionInfoVo getVersionInfo(Integer id) {
AppVersion appVersion = appVersionMapper.selectOne(
new QueryWrapper<AppVersion>()
.eq("id", id)
.eq("site_id", RequestUtils.siteId())
);
if (appVersion == null) {
return null;
}
AppVersionInfoVo vo = new AppVersionInfoVo();
BeanUtil.copyProperties(appVersion, vo);
return vo;
}
/**
* 添加版本
* @param param 添加版本参数
* @return boolean
*/
@Override
public boolean addVersion(AppVersionAddParam param) {
AppVersion notRelease = appVersionMapper.selectOne(
new QueryWrapper<AppVersion>()
.eq("site_id", RequestUtils.siteId())
.eq("release_time", 0)
.last("limit 1")
);
Assert.isNull(notRelease, "当前已存在未发布的版本");
AppVersion lastVersion = appVersionMapper.selectOne(
new QueryWrapper<AppVersion>()
.eq("site_id", RequestUtils.siteId())
.orderByDesc("id")
.last("limit 1")
);
if (lastVersion != null && Long.valueOf(param.getVersionCode()) <= Long.valueOf(lastVersion.getVersionCode())) {
throw new CommonException("版本号必须高于上一版本设置的值");
}
AppVersion appVersion = new AppVersion();
param.setSiteId(RequestUtils.siteId());
BeanUtil.copyProperties(param, appVersion);
appVersion.setCreateTime(DateUtils.currTime());
if (param.getPackageType().equals("cloud")) {
appVersion.setTaskKey(coreAppCloudService.appCloudBuid(param));
appVersion.setStatus(AppDict.StatusEnum.STATUS_CREATING.getValue());
} else {
appVersion.setStatus(AppDict.StatusEnum.STATUS_UPLOAD_SUCCESS.getValue());
}
return appVersionMapper.insert(appVersion) > 0;
}
/**
* 编辑版本
* @param param 编辑版本参数
* @return boolean
*/
@Override
public boolean editVersion(Integer id, AppVersionAddParam param) {
AppVersion appVersion = appVersionMapper.selectOne(
new QueryWrapper<AppVersion>()
.eq("site_id", RequestUtils.siteId())
.eq("id", id)
);
Assert.notNull(appVersion, "版本不存在");
AppVersion lastVersion = appVersionMapper.selectOne(
new QueryWrapper<AppVersion>()
.eq("site_id", RequestUtils.siteId())
.ne("id", id)
.orderByDesc("id")
.last("limit 1")
);
if (lastVersion != null && Long.valueOf(param.getVersionCode()) <= Long.valueOf(lastVersion.getVersionCode())) {
throw new CommonException("版本号必须高于上一版本设置的值");
}
BeanUtil.copyProperties(param, appVersion);
appVersion.setUpdateTime(DateUtils.currTime());
if (param.getPackageType().equals("cloud")) {
param.setSiteId(RequestUtils.siteId());
appVersion.setTaskKey(coreAppCloudService.appCloudBuid(param));
appVersion.setStatus(AppDict.StatusEnum.STATUS_CREATING.getValue());
} else {
appVersion.setStatus(AppDict.StatusEnum.STATUS_UPLOAD_SUCCESS.getValue());
}
return appVersionMapper.updateById(appVersion) > 0;
}
/**
* 删除版本
* @param id 版本ID
* @return boolean
*/
@Override
public void delVersion(Integer id) {
appVersionMapper.delete(new QueryWrapper<AppVersion>()
.eq("id", id)
.eq("site_id", RequestUtils.siteId()));
}
/**
* 获取构建日志
* @return String
*/
@Override
public AppCompileLogVo getBuildLog(String key) {
AppCompileLogVo vo = coreAppCloudService.getAppCompileLog(key);
if (vo.getStatus().equals("fail")) {
appVersionMapper.update(null, new UpdateWrapper<AppVersion>()
.eq("task_key", key)
.eq("site_id", RequestUtils.siteId())
.set("status", AppDict.StatusEnum.STATUS_CREATE_FAIL.getValue())
.set("update_time", DateUtils.currTime())
.set("fail_reason", ObjectUtil.defaultIfNull(vo.getFailReason(), "") ));
}
if (vo.getStatus().equals("success")) {
appVersionMapper.update(null, new UpdateWrapper<AppVersion>()
.eq("task_key", key)
.eq("site_id", RequestUtils.siteId())
.set("status", AppDict.StatusEnum.STATUS_UPLOAD_SUCCESS.getValue())
.set("update_time", DateUtils.currTime())
.set("package_path", vo.getFilePath() ));
}
return vo;
}
/**
* 发布版本
* @param id 版本ID
* @return boolean
*/
@Override
public boolean release(Integer id) {
AppVersion appVersion = appVersionMapper.selectOne(
new QueryWrapper<AppVersion>()
.eq("site_id", RequestUtils.siteId())
.eq("id", id)
);
Assert.notNull(appVersion, "版本不存在");
if (!appVersion.getStatus().equals(AppDict.StatusEnum.STATUS_UPLOAD_SUCCESS.getValue())) {
throw new CommonException("版本未上传成功");
}
AppVersion model = new AppVersion();
model.setId(appVersion.getId());
appVersion.setStatus(AppDict.StatusEnum.STATUS_PUBLISHED.getValue());
appVersion.setReleaseTime(DateUtils.currTime());
return appVersionMapper.updateById(appVersion) > 0;
}
/**
* 生成签名证书
* @param param 证书生成参数
* @return Map<String, Object>
*/
@Override
public Map<String, Object> generateSingCert(Map<String, Object> param) {
// 这里简化实现实际项目中可能需要调用证书生成服务
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "证书生成成功");
return result;
}
}

View File

@ -0,0 +1,72 @@
package com.niu.core.service.admin.channel.param;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 添加版本参数
*/
@Data
public class AppVersionAddParam {
private Integer siteId;
/**
* 版本代码
*/
@NotEmpty(message = "版本代码不能为空")
private String versionCode;
/**
* 版本号
*/
@NotEmpty(message = "版本号不能为空")
private String versionName;
private String versionDesc;
/**
* 平台类型 (1:Android, 2:iOS)
*/
@NotNull(message = "平台类型不能为空")
private String platform;
/**
* 强制更新 (0:, 1:)
*/
private Integer isForceUpdate = 0;
private String packagePath;
private String packageType;
private String upgradeType;
private Build build;
private Cert cert;
@Data
public static class Build {
private String icon;
}
@Data
public static class Cert {
private String type;
private String file;
@JsonProperty("key_alias")
private String keyAlias;
@JsonProperty("key_password")
private String keyPassword;
@JsonProperty("store_password")
private String storePassword;
}
}

View File

@ -0,0 +1,50 @@
package com.niu.core.service.admin.channel.param;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 编辑版本参数
*/
@Data
public class AppVersionEditParam {
/**
* 版本ID
*/
@NotNull(message = "版本ID不能为空")
private Integer id;
/**
* 版本号
*/
@NotEmpty(message = "版本号不能为空")
private String version;
/**
* 版本代码
*/
@NotEmpty(message = "版本代码不能为空")
private String versionCode;
/**
* 版本描述
*/
private String remark;
/**
* 下载地址
*/
private String downloadUrl;
/**
* 强制更新 (0:, 1:)
*/
private Integer forceUpdate = 0;
/**
* 图标地址
*/
private String iconUrl;
}

View File

@ -0,0 +1,19 @@
package com.niu.core.service.admin.channel.param;
import com.niu.core.common.domain.PageParam;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* APP版本分页查询参数
*/
@Data
public class AppVersionPageParam {
/**
* 平台类型
*/
private Integer platform;
}

View File

@ -0,0 +1,94 @@
package com.niu.core.service.admin.channel.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.niu.core.common.domain.BeanJsonSerializer;
import com.niu.core.enums.channel.AppDict;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 版本详情VO
*/
@Data
public class AppVersionInfoVo {
/**
* 版本ID
*/
private Integer id;
/**
* 平台类型
*/
private String platform;
/**
* 平台名称
*/
private String platformName;
/**
* 版本号
*/
private String versionName;
/**
* 版本代码
*/
private String versionCode;
/**
* 应用ID
*/
private String applicationId;
/**
* 版本状态
*/
private String status;
/**
* 状态名称
*/
private String statusName;
private String failReason;
/**
* 强制更新
*/
private Integer forceUpdate;
private String upgradeType;
private String packagePath;
private String taskKey;
/**
* 创建时间
*/
@JsonSerialize(using = BeanJsonSerializer.LongDateToStringSerializer.class)
private Long createTime;
/**
* 更新时间
*/
@JsonSerialize(using = BeanJsonSerializer.LongDateToStringSerializer.class)
private Long updateTime;
/**
* 发布时间
*/
@JsonSerialize(using = BeanJsonSerializer.LongDateToStringSerializer.class)
private Long releaseTime;
public String getPlatformName() {
return AppDict.getPlatformName(platform);
}
public String getStatusName() {
return AppDict.getStatusName(status);
}
}

View File

@ -0,0 +1,91 @@
package com.niu.core.service.admin.channel.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.niu.core.common.domain.BeanJsonSerializer;
import com.niu.core.enums.channel.AppDict;
import lombok.Data;
/**
* 版本列表VO
*/
@Data
public class AppVersionListVo {
/**
* 版本ID
*/
private Integer id;
/**
* 平台类型
*/
private String platform;
/**
* 平台名称
*/
private String platformName;
/**
* 版本号
*/
private String versionName;
/**
* 版本代码
*/
private String versionCode;
/**
* 应用ID
*/
private String applicationId;
/**
* 版本状态
*/
private String status;
/**
* 状态名称
*/
private String statusName;
private String failReason;
/**
* 强制更新
*/
private Integer forceUpdate;
private String upgradeType;
private String packagePath;
private String taskKey;
/**
* 创建时间
*/
@JsonSerialize(using = BeanJsonSerializer.LongDateToStringSerializer.class)
private Long createTime;
/**
* 更新时间
*/
@JsonSerialize(using = BeanJsonSerializer.LongDateToStringSerializer.class)
private Long updateTime;
/**
* 发布时间
*/
@JsonSerialize(using = BeanJsonSerializer.LongDateToStringSerializer.class)
private Long releaseTime;
public String getPlatformName() {
return AppDict.getPlatformName(platform);
}
public String getStatusName() {
return AppDict.getStatusName(status);
}
}

View File

@ -10,6 +10,7 @@ import com.niu.core.service.core.diy.param.StartUpPageConfigParam;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Map;
/**
* 自定义页面服务接口类
@ -134,4 +135,16 @@ public interface IDiyService {
* @param id
*/
void copy(Integer id);
/**
* 更新微页面数据
* @param param
*/
void loadDiyData(Map<String, Object> param);
/**
* 获取自定义链接
* @return
*/
PageResult<DiyPageListVo> getPageLink(PageParam pageParam);
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -11,6 +12,7 @@ import com.niu.core.common.domain.PageParam;
import com.niu.core.common.domain.PageResult;
import com.niu.core.common.exception.CommonException;
import com.niu.core.common.utils.RequestUtils;
import com.niu.core.common.utils.StringUtils;
import com.niu.core.common.utils.json.JacksonUtils;
import com.niu.core.entity.diy.DiyPage;
import com.niu.core.enums.diy.ComponentEnum;
@ -40,6 +42,7 @@ import org.springframework.util.Assert;
import jakarta.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -113,7 +116,10 @@ public class DiyServiceImpl implements IDiyService {
if (ObjectUtil.isNotEmpty(searchParam.getTitle())) queryWrapper.like("title", searchParam.getTitle());
if (ObjectUtil.isNotEmpty(searchParam.getMode())) queryWrapper.eq("mode", searchParam.getMode());
if (ObjectUtil.isNotEmpty(searchParam.getType())) queryWrapper.eq("type", searchParam.getType());
if (ObjectUtil.isNotEmpty(searchParam.getType())) {
List<String> type = Arrays.stream(searchParam.getType()).collect(Collectors.toList());
queryWrapper.in("type", type);
}
List<DiyPage> pages = diyPageMapper.selectList(queryWrapper);
List<DiyPageListVo> list = new LinkedList<>();
@ -285,11 +291,12 @@ public class DiyServiceImpl implements IDiyService {
info = this.infoByName(param.getName());
}
if (!param.getName().isEmpty()) {
if (StringUtils.isNotBlank(param.getName())) {
StartUpPageConfigVo startConfig = coreDiyConfigService.getStartUpPageConfig(RequestUtils.siteId(), param.getName());
if (startConfig != null) {
if (startConfig.getParent().equals("DIY_PAGE")) {
Integer id = Integer.parseInt(startConfig.getPage().replace("/app/pages/index/diy?id=", ""));
if ("DIY_PAGE".equals(startConfig.getParent())) {
String page = startConfig.getPage();
Integer id = Integer.parseInt(page.replace("/app/pages/index/diy?id=", ""));
info = this.info(id);
if (info != null) {
param.setType(info.getType());
@ -297,7 +304,9 @@ public class DiyServiceImpl implements IDiyService {
}
} else {
for (String key : template.keySet()) {
if (ObjectUtil.defaultIfNull(template.getByPath(key + ".page", String.class), "").equals(startConfig.getPage())) {
JSONObject templateItem = template.getJSONObject(key);
String templatePage = templateItem != null ? templateItem.getStr("page") : "";
if (startConfig.getPage().equals(templatePage)) {
info = this.infoByName(key);
if (info != null) {
param.setType(key);
@ -328,28 +337,45 @@ public class DiyServiceImpl implements IDiyService {
Integer isDefault = 0;
String value = "";
JSONObject page = null;
if (template.getJSONObject(param.getName()) != null) {
page = template.getJSONObject(param.getName());
JSONObject pageObj = null;
if (StringUtils.isNotBlank(param.getName()) && template.getJSONObject(param.getName()) != null) {
pageObj = template.getJSONObject(param.getName());
type = name = param.getName();
pageTitle = typeName = page.getStr("title");
pageRoute = page.getStr("page");
pageTitle = typeName = pageObj.getStr("title");
pageRoute = pageObj.getStr("page");
JSONObject pageData = this.getFirstPageData(type, "");
if (pageData != null) {
mode = pageData.getByPath("template.mode", String.class);
value = pageData.getJSONObject("template").toString();
isDefault = 1;
JSONObject templateObj = pageData.getJSONObject("template");
if (templateObj != null) {
mode = templateObj.getStr("mode");
value = templateObj.toString();
isDefault = 1;
}
}
} else if (template.getJSONObject(param.getType()) != null) {
page = template.getJSONObject(param.getType());
typeName = page.getStr("title");
pageRoute = page.getStr("page");
pageObj = template.getJSONObject(param.getType());
typeName = pageObj.getStr("title");
pageRoute = pageObj.getStr("page");
Long count = diyPageMapper.selectCount(new LambdaQueryWrapper<DiyPage>()
.eq(DiyPage::getSiteId, RequestUtils.siteId())
.eq(DiyPage::getType, param.getType()));
if (count == 0) {
isDefault = 1;
}
}
// 页面标题用于前台展示
String title = pageTitle;
if (!"DIY_PAGE".equals(type)) {
title = typeName;
}
info = new DiyPageInfoVo();
info.setName(name);
info.setPageTitle(pageTitle);
info.setTitle(pageTitle);
info.setPageTitle(pageTitle); // 页面名称用于后台展示
info.setTitle(title); // 页面标题用于前台展示
info.setType(type);
info.setTypeName(typeName);
info.setTemplate(templateName);
@ -362,6 +388,19 @@ public class DiyServiceImpl implements IDiyService {
info.setComponent(getComponentList(info.getType()));
info.setDomainUrl(coreSysConfigService.getSceneDomain(RequestUtils.siteId()));
// 处理全局模板数据
JSONObject diyTemplate = new JSONObject();
if (StringUtils.isNotBlank(info.getName())) {
TemplateParam templateParam = new TemplateParam();
String[] key = {info.getName()};
templateParam.setKey(key);
diyTemplate = getTemplate(templateParam);
if (diyTemplate != null && diyTemplate.containsKey(info.getName())) {
JSONObject templateInfo = diyTemplate.getJSONObject(info.getName());
info.setGlobal(templateInfo.getJSONObject("global"));
}
}
return info;
}
@ -479,12 +518,13 @@ public class DiyServiceImpl implements IDiyService {
*/
public JSONObject getDecoratePage(DiyPageSearchParam searchParam) {
TemplateParam templateParam = new TemplateParam();
String[] key = {searchParam.getType()};
String oneType = searchParam.getType()[0];
String[] key = searchParam.getType();
templateParam.setKey(key);
JSONObject template = this.getTemplate(templateParam).getJSONObject(searchParam.getType());
JSONObject template = this.getTemplate(templateParam).getJSONObject(oneType);
if (template == null) throw new CommonException("模板不存在");
JSONObject defaultPage = getFirstPageData(searchParam.getType(), "");
JSONObject defaultPage = getFirstPageData(oneType, "");
JSONObject useTemplate = new JSONObject();
useTemplate.put("type", searchParam.getType());
@ -496,9 +536,9 @@ public class DiyServiceImpl implements IDiyService {
useTemplate.put("url", "");
useTemplate.put("parent", "");
DiyPageInfoVo info = infoByName(searchParam.getType());
DiyPageInfoVo info = infoByName(oneType);
StartUpPageConfigVo startConfig = coreDiyConfigService.getStartUpPageConfig(RequestUtils.siteId(), searchParam.getType());
StartUpPageConfigVo startConfig = coreDiyConfigService.getStartUpPageConfig(RequestUtils.siteId(), oneType);
if (startConfig != null) {
useTemplate.set("title", startConfig.getTitle());
useTemplate.set("name", startConfig.getName());
@ -587,7 +627,7 @@ public class DiyServiceImpl implements IDiyService {
addonTemplateParam.setAddon(param.getAddon());
addonTemplateParam.setType(param.getType());
JSONObject addonTemplate = getTemplate(addonTemplateParam);
if (addonTemplate != null) {
if (addonTemplate != null && !addonTemplate.isEmpty()) {
addonFlag = addonTemplate.keySet().iterator().next();
template = addonTemplate.getJSONObject(addonFlag);
}
@ -655,4 +695,80 @@ public class DiyServiceImpl implements IDiyService {
page.setCreateTime(System.currentTimeMillis() / 1000);
diyPageMapper.insert(page);
}
@Override
public void loadDiyData(Map<String, Object> params) {
// 获取参数
String mainAppStr = params.get("main_app").toString();
JSONArray mainApp = JSONUtil.parseArray(mainAppStr);
int count = mainApp.size();
String tag = (String) params.getOrDefault("tag", "add");
Integer siteId = (Integer) params.get("site_id");
// 创建addon数组在开头添加空字符串
JSONArray addon = new JSONArray();
addon.add("");
addon.addAll(mainApp);
// 遍历处理
for (int k = 0; k < addon.size(); k++) {
String v = addon.get(k).toString();
int isStart;
if ("add".equals(tag)) {
if (count > 1) {
// 站点多应用使用系统的页面
isStart = (k == 0) ? 1 : 0;
} else {
// 站点单应用将应用的设为使用中
isStart = (k == 0) ? 0 : 1;
}
} else {
// 编辑站点套餐的情况
if (count > 1) {
// 站点多应用将不更新启动页
isStart = 0;
} else {
// 站点单应用将应用的设为使用中
isStart = (k == 0) ? 0 : 1;
}
}
SetDiyDataParam setParam = new SetDiyDataParam();
setParam.setSiteId(siteId);
setParam.setType("index");
setParam.setKey("DIY_INDEX");
setParam.setIsStart(isStart);
setParam.setMainApp(addon);
setParam.setAddon(v);
setDiyData(setParam);
setParam.setType("member_index");
setParam.setKey("DIY_MEMBER_INDEX");
setDiyData(setParam);
}
}
@Override
public PageResult<DiyPageListVo> getPageLink(PageParam pageParam) {
Integer page = pageParam.getPage();
Integer limit = pageParam.getLimit();
QueryWrapper<DiyPage> queryWrapper = new QueryWrapper<DiyPage>()
.eq("site_id", RequestUtils.siteId())
.and(i -> i.eq("type", "DIY_PAGE").or().ne("type", "DIY_PAGE").eq("is_default", 0))
.orderByDesc("update_time");
JSONObject templates = TemplateEnum.getTemplate(new TemplateParam());
IPage<DiyPage> iPage = diyPageMapper.selectPage(new Page<DiyPage>(page, limit), queryWrapper);
List<DiyPageListVo> list = new LinkedList<>();
for (DiyPage item : iPage.getRecords()) {
DiyPageListVo vo = new DiyPageListVo();
BeanUtils.copyProperties(item, vo);
vo.setTypeName(templates.getByPath(item.getType() + ".title", String.class));
list.add(vo);
}
return PageResult.build(page, limit, iPage.getTotal()).setData(list);
}
}

View File

@ -15,7 +15,7 @@ public class DiyPageSearchParam implements Serializable {
private String title;
private String type;
private String[] type;
private String mode;

View File

@ -33,6 +33,7 @@ public class DiyPageInfoVo implements Serializable {
private String page;
private JSONObject component;
private SceneDomainVo domainUrl;
private Object global;
public String getTypeName() {
JSONObject template = TemplateEnum.getTemplate();

View File

@ -37,4 +37,9 @@ public class DiyPageListVo implements Serializable {
private String typeName;
private String addonName;
private String typePage;
private String url;
public String getUrl() {
return "/app/pages/index/diy?id=" + id;
}
}

View File

@ -37,6 +37,7 @@ import java.io.FileOutputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.*;
/**
@ -263,7 +264,6 @@ public class CloudBuildServiceImpl implements ICloudBuildService {
cached.put("cloud_build_task", this.buildTask);
} else {
File file = new File(tempDir + "download.zip");
FileOutputStream fos = new FileOutputStream(file, true);
Integer index = this.buildTask.getInt("index");
Integer step = this.buildTask.getInt("step");
@ -273,7 +273,9 @@ public class CloudBuildServiceImpl implements ICloudBuildService {
if (end > this.buildTask.getInt("length")) end = this.buildTask.getInt("length");
HttpResponse response = new NiucloudUtils.Cloud().useThirdBuild().build("cloud/build_download").query(query).header("Range", "bytes=" + start + "-" + end).method(Method.GET).execute();
fos.write(response.bodyBytes());
try (FileOutputStream fos = new FileOutputStream(file, true)) {
fos.write(response.bodyBytes());
}
this.buildTask.set("index", index + 1);
cached.put("cloud_build_task", this.buildTask);
@ -291,7 +293,7 @@ public class CloudBuildServiceImpl implements ICloudBuildService {
logArray.put(log);
FileTools.createDirs(tempDir + "download");
File unzipFile = ZipUtil.unzip(file.getPath(), tempDir + "download", CharsetUtil.CHARSET_UTF_8);
File unzipFile = ZipUtil.unzip(file.getPath(), tempDir + "download", Charset.forName(System.getProperty("sun.jnu.encoding")));
FileUtils.copyDirectory(unzipFile, new File(WebAppEnvs.get().webRoot));
this.clearBuildTask();
}

View File

@ -2,6 +2,7 @@ package com.niu.core.service.admin.notice;
import cn.hutool.json.JSONObject;
import com.niu.core.common.domain.PageParam;
import com.niu.core.entity.sys.NiuSmsTemplate;
import com.niu.core.service.admin.notice.param.*;
import com.niu.core.service.admin.notice.vo.TemplateListVo;
import com.niu.core.service.api.login.param.SendMobileCodeParam;
@ -201,4 +202,13 @@ public interface INuiSmsService {
* @param templateId
*/
void templateDelete(String username, String templateId);
/**
* 模板信息
* @param username
* @param smsType
* @param templateKey
* @return
*/
NiuSmsTemplate templateInfo(String username, String smsType, String templateKey);
}

View File

@ -10,6 +10,7 @@ import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.util.StringUtil;
import com.niu.core.common.domain.PageParam;
@ -102,6 +103,8 @@ public class NuiSmsServiceImpl implements INuiSmsService {
private final String TEMPLATE_DELETE = "https://api-shss.zthysms.com/sms/v2/template/delete";
private final String TEMPLATE_INFO_URL = "/niusms/template/%s/info";
@Resource
private ICoreConfigService coreConfigService;
@ -307,7 +310,7 @@ public class NuiSmsServiceImpl implements INuiSmsService {
map = list.stream().collect(Collectors.toMap(SysNotice::getKey, item -> item));
}
List<TemplateListVo> notice = new ArrayList<>();
for (Map.Entry<String, NoticeEnumListVo> noticeMap : NoticeEnum.getNotice().entrySet()) {
for (Map.Entry<String, NoticeEnumListVo> noticeMap : NoticeEnum.getNiuyunNotice().entrySet()) {
TemplateListVo noticeInfoVo = new TemplateListVo();
BeanUtil.copyProperties(noticeMap.getValue(), noticeInfoVo);
if (map.containsKey(noticeMap.getKey())) {
@ -808,6 +811,35 @@ public class NuiSmsServiceImpl implements INuiSmsService {
}
}
@Override
public NiuSmsTemplate templateInfo(String username, String smsType, String templateKey) {
NiuSmsTemplate niuSmsTemplate = niuSmsTemplateMapper.selectOne(new LambdaQueryWrapper<NiuSmsTemplate>()
.eq(NiuSmsTemplate::getSiteId, RequestUtils.siteId())
.eq(NiuSmsTemplate::getSmsType, smsType)
.eq(NiuSmsTemplate::getUsername, username)
.eq(NiuSmsTemplate::getTemplateKey, templateKey));
if (ObjectUtil.isEmpty(niuSmsTemplate)){
throw new AdminException("短信模版暂未报备");
}
String orderCreateUrl = String.format(TEMPLATE_INFO_URL, username);
Map<String, Object> templateInfoParam = new HashMap<>();
templateInfoParam.put("tem_id", niuSmsTemplate.getTemplateId());
try {
JSONObject jsonObject = NiucloudUtils.Niucloud.get(orderCreateUrl, templateInfoParam);
JSONObject result = jsonObject.getJSONObject("data");
//删除null值 防止序列化报错
JacksonUtils.removeNull(result);
String auditStatus = result.containsKey("auditResult") && ObjectUtil.isNotEmpty(result.getJSONObject("auditResult"))
? result.getStr("auditResult") : niuSmsTemplate.getAuditStatus();
niuSmsTemplate.setAuditStatus(auditStatus);
niuSmsTemplateMapper.updateById(niuSmsTemplate);
return niuSmsTemplate;
} catch (Exception e) {
log.error("获取模版信息失败异常信息:{}", e.getMessage());
throw new AdminException("获取模版信息失败");
}
}
public JSONObject sendHttp(String url, Map<String, Object> body){
HttpResponse response = HttpRequest.post(url).body(JSONUtil.toJsonStr(body)).execute();
if (!response.isOk()) {

View File

@ -2,6 +2,7 @@ package com.niu.core.service.admin.notice.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.niu.core.common.exception.CommonException;
import com.niu.core.common.utils.RequestUtils;
@ -47,6 +48,7 @@ public class SmsService implements ISmsService {
vo.setIsUse(isUse ? 1 : 0);
}
JSONObject params = (JSONObject) smsType.get("params");
JSONArray encryptParams = ObjectUtil.defaultIfNull(smsType.getJSONArray("encrypt_params"), new JSONArray());
for (Map.Entry<String, Object> paramEntry : params.entrySet()) {
@ -60,8 +62,8 @@ public class SmsService implements ISmsService {
JSONObject smsTypeParam = (JSONObject) smsTypeConfig.get(entry.getKey());
if(!ObjectUtil.isEmpty(smsTypeParam)){
if ("secret_key".equals(paramEntry.getKey()) && ObjectUtil.isNotEmpty(paramEntry.getValue())){
String secretValue = smsTypeParam.getStr("secret_key");
if (encryptParams.contains(paramEntry.getKey()) && ObjectUtil.isNotEmpty(paramEntry.getValue())){
String secretValue = smsTypeParam.getStr(paramEntry.getKey());
paramVo.setValue(StringUtils.hide(secretValue, 0, secretValue.length()));
}else {
paramVo.setValue(smsTypeParam.getStr(paramEntry.getKey()));
@ -113,7 +115,10 @@ public class SmsService implements ISmsService {
JSONObject smsTypeEnum = SmsTypeEnum.getType().getJSONObject(smsType);
for (String key: smsTypeEnum.getJSONObject("params").keySet()) {
config.putByPath(smsType + "." + key, data.getStr(key, ""));
String value = data.getStr(key, "");
if (!value.contains("*")) {
config.putByPath(smsType + "." + key, value);
}
}
coreConfigService.setConfig(RequestUtils.siteId(), "SMS", config);

View File

@ -75,7 +75,13 @@ public class PayChannelServiceImpl implements IPayChannelService {
JSONObject config = payChannelList.getJSONObject(item.getCode()).getJSONObject(key);
type.set("status", config.getInt("status"));
try {
type.set("config", JSONUtil.parseObj(config.getStr("config")));
JSONObject payConfig = JSONUtil.parseObj(config.getStr("config"));
JSONArray encryptParams = ObjectUtil.defaultIfNull(type.getJSONArray("encrypt_params"), new JSONArray());
for (String payConfigkey : payConfig.keySet()) {
String value = payConfig.getStr(payConfigkey, "");
if (encryptParams.contains(payConfigkey)) payConfig.set(payConfigkey, StringUtils.hide(value, 0, value.length()));
}
type.set("config", payConfig);
} catch (Exception e) {
type.set("config", new JSONObject().set("name", ""));
}
@ -132,8 +138,14 @@ public class PayChannelServiceImpl implements IPayChannelService {
.eq("channel", channel)
.eq("type", type)
);
if (ObjectUtil.isNotEmpty(payChannel)) {
payChannel.setConfig(data.getJSONObject("config").toString());
JSONObject config = JSONUtil.parseObj(payChannel.getConfig());
for (String key : data.getJSONObject("config").keySet()) {
String value = data.getJSONObject("config").getStr(key, "");
if (!value.contains("*")) config.set(key, value);
}
payChannel.setConfig(config.toString());
payChannel.setSort(data.getInt("sort"));
payChannel.setStatus(data.getInt("status"));
payChannelMapper.updateById(payChannel);

View File

@ -6,10 +6,7 @@ import com.niu.core.entity.site.Site;
import com.niu.core.entity.site.SiteGroup;
import com.niu.core.service.admin.site.param.*;
import com.niu.core.common.domain.PageParam;
import com.niu.core.service.admin.site.vo.ShowAppListVo;
import com.niu.core.service.admin.site.vo.ShowMarketingVo;
import com.niu.core.service.admin.site.vo.SiteInfoVo;
import com.niu.core.service.admin.site.vo.SiteListVo;
import com.niu.core.service.admin.site.vo.*;
import java.util.List;
import java.util.Map;
@ -112,4 +109,17 @@ public interface ISiteService {
* @return
*/
Boolean siteInit(Integer siteId);
/**
* 生成菜单数据
* @return
*/
SpecialMenuListVo getSpecialMenuList();
/**
* 统一展示 安装的插件 应用 营销工具等
* @param isSort
* @return
*/
Map<String, Object> showCustomer(boolean isSort);
}

View File

@ -1,11 +1,11 @@
package com.niu.core.service.admin.site.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -14,7 +14,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseMapper;
import com.github.yulichang.query.MPJQueryWrapper;
import com.google.gson.JsonArray;
import com.niu.core.common.component.context.SpringContext;
@ -33,6 +32,7 @@ import com.niu.core.entity.site.SiteGroup;
import com.niu.core.entity.sys.SysMenu;
import com.niu.core.entity.sys.SysUserRole;
import com.niu.core.enums.addon.AddonTypeEnum;
import com.niu.core.enums.site.AddonChildMenuEnum;
import com.niu.core.enums.site.ShowMarketingEnum;
import com.niu.core.enums.site.SiteInitEnum;
import com.niu.core.enums.site.SiteStatusEnum;
@ -40,11 +40,14 @@ import com.niu.core.enums.sys.AppTypeEnum;
import com.niu.core.enums.sys.CacheTagEnum;
import com.niu.core.event.site.SiteAddAfterEvent;
import com.niu.core.event.site.SiteEditAfterEvent;
import com.niu.core.event.sys.ShowCustomerEventDefiner;
import com.niu.core.mapper.addon.AddonMapper;
import com.niu.core.mapper.site.SiteGroupMapper;
import com.niu.core.mapper.site.SiteMapper;
import com.niu.core.mapper.sys.SysMenuMapper;
import com.niu.core.mapper.sys.SysUserRoleMapper;
import com.niu.core.service.admin.auth.IAuthService;
import com.niu.core.service.admin.diy.IDiyService;
import com.niu.core.service.admin.generator.IGenerateService;
import com.niu.core.service.admin.site.ISiteGroupService;
import com.niu.core.service.admin.site.ISiteService;
@ -58,13 +61,14 @@ import com.niu.core.service.core.app.helper.EventAndSubscribeOfPublisher;
import com.niu.core.service.core.site.ICoreSiteService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import jakarta.annotation.Resource;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.*;
import java.util.*;
@ -127,6 +131,13 @@ public class SiteServiceImpl implements ISiteService {
@Resource
SysMenuMapper sysMenuMapper;
@Resource
private IDiyService diyService;
@Resource
@Lazy
private IAuthService authService;
/**
* 站点列表
*
@ -139,39 +150,49 @@ public class SiteServiceImpl implements ISiteService {
Integer page = pageParam.getPage();
Integer limit = pageParam.getLimit();
LambdaQueryWrapper<Site> queryWrapper = new LambdaQueryWrapper<>();
MPJQueryWrapper<Site> queryWrapper = new MPJQueryWrapper();
queryWrapper.setAlias("se")
.selectAll(Site.class)
.select("sg.group_name")
.leftJoin("?_site_group sg on sg.group_id = se.group_id".replace("?_", GlobalConfig.tablePrefix));
//查询条件判断组装
if (ObjectUtil.isNotEmpty(searchParam.getKeywords())) {
queryWrapper.like(Site::getSiteName, searchParam.getKeywords()).or().like(Site::getSiteId, searchParam.getKeywords());
}
if (ObjectUtil.isNotEmpty(searchParam.getStatus())) {
queryWrapper.eq(Site::getStatus, searchParam.getStatus());
}
if (ObjectUtil.isNotEmpty(searchParam.getGroupId())) {
queryWrapper.eq(Site::getGroupId, searchParam.getGroupId());
queryWrapper.like("se.siteName", searchParam.getKeywords()).or().like("se.siteName", searchParam.getKeywords());
}
if (ObjectUtil.isNotEmpty(searchParam.getApp())) {
queryWrapper.like(Site::getApp, searchParam.getApp()).or().like(Site::getInitalledAddon, searchParam.getApp());
queryWrapper.and(wrapper -> wrapper
.like("sg.addon", searchParam.getApp())
.or()
.like("sg.app", searchParam.getApp())
);
}
if (ObjectUtil.isNotEmpty(searchParam.getStatus())) {
queryWrapper.eq("se.status", searchParam.getStatus());
}
if (ObjectUtil.isNotEmpty(searchParam.getGroupId())) {
queryWrapper.eq("se.group_id", searchParam.getGroupId());
}
if (ObjectUtil.isNotEmpty(searchParam.getSiteDomain())) {
queryWrapper.like(Site::getSiteDomain, searchParam.getSiteDomain());
queryWrapper.like("se.site_domain", searchParam.getSiteDomain());
}
queryWrapper.ne(Site::getAppType, "admin");
queryWrapper.ne("se.app_type", "admin");
if (ObjectUtil.isNotEmpty(searchParam.getCreateTime())) {
String[] createTime = searchParam.getCreateTime();
long startTime = (createTime[0] == null) ? 0L : DateUtils.StringToTimestamp(createTime[0]);
long endTime = (createTime[1] == null) ? 0L : DateUtils.StringToTimestamp(createTime[1]);
if (startTime > 0L && endTime > 0L) {
queryWrapper.between(Site::getCreateTime, startTime, endTime);
queryWrapper.between("se.create_time", startTime, endTime);
} else if (startTime > 0L && endTime == 0L) {
queryWrapper.ge(Site::getCreateTime, startTime);
queryWrapper.ge("se.create_time", startTime);
} else if (startTime == 0L && endTime > 0L) {
queryWrapper.le(Site::getCreateTime, startTime);
queryWrapper.le("se.create_time", startTime);
}
}
@ -181,20 +202,20 @@ public class SiteServiceImpl implements ISiteService {
long startTime = (expireTime[0] == null) ? 0 : DateUtils.StringToTimestamp(expireTime[0]);
long endTime = (expireTime[1] == null) ? 0 : DateUtils.StringToTimestamp(expireTime[1]);
if (startTime > 0 && endTime > 0) {
queryWrapper.between(Site::getExpireTime, startTime, endTime);
queryWrapper.between("se.expire_time", startTime, endTime);
} else if (startTime > 0 && endTime == 0) {
queryWrapper.ge(Site::getExpireTime, startTime);
queryWrapper.ge("se.expire_time", startTime);
} else if (startTime == 0 && endTime > 0) {
queryWrapper.le(Site::getExpireTime, startTime);
queryWrapper.le("se.expire_time", startTime);
}
}
queryWrapper.orderByDesc("se.create_time");
queryWrapper.orderByDesc(Site::getCreateTime);
IPage<SiteListVo> iPage = siteMapper.selectJoinPage(new Page<>(page, limit), SiteListVo.class, queryWrapper);
IPage<Site> iPage = siteMapper.selectPage(new Page<>(page, limit), queryWrapper);
List<SiteListVo> list = new LinkedList<>();
for (Site item : iPage.getRecords()) {
for (SiteListVo item : iPage.getRecords()) {
SiteListVo vo = new SiteListVo();
BeanUtils.copyProperties(item, vo);
MPJQueryWrapper<SysUserRole> userRoleMPJQueryWrapper = new MPJQueryWrapper<>();
@ -204,11 +225,6 @@ public class SiteServiceImpl implements ISiteService {
userRoleMPJQueryWrapper.eq("nsur.is_admin", 1);
userRoleMPJQueryWrapper.eq("nsur.site_id", item.getSiteId());
vo.setAdmin(sysUserRoleMapper.selectJoinOne(SiteAdminVo.class, userRoleMPJQueryWrapper));
SiteGroup siteGroup = siteGroupMapper.selectById(item.getGroupId());
if (ObjectUtil.isNotEmpty(siteGroup)) {
vo.setGroupName(siteGroup.getGroupName());
}
list.add(vo);
}
return PageResult.build(page, limit, iPage.getTotal(), list);
@ -274,6 +290,12 @@ public class SiteServiceImpl implements ISiteService {
event.setSite(model);
event.setSiteGroup(siteGroup);
EventAndSubscribeOfPublisher.publishAll(event);
Map<String, Object> param = new HashMap<>();
param.put("site_id", siteId);
param.put("main_app", siteGroup.getApp());
param.put("tag", "add");
diyService.loadDiyData(param);
}
/**
@ -641,5 +663,243 @@ public class SiteServiceImpl implements ISiteService {
return coreSiteService.siteInitBySiteId(siteId, tables);
}
@Override
public SpecialMenuListVo getSpecialMenuList() {
JSONArray authMenuList = authService.getAuthMenuTreeList(1, "all");
// 将菜单列表转换为Map便于通过menu_key查找
Map<String, JSONObject> authMenuMap = new HashMap<>();
for (int i = 0; i < authMenuList.size(); i++) {
JSONObject menu = authMenuList.getJSONObject(i);
authMenuMap.put(menu.get("menu_key").toString(), menu);
}
// 获取"addon"菜单
JSONObject addonMenu = authMenuMap.get("addon");
if (addonMenu == null) {
return new SpecialMenuListVo("addon", new ArrayList<>());
}
Map<String, Object> showList = showCustomer(false);
List<AddonChildMenuEnum.MenuConfig> addonChildMenus = AddonChildMenuEnum.getAll();
List<SpecialMenuListVo.MenuVo> menuList = new ArrayList<>();
// 遍历插件子菜单构建菜单列表
for (AddonChildMenuEnum.MenuConfig item : addonChildMenus) {
String menuKey = item.getKey();
List<String> menuKeyList = new ArrayList<>();
if (showList.containsKey(menuKey) && showList.get(menuKey) instanceof Map) {
Map<String, Object> menuItem = (Map<String, Object>) showList.get(menuKey);
if (menuItem.containsKey("list") && menuItem.get("list") instanceof List) {
List<Map<String, Object>> listItems = (List<Map<String, Object>>) menuItem.get("list");
for (Map<String, Object> listItem : listItems) {
if (listItem.containsKey("key")) {
menuKeyList.add(listItem.get("key").toString());
}
}
}
}
SpecialMenuListVo.MenuVo tempMenu = new SpecialMenuListVo.MenuVo();
tempMenu.setMenuName(item.getName());
tempMenu.setMenuKey(item.getKey());
tempMenu.setMenuShortName(item.getShortName());
tempMenu.setParentKey("addon");
tempMenu.setMenuType("0");
tempMenu.setIcon("iconfont iconzhuangxiu3");
tempMenu.setApiUrl("");
tempMenu.setRouterPath("");
tempMenu.setViewPath("");
tempMenu.setMethods("");
tempMenu.setSort(item.getSort());
tempMenu.setStatus("1");
tempMenu.setIsShow("1");
List<SpecialMenuListVo.MenuVo> children = new ArrayList<>();
JSONArray authChildren = addonMenu.getJSONArray("children");
if (authChildren != null) {
for (int j = 0; j < authChildren.size(); j++) {
JSONObject child = authChildren.getJSONObject(j);
if (menuKeyList.contains(child.get("menu_key").toString())) {
// 转换为MenuVo对象
SpecialMenuListVo.MenuVo childMenu = convertToMenuVo(child);
children.add(childMenu);
}
}
}
tempMenu.setChildren(children);
menuList.add(tempMenu);
}
SpecialMenuListVo result = new SpecialMenuListVo();
result.setParentKey("addon");
result.setList(menuList);
return result;
}
@Override
public Map<String, Object> showCustomer(boolean isSort) {
// 创建并设置事件
ShowCustomerEventDefiner.ShowCustomerEvent event = new ShowCustomerEventDefiner.ShowCustomerEvent();
event.setName("ShowCustomer");
event.setSiteId(RequestUtils.siteId());
// 发布事件并获取所有监听器的返回结果
List<ShowCustomerEventDefiner.ShowCustomerEventResult> eventResults =
EventAndSubscribeOfPublisher.publishAndCallback(event);
// 转换事件结果为统一格式
List<Map<String, List<ShowCustomerEventDefiner.MenuItem>>> showList = new ArrayList<>();
for (ShowCustomerEventDefiner.ShowCustomerEventResult result : eventResults) {
Map<String, List<ShowCustomerEventDefiner.MenuItem>> menuMap = result.getData();
showList.add(menuMap);
}
// 获取插件类型列表
List<AddonChildMenuEnum.MenuConfig> addonTypeList = AddonChildMenuEnum.getAll();
if (addonTypeList == null) {
addonTypeList = new ArrayList<>();
}
// 初始化返回结果并合并事件数据
Map<String, Object> result = new HashMap<>();
for (Map<String, List<ShowCustomerEventDefiner.MenuItem>> eventItem : showList) {
for (AddonChildMenuEnum.MenuConfig type : addonTypeList) {
String key = type.getKey();
// 初始化类型条目
if (!result.containsKey(key)) {
Map<String, Object> typeItem = new HashMap<>();
typeItem.put("title", type.getName());
typeItem.put("sort", type.getSort());
typeItem.put("list", new ArrayList<Map<String, Object>>());
result.put(key, typeItem);
}
// 合并列表数据
Map<String, Object> typeItem = (Map<String, Object>) result.get(key);
List<Map<String, Object>> typeList = (List<Map<String, Object>>) typeItem.get("list");
// 将MenuItem转换为Map并添加到列表
List<ShowCustomerEventDefiner.MenuItem> menuItems = eventItem.getOrDefault(key, new ArrayList<>());
for (ShowCustomerEventDefiner.MenuItem item : menuItems) {
Map<String, Object> itemMap = new HashMap<>();
itemMap.put("title", item.getTitle());
itemMap.put("desc", item.getDesc());
itemMap.put("icon", item.getIcon());
itemMap.put("key", item.getKey());
itemMap.put("url", item.getUrl());
typeList.add(itemMap);
}
}
}
// 收集已存在的key用于处理未实现事件的插件
Set<String> existingKeys = new HashSet<>();
for (Object value : result.values()) {
Map<String, Object> typeItem = (Map<String, Object>) value;
List<Map<String, Object>> list = (List<Map<String, Object>>) typeItem.get("list");
for (Map<String, Object> item : list) {
if (item.containsKey("key")) {
existingKeys.add(item.get("key").toString());
}
}
}
// 处理未实现事件的插件添加到addon_tool
List<Addon> siteAddons = getSiteAddons();
if (siteAddons != null && !siteAddons.isEmpty()) {
// 查询菜单表获取路由路径
List<String> addonKeys = siteAddons.stream()
.map(Addon::getKey)
.collect(Collectors.toList());
List<SysMenu> menuList = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenu>()
.select(SysMenu::getAddon, SysMenu::getRouterPath)
.in(!CollectionUtils.isEmpty(addonKeys),SysMenu::getAddon, addonKeys)
.notIn(!CollectionUtils.isEmpty(existingKeys), SysMenu::getAddon, existingKeys)
.eq(SysMenu::getMenuType, 1)
.eq(SysMenu::getIsShow, 1)
.orderByAsc(SysMenu::getId)
.groupBy(SysMenu::getAddon));
Map<String, String> addonUrls = menuList.stream()
.collect(Collectors.toMap(
SysMenu::getAddon,
SysMenu::getRouterPath,
// 处理重复key的情况保留第一个
(existingValue, newValue) -> existingValue
));
// 初始化addon_tool条目
if (!result.containsKey("addon_tool")) {
Map<String, Object> toolItem = new HashMap<>();
toolItem.put("title", "附加工具");
toolItem.put("sort", 0);
toolItem.put("list", new ArrayList<Map<String, Object>>());
result.put("addon_tool", toolItem);
}
Map<String, Object> toolItem = (Map<String, Object>) result.get("addon_tool");
List<Map<String, Object>> toolList = (List<Map<String, Object>>) toolItem.get("list");
// 添加未处理的插件
for (Addon addon : siteAddons) {
String key = addon.getKey();
if (existingKeys.contains(key)) {
continue;
}
Map<String, Object> item = new HashMap<>();
item.put("title", addon.getTitle());
item.put("desc", addon.getDesc());
item.put("icon", addon.getIcon());
item.put("key", key);
String url = addonUrls.getOrDefault(key, "");
item.put("url", url.startsWith("/") ? url : "/" + url);
toolList.add(item);
}
}
// 排序处理
if (isSort) {
List<Map.Entry<String, Object>> entryList = new ArrayList<>(result.entrySet());
entryList.sort((a, b) -> {
Map<String, Object> mapA = (Map<String, Object>) a.getValue();
Map<String, Object> mapB = (Map<String, Object>) b.getValue();
int sortA = mapA.containsKey("sort") ? Convert.toInt(mapA.get("sort"), 0) : 0;
int sortB = mapB.containsKey("sort") ? Convert.toInt(mapB.get("sort"), 0) : 0;
return Integer.compare(sortB, sortA); // 降序排序
});
// 重建排序后的map
Map<String, Object> sortedResult = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : entryList) {
sortedResult.put(entry.getKey(), entry.getValue());
}
result = sortedResult;
}
return result;
}
private SpecialMenuListVo.MenuVo convertToMenuVo(JSONObject json) {
SpecialMenuListVo.MenuVo menuVo = JSONUtil.toBean(json, SpecialMenuListVo.MenuVo.class);
JSONArray children = json.getJSONArray("children");
if (children != null) {
List<SpecialMenuListVo.MenuVo> childList = new ArrayList<>();
for (int i = 0; i < children.size(); i++) {
childList.add(convertToMenuVo(children.getJSONObject(i)));
}
menuVo.setChildren(childList);
}
return menuVo;
}
}

View File

@ -57,5 +57,5 @@ public class SiteInfoVo implements Serializable {
public String getStatusName() {
return SiteStatusEnum.getNameByCode(this.status);
}
private Integer uid;
}

View File

@ -0,0 +1,37 @@
package com.niu.core.service.admin.site.vo;
import lombok.Data;
import java.util.List;
@Data
public class SpecialMenuListVo {
private String parentKey;
private List<MenuVo> list;
public SpecialMenuListVo() {}
public SpecialMenuListVo(String parentKey, List<MenuVo> list) {
this.parentKey = parentKey;
this.list = list;
}
@Data
public static class MenuVo {
private String key;
private String menuName;
private String menuKey;
private String menuShortName;
private String parentKey;
private String menuType;
private String icon;
private String apiUrl;
private String routerPath;
private String viewPath;
private String methods;
private Integer sort;
private String status;
private String isShow;
private List<MenuVo> children;}
}

View File

@ -138,4 +138,6 @@ public interface ISysMenuService {
* @return
*/
JSONArray getAddonMenu(String appKey, String status, Integer isTree, Integer isButton);
JSONArray getSystemMenu(String status, Integer isTree, Integer isButton);
}

View File

@ -523,4 +523,26 @@ public class SysMenuServiceImpl implements ISysMenuService {
return TreeUtils.listToTree(jsonArray, "menu_key", "parent_key", "children");
});
}
@Override
public JSONArray getSystemMenu(String status, Integer isTree, Integer isButton) {
return cached.rememberObject(useCache, cacheTagName, Arrays.asList("getSystemMenu", status, isTree.toString(), isButton.toString()), uniqueKey ->{
QueryWrapper<SysMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("app_type", "site");
queryWrapper.eq("addon", "");
queryWrapper.orderByDesc(Arrays.asList("sort", "id"));
if (!status.equals("all")) {
queryWrapper.eq("status", status);
}
if (isButton == 0) {
queryWrapper.in("menu_type", new Integer[]{0, 1});
}
List<SysMenu> sysMenuList = sysMenuMapper.selectList(queryWrapper);
JSONArray jsonArray = JSONUtil.parseArray(JacksonUtils.toSnakeCaseJSONString(sysMenuList));
if (isTree == 0) {
return jsonArray;
}
return TreeUtils.listToTree(jsonArray, "menu_key", "parent_key", "children");
});
}
}

View File

@ -318,7 +318,7 @@ public class SysScheduleServiceImpl implements ISysScheduleService {
qw.eq(SysScheduleLog::getStatus, searchParam.getStatus());
}
if (ObjectUtil.isNotEmpty(searchParam.getExecuteTime())) {
QueryMapperUtils.buildByTime(qw, "execute_time", searchParam.getExecuteTime());
QueryMapperUtils.buildByTime(qw, SysScheduleLog::getExecuteTime, searchParam.getExecuteTime());
}
Page<SysScheduleLog> sysScheduleLogPage = sysScheduleLogMapper.selectPage(new Page<>(page, limit), qw);
List<SysScheduleLogListVo> result = new ArrayList<>();

View File

@ -1,6 +1,8 @@
package com.niu.core.service.admin.sys.impl;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.niu.core.common.component.context.WebAppEnvs;
import com.niu.core.common.component.context.cache.Cached;
@ -163,7 +165,9 @@ public class SystemServiceImpl implements ISystemService {
try {
Map<String, Object> data = new HashMap<>();
for (SpreadQrcodeParam.Param qrcodeParam : param.getParams()) {
data.put(qrcodeParam.getColumnName(), qrcodeParam.getColumnValue());
JSONArray jsonArray = JSONUtil.parseArray(qrcodeParam);
JSONObject jsonObject = JSONUtil.parseObj(jsonArray);
data.put(jsonObject.getStr("name"), qrcodeParam.getColumnValue());
}
String dir = "upload/qrcode/" + RequestUtils.siteId() + "/" + param.getFolder();
vo.setWeappPath(QrcodeUtils.qrcodeToFile(RequestUtils.siteId(), "weapp", "", param.getPage(), data, dir));

View File

@ -16,7 +16,7 @@ public class SpreadQrcodeParam {
@Data
public static class Param {
private String columnName;
private Object columnName;
private String columnValue;
}
}

View File

@ -2,7 +2,6 @@ package com.niu.core.service.admin.upgrade.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ZipUtil;
@ -33,6 +32,7 @@ import com.niu.core.enums.addon.AddonStatusEnum;
import com.niu.core.enums.addon.AddonTypeEnum;
import com.niu.core.enums.sys.UpgradeRecordStatusEnum;
import com.niu.core.mapper.addon.AddonMapper;
import com.niu.core.mapper.addon.AddonMapper;
import com.niu.core.mapper.site.SiteMapper;
import com.niu.core.mapper.sys.SysBackupRecordsMapper;
import com.niu.core.service.admin.niucloud.ICloudBuildService;
@ -59,6 +59,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.*;
@ -455,7 +456,7 @@ public class UpgradeServiceImpl implements IUpgradeService {
return param;
} else {
fos.close();
ZipUtil.unzip(file.getPath(), downloadDir.getPath(), CharsetUtil.CHARSET_UTF_8);
ZipUtil.unzip(file.getPath(), downloadDir.getPath(), Charset.forName(System.getProperty("sun.jnu.encoding")));
Thread.sleep(1000);
file.delete();
@ -723,7 +724,9 @@ public class UpgradeServiceImpl implements IUpgradeService {
UpgradeContentVo.VersionItem version = addon.getVersionList().get(versionIndex);
String versionNo = version.getVersionNo();
String sql = AddonModuleContext.readResourceAsStreamToText(addon.getApp().getAppKey(), "upgrade/" + versionNo + ".sql");
String addonKey = addon.getApp().getAppKey().equals("niucloud-admin") ? "core" : addon.getApp().getAppKey();
String sql = AddonModuleContext.readResourceAsStreamToText(addonKey, "upgrade/" + versionNo + ".sql");
if (!sql.isEmpty()) {
sql = sql.replace("{{prefix}}", GlobalConfig.tablePrefix);
try {
@ -734,7 +737,7 @@ public class UpgradeServiceImpl implements IUpgradeService {
}
try {
UpgradeProviderFactory.handle(addon.getApp().getAppKey(), versionNo);
UpgradeProviderFactory.handle(addonKey, versionNo);
} catch (Exception e) {
throw new CommonException("" + addon.getApp().getAppName() + "" + versionNo + "版本升级方法执行出错,错误原因:" + e.getMessage());
}
@ -776,8 +779,8 @@ public class UpgradeServiceImpl implements IUpgradeService {
List<String> addons = addonMapper.selectList(new MPJQueryWrapper<Addon>()
.select("`key`")
.eq("status", AddonStatusEnum.ON.getCode()))
.stream().map(i -> i.getKey()).collect(Collectors.toList())
;
.stream().map(i -> i.getKey()).collect(Collectors.toList())
;
// 处理pages.json
if (new File(envs.webRoot + "uni-app/").exists()) addonInstallTools.handlePagesJson(envs.webRoot + "/uni-app/", addons);
@ -843,7 +846,7 @@ public class UpgradeServiceImpl implements IUpgradeService {
cloudBuildService.build("build");
}
public Map<String, Object> gteCloudBuildLog(UpgradeTaskVo vo) throws InterruptedException {
public Map<String, Object> gteCloudBuildLog(UpgradeTaskVo vo) throws InterruptedException{
JSONObject log = cloudBuildService.getBuildLog("build");
if (log != null) {
JSONArray data = log.getByPath("data.0", JSONArray.class);
@ -873,6 +876,9 @@ public class UpgradeServiceImpl implements IUpgradeService {
}
}
File backupDir = new File(upgradeDir(vo));
FileUtil.writeUtf8String(JSONUtil.toJsonPrettyStr(JSONUtil.parseObj(vo)), new File(backupDir, DateUtil.now() + ".log"));
// 变更升级记录
SysUpgradeRecordsParam editParam = new SysUpgradeRecordsParam();
editParam.setStatus(UpgradeRecordStatusEnum.STATUS_COMPLETE.getStatus());
@ -971,8 +977,10 @@ public class UpgradeServiceImpl implements IUpgradeService {
vo.setStep("restoreCover");
vo.setStatus("restarting");
setUpgradeTaskCache(vo);
PipeNameUtils.noticeBootRestartByUpgradeRollback(GlobalConfig.applicationName, backup.getBackupKey());
Thread.sleep(3000);
return null;
}
}

View File

@ -51,7 +51,7 @@ public class UpgradeTaskVo implements Serializable {
steps.put("backupSql", new Step("backupSql", "备份数据库"));
steps.put("coverCode", new Step("coverCode", "合并更新文件"));
steps.put("handleUpgrade", new Step("coverCode", "执行升级sql和升级方法"));
steps.put("handleVue", new Step("handleUniapp", "处理前端源码"));
steps.put("handleVue", new Step("handleVue", "处理前端源码"));
steps.put("refreshMenu", new Step("refreshMenu", "刷新菜单"));
steps.put("installSchedule", new Step("installSchedule", "安装计划任务"));
steps.put("cloudBuild", new Step("cloudBuild", "开始云编译"));

View File

@ -1,6 +1,7 @@
package com.niu.core.service.admin.upload.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.niu.core.common.exception.AdminException;
@ -16,6 +17,7 @@ import com.niu.core.service.core.upload.vo.CoreStorAgeConfigVo;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@ -26,6 +28,7 @@ public class StorageConfigServiceImpl implements IStorageConfigService {
ICoreStorageService coreStorageService;
@Resource
ICoreConfigService coreConfigService;
/**
* 获取本地云存储列表
*
@ -45,35 +48,37 @@ public class StorageConfigServiceImpl implements IStorageConfigService {
@Override
public CoreStorAgeConfigVo getStorageConfig(String storageType) {
JSONObject storageTypeList= UploadLoader.getType();
if(ObjectUtil.isNull(storageTypeList.get(storageType))){
JSONObject storageTypeList = UploadLoader.getType();
if (ObjectUtil.isNull(storageTypeList.get(storageType))) {
throw new AdminException("OSS_TYPE_NOT_EXIST");
}
/**
* 获取配置
*/
JSONObject storageConfig=coreStorageService.getStorageConfig(RequestUtils.siteId());
JSONObject storageConfig = coreStorageService.getStorageConfig(RequestUtils.siteId());
JSONObject storageValues= JSONUtil.parseObj(storageTypeList.get(storageType));
CoreStorAgeConfigVo coreStorAgeConfigVo=new CoreStorAgeConfigVo();
JSONObject storageValues = JSONUtil.parseObj(storageTypeList.get(storageType));
CoreStorAgeConfigVo coreStorAgeConfigVo = new CoreStorAgeConfigVo();
coreStorAgeConfigVo.setStorageType(storageType);
coreStorAgeConfigVo.setIsUse(storageType.equals(storageConfig.get("default"))? StorageEnum.ON.getCode() : StorageEnum.OFF.getCode());
coreStorAgeConfigVo.setIsUse(storageType.equals(storageConfig.get("default")) ? StorageEnum.ON.getCode() : StorageEnum.OFF.getCode());
coreStorAgeConfigVo.setName(storageValues.get("name").toString());
coreStorAgeConfigVo.setComponent(storageValues.get("component").toString());
JSONObject params=new JSONObject();
if(ObjectUtil.isNotNull(storageValues.get("params"))){
JSONObject valuesParams=JSONUtil.parseObj(storageValues.get("params"));
JSONObject configParams=new JSONObject();
if(ObjectUtil.isNotNull(storageConfig.get(storageType))){
configParams=JSONUtil.parseObj(storageConfig.get(storageType));
JSONArray encryptParams = ObjectUtil.defaultIfNull(storageValues.getJSONArray("encrypt_params"), new JSONArray());
JSONObject params = new JSONObject();
if (ObjectUtil.isNotNull(storageValues.get("params"))) {
JSONObject valuesParams = JSONUtil.parseObj(storageValues.get("params"));
JSONObject configParams = new JSONObject();
if (ObjectUtil.isNotNull(storageConfig.get(storageType))) {
configParams = JSONUtil.parseObj(storageConfig.get(storageType));
}
for (String paramsKey:valuesParams.keySet()) {
JSONObject itemParam=new JSONObject();
String paramsValues=valuesParams.get(paramsKey).toString();
for (String paramsKey : valuesParams.keySet()) {
JSONObject itemParam = new JSONObject();
String paramsValues = valuesParams.get(paramsKey).toString();
itemParam.set("name", paramsValues);
String value = configParams.getStr(paramsKey);
if("secret_key".equals(paramsKey) && ObjectUtil.isNotEmpty(value)){
if (encryptParams.contains(paramsKey) && ObjectUtil.isNotEmpty(value)) {
value = StringUtils.hide(value, 0, value.length());
}
itemParam.set("value", value);
@ -92,39 +97,42 @@ public class StorageConfigServiceImpl implements IStorageConfigService {
*/
@Override
public void setStorageConfig(String storageType, JSONObject storageData) {
JSONObject storageTypeList= UploadLoader.getType();
if(ObjectUtil.isNull(storageTypeList.get(storageType))){
JSONObject storageTypeList = UploadLoader.getType();
if (ObjectUtil.isNull(storageTypeList.get(storageType))) {
throw new AdminException("云存储类型不存在");
}
if(!storageType.equals(FileEnum.LOCAL.getCode())){
String domain=storageData.getStr("domain");
if(domain.indexOf("http://")<0 && domain.indexOf("https://")<0){
if (!storageType.equals(FileEnum.LOCAL.getCode())) {
String domain = storageData.getStr("domain");
if (domain.indexOf("http://") < 0 && domain.indexOf("https://") < 0) {
throw new AdminException("空间域名请补全http://或https://");
}
}
/**
* 获取配置
*/
JSONObject storageConfig=coreStorageService.getStorageConfig(RequestUtils.siteId());
JSONObject storageConfig = coreStorageService.getStorageConfig(RequestUtils.siteId());
JSONObject storageValues= JSONUtil.parseObj(storageTypeList.get(storageType));
if(storageData.getInt("is_use")==1){
JSONObject storageValues = JSONUtil.parseObj(storageTypeList.get(storageType));
if (storageData.getInt("is_use") == 1) {
storageConfig.set("default", storageType);
}else{
if(storageData.getStr("storage_type").equals(storageConfig.get("default"))){
} else {
if (storageData.getStr("storage_type").equals(storageConfig.get("default"))) {
storageConfig.set("default", "");
}
}
JSONObject configParams=new JSONObject();
if(ObjectUtil.isNotNull(storageConfig.get(storageType))){
configParams=JSONUtil.parseObj(storageConfig.get(storageType));
JSONObject configParams = new JSONObject();
if (ObjectUtil.isNotNull(storageConfig.get(storageType))) {
configParams = JSONUtil.parseObj(storageConfig.get(storageType));
}
if(ObjectUtil.isNotNull(storageValues.get("params"))){
JSONObject valuesParams=JSONUtil.parseObj(storageValues.get("params"));
for (String paramsKey:valuesParams.keySet()) {
configParams.set(paramsKey, storageData.getStr(paramsKey));
if (ObjectUtil.isNotNull(storageValues.get("params"))) {
JSONObject valuesParams = JSONUtil.parseObj(storageValues.get("params"));
for (String paramsKey : valuesParams.keySet()) {
String value = storageData.getStr(paramsKey);
if (!value.contains("*")) {
configParams.set(paramsKey, value);
}
}
}

View File

@ -10,6 +10,7 @@ import com.niu.core.common.utils.RequestUtils;
import com.niu.core.common.utils.mapper.QueryMapperUtils;
import com.niu.core.common.config.GlobalConfig;
import com.niu.core.entity.verify.Verify;
import com.niu.core.event.sys.VerifyInfoEventDefiner;
import com.niu.core.mapper.verify.VerifyMapper;
import com.niu.core.service.admin.member.vo.MemberBriefInfoVo;
import com.niu.core.service.admin.verify.param.VerifySearchParam;
@ -17,10 +18,13 @@ import com.niu.core.service.admin.verify.IVerifyService;
import com.niu.core.service.admin.verify.vo.VerifyInfoVo;
import com.niu.core.service.admin.verify.vo.VerifyListVo;
import com.niu.core.service.admin.verify.vo.VerifyVo;
import com.niu.core.service.core.app.helper.EventAndSubscribeOfPublisher;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import jakarta.annotation.Resource;
import org.springframework.util.CollectionUtils;
import java.util.LinkedList;
import java.util.List;
@ -55,6 +59,9 @@ public class VerifyServiceImpl implements IVerifyService {
if (ObjectUtil.isNotEmpty(searchParam.getType())) queryWrapper.eq("type", searchParam.getType());
if (ObjectUtil.isNotEmpty(searchParam.getVerifierMemberId())) queryWrapper.eq("verifier_member_id", searchParam.getVerifierMemberId());
if (ObjectUtil.isNotEmpty(searchParam.getCreateTime())) QueryMapperUtils.buildByTime(queryWrapper, "v.create_time", searchParam.getCreateTime());
if (ObjectUtil.isNotEmpty(searchParam.getOrderId())){
queryWrapper.like("data", searchParam.getOrderId());
}
IPage<VerifyVo> iPage = verifyMapper.selectJoinPage(new Page<>(page, limit), VerifyVo.class, queryWrapper);
List<VerifyListVo> list = new LinkedList<>();
@ -93,6 +100,17 @@ public class VerifyServiceImpl implements IVerifyService {
BeanUtils.copyProperties(model, memberInfoVo);
vo.setMember(memberInfoVo);
VerifyInfoEventDefiner.VerifyInfoEvent event = new VerifyInfoEventDefiner.VerifyInfoEvent();
event.setData(vo);
List<VerifyInfoEventDefiner.VerifyInfoEventResult> list = EventAndSubscribeOfPublisher.publishAndCallback(event);
if (!CollectionUtils.isEmpty(list)){
for (VerifyInfoEventDefiner.VerifyInfoEventResult verifyInfoEventResult : list) {
if (verifyInfoEventResult != null && verifyInfoEventResult.getDataMap() != null && !verifyInfoEventResult.getDataMap().isEmpty()){
vo.setVerifyInfo(verifyInfoEventResult.getDataMap());
break;
}
}
}
return vo;
}
}

View File

@ -27,4 +27,6 @@ public class VerifySearchParam implements Serializable {
private String relateTag;
private String[] createTime;
private Integer orderId;
}

View File

@ -6,6 +6,7 @@ import com.niu.core.service.admin.member.vo.MemberBriefInfoVo;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* Verify视图
@ -29,6 +30,7 @@ public class VerifyInfoVo implements Serializable {
private String relateTag; // 业务标识
private MemberBriefInfoVo member; // 会员信息
private String typeName;
private Map<String, Object> verifyInfo;
public String getTypeName() {
return VerifyTypeEnum.getTypeByName(this.type);

Some files were not shown because too many files have changed in this diff Show More