mirror of https://github.com/jeecgboot/jeecg-boot
Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot
commit
47a2a6fbac
57
README.md
57
README.md
|
@ -41,6 +41,35 @@ JeecgBoot AI低代码平台,可以应用在任何J2EE项目的开发中,支
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
项目说明
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
| 项目名 | 说明 |
|
||||||
|
|--------------------|------------------------|
|
||||||
|
| `jeecg-boot` | 后端源码JAVA(SpringBoot微服务架构) |
|
||||||
|
| `jeecgboot-vue3` | 前端源码VUE3(vue3+vite6+ts最新技术栈) |
|
||||||
|
| `JeecgUniapp` | [配套APP框架](https://github.com/jeecgboot/JeecgUniapp) 适配多个终端,支持APP、小程序、H5 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
技术文档
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
||||||
|
- 在线演示 : [平台演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [体验低代码](https://jeecg.blog.csdn.net/article/details/106079007) | [体验零代码](https://app.qiaoqiaoyun.com/myapps/index)
|
||||||
|
- 开发文档: [文档中心](https://help.jeecg.com) | [AIGC大模块](https://help.jeecg.com/aigc)
|
||||||
|
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [入门视频](http://jeecg.com/doc/video) | [如何反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md)
|
||||||
|
- QQ交流群 : ⑩716488839、⑨808791225(满)、其他(满)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
启动项目
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup)
|
||||||
|
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
AIGC应用平台介绍
|
AIGC应用平台介绍
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
@ -91,34 +120,6 @@ AIGC应用平台介绍
|
||||||
| 等等。。 | √ |
|
| 等等。。 | √ |
|
||||||
|
|
||||||
|
|
||||||
项目说明
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
| 项目名 | 说明 |
|
|
||||||
|--------------------|------------------------|
|
|
||||||
| `jeecg-boot` | 后端源码JAVA(SpringBoot微服务架构) |
|
|
||||||
| `jeecgboot-vue3` | 前端源码VUE3(vue3+vite6+ts最新技术栈) |
|
|
||||||
| `JeecgUniapp` | [配套APP框架](https://github.com/jeecgboot/JeecgUniapp) 适配多个终端,支持APP、小程序、H5 |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
技术文档
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
|
||||||
- 在线演示 : [平台演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [体验低代码](https://jeecg.blog.csdn.net/article/details/106079007) | [体验零代码](https://app.qiaoqiaoyun.com/myapps/index)
|
|
||||||
- 开发文档: [文档中心](https://help.jeecg.com) | [AIGC大模块](https://help.jeecg.com/aigc)
|
|
||||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [入门视频](http://jeecg.com/doc/video) | [如何反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md)
|
|
||||||
- QQ交流群 : ⑩716488839、⑨808791225(满)、其他(满)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
启动项目
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup)
|
|
||||||
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
技术架构:
|
技术架构:
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package org.jeecg.config.init;
|
package org.jeecg.config.init;
|
||||||
|
|
||||||
import io.undertow.server.DefaultByteBufferPool;
|
import io.undertow.server.DefaultByteBufferPool;
|
||||||
|
import io.undertow.server.handlers.BlockingHandler;
|
||||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||||
|
import org.jeecg.modules.monitor.actuator.undertow.CustomUndertowMetricsHandler;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -14,6 +17,13 @@ import org.springframework.context.annotation.Configuration;
|
||||||
@Configuration
|
@Configuration
|
||||||
public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义undertow监控指标工具类
|
||||||
|
* for [QQYUN-11902]tomcat 替换undertow 这里的功能还没修改
|
||||||
|
*/
|
||||||
|
@Autowired
|
||||||
|
private CustomUndertowMetricsHandler customUndertowMetricsHandler;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customize(UndertowServletWebServerFactory factory) {
|
public void customize(UndertowServletWebServerFactory factory) {
|
||||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||||
|
@ -24,6 +34,9 @@ public class UndertowConfiguration implements WebServerFactoryCustomizer<Underto
|
||||||
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 8192));
|
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 8192));
|
||||||
|
|
||||||
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||||
|
|
||||||
|
// 添加自定义 监控 handler
|
||||||
|
deploymentInfo.addInitialHandlerChainWrapper(next -> new BlockingHandler(customUndertowMetricsHandler.wrap(next)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.jeecg.modules.monitor.actuator.undertow;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.undertow.server.HttpHandler;
|
||||||
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import io.undertow.server.session.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义undertow监控指标工具类
|
||||||
|
* for [QQYUN-11902]tomcat 替换undertow 这里的功能还没修改
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2025/4/8 19:06
|
||||||
|
*/
|
||||||
|
@Component("jeecgCustomUndertowMetricsHandler")
|
||||||
|
public class CustomUndertowMetricsHandler {
|
||||||
|
|
||||||
|
// 用于统计已创建的 session 数量
|
||||||
|
private final LongAdder sessionsCreated = new LongAdder();
|
||||||
|
|
||||||
|
// 用于统计已销毁的 session 数量
|
||||||
|
private final LongAdder sessionsExpired = new LongAdder();
|
||||||
|
|
||||||
|
// 当前活跃的 session 数量
|
||||||
|
private final AtomicInteger activeSessions = new AtomicInteger();
|
||||||
|
|
||||||
|
// 历史最大活跃 session 数
|
||||||
|
private final AtomicInteger maxActiveSessions = new AtomicInteger();
|
||||||
|
|
||||||
|
// Undertow 内存 session 管理器(用于创建与管理 session)
|
||||||
|
private final InMemorySessionManager sessionManager = new InMemorySessionManager("undertow-session-manager");
|
||||||
|
|
||||||
|
// 使用 Cookie 存储 session ID
|
||||||
|
private final SessionConfig sessionConfig = new SessionCookieConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param meterRegistry
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2025/4/8 19:07
|
||||||
|
*/
|
||||||
|
public CustomUndertowMetricsHandler(MeterRegistry meterRegistry) {
|
||||||
|
// 注册 Micrometer 指标
|
||||||
|
meterRegistry.gauge("undertow.sessions.created", sessionsCreated, LongAdder::longValue);
|
||||||
|
meterRegistry.gauge("undertow.sessions.expired", sessionsExpired, LongAdder::longValue);
|
||||||
|
meterRegistry.gauge("undertow.sessions.active.current", activeSessions, AtomicInteger::get);
|
||||||
|
meterRegistry.gauge("undertow.sessions.active.max", maxActiveSessions, AtomicInteger::get);
|
||||||
|
|
||||||
|
// 添加 session 生命周期监听器,统计 session 创建与销毁
|
||||||
|
sessionManager.registerSessionListener(new SessionListener() {
|
||||||
|
@Override
|
||||||
|
public void sessionCreated(Session session, HttpServerExchange exchange) {
|
||||||
|
sessionsCreated.increment();
|
||||||
|
int now = activeSessions.incrementAndGet();
|
||||||
|
maxActiveSessions.getAndUpdate(max -> Math.max(max, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) {
|
||||||
|
sessionsExpired.increment();
|
||||||
|
activeSessions.decrementAndGet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包装 Undertow 的 HttpHandler,实现 session 自动创建逻辑
|
||||||
|
* @param next
|
||||||
|
* @return
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2025/4/8 19:07
|
||||||
|
*/
|
||||||
|
public HttpHandler wrap(HttpHandler next) {
|
||||||
|
return exchange -> {
|
||||||
|
// 获取当前 session,如果不存在则创建
|
||||||
|
Session session = sessionManager.getSession(exchange, sessionConfig);
|
||||||
|
if (session == null) {
|
||||||
|
sessionManager.createSession(exchange, sessionConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行下一个 Handler
|
||||||
|
next.handleRequest(exchange);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package org.jeecg.modules.system.test;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.common.modules.redis.client.JeecgRedisClient;
|
||||||
|
import org.jeecg.common.util.RedisUtil;
|
||||||
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
|
import org.jeecg.modules.base.service.BaseCommonService;
|
||||||
|
import org.jeecg.modules.system.controller.SysUserController;
|
||||||
|
import org.jeecg.modules.system.entity.SysUser;
|
||||||
|
import org.jeecg.modules.system.service.*;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统用户单元测试
|
||||||
|
*/
|
||||||
|
@WebMvcTest(SysUserController.class)
|
||||||
|
public class SysUserApiTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysUserService sysUserService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysDepartService sysDepartService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysUserRoleService sysUserRoleService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysUserDepartService sysUserDepartService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysDepartRoleUserService departRoleUserService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysDepartRoleService departRoleService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Value("${jeecg.path.upload}")
|
||||||
|
private String upLoadPath;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private BaseCommonService baseCommonService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysUserAgentService sysUserAgentService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysPositionService sysPositionService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private ISysUserTenantService userTenantService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private JeecgRedisClient jeecgRedisClient;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private JeecgBaseConfig jeecgBaseConfig;
|
||||||
|
/**
|
||||||
|
* 测试地址:实际使用时替换成你自己的地址
|
||||||
|
*/
|
||||||
|
private final String BASE_URL = "/sys/user/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例:查询记录
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testQuery() throws Exception{
|
||||||
|
// 请求地址
|
||||||
|
String url = BASE_URL + "list";
|
||||||
|
|
||||||
|
Page<SysUser> sysUserPage = new Page<>();
|
||||||
|
SysUser sysUser = new SysUser();
|
||||||
|
sysUser.setUsername("admin");
|
||||||
|
List<SysUser> users = new ArrayList<>();
|
||||||
|
users.add(sysUser);
|
||||||
|
sysUserPage.setRecords(users);
|
||||||
|
sysUserPage.setCurrent(1);
|
||||||
|
sysUserPage.setSize(10);
|
||||||
|
sysUserPage.setTotal(1);
|
||||||
|
|
||||||
|
given(this.sysUserService.queryPageList(any(), any(), any(), any())).willReturn(Result.OK(sysUserPage));
|
||||||
|
|
||||||
|
String result = mockMvc.perform(get(url)).andReturn().getResponse().getContentAsString();
|
||||||
|
JSONObject jsonObject = JSON.parseObject(result);
|
||||||
|
Assertions.assertEquals("admin", jsonObject.getJSONObject("result").getJSONArray("records").getJSONObject(0).getString("username"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例:新增
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAdd() throws Exception {
|
||||||
|
// 请求地址
|
||||||
|
String url = BASE_URL + "add" ;
|
||||||
|
|
||||||
|
JSONObject params = new JSONObject();
|
||||||
|
params.put("username", "wangwuTest");
|
||||||
|
params.put("password", "123456");
|
||||||
|
params.put("confirmpassword","123456");
|
||||||
|
params.put("realname", "单元测试");
|
||||||
|
params.put("activitiSync", "1");
|
||||||
|
params.put("userIdentity","1");
|
||||||
|
params.put("workNo","0025");
|
||||||
|
|
||||||
|
String result = mockMvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(params.toJSONString()))
|
||||||
|
.andReturn().getResponse().getContentAsString();
|
||||||
|
JSONObject jsonObject = JSON.parseObject(result);
|
||||||
|
Assertions.assertTrue(jsonObject.getBoolean("success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例:修改
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEdit() throws Exception {
|
||||||
|
// 数据Id
|
||||||
|
String dataId = "1331795062924374018";
|
||||||
|
// 请求地址
|
||||||
|
String url = BASE_URL + "edit";
|
||||||
|
|
||||||
|
JSONObject params = new JSONObject();
|
||||||
|
params.put("username", "wangwuTest");
|
||||||
|
params.put("realname", "单元测试1111");
|
||||||
|
params.put("activitiSync", "1");
|
||||||
|
params.put("userIdentity","1");
|
||||||
|
params.put("workNo","0025");
|
||||||
|
params.put("id",dataId);
|
||||||
|
|
||||||
|
SysUser sysUser = new SysUser();
|
||||||
|
sysUser.setUsername("admin");
|
||||||
|
|
||||||
|
given(this.sysUserService.getById(any())).willReturn(sysUser);
|
||||||
|
|
||||||
|
String result = mockMvc.perform(put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(params.toJSONString()))
|
||||||
|
.andReturn().getResponse().getContentAsString();
|
||||||
|
JSONObject jsonObject = JSON.parseObject(result);
|
||||||
|
Assertions.assertTrue(jsonObject.getBoolean("success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例:删除
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDelete() throws Exception {
|
||||||
|
// 数据Id
|
||||||
|
String dataId = "1331795062924374018";
|
||||||
|
// 请求地址
|
||||||
|
String url = BASE_URL + "delete" + "?id=" + dataId;
|
||||||
|
String result = mockMvc.perform(delete(url)).andReturn().getResponse().getContentAsString();
|
||||||
|
JSONObject jsonObject = JSON.parseObject(result);
|
||||||
|
Assertions.assertTrue(jsonObject.getBoolean("success"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -359,7 +359,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework</groupId>
|
<groupId>org.jeecgframework</groupId>
|
||||||
<artifactId>weixin4j</artifactId>
|
<artifactId>weixin4j</artifactId>
|
||||||
<version>2.0.1</version>
|
<version>2.0.2</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<artifactId>commons-beanutils</artifactId>
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<Select @dropdownVisibleChange="handleFetch" v-bind="attrs_" @change="handleChange" :options="getOptions" v-model:value="state">
|
<Select
|
||||||
|
v-bind="attrs_"
|
||||||
|
v-model:value="state"
|
||||||
|
:options="getOptions"
|
||||||
|
@change="handleChange"
|
||||||
|
@dropdownVisibleChange="handleFetch"
|
||||||
|
@popupScroll="handlePopupScroll"
|
||||||
|
>
|
||||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||||
<slot :name="item" v-bind="data || {}"></slot>
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,9 +31,10 @@
|
||||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
import { isNumber } from '/@/utils/is';
|
||||||
|
|
||||||
type OptionsItem = { label: string; value: string; disabled?: boolean };
|
type OptionsItem = { label: string; value: string; disabled?: boolean };
|
||||||
|
//文档 https://help.jeecg.com/ui/apiSelect#pageconfig%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ApiSelect',
|
name: 'ApiSelect',
|
||||||
components: {
|
components: {
|
||||||
|
@ -35,7 +43,7 @@
|
||||||
},
|
},
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: {
|
props: {
|
||||||
value: [Array, Object, String, Number],
|
value: [Array, String, Number],
|
||||||
numberToString: propTypes.bool,
|
numberToString: propTypes.bool,
|
||||||
api: {
|
api: {
|
||||||
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
|
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
|
||||||
|
@ -46,6 +54,11 @@
|
||||||
type: Object as PropType<Recordable>,
|
type: Object as PropType<Recordable>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
|
//分页配置
|
||||||
|
pageConfig: {
|
||||||
|
type: Object as PropType<Recordable>,
|
||||||
|
default: () => ({ isPage: false }),
|
||||||
|
},
|
||||||
// support xxx.xxx.xx
|
// support xxx.xxx.xx
|
||||||
resultField: propTypes.string.def(''),
|
resultField: propTypes.string.def(''),
|
||||||
labelField: propTypes.string.def('label'),
|
labelField: propTypes.string.def('label'),
|
||||||
|
@ -60,7 +73,15 @@
|
||||||
const emitData = ref<any[]>([]);
|
const emitData = ref<any[]>([]);
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
// update-begin--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
|
const hasMore = ref(true);
|
||||||
|
const pagination = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const defPageConfig = { isPage: false, pageField: 'pageNo', pageSizeField: 'pageSize', totalField: 'total', listField: 'records' };
|
||||||
|
// update-end--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
// Embedded in the form, just use the hook binding to perform form verification
|
// Embedded in the form, just use the hook binding to perform form verification
|
||||||
const [state, setState] = useRuleFormItem(props, 'value', 'change', emitData);
|
const [state, setState] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||||
// update-begin--author:liaozhiyang---date:20230830---for:【QQYUN-6308】解决警告
|
// update-begin--author:liaozhiyang---date:20230830---for:【QQYUN-6308】解决警告
|
||||||
|
@ -122,10 +143,25 @@
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const api = props.api;
|
const api = props.api;
|
||||||
if (!api || !isFunction(api)) return;
|
if (!api || !isFunction(api)) return;
|
||||||
|
// update-begin--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
|
if (!props.pageConfig.isPage || pagination.value.pageNo == 1) {
|
||||||
options.value = [];
|
options.value = [];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await api(props.params);
|
let { isPage, pageField, pageSizeField, totalField, listField } = { ...defPageConfig, ...props.pageConfig };
|
||||||
|
let params = isPage
|
||||||
|
? { ...props.params, [pageField]: pagination.value.pageNo, [pageSizeField]: pagination.value.pageSize }
|
||||||
|
: { ...props.params };
|
||||||
|
// update-end--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
|
const res = await api(params);
|
||||||
|
if (isPage) {
|
||||||
|
// update-begin--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
|
options.value = [...options.value, ...res[listField]];
|
||||||
|
pagination.value.total = res[totalField] || 0;
|
||||||
|
hasMore.value = res[totalField] ? options.value.length < res[totalField] : res[listField] < pagination.value.pageSize;
|
||||||
|
// update-end--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
|
} else {
|
||||||
if (Array.isArray(res)) {
|
if (Array.isArray(res)) {
|
||||||
options.value = res;
|
options.value = res;
|
||||||
emitChange();
|
emitChange();
|
||||||
|
@ -134,6 +170,7 @@
|
||||||
if (props.resultField) {
|
if (props.resultField) {
|
||||||
options.value = get(res, props.resultField) || [];
|
options.value = get(res, props.resultField) || [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
emitChange();
|
emitChange();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
|
@ -151,9 +188,17 @@
|
||||||
|
|
||||||
function initValue() {
|
function initValue() {
|
||||||
let value = props.value;
|
let value = props.value;
|
||||||
|
// update-begin--author:liaozhiyang---date:20250407---for:【issues/8037】初始化值单选的值被错误地写入数组值
|
||||||
|
if (unref(attrs).mode == 'multiple') {
|
||||||
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
|
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
|
||||||
state.value = value.split(',');
|
state.value = value.split(',');
|
||||||
|
} else if (isNumber(value)) {
|
||||||
|
state.value = [value];
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
state.value = value;
|
||||||
|
}
|
||||||
|
// update-end--author:liaozhiyang---date:20250407---for:【issues/8037】初始化值单选的值被错误地写入数组值
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFetch() {
|
async function handleFetch() {
|
||||||
|
@ -171,8 +216,18 @@
|
||||||
vModalValue && vModalValue(_);
|
vModalValue && vModalValue(_);
|
||||||
emitData.value = args;
|
emitData.value = args;
|
||||||
}
|
}
|
||||||
|
// update-begin--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
return { state, attrs_, attrs, getOptions, loading, t, handleFetch, handleChange };
|
// 滚动加载更多
|
||||||
|
function handlePopupScroll(e) {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
|
const isNearBottom = scrollHeight - scrollTop <= clientHeight + 20;
|
||||||
|
if (props.pageConfig.isPage && isNearBottom && hasMore.value && !loading.value) {
|
||||||
|
pagination.value.pageNo += 1;
|
||||||
|
fetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update-end--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||||
|
return { state, attrs_, attrs, getOptions, loading, t, handleFetch, handleChange, handlePopupScroll };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -209,18 +209,11 @@ export const useUserStore = defineStore({
|
||||||
//update-begin---author:wangshuai ---date:20230424 for:【QQYUN-5195】登录之后直接刷新页面导致没有进入创建组织页面------------
|
//update-begin---author:wangshuai ---date:20230424 for:【QQYUN-5195】登录之后直接刷新页面导致没有进入创建组织页面------------
|
||||||
if (redirect && goHome) {
|
if (redirect && goHome) {
|
||||||
//update-end---author:wangshuai ---date:20230424 for:【QQYUN-5195】登录之后直接刷新页面导致没有进入创建组织页面------------
|
//update-end---author:wangshuai ---date:20230424 for:【QQYUN-5195】登录之后直接刷新页面导致没有进入创建组织页面------------
|
||||||
// update-begin--author:liaozhiyang---date:20240104---for:【QQYUN-7804】部署生产环境,登录跳转404问题
|
// update-begin--author:liaozhiyang---date:20250407---for:【issues/8034】hash模式下退出重登录默认跳转地址异常
|
||||||
let publicPath = import.meta.env.VITE_PUBLIC_PATH;
|
// router.options.history.base可替代之前的publicPath
|
||||||
if (publicPath && publicPath != '/') {
|
|
||||||
// update-begin--author:liaozhiyang---date:20240509---for:【issues/1147】登录跳转时去掉发布路径的最后一个/以解决404问题
|
|
||||||
if (publicPath.endsWith('/')) {
|
|
||||||
publicPath = publicPath.slice(0, -1);
|
|
||||||
}
|
|
||||||
redirect = publicPath + redirect;
|
|
||||||
}
|
|
||||||
// update-end--author:liaozhiyang---date:20240509---for:【issues/1147】登录跳转时去掉发布路径的最后一个/以解决404问题
|
|
||||||
// 当前页面打开
|
// 当前页面打开
|
||||||
window.open(redirect, '_self')
|
window.open(`${router.options.history.base}${redirect}`, '_self');
|
||||||
|
// update-end--author:liaozhiyang---date:20250407---for:【issues/8034】hash模式下退出重登录默认跳转地址异常
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
// update-end-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
|
// update-end-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
<a-tabs v-model:activeKey="activeKey" @change="tabChange">
|
<a-tabs v-model:activeKey="activeKey" @change="tabChange">
|
||||||
<a-tab-pane key="1" tab="服务器信息"></a-tab-pane>
|
<a-tab-pane key="1" tab="服务器信息"></a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="JVM信息" force-render></a-tab-pane>
|
<a-tab-pane key="2" tab="JVM信息" force-render></a-tab-pane>
|
||||||
<a-tab-pane key="3" tab="Tomcat信息"></a-tab-pane>
|
<!-- <a-tab-pane key="3" tab="Tomcat信息"></a-tab-pane> -->
|
||||||
|
<a-tab-pane key="6" tab="Undertow信息"></a-tab-pane>
|
||||||
<a-tab-pane key="4" tab="磁盘监控">
|
<a-tab-pane key="4" tab="磁盘监控">
|
||||||
<DiskInfo v-if="activeKey == 4" style="height: 100%"></DiskInfo>
|
<DiskInfo v-if="activeKey == 4" style="height: 100%"></DiskInfo>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
|
@ -30,6 +30,11 @@ enum Api {
|
||||||
tomcatSessionsRejected = '/actuator/metrics/tomcat.sessions.rejected',
|
tomcatSessionsRejected = '/actuator/metrics/tomcat.sessions.rejected',
|
||||||
|
|
||||||
memoryInfo = '/sys/actuator/memory/info',
|
memoryInfo = '/sys/actuator/memory/info',
|
||||||
|
// undertow 监控
|
||||||
|
undertowSessionsCreated = '/actuator/metrics/undertow.sessions.created',
|
||||||
|
undertowSessionsExpired = '/actuator/metrics/undertow.sessions.expired',
|
||||||
|
undertowSessionsActiveCurrent = '/actuator/metrics/undertow.sessions.active.current',
|
||||||
|
undertowSessionsActiveMax = '/actuator/metrics/undertow.sessions.active.max',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -207,6 +212,34 @@ export const getTomcatSessionsRejected = () => {
|
||||||
return defHttp.get({ url: Api.tomcatSessionsRejected }, { isTransformResponse: false });
|
return defHttp.get({ url: Api.tomcatSessionsRejected }, { isTransformResponse: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*undertow 已创建 session 数
|
||||||
|
*/
|
||||||
|
export const getUndertowSessionsCreated = () => {
|
||||||
|
return defHttp.get({ url: Api.undertowSessionsCreated }, { isTransformResponse: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*undertow 已过期 session 数
|
||||||
|
*/
|
||||||
|
export const getUndertowSessionsExpired = () => {
|
||||||
|
return defHttp.get({ url: Api.undertowSessionsExpired }, { isTransformResponse: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*undertow 当前活跃 session 数
|
||||||
|
*/
|
||||||
|
export const getUndertowSessionsActiveCurrent = () => {
|
||||||
|
return defHttp.get({ url: Api.undertowSessionsActiveCurrent }, { isTransformResponse: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*undertow 活跃 session 数峰值
|
||||||
|
*/
|
||||||
|
export const getUndertowSessionsActiveMax = () => {
|
||||||
|
return defHttp.get({ url: Api.undertowSessionsActiveMax }, { isTransformResponse: false });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内存信息
|
* 内存信息
|
||||||
*/
|
*/
|
||||||
|
@ -230,6 +263,9 @@ export const getMoreInfo = (infoType) => {
|
||||||
if (infoType == '5') {
|
if (infoType == '5') {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
if (infoType == '6') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTextInfo = (infoType) => {
|
export const getTextInfo = (infoType) => {
|
||||||
|
@ -293,6 +329,16 @@ export const getTextInfo = (infoType) => {
|
||||||
'memory.runtime.usage': { color: 'purple', text: 'JVM内存使用率', unit: '%', valueType: 'Number' },
|
'memory.runtime.usage': { color: 'purple', text: 'JVM内存使用率', unit: '%', valueType: 'Number' },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (infoType == '6') {
|
||||||
|
// undertow 监控
|
||||||
|
return {
|
||||||
|
'undertow.sessions.created': { color: 'green', text: 'undertow 已创建 session 数', unit: '个' },
|
||||||
|
'undertow.sessions.expired': { color: 'green', text: 'undertow 已过期 session 数', unit: '个' },
|
||||||
|
'undertow.sessions.active.current': { color: 'green', text: 'undertow 当前活跃 session 数', unit: '个' },
|
||||||
|
'undertow.sessions.active.max': { color: 'green', text: 'undertow 活跃 session 数峰值', unit: '个' },
|
||||||
|
'undertow.sessions.rejected': { color: 'green', text: '超过session 最大配置后,拒绝的 session 个数', unit: '个' },
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,4 +380,13 @@ export const getServerInfo = (infoType) => {
|
||||||
if (infoType == '5') {
|
if (infoType == '5') {
|
||||||
return Promise.all([getMemoryInfo()]);
|
return Promise.all([getMemoryInfo()]);
|
||||||
}
|
}
|
||||||
|
// undertow监控
|
||||||
|
if (infoType == '6') {
|
||||||
|
return Promise.all([
|
||||||
|
getUndertowSessionsActiveCurrent(),
|
||||||
|
getUndertowSessionsActiveMax(),
|
||||||
|
getUndertowSessionsCreated(),
|
||||||
|
getUndertowSessionsExpired(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue