61 Commits

Author SHA1 Message Date
Chuck1sn
d4b2343ac9 remove mirrors 2025-06-27 21:11:25 +08:00
Chuck1sn
5665ae0fda JavaVersion.VERSION_21 2025-06-27 21:08:51 +08:00
Chuck1sn
76b77dbf18 新增推广横幅组件,并在用户管理页面中集成,展示官方教程链接和相关信息,同时添加新的图片资源。 2025-06-25 12:10:53 +08:00
Chuck1sn
8ffa4324d3 fix readme 2025-06-24 15:24:55 +08:00
Chuck1sn
7442516c07 新增部门查询功能的参数支持,包括用户ID和绑定状态,优化可用部门列表查询逻辑 2025-06-19 15:45:34 +08:00
Chuck1sn
f04ea3a9e8 Merge branch 'dev' 2025-06-19 15:30:04 +08:00
Chuck1sn
ea86342a0f 新增部门子级查询功能,更新相关API接口和前端组件,优化部门树形结构展示,支持父子部门关系的处理 2025-06-19 15:29:22 +08:00
Chuck1sn
3d48ae9119 fix readme 2025-06-18 09:36:21 +08:00
Chuck1sn
fa580a5dd4 新增岗位、角色和权限的删除功能,更新相关API接口和前端组件,优化操作逻辑和权限校验 2025-06-17 12:17:03 +08:00
Chuck1sn
87d288c58e 重构错误处理逻辑,更新组件导入路径,新增多个模态框组件以支持用户、部门、角色和权限管理功能,优化分页和排序逻辑,更新类型定义以提高代码可读性和维护性 2025-06-16 18:00:15 +08:00
Chuck1sn
772ad547bf 重构错误处理逻辑,移除旧的错误处理工具,新增 useErrorHandler 组合式 API,优化模态框组件,添加 ID 属性以支持更灵活的 DOM 操作 2025-06-16 15:41:09 +08:00
Chuck1sn
ca42fbbda9 重构组件结构,优化导入路径并移除不必要的组件,新增多个模态框组件以支持部门、角色、权限和用户管理功能 2025-06-16 15:09:52 +08:00
Chuck1sn
28eed04823 修复用户视图路由参数传递方式,使用 fullPath() 方法替代空参数推送 2025-06-16 11:56:37 +08:00
Chuck1sn
5306e24aa2 refactor(component): 优化部门、岗位和用户创建功能的参数校验
- 在 DepartmentOperatorTool、PositionOperatorTool 和 UserRolePermissionOperatorTool 中添加了 @Size 注解- 用于限制部门名称、岗位名称和用户名的长度
- 更新了前端 UserUpsertModal 组件中用户名的校验规则
2025-06-16 11:44:51 +08:00
Chuck1sn
621170b347 fix bugs 2025-06-16 11:26:20 +08:00
Chuck1sn
ea10b156e3 重构角色和权限相关DTO,替换RoleDto为RoleRespDto,并更新相关服务和控制器逻辑 2025-06-16 10:57:35 +08:00
Chuck1sn
7b8ef54e7b avatar fix 2025-06-15 22:14:52 +08:00
Chuck1sn
7e1940e459 修复用户菜单按钮的背景颜色 2025-06-15 21:38:50 +08:00
Chuck1sn
567223eac3 fix img 2025-06-15 21:35:29 +08:00
Chuck1sn
1995985750 fix tablebutton 2025-06-15 17:54:33 +08:00
Chuck1sn
fe3ebdf931 fix table button 2025-06-15 17:10:16 +08:00
Chuck1sn
f011086fcf fix head 2025-06-15 16:52:06 +08:00
Chuck1sn
d9853ef8d0 fix avatar pc 2025-06-15 16:38:39 +08:00
Chuck1sn
dc7780e0a8 init minio 2025-06-15 15:09:52 +08:00
Chuck1sn
c64f2eb0f6 add clear api 2025-06-14 14:04:58 +08:00
Chuck1sn
f559e4cde3 add clear 2025-06-14 13:54:31 +08:00
Chuck1sn
ab72508408 css fix 2025-06-14 13:35:07 +08:00
Chuck1sn
219b4f40a6 fix icon 2025-06-14 13:23:55 +08:00
Chuck1sn
bb9cd2e529 fix table filter 2025-06-14 13:07:00 +08:00
Chuck1sn
8a8588588f tablefilter 2025-06-14 12:34:11 +08:00
Chuck1sn
ac6c50ff28 add date picker 2025-06-13 14:36:47 +08:00
Chuck1sn
a00c3e129f add notify ai 2025-06-13 11:43:35 +08:00
Chuck1sn
81b02e68e7 add deep seek tool action 2025-06-11 10:48:29 +08:00
Chuck1sn
79d4ced364 fix ai button 2025-06-11 10:40:24 +08:00
Chuck1sn
a30d63ebdd fix bind view 2025-06-11 10:06:36 +08:00
Chuck1sn
f65349f246 fix readme 2025-06-11 09:52:05 +08:00
Chuck1sn
2324bcaa13 0.2K 2025-06-10 17:16:35 +08:00
Chuck1sn
4f35b256e6 init table form layout 2025-06-10 17:09:10 +08:00
Chuck1sn
24f379857a Abstract Form Components 2025-06-10 16:28:30 +08:00
Chuck1sn
17200ec6d1 mobile style v2.1 2025-06-10 13:58:55 +08:00
Chuck1sn
5068e7ff58 mobile version 2.0 2025-06-10 13:04:38 +08:00
Chuck1sn
45c921fa24 fix undefined 2025-06-09 16:03:28 +08:00
Chuck1sn
db02af8677 fix user and role undefined 2025-06-09 15:58:09 +08:00
Chuck1sn
56e92b2cb8 SortOrder.DESC 2025-06-07 12:31:00 +08:00
Chuck1sn
39c91a971b fix chat 2025-06-06 13:21:05 +08:00
Chuck1sn
a1752df1ed fix ai assistant bugs 2025-06-06 11:32:05 +08:00
Chuck1sn
df3862d496 fix dep bugs 2025-06-06 10:34:44 +08:00
Chuck1sn
bdf9da7eed fix pmd and test 2025-06-05 12:47:14 +08:00
Chuck1sn
d4a34e7b52 fix param name 2025-06-05 11:57:58 +08:00
Chuck1sn
a982942153 fix ai reaction 2025-06-05 11:31:23 +08:00
Chuck1sn
f53db08c25 fix option 2025-06-05 10:36:08 +08:00
Chuck1sn
2210348d3b 职位->岗位 2025-06-04 16:11:45 +08:00
Chuck1sn
690228cb42 职位->岗位 2025-06-04 16:11:03 +08:00
Chuck1sn
d5801b3b50 fix next tick 2025-06-04 13:21:36 +08:00
Chuck1sn
27529ee70c bottom-1.5 2025-06-04 12:48:08 +08:00
Chuck1sn
50a1bc65ef fix z-index 2025-06-04 12:41:34 +08:00
Chuck1sn
54201aa0f0 优化页面显示效果 2025-06-04 11:49:47 +08:00
Chuck1sn
07e590dc2d fix modal dup id 2025-06-04 10:53:33 +08:00
Chuck1sn
34e0d3d8e7 fix page css 2025-06-04 10:23:19 +08:00
Chuck1sn
ad5b20d3c5 Merge branch 'main' into dev 2025-06-03 18:07:02 +08:00
Chuck1sn
dba64a38f2 add bind dep and pos 2025-06-03 18:05:51 +08:00
169 changed files with 9750 additions and 6446 deletions

View File

@@ -34,21 +34,27 @@
## 🥝 产品社群 ## 🥝 产品社群
**加 QQ 群,获取一键部署脚本(包含数据库 Redis 消息队列等所有中间件!)** **加 QQ 群或微信群立送以下装备,瞬间秒杀全服!!**
1. 一键部署脚本(包含数据库 Redis 消息队列等所有中间件!)
2. 永久免费的 Https 证书
3. 永久免费的分布式对象存储
4. 永久免费的 AI 模型
5. 永久免费的 Node、Docker、Maven 国内镜像仓库
- QQ群638254979 (获取部署脚本) ![group](assets/group.png)
[![点击按钮加入 QQ群](https://img.shields.io/badge/-white?style=social&logo=QQ&label=或点击按钮加入QQ群)](https://qm.qq.com/q/9mvVC57jPO) [![点击按钮加入 QQ群](https://img.shields.io/badge/-white?style=social&logo=QQ&label=或点击按钮加入QQ群)](https://qm.qq.com/q/9mvVC57jPO)
实在没有 QQ 的可以加微信
- 微信Chuck9996 - QQ群638254979(目前人较多)
- 微信Chuck9996(若微信群已过期可以加我 vx)
## 🍅 相关课程 ## 🍅 相关课程
已上线: 已上线:
- [重构方法论与单元测试的艺术](https://www.bilibili.com/cheese/play/ep1615343) - [国内首个无幻觉式 AI 编程指南](https://www.bilibili.com/cheese/play/ep1615343)
敬请期待:(加群获取) 敬请期待:(加群获取)
@@ -70,11 +76,8 @@
用户通过和知路智能体对话,便可完成所有核心业务操作,不再需要再去学习操作复杂的前端页面。使你在任何时间都可享受运筹帷幄,指点江山的人生乐趣! 用户通过和知路智能体对话,便可完成所有核心业务操作,不再需要再去学习操作复杂的前端页面。使你在任何时间都可享受运筹帷幄,指点江山的人生乐趣!
**企业问答** ![actionChat](assets/action1.png)
![aichat](assets/aichat.png) ![actionChat](assets/action2.png)
**操作业务功能**
![actionChat](assets/action.png)
**更多功能正在锐意制作中,敬请期待。。。** **更多功能正在锐意制作中,敬请期待。。。**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

BIN
assets/action1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
assets/action2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

BIN
assets/group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

BIN
assets/wechat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@@ -15,3 +15,11 @@ ALLOWED_ORIGINS=http://localhost,https://localhost,http://localhost:8080,http://
ALLOWED_METHODS=* ALLOWED_METHODS=*
ALLOWED_HEADERS=* ALLOWED_HEADERS=*
ALLOWED_EXPOSE_HEADERS=* ALLOWED_EXPOSE_HEADERS=*
# minio
MINIO_ENDPOINT=http://host.docker.internal:9000
MINIO_EXPOSE_PORT=9000
MINIO_WEB_PORT=9001
MINIO_STORE=~/docker/store/mjga/minio/data
MINIO_ROOT_USER=minio
MINIO_ROOT_PASSWORD=minio123
MINIO_DEFAULT_BUCKETS=zhilu

View File

@@ -32,7 +32,7 @@ sourceSets {
group = "com.zl.mjga" group = "com.zl.mjga"
version = "1.0.0" version = "1.0.0"
description = "make java great again!" description = "make java great again!"
java.sourceCompatibility = JavaVersion.VERSION_17 java.sourceCompatibility = JavaVersion.VERSION_21
configurations { configurations {
compileOnly { compileOnly {
@@ -53,6 +53,7 @@ dependencies {
implementation("org.apache.commons:commons-lang3:3.17.0") implementation("org.apache.commons:commons-lang3:3.17.0")
implementation("org.apache.commons:commons-collections4:4.4") implementation("org.apache.commons:commons-collections4:4.4")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
implementation("io.minio:minio:8.5.17")
implementation("org.jooq:jooq-meta:$jooqVersion") implementation("org.jooq:jooq-meta:$jooqVersion")
implementation("com.auth0:java-jwt:4.4.0") implementation("com.auth0:java-jwt:4.4.0")
implementation("org.flywaydb:flyway-core:$flywayVersion") implementation("org.flywaydb:flyway-core:$flywayVersion")

View File

@@ -9,9 +9,11 @@
</description> </description>
<rule ref="category/java/bestpractices.xml"> <rule ref="category/java/bestpractices.xml">
<exclude name="GuardLogStatement"/> <exclude name="GuardLogStatement"/>
<exclude name="AvoidReassigningParameters"/>
</rule> </rule>
<rule ref="category/java/errorprone.xml"> <rule ref="category/java/errorprone.xml">
<exclude name="AvoidLiteralsInIfCondition"/> <exclude name="AvoidLiteralsInIfCondition"/>
</rule> <exclude name="AvoidDuplicateLiterals"/>
</ruleset> </rule>
</ruleset>

View File

@@ -2,8 +2,8 @@ rootProject.name = "backend"
pluginManagement { pluginManagement {
repositories { repositories {
maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") } // maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") }
maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/gradle-plugin/") } // maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/gradle-plugin/") }
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
} }
@@ -11,8 +11,8 @@ pluginManagement {
dependencyResolutionManagement { dependencyResolutionManagement {
repositories { repositories {
maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") } // maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") }
maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/apache-snapshots/") } // maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/apache-snapshots/") }
mavenCentral() mavenCentral()
} }
} }

View File

@@ -6,6 +6,7 @@ import com.zl.mjga.service.DepartmentService;
import dev.langchain4j.agent.tool.P; import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.model.output.structured.Description; import dev.langchain4j.model.output.structured.Description;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jooq.generated.mjga.tables.pojos.Department; import org.jooq.generated.mjga.tables.pojos.Department;
@@ -21,37 +22,65 @@ public class DepartmentOperatorTool {
@Tool(value = "创建部门") @Tool(value = "创建部门")
void createDepartment( void createDepartment(
@P(value = "部门名称") String departmentName, @P(value = "部门名称") @Size(min = 2, max = 15) String name,
@P(value = "上级部门名称", required = false) String parentDepartmentName) { @P(value = "上级部门名称", required = false) @Size(min = 2, max = 15) String parentName) {
Department exist = departmentRepository.fetchOneByName(departmentName); Department exist = departmentRepository.fetchOneByName(name);
Department department = new Department(null, name, null);
if (exist != null) { if (exist != null) {
throw new BusinessException("当前部门已存在"); throw new BusinessException("当前部门已存在");
} }
if (StringUtils.isNotEmpty(parentDepartmentName)) { if (StringUtils.isNotEmpty(parentName)) {
Department parent = departmentRepository.fetchOneByName(parentDepartmentName); Department parent = departmentRepository.fetchOneByName(parentName);
if (parent == null) { if (parent == null) {
throw new BusinessException("上级部门不存在"); throw new BusinessException("指定的上级部门不存在");
} else {
department.setParentId(parent.getId());
} }
} }
departmentService.upsertDepartment(new Department(null, departmentName, null));
}
@Tool(value = "更新部门/绑定上级部门/解绑上级部门")
void updateDepartment(
@P(value = "部门名称") String departmentName,
@P(value = "上级部门名称", required = false) String parentDepartmentName) {
Department exist = departmentRepository.fetchOneByName(departmentName);
if (exist == null) {
throw new BusinessException("不存在的部门");
}
Department department = new Department(null, departmentName, null);
if (StringUtils.isNotEmpty(parentDepartmentName)) {
Department parent = departmentRepository.fetchOneByName(parentDepartmentName);
if (parent == null) {
throw new BusinessException("上级部门不存在");
}
department.setParentId(parent.getId());
}
departmentService.upsertDepartment(department); departmentService.upsertDepartment(department);
} }
@Tool(value = {"更新部门信息"})
void updateDepartment(@P(value = "部门名称") String name) {
Department exist = departmentRepository.fetchOneByName(name);
if (exist == null) {
throw new BusinessException("不存在的部门");
}
exist.setName(name);
departmentService.upsertDepartment(exist);
}
@Tool(value = {"给指定部门绑定/分配上级部门"})
void bindParentDepartment(
@P(value = "部门名称") String name, @P(value = "上级部门名称") String parentName) {
Department exist = departmentRepository.fetchOneByName(name);
if (exist == null) {
throw new BusinessException("不存在的部门");
}
Department parent = departmentRepository.fetchOneByName(parentName);
if (parent == null) {
throw new BusinessException("上级部门不存在");
}
exist.setParentId(parent.getId());
departmentService.upsertDepartment(exist);
}
@Tool(value = {"给指定部门解绑/撤销上级部门"})
void unbindParentDepartment(@P(value = "部门名称") String name) {
Department exist = departmentRepository.fetchOneByName(name);
if (exist == null) {
throw new BusinessException("不存在的部门");
}
exist.setParentId(null);
departmentService.upsertDepartment(exist);
}
@Tool(value = "删除指定部门")
void deleteDepartment(@P(value = "部门名称") String name) {
Department exist = departmentRepository.fetchOneByName(name);
if (exist == null) {
throw new BusinessException("不存在的部门");
}
departmentRepository.deleteById(exist.getId());
}
} }

View File

@@ -5,10 +5,12 @@ import com.zl.mjga.repository.PositionRepository;
import dev.langchain4j.agent.tool.P; import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.model.output.structured.Description; import dev.langchain4j.model.output.structured.Description;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jooq.generated.mjga.tables.pojos.Position; import org.jooq.generated.mjga.tables.pojos.Position;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@Description("和岗位管理有关的操作工具") @Description("和岗位管理有关的操作工具")
@RequiredArgsConstructor @RequiredArgsConstructor
@Component @Component
@@ -16,17 +18,17 @@ public class PositionOperatorTool {
private final PositionRepository positionRepository; private final PositionRepository positionRepository;
@Tool(value = "创建岗位") @Tool(value = "创建岗位")
void createPosition(@P("岗位名称") String positionName) { void createPosition(@P("岗位名称") @Size(min = 2, max = 15) String name) {
Position position = positionRepository.fetchOneByName(positionName); Position position = positionRepository.fetchOneByName(name);
if (position != null) { if (position != null) {
throw new BusinessException("岗位已存在"); throw new BusinessException("岗位已存在");
} }
positionRepository.merge(new Position(null, positionName)); positionRepository.merge(new Position(null, name));
} }
@Tool(value = "删除岗位") @Tool(value = "删除岗位")
void deletePosition(@P("岗位名称") String positionName) { void deletePosition(@P("岗位名称") String name) {
Position position = positionRepository.fetchOneByName(positionName); Position position = positionRepository.fetchOneByName(name);
if (position == null) { if (position == null) {
throw new BusinessException("岗位不存在"); throw new BusinessException("岗位不存在");
} }

View File

@@ -1,25 +1,25 @@
package com.zl.mjga.component; package com.zl.mjga.component;
import com.zl.mjga.dto.department.DepartmentBindDto;
import com.zl.mjga.dto.position.PositionBindDto;
import com.zl.mjga.dto.urp.PermissionUpsertDto; import com.zl.mjga.dto.urp.PermissionUpsertDto;
import com.zl.mjga.dto.urp.RoleUpsertDto; import com.zl.mjga.dto.urp.RoleUpsertDto;
import com.zl.mjga.dto.urp.UserUpsertDto; import com.zl.mjga.dto.urp.UserUpsertDto;
import com.zl.mjga.exception.BusinessException; import com.zl.mjga.exception.BusinessException;
import com.zl.mjga.repository.PermissionRepository; import com.zl.mjga.repository.*;
import com.zl.mjga.repository.RoleRepository;
import com.zl.mjga.repository.UserRepository;
import com.zl.mjga.service.IdentityAccessService; import com.zl.mjga.service.IdentityAccessService;
import dev.langchain4j.agent.tool.P; import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.model.output.structured.Description; import dev.langchain4j.model.output.structured.Description;
import jakarta.validation.constraints.Size;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jooq.generated.mjga.tables.pojos.Permission; import org.jooq.generated.mjga.tables.pojos.*;
import org.jooq.generated.mjga.tables.pojos.Role;
import org.jooq.generated.mjga.tables.pojos.User;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Description("和用户管理有关的操作工具") @Description("和用户管理有关的操作工具")
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@RequiredArgsConstructor @RequiredArgsConstructor
@Component @Component
public class UserRolePermissionOperatorTool { public class UserRolePermissionOperatorTool {
@@ -28,14 +28,16 @@ public class UserRolePermissionOperatorTool {
private final UserRepository userRepository; private final UserRepository userRepository;
private final RoleRepository roleRepository; private final RoleRepository roleRepository;
private final PermissionRepository permissionRepository; private final PermissionRepository permissionRepository;
private final DepartmentRepository departmentRepository;
private final PositionRepository positionRepository;
@Tool(value = "创建用户或注册用户") @Tool(value = {"创建用户", "入职申请", "开通账号"})
void createUser(@P(value = "用户名") String username) { void createUser(@P(value = "用户名") @Size(min = 1, max = 15) String name) {
User user = userRepository.fetchOneByUsername(username); User user = userRepository.fetchOneByUsername(name);
if (user != null) { if (user != null) {
throw new BusinessException("用户已存在"); throw new BusinessException("用户已存在");
} }
identityAccessService.upsertUser(new UserUpsertDto(null, username, username, true)); identityAccessService.upsertUser(new UserUpsertDto(null, name, name, true, null));
} }
@Tool(value = "删除用户") @Tool(value = "删除用户")
@@ -43,34 +45,86 @@ public class UserRolePermissionOperatorTool {
userRepository.deleteByUsername(username); userRepository.deleteByUsername(username);
} }
@Tool(value = "编辑/更新/更改用户") @Tool(value = {"编辑用户", "更新用户", "更改用户"})
void updateUser( void updateUser(
@P(value = "用户名") String username, @P(value = "用户名") String name,
@P(value = "密码", required = false) String password, @P(value = "密码", required = false) String password,
@P(value = "是否开启", required = false) Boolean enable) { @P(value = "是否开启", required = false) Boolean enable) {
identityAccessService.upsertUser(new UserUpsertDto(null, username, password, enable)); identityAccessService.upsertUser(new UserUpsertDto(null, name, password, enable, null));
} }
@Tool(value = "给用户绑定/分配角色") @Tool(value = {"给用户绑定角色", "给用户分配角色"})
void bindRoleToUser( void bindRoleToUser(
@P(value = "用户名") String username, @P(value = "角色名称") List<String> roleNames) { @P(value = "用户名") String username, @P(value = "角色名称") List<String> roleNames) {
User user = userRepository.fetchOneByUsername(username); User user = checkUserExistBy(username);
if (user == null) {
throw new BusinessException("指定用户不存在");
}
List<Long> bindRoleIds = getRoleIdsBy(roleNames); List<Long> bindRoleIds = getRoleIdsBy(roleNames);
identityAccessService.bindRoleToUser(user.getId(), bindRoleIds); identityAccessService.bindRoleToUser(user.getId(), bindRoleIds);
} }
@Tool(value = "给用户解绑/撤销角色") @Tool(value = {"给用户解绑角色", "给用户撤销角色"})
void unbindRoleToUser( void unbindRoleToUser(
@P(value = "用户名") String username, @P(value = "角色名称") List<String> roleNames) { @P(value = "用户名") String username, @P(value = "角色名称") List<String> roleNames) {
User user = checkUserExistBy(username);
List<Long> bindRoleIds = getRoleIdsBy(roleNames);
identityAccessService.unBindRoleToUser(user.getId(), bindRoleIds);
}
@Tool(value = {"给用户绑定部门", "给用户分配部门"})
void bindDepartmentToUser(
@P(value = "用户名") String username, @P(value = "部门名称列表") List<String> departmentNames) {
User user = checkUserExistBy(username);
List<Department> departments =
departmentRepository.fetchByName(departmentNames.toArray(String[]::new));
if (departments.isEmpty()) {
throw new BusinessException("指定部门不存在");
}
identityAccessService.bindDepartmentBy(
new DepartmentBindDto(user.getId(), departments.stream().map(Department::getId).toList()));
}
@Tool(value = {"给用户解绑部门", "给用户撤销部门"})
void unbindDepartmentToUser(
@P(value = "用户名") String username, @P(value = "部门名称列表") List<String> departmentNames) {
User user = checkUserExistBy(username);
List<Department> departments =
departmentRepository.fetchByName(departmentNames.toArray(String[]::new));
if (departments.isEmpty()) {
throw new BusinessException("指定部门不存在");
}
identityAccessService.unBindDepartmentBy(
new DepartmentBindDto(user.getId(), departments.stream().map(Department::getId).toList()));
}
private User checkUserExistBy(String username) {
User user = userRepository.fetchOneByUsername(username); User user = userRepository.fetchOneByUsername(username);
if (user == null) { if (user == null) {
throw new BusinessException("指定用户不存在"); throw new BusinessException("指定用户不存在");
} }
List<Long> bindRoleIds = getRoleIdsBy(roleNames); return user;
identityAccessService.unBindRoleToUser(user.getId(), bindRoleIds); }
@Tool(value = {"给用户绑定岗位", "给用户分配岗位"})
void bindPositionToUser(
@P(value = "用户名") String username, @P(value = "岗位名称列表") List<String> positionNames) {
User user = checkUserExistBy(username);
List<Position> positions = positionRepository.fetchByName(positionNames.toArray(String[]::new));
if (positions.isEmpty()) {
throw new BusinessException("指定岗位不存在");
}
identityAccessService.bindPositionBy(
new PositionBindDto(user.getId(), positions.stream().map(Position::getId).toList()));
}
@Tool(value = {"给用户解绑岗位", "给用户撤销岗位"})
void unbindPositionToUser(
@P(value = "用户名") String username, @P(value = "岗位名称列表") List<String> positionNames) {
User user = checkUserExistBy(username);
List<Position> positions = positionRepository.fetchByName(positionNames.toArray(String[]::new));
if (positions.isEmpty()) {
throw new BusinessException("指定岗位不存在");
}
identityAccessService.unBindPositionBy(
new PositionBindDto(user.getId(), positions.stream().map(Position::getId).toList()));
} }
private List<Long> getRoleIdsBy(List<String> roleNames) { private List<Long> getRoleIdsBy(List<String> roleNames) {
@@ -81,33 +135,31 @@ public class UserRolePermissionOperatorTool {
return roles.stream().map(Role::getId).toList(); return roles.stream().map(Role::getId).toList();
} }
@Tool(value = "创建角色") @Tool(value = {"创建角色", "创建系统角色"})
void createRole( void createRole(
@P(value = "角色名称") String roleName, @P(value = "角色编码", required = false) String roleCode) { @P(value = "角色名称") String name, @P(value = "角色编码", required = false) String code) {
if (StringUtils.isEmpty(roleCode)) { if (StringUtils.isEmpty(code)) {
roleCode = roleName; code = name;
} }
if (identityAccessService.isRoleDuplicate(roleCode, roleName)) { if (identityAccessService.isRoleDuplicate(code, name)) {
throw new BusinessException("角色已存在"); throw new BusinessException("角色已存在");
} }
identityAccessService.upsertRole(new RoleUpsertDto(null, roleName, roleCode)); identityAccessService.upsertRole(new RoleUpsertDto(null, name, code));
} }
@Tool(value = "更新角色") @Tool(value = "更新角色")
void updateRole(@P(value = "角色名称") String roleName, @P(value = "角色编码") String roleCode) { void updateRole(@P(value = "角色名称") String name, @P(value = "角色编码") String code) {
identityAccessService.upsertRole(new RoleUpsertDto(null, roleName, roleCode)); identityAccessService.upsertRole(new RoleUpsertDto(null, name, code));
} }
@Tool(value = "更新权限") @Tool(value = "更新权限")
void updatePermission( void updatePermission(@P(value = "权限名称") String name, @P(value = "权限编码") String code) {
@P(value = "权限名称") String permissionName, @P(value = "权限编码") String permissionCode) { identityAccessService.upsertPermission(new PermissionUpsertDto(null, name, code));
identityAccessService.upsertPermission(
new PermissionUpsertDto(null, permissionName, permissionCode));
} }
@Tool(value = "删除角色") @Tool(value = "删除角色")
void deleteRole(@P(value = "角色名称") String roleName) { void deleteRole(@P(value = "角色名称") String name) {
Role role = roleRepository.fetchOneByName(roleName); Role role = roleRepository.fetchOneByName(name);
if (role == null) { if (role == null) {
throw new BusinessException("指定角色不存在"); throw new BusinessException("指定角色不存在");
} }
@@ -115,15 +167,15 @@ public class UserRolePermissionOperatorTool {
} }
@Tool(value = "删除权限") @Tool(value = "删除权限")
void deletePermission(@P(value = "权限名称") String permissionName) { void deletePermission(@P(value = "权限名称") String name) {
Permission permission = permissionRepository.fetchOneByName(permissionName); Permission permission = permissionRepository.fetchOneByName(name);
if (permission == null) { if (permission == null) {
throw new BusinessException("指定权限不存在"); throw new BusinessException("指定权限不存在");
} }
permissionRepository.deleteById(permission.getId()); permissionRepository.deleteById(permission.getId());
} }
@Tool(value = "给角色绑定/分配权限") @Tool(value = {"给角色绑定权限", "给用户分配权限"})
void bindPermissionToRole( void bindPermissionToRole(
@P(value = "角色名称") String roleName, @P(value = "权限名称") List<String> permissionNames) { @P(value = "角色名称") String roleName, @P(value = "权限名称") List<String> permissionNames) {
Role role = roleRepository.fetchOneByName(roleName); Role role = roleRepository.fetchOneByName(roleName);
@@ -139,7 +191,7 @@ public class UserRolePermissionOperatorTool {
role.getId(), permissions.stream().map(Permission::getId).toList()); role.getId(), permissions.stream().map(Permission::getId).toList());
} }
@Tool(value = "给角色解绑/撤销权限") @Tool(value = {"给角色解绑权限", "给角色撤销权限"})
void unBindPermissionToRole( void unBindPermissionToRole(
@P(value = "角色名称") String roleName, @P(value = "权限名称") List<String> permissionNames) { @P(value = "角色名称") String roleName, @P(value = "权限名称") List<String> permissionNames) {
Role role = roleRepository.fetchOneByName(roleName); Role role = roleRepository.fetchOneByName(roleName);
@@ -157,15 +209,13 @@ public class UserRolePermissionOperatorTool {
@Tool(value = "创建权限") @Tool(value = "创建权限")
void createPermission( void createPermission(
@P(value = "权限名称") String permissionName, @P(value = "权限名称") String name, @P(value = "权限编码", required = false) String code) {
@P(value = "权限编码", required = false) String permissionCode) { if (StringUtils.isEmpty(code)) {
if (StringUtils.isEmpty(permissionCode)) { code = name;
permissionCode = permissionName;
} }
if (identityAccessService.isPermissionDuplicate(permissionName, permissionName)) { if (identityAccessService.isPermissionDuplicate(name, name)) {
throw new BusinessException("权限已存在"); throw new BusinessException("权限已存在");
} }
identityAccessService.upsertPermission( identityAccessService.upsertPermission(new PermissionUpsertDto(null, name, code));
new PermissionUpsertDto(null, permissionName, permissionCode));
} }
} }

View File

@@ -5,6 +5,7 @@ import com.zl.mjga.component.PositionOperatorTool;
import com.zl.mjga.component.UserRolePermissionOperatorTool; import com.zl.mjga.component.UserRolePermissionOperatorTool;
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel; import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices; import dev.langchain4j.service.AiServices;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -28,4 +29,14 @@ public class ToolsInitializer {
.tools(userRolePermissionOperatorTool, departmentOperatorTool, positionOperatorTool) .tools(userRolePermissionOperatorTool, departmentOperatorTool, positionOperatorTool)
.build(); .build();
} }
@Bean
@DependsOn("flywayInitializer")
public SystemToolAssistant deepSeekToolAssistant(OpenAiStreamingChatModel deepSeekChatModel) {
return AiServices.builder(SystemToolAssistant.class)
.streamingChatModel(deepSeekChatModel)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.tools(userRolePermissionOperatorTool, departmentOperatorTool, positionOperatorTool)
.build();
}
} }

View File

@@ -0,0 +1,26 @@
package com.zl.mjga.config.minio;
import io.minio.MinioClient;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Setter
@Getter
@ConfigurationProperties(prefix = "minio")
@Slf4j
public class MinIoConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String defaultBucket;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
}
}

View File

@@ -5,8 +5,7 @@ import com.zl.mjga.dto.PageResponseDto;
import com.zl.mjga.dto.ai.LlmQueryDto; import com.zl.mjga.dto.ai.LlmQueryDto;
import com.zl.mjga.dto.ai.LlmVm; import com.zl.mjga.dto.ai.LlmVm;
import com.zl.mjga.exception.BusinessException; import com.zl.mjga.exception.BusinessException;
import com.zl.mjga.repository.DepartmentRepository; import com.zl.mjga.repository.*;
import com.zl.mjga.repository.UserRepository;
import com.zl.mjga.service.AiChatService; import com.zl.mjga.service.AiChatService;
import com.zl.mjga.service.EmbeddingService; import com.zl.mjga.service.EmbeddingService;
import com.zl.mjga.service.LlmService; import com.zl.mjga.service.LlmService;
@@ -20,9 +19,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jooq.generated.mjga.enums.LlmCodeEnum; import org.jooq.generated.mjga.enums.LlmCodeEnum;
import org.jooq.generated.mjga.tables.pojos.AiLlmConfig; import org.jooq.generated.mjga.tables.pojos.*;
import org.jooq.generated.mjga.tables.pojos.Department;
import org.jooq.generated.mjga.tables.pojos.User;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -41,23 +38,35 @@ public class AiController {
private final EmbeddingService embeddingService; private final EmbeddingService embeddingService;
private final UserRepository userRepository; private final UserRepository userRepository;
private final DepartmentRepository departmentRepository; private final DepartmentRepository departmentRepository;
private final PositionRepository positionRepository;
private final RoleRepository repository;
private final PermissionRepository permissionRepository;
private final RoleRepository roleRepository;
@PostMapping(value = "/action/execute", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @PostMapping(value = "/action/execute", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> actionExecute(Principal principal, @RequestBody String userMessage) { public Flux<String> actionExecute(Principal principal, @RequestBody String userMessage) {
Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer(); Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
TokenStream chat = aiChatService.actionExecuteWithZhiPu(principal.getName(), userMessage); TokenStream chat = aiChatService.actionPrecedenceExecuteWith(principal.getName(), userMessage);
chat.onPartialResponse( chat.onPartialResponse(
text -> (text) -> {
sink.tryEmitNext( log.debug("ai action partialResponse: {}", text);
StringUtils.isNotEmpty(text) ? text.replace(" ", "").replace("\t", "") : "")) sink.tryEmitNext(
StringUtils.isNotEmpty(text) ? text.replace(" ", "").replace("\t", "") : "");
})
.onToolExecuted( .onToolExecuted(
toolExecution -> log.debug("当前请求 {} 成功执行函数调用: {}", userMessage, toolExecution)) toolExecution -> log.debug("当前请求 {} 成功执行函数调用: {}", userMessage, toolExecution))
.onCompleteResponse( .onCompleteResponse(
r -> { r -> {
log.debug("ai action completeResponse: {}", r);
sink.tryEmitComplete();
sink.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST);
})
.onError(
(e) -> {
sink.tryEmitError(e);
sink.tryEmitComplete(); sink.tryEmitComplete();
sink.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); sink.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST);
}) })
.onError(sink::tryEmitError)
.start(); .start();
return sink.asFlux().timeout(Duration.ofSeconds(120)); return sink.asFlux().timeout(Duration.ofSeconds(120));
} }
@@ -116,7 +125,7 @@ public class AiController {
userRepository.deleteByUsername(username); userRepository.deleteByUsername(username);
} }
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_DEPARTMENT_PERMISSION)")
@DeleteMapping("/action/department") @DeleteMapping("/action/department")
void deleteDepartment(@RequestParam String name) { void deleteDepartment(@RequestParam String name) {
Department department = departmentRepository.fetchOneByName(name); Department department = departmentRepository.fetchOneByName(name);
@@ -125,4 +134,39 @@ public class AiController {
} }
departmentRepository.deleteByName(name); departmentRepository.deleteByName(name);
} }
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_POSITION_PERMISSION)")
@DeleteMapping("/action/position")
void deletePosition(@RequestParam String name) {
Position position = positionRepository.fetchOneByName(name);
if (position == null) {
throw new BusinessException("该岗位不存在");
}
positionRepository.deleteById(position.getId());
}
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@DeleteMapping("/action/role")
void deleteRole(@RequestParam String name) {
Role role = roleRepository.fetchOneByName(name);
if (role == null) {
throw new BusinessException("该角色不存在");
}
roleRepository.deleteById(role.getId());
}
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@DeleteMapping("/action/permission")
void deletePermission(@RequestParam String name) {
Permission permission = permissionRepository.fetchOneByName(name);
if (permission == null) {
throw new BusinessException("该权限不存在");
}
permissionRepository.deleteById(permission.getId());
}
@PostMapping("/chat/refresh")
void createNewConversation(Principal principal) {
aiChatService.evictChatMemory(principal.getName());
}
} }

View File

@@ -4,6 +4,7 @@ import com.zl.mjga.dto.PageRequestDto;
import com.zl.mjga.dto.PageResponseDto; import com.zl.mjga.dto.PageResponseDto;
import com.zl.mjga.dto.department.DepartmentQueryDto; import com.zl.mjga.dto.department.DepartmentQueryDto;
import com.zl.mjga.dto.department.DepartmentRespDto; import com.zl.mjga.dto.department.DepartmentRespDto;
import com.zl.mjga.dto.department.DepartmentWithParentDto;
import com.zl.mjga.repository.DepartmentRepository; import com.zl.mjga.repository.DepartmentRepository;
import com.zl.mjga.service.DepartmentService; import com.zl.mjga.service.DepartmentService;
import java.util.List; import java.util.List;
@@ -37,6 +38,12 @@ public class DepartmentController {
return departmentService.queryAvailableParentDepartmentsBy(id); return departmentService.queryAvailableParentDepartmentsBy(id);
} }
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_DEPARTMENT_PERMISSION)")
@GetMapping("/query-sub")
List<DepartmentWithParentDto> querySubDepartment(@RequestParam(required = false) Long id) {
return departmentService.queryDepartmentAndSubsBy(id);
}
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_DEPARTMENT_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_DEPARTMENT_PERMISSION)")
@DeleteMapping() @DeleteMapping()
void deleteDepartment(@RequestParam Long id) { void deleteDepartment(@RequestParam Long id) {

View File

@@ -1,5 +1,6 @@
package com.zl.mjga.controller; package com.zl.mjga.controller;
import com.zl.mjga.config.minio.MinIoConfig;
import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.PageRequestDto;
import com.zl.mjga.dto.PageResponseDto; import com.zl.mjga.dto.PageResponseDto;
import com.zl.mjga.dto.department.DepartmentBindDto; import com.zl.mjga.dto.department.DepartmentBindDto;
@@ -12,17 +13,25 @@ import com.zl.mjga.repository.PermissionRepository;
import com.zl.mjga.repository.RoleRepository; import com.zl.mjga.repository.RoleRepository;
import com.zl.mjga.repository.UserRepository; import com.zl.mjga.repository.UserRepository;
import com.zl.mjga.service.IdentityAccessService; import com.zl.mjga.service.IdentityAccessService;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.awt.image.BufferedImage;
import java.security.Principal; import java.security.Principal;
import java.time.Instant;
import java.util.List; import java.util.List;
import javax.imageio.ImageIO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.jooq.generated.mjga.tables.pojos.User; import org.jooq.generated.mjga.tables.pojos.User;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@RestController @RestController
@RequestMapping("/iam") @RequestMapping("/iam")
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -32,6 +41,50 @@ public class IdentityAccessController {
private final UserRepository userRepository; private final UserRepository userRepository;
private final RoleRepository roleRepository; private final RoleRepository roleRepository;
private final PermissionRepository permissionRepository; private final PermissionRepository permissionRepository;
private final MinioClient minioClient;
private final MinIoConfig minIoConfig;
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@PostMapping(
value = "/avatar/upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.TEXT_PLAIN_VALUE)
public String uploadAvatar(@RequestPart("file") MultipartFile multipartFile) throws Exception {
String originalFilename = multipartFile.getOriginalFilename();
if (StringUtils.isEmpty(originalFilename)) {
throw new BusinessException("文件名不能为空");
}
String contentType = multipartFile.getContentType();
String extension = "";
if ("image/jpeg".equals(contentType)) {
extension = ".jpg";
} else if ("image/png".equals(contentType)) {
extension = ".png";
}
String objectName =
String.format(
"/avatar/%d%s%s",
Instant.now().toEpochMilli(),
RandomStringUtils.insecure().nextAlphabetic(6),
extension);
if (multipartFile.isEmpty()) {
throw new BusinessException("上传的文件不能为空");
}
long size = multipartFile.getSize();
if (size > 200 * 1024) {
throw new BusinessException("头像文件大小不能超过200KB");
}
BufferedImage img = ImageIO.read(multipartFile.getInputStream());
if (img == null) {
throw new BusinessException("非法的上传文件");
}
minioClient.putObject(
PutObjectArgs.builder().bucket(minIoConfig.getDefaultBucket()).object(objectName).stream(
multipartFile.getInputStream(), size, -1)
.contentType(multipartFile.getContentType())
.build());
return objectName;
}
@GetMapping("/me") @GetMapping("/me")
UserRolePermissionDto currentUser(Principal principal) { UserRolePermissionDto currentUser(Principal principal) {
@@ -66,9 +119,6 @@ public class IdentityAccessController {
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@DeleteMapping("/user") @DeleteMapping("/user")
void deleteUser(@RequestParam Long userId) { void deleteUser(@RequestParam Long userId) {
if (userId == 1) {
throw new BusinessException("演示系统不允许操作管理员角色");
}
userRepository.deleteById(userId); userRepository.deleteById(userId);
} }
@@ -81,15 +131,12 @@ public class IdentityAccessController {
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@DeleteMapping("/role") @DeleteMapping("/role")
void deleteRole(@RequestParam Long roleId) { void deleteRole(@RequestParam Long roleId) {
if (roleId == 1) {
throw new BusinessException("演示系统不允许删除管理员角色");
}
roleRepository.deleteById(roleId); roleRepository.deleteById(roleId);
} }
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@GetMapping("/role") @GetMapping("/role")
RoleDto queryRoleWithPermission(@RequestParam Long roleId) { RoleRespDto queryRoleWithPermission(@RequestParam Long roleId) {
return identityAccessService.queryUniqueRoleWithPermission(roleId); return identityAccessService.queryUniqueRoleWithPermission(roleId);
} }
@@ -102,9 +149,6 @@ public class IdentityAccessController {
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
@DeleteMapping("/permission") @DeleteMapping("/permission")
void deletePermission(@RequestParam Long permissionId) { void deletePermission(@RequestParam Long permissionId) {
if (permissionId < 10) {
throw new BusinessException("演示系统不允许删除原有权限");
}
permissionRepository.deleteById(permissionId); permissionRepository.deleteById(permissionId);
} }
@@ -119,7 +163,7 @@ public class IdentityAccessController {
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_USER_ROLE_PERMISSION)") @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_USER_ROLE_PERMISSION)")
@GetMapping("/roles") @GetMapping("/roles")
@ResponseStatus(HttpStatus.OK) @ResponseStatus(HttpStatus.OK)
PageResponseDto<List<RoleDto>> queryRoles( PageResponseDto<List<RoleRespDto>> queryRoles(
@ModelAttribute PageRequestDto pageRequestDto, @ModelAttribute RoleQueryDto roleQueryDto) { @ModelAttribute PageRequestDto pageRequestDto, @ModelAttribute RoleQueryDto roleQueryDto) {
return identityAccessService.pageQueryRole(pageRequestDto, roleQueryDto); return identityAccessService.pageQueryRole(pageRequestDto, roleQueryDto);
} }

View File

@@ -79,7 +79,7 @@ public class PageRequestDto {
.sort(SortOrder.valueOf(entry.getValue().getKeyword()))) .sort(SortOrder.valueOf(entry.getValue().getKeyword())))
.toList(); .toList();
if (sortFields.isEmpty()) { if (sortFields.isEmpty()) {
return List.of(field(name("id")).sort(SortOrder.ASC)); return List.of(field(name("id")).sort(SortOrder.DESC));
} else { } else {
return sortFields; return sortFields;
} }

View File

@@ -7,6 +7,7 @@ import lombok.*;
@Data @Data
@Builder @Builder
public class PermissionRespDto { public class PermissionRespDto {
private Long id; private Long id;
private String code; private String code;
private String name; private String name;

View File

@@ -1,5 +1,6 @@
package com.zl.mjga.dto.urp; package com.zl.mjga.dto.urp;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import lombok.*; import lombok.*;
@@ -8,10 +9,18 @@ import lombok.*;
@NoArgsConstructor @NoArgsConstructor
@Data @Data
@Builder @Builder
public class RoleDto { public class RoleRespDto {
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private Long id; private Long id;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private String code; private String code;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private String name; private String name;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean isBound; private Boolean isBound;
@Builder.Default List<PermissionRespDto> permissions = new LinkedList<>(); @Builder.Default List<PermissionRespDto> permissions = new LinkedList<>();
} }

View File

@@ -1,5 +1,6 @@
package com.zl.mjga.dto.urp; package com.zl.mjga.dto.urp;
import java.time.OffsetDateTime;
import lombok.*; import lombok.*;
@AllArgsConstructor @AllArgsConstructor
@@ -7,4 +8,6 @@ import lombok.*;
@Data @Data
public class UserQueryDto { public class UserQueryDto {
private String username; private String username;
private OffsetDateTime startDate;
private OffsetDateTime endDate;
} }

View File

@@ -1,6 +1,7 @@
package com.zl.mjga.dto.urp; package com.zl.mjga.dto.urp;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -13,15 +14,24 @@ import lombok.*;
@Data @Data
@Builder @Builder
public class UserRolePermissionDto { public class UserRolePermissionDto {
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private Long id; private Long id;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private String username; private String username;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password; private String password;
private Boolean enable; private String avatar;
@Builder.Default private List<RoleDto> roles = new LinkedList<>();
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean enable;
@Builder.Default private List<RoleRespDto> roles = new LinkedList<>();
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private OffsetDateTime createTime; private OffsetDateTime createTime;
public Set<PermissionRespDto> getPermissions() { public Set<PermissionRespDto> getPermissions() {

View File

@@ -14,4 +14,5 @@ public class UserUpsertDto {
@NotEmpty private String username; @NotEmpty private String username;
private String password; private String password;
@NotNull private Boolean enable; @NotNull private Boolean enable;
private String avatar;
} }

View File

@@ -7,9 +7,15 @@ import lombok.Getter;
@Getter @Getter
public enum Actions { public enum Actions {
CREATE_USER("CREATE_USER", "创建用户"), CREATE_USER("CREATE_USER", "创建用户"),
CREATE_DEPARTMENT("CREATE_DEPARTMENT", "创建部门"),
DELETE_USER("DELETE_USER", "删除用户"), DELETE_USER("DELETE_USER", "删除用户"),
DELETE_DEPARTMENT("DELETE_DEPARTMENT", "删除部门"); CREATE_DEPARTMENT("CREATE_DEPARTMENT", "创建部门"),
DELETE_DEPARTMENT("DELETE_DEPARTMENT", "删除部门"),
CREATE_POSITION("CREATE_POSITION", "创建岗位"),
DELETE_POSITION("DELETE_POSITION", "删除岗位"),
CREATE_ROLE("CREATE_ROLE", "创建角色"),
DELETE_ROLE("CREATE_ROLE", "删除角色"),
CREATE_PERMISSION("CREATE_PERMISSION", "创建权限"),
DELETE_PERMISSION("DELETE_PERMISSION", "删除权限");
public static final String INDEX_KEY = "action"; public static final String INDEX_KEY = "action";
private final String code; private final String code;
private final String content; private final String content;

View File

@@ -37,7 +37,8 @@ public class DepartmentRepository extends DepartmentDao {
DEPARTMENT.PARENT_ID, DEPARTMENT.PARENT_ID,
DEPARTMENT.NAME.cast(VARCHAR)) DEPARTMENT.NAME.cast(VARCHAR))
.from(DEPARTMENT) .from(DEPARTMENT)
.where(DEPARTMENT.ID.eq(id)) .where(id == null ? noCondition() : DEPARTMENT.ID.eq(id))
.and(DEPARTMENT.PARENT_ID.isNull())
.unionAll( .unionAll(
select( select(
DEPARTMENT.ID, DEPARTMENT.ID,

View File

@@ -1,14 +1,14 @@
package com.zl.mjga.repository; package com.zl.mjga.repository;
import static org.jooq.generated.mjga.Tables.*;
import static org.jooq.generated.mjga.tables.User.USER; import static org.jooq.generated.mjga.tables.User.USER;
import static org.jooq.impl.DSL.*; import static org.jooq.impl.DSL.*;
import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.PageRequestDto;
import com.zl.mjga.dto.urp.PermissionRespDto; import com.zl.mjga.dto.urp.PermissionRespDto;
import com.zl.mjga.dto.urp.RoleDto; import com.zl.mjga.dto.urp.RoleRespDto;
import com.zl.mjga.dto.urp.UserQueryDto; import com.zl.mjga.dto.urp.UserQueryDto;
import com.zl.mjga.dto.urp.UserRolePermissionDto; import com.zl.mjga.dto.urp.UserRolePermissionDto;
import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jooq.*; import org.jooq.*;
import org.jooq.Record; import org.jooq.Record;
@@ -36,6 +36,7 @@ public class UserRepository extends UserDao {
value(user.getId()).as("id"), value(user.getId()).as("id"),
value(user.getUsername()).as("username"), value(user.getUsername()).as("username"),
value(user.getPassword()).as("password"), value(user.getPassword()).as("password"),
value(user.getAvatar()).as("avatar"),
value(user.getEnable()).as("enable")) value(user.getEnable()).as("enable"))
.asTable("newUser")) .asTable("newUser"))
.on(USER.ID.eq(DSL.field(DSL.name("newUser", "id"), Long.class))) .on(USER.ID.eq(DSL.field(DSL.name("newUser", "id"), Long.class)))
@@ -46,16 +47,18 @@ public class UserRepository extends UserDao {
StringUtils.isNotEmpty(user.getPassword()) StringUtils.isNotEmpty(user.getPassword())
? DSL.field(DSL.name("newUser", "password"), String.class) ? DSL.field(DSL.name("newUser", "password"), String.class)
: USER.PASSWORD) : USER.PASSWORD)
.set(USER.AVATAR, DSL.field(DSL.name("newUser", "avatar"), String.class))
.set(USER.ENABLE, DSL.field(DSL.name("newUser", "enable"), Boolean.class)) .set(USER.ENABLE, DSL.field(DSL.name("newUser", "enable"), Boolean.class))
.whenNotMatchedThenInsert(USER.USERNAME, USER.PASSWORD, USER.ENABLE) .whenNotMatchedThenInsert(USER.USERNAME, USER.PASSWORD, USER.AVATAR, USER.ENABLE)
.values( .values(
DSL.field(DSL.name("newUser", "username"), String.class), DSL.field(DSL.name("newUser", "username"), String.class),
DSL.field(DSL.name("newUser", "password"), String.class), DSL.field(DSL.name("newUser", "password"), String.class),
DSL.field(DSL.name("newUser", "avatar"), String.class),
DSL.field(DSL.name("newUser", "enable"), Boolean.class)) DSL.field(DSL.name("newUser", "enable"), Boolean.class))
.execute(); .execute();
} }
public Result<Record> pageFetchBy(PageRequestDto pageRequestDto, UserQueryDto userQueryDto) { public SelectConditionStep<Record> selectBy(UserQueryDto userQueryDto) {
return ctx() return ctx()
.select(asterisk(), DSL.count().over().as("total_user")) .select(asterisk(), DSL.count().over().as("total_user"))
.from(USER) .from(USER)
@@ -63,6 +66,22 @@ public class UserRepository extends UserDao {
userQueryDto.getUsername() != null userQueryDto.getUsername() != null
? USER.USERNAME.like("%" + userQueryDto.getUsername() + "%") ? USER.USERNAME.like("%" + userQueryDto.getUsername() + "%")
: noCondition()) : noCondition())
.and(
userQueryDto.getStartDate() != null
? USER.CREATE_TIME.ge(userQueryDto.getStartDate())
: noCondition())
.and(
userQueryDto.getEndDate() != null
? USER.CREATE_TIME.le(userQueryDto.getEndDate())
: noCondition());
}
public List<User> fetchBy(UserQueryDto userQueryDto) {
return selectBy(userQueryDto).fetchInto(User.class);
}
public Result<Record> pageFetchBy(PageRequestDto pageRequestDto, UserQueryDto userQueryDto) {
return selectBy(userQueryDto)
.orderBy(pageRequestDto.getSortFields()) .orderBy(pageRequestDto.getSortFields())
.limit(pageRequestDto.getSize()) .limit(pageRequestDto.getSize())
.offset(pageRequestDto.getOffset()) .offset(pageRequestDto.getOffset())
@@ -83,7 +102,7 @@ public class UserRepository extends UserDao {
r -> r.map((record) -> record.into(PermissionRespDto.class))) r -> r.map((record) -> record.into(PermissionRespDto.class)))
.as("permissions")) .as("permissions"))
.from(USER.role())) .from(USER.role()))
.convertFrom(r -> r.map((record) -> record.into(RoleDto.class))) .convertFrom(r -> r.map((record) -> record.into(RoleRespDto.class)))
.as("roles")) .as("roles"))
.from(USER) .from(USER)
.where(USER.ID.eq(userId)) .where(USER.ID.eq(userId))

View File

@@ -19,6 +19,7 @@ public class AiChatService {
private final AiChatAssistant deepSeekChatAssistant; private final AiChatAssistant deepSeekChatAssistant;
private final AiChatAssistant zhiPuChatAssistant; private final AiChatAssistant zhiPuChatAssistant;
private final SystemToolAssistant zhiPuToolAssistant; private final SystemToolAssistant zhiPuToolAssistant;
private final SystemToolAssistant deepSeekToolAssistant;
private final LlmService llmService; private final LlmService llmService;
public TokenStream chatWithDeepSeek(String sessionIdentifier, String userMessage) { public TokenStream chatWithDeepSeek(String sessionIdentifier, String userMessage) {
@@ -29,8 +30,13 @@ public class AiChatService {
return zhiPuChatAssistant.chat(sessionIdentifier, userMessage); return zhiPuChatAssistant.chat(sessionIdentifier, userMessage);
} }
public TokenStream actionExecuteWithZhiPu(String sessionIdentifier, String userMessage) { public TokenStream actionPrecedenceExecuteWith(String sessionIdentifier, String userMessage) {
return zhiPuToolAssistant.ask(sessionIdentifier, userMessage); LlmCodeEnum code = getPrecedenceLlmCode();
return switch (code) {
case ZHI_PU -> zhiPuToolAssistant.ask(sessionIdentifier, userMessage);
case DEEP_SEEK -> deepSeekToolAssistant.ask(sessionIdentifier, userMessage);
default -> throw new BusinessException(String.format("无效的模型代码 %s", code));
};
} }
public TokenStream chatPrecedenceLlmWith(String sessionIdentifier, String userMessage) { public TokenStream chatPrecedenceLlmWith(String sessionIdentifier, String userMessage) {
@@ -47,4 +53,11 @@ public class AiChatService {
AiLlmConfig aiLlmConfig = precedenceLlmBy.orElseThrow(() -> new BusinessException("没有开启的大模型")); AiLlmConfig aiLlmConfig = precedenceLlmBy.orElseThrow(() -> new BusinessException("没有开启的大模型"));
return aiLlmConfig.getCode(); return aiLlmConfig.getCode();
} }
public void evictChatMemory(String sessionIdentifier) {
deepSeekChatAssistant.evictChatMemory(sessionIdentifier);
zhiPuChatAssistant.evictChatMemory(sessionIdentifier);
zhiPuToolAssistant.evictChatMemory(sessionIdentifier);
deepSeekToolAssistant.evictChatMemory(sessionIdentifier);
}
} }

View File

@@ -80,17 +80,17 @@ public class IdentityAccessService {
return userRepository.fetchUniqueUserDtoWithNestedRolePermissionBy(userId); return userRepository.fetchUniqueUserDtoWithNestedRolePermissionBy(userId);
} }
public PageResponseDto<List<RoleDto>> pageQueryRole( public PageResponseDto<List<RoleRespDto>> pageQueryRole(
PageRequestDto pageRequestDto, RoleQueryDto roleQueryDto) { PageRequestDto pageRequestDto, RoleQueryDto roleQueryDto) {
Result<Record> roleRecords = roleRepository.pageFetchBy(pageRequestDto, roleQueryDto); Result<Record> roleRecords = roleRepository.pageFetchBy(pageRequestDto, roleQueryDto);
if (roleRecords.isEmpty()) { if (roleRecords.isEmpty()) {
return PageResponseDto.empty(); return PageResponseDto.empty();
} }
List<RoleDto> roleDtoList = List<RoleRespDto> roleRespDtoList =
roleRecords.stream() roleRecords.stream()
.map( .map(
record -> { record -> {
return RoleDto.builder() return RoleRespDto.builder()
.id(record.getValue("id", Long.class)) .id(record.getValue("id", Long.class))
.code(record.getValue("code", String.class)) .code(record.getValue("code", String.class))
.name(record.getValue("name", String.class)) .name(record.getValue("name", String.class))
@@ -103,17 +103,17 @@ public class IdentityAccessService {
}) })
.toList(); .toList();
return new PageResponseDto<>( return new PageResponseDto<>(
roleRecords.get(0).getValue("total_role", Integer.class), roleDtoList); roleRecords.get(0).getValue("total_role", Integer.class), roleRespDtoList);
} }
public @Nullable RoleDto queryUniqueRoleWithPermission(Long roleId) { public @Nullable RoleRespDto queryUniqueRoleWithPermission(Long roleId) {
Result<Record> roleWithPermissionRecords = roleRepository.fetchUniqueRoleWithPermission(roleId); Result<Record> roleWithPermissionRecords = roleRepository.fetchUniqueRoleWithPermission(roleId);
if (roleWithPermissionRecords.isEmpty()) { if (roleWithPermissionRecords.isEmpty()) {
return null; return null;
} }
RoleDto roleDto = createRbacDtoRolePart(roleWithPermissionRecords); RoleRespDto roleRespDto = createRbacDtoRolePart(roleWithPermissionRecords);
setCurrentRolePermission(roleDto, roleWithPermissionRecords); setCurrentRolePermission(roleRespDto, roleWithPermissionRecords);
return roleDto; return roleRespDto;
} }
public PageResponseDto<List<PermissionRespDto>> pageQueryPermission( public PageResponseDto<List<PermissionRespDto>> pageQueryPermission(
@@ -199,12 +199,12 @@ public class IdentityAccessService {
.toList()); .toList());
} }
private void setCurrentRolePermission(RoleDto roleDto, List<Record> roleResult) { private void setCurrentRolePermission(RoleRespDto roleRespDto, List<Record> roleResult) {
if (roleResult.get(0).getValue(PERMISSION.ID) != null) { if (roleResult.get(0).getValue(PERMISSION.ID) != null) {
roleResult.forEach( roleResult.forEach(
(record) -> { (record) -> {
PermissionRespDto permissionRespDto = createRbacDtoPermissionPart(record); PermissionRespDto permissionRespDto = createRbacDtoPermissionPart(record);
roleDto.getPermissions().add(permissionRespDto); roleRespDto.getPermissions().add(permissionRespDto);
}); });
} }
} }
@@ -217,12 +217,12 @@ public class IdentityAccessService {
return permissionRespDto; return permissionRespDto;
} }
private RoleDto createRbacDtoRolePart(List<Record> roleResult) { private RoleRespDto createRbacDtoRolePart(List<Record> roleResult) {
RoleDto roleDto = new RoleDto(); RoleRespDto roleRespDto = new RoleRespDto();
roleDto.setId(roleResult.get(0).getValue(ROLE.ID)); roleRespDto.setId(roleResult.get(0).getValue(ROLE.ID));
roleDto.setCode(roleResult.get(0).getValue(ROLE.CODE)); roleRespDto.setCode(roleResult.get(0).getValue(ROLE.CODE));
roleDto.setName(roleResult.get(0).getValue(ROLE.NAME)); roleRespDto.setName(roleResult.get(0).getValue(ROLE.NAME));
return roleDto; return roleRespDto;
} }
public boolean isRoleDuplicate(String roleCode, String name) { public boolean isRoleDuplicate(String roleCode, String name) {

View File

@@ -27,9 +27,18 @@ spring:
enabled: true enabled: true
locations: classpath:db/migration locations: classpath:db/migration
default-schema: ${DATABASE_DEFAULT_SCHEMA} default-schema: ${DATABASE_DEFAULT_SCHEMA}
servlet:
multipart:
max-file-size: 1MB
max-request-size: 10MB
springdoc: springdoc:
swagger-ui: swagger-ui:
path: /swagger-ui.html path: /swagger-ui.html
jwt: jwt:
secret: ${JWT_SECRET:secret} secret: ${JWT_SECRET:secret}
expiration-min: ${JWT_EXPIRATION_MIN:100} expiration-min: ${JWT_EXPIRATION_MIN:100}
minio:
endpoint: ${MINIO_ENDPOINT}
access-key: ${MINIO_ROOT_USER}
secret-key: ${MINIO_ROOT_PASSWORD}
default-bucket: ${MINIO_DEFAULT_BUCKETS}

View File

@@ -3,7 +3,8 @@ CREATE SCHEMA IF NOT EXISTS mjga;
CREATE TABLE mjga.user ( CREATE TABLE mjga.user (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
username VARCHAR NOT NULL UNIQUE, username VARCHAR NOT NULL UNIQUE,
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, avatar VARCHAR,
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
password VARCHAR NOT NULL, password VARCHAR NOT NULL,
enable BOOLEAN NOT NULL DEFAULT TRUE enable BOOLEAN NOT NULL DEFAULT TRUE
); );

View File

@@ -5,6 +5,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.zl.mjga.config.minio.MinIoConfig;
import com.zl.mjga.config.security.HttpFireWallConfig; import com.zl.mjga.config.security.HttpFireWallConfig;
import com.zl.mjga.controller.IdentityAccessController; import com.zl.mjga.controller.IdentityAccessController;
import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.PageRequestDto;
@@ -15,6 +16,7 @@ import com.zl.mjga.repository.PermissionRepository;
import com.zl.mjga.repository.RoleRepository; import com.zl.mjga.repository.RoleRepository;
import com.zl.mjga.repository.UserRepository; import com.zl.mjga.repository.UserRepository;
import com.zl.mjga.service.IdentityAccessService; import com.zl.mjga.service.IdentityAccessService;
import io.minio.MinioClient;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +36,8 @@ public class JacksonAnnotationMvcTest {
@MockBean private UserRepository userRepository; @MockBean private UserRepository userRepository;
@MockBean private RoleRepository roleRepository; @MockBean private RoleRepository roleRepository;
@MockBean private PermissionRepository permissionRepository; @MockBean private PermissionRepository permissionRepository;
@MockBean private MinioClient minioClient;
@MockBean private MinIoConfig minIoConfig;
@Test @Test
@WithMockUser @WithMockUser
@@ -46,7 +50,7 @@ public class JacksonAnnotationMvcTest {
stubUserRolePermissionDto.setUsername(stubUsername); stubUserRolePermissionDto.setUsername(stubUsername);
stubUserRolePermissionDto.setPassword(stubPassword); stubUserRolePermissionDto.setPassword(stubPassword);
when(identityAccessService.pageQueryUser( when(identityAccessService.pageQueryUser(
PageRequestDto.of(1, 5), new UserQueryDto(stubUsername))) PageRequestDto.of(1, 5), new UserQueryDto(stubUsername, null, null)))
.thenReturn(new PageResponseDto<>(1, List.of(stubUserRolePermissionDto))); .thenReturn(new PageResponseDto<>(1, List.of(stubUserRolePermissionDto)));
mockMvc mockMvc
.perform( .perform(

View File

@@ -7,6 +7,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.zl.mjga.config.minio.MinIoConfig;
import com.zl.mjga.config.security.HttpFireWallConfig; import com.zl.mjga.config.security.HttpFireWallConfig;
import com.zl.mjga.controller.IdentityAccessController; import com.zl.mjga.controller.IdentityAccessController;
import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.PageRequestDto;
@@ -16,6 +17,7 @@ import com.zl.mjga.repository.PermissionRepository;
import com.zl.mjga.repository.RoleRepository; import com.zl.mjga.repository.RoleRepository;
import com.zl.mjga.repository.UserRepository; import com.zl.mjga.repository.UserRepository;
import com.zl.mjga.service.IdentityAccessService; import com.zl.mjga.service.IdentityAccessService;
import io.minio.MinioClient;
import java.util.List; import java.util.List;
import org.jooq.generated.mjga.tables.pojos.User; import org.jooq.generated.mjga.tables.pojos.User;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -36,6 +38,8 @@ class UserRolePermissionMvcTest {
@MockBean private UserRepository userRepository; @MockBean private UserRepository userRepository;
@MockBean private RoleRepository roleRepository; @MockBean private RoleRepository roleRepository;
@MockBean private PermissionRepository permissionRepository; @MockBean private PermissionRepository permissionRepository;
@MockBean private MinioClient minioClient;
@MockBean private MinIoConfig minIoConfig;
@Test @Test
@WithMockUser @WithMockUser
@@ -59,7 +63,7 @@ class UserRolePermissionMvcTest {
@Test @Test
@WithMockUser @WithMockUser
void deleteUser_givenValidHttpRequest_shouldSucceedWith200() throws Exception { void deleteUser_givenValidHttpRequest_shouldSucceedWith200() throws Exception {
Long stubUserId = 1L; Long stubUserId = 2L;
mockMvc mockMvc
.perform( .perform(
delete(String.format("/iam/user?userId=%s", stubUserId)) delete(String.format("/iam/user?userId=%s", stubUserId))
@@ -152,7 +156,7 @@ class UserRolePermissionMvcTest {
stubUserRolePermissionDto.setId(1L); stubUserRolePermissionDto.setId(1L);
stubUserRolePermissionDto.setUsername(stubUsername); stubUserRolePermissionDto.setUsername(stubUsername);
when(identityAccessService.pageQueryUser( when(identityAccessService.pageQueryUser(
PageRequestDto.of(1, 5), new UserQueryDto(stubUsername))) PageRequestDto.of(1, 5), new UserQueryDto(stubUsername, null, null)))
.thenReturn(new PageResponseDto<>(1, List.of(stubUserRolePermissionDto))); .thenReturn(new PageResponseDto<>(1, List.of(stubUserRolePermissionDto)));
mockMvc mockMvc
.perform( .perform(
@@ -174,14 +178,14 @@ class UserRolePermissionMvcTest {
stubRoleQueryDto.setRoleId(stubRoleId); stubRoleQueryDto.setRoleId(stubRoleId);
stubRoleQueryDto.setRoleCode(stubRoleCode); stubRoleQueryDto.setRoleCode(stubRoleCode);
stubRoleQueryDto.setRoleName(stubRoleName); stubRoleQueryDto.setRoleName(stubRoleName);
RoleDto stubRoleDto = new RoleDto(); RoleRespDto stubRoleRespDto = new RoleRespDto();
stubRoleDto.setId(1L); stubRoleRespDto.setId(1L);
stubRoleDto.setName(stubRoleName); stubRoleRespDto.setName(stubRoleName);
stubRoleDto.setCode(stubRoleCode); stubRoleRespDto.setCode(stubRoleCode);
stubRoleDto.setPermissions( stubRoleRespDto.setPermissions(
List.of(new PermissionRespDto(1L, "9VWU1nmU89zEVH", "9VWU1nmU89zEVH", false))); List.of(new PermissionRespDto(1L, "9VWU1nmU89zEVH", "9VWU1nmU89zEVH", false)));
when(identityAccessService.pageQueryRole(PageRequestDto.of(1, 5), stubRoleQueryDto)) when(identityAccessService.pageQueryRole(PageRequestDto.of(1, 5), stubRoleQueryDto))
.thenReturn(new PageResponseDto<>(1, List.of(stubRoleDto))); .thenReturn(new PageResponseDto<>(1, List.of(stubRoleRespDto)));
mockMvc mockMvc
.perform( .perform(

View File

@@ -16,7 +16,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
public class AbstractDataAccessLayerTest { public class AbstractDataAccessLayerTest {
public static PostgreSQLContainer<?> postgres = public static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:17.3-alpine").withDatabaseName("mjga"); new PostgreSQLContainer<>("pgvector/pgvector:pg17").withDatabaseName("mjga");
@DynamicPropertySource @DynamicPropertySource
static void postgresProperties(DynamicPropertyRegistry registry) { static void postgresProperties(DynamicPropertyRegistry registry) {

View File

@@ -32,11 +32,11 @@ public class SortByDALTest extends AbstractDataAccessLayerTest {
"INSERT INTO mjga.user (id, username,password) VALUES (3, 'testC','qFVVFvPqs291k10')", "INSERT INTO mjga.user (id, username,password) VALUES (3, 'testC','qFVVFvPqs291k10')",
}) })
void userPageFetchWithNoSort() { void userPageFetchWithNoSort() {
UserQueryDto rbacQueryDto = new UserQueryDto("test"); UserQueryDto rbacQueryDto = new UserQueryDto("test", null, null);
Result<Record> records = userRepository.pageFetchBy(PageRequestDto.of(1, 10), rbacQueryDto); Result<Record> records = userRepository.pageFetchBy(PageRequestDto.of(1, 10), rbacQueryDto);
assertThat(records.get(0).get(USER.ID)).isEqualTo(1); assertThat(records.get(2).get(USER.ID)).isEqualTo(1);
assertThat(records.get(1).get(USER.ID)).isEqualTo(2); assertThat(records.get(1).get(USER.ID)).isEqualTo(2);
assertThat(records.get(2).get(USER.ID)).isEqualTo(3); assertThat(records.get(0).get(USER.ID)).isEqualTo(3);
} }
@Test @Test
@@ -48,7 +48,7 @@ public class SortByDALTest extends AbstractDataAccessLayerTest {
"INSERT INTO mjga.user (id, username,password) VALUES (4, 'testD','3')", "INSERT INTO mjga.user (id, username,password) VALUES (4, 'testD','3')",
}) })
void userPageFetchWithSort() { void userPageFetchWithSort() {
UserQueryDto rbacQueryDto = new UserQueryDto("test"); UserQueryDto rbacQueryDto = new UserQueryDto("test", null, null);
HashMap<String, PageRequestDto.Direction> sortByIdDesc = new HashMap<>(); HashMap<String, PageRequestDto.Direction> sortByIdDesc = new HashMap<>();
sortByIdDesc.put("id", PageRequestDto.Direction.DESC); sortByIdDesc.put("id", PageRequestDto.Direction.DESC);
Result<Record> records = Result<Record> records =

View File

@@ -96,12 +96,12 @@ public class UserRolePermissionDALTest extends AbstractDataAccessLayerTest {
"INSERT INTO mjga.user (id, username,password) VALUES (2, 'testB','NTjRCeUq2EqCy')", "INSERT INTO mjga.user (id, username,password) VALUES (2, 'testB','NTjRCeUq2EqCy')",
}) })
void user_pageFetchBy() { void user_pageFetchBy() {
UserQueryDto rbacQueryDto = new UserQueryDto("test"); UserQueryDto rbacQueryDto = new UserQueryDto("test", null, null);
Result<Record> records = userRepository.pageFetchBy(PageRequestDto.of(1, 10), rbacQueryDto); Result<Record> records = userRepository.pageFetchBy(PageRequestDto.of(1, 10), rbacQueryDto);
assertThat(records.size()).isEqualTo(2); assertThat(records.size()).isEqualTo(2);
assertThat(records.get(0).get(USER.ID)).isEqualTo(1); assertThat(records.get(1).get(USER.ID)).isEqualTo(1);
assertThat(records.get(1).get(USER.ID)).isEqualTo(2); assertThat(records.get(0).get(USER.ID)).isEqualTo(2);
} }
@Test @Test
@@ -142,8 +142,8 @@ public class UserRolePermissionDALTest extends AbstractDataAccessLayerTest {
roleQueryDto.setBindState(BindState.ALL); roleQueryDto.setBindState(BindState.ALL);
Result<Record> records = roleRepository.pageFetchBy(PageRequestDto.of(1, 10), roleQueryDto); Result<Record> records = roleRepository.pageFetchBy(PageRequestDto.of(1, 10), roleQueryDto);
assertThat(records.get(0).getValue("total_role")).isEqualTo(2); assertThat(records.get(0).getValue("total_role")).isEqualTo(2);
assertThat(records.get(0).getValue(ROLE.NAME)).isEqualTo("testRoleA"); assertThat(records.get(1).getValue(ROLE.NAME)).isEqualTo("testRoleA");
assertThat(records.get(1).getValue(ROLE.NAME)).isEqualTo("testRoleB"); assertThat(records.get(0).getValue(ROLE.NAME)).isEqualTo("testRoleB");
roleQueryDto = new RoleQueryDto(); roleQueryDto = new RoleQueryDto();
roleQueryDto.setRoleCode("testRoleA"); roleQueryDto.setRoleCode("testRoleA");

View File

@@ -6,6 +6,7 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.zl.mjga.config.minio.MinIoConfig;
import com.zl.mjga.config.security.HttpFireWallConfig; import com.zl.mjga.config.security.HttpFireWallConfig;
import com.zl.mjga.config.security.Jwt; import com.zl.mjga.config.security.Jwt;
import com.zl.mjga.config.security.UserDetailsServiceImpl; import com.zl.mjga.config.security.UserDetailsServiceImpl;
@@ -19,6 +20,7 @@ import com.zl.mjga.repository.RoleRepository;
import com.zl.mjga.repository.UserRepository; import com.zl.mjga.repository.UserRepository;
import com.zl.mjga.service.IdentityAccessService; import com.zl.mjga.service.IdentityAccessService;
import com.zl.mjga.service.SignService; import com.zl.mjga.service.SignService;
import io.minio.MinioClient;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -49,6 +51,8 @@ public class AuthenticationAndAuthorityTest {
@MockBean private UserRepository userRepository; @MockBean private UserRepository userRepository;
@MockBean private RoleRepository roleRepository; @MockBean private RoleRepository roleRepository;
@MockBean private PermissionRepository permissionRepository; @MockBean private PermissionRepository permissionRepository;
@MockBean private MinioClient minioClient;
@MockBean private MinIoConfig minIoConfig;
@Test @Test
public void givenRequestOnPublicService_shouldSucceedWith200() throws Exception { public void givenRequestOnPublicService_shouldSucceedWith200() throws Exception {

View File

@@ -112,19 +112,19 @@ class UserRolePermissionUnitTest {
DSL.field("total_user", Integer.class)) DSL.field("total_user", Integer.class))
.values(stubUserId2, stubUserName2, stubUserPassword2, true, null, 2)); .values(stubUserId2, stubUserName2, stubUserPassword2, true, null, 2));
UserRolePermissionDto mockUserRolePermissionDto1 = new UserRolePermissionDto(); UserRolePermissionDto mockUserRolePermissionDto1 = new UserRolePermissionDto();
RoleDto mockRoleDto = new RoleDto(); RoleRespDto mockRoleRespDto = new RoleRespDto();
mockRoleDto.setId(stubRoleId); mockRoleRespDto.setId(stubRoleId);
mockRoleDto.setCode(stubRoleCode); mockRoleRespDto.setCode(stubRoleCode);
mockRoleDto.setName(stubRoleName); mockRoleRespDto.setName(stubRoleName);
PermissionRespDto permissionRespDto = new PermissionRespDto(); PermissionRespDto permissionRespDto = new PermissionRespDto();
permissionRespDto.setId(stubPermissionId); permissionRespDto.setId(stubPermissionId);
permissionRespDto.setCode(stubPermissionCode); permissionRespDto.setCode(stubPermissionCode);
permissionRespDto.setName(stubPermissionName); permissionRespDto.setName(stubPermissionName);
mockRoleDto.getPermissions().add(permissionRespDto); mockRoleRespDto.getPermissions().add(permissionRespDto);
mockUserRolePermissionDto1.setId(stubUserId1); mockUserRolePermissionDto1.setId(stubUserId1);
mockUserRolePermissionDto1.setUsername(stubUserName1); mockUserRolePermissionDto1.setUsername(stubUserName1);
mockUserRolePermissionDto1.setPassword(stubUserPassword1); mockUserRolePermissionDto1.setPassword(stubUserPassword1);
mockUserRolePermissionDto1.getRoles().add(mockRoleDto); mockUserRolePermissionDto1.getRoles().add(mockRoleRespDto);
UserRolePermissionDto mockUserRolePermissionDto2 = new UserRolePermissionDto(); UserRolePermissionDto mockUserRolePermissionDto2 = new UserRolePermissionDto();
mockUserRolePermissionDto2.setId(stubUserId2); mockUserRolePermissionDto2.setId(stubUserId2);
@@ -143,7 +143,7 @@ class UserRolePermissionUnitTest {
// action // action
PageResponseDto<List<UserRolePermissionDto>> result = PageResponseDto<List<UserRolePermissionDto>> result =
identityAccessService.pageQueryUser( identityAccessService.pageQueryUser(
PageRequestDto.of(1, 10), new UserQueryDto(stubUserName2)); PageRequestDto.of(1, 10), new UserQueryDto(stubUserName2, null, null));
// assert // assert
List<UserRolePermissionDto> userRolePermissionDtoList = result.getData(); List<UserRolePermissionDto> userRolePermissionDtoList = result.getData();
@@ -173,7 +173,7 @@ class UserRolePermissionUnitTest {
.thenReturn(mockResult); .thenReturn(mockResult);
PageResponseDto<List<UserRolePermissionDto>> result = PageResponseDto<List<UserRolePermissionDto>> result =
identityAccessService.pageQueryUser( identityAccessService.pageQueryUser(
PageRequestDto.of(1, 10), new UserQueryDto("agydCO1Yi99a")); PageRequestDto.of(1, 10), new UserQueryDto("agydCO1Yi99a", null, null));
assertThat(result.getTotal()).isEqualTo(0); assertThat(result.getTotal()).isEqualTo(0);
assertThat(result.getData()).isNull(); assertThat(result.getData()).isNull();
} }
@@ -202,7 +202,7 @@ class UserRolePermissionUnitTest {
mockResult.setId(stubRoleId); mockResult.setId(stubRoleId);
mockResult.setRoles( mockResult.setRoles(
List.of( List.of(
new RoleDto( new RoleRespDto(
stubRoleId, stubRoleId,
stubRoleName, stubRoleName,
stubRoleCode, stubRoleCode,
@@ -245,12 +245,12 @@ class UserRolePermissionUnitTest {
when(roleRepository.pageFetchBy(any(PageRequestDto.class), any(RoleQueryDto.class))) when(roleRepository.pageFetchBy(any(PageRequestDto.class), any(RoleQueryDto.class)))
.thenReturn(mockRoleResult); .thenReturn(mockRoleResult);
RoleQueryDto roleQueryDto = new RoleQueryDto(); RoleQueryDto roleQueryDto = new RoleQueryDto();
PageResponseDto<List<RoleDto>> pageResult = PageResponseDto<List<RoleRespDto>> pageResult =
identityAccessService.pageQueryRole(PageRequestDto.of(1, 5), roleQueryDto); identityAccessService.pageQueryRole(PageRequestDto.of(1, 5), roleQueryDto);
assertThat(pageResult.getTotal()).isEqualTo(0L); assertThat(pageResult.getTotal()).isEqualTo(0L);
roleQueryDto.setUserId(1L); roleQueryDto.setUserId(1L);
PageResponseDto<List<RoleDto>> pageResult2 = PageResponseDto<List<RoleRespDto>> pageResult2 =
identityAccessService.pageQueryRole(PageRequestDto.of(1, 5), roleQueryDto); identityAccessService.pageQueryRole(PageRequestDto.of(1, 5), roleQueryDto);
assertThat(pageResult2.getTotal()).isEqualTo(0L); assertThat(pageResult2.getTotal()).isEqualTo(0L);
} }

View File

@@ -3,7 +3,8 @@ CREATE SCHEMA IF NOT EXISTS mjga;
CREATE TABLE mjga.user ( CREATE TABLE mjga.user (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
username VARCHAR NOT NULL UNIQUE, username VARCHAR NOT NULL UNIQUE,
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, avatar VARCHAR,
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
password VARCHAR NOT NULL, password VARCHAR NOT NULL,
enable BOOLEAN NOT NULL DEFAULT TRUE enable BOOLEAN NOT NULL DEFAULT TRUE
); );

View File

@@ -3,9 +3,12 @@ VITE_SOURCE_MAP=true
# mock # mock
#VITE_ENABLE_MOCK=true #VITE_ENABLE_MOCK=true
#VITE_BASE_URL=http://localhost:5173 #VITE_BASE_URL=http://localhost:5173
#VITE_STATIC_URL=http://localhost:9000/zhilu
# local # local
VITE_ENABLE_MOCK=false VITE_ENABLE_MOCK=false
VITE_BASE_URL=http://localhost:8080 VITE_BASE_URL=http://localhost:8080
VITE_STATIC_URL=http://localhost:9000/zhilu
# dev # dev
#VITE_ENABLE_MOCK=false #VITE_ENABLE_MOCK=false
#VITE_BASE_URL=https://localhost/api #VITE_BASE_URL=https://localhost/api
#VITE_STATIC_URL=https://localhost/static

View File

@@ -30,5 +30,18 @@
"formatter": { "formatter": {
"quoteStyle": "double" "quoteStyle": "double"
} }
} },
"overrides": [
{
"include": ["*.vue"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
}
}
}
}
]
} }

1
frontend/env.d.ts vendored
View File

@@ -9,6 +9,7 @@ interface ImportMetaEnv {
readonly VITE_ENABLE_MOCK: "true" | "false"; readonly VITE_ENABLE_MOCK: "true" | "false";
readonly VITE_BACKEND_PORT: string; readonly VITE_BACKEND_PORT: string;
readonly VITE_BASE_URL: string; readonly VITE_BASE_URL: string;
readonly VITE_STATIC_URL: string;
readonly VITE_SOURCE_MAP: "true" | "false"; readonly VITE_SOURCE_MAP: "true" | "false";
} }

View File

@@ -10,8 +10,10 @@
"dependencies": { "dependencies": {
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"@tailwindcss/vite": "^4.0.14", "@tailwindcss/vite": "^4.0.14",
"@vuepic/vue-datepicker": "^11.0.2",
"@vueuse/core": "^13.0.0", "@vueuse/core": "^13.0.0",
"apexcharts": "^3.46.0", "apexcharts": "^3.46.0",
"compressorjs": "^1.2.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dompurify": "^3.2.6", "dompurify": "^3.2.6",
"flowbite": "^3.1.2", "flowbite": "^3.1.2",
@@ -2691,6 +2693,21 @@
} }
} }
}, },
"node_modules/@vuepic/vue-datepicker": {
"version": "11.0.2",
"resolved": "http://mirrors.tencent.com/npm/@vuepic/vue-datepicker/-/vue-datepicker-11.0.2.tgz",
"integrity": "sha512-uHh78mVBXCEjam1uVfTzZ/HkyDwut/H6b2djSN9YTF+l/EA+XONfdCnOVSi1g+qVGSy65DcQAwyBNidAssnudQ==",
"license": "MIT",
"dependencies": {
"date-fns": "^4.1.0"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"vue": ">=3.3.0"
}
},
"node_modules/@vueuse/core": { "node_modules/@vueuse/core": {
"version": "13.1.0", "version": "13.1.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz",
@@ -2885,6 +2902,12 @@
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/blueimp-canvas-to-blob": {
"version": "3.29.0",
"resolved": "http://mirrors.tencent.com/npm/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
"integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==",
"license": "MIT"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -3150,6 +3173,16 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/compressorjs": {
"version": "1.2.1",
"resolved": "http://mirrors.tencent.com/npm/compressorjs/-/compressorjs-1.2.1.tgz",
"integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==",
"license": "MIT",
"dependencies": {
"blueimp-canvas-to-blob": "^3.29.0",
"is-blob": "^2.1.0"
}
},
"node_modules/config-chain": { "node_modules/config-chain": {
"version": "1.1.13", "version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -3237,6 +3270,16 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "http://mirrors.tencent.com/npm/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.11.13", "version": "1.11.13",
"resolved": "http://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz", "resolved": "http://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz",
@@ -3893,6 +3936,18 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/is-blob": {
"version": "2.1.0",
"resolved": "http://mirrors.tencent.com/npm/is-blob/-/is-blob-2.1.0.tgz",
"integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-core-module": { "node_modules/is-core-module": {
"version": "2.16.1", "version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",

View File

@@ -20,8 +20,10 @@
"dependencies": { "dependencies": {
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"@tailwindcss/vite": "^4.0.14", "@tailwindcss/vite": "^4.0.14",
"@vuepic/vue-datepicker": "^11.0.2",
"@vueuse/core": "^13.0.0", "@vueuse/core": "^13.0.0",
"apexcharts": "^3.46.0", "apexcharts": "^3.46.0",
"compressorjs": "^1.2.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dompurify": "^3.2.6", "dompurify": "^3.2.6",
"flowbite": "^3.1.2", "flowbite": "^3.1.2",

BIN
frontend/public/ai-tdd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

50
frontend/public/logo.svg Normal file
View File

@@ -0,0 +1,50 @@
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5194 13.4429C24.4453 13.9533 24.1087 14.6468 23.7226 15.2497C23.2354 16.0105 22.4622 16.5433 21.5774 16.7283L17.6797 17.5434C17.0533 17.6744 16.4852 18.0022 16.0587 18.479L13.503 21.3357C13.0227 21.8725 12.687 21.7445 12.687 21.0244C12.6812 21.0511 11.4186 24.3072 14.7646 26.2371C16.0502 26.9787 17.9009 26.7122 19.1865 25.9707L25.9983 22.0416C28.5458 20.5722 30.3445 18.0863 30.9424 15.2089C30.966 15.095 30.9843 14.9808 31.004 14.8667L24.5194 13.4429Z" fill="url(#paint0_linear_2484_3243)" />
<path d="M22.7528 9.51774C24.0384 10.2593 24.5637 11.3633 24.5637 12.8464C24.5637 13.0477 24.5479 13.2466 24.5194 13.4425L27.2641 14.6215L31.004 14.8663C31.4829 12.0948 30.5444 9.24202 28.862 6.97445C27.5959 5.268 25.9667 3.78714 24.0081 2.65738C22.417 1.73966 20.7636 1.13501 19.1025 0.803223L17.2361 3.22023L16.6465 5.99559L22.7528 9.51774Z" fill="url(#paint1_linear_2484_3243)" />
<path d="M0.783597 11.5518C0.782899 11.554 0.784832 11.5546 0.78556 11.5524C0.929583 11.1205 1.11018 10.6385 1.33564 10.1237C2.51334 7.4343 4.78286 5.64062 7.57492 4.72608C10.367 3.81156 13.4155 4.13212 15.9601 5.59988L16.6465 5.99578L19.1025 0.803412C11.291 -0.756765 3.30728 3.83253 0.793528 11.5217C0.792327 11.5254 0.787957 11.5382 0.783597 11.5518Z" fill="url(#paint2_linear_2484_3243)" />
<path d="M18.9199 25.9704C17.6343 26.712 16.0503 26.712 14.7647 25.9704C14.5902 25.8697 14.4257 25.7566 14.2701 25.634L12.0091 27.1885L10.0603 30.3376C12.2233 32.1377 15.0321 32.7164 17.839 32.3945C19.9513 32.1523 22.0495 31.4832 24.0082 30.3534C25.5992 29.4357 26.9501 28.3075 28.0682 27.0361L26.9063 24.2128L25.0262 22.4482L18.9199 25.9704Z" fill="url(#paint3_linear_2484_3243)" />
<path d="M14.2701 25.6341C13.2796 24.8539 12.6872 23.6572 12.6872 22.3754V22.2476V11.5724C12.6872 10.9687 12.865 10.8661 13.3884 11.168C12.5823 10.703 10.7203 9.10701 8.42118 10.4331C7.13557 11.1747 6.0769 12.8116 6.0769 14.2946V22.1529C6.0769 25.0917 7.59906 28.1573 9.79448 30.1133C9.88132 30.1906 9.97122 30.2636 10.0603 30.3377L14.2701 25.6341Z" fill="url(#paint4_linear_2484_3243)" />
<path d="M27.9105 5.8123C27.909 5.8106 27.9075 5.81197 27.909 5.81368C28.2114 6.15428 28.5389 6.5515 28.8725 7.00399C30.6149 9.36765 31.2659 12.3613 30.6627 15.2343C30.0594 18.1072 28.2573 20.5846 25.7126 22.0523L25.0262 22.4482L28.0683 27.0361C33.3265 21.0576 33.3401 11.8554 27.9316 5.83594C27.9291 5.83306 27.9201 5.82287 27.9105 5.8123Z" fill="url(#paint5_linear_2484_3243)" />
<path d="M6.34355 14.2944C6.34354 12.8113 7.13552 11.4408 8.42113 10.6993C8.59565 10.5986 8.77601 10.5129 8.96002 10.4395L8.74304 7.70603L7.21862 4.57861C4.57671 5.55005 2.4397 7.55766 1.31528 10.1471C0.469097 12.0957 9.792e-06 14.2458 0 16.5052C0 18.3407 0.302549 20.0735 0.845533 21.6767L3.87391 22.083L6.34355 21.3387V14.2944V14.2944Z" fill="url(#paint6_linear_2484_3243)" />
<path d="M8.96003 10.4395C10.1316 9.97264 11.4652 10.0584 12.5763 10.6993L12.6871 10.7632L21.5825 15.8941C22.2065 16.254 22.1498 16.6082 21.4445 16.7557L21.9577 16.6484C22.6329 16.5072 23.2498 16.1621 23.7216 15.6592C24.5327 14.7946 24.8305 13.7515 24.8305 12.8463C24.8304 11.3632 24.0385 9.99274 22.7529 9.2512L15.941 5.32209C13.3935 3.85267 10.3394 3.53934 7.5461 4.46083C7.4356 4.49727 7.32744 4.5386 7.21863 4.57861L8.96003 10.4395Z" fill="url(#paint7_linear_2484_3243)" />
<path d="M19.3222 32.1523C19.3245 32.1518 19.3241 32.1498 19.3218 32.1503C18.8753 32.2417 18.3673 32.3264 17.8083 32.3888C14.8881 32.7145 11.9676 31.781 9.77876 29.8225C7.58999 27.8641 6.3436 25.0662 6.3436 22.1307L6.34359 21.3389L0.845581 21.6769C3.39893 29.2156 11.369 33.8285 19.2912 32.1588C19.295 32.158 19.3083 32.1553 19.3222 32.1523Z" fill="url(#paint8_linear_2484_3243)" />
<defs>
<linearGradient id="paint0_linear_2484_3243" x1="20.0599" y1="24.2701" x2="23.2075" y2="13.307" gradientUnits="userSpaceOnUse">
<stop stop-color="#1724C9" />
<stop offset="1" stop-color="#1C64F2" />
</linearGradient>
<linearGradient id="paint1_linear_2484_3243" x1="27.3093" y1="10.9001" x2="19.0297" y2="2.64962" gradientUnits="userSpaceOnUse">
<stop stop-color="#1C64F2" />
<stop offset="1" stop-color="#0092FF" />
</linearGradient>
<linearGradient id="paint2_linear_2484_3243" x1="16.1645" y1="5.52115" x2="3.67432" y2="6.3104" gradientUnits="userSpaceOnUse">
<stop stop-color="#0092FF" />
<stop offset="1" stop-color="#45B2FF" />
</linearGradient>
<linearGradient id="paint3_linear_2484_3243" x1="15.3198" y1="29.1626" x2="26.5366" y2="26.1359" gradientUnits="userSpaceOnUse">
<stop stop-color="#1C64F2" />
<stop offset="1" stop-color="#0092FF" />
</linearGradient>
<linearGradient id="paint4_linear_2484_3243" x1="7.26881" y1="16.1827" x2="15.2325" y2="24.4347" gradientUnits="userSpaceOnUse">
<stop stop-color="#1724C9" />
<stop offset="1" stop-color="#1C64F2" />
</linearGradient>
<linearGradient id="paint5_linear_2484_3243" x1="25.4505" y1="22.1356" x2="31.007" y2="10.9345" gradientUnits="userSpaceOnUse">
<stop stop-color="#0092FF" />
<stop offset="1" stop-color="#45B2FF" />
</linearGradient>
<linearGradient id="paint6_linear_2484_3243" x1="5.36387" y1="9.63067" x2="2.39054" y2="20.8063" gradientUnits="userSpaceOnUse">
<stop stop-color="#1C64F2" />
<stop offset="1" stop-color="#0092FF" />
</linearGradient>
<linearGradient id="paint7_linear_2484_3243" x1="20.5431" y1="9.09912" x2="9.67768" y2="11.8044" gradientUnits="userSpaceOnUse">
<stop stop-color="#1724C9" />
<stop offset="1" stop-color="#1C64F2" />
</linearGradient>
<linearGradient id="paint8_linear_2484_3243" x1="6.40679" y1="21.8566" x2="13.3326" y2="32.2745" gradientUnits="userSpaceOnUse">
<stop stop-color="#0092FF" />
<stop offset="1" stop-color="#45B2FF" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { RouterLink, RouterView } from "vue-router"; import { RouterView } from "vue-router";
import Alert from "./components/Alert.vue"; import Alert from "./components/ui/Alert.vue";
</script> </script>
<template> <template>
<RouterView /> <RouterView />
<Alert/> <Alert />
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,12 +1,12 @@
import createClient, { type Middleware } from "openapi-fetch"; import createClient, { type Middleware } from "openapi-fetch";
import useAuthStore from "../composables/store/useAuthStore"; import useAuthStore from "../composables/store/useAuthStore";
import type { paths } from "./types/schema"; // generated by openapi-typescript
import { import {
ForbiddenError, ForbiddenError,
RequestError, RequestError,
UnAuthError, UnAuthError,
InternalServerError, InternalServerError,
} from "../types/error"; } from "@/types/ErrorTypes";
import type { paths } from "./types/schema"; // generated by openapi-typescript
const myMiddleware: Middleware = { const myMiddleware: Middleware = {
onRequest({ request, options }) { onRequest({ request, options }) {

View File

@@ -15,10 +15,13 @@ export default [
}), }),
http.post("/ai/action/search", () => { http.post("/ai/action/search", () => {
const response = HttpResponse.json({ const response = HttpResponse.json({
action: "CREATE_USER", action: "DELETE_USER",
}); });
return response; return response;
}), }),
http.delete("/ai/action/user", () => {
return HttpResponse.json({ success: true });
}),
http.get("/ai/llm/page-query", () => { http.get("/ai/llm/page-query", () => {
const generateLlm = () => ({ const generateLlm = () => ({
id: faker.number.int({ min: 1, max: 100 }), id: faker.number.int({ min: 1, max: 100 }),
@@ -26,6 +29,7 @@ export default [
modelName: faker.lorem.word(), modelName: faker.lorem.word(),
apiKey: faker.string.uuid(), apiKey: faker.string.uuid(),
url: faker.internet.url(), url: faker.internet.url(),
type: faker.helpers.arrayElement(["CHAT", "EMBEDDING"]),
enable: faker.datatype.boolean(), enable: faker.datatype.boolean(),
priority: faker.number.int({ min: 1, max: 10 }), priority: faker.number.int({ min: 1, max: 10 }),
}); });

View File

@@ -3,13 +3,17 @@ import { http, HttpResponse } from "msw";
export default [ export default [
http.get("/department/page-query", () => { http.get("/department/page-query", () => {
const generateDepartment = () => ({ const generateDepartment = () => {
id: faker.number.int({ min: 1, max: 100 }), // 20% 的概率生成 parentId 为 null 的数据
name: faker.company.name(), const hasParent = faker.datatype.boolean(0.8);
parentId: faker.number.int({ min: 1, max: 100 }), return {
isBound: faker.datatype.boolean(), id: faker.number.int({ min: 1, max: 100 }),
parentName: faker.company.name(), name: faker.company.name(),
}); parentId: hasParent ? faker.number.int({ min: 1, max: 100 }) : null,
isBound: faker.datatype.boolean(),
parentName: hasParent ? faker.company.name() : null,
};
};
const mockData = { const mockData = {
data: faker.helpers.multiple(generateDepartment, { count: 10 }), data: faker.helpers.multiple(generateDepartment, { count: 10 }),
total: 30, total: 30,
@@ -17,21 +21,41 @@ export default [
return HttpResponse.json(mockData); return HttpResponse.json(mockData);
}), }),
http.get("/department/query-available", () => { http.get("/department/query-available", () => {
const generateDepartment = () => ({ const generateDepartment = () => {
id: faker.number.int({ min: 1, max: 30 }), // 20% 的概率生成 parentId 为 null 的数据
name: faker.company.name(), const hasParent = faker.datatype.boolean(0.8);
parentId: faker.number.int({ min: 1, max: 30 }), return {
parentName: faker.company.name(), id: faker.number.int({ min: 1, max: 30 }),
}); name: faker.company.name(),
parentId: hasParent ? faker.number.int({ min: 1, max: 30 }) : null,
parentName: hasParent ? faker.company.name() : null,
};
};
const mockData = faker.helpers.multiple(generateDepartment, { count: 30 }); const mockData = faker.helpers.multiple(generateDepartment, { count: 30 });
return HttpResponse.json(mockData); return HttpResponse.json(mockData);
}), }),
http.post("/department", () => { http.post("/department", () => {
console.log("Captured department upsert"); console.log("Captured department upsert");
return HttpResponse.json(); return HttpResponse.json();
}), }),
http.get("/department/query-sub", ({ request }) => {
const generateDepartment = () => {
// 20% 的概率生成 parentId 为 null 的数据
const hasParent = faker.datatype.boolean(0.8);
return {
id: faker.number.int({ min: 1, max: 30 }),
name: faker.company.name(),
parentId: hasParent ? faker.number.int({ min: 1, max: 30 }) : null,
parentName: hasParent ? faker.company.name() : null,
};
};
const mockData = faker.helpers.multiple(generateDepartment, {
count: 30,
});
return HttpResponse.json(mockData);
}),
http.delete("/department", () => { http.delete("/department", () => {
console.log("Captured department delete"); console.log("Captured department delete");
return HttpResponse.json(); return HttpResponse.json();

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