refactor: 代码规范、Bug修复

This commit is contained in:
2025-09-26 09:10:37 +08:00
parent 3610f8f7db
commit dcc288acc4
572 changed files with 41415 additions and 44746 deletions
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
is_weekend() {
day=$(date +%u)
[ "$day" -eq 6 ] || [ "$day" -eq 7 ]
}
if is_weekend; then
# 如果是周末,使用当前时间
git add .
git commit -m "$1"
git push
else
# 如果不是周末,设置为最近的周末时间
day=$(date +%u)
if [ "$day" -le 5 ]; then
# 周一到周五,设置为即将到来的周六
days_until_saturday=$((6 - day))
weekend_date=$(date -d "$days_until_saturday days" "+%Y-%m-%d 12:00:00")
else
# 周日,使用昨天(周六)
weekend_date=$(date -d "yesterday" "+%Y-%m-%d 12:00:00")
fi
export GIT_AUTHOR_DATE="$weekend_date"
export GIT_COMMITTER_DATE="$weekend_date"
git add .
git commit -m "$1"
git push
unset GIT_AUTHOR_DATE
unset GIT_COMMITTER_DATE
fi
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "cutejava-front", "name": "cutejava-front",
"version": "1.3.0", "version": "1.4.0",
"description": "CuteJava 前端源码", "description": "CuteJava 前端源码",
"author": "Odboy", "author": "Odboy",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -48,6 +48,7 @@
"element-ui": "^2.15.14", "element-ui": "^2.15.14",
"file-saver": "1.3.8", "file-saver": "1.3.8",
"fuse.js": "3.4.4", "fuse.js": "3.4.4",
"html2canvas": "^1.4.1",
"js-beautify": "^1.10.2", "js-beautify": "^1.10.2",
"js-cookie": "2.2.0", "js-cookie": "2.2.0",
"jsencrypt": "^3.0.0-rc.1", "jsencrypt": "^3.0.0-rc.1",
@@ -1,53 +0,0 @@
import request from '@/utils/request'
export function startPipeline(data) {
return request({
url: 'api/devops/pipelineInstance/start',
method: 'post',
data
})
}
export function stopPipeline(data) {
return request({
url: 'api/devops/pipelineInstance/stop',
method: 'post',
data
})
}
export function retryPipeline(data) {
return request({
url: 'api/devops/pipelineInstance/retry',
method: 'post',
data
})
}
export function restartPipeline(data) {
return request({
url: 'api/devops/pipelineInstance/restart',
method: 'post',
data
})
}
export function queryLastPipelineDetail(instanceId) {
return request({
url: 'api/devops/pipelineInstance/last',
method: 'post',
data: {
instanceId
}
})
}
export function queryLastPipelineDetailWs(instanceId) {
return request({
url: 'api/devops/pipelineInstance/lastWs',
method: 'post',
data: {
instanceId
}
})
}
@@ -1,9 +0,0 @@
import request from '@/utils/request'
export function getPipelineTemplate(id) {
return request({
url: 'api/devops/pipelineTemplate/getPipelineTemplate',
method: 'post',
data: { id }
})
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

@@ -34,6 +34,7 @@
:disabled="disabledDle" :disabled="disabledDle"
type="text" type="text"
size="mini" size="mini"
style="color: #F56C6C"
@click.stop="toDelete" @click.stop="toDelete"
>删除</el-button> >删除</el-button>
</el-popover> </el-popover>
+13
View File
@@ -88,6 +88,19 @@ Vue.filter('priceFormat', function(data) {
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.config.errorHandler = function(err, vm, info) {
// err: 错误对象
// vm: 发生错误的 Vue 实例
// info: Vue 特定的错误信息,如生命周期钩子、事件等
console.error('Global Vue error:', err)
console.log('Component:', vm)
console.log('Error info:', info)
// 可以在这里发送错误日志到服务器
// logErrorToServer(err, vm, info);
}
new Vue({ new Vue({
el: '#app', el: '#app',
router, router,
+59 -2
View File
@@ -70,8 +70,21 @@ export function DeepCopyObject(obj) {
* @returns {*|number} * @returns {*|number}
*/ */
export function CountArraysObjectByPropKey(arr, propKey, targetValue) { export function CountArraysObjectByPropKey(arr, propKey, targetValue) {
if (!Array.isArray(arr)) return 0 if (!Array.isArray(arr)) {
return arr.filter(item => item[propKey] === targetValue).length return 0
}
let cnt = 0
for (const element of arr) {
if (element.hasOwnProperty(propKey)) {
// console.error('that.pipelineInstance.propKey', propKey)
// console.error('that.pipelineInstance.element[propKey]', element[propKey])
// console.error('that.pipelineInstance.targetValue', targetValue)
if (element[propKey] === targetValue) {
cnt = cnt + 1
}
}
}
return cnt
} }
/** /**
@@ -290,6 +303,7 @@ export function MergeObject(target, source) {
}) })
return target return target
} }
/** /**
* 防抖动 * 防抖动
* @param {Function} func * @param {Function} func
@@ -328,3 +342,46 @@ export function DeBounce(func, wait, immediate = false) {
return result return result
} }
} }
/**
* 格式化表格行列中的环境字符串
* @param row
* @param column
* @param cellValue
* @param index
* @returns {string}
* @constructor
*/
export function FormatRowEnvStr(row, column, cellValue, index) {
if (cellValue === undefined) {
return ''
}
const data = {
daily: '日常环境',
stage: '预发环境',
gray: '灰度环境',
production: '生产环境'
}
const title = data[cellValue]
if (title) {
return title
}
return ''
}
export function IsEmpty(obj) {
if (obj === undefined) {
// console.error('is undefined')
return true
}
if (obj === '') {
// console.error('is ""')
return true
}
if (obj === 'undefined') {
// console.error('is "undefined"')
return true
}
// console.error('is null')
return obj === null
}
+3 -7
View File
@@ -147,16 +147,12 @@ export function IsValidTelephone(rule, value, callback) {
} }
/* 是否手机号码*/ /* 是否手机号码*/
export function IsValidPhone(rule, value, callback) { export function IsValidPhone(value) {
const reg = /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}$/ const reg = /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}$/
if (value === '' || value === undefined || value == null) { if (value === '' || value === undefined || value == null) {
callback() return false
} else { } else {
if ((!reg.test(value)) && value !== '') { return !((!reg.test(value)) && value !== '')
callback(new Error('请输入正确的电话号码'))
} else {
callback()
}
} }
} }
@@ -0,0 +1,43 @@
import Vue from 'vue'
/**
* 全屏锁定加载动画
*/
// export default function CsLoading() {
// this.instance = null
// this.show = function() {
// this.instance = Vue.prototype.$loading({
// lock: true,
// text: 'Loading',
// spinner: 'el-icon-loading',
// background: 'rgba(0, 0, 0, 0.7)'
// })
// }
// this.close = function() {
// if (this.instance) {
// this.instance.close()
// }
// }
// }
export default class CsLoading {
constructor() {
this.instance = null
}
show() {
this.instance = Vue.prototype.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
}
close() {
if (this.instance) {
this.instance.close()
}
}
}
+27
View File
@@ -0,0 +1,27 @@
<template>
<div>Demo</div>
</template>
<script>
export default {
name: 'Demo',
components: { },
props: {
value: {
type: Object,
required: true,
default: null
}
},
data() {
return {
}
},
computed: {},
watch: {},
mounted() {},
methods: {}
}
</script>
@@ -1,3 +1,9 @@
<!--
* 部门树组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-07-29
-->
<template> <template>
<el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4"> <el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4">
<div class="head-container"> <div class="head-container">
@@ -1,3 +1,9 @@
<!--
* 文件上传组件:支持拖拽上传
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-07-29
-->
<template> <template>
<el-upload <el-upload
ref="upload" ref="upload"
@@ -1,3 +1,9 @@
<!--
* 文件上传组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-07-29
-->
<template> <template>
<el-upload <el-upload
ref="upload" ref="upload"
@@ -1,9 +1,16 @@
<!--
* 单用户选择组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-07-29
-->
<template> <template>
<el-select <el-select
v-model="users" v-model="user"
style="width: 100%" style="width: 100%"
filterable filterable
remote remote
clearable
reserve-keyword reserve-keyword
placeholder="支持用户姓名、手机号、邮箱、用户名查询" placeholder="支持用户姓名、手机号、邮箱、用户名查询"
:remote-method="remoteMethod" :remote-method="remoteMethod"
@@ -34,14 +41,14 @@ export default {
}, },
data() { data() {
return { return {
users: this.value, user: this.value,
loading: false, loading: false,
options: [] options: []
} }
}, },
watch: { watch: {
value(newVal) { value(newVal) {
this.users = newVal this.user = newVal
} }
}, },
methods: { methods: {
@@ -1,6 +1,12 @@
<!--
* 产品线选择组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<el-select <el-select
v-model="selectedValue" v-model="productLine"
filterable filterable
clearable clearable
placeholder="请选择产品线" placeholder="请选择产品线"
@@ -37,12 +43,12 @@ export default {
data() { data() {
return { return {
deptOptions: [], deptOptions: [],
selectedValue: this.value productLine: this.value
} }
}, },
watch: { watch: {
value(newVal) { value(newVal) {
this.selectedValue = newVal this.productLine = newVal
} }
}, },
created() { created() {
@@ -58,7 +64,7 @@ export default {
}) })
}, },
handleChange(value) { handleChange(value) {
this.selectedValue = value this.productLine = value
for (const item of this.deptOptions) { for (const item of this.deptOptions) {
if (item.value === value) { if (item.value === value) {
this.$emit('detail', item) this.$emit('detail', item)
@@ -1,6 +1,12 @@
<!--
* 产品线级联选择组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<el-cascader <el-cascader
v-model="value" v-model="productLine"
:disabled="disabled" :disabled="disabled"
:options="deptOptions" :options="deptOptions"
style="width: 100%" style="width: 100%"
@@ -28,12 +34,12 @@ export default {
data() { data() {
return { return {
deptOptions: [], deptOptions: [],
selectedValue: this.value productLine: this.value
} }
}, },
watch: { watch: {
value(newVal) { value(newVal) {
this.selectedValue = newVal this.productLine = newVal
} }
}, },
created() { created() {
@@ -57,14 +63,15 @@ export default {
fetchDeptData() { fetchDeptData() {
queryDeptSelectProDataSource().then(data => { queryDeptSelectProDataSource().then(data => {
this.deptOptions = this.cleanEmptyChildren(data) this.deptOptions = this.cleanEmptyChildren(data)
console.error('deptOptions', this.deptOptions) // console.error('deptOptions', this.deptOptions)
}).catch(error => { }).catch(error => {
console.error('获取部门数据失败:', error) console.error('获取部门数据失败:', error)
CsMessage.Error('获取部门数据失败') CsMessage.Error('获取部门数据失败')
}) })
}, },
handleChange(value) { handleChange(value) {
this.selectedValue = value this.productLine = value
// console.error('value', value)
// 绑定change事件 // 绑定change事件
this.$emit('change', value) this.$emit('change', value)
// 绑定form value // 绑定form value
@@ -1,3 +1,9 @@
<!--
* 多用户选择组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-07
-->
<template> <template>
<el-select <el-select
v-model="users" v-model="users"
@@ -31,6 +37,11 @@ export default {
type: Array, type: Array,
required: false, required: false,
default: null default: null
},
dataSource: {
type: Array,
required: false,
default: null
} }
}, },
data() { data() {
@@ -45,6 +56,11 @@ export default {
this.users = newVal this.users = newVal
} }
}, },
mounted() {
if (this.dataSource && this.dataSource.length > 0) {
this.options = this.dataSource
}
},
methods: { methods: {
remoteMethod(query) { remoteMethod(query) {
if (query !== '') { if (query !== '') {
@@ -1,8 +1,11 @@
<!--Yaml编辑器 基于--> <!--
<!--<a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>,--> * 代码编辑器
<!--主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a>--> * @author odboy
<!--</p>--> * @email tianjun@odboy.cn
<!-- mode取值如下:yaml、java、go、swift、dockerfile、groovy、lua、perl、python、ruby、sql、xml、vue --> * @created 2025-08-01
* @description 基于Yaml编辑器 https://github.com/codemirror/CodeMirror,主题预览地址 https://codemirror.net/demo/theme.html#idea
* @参数mode取值范围 yaml、java、go、swift、dockerfile、groovy、lua、perl、python、ruby、sql、xml、vue
-->
<template> <template>
<textarea ref="textarea" /> <textarea ref="textarea" />
</template> </template>
@@ -1,3 +1,9 @@
<!--
* 拖拽表格组件:封装自定义逻辑与参数,表格支持拖拽排序
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<div> <div>
<el-table <el-table
@@ -151,6 +157,9 @@ export default {
data.forEach((item, index) => { data.forEach((item, index) => {
item.orderNum = index + 1 item.orderNum = index + 1
}) })
},
getDataSource() {
return this.crud.dataSource
} }
} }
} }
@@ -1,3 +1,9 @@
<!--
* 简单表单弹窗:封装了常用方法和参数
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<el-dialog <el-dialog
:title="title" :title="title"
@@ -17,7 +23,7 @@
<div class="dialog-footer" style="padding-right: 30px;text-align: right"> <div class="dialog-footer" style="padding-right: 30px;text-align: right">
<el-button type="danger" @click="hidden">取 消</el-button> <el-button type="danger" @click="hidden">取 消</el-button>
<!--<el-button @click="resetForm('form')">重 置</el-button>--> <!--<el-button @click="resetForm('form')">重 置</el-button>-->
<el-button type="primary" @click="submitForm('form')">提 交</el-button> <el-button v-prevent-re-click type="primary" @click="submitForm('form')">提 交</el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
@@ -1,3 +1,9 @@
<!--
* 简单表单抽屉:封装了常用方法和参数
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<el-drawer <el-drawer
:title="title" :title="title"
@@ -6,8 +12,9 @@
:before-close="beforeClose" :before-close="beforeClose"
:append-to-body="true" :append-to-body="true"
:close-on-press-escape="false" :close-on-press-escape="false"
:wrapper-closable="false"
:destroy-on-close="true" :destroy-on-close="true"
:show-close="false" :show-close="true"
:modal="false" :modal="false"
> >
<div class="container-form"> <div class="container-form">
@@ -19,7 +26,7 @@
<el-divider /> <el-divider />
<div class="dialog-footer" style="padding-right: 30px;text-align: right"> <div class="dialog-footer" style="padding-right: 30px;text-align: right">
<el-button type="danger" @click="hidden">取 消</el-button> <el-button type="danger" @click="hidden">取 消</el-button>
<el-button type="primary" @click="submitForm('form')">提 交</el-button> <el-button v-prevent-re-click type="primary" @click="submitForm('form')">提 交</el-button>
</div> </div>
</el-drawer> </el-drawer>
</template> </template>
@@ -0,0 +1,40 @@
<!--
* 横向滚动条容器
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-20
-->
<template>
<div class="overflow-panel">
<slot />
</div>
</template>
<script>
export default {
name: 'CuteHorizontalScrollBar'
}
</script>
<style scoped>
.overflow-panel {
width: 1500px; /* 固定宽度 */
overflow-x: auto; /* 允许水平滚动 */
white-space: nowrap; /* 防止内容换行,使所有内容在一行显示 */
padding: 30px 20px 30px 20px;
}
.overflow-panel::-webkit-scrollbar {
height: 5px; /* 设置水平滚动条高度 */
}
.overflow-panel::-webkit-scrollbar-track {
background: #f1f1f1; /* 滚动条底色 */
}
.overflow-panel::-webkit-scrollbar-thumb {
background: #DCDFE6; /* 滚动条颜色 */
}
</style>
@@ -1,7 +1,9 @@
<!--Yaml编辑器 基于--> <!--
<!--<a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>,--> * Java代码编辑器
<!--主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a>--> * @author odboy
<!--</p>--> * @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<textarea ref="textarea" /> <textarea ref="textarea" />
</template> </template>
@@ -0,0 +1,34 @@
<!--
* 文字超链接
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-26
-->
<template>
<el-link
type="primary"
:underline="false"
:href="href"
target="_blank"
>{{ title }}</el-link>
</template>
<script>
export default {
name: 'CuteLink',
props: {
title: {
type: String,
required: true,
default: '默认标题'
},
href: {
type: String,
required: true,
default: 'https://github.com/odboy-tianjun'
}
}
}
</script>
@@ -1,7 +1,14 @@
<!--<p class="warn-content">--> <!--<p class="warn-content">-->
<!--Markdown 基于--> <!--Markdown 基于-->
<!--<el-link type="primary" href="https://github.com/hinesboy/mavonEditor" target="_blank">MavonEditor</el-link>--> <!--<el-link type="primary" href="" target="_blank">MavonEditor</el-link>-->
<!--</p>--> <!--</p>-->
<!--
* Markdown编辑器
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
* @description 基于MavonEditor https://github.com/hinesboy/mavonEditor
-->
<template> <template>
<mavon-editor <mavon-editor
ref="md" ref="md"
@@ -0,0 +1,24 @@
<!--
* 无数据
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-20
-->
<template>
<el-empty :description="desc" />
</template>
<script>
export default {
name: 'CuteNoData',
props: {
desc: {
type: String,
required: true,
default: '暂无匹配数据'
}
}
}
</script>
@@ -1,5 +1,10 @@
<!-- 一键复制组件 --> <!--
<!-- 使用方式: <one-key-copy style="margin-left: 10pt" :content.sync="data" /> --> * 一键复制组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
* @description 使用方式:<cute-one-key-copy style="margin-left: 10pt" :content.sync="data" />
-->
<template> <template>
<el-button <el-button
v-clipboard:copy="content" v-clipboard:copy="content"
@@ -1,355 +0,0 @@
<template>
<div
class="box-card"
:style="cardStyle"
>
<div>
<div style="float: left;">
<i :class="statusIconClass" :style="statusIconStyle" />
</div>
<div class="box-name">{{ currentTemplateData.name }}</div>
<div style="clear: both" />
</div>
<div class="box-current-node">
<a @click="onCurrentNodeClick">
{{ currentNodeDisplayText }}
</a>
</div>
<el-row>
<el-col :span="12" style="text-align: left">
<!-- 流水线节点状态为fail,且节点支持重试 -->
<div v-if="showRetryButton">
<div class="box-buttons">
<el-button
v-prevent-re-click="5000"
type="text"
size="medium"
:style="getButtonStyle()"
@click="onNodeRetryClick"
>
重试
</el-button>
</div>
</div>
<!-- 流水线节点状态为success,且有操作按钮 -->
<div v-else-if="showOperationButtons">
<div class="box-buttons">
<el-button
v-for="buttonItem in currentTemplateData.buttons"
:key="buttonItem.service"
size="medium"
type="text"
:style="getButtonStyle(buttonItem)"
@click="onNodeOperationClick(buttonItem)"
>
{{ buttonItem.title }}
</el-button>
</div>
</div>
<!-- 流水线节点状态为running,且有操作按钮 -->
<div v-else-if="showOperationButtons">
<div class="box-buttons">
<el-button
v-for="buttonItem in currentTemplateData.buttons"
:key="buttonItem.service"
size="medium"
type="text"
:style="getButtonStyle(buttonItem)"
@click="onNodeOperationClick(buttonItem)"
>
{{ buttonItem.title }}
</el-button>
</div>
</div>
<!-- 占位用,为了前端样式美观些 -->
<div v-else>
<div style="width: 110px;height: 30px" />
</div>
</el-col>
<el-col :span="12">
<div class="box-execute-time">
{{ executeTimeStr }}
</div>
</el-col>
<div style="clear: both" />
</el-row>
</div>
</template>
<script>
import dayjs from 'dayjs'
import { mapGetters } from 'vuex'
import CsMessage from '@/utils/elementui/CsMessage'
export default {
name: 'CutePipelineNode',
props: {
templateData: {
type: Object,
required: true,
default: function() {
return {}
}
},
instanceData: {
type: Object,
required: false,
default: function() {
return {}
}
}
},
data() {
return {
currentTemplateData: {},
currentInstanceData: {}
}
},
// 动态计算
computed: {
...mapGetters([
'statusConst'
]),
cardStyle() {
const statusKey = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
const statusConfig = this.statusConst[statusKey] || this.statusConst.pending
return {
borderTop: `5px solid ${statusConfig.color}`
}
},
statusIconClass() {
const status = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
return this.statusConst[status].icon || 'el-icon-time'
},
statusIconStyle() {
return {
fontSize: '22px',
color: this.getStatusColor()
}
},
currentNodeDisplayText() {
return this.currentTemplateData.currentNodeMsg || this.statusConst.pending.label
},
showRetryButton() {
return this.currentTemplateData.currentNodeStatus === this.statusConst.fail.code &&
this.currentTemplateData.retry === true
},
showOperationButtons() {
const status = this.currentTemplateData.currentNodeStatus
const hasButtons = this.currentTemplateData.buttons && this.currentTemplateData.buttons.length > 0
return hasButtons && (
status === this.statusConst.success.code ||
status === this.statusConst.running.code
)
},
executeTimeStr() {
return this.renderDateTimeStr(this.currentTemplateData)
}
},
watch: {
templateData: {
handler(newVal, oldVal) {
this.currentTemplateData = { ...newVal }
},
deep: true
},
instanceData: {
handler(newVal, oldVal) {
this.currentInstanceData = { ...newVal }
},
deep: true
}
},
mounted() {
// 初始化时,设置流水线模板
this.currentTemplateData = this.templateData
this.currentInstanceData = this.instanceData
},
methods: {
getStatusColor() {
const status = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
const statusConfig = this.statusConst[status] || this.statusConst.pending
return statusConfig.color
},
getButtonStyle(buttonItem) {
if (buttonItem) {
const isPositiveAction = ['apply', 'agree', 'ok', 'success'].includes(buttonItem.code)
const status = this.currentTemplateData.currentNodeStatus
const color = isPositiveAction
? this.statusConst.running.color
: this.statusConst.fail.color
let display = ''
if (status === this.statusConst.running.code) {
display = this.getRunningNodeButtonVisibleStatus(buttonItem)
} else if (status === this.statusConst.success.code) {
display = this.getSuccessNodeButtonVisibleStatus(buttonItem)
}
return {
padding: 0,
margin: '0 10px 0 10px',
color,
display
}
}
return {
padding: 0,
margin: '0 10px 0 10px',
color: this.statusConst.running.color
}
},
/**
* 运行中节点的按钮显示
* @param buttonItem
* @returns {string}
*/
getRunningNodeButtonVisibleStatus(buttonItem) {
if (buttonItem.type === 'qrCodeDialog') {
return 'none'
}
return ''
},
/**
* 成功节点的按钮显示
* @param buttonItem
* @returns {string}
*/
getSuccessNodeButtonVisibleStatus(buttonItem) {
const that = this
// 根据按钮编码
if (buttonItem.code === that.currentTemplateData.currentNodeStatus) {
return ''
}
return 'none'
},
renderDateTimeStr(newVal) {
let executeTimeStr = ''
switch (newVal.status) {
case 'pending':
executeTimeStr = ''
break
case 'running':
case 'success':
case 'fail':
if (newVal.currentNodeStatus !== this.statusConst.pending.code) {
if (newVal.startTime && newVal.finishTime) {
executeTimeStr = this.formatTimeDifference(newVal.startTime, newVal.finishTime)
return executeTimeStr
}
if (newVal.startTime) {
executeTimeStr = this.formatTimeDifference(newVal.startTime, new Date())
return executeTimeStr
}
if (newVal.createTime) {
executeTimeStr = this.formatTimeDifference(newVal.createTime, new Date())
return executeTimeStr
}
}
break
default:
executeTimeStr = ''
break
}
return executeTimeStr
},
formatTimeDifference(createTimeStr, updateTimeStr) {
const startTime = dayjs(createTimeStr)
const endTime = dayjs(updateTimeStr)
// 计算两个时间点之间的差异(毫秒)
const diffInMilliseconds = endTime.diff(startTime)
// 将毫秒转换为秒
let totalSeconds = Math.floor(diffInMilliseconds / 1000)
const days = Math.floor(totalSeconds / (3600 * 24))
totalSeconds %= 3600 * 24
const hours = Math.floor(totalSeconds / 3600)
totalSeconds %= 3600
const minutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
// 格式化为字符串
if (days > 0) {
return `${days} 天 ${hours} 时 ${minutes} 分 ${seconds} 秒`
}
if (hours > 0) {
return `${hours} 时 ${minutes} 分 ${seconds} 秒`
}
if (minutes > 0) {
return `${minutes} 分 ${seconds} 秒`
}
if (seconds >= 0) {
return `${seconds} 秒`
}
return `${diffInMilliseconds} 毫秒`
},
onCurrentNodeClick() {
const currentTemplateData = this.currentTemplateData || { click: false }
if (!currentTemplateData.click) {
CsMessage.Warning('当前流水线节点不支持查看明细')
return
}
this.$emit('node-click', this.templateData)
},
onNodeRetryClick() {
const currentTemplateData = this.currentTemplateData || { retry: false }
if (!currentTemplateData.retry) {
CsMessage.Warning('当前流水线节点不支持重试')
return
}
const data = {
...this.currentInstanceData,
code: this.currentTemplateData.code
}
this.$emit('node-retry', data)
},
onNodeOperationClick(buttonInfo) {
const currentTemplateData = this.currentTemplateData || {}
this.$emit('node-opera', currentTemplateData, buttonInfo)
}
}
}
</script>
<style scoped>
.box-card {
width: 300px; /* 卡片宽度 */
max-width: 300px; /* 卡片宽度 */
padding: 10px;
background-color: #fff; /* 背景颜色 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影效果 */
border-radius: 8px; /* 圆角效果 */
overflow: hidden; /* 防止子元素溢出 */
transition: transform 0.3s ease, box-shadow 0.3s ease; /* 过渡效果 */
display: inline-block;
margin-right: 30px;
}
.box-card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.box-name {
float: left;
margin-left: 5px;
font-size: 16px;
line-height: 22px
}
.box-current-node {
padding: 20px;
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
font-size: 15px;
text-align: center;
}
.box-buttons {
padding: 5px;
overflow: hidden;
text-align: center;
height: 30px;
}
.box-execute-time {
text-align: right;
font-size: 12px;
height: 30px;
line-height: 30px;
}
</style>
@@ -1,3 +1,9 @@
<!--
* 预览抽屉:封装了常用的方法和属性
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<el-drawer <el-drawer
:title="title" :title="title"
@@ -6,8 +12,9 @@
:before-close="beforeClose" :before-close="beforeClose"
:append-to-body="true" :append-to-body="true"
:close-on-press-escape="false" :close-on-press-escape="false"
:wrapper-closable="wrapperClosable"
:destroy-on-close="true" :destroy-on-close="true"
:show-close="false" :show-close="true"
:modal="false" :modal="false"
> >
<!-- 这里是插槽,用于渲染传入的组件 --> <!-- 这里是插槽,用于渲染传入的组件 -->
@@ -29,6 +36,11 @@ export default {
type: String, type: String,
required: false, required: false,
default: '40%' default: '40%'
},
wrapperClosable: {
type: Boolean,
required: false,
default: false
} }
}, },
data() { data() {
@@ -1,5 +1,9 @@
<!--富文本基于--> <!--
<!--<el-link type="primary" href="https://www.wangeditor.com/v5/getting-started.html" target="_blank">wangEditor</el-link>--> * 富文本编辑器
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<div ref="editor" style="border: 1px solid #ccc;"> <div ref="editor" style="border: 1px solid #ccc;">
<Toolbar <Toolbar
@@ -0,0 +1,59 @@
<!--
* 截屏工具
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-20
-->
<template>
<div>
<div style="text-align: right">
<el-button
type="primary"
icon="el-icon-camera"
:loading="capturing"
size="small"
@click="capture"
>
截图并下载
</el-button>
</div>
<div id="elementIdSelector">
<slot />
</div>
</div>
</template>
<script>
import CsMessage from '@/utils/elementui/CsMessage'
import { FormatDateTimeShortStr } from '@/utils/CsUtil'
import html2canvas from 'html2canvas'
export default {
name: 'CuteScreenShot',
data() {
return {
capturing: false
}
},
methods: {
async capture() {
this.capturing = true
const element = document.querySelector('#elementIdSelector')
if (!element) {
CsMessage.Error('未找到匹配的元素,请检查选择器是否正确')
this.capturing = false
return
}
const canvas = await html2canvas(element)
// const base64 = canvas.toDataURL('image/png')
const link = document.createElement('a')
link.download = `screenshot_${FormatDateTimeShortStr(new Date())}.png`
link.href = canvas.toDataURL('image/png')
link.click()
this.capturing = false
}
}
}
</script>
@@ -0,0 +1,57 @@
<!--
* 查询组件
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-20
-->
<template>
<el-form
ref="searchForm"
:inline="true"
:model.sync="model"
label-position="right"
label-width="90px"
>
<slot name="suffix" />
<el-form-item>
<el-button type="primary" @click="onFormSubmit('searchForm')">查询</el-button>
<el-button @click="onFormReset('searchForm')">重置</el-button>
</el-form-item>
<slot name="operation" />
</el-form>
</template>
<script>
export default {
name: 'CuteSearchForm',
components: {},
props: {
model: {
type: Object,
required: true
}
},
methods: {
onFormSubmit(formName) {
const that = this
that.$refs[formName].validate((valid) => {
if (valid) {
that.$emit('submit', that.model)
} else {
return false
}
})
},
onFormReset(formName) {
try {
this.$refs[formName].resetFields()
this.$emit('reset')
} catch (e) {
console.error(e)
}
}
}
}
</script>
@@ -0,0 +1,33 @@
<!--
* 段落标题组件:占一行,支持嵌套按钮
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-13
-->
<template>
<el-row>
<el-col :span="12">
<h4>
<i class="el-icon-s-help" style="color: #409EFF;font-size: 20px" />
<i style="color: #303133;">{{ title }}</i>
</h4>
</el-col>
<el-col :span="12" style="text-align: right;line-height: 64px;height: 64px;padding-right: 30px">
<slot />
</el-col>
</el-row>
</template>
<script>
export default {
name: 'CuteSectionTitle',
props: {
title: {
type: String,
required: true,
default: '默认标题'
}
}
}
</script>
@@ -1,3 +1,9 @@
<!--
* 简单表格组件:封装自定义逻辑与参数
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<div> <div>
<el-table <el-table
@@ -68,6 +74,11 @@ export default {
total: 0 total: 0
} }
} }
},
customHeight: {
type: Number,
required: false,
default: null
} }
}, },
data() { data() {
@@ -89,6 +100,9 @@ export default {
}, },
computed: { computed: {
height() { height() {
if (this.customHeight) {
return this.customHeight
}
return document.documentElement.clientHeight - 300 return document.documentElement.clientHeight - 300
} }
}, },
@@ -0,0 +1,47 @@
<!--
* 纵向滚动条容器
* @author odboy
* @email tianjun@odboy.cn
* @created 2025-08-20
-->
<template>
<div class="overflow-panel">
<slot />
</div>
</template>
<script>
export default {
name: 'CuteVerticalScrollBar'
}
</script>
<style scoped>
.overflow-panel {
height: 800px;
overflow-y: scroll;
}
/* 滚动条宽度 */
.overflow-panel::-webkit-scrollbar {
width: 5px;
}
/* 滚动条轨道 */
.overflow-panel::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* 滚动条滑块 */
.overflow-panel::-webkit-scrollbar-thumb {
background: #409EFF;
border-radius: 6px;
}
/* 鼠标悬停在滑块上时 */
.overflow-panel::-webkit-scrollbar-thumb:hover {
background: #F56C6C;
}
</style>
@@ -1,7 +1,9 @@
<!--Yaml编辑器 基于--> <!--
<!--<a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>,--> * YAML编辑器
<!--主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a>--> * @author odboy
<!--</p>--> * @email tianjun@odboy.cn
* @created 2025-08-01
-->
<template> <template>
<textarea ref="textarea" /> <textarea ref="textarea" />
</template> </template>
@@ -69,7 +69,8 @@ export default {
dept: null, dept: null,
productLine: null, productLine: null,
productLinePro: null productLinePro: null
} },
searchFormModel: {}
} }
}, },
methods: { methods: {
@@ -1,304 +0,0 @@
<template>
<div>
<el-divider>演示:{{ dynamicTemplate.description ? dynamicTemplate.description: '动态流水线演示' }}
<el-button
v-prevent-re-click
style="margin-left: 10px"
type="primary"
:loading="dynamicStartButtonLoading"
:disabled="dynamicStartButtonLoading"
@click="doStartPipeline"
>{{ dynamicStartupStatusMap[dynamicStartupStatus].label }}
</el-button>
<el-button
v-prevent-re-click
:disabled="!dynamicRunning"
style="margin-left: 10px"
type="danger"
@click="doStopPipeline"
>停止
</el-button>
</el-divider>
<div class="box-pipeline">
<div v-if="dynamicInstance.nodes && dynamicInstance.nodes.length > 0" class="box-pipeline-content">
<cute-pipeline-node
v-for="(template, index) in dynamicInstance.nodes"
:key="template.code"
:template-data.sync="dynamicInstance.nodes[index]"
:instance-data.sync="dynamicInstance"
@node-click="onPipelineNodeClick"
@node-retry="onPipelineNodeRetryClick"
@node-opera="onPipelineNodeOperationClick"
/>
</div>
<div v-else class="box-pipeline-content">
<cute-pipeline-node
v-for="(template, index) in dynamicTemplateList"
:key="template.code"
:template-data.sync="dynamicTemplateList[index]"
:instance-data.sync="dynamicInstance"
/>
</div>
</div>
<cute-preview-drawer ref="detailDrawer" :title="currentNodeData.name">
<el-timeline>
<el-timeline-item timestamp="2018/4/12" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/12 20:46</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2018/4/3" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/3 20:46</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2018/4/2" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/2 20:46</p>
</el-card>
</el-timeline-item>
</el-timeline>
</cute-preview-drawer>
</div>
</template>
<script>
import CutePipelineNode from '@/views/components/dev/CutePipelineNode'
import { getPipelineTemplate } from '@/api/pipeline/pipelineTemplate'
import {
queryLastPipelineDetailWs,
restartPipeline,
retryPipeline,
startPipeline,
stopPipeline
} from '@/api/pipeline/pipelineInstance'
import { CountArraysObjectByPropKey } from '@/utils/CsUtil'
import CsMessage from '@/utils/elementui/CsMessage'
import CsWsClient from '@/utils/CsWsClient'
import { mapGetters } from 'vuex'
import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer.vue'
export default {
name: 'CutePipelineNodeDemo',
components: { CutePreviewDrawer, CutePipelineNode },
data() {
return {
testAppInfo: {
appName: 'cuteops',
pipelineTemplateEnv: 'daily',
iterationName: '流水线测试'
},
// ============== 动态流水线
dynamicStartupStatusMap: {
start: { label: '启动', code: 'start' },
restart: { label: '重新启动', code: 'restart' }
},
// 按钮状态
dynamicStartupStatus: 'start',
dynamicStartButtonLoading: false,
// 当前模板
dynamicTemplate: {
id: 21
},
// 当前流水线节点模板
dynamicTemplateList: [],
// dynamicHook: null,
// 当前流水线实例
dynamicInstance: {},
// 当前ws客户端
dynamicWsClient: null,
dynamicRunning: false,
// ============== 交互变量
currentNodeData: {}
}
},
computed: {
...mapGetters([
'user'
])
},
mounted() {
this.initPipelineTemplate(this.dynamicTemplate.id)
},
methods: {
/**
* 初始化流水线模板
* @param templateId
* @returns {Promise<void>}
*/
async initPipelineTemplate(templateId) {
const pipelineTemplate = await getPipelineTemplate(templateId)
if (pipelineTemplate && pipelineTemplate.template) {
try {
this.dynamicTemplate = pipelineTemplate
this.dynamicTemplateList = JSON.parse(pipelineTemplate.template)
} catch (e) {
// ignore
}
}
},
/**
* 连接WebSocket服务
*/
connectWebSocketServer(instanceId) {
const that = this
// {username}_{bizCode}_{contextParams}
const sid = `${that.user.username}_FetchPipelineLastDetail_${instanceId}`
if (that.dynamicWsClient) {
try {
that.dynamicWsClient.close()
} catch (e) {
// ignore
}
}
that.dynamicWsClient = new CsWsClient(sid)
that.dynamicWsClient.connect(that.handleWebSocketError, that.handleWebSocketMessage)
setTimeout(() => {
// 请求推送流水线数据
queryLastPipelineDetailWs(sid)
that.dynamicStartButtonLoading = false
that.dynamicStartupStatus = that.dynamicStartupStatusMap.restart.code
}, 1000)
},
handleWebSocketError(e) {
console.error('连接WebSocket服务失败', e)
},
handleWebSocketMessage(e) {
const that = this
const data = e.data
// console.log('来自服务器的数据:data', data)
const callbackData = JSON.parse(data)
that.dynamicRunning = true
// console.log('callbackData', callbackData)
try {
that.dynamicInstance = JSON.parse(callbackData.data)
// 判断流水线是否结束
const successCount = CountArraysObjectByPropKey(that.dynamicInstance.nodes, 'status', 'success')
if (that.dynamicTemplateList && that.dynamicTemplateList.length === successCount) {
// 所有节点执行成功
if (that.dynamicWsClient) {
sessionStorage.removeItem('pipelineInstanceId')
that.dynamicWsClient.close()
that.dynamicRunning = false
}
that.dynamicStartupStatus = that.dynamicStartupStatusMap.start.code
} else {
const failCount = CountArraysObjectByPropKey(that.dynamicInstance.nodes, 'status', 'fail')
// 存在失败的节点
if (that.dynamicWsClient && failCount > 0) {
that.dynamicWsClient.close()
that.dynamicRunning = false
}
that.dynamicStartupStatus = that.dynamicStartupStatusMap.restart.code
}
} catch (e) {
// ignore
that.dynamicRunning = false
}
},
/**
* 启动流水线
* @returns {Promise<void>}
*/
async doStartPipeline() {
const that = this
try {
that.dynamicStartButtonLoading = true
// 这里的 dynamicStartupStatus状态,需要后期与上下文关联后,初始化的时候带入最近一个流水线实例
const args = {
templateId: that.dynamicTemplate.id,
instanceName: that.testAppInfo.iterationName,
contextName: that.testAppInfo.appName,
env: that.testAppInfo.pipelineTemplateEnv
}
let result
const pipelineInstanceId = sessionStorage.getItem('pipelineInstanceId')
if (pipelineInstanceId) {
result = await restartPipeline({
...args,
instanceId: pipelineInstanceId
})
CsMessage.Success('流水线重启成功')
} else {
result = await startPipeline(args)
CsMessage.Success('流水线启动成功')
}
sessionStorage.setItem('pipelineInstanceId', result.instanceId)
that.connectWebSocketServer(result.instanceId)
} catch (e) {
console.error('error', e)
that.dynamicStartButtonLoading = false
CsMessage.Error('流水线启动失败')
}
},
/**
* 停止流水线
* @returns {Promise<void>}
*/
async doStopPipeline() {
const that = this
const args = {
templateId: that.dynamicTemplate.id,
instanceName: that.testAppInfo.iterationName,
contextName: that.testAppInfo.appName,
env: that.testAppInfo.pipelineTemplateEnv
}
const pipelineInstanceId = sessionStorage.getItem('pipelineInstanceId')
if (pipelineInstanceId) {
await stopPipeline({
...args,
instanceId: pipelineInstanceId
})
CsMessage.Success('流水线停止成功')
}
},
onPipelineNodeClick(data) {
console.log('data', data)
this.currentNodeData = data
this.$refs.detailDrawer.show()
},
async onPipelineNodeRetryClick(data) {
console.log('data', data)
const result = await retryPipeline({
instanceId: data.instanceId,
code: data.code
})
sessionStorage.setItem('pipelineInstanceId', result.instanceId)
this.connectWebSocketServer(data.instanceId)
},
async onPipelineNodeOperationClick(data, buttonInfo) {
console.log('data', data, buttonInfo)
}
}
}
</script>
<style scoped>
.box-pipeline {
width: 1500px; /* 固定宽度 */
overflow-x: auto; /* 允许水平滚动 */
white-space: nowrap; /* 防止内容换行,使所有内容在一行显示 */
padding: 30px 20px 30px 20px;
}
.box-pipeline::-webkit-scrollbar {
height: 5px; /* 设置水平滚动条高度 */
}
.box-pipeline::-webkit-scrollbar-track {
background: #f1f1f1; /* 滚动条底色 */
}
.box-pipeline::-webkit-scrollbar-thumb {
background: #DCDFE6; /* 滚动条颜色 */
}
.box-pipeline-content {
display: inline-block; /* 使内容水平排列 */
white-space: nowrap; /* 防止内容换行 */
}
</style>
@@ -50,8 +50,8 @@ export default {
' shutdown: graceful\n' + ' shutdown: graceful\n' +
'spring:\n' + 'spring:\n' +
' application:\n' + ' application:\n' +
' name: kenaito-devops\n' + ' name: cutejava\n' +
' title: Kenaito DevOps\n' + ' title: CuteJava\n' +
' lifecycle:\n' + ' lifecycle:\n' +
' # 采用java.time.Duration格式的值,如果在这个时间内,优雅停机没有停掉应用,超过这个时间就会强制停止应用\n' + ' # 采用java.time.Duration格式的值,如果在这个时间内,优雅停机没有停掉应用,超过这个时间就会强制停止应用\n' +
' timeout-per-shutdown-phase: 30s\n' + ' timeout-per-shutdown-phase: 30s\n' +
+22 -10
View File
@@ -1,5 +1,7 @@
<template> <template>
<div class="login" :style="'background-image:url('+ Background +');'"> <div class="login" :style="'background-image:url('+ Background +');'">
<div class="login-box">
<div class="login-form-box">
<el-form <el-form
ref="loginForm" ref="loginForm"
:model="loginForm" :model="loginForm"
@@ -8,9 +10,7 @@
label-width="0px" label-width="0px"
class="login-form" class="login-form"
> >
<h3 class="title"> <h3 class="title">CuteJava 管理系统</h3>
CuteJava 后台管理系统
</h3>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号"> <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
@@ -49,7 +49,7 @@
:loading="loading" :loading="loading"
size="medium" size="medium"
type="primary" type="primary"
style="width:100%;" style="width:100%;background-color: #737bfe;border-color: #737bfe;"
@click.native.prevent="handleLogin" @click.native.prevent="handleLogin"
> >
<span v-if="!loading">登 录</span> <span v-if="!loading">登 录</span>
@@ -57,6 +57,7 @@
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div>
<!-- 底部 --> <!-- 底部 -->
<div v-if="$store.state.settings.showFooter" id="el-login-footer"> <div v-if="$store.state.settings.showFooter" id="el-login-footer">
<span v-html="$store.state.settings.footerTxt" /> <span v-html="$store.state.settings.footerTxt" />
@@ -64,6 +65,7 @@
<a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ $store.state.settings.caseNumber }}</a> <a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ $store.state.settings.caseNumber }}</a>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@@ -72,7 +74,7 @@ import Config from '@/settings'
import { getCodeImg } from '@/api/login' import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import qs from 'qs' import qs from 'qs'
import Background from '@/assets/images/background.jpeg' import Background from '@/assets/images/background.png'
export default { export default {
name: 'Login', name: 'Login',
@@ -205,12 +207,24 @@ export default {
margin: 0 auto 30px auto; margin: 0 auto 30px auto;
text-align: center; text-align: center;
font-size: xx-large; font-size: xx-large;
color: white; color: #7179fe;
}
.login-box {
background: rgba(242, 246, 252, 0.3);
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.login-form-box {
width: 360px;
height: 370px;
background-color: #FFFFFF;
} }
.login-form { .login-form {
background: rgba(255, 255, 255, 0.40);
width: 400px;
padding: 10% 25px 5px 25px; padding: 10% 25px 5px 25px;
.el-input { .el-input {
@@ -226,8 +240,6 @@ export default {
width: 14px; width: 14px;
margin-left: 2px; margin-left: 2px;
} }
float: right;
} }
.login-tip { .login-tip {
@@ -73,7 +73,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -98,7 +98,7 @@
<el-switch <el-switch
v-model="scope.row.enabled" v-model="scope.row.enabled"
:disabled="scope.row.id === 1" :disabled="scope.row.id === 1"
active-color="#409EFF" active-color="#67C23A"
inactive-color="#F56C6C" inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)" @change="changeEnabled(scope.row, scope.row.enabled,)"
/> />
@@ -47,7 +47,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -18,7 +18,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -24,7 +24,7 @@
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <el-switch
v-model="scope.row.enabled" v-model="scope.row.enabled"
active-color="#409EFF" active-color="#67C23A"
inactive-color="#F56C6C" inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)" @change="changeEnabled(scope.row, scope.row.enabled)"
/> />
@@ -128,7 +128,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -146,7 +146,7 @@
@selection-change="crud.selectionChangeHandler" @selection-change="crud.selectionChangeHandler"
> >
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" label="菜单标题" width="125px" prop="title" /> <el-table-column :show-overflow-tooltip="true" label="菜单标题" width="220px" prop="title" />
<el-table-column prop="icon" label="图标" align="center" width="60px"> <el-table-column prop="icon" label="图标" align="center" width="60px">
<template slot-scope="scope"> <template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" /> <svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" />
@@ -81,7 +81,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -62,7 +62,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -61,7 +61,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button> <el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button>
<el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
@@ -58,7 +58,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button> <el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button>
<el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
@@ -149,7 +149,7 @@ import updatePass from './center/updatePass'
import updateEmail from './center/updateEmail' import updateEmail from './center/updateEmail'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import store from '@/store' import store from '@/store'
import { isvalidPhone } from '@/utils/CsValidateUtil' import { IsValidPhone } from '@/utils/CsValidateUtil'
import crud from '@/mixins/crud' import crud from '@/mixins/crud'
import { editUser } from '@/api/system/user' import { editUser } from '@/api/system/user'
import Avatar from '@/assets/images/avatar.png' import Avatar from '@/assets/images/avatar.png'
@@ -163,7 +163,7 @@ export default {
const validPhone = (rule, value, callback) => { const validPhone = (rule, value, callback) => {
if (!value) { if (!value) {
callback(new Error('请输入电话号码')) callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) { } else if (!IsValidPhone(value)) {
callback(new Error('请输入正确的11位手机号码')) callback(new Error('请输入正确的11位手机号码'))
} else { } else {
callback() callback()
@@ -58,7 +58,7 @@
:before-close="crud.cancelCU" :before-close="crud.cancelCU"
:visible.sync="crud.status.cu > 0" :visible.sync="crud.status.cu > 0"
:title="crud.status.title" :title="crud.status.title"
width="555px" width="640px"
> >
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px"> <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px">
<el-form-item label="用户名" prop="username"> <el-form-item label="用户名" prop="username">
@@ -119,7 +119,6 @@
<el-select <el-select
v-model="roleDatas" v-model="roleDatas"
:disabled="form.id === user.id" :disabled="form.id === user.id"
style="width: 426px"
multiple multiple
placeholder="请选择" placeholder="请选择"
@remove-tag="deleteTag" @remove-tag="deleteTag"
@@ -136,7 +135,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
@@ -164,7 +163,7 @@
<el-switch <el-switch
v-model="scope.row.enabled" v-model="scope.row.enabled"
:disabled="user.id === scope.row.id" :disabled="user.id === scope.row.id"
active-color="#409EFF" active-color="#67C23A"
inactive-color="#F56C6C" inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)" @change="changeEnabled(scope.row, scope.row.enabled)"
/> />
@@ -196,7 +195,7 @@
<script> <script>
import crudUser from '@/api/system/user' import crudUser from '@/api/system/user'
import { isvalidPhone } from '@/utils/CsValidateUtil' import { IsValidPhone } from '@/utils/CsValidateUtil'
import { queryDeptList, queryDeptSuperiorTree } from '@/api/system/dept' import { queryDeptList, queryDeptSuperiorTree } from '@/api/system/dept'
import { getLevel, queryRoleList } from '@/api/system/role' import { getLevel, queryRoleList } from '@/api/system/role'
import { queryAllEnableJob } from '@/api/system/job' import { queryAllEnableJob } from '@/api/system/job'
@@ -239,7 +238,7 @@ export default {
const validPhone = (rule, value, callback) => { const validPhone = (rule, value, callback) => {
if (!value) { if (!value) {
callback(new Error('请输入电话号码')) callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) { } else if (!IsValidPhone(value)) {
callback(new Error('请输入正确的11位手机号码')) callback(new Error('请输入正确的11位手机号码'))
} else { } else {
callback() callback()
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.annotation; package cn.odboy.annotation;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
@@ -71,5 +72,4 @@ public @interface AnonymousDeleteMapping {
* Alias for {@link RequestMapping#produces}. * Alias for {@link RequestMapping#produces}.
*/ */
@AliasFor(annotation = RequestMapping.class) String[] produces() default {}; @AliasFor(annotation = RequestMapping.class) String[] produces() default {};
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.annotation; package cn.odboy.annotation;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
@@ -71,5 +72,4 @@ public @interface AnonymousGetMapping {
* Alias for {@link RequestMapping#produces}. * Alias for {@link RequestMapping#produces}.
*/ */
@AliasFor(annotation = RequestMapping.class) String[] produces() default {}; @AliasFor(annotation = RequestMapping.class) String[] produces() default {};
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.annotation; package cn.odboy.annotation;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
@@ -71,5 +72,4 @@ public @interface AnonymousPatchMapping {
* Alias for {@link RequestMapping#produces}. * Alias for {@link RequestMapping#produces}.
*/ */
@AliasFor(annotation = RequestMapping.class) String[] produces() default {}; @AliasFor(annotation = RequestMapping.class) String[] produces() default {};
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.annotation; package cn.odboy.annotation;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
@@ -71,5 +72,4 @@ public @interface AnonymousPostMapping {
* Alias for {@link RequestMapping#produces}. * Alias for {@link RequestMapping#produces}.
*/ */
@AliasFor(annotation = RequestMapping.class) String[] produces() default {}; @AliasFor(annotation = RequestMapping.class) String[] produces() default {};
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.annotation; package cn.odboy.annotation;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
@@ -71,5 +72,4 @@ public @interface AnonymousPutMapping {
* Alias for {@link RequestMapping#produces}. * Alias for {@link RequestMapping#produces}.
*/ */
@AliasFor(annotation = RequestMapping.class) String[] produces() default {}; @AliasFor(annotation = RequestMapping.class) String[] produces() default {};
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import lombok.Getter; import lombok.Getter;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
@@ -0,0 +1,67 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.data.annotation.CreatedBy;
import java.lang.reflect.Field;
import java.util.Date;
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
public class CsBaseUserCreateTimeTb extends CsObject {
@CreatedBy
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建人", hidden = true)
private String createBy;
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
private Date createTime;
/* 分组校验 */
public @interface Create {
}
/* 分组校验 */
public @interface Update {
}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Field[] fields = this.getClass().getDeclaredFields();
try {
for (Field f : fields) {
f.setAccessible(true);
builder.append(f.getName(), f.get(this)).append("\n");
}
} catch (Exception e) {
builder.append("toString builder encounter an error");
}
return builder.toString();
}
}
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import java.io.Serializable; import java.io.Serializable;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.base; package cn.odboy.base;
import lombok.*; import lombok.*;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.constant; package cn.odboy.constant;
/** /**
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.constant; package cn.odboy.constant;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -22,7 +23,11 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum FileTypeEnum { public enum FileTypeEnum {
/* 文件类型 */ /* 文件类型 */
IMAGE("image", "图片"), DOC("doc", "文档"), VOICE("voice", "音频"), VIDEO("video", "视频"), OTHER("other", "其他"); IMAGE("image", "图片"),
DOC("doc", "文档"),
VOICE("voice", "音频"),
VIDEO("video", "视频"),
OTHER("other", "其他");
private final String code; private final String code;
private final String description; private final String description;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.constant; package cn.odboy.constant;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.constant; package cn.odboy.constant;
/** /**
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.context; package cn.odboy.framework.context;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.context; package cn.odboy.framework.context;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@@ -41,8 +42,13 @@ public class BootApplication {
Environment env = application.getEnvironment(); Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress(); String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port"); String port = env.getProperty("server.port");
String path = StrUtil.isEmpty(env.getProperty("config.servlet.context-path")) ? "" : env.getProperty("config.servlet.context-path"); String path = StrUtil.isEmpty(env.getProperty("config.servlet.context-path")) ?
"" :
env.getProperty("config.servlet.context-path");
log.info( log.info(
"\n----------------------------------------------------------\n\t" + "Application is running! Access URLs:\n\t" + "Local: \t\thttp://localhost:" + port + path + "/\n\t" + "External: \thttp://" + ip + ":" + port + path + "/\n\t" + "Swagger文档: \thttp://" + ip + ":" + port + path + "/doc.html\n" + "----------------------------------------------------------"); "\n----------------------------------------------------------\n\t" +
"Application is running! Access URLs:\n\t" + "Local: \t\thttp://localhost:" + port + path + "/\n\t" +
"External: \thttp://" + ip + ":" + port + path + "/\n\t" + "Swagger文档: \thttp://" + ip + ":" + port +
path + "/doc.html\n" + "----------------------------------------------------------");
} }
} }
@@ -1,3 +1,20 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.context; package cn.odboy.framework.context;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
@@ -12,6 +29,6 @@ import java.util.Objects;
public class CsRequestHolder { public class CsRequestHolder {
public static HttpServletRequest getHttpServletRequest() { public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
} }
} }
@@ -13,86 +13,51 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.context; package cn.odboy.framework.context;
import cn.hutool.core.util.ReflectUtil; import lombok.extern.slf4j.Slf4j;
import cn.hutool.script.ScriptRuntimeException; import org.mozilla.javascript.Context;
import cn.odboy.framework.exception.BadRequestException; import org.mozilla.javascript.Scriptable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.script.*;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* 脚本工具 * 脚本工具
* *
* @author odboy * @author odboy
* @date 2023-12-05 * @date 2025-09-23
*/ */
@Slf4j
@Component @Component
public class CsScriptHelper { public class CsScriptHelper {
private static final String FUNCTION_TAG = "function"; public Object evalObj(String script, Map<String, Object> args) {
private final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); try (Context rhino = Context.enter()) {
Scriptable scope = rhino.initStandardObjects();
/** bindArgumentsToScope(scope, args);
* @param script 函数表达式 Object result = rhino.evaluateString(scope, script, "JavaScript", 1, null);
* @return 返回值 log.info("执行结果: {}", Context.toString(result));
*/ return result;
public Object evalObj(String script, Object compareObj) {
try {
if (script.contains(FUNCTION_TAG)) {
throw new BadRequestException("script格式异常");
}
// 构建一个不同的脚本上下文
ScriptContext newContext = new SimpleScriptContext();
if (compareObj != null) {
Field[] fields = ReflectUtil.getFields(compareObj.getClass());
Map<String, Object> filedNameValueMap = new HashMap<>(3);
Arrays.stream(fields).parallel().forEach(f -> filedNameValueMap.put(f.getName(), ReflectUtil.getFieldValue(compareObj, f)));
Set<Map.Entry<String, Object>> entries = filedNameValueMap.entrySet();
long count = entries.stream().filter(f -> f.getValue() != null).count();
if (count == 0) {
throw new BadRequestException("比较对象中没有有效值");
}
Bindings engineScope = engine.createBindings();
entries.forEach(f -> {
// 在新的执行引擎作用域中添加与上面相同名称的变量
engineScope.put(f.getKey(), f.getValue());
});
newContext.setBindings(engineScope, ScriptContext.ENGINE_SCOPE);
}
// 执行脚本, 注意这里指定新的执行作用域上下文
return engine.eval(script, newContext);
} catch (ScriptException e) {
throw new ScriptRuntimeException(e);
} }
} }
/** private void bindArgumentsToScope(Scriptable scope, Map<?, ?> args) {
* @param script 函数表达式, 例如: function hello(name) { print('Hello, ' + name); } if (args == null) {
* @param functionName 函数名称, 例如: "hello" return;
* @param args 函数参数, 例如: "小明"
* @return 返回值
*/
public Object evalFunction(String script, String functionName, Object... args) {
if (!script.contains(FUNCTION_TAG)) {
throw new BadRequestException("script格式异常");
} }
try { for (Map.Entry<?, ?> entry : args.entrySet()) {
// 执行脚本 Object jsValue = Context.javaToJS(entry.getValue(), scope);
engine.eval(script); scope.put(entry.getKey().toString(), scope, jsValue);
// 由ScriptEngines实现的接口, 其方法允许调用以前已执行的脚本中的过程。
Invocable inv = (Invocable)engine;
// 调用全局函数, 并传入参数
return inv.invokeFunction(functionName, args);
} catch (ScriptException e) {
throw new ScriptRuntimeException(e);
} catch (NoSuchMethodException e) {
throw new ScriptRuntimeException(e);
} }
} }
private void bindArgumentsToScope(Scriptable scope, Object args) {
if (args == null) {
return;
}
// 单个对象参数
Object jsValue = Context.javaToJS(args, scope);
scope.put("args", scope, jsValue);
}
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.context; package cn.odboy.framework.context;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -54,7 +55,7 @@ public class CsSpringBeanHolder implements ApplicationContextAware, DisposableBe
*/ */
public static <T> T getBean(String name) { public static <T> T getBean(String name) {
assertContextInjected(); assertContextInjected();
return (T)applicationContext.getBean(name); return (T) applicationContext.getBean(name);
} }
/** /**
@@ -109,7 +110,8 @@ public class CsSpringBeanHolder implements ApplicationContextAware, DisposableBe
private static void assertContextInjected() { private static void assertContextInjected() {
if (applicationContext == null) { if (applicationContext == null) {
throw new IllegalStateException( throw new IllegalStateException(
"applicaitonContext属性未注入, 请在applicationContext" + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); "applicaitonContext属性未注入, 请在applicationContext" +
".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
} }
} }
@@ -129,7 +131,8 @@ public class CsSpringBeanHolder implements ApplicationContextAware, DisposableBe
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (CsSpringBeanHolder.applicationContext != null) { if (CsSpringBeanHolder.applicationContext != null) {
log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + CsSpringBeanHolder.applicationContext); log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" +
CsSpringBeanHolder.applicationContext);
} }
CsSpringBeanHolder.applicationContext = applicationContext; CsSpringBeanHolder.applicationContext = applicationContext;
if (addCallback) { if (addCallback) {
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.doc; package cn.odboy.framework.doc;
import cn.odboy.constant.SystemConst; import cn.odboy.constant.SystemConst;
@@ -59,14 +60,23 @@ public class SwaggerConfig {
@Bean @Bean
public Docket createRestApi() { public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).enable(properties.getSwagger().getEnabled()).pathMapping("/").apiInfo(apiInfo()).select() return new Docket(DocumentationType.SWAGGER_2).enable(properties.getSwagger().getEnabled())
.paths(PathSelectors.regex("^(?!/error).*")).paths(PathSelectors.any()).build() .pathMapping("/")
.apiInfo(apiInfo())
.select()
.paths(PathSelectors.regex("^(?!/error).*"))
.paths(PathSelectors.any())
.build()
//添加登陆认证 //添加登陆认证
.securitySchemes(securitySchemes()).securityContexts(securityContexts()); .securitySchemes(securitySchemes())
.securityContexts(securityContexts());
} }
private ApiInfo apiInfo() { private ApiInfo apiInfo() {
return new ApiInfoBuilder().description("一个简单且易上手的 Spring boot 后台管理框架").title("CuteJava 接口文档").version("1.4.1").build(); return new ApiInfoBuilder().description("一个简单且易上手的自动化运维平台")
.title("CuteJava 接口文档")
.version("1.4.1")
.build();
} }
private List<SecurityScheme> securitySchemes() { private List<SecurityScheme> securitySchemes() {
@@ -121,7 +131,8 @@ public class SwaggerConfig {
} }
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) { private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> filteredMappings = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList()); List<T> filteredMappings =
mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList());
mappings.clear(); mappings.clear();
mappings.addAll(filteredMappings); mappings.addAll(filteredMappings);
} }
@@ -131,7 +142,7 @@ public class SwaggerConfig {
if (field != null) { if (field != null) {
field.setAccessible(true); field.setAccessible(true);
try { try {
return (List<RequestMappingInfoHandlerMapping>)field.get(bean); return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new IllegalStateException("Failed to access handlerMappings field", e); throw new IllegalStateException("Failed to access handlerMappings field", e);
} }
@@ -1,39 +0,0 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* 统一异常处理
*/
@Getter
public class BadRequestException extends RuntimeException {
private Integer status = BAD_REQUEST.value();
public BadRequestException(String msg) {
super(msg);
}
public BadRequestException(HttpStatus status, String msg) {
super(msg);
this.status = status.value();
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.exception.handler;
import lombok.Data;
@Data
public final class ApiError {
private Integer status = 400;
private Long timestamp;
private String message;
private ApiError() {
timestamp = System.currentTimeMillis();
}
public static ApiError error(final String message) {
ApiError apiError = new ApiError();
apiError.setMessage(message);
return apiError;
}
public static ApiError error(final Integer status, final String message) {
ApiError apiError = new ApiError();
apiError.setStatus(status);
apiError.setMessage(message);
return apiError;
}
}
@@ -0,0 +1,50 @@
// /*
// * Copyright 2021-2025 Odboy
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
// package cn.odboy.framework.exception.thirdapi;
//
// import java.lang.annotation.Documented;
// import java.lang.annotation.ElementType;
// import java.lang.annotation.Inherited;
// import java.lang.annotation.Retention;
// import java.lang.annotation.RetentionPolicy;
// import java.lang.annotation.Target;
//
// /**
// * 用于捕捉三方异常
// *
// * @author odboy
// */
// @Inherited
// @Documented
// @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
// @Retention(RetentionPolicy.RUNTIME)
// public @interface ThirdApiExceptionCatch {
// /**
// * 模块名称
// */
// String modelName();
//
// /**
// * 接口描述
// */
// String description();
//
// /**
// * 是否抛出异常
// */
// boolean throwException() default true;
// }
@@ -0,0 +1,148 @@
// /*
// * Copyright 2021-2025 Odboy
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
// package cn.odboy.framework.exception.thirdapi;
//
// import cn.hutool.core.date.TimeInterval;
// import cn.odboy.framework.exception.thirdapi.vo.KubernetesApiExceptionVo;
// import cn.odboy.framework.exception.web.BadRequestException;
// import cn.odboy.util.CsReturnValueHandleUtil;
// import cn.odboy.util.LogFmtUtil;
// import com.alibaba.fastjson2.JSON;
// import com.aliyun.tea.TeaException;
// import io.kubernetes.client.openapi.ApiException;
// import lombok.extern.slf4j.Slf4j;
// import org.aspectj.lang.ProceedingJoinPoint;
// import org.aspectj.lang.annotation.Around;
// import org.aspectj.lang.annotation.Aspect;
// import org.gitlab4j.api.GitLabApiException;
// import org.springframework.stereotype.Component;
//
// /**
// * DingtalkApi调用异常
// *
// * @author odboy
// * @date 2025-05-07
// */
// @Slf4j
// @Aspect
// @Component
// public class ThirdExceptionCatchAspect {
// @Around("@annotation(thirdApiExceptionCatch)")
// public Object aroundDingtalkApiExceptionCatch(ProceedingJoinPoint joinPoint, ThirdApiExceptionCatch thirdApiExceptionCatch) throws Throwable {
// return handleThirdApiException(joinPoint, thirdApiExceptionCatch);
// }
//
// /**
// * 三方服务调用异常处理
// */
// private Object handleThirdApiException(ProceedingJoinPoint joinPoint, ThirdApiExceptionCatch annotation) {
// if ("dingtalk".equals(annotation.modelName())) {
// return handleDingtalkApiException(joinPoint, annotation);
// }
// if ("gitlab".equals(annotation.modelName())) {
// return handleGitlabApiException(joinPoint, annotation);
// }
// if ("kubernetes".equals(annotation.modelName())) {
// return handleKubernetesApiException(joinPoint, annotation);
// }
// return null;
// }
//
// private Object handleDingtalkApiException(ProceedingJoinPoint joinPoint, ThirdApiExceptionCatch annotation) {
// TimeInterval timeInterval = new TimeInterval();
// try {
// Object result = joinPoint.proceed();
// log.info("接口 [{}] 执行耗时: {} ms", annotation.description(), timeInterval.intervalMs());
// return result;
// } catch (TeaException teaException) {
// if (!com.aliyun.teautil.Common.empty(teaException.code) && !com.aliyun.teautil.Common.empty(teaException.message)) {
// String exceptionMessage = annotation.description() + "失败, code={}, message={}";
// log.error(exceptionMessage, teaException.code, teaException.message, teaException);
// if (annotation.throwException()) {
// throw new BadRequestException(LogFmtUtil.format(exceptionMessage, teaException.code, teaException.message));
// }
// }
// String exceptionMessage = annotation.description() + "失败";
// log.error(exceptionMessage, teaException);
// if (annotation.throwException()) {
// throw new BadRequestException(exceptionMessage);
// }
// } catch (Throwable exception) {
// TeaException err = new TeaException(exception.getMessage(), exception);
// if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// String exceptionMessage = annotation.description() + "失败, code={}, message={}";
// log.error(exceptionMessage, err.code, err.message, err);
// if (annotation.throwException()) {
// throw new BadRequestException(LogFmtUtil.format(exceptionMessage, err.code, err.message));
// }
// }
// String exceptionMessage = annotation.description() + "失败";
// log.error(exceptionMessage, exception);
// if (annotation.throwException()) {
// throw new BadRequestException(exceptionMessage);
// }
// }
// return CsReturnValueHandleUtil.getDefaultValue(joinPoint);
// }
//
// private Object handleGitlabApiException(ProceedingJoinPoint joinPoint, ThirdApiExceptionCatch annotation) {
// TimeInterval timeInterval = new TimeInterval();
// try {
// Object result = joinPoint.proceed();
// log.info("接口 [{}] 执行耗时: {} ms", annotation.description(), timeInterval.intervalMs());
// return result;
// } catch (GitLabApiException gitLabApiException) {
// String exceptionMessage = annotation.description() + "失败,原因:" + gitLabApiException.getMessage();
// log.error(exceptionMessage, gitLabApiException);
// if (annotation.throwException()) {
// throw new BadRequestException(exceptionMessage);
// }
// } catch (Throwable exception) {
// String exceptionMessage = annotation.description() + "失败";
// log.error(exceptionMessage, exception);
// if (annotation.throwException()) {
// throw new BadRequestException(exceptionMessage);
// }
// }
// return CsReturnValueHandleUtil.getDefaultValue(joinPoint);
// }
//
// private Object handleKubernetesApiException(ProceedingJoinPoint joinPoint, ThirdApiExceptionCatch annotation) {
// TimeInterval timeInterval = new TimeInterval();
// try {
// Object result = joinPoint.proceed();
// log.info("接口 [{}] 执行耗时: {} ms", annotation.description(), timeInterval.intervalMs());
// return result;
// } catch (ApiException e) {
// String responseBody = e.getResponseBody();
// log.error("接口 [{}] 执行异常,耗时: {} ms,异常信息: {}", annotation.description(), timeInterval.intervalMs(), responseBody, e);
// if (annotation.throwException()) {
// KubernetesApiExceptionVo actionExceptionBody = JSON.parseObject(responseBody, KubernetesApiExceptionVo.class);
// if (actionExceptionBody != null) {
// throw new BadRequestException(annotation.description() + "失败, 原因:" + actionExceptionBody.getReason());
// }
// throw new BadRequestException(annotation.description() + "失败");
// }
// } catch (Throwable e) {
// log.error("接口 [{}] 执行异常,耗时: {} ms", annotation.description(), timeInterval.intervalMs(), e);
// if (annotation.throwException()) {
// throw new BadRequestException(annotation.description() + "失败");
// }
// }
// return CsReturnValueHandleUtil.getDefaultValue(joinPoint);
// }
// }
@@ -0,0 +1,46 @@
//
// /*
// * Copyright 2021-2025 Odboy
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
// package cn.odboy.framework.exception.thirdapi.vo;
//
// import cn.odboy.base.CsObject;
// import lombok.Data;
// import lombok.EqualsAndHashCode;
//
// /**
// * Kubernetes异常信息
// *
// * @author odboy
// * @date 2025-08-03
// */
// @Data
// @EqualsAndHashCode(callSuper = false)
// public class KubernetesApiExceptionVo extends CsObject {
// private String kind;
// private String apiVersion;
// private String status;
// private String message;
// private String reason;
// private Details details;
// private int code;
//
// @Data
// public static class Details {
// private String name;
// private String kind;
// }
// }
@@ -0,0 +1,46 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.exception.web;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* 统一异常处理
*/
@Getter
public class BadRequestException extends RuntimeException {
private Integer status = BAD_REQUEST.value();
public BadRequestException(String msg) {
super(msg);
}
public BadRequestException(HttpStatus status, String msg) {
super(msg);
this.status = status.value();
}
public BadRequestException(Throwable cause) {
// FIXME 20250827 修复完整异常链暴露给用户的问题
super(cause.getMessage());
this.status = BAD_REQUEST.value();
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.exception.web.handler;
import lombok.Data;
@Data
public final class ApiError {
private Integer status = 400;
private Long timestamp;
private String message;
private ApiError() {
timestamp = System.currentTimeMillis();
}
public static ApiError error(final String message) {
ApiError apiError = new ApiError();
apiError.setMessage(message);
return apiError;
}
public static ApiError error(final Integer status, final String message) {
ApiError apiError = new ApiError();
apiError.setStatus(status);
apiError.setMessage(message);
return apiError;
}
}
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.exception.handler;
package cn.odboy.framework.exception.web.handler;
import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.exceptions.ExceptionUtil;
import cn.odboy.framework.exception.BadRequestException; import cn.odboy.framework.exception.web.BadRequestException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -72,7 +73,7 @@ public class GlobalExceptionHandler {
ObjectError objectError = e.getBindingResult().getAllErrors().get(0); ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
String message = objectError.getDefaultMessage(); String message = objectError.getDefaultMessage();
if (objectError instanceof FieldError) { if (objectError instanceof FieldError) {
message = ((FieldError)objectError).getField() + ": " + message; message = ((FieldError) objectError).getField() + ": " + message;
} }
return buildResponseEntity(ApiError.error(message)); return buildResponseEntity(ApiError.error(message));
} }
@@ -0,0 +1,40 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.logging;
import java.lang.annotation.*;
/**
* 统计执行时长
*
* @author odboy
*/
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExecutionTimeStatisticsFace {
/**
* 接口描述
*/
String description();
/**
* 是否展示异常日志
*/
boolean errorLog() default false;
}
@@ -0,0 +1,50 @@
/*
* Copyright 2021-2025 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.logging;
import cn.hutool.core.date.TimeInterval;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* DingtalkApi调用异常
*
* @author odboy
* @date 2025-05-07
*/
@Slf4j
@Aspect
@Component
public class ExecutionTimeStatisticsFaceAspect {
@Around("@annotation(executionTimeStatisticsFace)")
public Object aroundFace(ProceedingJoinPoint joinPoint, ExecutionTimeStatisticsFace executionTimeStatisticsFace) throws Throwable {
TimeInterval timeInterval = new TimeInterval();
try {
Object proceed = joinPoint.proceed();
log.info("[{}] 接口执行成功,耗时: {} ms", executionTimeStatisticsFace.description(), timeInterval.intervalMs());
return proceed;
} catch (Exception e) {
if (executionTimeStatisticsFace.errorLog()) {
log.info("[{}] 接口执行失败,耗时: {} ms", executionTimeStatisticsFace.description(), timeInterval.intervalMs());
}
throw e;
}
}
}
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.monitor.controller; package cn.odboy.framework.monitor.controller;
import cn.odboy.annotation.AnonymousGetMapping; import cn.odboy.annotation.AnonymousGetMapping;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.monitor.service; package cn.odboy.framework.monitor.service;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
/** /**
@@ -26,49 +27,61 @@ public enum CsMpQTypeEnum {
/** /**
* 大于等于 * 大于等于
*/ */
, GREATER_THAN ,
GREATER_THAN
/** /**
* 小于等于 * 小于等于
*/ */
, LESS_THAN ,
LESS_THAN
/** /**
* 中模糊查询 * 中模糊查询
*/ */
, INNER_LIKE ,
INNER_LIKE
/** /**
* 左模糊查询 * 左模糊查询
*/ */
, LEFT_LIKE ,
LEFT_LIKE
/** /**
* 右模糊查询 * 右模糊查询
*/ */
, RIGHT_LIKE ,
RIGHT_LIKE
/** /**
* 小于 * 小于
*/ */
, LESS_THAN_NQ ,
LESS_THAN_NQ
/** /**
* 包含 * 包含
*/ */
, IN ,
IN
/** /**
* 不包含 * 不包含
*/ */
, NOT_IN ,
NOT_IN
/** /**
* 不等于 * 不等于
*/ */
, NOT_EQUAL ,
NOT_EQUAL
/** /**
* between(注:被注解的属性一定得是数组或者集合, 长度大于等于2) * between(注:被注解的属性一定得是数组或者集合, 长度大于等于2)
*/ */
, BETWEEN ,
BETWEEN
/** /**
* 不为空 * 不为空
*/ */
, NOT_NULL ,
NOT_NULL
/** /**
* 为空 * 为空
*/ */
, IS_NULL ,
IS_NULL
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
import cn.odboy.framework.exception.BadRequestException; import cn.odboy.framework.exception.web.BadRequestException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
@@ -93,7 +94,14 @@ public enum DataTypeEnum {
private String aliasSuffix; private String aliasSuffix;
private static final String JDBC_URL_PREFIX = "jdbc:"; private static final String JDBC_URL_PREFIX = "jdbc:";
DataTypeEnum(String feature, String desc, String driver, String keywordPrefix, String keywordSuffix, String aliasPrefix, String aliasSuffix) { DataTypeEnum(
String feature,
String desc,
String driver,
String keywordPrefix,
String keywordSuffix,
String aliasPrefix,
String aliasSuffix) {
this.feature = feature; this.feature = feature;
this.desc = desc; this.desc = desc;
this.driver = driver; this.driver = driver;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@@ -60,7 +61,6 @@ public class MpCustomP6SpyLogger implements MessageFormattingStrategy {
sql = sql.replaceAll("\\s+", " ").trim(); sql = sql.replaceAll("\\s+", " ").trim();
} }
// 格式化并加上颜色 // 格式化并加上颜色
return String.format("CuteJava - %s%s%s %s[%s] %sREVIEW_SQL [Interval: %dms]%s -> %s;", RED, now, RESET, GREEN, Thread.currentThread().getName(), BLUE, return String.format("CuteJava - %s%s%s %s[%s] %sREVIEW_SQL [Interval: %dms]%s -> %s;", RED, now, RESET, GREEN, Thread.currentThread().getName(), BLUE, elapsed, RESET, sql);
elapsed, RESET, sql);
} }
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.DbType;
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.config; package cn.odboy.framework.mybatisplus.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
@@ -61,9 +62,10 @@ public class RemoveDruidAdConfig {
} }
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
HttpServletRequest httpRequest = (HttpServletRequest)request; throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse)response; HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (httpRequest.getRequestURI().endsWith("js/common.js")) { if (httpRequest.getRequestURI().endsWith("js/common.js")) {
// 获取common.js // 获取common.js
String text = Utils.readFromResource(filePath); String text = Utils.readFromResource(filePath);
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.core; package cn.odboy.framework.mybatisplus.core;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
@@ -68,7 +69,11 @@ public class CsMpAnyQUtil {
return baseMapper.selectPage(page, queryWrapper); return baseMapper.selectPage(page, queryWrapper);
} }
public static <T, M> Page<M> selectPage(BaseMapper<T> baseMapper, Page<T> page, Object queryParams, Class<M> mapperClazz) { public static <T, M> Page<M> selectPage(
BaseMapper<T> baseMapper,
Page<T> page,
Object queryParams,
Class<M> mapperClazz) {
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams); QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
Page<T> tPage = baseMapper.selectPage(page, queryWrapper); Page<T> tPage = baseMapper.selectPage(page, queryWrapper);
Page<M> mapperTarget = new Page<>(); Page<M> mapperTarget = new Page<>();
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.mybatisplus.core; package cn.odboy.framework.mybatisplus.core;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.odboy.base.CsBaseUserTimeTb; import cn.odboy.base.CsBaseUserTimeTb;
import cn.odboy.framework.exception.BadRequestException; import cn.odboy.framework.exception.web.BadRequestException;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.*;
@@ -74,7 +75,8 @@ public class CsMpCmdGenUtil {
this.parentModuleName = moduleName; this.parentModuleName = moduleName;
// 1.数据库配置 // 1.数据库配置
DataSourceConfig.Builder dataSourceConfigBuilder = DataSourceConfig.Builder dataSourceConfigBuilder =
new DataSourceConfig.Builder(this.databaseUrl, this.databaseUsername, this.databasePassword).driverClassName(this.driverClassName); new DataSourceConfig.Builder(this.databaseUrl, this.databaseUsername, this.databasePassword).driverClassName(
this.driverClassName);
dataSourceConfigBuilder.dbQuery(DB_QUERY); dataSourceConfigBuilder.dbQuery(DB_QUERY);
dataSourceConfigBuilder.keyWordsHandler(new MySqlKeyWordsHandler()); dataSourceConfigBuilder.keyWordsHandler(new MySqlKeyWordsHandler());
// 1.1.快速生成器 // 1.1.快速生成器
@@ -114,10 +116,13 @@ public class CsMpCmdGenUtil {
fastAutoGenerator.strategyConfig(getEntityConfigConsumer()); fastAutoGenerator.strategyConfig(getEntityConfigConsumer());
// 5.2.Controller策略配置 // 5.2.Controller策略配置
// 开启生成@RestController控制器 // 开启生成@RestController控制器
fastAutoGenerator.strategyConfig(strategyConfigBuilder -> strategyConfigBuilder.controllerBuilder().enableRestStyle()); fastAutoGenerator.strategyConfig(strategyConfigBuilder -> strategyConfigBuilder.controllerBuilder()
.enableRestStyle());
// 如果不需要生成Controller, fastAutoGenerator.templateConfig(templateConfig -> templateConfig.controller(""))。5.3.Service策略配置:格式化service接口和实现类的文件名称, 去掉默认的ServiceName前面的I // 如果不需要生成Controller, fastAutoGenerator.templateConfig(templateConfig -> templateConfig.controller(""))。5.3.Service策略配置:格式化service接口和实现类的文件名称, 去掉默认的ServiceName前面的I
fastAutoGenerator.strategyConfig( fastAutoGenerator.strategyConfig(
strategyConfigBuilder -> strategyConfigBuilder.serviceBuilder().formatServiceFileName("%sService").formatServiceImplFileName("%sServiceImpl")); strategyConfigBuilder -> strategyConfigBuilder.serviceBuilder()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl"));
// 5.4.Mapper策略配置 // 5.4.Mapper策略配置
// 格式化 mapper文件名,格式化xml实现类文件名称 // 格式化 mapper文件名,格式化xml实现类文件名称
fastAutoGenerator.strategyConfig(getStrategyConfigConsumer()); fastAutoGenerator.strategyConfig(getStrategyConfigConsumer());
@@ -126,19 +131,29 @@ public class CsMpCmdGenUtil {
} }
private static Consumer<StrategyConfig.Builder> getEntityConfigConsumer() { private static Consumer<StrategyConfig.Builder> getEntityConfigConsumer() {
return strategyConfigBuilder -> strategyConfigBuilder.entityBuilder().enableLombok() return strategyConfigBuilder -> strategyConfigBuilder.entityBuilder()
.enableLombok()
// 如果不需要生成注解, 去掉.enableTableFieldAnnotation() // 如果不需要生成注解, 去掉.enableTableFieldAnnotation()
.enableTableFieldAnnotation().enableFileOverride().naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel) .enableTableFieldAnnotation()
.enableFileOverride()
.naming(NamingStrategy.underline_to_camel)
.columnNaming(NamingStrategy.underline_to_camel)
.idType(IdType.AUTO); .idType(IdType.AUTO);
} }
private static Consumer<StrategyConfig.Builder> getStrategyConfigConsumer(List<String> tableNames) { private static Consumer<StrategyConfig.Builder> getStrategyConfigConsumer(List<String> tableNames) {
return strategyConfigBuilder -> strategyConfigBuilder.enableCapitalMode().enableSkipView().disableSqlFilter().addInclude(tableNames) return strategyConfigBuilder -> strategyConfigBuilder.enableCapitalMode()
.enableSkipView()
.disableSqlFilter()
.addInclude(tableNames)
// 实体类生成策略 // 实体类生成策略
.entityBuilder().superClass(CsBaseUserTimeTb.class).addIgnoreColumns("create_time", "update_time", "create_by", "update_by") .entityBuilder()
.superClass(CsBaseUserTimeTb.class)
.addIgnoreColumns("create_time", "update_time", "create_by", "update_by")
// 开启生成实体时生成字段注解。 // 开启生成实体时生成字段注解。
// 会在实体类的属性前,添加[@TableField("nickname")] // 会在实体类的属性前,添加[@TableField("nickname")]
.enableTableFieldAnnotation().disableSerialVersionUID() .enableTableFieldAnnotation()
.disableSerialVersionUID()
// 阶段2:Mapper策略配置 // 阶段2:Mapper策略配置
.mapperBuilder() .mapperBuilder()
// 开启 @Mapper 注解。 // 开启 @Mapper 注解。
@@ -155,7 +170,8 @@ public class CsMpCmdGenUtil {
// 会在控制类中加[@RestController]注解。 // 会在控制类中加[@RestController]注解。
.enableRestStyle() .enableRestStyle()
// 开启驼峰转连字符 // 开启驼峰转连字符
.enableHyphenStyle().build(); .enableHyphenStyle()
.build();
} }
private Consumer<PackageConfig.Builder> getPackageConfigConsumer() { private Consumer<PackageConfig.Builder> getPackageConfigConsumer() {
@@ -163,20 +179,32 @@ public class CsMpCmdGenUtil {
if (StrUtil.isBlank(this.parentModuleName)) { if (StrUtil.isBlank(this.parentModuleName)) {
throw new BadRequestException("模块名称必填"); throw new BadRequestException("模块名称必填");
} }
return packageConfigBuilder -> packageConfigBuilder.parent(this.parentPackageName).moduleName(this.parentModuleName).entity("dal.dataobject") return packageConfigBuilder -> packageConfigBuilder.parent(this.parentPackageName)
.mapper("dal.mysql").service("service").serviceImpl("service.impl").controller("controller") .moduleName(this.parentModuleName)
.entity("dal.dataobject")
.mapper("dal.mysql")
.service("service")
.serviceImpl("service.impl")
.controller("controller")
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperPath)); .pathInfo(Collections.singletonMap(OutputFile.xml, mapperPath));
} }
private static Consumer<GlobalConfig.Builder> getGlobalConfigConsumer() { private static Consumer<GlobalConfig.Builder> getGlobalConfigConsumer() {
String outputDir = projectRootPath + "CodeGen"; String outputDir = projectRootPath + "CodeGen";
return globalConfigBuilder -> globalConfigBuilder.enableSwagger().outputDir(outputDir).author("codegen").commentDate("yyyy-MM-dd") return globalConfigBuilder -> globalConfigBuilder.enableSwagger()
.outputDir(outputDir)
.author("codegen")
.commentDate("yyyy-MM-dd")
.dateType(DateType.TIME_PACK); .dateType(DateType.TIME_PACK);
} }
private static Consumer<StrategyConfig.Builder> getStrategyConfigConsumer() { private static Consumer<StrategyConfig.Builder> getStrategyConfigConsumer() {
return strategyConfigBuilder -> strategyConfigBuilder.mapperBuilder().mapperAnnotation(Mapper.class).formatMapperFileName("%sMapper") return strategyConfigBuilder -> strategyConfigBuilder.mapperBuilder()
.formatXmlFileName("%sMapper").entityBuilder().formatFileName("%sTb"); .mapperAnnotation(Mapper.class)
.formatMapperFileName("%sMapper")
.formatXmlFileName("%sMapper")
.entityBuilder()
.formatFileName("%sTb");
} }
private static Consumer<DataSourceConfig.Builder> getDataSourceConfigConsumer() { private static Consumer<DataSourceConfig.Builder> getDataSourceConfigConsumer() {

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