This commit is contained in:
zlwu committed 2025-06-26 18:30:03 +08:00
commit 1ab9f9c458
1319 files changed
+137505

No files matched your search

+643
View File
@@ -0,0 +1,643 @@
# Cursor Rules - 项目开发规范
## 项目结构说明
- **前端工程**:`web/` (基于Vue 3 + TypeScript + Element Plus)
- **后端工程**:`backend/` (基于RuoYi-Vue-Plus + Spring Boot 3.4.6)
- **移动端工程**:`mobile/` (基于uniapp + uView UI构建的校园管理移动端应用)
- **文档管理**:`docs/` (项目的需求、设计、开发、测试、部署文档)
---
# 第一部分:项目文档管理规范
## 1. 文档结构管理 (docs/)
### 文档目录组织
```
docs/
├── requirements/ # 需求文档
│ ├── README.md # 需求管理说明
│ └── [功能模块]/ # 具体功能需求文档
├── design/ # 设计文档
│ ├── README.md # 设计文档说明
│ ├── database/ # 数据库设计
│ │ ├── hzdyjs_xg.sql # 完整数据库模型
│ │ ├── database_msg.md # 环境配置信息
│ │ └── README.md # 数据库设计说明
│ └── [模块设计]/ # 其他设计文档
├── development/ # 开发文档
│ └── README.md # 开发指南
├── testing/ # 测试文档
│ └── README.md # 测试计划和用例
├── deployment/ # 部署文档
│ └── README.md # 部署指南
├── middleware/ # 中间件配置文档
│ ├── mysql-config.md # MySQL配置文档
│ └── README.md # 中间件配置说明
└── code-adjust-record.md # 代码调整记录
```
## 2. 代码调整记录管理
### 📝 重要提醒:代码调整记录要求
**每次使用Cursor修改任何代码时,都必须在 `docs/code-adjust-record.md` 文件中记录调整信息。**
### 记录格式标准
每次调整后请按以下格式记录:
```markdown
## 调整记录 - YYYY-MM-DD HH:mm:ss
**调整时间**: YYYY-MM-DD HH:mm:ss
**调整人**: [开发者姓名/Cursor AI辅助]
**调整目的**: [简述调整原因和目标]
**涉及文件**:
- [具体的文件路径]
**调整内容详述**:
1. 具体调整了哪些代码
2. 修改了哪些功能
3. 优化了哪些性能
4. 修复了哪些问题
**技术细节**:
- 使用的技术栈: [技术栈信息]
- 遵循的设计规范: [设计规范]
- 其他重要技术决策
**测试验证**:
- [ ] 功能测试通过
- [ ] 代码规范检查通过
- [ ] 类型检查通过
- [ ] 兼容性测试通过
**备注**: 其他需要说明的内容
---
```
### 记录要求
1. **及时性**: 每次代码调整完成后立即记录
2. **完整性**: 包含所有必要信息字段
3. **准确性**: 详细描述实际的调整内容
4. **可追溯性**: 便于后续查看和维护
### ⚠️ 强制要求
**所有通过Cursor AI进行的代码调整都必须在 `docs/code-adjust-record.md` 中留下记录,这是项目管理的强制性要求。**
## 3. 文档同步更新规则
### 📋 核心原则:代码与文档同步更新
**⚠️ 重要规则:每次代码调整完成后,必须同步更新相应的项目文档,确保项目文档永远保持最新状态。**
### 强制更新规则
每次代码调整必须同步更新相关文档:
- **API文档**: 接口变更时更新相关文档
- **数据库文档**: 表结构变更时更新 `docs/design/database/` 下的所有相关文档
- **配置文档**: 配置项变更时更新 `docs/middleware/` 下的配置说明
- **部署文档**: 部署流程变更时更新 `docs/deployment/` 下的部署手册
- **功能文档**: 新增功能时更新 `docs/requirements/` 下的需求文档
- **设计文档**: 架构变更时更新 `docs/design/` 下的设计文档
### 📝 文档同步更新清单
每次代码调整后,请检查以下文档是否需要更新:
#### 前端代码调整时需更新的文档:
- [ ] **组件文档**: 新增或修改组件时,更新组件使用说明
- [ ] **页面功能文档**: 页面功能变更时,更新用户操作手册
- [ ] **API对接文档**: 前端API调用变更时,更新接口对接说明
- [ ] **UI设计文档**: 界面样式重大变更时,更新设计规范文档
#### 后端代码调整时需更新的文档:
- [ ] **接口文档**: API接口变更时,更新接口文档和示例
- [ ] **数据库文档**: 表结构变更时,更新数据库设计文档
- [ ] **业务逻辑文档**: 业务规则变更时,更新业务流程文档
- [ ] **配置文档**: 系统配置变更时,更新配置说明文档
#### 移动端代码调整时需更新的文档:
- [ ] **功能文档**: 移动端功能变更时,更新功能说明文档
- [ ] **API对接文档**: 移动端API调用变更时,更新接口对接说明
- [ ] **用户手册**: 用户操作流程变更时,更新操作指南
### 🔄 文档更新工作流程
1. **代码调整完成** → 2. **记录代码调整日志** → 3. **识别影响的文档** → 4. **更新相应文档** → 5. **验证文档完整性**
### 版本管理
- **文档版本**: 与代码版本保持同步
- **变更记录**: 每次变更都要记录变更内容和原因
- **向后兼容**: 接口变更需要考虑向后兼容性
---
# 第二部分:Web前端开发规范 (web/)
## 1. 技术栈规范
### 核心技术
- **框架**: Vue 3.5.13 + TypeScript 5.8.3
- **UI组件库**: Element Plus 2.9.8
- **构建工具**: Vite 7.0.9
- **状态管理**: Pinia
- **路由管理**: Vue Router 4
- **HTTP客户端**: Axios
- **代码风格**: ESLint + Prettier
## 2. Art Design Pro UI 规范
### 设计理念
基于 Art Design Pro 的设计语言,追求现代化、精致、用户友好的界面体验。注重细节、动效和用户交互的流畅性。
### 颜色系统
#### 主色调 (Primary Colors)
```css
--art-primary: 93, 135, 255; /* 主品牌色 - 蓝色 */
--art-secondary: 73, 190, 255; /* 辅助色 - 天蓝色 */
--art-success: 19, 222, 185; /* 成功色 - 绿色 */
--art-warning: 255, 174, 31; /* 警告色 - 橙色 */
--art-error: 250, 137, 107; /* 错误色 - 红色 */
--art-info: 83, 155, 255; /* 信息色 - 蓝色 */
--art-danger: 255, 77, 79; /* 危险色 - 红色 */
```
#### 背景色系统
```css
/* 浅色主题 */
--art-bg-color: #fafbfc; /* 页面底色 */
--art-main-bg-color: #ffffff; /* 主要内容背景 */
--art-bg-primary: 236, 242, 255; /* 主色背景 */
--art-bg-secondary: 232, 247, 255; /* 辅助背景 */
--art-bg-success: 230, 255, 250; /* 成功背景 */
--art-bg-warning: 254, 245, 229; /* 警告背景 */
--art-bg-error: 253, 237, 232; /* 错误背景 */
/* 暗色主题 */
html.dark {
--art-bg-color: #070707; /* 页面底色 */
--art-main-bg-color: #161618; /* 主要内容背景 */
}
```
### 轮廓式按钮设计系统
#### 🎨 设计理念
全面采用轮廓式(Outline)按钮设计,提供更加优雅、现代和一致的用户界面体验。
#### 核心规范
- **Primary按钮**: 轮廓式主色设计,主色边框和文字,5%透明度主色背景
- **Success按钮**: 轮廓式成功色设计,成功色边框和文字,5%透明度成功色背景
- **Warning按钮**: 轮廓式警告色设计,警告色边框和文字,5%透明度警告色背景
- **Danger按钮**: 轮廓式危险色设计,危险色边框和文字,5%透明度危险色背景
- **Info按钮**: 轮廓式信息色设计,信息色边框和文字,5%透明度信息色背景
- **Default按钮**: 白色背景,灰色边框,灰色文字
#### 交互效果
- **悬浮状态**: 上移1px + 背景透明度变为10% + 边框透明度变为80% + 彩色阴影
- **点击状态**: 98%缩放 + 0.1s快速过渡 + 背景透明度变为15%
- **禁用状态**: 50%透明度,禁用所有交互和变换
## 3. 组件开发规范
### Vue组件结构标准
```vue
<template>
<div class="component-container">
<!-- 组件内容 -->
</div>
</template>
<script setup lang="ts" name="ComponentName">
// 1. 引入依赖
import { ref, reactive, computed, onMounted } from 'vue';
import type { ComponentProps, ComponentEmits } from './types';
// 2. 定义Props和Emits
const props = defineProps<ComponentProps>();
const emits = defineEmits<ComponentEmits>();
// 3. 响应式数据
const loading = ref(false);
const formData = reactive({});
// 4. 计算属性
const computedValue = computed(() => {
return /* 计算逻辑 */;
});
// 5. 方法定义
const handleClick = () => {
// 方法实现
};
// 6. 生命周期
onMounted(() => {
// 组件挂载后的逻辑
});
</script>
<style lang="scss" scoped>
.component-container {
// 样式实现
}
</style>
```
### 页面开发规范
#### 列表页面标准模板
```vue
<template>
<div class="p-2 page-container">
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
<!-- 搜索表单项 -->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<!-- 表格区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<!-- 数据表格 -->
<el-table v-loading="loading" border :data="dataList" :height="tableHeight">
<el-table-column type="selection" width="55" align="center" />
<!-- 数据列 -->
<el-table-column label="操作" align="center" width="160">
<template #default="scope">
<!-- 操作按钮 -->
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList" />
</el-card>
</div>
</template>
```
## 4. API开发规范
### API接口定义标准
```typescript
// api/[module]/index.ts
import request from '@/utils/request';
import type { AxiosPromise } from 'axios';
import type { DataVO, DataForm, DataQuery } from './types';
/**
* 查询数据列表
* @param query 查询参数
*/
export const listData = (query?: DataQuery): AxiosPromise<DataVO[]> => {
return request({
url: '/api/data/list',
method: 'get',
params: query
});
};
```
## 5. 项目架构规范
### 目录结构标准
```
web/src/
├── api/ # API接口定义
├── assets/ # 静态资源
├── components/ # 通用组件
├── views/ # 页面视图
├── layout/ # 布局组件
├── router/ # 路由配置
├── store/ # 状态管理
├── utils/ # 工具函数
├── plugins/ # 插件配置
├── types/ # 全局类型定义
└── main.ts # 应用入口
```
---
# 第三部分:后端开发规范 (backend/)
## 1. 技术栈规范
### 核心技术
- **框架**: Spring Boot 3.4.6
- **Java版本**: JDK 17/21
- **Web容器**: Undertow
- **ORM框架**: MyBatis-Plus 3.5.12
- **数据库**: MySQL/Oracle/PostgreSQL/SQLServer (多数据库支持)
- **缓存**: Redis 5-7 + Redisson 3.45.1
- **权限认证**: Sa-Token 1.42.0 + JWT
- **任务调度**: SnailJob 1.5.0
- **工作流**: Warm-Flow 1.7.3
## 2. 项目结构规范
### 后端模块结构
```
backend/
├── ruoyi-admin/ # 管理后台启动模块
├── ruoyi-common/ # 公共模块组
│ ├── ruoyi-common-core/ # 核心模块
│ ├── ruoyi-common-mybatis/ # MyBatis配置
│ ├── ruoyi-common-redis/ # Redis配置
│ ├── ruoyi-common-satoken/ # Sa-Token配置
│ └── ... (其他公共模块)
├── ruoyi-modules/ # 业务模块组
│ ├── ruoyi-system/ # 系统管理模块
│ ├── ruoyi-demo/ # 示例模块
│ └── ... (其他业务模块)
├── ruoyi-extend/ # 扩展模块组
└── script/ # 脚本和SQL
```
## 3. 开发规范
### 代码规范
- **严格遵守**: Alibaba Java开发手册
- **命名规范**:
- 类名: PascalCase (如: UserController)
- 方法名: camelCase (如: getUserList)
- 常量: UPPER_SNAKE_CASE (如: MAX_RETRY_COUNT)
- 包名: 全小写 (如: org.dromara.system)
### 数据库规范
- **现有数据库模型**: 参考 `docs/design/database/hzdyjs_xg.sql` 中的完整表结构
- **环境配置**: 开发环境数据库信息见 `docs/design/database/database_msg.md`
- **主键设计**: 使用雪花ID (ASSIGN_ID),不使用数据库自增
- **字段命名**: 下划线命名法 (如: user_name, create_time)
- **基础字段**: 所有业务表必须包含以下字段
```sql
id BIGINT PRIMARY KEY,
tenant_id VARCHAR(20), -- 租户ID (多租户)
create_dept BIGINT, -- 创建部门
create_by BIGINT, -- 创建人
create_time DATETIME, -- 创建时间
update_by BIGINT, -- 更新人
update_time DATETIME, -- 更新时间
deleted TINYINT DEFAULT 0 -- 逻辑删除标记
```
### API接口规范
- **RESTful设计**: 遵循REST API设计原则
- **统一响应格式**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {}
}
```
- **权限控制**: 使用Sa-Token注解进行权限校验
```java
@SaCheckPermission("system:user:list")
```
## 4. 标准代码模板
### Controller模板
```java
@RestController
@RequestMapping("/system/user")
@Api(tags = "用户管理")
public class SysUserController extends BaseController {
@Autowired
private ISysUserService userService;
@SaCheckPermission("system:user:list")
@GetMapping("/list")
@Operation(summary = "查询用户列表")
public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
return userService.queryPageList(user, pageQuery);
}
}
```
### Service模板
```java
@Service
public class SysUserServiceImpl implements ISysUserService {
@Autowired
private SysUserMapper userMapper;
@Override
public TableDataInfo<SysUserVo> queryPageList(SysUserBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysUser> lqw = buildQueryWrapper(bo);
Page<SysUserVo> result = userMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
}
```
---
# 第四部分:移动端开发规范 (mobile/)
## 1. 技术栈规范
### 核心技术
- **框架**: uni-app
- **UI组件库**: uView UI
- **开发语言**: JavaScript/TypeScript
- **状态管理**: Vuex
- **HTTP客户端**: uni.request 封装
## 2. 项目结构规范
### 移动端目录结构
```
mobile/
├── src/
│ ├── api/ # API接口定义
│ ├── components/ # 通用组件
│ ├── pages/ # 页面文件
│ │ ├── index/ # 首页
│ │ ├── login/ # 登录页
│ │ ├── mine/ # 个人中心
│ │ └── workbench/ # 工作台
│ ├── static/ # 静态资源
│ ├── utils/ # 工具函数
│ ├── App.vue # 应用入口
│ ├── config.js # 配置文件
│ └── main.js # 主入口文件
├── pages.json # 页面配置
├── manifest.json # 应用配置
└── uni.scss # 全局样式
```
## 3. 开发规范
### 页面开发规范
- **页面命名**: 使用小写字母和连字符 (如: user-list)
- **组件命名**: 使用PascalCase (如: UserCard)
- **API调用**: 统一使用封装的request工具
- **样式规范**: 使用uView UI组件和样式变量
### 页面模板
```vue
<template>
<view class="page-container">
<u-navbar title="页面标题" @leftClick="handleBack"></u-navbar>
<!-- 页面内容 -->
</view>
</template>
<script>
export default {
data() {
return {
// 页面数据
}
},
onLoad() {
// 页面加载逻辑
},
methods: {
handleBack() {
uni.navigateBack();
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
// 页面样式
}
</style>
```
## 4. 与后端API对接规范
### 请求封装
```javascript
// utils/request.js
const request = (options) => {
return new Promise((resolve, reject) => {
uni.request({
url: getApp().globalData.baseUrl + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'Authorization': uni.getStorageSync('token'),
'Content-Type': 'application/json'
},
success: (res) => {
if (res.data.code === 200) {
resolve(res.data);
} else {
reject(res.data);
}
},
fail: (err) => {
reject(err);
}
});
});
};
```
---
# 通用开发原则
## 1. 代码质量保证
### 编码规范
- 遵循各技术栈的官方编码规范
- 使用统一的代码格式化工具
- 添加适当的注释和文档
- 进行代码审查
### 测试要求
- 单元测试覆盖核心业务逻辑
- 集成测试验证API接口
- UI测试确保界面交互正常
- 性能测试保证系统响应速度
## 2. 安全规范
### 数据安全
- 敏感数据脱敏处理
- API接口权限控制
- 输入参数严格校验
- SQL注入防护
### 系统安全
- HTTPS协议传输
- JWT token认证
- 密码加密存储
- 定期安全检查
## 3. 性能优化
### 前端优化
- 代码分割和懒加载
- 图片压缩和CDN
- 缓存策略优化
- 首屏加载优化
### 后端优化
- 数据库查询优化
- Redis缓存策略
- 异步处理耗时操作
- JVM参数调优
## 4. 文档维护
### 同步更新
- 代码变更必须同步更新文档
- API文档自动生成和维护
- 数据库变更记录追踪
- 部署文档及时更新
### 版本管理
- 文档版本与代码版本保持一致
- 重要变更记录详细说明
- 保持向后兼容性考虑
- 定期文档审查和整理
---
## 注意事项
1. **严格遵循规范**: 所有开发必须按照本规范执行
2. **代码调整记录**: 每次代码修改都必须在 `docs/code-adjust-record.md` 中记录
3. **文档同步更新**: 代码变更必须同步更新相关文档
4. **安全第一**: 所有功能开发都要考虑安全性
5. **性能考虑**: 大数据量操作需要性能测试验证
6. **团队协作**: 重要变更需要团队讨论和代码审查
## 参考资源
- **前端官方文档**: Vue 3、Element Plus、Vite
- **后端官方文档**: Spring Boot、MyBatis-Plus、Sa-Token
- **移动端官方文档**: uni-app、uView UI
- **项目文档**: `docs/` 目录下的详细文档
- **代码调整记录**: `docs/code-adjust-record.md`
+163
View File
@@ -0,0 +1,163 @@
# ======================================================================
# 校园管理系统 (hzdyjs_xg) - 统一 Git 忽略文件
# 包含前端 (Vue 3 + TypeScript) 和后端 (Spring Boot) 的所有忽略规则
# ======================================================================
# ======================================================================
# 操作系统相关文件
# ======================================================================
.DS_Store
.DS_Store?
Thumbs.db
*.swp
*~
# ======================================================================
# 通用日志和临时文件
# ======================================================================
*.log
*.tmp
*.temp
*.bak
*.cache
.history
# ======================================================================
# IDE 和编辑器配置文件
# ======================================================================
# IntelliJ IDEA
.idea/
*.iws
*.iml
*.ipr
# Visual Studio Code
.vscode/
# Eclipse
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
# NetBeans
nbproject/private/
build/*
nbbuild/
nbdist/
.nb-gradle/
# 其他编辑器
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
# ======================================================================
# 前端 (Vue 3 + TypeScript) 相关
# ======================================================================
# 依赖目录
web/node_modules/
mobile/node_modules/
# 构建输出
web/dist/
mobile/dist/
# npm/yarn/pnpm 日志和锁定文件
web/npm-debug.log*
web/yarn-debug.log*
web/yarn-error.log*
web/package-lock.json
web/yarn.lock
web/pnpm-lock.yaml
mobile/npm-debug.log*
mobile/yarn-debug.log*
mobile/yarn-error.log*
mobile/package-lock.json
mobile/yarn.lock
mobile/pnpm-lock.yaml
# Vue 自动生成的文件
web/auto-imports.d.ts
web/components.d.ts
mobile/auto-imports.d.ts
mobile/components.d.ts
# 测试相关
web/tests/**/coverage/
web/tests/e2e/reports
web/selenium-debug.log
# ======================================================================
# 后端 (Spring Boot + Java) 相关
# ======================================================================
# Maven 构建输出
backend/target/
!backend/.mvn/wrapper/maven-wrapper.jar
# Gradle 构建输出
backend/.gradle/
backend/build/
!backend/gradle/wrapper/gradle-wrapper.jar
# Maven 版本备份
backend/*.xml.versionsBackup
backend/.flattened-pom.xml
# JRebel
backend/rebel.xml
# 应用日志
backend/logs/
# ======================================================================
# 环境配置文件 (包含敏感信息)
# ======================================================================
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# 数据库配置文件 (如果包含密码)
backend/src/main/resources/application-local.yml
backend/src/main/resources/application-local.properties
# ======================================================================
# 其他常见忽略项
# ======================================================================
# 压缩包
*.zip
*.rar
*.7z
*.tar
*.tar.gz
# 备份文件
*.backup
*.bkp
# 用户上传的文件
uploads/
files/
media/
# 临时数据
temp/
tmp/
# 覆盖率报告
coverage/
*.lcov
# 安全证书
*.pem
*.key
*.crt
*.p12
*.keystore
+18
View File
@@ -0,0 +1,18 @@
# http://editorconfig.org
root = true
# 空格替代Tab缩进在各种编辑工具下效果一致
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.{json,yml,yaml}]
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
+51
View File
@@ -0,0 +1,51 @@
name: Bug 反馈
description: 当你使用过程中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个组件存在问题,或者某些地方看起来不对劲,请在这里反馈。
title: "[Bug]: "
labels: ["bug"]
body:
- type: textarea
id: version
attributes:
label: 版本
description: 你当前正在使用我们软件的哪个版本(pom文件内的版本号)?
value: |
注意: 未填写版本号不予处理直接关闭或删除
jdk版本(带上尾号):
框架版本(项目启动时输出的版本号):
其他依赖版本(你觉得有必要的):
validations:
required: true
- type: checkboxes
attributes:
label: 功能不好用不会用是否已经看过项目文档?
options:
- label: https://plus-doc.dromara.org
required: true
- type: checkboxes
attributes:
label: 这个问题是否已经存在?
options:
- label: 我已经搜索过现有的问题 (https://gitee.com/dromara/RuoYi-Vue-Plus/issues)
required: true
- type: textarea
attributes:
label: 希望结果
description: 想知道你觉得怎么样是正常或者合理的。
validations:
required: true
- type: markdown
attributes:
label: 如何复现
description: 请详细告诉我们如何复现你遇到的问题。
value: |
如涉及代码,可提供一个最小代码示例,并使用```附上它,或者截图均可,越详细越好。<br>
大多数问题都是:代码编写错误问题,逻辑问题,或者用法错误等问题。
validations:
required: true
- type: textarea
attributes:
label: 相关代码与报错信息(请勿发混乱格式)
description: 如果可以的话,上传任何关于 bug 的截图。
value: |
[在这里上传图片]
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: RuoYi-Vue-Plus 文档中心
url: https://plus-doc.dromara.org
about: 提供 RuoYi-Vue-Plus 搭建使用指南、平台基本开发使用方式、介绍、基础知识和常见问题解答
+43
View File
@@ -0,0 +1,43 @@
name: 功能建议
description: 对本项目提出一个功能建议。
title: "[功能建议]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
感谢提出功能建议,我们将仔细考虑!请持续关注该issues,在加入计划后我们会有贡献者设置为负责人,同时状态成为进行中。
- type: textarea
id: related-problem
attributes:
label: 你的功能建议是否和某个问题相关?
description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。
validations:
required: false
- type: textarea
id: desired-solution
attributes:
label: 你希望看到什么解决方案?
description: 清晰并简洁地描述你希望发生的事情。
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 你考虑过哪些替代方案?
description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: 你有其他上下文或截图吗?
description: 在此处添加有关功能请求的任何其他上下文或截图。
validations:
required: false
- type: checkboxes
attributes:
label: 意向参与贡献
options:
- label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区。
required: false
@@ -0,0 +1,7 @@
### 更改目的 解决了什么问题(请提交到dev分支)
### 改动逻辑 这么写的思路(让作者更好的理解你的意图)
### 测试 都做了哪些测试(未经过测试不采纳)
+48
View File
@@ -0,0 +1,48 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
.flattened-pom.xml
+12
View File
@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>
+12
View File
@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2019 RuoYi-Vue-Plus
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+187
View File
@@ -0,0 +1,187 @@
<img src="https://foruda.gitee.com/images/1679673773341074847/178e8451_1766278.png" width="50%" height="50%">
<div style="height: 10px; clear: both;"></div>
- - -
## 平台简介
[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
[![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.4.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
> Dromara RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
## 赞助商
MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异
| 功能 | 本框架 | RuoYi |
|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
| 前端项目 | 采用 Vue3 + TS + ElementPlus 重写 | 基于Vue2/Vue3 + JS |
| 后端项目结构 | 采用插件化 + 扩展包形式 结构解耦 易于扩展 | 模块相互注入耦合严重难以扩展 |
| 后端代码风格 | 严格遵守Alibaba规范与项目统一配置的代码格式化 | 代码书写与常规结构不同阅读障碍大 |
| Web容器 | 采用 Undertow 基于 XNIO 的高性能容器 | 采用 Tomcat |
| 权限认证 | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展 | Spring Security 配置繁琐扩展性极差 |
| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 |
| 三方鉴权 | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证 | 无 |
| 关系数据库支持 | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例) | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换 |
| 缓存数据库 | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 |
| Redis客户端 | 采用 Redisson Redis官方推荐 基于Netty的客户端工具<br/>支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan<br/>支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐<br/>连接池采用 common-pool Bug多经常性出问题 |
| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 |
| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多<br/>例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL |
| SQL监控 | 采用 p6spy 可输出完整SQL与执行时间监控 | log输出 需手动拼接sql与参数无法快速查看调试问题 |
| 数据分页 | 采用 Mybatis-Plus 分页插件<br/>框架对其进行了扩展 对象化分页对象 支持多种方式传参 支持前端多排序 复杂排序 | 采用 PageHelper 仅支持单查询分页 参数只能从param传 只能单排序 功能扩展性差 体验不好 |
| 数据权限 | 采用 Mybatis-Plus 插件 自行分析拼接SQL 无感式过滤<br/>只需为Mapper设置好注解条件 支持多种自定义 不限于部门角色 | 采用 注解+aop 实现 基于部门角色 生成的sql兼容性差 不支持其他业务扩展<br/>生成sql后需手动拼接到具体业务sql上 对于多个Mapper查询不起作用 |
| 数据脱敏 | 采用 注解 + jackson 序列化期间脱敏 支持不同模块不同的脱敏条件<br/>支持多种策略 如身份证、手机号、地址、邮箱、银行卡等 可自行扩展 | 无 |
| 数据加解密 | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等 | 无 |
| 接口传输加密 | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性 | 无 |
| 数据翻译 | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现 | 无 |
| 多数据源框架 | 采用 dynamic-datasource 支持市面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 |
| 多数据源事务 | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚 | 不支持 |
| 数据库连接池 | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下 | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般 |
| 数据库主键 | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁 | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一 |
| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物 | 无 |
| SSE推送 | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步 | 无 |
| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!! | 采用 fastjson bugjson 远近闻名 |
| 分布式幂等 | 参考美团GTIS防重系统简化实现(细节可看文档) | 手动编写注解基于aop实现 |
| 分布式锁 | 采用 Lock4j 底层基于 Redisson | 无 |
| 分布式任务调度 | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 |
| 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 |
| 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 |
| 短信 | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在yml配置好厂家密钥即可使用 可多厂家共用 | 不支持 |
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
| Excel框架 | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
| 链路追踪 | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点 | 无 |
| 代码生成器 | 只需设计好表结构 一键生成所有crud代码与页面<br/>降低80%的开发量 把精力都投入到业务设计上<br/>框架为其适配MP、SpringDoc规范化代码 同时支持动态多数据源代码生成 | 代码生成原生结构 只支持单数据源生成 |
| 部署方式 | 支持 Docker 编排 一键搭建所有环境 让开发人员从此不再为搭建环境而烦恼 | 原生jar部署 其他环境需手动下载安装 自行搭建 |
| 项目路径修改 | 提供详细的修改方案文档 并为其做了一些改动 非常简单即可修改成自己想要的 | 需要做很多改造 文档说明有限 |
| 国际化 | 基于请求头动态返回不同语种的文本内容 开发难度低 有对应的工具类 支持大部分注解内容国际化 | 只提供基础功能 其他需自行编写扩展 |
| 代码单例测试 | 提供单例测试 使用方式编写方法与maven多环境单测插件 | 只提供基础功能 其他需自行编写扩展 |
| Demo案例 | 提供框架功能的实际使用案例 单独一个模块提供了很多很全 | 无 |
## 本框架与RuoYi的业务差异
| 业务 | 功能说明 | 本框架 | RuoYi |
|--------|----------------------------------------------------------------------|-----|------------------|
| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 |
| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 |
| 客户端管理 | 系统内对接的所有客户端管理 如: pc端、小程序端等<br>支持动态授权登录方式 如: 短信登录、密码登录等 支持动态控制token时效 | 支持 | 无 |
| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 |
| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 |
| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 |
| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 |
| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 |
| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 |
| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 |
| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 |
| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 |
| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 |
| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 |
| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 |
| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 |
| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 |
| 代码生成 | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持 | 仅支持单数据源 |
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
## 参考文档
使用框架前请仔细阅读文档重点注意事项
<br>
>[初始化项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
>
>[专栏与视频 入门必看](https://plus-doc.dromara.org/#/common/column)
>>[https://plus-doc.dromara.org/#/common/column](https://plus-doc.dromara.org/#/common/column)
>
>[部署项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
>
>[如何加群](https://plus-doc.dromara.org/#/common/add_group)
>>[https://plus-doc.dromara.org/#/common/add_group](https://plus-doc.dromara.org/#/common/add_group)
>
>[参考文档 Wiki](https://plus-doc.dromara.org)
>>[https://plus-doc.dromara.org](https://plus-doc.dromara.org)
## 软件架构图
![Plus部署架构图](https://foruda.gitee.com/images/1678981882624240692/ae2a3f3e_1766278.png "Plus部署架构图.png")
## 如何参与贡献
[参与贡献的方式 https://plus-doc.dromara.org/#/common/contribution](https://plus-doc.dromara.org/#/common/contribution)
## 捐献作者
作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭
<img src="https://foruda.gitee.com/images/1678975784848381069/d8661ed9_1766278.png" width="300px" height="450px" />
<img src="https://foruda.gitee.com/images/1678975801230205215/6f96229d_1766278.png" width="300px" height="450px" />
## 演示图例
| | |
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1735829153637063344/3c21fd4c_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1735829181303499815/4522cefa_1419627.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1735829377205259767/76a705d7_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1722959592856812900/e2d0d342_1419627.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |
+501
View File
@@ -0,0 +1,501 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-vue-plus</artifactId>
<version>${revision}</version>
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.4.0</revision>
<spring-boot.version>3.4.6</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.8.8</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.2.0</fastexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.42.0</satoken.version>
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.35</hutool.version>
<spring-boot-admin.version>3.4.7</spring-boot-admin.version>
<redisson.version>3.45.1</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.5.0</snailjob.version>
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.36</lombok.version>
<bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 -->
<sms4j.version>3.3.4</sms4j.version>
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250101</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.7.3</warm-flow.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<!-- 打包默认跳过测试 -->
<skipTests>true</skipTests>
</properties>
<profiles>
<profile>
<id>local</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>local</profiles.active>
<logging.level>info</logging.level>
<monitor.username>ruoyi</monitor.username>
<monitor.password>123456</monitor.password>
</properties>
</profile>
<profile>
<id>dev</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
<logging.level>info</logging.level>
<monitor.username>ruoyi</monitor.username>
<monitor.password>123456</monitor.password>
</properties>
<activation>
<!-- 默认环境 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
<logging.level>warn</logging.level>
<monitor.username>ruoyi</monitor.username>
<monitor.password>123456</monitor.password>
</properties>
</profile>
</profiles>
<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- hutool 的依赖配置-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-bom</artifactId>
<version>${hutool.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<!-- common 的依赖配置-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-bom</artifactId>
<version>${revision}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc</artifactId>
<version>${therapi-javadoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
<version>${fastexcel.version}</version>
</dependency>
<!-- velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${satoken.version}</version>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>${satoken.version}</version>
<exclusions>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>${satoken.version}</version>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>${p6spy.version}</version>
</dependency>
<!-- AWS SDK for Java 2.x -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!--短信sms4j-->
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-spring-boot-starter</artifactId>
<version>${sms4j.version}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>${lock4j.version}</version>
</dependency>
<!-- SnailJob Client -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-starter</artifactId>
<version>${snailjob.version}</version>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-job-core</artifactId>
<version>${snailjob.version}</version>
</dependency>
<!-- 加密包引入 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>
<version>${mapstruct-plus.version}</version>
</dependency>
<!-- 离线IP地址定位库 ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>${ip2region.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-system</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-job</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-generator</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-demo</artifactId>
<version>${revision}</version>
</dependency>
<!-- 工作流模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-workflow</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>ruoyi-admin</module>
<module>ruoyi-common</module>
<module>ruoyi-extend</module>
<module>ruoyi-modules</module>
</modules>
<packaging>pom</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc-scribe</artifactId>
<version>${therapi-javadoc.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
</path>
<path>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-processor</artifactId>
<version>${mapstruct-plus.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${mapstruct-plus.lombok.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- 单元测试使用 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
<!-- 根据打包环境执行对应的@Tag测试方法 -->
<groups>${profiles.active}</groups>
<!-- 排除标签 -->
<excludedGroups>exclude</excludedGroups>
</configuration>
</plugin>
<!-- 统一版本号管理 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<!-- 关闭过滤 -->
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<!-- 引入所有 匹配文件进行过滤 -->
<includes>
<include>application*</include>
<include>bootstrap*</include>
<include>banner*</include>
</includes>
<!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 -->
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>public</id>
<name>huawei nexus</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>huawei nexus</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
+31
View File
@@ -0,0 +1,31 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/server/logs \
/ruoyi/server/temp \
/ruoyi/skywalking/agent
WORKDIR /ruoyi/server
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT}
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
-Dsnail-job.port=${SNAIL_PORT} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
#-Dskywalking.agent.service_name=ruoyi-server \
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
-XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
-jar app.jar
+153
View File
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>org.dromara</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-admin</artifactId>
<description>
web服务入口
</description>
<dependencies>
<!-- Mysql驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- &lt;!&ndash; mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 &ndash;&gt;-->
<!-- &lt;!&ndash; Oracle &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.jdbc</groupId>-->
<!-- <artifactId>ojdbc8</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; 兼容oracle低版本 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.nls</groupId>-->
<!-- <artifactId>orai18n</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; PostgreSql &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; SqlServer &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.microsoft.sqlserver</groupId>-->
<!-- <artifactId>mssql-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-ratelimiter</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mail</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-job</artifactId>
</dependency>
<!-- 代码生成-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-demo</artifactId>
</dependency>
<!-- 工作流模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-workflow</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- skywalking 整合 logback -->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->
<!-- <artifactId>apm-toolkit-logback-1.x</artifactId>-->
<!-- <version>${与你的agent探针版本保持一致}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->
<!-- <artifactId>apm-toolkit-trace</artifactId>-->
<!-- <version>${与你的agent探针版本保持一致}</version>-->
<!-- </dependency>-->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,23 @@
package org.dromara;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/**
* 启动程序
*
* @author Lion Li
*/
@SpringBootApplication
public class DromaraApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(DromaraApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Vue-Plus启动成功 ლ(´ڡ`ლ)゙");
}
}
@@ -0,0 +1,18 @@
package org.dromara;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* web容器中进行部署
*
* @author Lion Li
*/
public class DromaraServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DromaraApplication.class);
}
}
@@ -0,0 +1,105 @@
package org.dromara.test.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.test.domain.vo.XgStuMsgVo;
import org.dromara.test.domain.bo.XgStuMsgBo;
import org.dromara.test.service.IXgStuMsgService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 学生信息
*
* @author Lion Li
* @date 2025-06-23
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/test/stuMsg")
public class XgStuMsgController extends BaseController {
private final IXgStuMsgService xgStuMsgService;
/**
* 查询学生信息列表
*/
@SaCheckPermission("test:stuMsg:list")
@GetMapping("/list")
public TableDataInfo<XgStuMsgVo> list(XgStuMsgBo bo, PageQuery pageQuery) {
return xgStuMsgService.queryPageList(bo, pageQuery);
}
/**
* 导出学生信息列表
*/
@SaCheckPermission("test:stuMsg:export")
@Log(title = "学生信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(XgStuMsgBo bo, HttpServletResponse response) {
List<XgStuMsgVo> list = xgStuMsgService.queryList(bo);
ExcelUtil.exportExcel(list, "学生信息", XgStuMsgVo.class, response);
}
/**
* 获取学生信息详细信息
*
* @param id 主键
*/
@SaCheckPermission("test:stuMsg:query")
@GetMapping("/{id}")
public R<XgStuMsgVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(xgStuMsgService.queryById(id));
}
/**
* 新增学生信息
*/
@SaCheckPermission("test:stuMsg:add")
@Log(title = "学生信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody XgStuMsgBo bo) {
return toAjax(xgStuMsgService.insertByBo(bo));
}
/**
* 修改学生信息
*/
@SaCheckPermission("test:stuMsg:edit")
@Log(title = "学生信息", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody XgStuMsgBo bo) {
return toAjax(xgStuMsgService.updateByBo(bo));
}
/**
* 删除学生信息
*
* @param ids 主键串
*/
@SaCheckPermission("test:stuMsg:remove")
@Log(title = "学生信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(xgStuMsgService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,41 @@
package org.dromara.test.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 学生信息对象 xg_stu_msg
*
* @author Lion Li
* @date 2025-06-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("xg_stu_msg")
public class XgStuMsg extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 学生姓名
*/
private String stuName;
/**
* 学生工号
*/
private Long stuNo;
}
@@ -0,0 +1,42 @@
package org.dromara.test.domain.bo;
import org.dromara.test.domain.XgStuMsg;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 学生信息业务对象 xg_stu_msg
*
* @author Lion Li
* @date 2025-06-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = XgStuMsg.class, reverseConvertGenerate = false)
public class XgStuMsgBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 学生姓名
*/
@NotBlank(message = "学生姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String stuName;
/**
* 学生工号
*/
@NotNull(message = "学生工号不能为空", groups = { AddGroup.class, EditGroup.class })
private Long stuNo;
}
@@ -0,0 +1,50 @@
package org.dromara.test.domain.vo;
import org.dromara.test.domain.XgStuMsg;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 学生信息视图对象 xg_stu_msg
*
* @author Lion Li
* @date 2025-06-23
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = XgStuMsg.class)
public class XgStuMsgVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 学生姓名
*/
@ExcelProperty(value = "学生姓名")
private String stuName;
/**
* 学生工号
*/
@ExcelProperty(value = "学生工号")
private Long stuNo;
}
@@ -0,0 +1,15 @@
package org.dromara.test.mapper;
import org.dromara.test.domain.XgStuMsg;
import org.dromara.test.domain.vo.XgStuMsgVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 学生信息Mapper接口
*
* @author Lion Li
* @date 2025-06-23
*/
public interface XgStuMsgMapper extends BaseMapperPlus<XgStuMsg, XgStuMsgVo> {
}
@@ -0,0 +1,68 @@
package org.dromara.test.service;
import org.dromara.test.domain.vo.XgStuMsgVo;
import org.dromara.test.domain.bo.XgStuMsgBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 学生信息Service接口
*
* @author Lion Li
* @date 2025-06-23
*/
public interface IXgStuMsgService {
/**
* 查询学生信息
*
* @param id 主键
* @return 学生信息
*/
XgStuMsgVo queryById(Long id);
/**
* 分页查询学生信息列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 学生信息分页列表
*/
TableDataInfo<XgStuMsgVo> queryPageList(XgStuMsgBo bo, PageQuery pageQuery);
/**
* 查询符合条件的学生信息列表
*
* @param bo 查询条件
* @return 学生信息列表
*/
List<XgStuMsgVo> queryList(XgStuMsgBo bo);
/**
* 新增学生信息
*
* @param bo 学生信息
* @return 是否新增成功
*/
Boolean insertByBo(XgStuMsgBo bo);
/**
* 修改学生信息
*
* @param bo 学生信息
* @return 是否修改成功
*/
Boolean updateByBo(XgStuMsgBo bo);
/**
* 校验并批量删除学生信息信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,133 @@
package org.dromara.test.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.test.domain.bo.XgStuMsgBo;
import org.dromara.test.domain.vo.XgStuMsgVo;
import org.dromara.test.domain.XgStuMsg;
import org.dromara.test.mapper.XgStuMsgMapper;
import org.dromara.test.service.IXgStuMsgService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 学生信息Service业务层处理
*
* @author Lion Li
* @date 2025-06-23
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class XgStuMsgServiceImpl implements IXgStuMsgService {
private final XgStuMsgMapper baseMapper;
/**
* 查询学生信息
*
* @param id 主键
* @return 学生信息
*/
@Override
public XgStuMsgVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询学生信息列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 学生信息分页列表
*/
@Override
public TableDataInfo<XgStuMsgVo> queryPageList(XgStuMsgBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<XgStuMsg> lqw = buildQueryWrapper(bo);
Page<XgStuMsgVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的学生信息列表
*
* @param bo 查询条件
* @return 学生信息列表
*/
@Override
public List<XgStuMsgVo> queryList(XgStuMsgBo bo) {
LambdaQueryWrapper<XgStuMsg> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<XgStuMsg> buildQueryWrapper(XgStuMsgBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<XgStuMsg> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(XgStuMsg::getId);
lqw.like(StringUtils.isNotBlank(bo.getStuName()), XgStuMsg::getStuName, bo.getStuName());
lqw.eq(bo.getStuNo() != null, XgStuMsg::getStuNo, bo.getStuNo());
return lqw;
}
/**
* 新增学生信息
*
* @param bo 学生信息
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(XgStuMsgBo bo) {
XgStuMsg add = MapstructUtils.convert(bo, XgStuMsg.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改学生信息
*
* @param bo 学生信息
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(XgStuMsgBo bo) {
XgStuMsg update = MapstructUtils.convert(bo, XgStuMsg.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(XgStuMsg entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除学生信息信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}
@@ -0,0 +1,239 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.utils.*;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.service.ISysClientService;
import org.dromara.system.service.ISysConfigService;
import org.dromara.system.service.ISysSocialService;
import org.dromara.system.service.ISysTenantService;
import org.dromara.web.domain.vo.LoginTenantVo;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.domain.vo.TenantListVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.dromara.web.service.SysRegisterService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 认证
*
* @author Lion Li
*/
@Slf4j
@SaIgnore
@RequiredArgsConstructor
@RestController
@RequestMapping("/auth")
public class AuthController {
private final SocialProperties socialProperties;
private final SysLoginService loginService;
private final SysRegisterService registerService;
private final ISysConfigService configService;
private final ISysTenantService tenantService;
private final ISysSocialService socialUserService;
private final ISysClientService clientService;
private final ScheduledExecutorService scheduledExecutorService;
/**
* 登录方法
*
* @param body 登录信息
* @return 结果
*/
@ApiEncrypt
@PostMapping("/login")
public R<LoginVo> login(@RequestBody String body) {
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
ValidatorUtils.validate(loginBody);
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
SysClientVo client = clientService.queryByClientId(clientId);
// 查询不到 client 或 client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
loginService.checkTenant(loginBody.getTenantId());
// 登录
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
SseMessageDto dto = new SseMessageDto();
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
dto.setUserIds(List.of(userId));
SseMessageUtils.publishMessage(dto);
}, 5, TimeUnit.SECONDS);
return R.ok(loginVo);
}
/**
* 获取跳转URL
*
* @param source 登录来源
* @return 结果
*/
@GetMapping("/binding/{source}")
public R<String> authBinding(@PathVariable("source") String source,
@RequestParam String tenantId, @RequestParam String domain) {
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) {
return R.fail(source + "平台账号暂不支持");
}
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
Map<String, String> map = new HashMap<>();
map.put("tenantId", tenantId);
map.put("domain", domain);
map.put("state", AuthStateUtils.createState());
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
return R.ok("操作成功", authorizeUrl);
}
/**
* 前端回调绑定授权(需要token)
*
* @param loginBody 请求体
* @return 结果
*/
@PostMapping("/social/callback")
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
// 校验token
StpUtil.checkLogin();
// 获取第三方登录信息
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
AuthUser authUserData = response.getData();
// 判断授权响应是否成功
if (!response.ok()) {
return R.fail(response.getMsg());
}
loginService.socialRegister(authUserData);
return R.ok();
}
/**
* 取消授权(需要token)
*
* @param socialId socialId
*/
@DeleteMapping(value = "/unlock/{socialId}")
public R<Void> unlockSocial(@PathVariable Long socialId) {
// 校验token
StpUtil.checkLogin();
Boolean rows = socialUserService.deleteWithValidById(socialId);
return rows ? R.ok() : R.fail("取消授权失败");
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/**
* 用户注册
*/
@ApiEncrypt
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) {
if (!configService.selectRegisterEnabled(user.getTenantId())) {
return R.fail("当前系统没有开启注册功能!");
}
registerService.register(user);
return R.ok();
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象
LoginTenantVo result = new LoginTenantVo();
boolean enable = TenantHelper.isEnable();
result.setTenantEnabled(enable);
// 如果未开启租户这直接返回
if (!enable) {
return R.ok(result);
}
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
try {
// 如果只超管返回所有租户
if (LoginHelper.isSuperAdmin()) {
result.setVoList(voList);
return R.ok(result);
}
} catch (NotLoginException ignored) {
}
// 获取域名
String host;
String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
// 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试
host = referer.split("//")[1].split("/")[0];
} else {
host = new URL(request.getRequestURL().toString()).getHost();
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}
}
@@ -0,0 +1,154 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.common.web.enums.CaptchaType;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.web.domain.vo.CaptchaVo;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.LinkedHashMap;
/**
* 验证码操作处理
*
* @author Lion Li
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final MailProperties mailProperties;
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
/**
* 邮箱验证码
*
* @param email 邮箱
*/
@GetMapping("/resource/email/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
SpringUtils.getAopProxy(this).emailCodeImpl(email);
return R.ok();
}
/**
* 邮箱验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(key = "#email", time = 60, count = 1)
public void emailCodeImpl(String email) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
throw new ServiceException(e.getMessage());
}
}
/**
* 生成验证码
*/
@GetMapping("/auth/code")
public R<CaptchaVo> getCode() {
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
}
/**
* 生成验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
public CaptchaVo getCodeImpl() {
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码,使用SpEL表达式处理验证码结果
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return captchaVo;
}
}
@@ -0,0 +1,28 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 首页
*
* @author Lion Li
*/
@SaIgnore
@RequiredArgsConstructor
@RestController
public class IndexController {
/**
* 访问首页,提示语
*/
@GetMapping("/")
public String index() {
return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName());
}
}
@@ -0,0 +1,25 @@
package org.dromara.web.domain.vo;
import lombok.Data;
/**
* 验证码信息
*
* @author Michelle.Chung
*/
@Data
public class CaptchaVo {
/**
* 是否开启验证码
*/
private Boolean captchaEnabled = true;
private String uuid;
/**
* 验证码图片
*/
private String img;
}
@@ -0,0 +1,25 @@
package org.dromara.web.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 登录租户对象
*
* @author Michelle.Chung
*/
@Data
public class LoginTenantVo {
/**
* 租户开关
*/
private Boolean tenantEnabled;
/**
* 租户对象列表
*/
private List<TenantListVo> voList;
}
@@ -0,0 +1,54 @@
package org.dromara.web.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 登录验证信息
*
* @author Michelle.Chung
*/
@Data
public class LoginVo {
/**
* 授权令牌
*/
@JsonProperty("access_token")
private String accessToken;
/**
* 刷新令牌
*/
@JsonProperty("refresh_token")
private String refreshToken;
/**
* 授权令牌 access_token 的有效期
*/
@JsonProperty("expire_in")
private Long expireIn;
/**
* 刷新令牌 refresh_token 的有效期
*/
@JsonProperty("refresh_expire_in")
private Long refreshExpireIn;
/**
* 应用id
*/
@JsonProperty("client_id")
private String clientId;
/**
* 令牌权限
*/
private String scope;
/**
* 用户 openid
*/
private String openid;
}
@@ -0,0 +1,31 @@
package org.dromara.web.domain.vo;
import org.dromara.system.domain.vo.SysTenantVo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
/**
* 租户列表
*
* @author Lion Li
*/
@Data
@AutoMapper(target = SysTenantVo.class)
public class TenantListVo {
/**
* 租户编号
*/
private String tenantId;
/**
* 企业名称
*/
private String companyName;
/**
* 域名
*/
private String domain;
}
@@ -0,0 +1,163 @@
package org.dromara.web.listener;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.domain.dto.UserOnlineDTO;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* 用户行为 侦听器的实现
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class UserActionListener implements SaTokenListener {
private final SysLoginService loginService;
/**
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
UserOnlineDTO dto = new UserOnlineDTO();
dto.setIpaddr(ip);
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
dto.setBrowser(userAgent.getBrowser().getName());
dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue);
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username);
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginParameter.getDeviceType());
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if(loginParameter.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
}
});
// 记录登录日志
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次注销时触发
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被踢下线时触发
*/
@Override
public void doKickout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被顶下线时触发
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被封禁时触发
*/
@Override
public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
}
/**
* 每次被解封时触发
*/
@Override
public void doUntieDisable(String loginType, Object loginId, String service) {
}
/**
* 每次打开二级认证时触发
*/
@Override
public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
}
/**
* 每次创建Session时触发
*/
@Override
public void doCloseSafe(String loginType, String tokenValue, String service) {
}
/**
* 每次创建Session时触发
*/
@Override
public void doCreateSession(String id) {
}
/**
* 每次注销Session时触发
*/
@Override
public void doLogoutSession(String id) {
}
/**
* 每次Token续期时触发
*/
@Override
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
}
}
@@ -0,0 +1,46 @@
package org.dromara.web.service;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.web.domain.vo.LoginVo;
/**
* 授权策略
*
* @author Michelle.Chung
*/
public interface IAuthStrategy {
String BASE_NAME = "AuthStrategy";
/**
* 登录
*
* @param body 登录对象
* @param client 授权管理视图对象
* @param grantType 授权类型
* @return 登录验证信息
*/
static LoginVo login(String body, SysClientVo client, String grantType) {
// 授权类型和客户端id
String beanName = grantType + BASE_NAME;
if (!SpringUtils.containsBean(beanName)) {
throw new ServiceException("授权类型不正确!");
}
IAuthStrategy instance = SpringUtils.getBean(beanName);
return instance.login(body, client);
}
/**
* 登录
*
* @param body 登录对象
* @param client 授权管理视图对象
* @return 登录验证信息
*/
LoginVo login(String body, SysClientVo client);
}
@@ -0,0 +1,251 @@
package org.dromara.web.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.*;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.exception.TenantException;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysSocialBo;
import org.dromara.system.domain.vo.*;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
/**
* 登录校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {
@Value("${user.password.maxRetryCount}")
private Integer maxRetryCount;
@Value("${user.password.lockTime}")
private Integer lockTime;
private final ISysTenantService tenantService;
private final ISysPermissionService permissionService;
private final ISysSocialService sysSocialService;
private final ISysRoleService roleService;
private final ISysDeptService deptService;
private final ISysPostService postService;
private final SysUserMapper userMapper;
/**
* 绑定第三方用户
*
* @param authUserData 授权响应实体
*/
@Lock4j
public void socialRegister(AuthUser authUserData) {
String authId = authUserData.getSource() + authUserData.getUuid();
// 第三方用户信息
SysSocialBo bo = BeanUtil.toBean(authUserData, SysSocialBo.class);
BeanUtil.copyProperties(authUserData.getToken(), bo);
Long userId = LoginHelper.getUserId();
bo.setUserId(userId);
bo.setAuthId(authId);
bo.setOpenId(authUserData.getUuid());
bo.setUserName(authUserData.getUsername());
bo.setNickName(authUserData.getNickname());
List<SysSocialVo> checkList = sysSocialService.selectByAuthId(authId);
if (CollUtil.isNotEmpty(checkList)) {
throw new ServiceException("此三方账号已经被绑定!");
}
// 查询是否已经绑定用户
SysSocialBo params = new SysSocialBo();
params.setUserId(userId);
params.setSource(bo.getSource());
List<SysSocialVo> list = sysSocialService.queryList(params);
if (CollUtil.isEmpty(list)) {
// 没有绑定用户, 新增用户信息
sysSocialService.insertByBo(bo);
} else {
// 更新用户信息
bo.setId(list.get(0).getId());
sysSocialService.updateByBo(bo);
// 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
// throw new ServiceException("此平台账号已经被绑定!");
}
}
/**
* 退出登录
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
return;
}
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
} finally {
try {
StpUtil.logout();
} catch (NotLoginException ignored) {
}
}
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
*/
public void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 构建登录用户
*/
public LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser();
Long userId = user.getUserId();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(userId);
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
loginUser.setRolePermission(permissionService.getRolePermission(userId));
if (ObjectUtil.isNotNull(user.getDeptId())) {
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
}
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
return loginUser;
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId, String ip) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(ip);
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(userId);
DataPermissionHelper.ignore(() -> userMapper.updateById(sysUser));
}
/**
* 登录校验
*/
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip)
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
// 锁定时间内登录 则踢出
if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
if (supplier.get()) {
// 错误次数递增
errorNumber++;
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
// 达到规定错误次数 则锁定登录
if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
/**
* 校验租户
*
* @param tenantId 租户ID
*/
public void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
}
@@ -0,0 +1,115 @@
package org.dromara.web.service;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.core.enums.UserType;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysUserService;
import org.springframework.stereotype.Service;
/**
* 注册校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysRegisterService {
private final ISysUserService userService;
private final SysUserMapper userMapper;
private final CaptchaProperties captchaProperties;
/**
* 注册
*/
public void register(RegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType(userType);
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, sysUser.getUserName()));
});
if (exist) {
throw new UserException("user.register.save.error", username);
}
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
}
@@ -0,0 +1,102 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.EmailLoginBody;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("email" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class EmailAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByEmail(email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验邮箱验证码
*/
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(emailCode);
}
private SysUserVo loadUserByEmail(String email) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return user;
}
}
@@ -0,0 +1,123 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.PasswordLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 密码认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("password" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class PasswordAuthStrategy implements IAuthStrategy {
private final CaptchaProperties captchaProperties;
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
String code = loginBody.getCode();
String uuid = loginBody.getUuid();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid);
}
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
private SysUserVo loadUserByUsername(String username) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
}
}
@@ -0,0 +1,102 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SmsLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 短信认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("sms" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SmsAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByPhonenumber(phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
private SysUserVo loadUserByPhonenumber(String phonenumber) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
}
}
@@ -0,0 +1,131 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysSocialService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* 第三方授权策略
*
* @author thiszhc is 三三
*/
@Slf4j
@Service("social" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SocialAuthStrategy implements IAuthStrategy {
private final SocialProperties socialProperties;
private final ISysSocialService sysSocialService;
private final SysUserMapper userMapper;
private final SysLoginService loginService;
/**
* 登录-第三方授权登录
*
* @param body 登录信息
* @param client 客户端信息
*/
@Override
public LoginVo login(String body, SysClientVo client) {
SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
ValidatorUtils.validate(loginBody);
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
if (!response.ok()) {
throw new ServiceException(response.getMsg());
}
AuthUser authUserData = response.getData();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
SysSocialVo social;
if (TenantHelper.isEnable()) {
Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
social = opt.get();
} else {
social = list.get(0);
}
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
SysUserVo user = loadUser(social.getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
private SysUserVo loadUser(Long userId) {
SysUserVo user = userMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", "");
throw new UserException("user.not.exists", "");
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", "");
throw new UserException("user.blocked", "");
}
return user;
}
}
@@ -0,0 +1,111 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.XcxLoginBody;
import org.dromara.common.core.domain.model.XcxLoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 小程序认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("xcx" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class XcxAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@Override
public LoginVo login(String body, SysClientVo client) {
XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
ValidatorUtils.validate(loginBody);
// xcxCode 为 小程序调用 wx.login 授权后获取
String xcxCode = loginBody.getXcxCode();
// 多个小程序识别使用
String appid = loginBody.getAppid();
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
.clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
.ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
AuthCallback authCallback = new AuthCallback();
authCallback.setCode(xcxCode);
AuthResponse<AuthUser> resp = authRequest.login(authCallback);
String openid, unionId;
if (resp.ok()) {
AuthToken token = resp.getData().getToken();
openid = token.getOpenId();
// 微信小程序只有关联到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。
unionId = token.getUnionId();
} else {
throw new ServiceException(resp.getMsg());
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
SysUserVo user = loadUserByOpenid(openid);
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
loginUser.setOpenid(openid);
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
loginVo.setOpenid(openid);
return loginVo;
}
private SysUserVo loadUserByOpenid(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
// todo 自行实现 userService.selectUserByOpenid(openid);
SysUserVo user = new SysUserVo();
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", openid);
// todo 用户不存在 业务逻辑自行实现
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", openid);
// todo 用户已被停用 业务逻辑自行实现
}
return user;
}
}
@@ -0,0 +1,105 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqHallInfoVo;
import org.dromara.zhxy.domain.bo.HqHallInfoBo;
import org.dromara.zhxy.service.IHqHallInfoService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 报告厅信息
*
* @author wuzilong
* @date 2025-06-23
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/hallInfo")
public class HqHallInfoController extends BaseController {
private final IHqHallInfoService hqHallInfoService;
/**
* 查询报告厅信息列表
*/
@SaCheckPermission("hq:hallInfo:list")
@GetMapping("/list")
public TableDataInfo<HqHallInfoVo> list(HqHallInfoBo bo, PageQuery pageQuery) {
return hqHallInfoService.queryPageList(bo, pageQuery);
}
/**
* 导出报告厅信息列表
*/
@SaCheckPermission("hq:hallInfo:export")
@Log(title = "报告厅信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqHallInfoBo bo, HttpServletResponse response) {
List<HqHallInfoVo> list = hqHallInfoService.queryList(bo);
ExcelUtil.exportExcel(list, "报告厅信息", HqHallInfoVo.class, response);
}
/**
* 获取报告厅信息详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:hallInfo:query")
@GetMapping("/{id}")
public R<HqHallInfoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(hqHallInfoService.queryById(id));
}
/**
* 新增报告厅信息
*/
@SaCheckPermission("hq:hallInfo:add")
@Log(title = "报告厅信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqHallInfoBo bo) {
return toAjax(hqHallInfoService.insertByBo(bo));
}
/**
* 修改报告厅信息
*/
@SaCheckPermission("hq:hallInfo:edit")
@Log(title = "报告厅信息", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqHallInfoBo bo) {
return toAjax(hqHallInfoService.updateByBo(bo));
}
/**
* 删除报告厅信息
*
* @param ids 主键串
*/
@SaCheckPermission("hq:hallInfo:remove")
@Log(title = "报告厅信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(hqHallInfoService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,115 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqHallReservationVo;
import org.dromara.zhxy.domain.bo.HqHallReservationBo;
import org.dromara.zhxy.domain.bo.HqHallReservationAuditBo;
import org.dromara.zhxy.service.IHqHallReservationService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 报告厅预约
*
* @author wuzilong
* @date 2025-06-23
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/hallReservation")
public class HqHallReservationController extends BaseController {
private final IHqHallReservationService hqHallReservationService;
/**
* 查询报告厅预约列表
*/
@SaCheckPermission("hq:hallReservation:list")
@GetMapping("/list")
public TableDataInfo<HqHallReservationVo> list(HqHallReservationBo bo, PageQuery pageQuery) {
return hqHallReservationService.queryPageList(bo, pageQuery);
}
/**
* 导出报告厅预约列表
*/
@SaCheckPermission("hq:hallReservation:export")
@Log(title = "报告厅预约", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqHallReservationBo bo, HttpServletResponse response) {
List<HqHallReservationVo> list = hqHallReservationService.queryList(bo);
ExcelUtil.exportExcel(list, "报告厅预约", HqHallReservationVo.class, response);
}
/**
* 获取报告厅预约详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:hallReservation:query")
@GetMapping("/{id}")
public R<HqHallReservationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(hqHallReservationService.queryById(id));
}
/**
* 新增报告厅预约
*/
@SaCheckPermission("hq:hallReservation:add")
@Log(title = "报告厅预约", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqHallReservationBo bo) {
return toAjax(hqHallReservationService.insertByBo(bo));
}
/**
* 修改报告厅预约
*/
@SaCheckPermission("hq:hallReservation:edit")
@Log(title = "报告厅预约", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqHallReservationBo bo) {
return toAjax(hqHallReservationService.updateByBo(bo));
}
/**
* 删除报告厅预约
*
* @param ids 主键串
*/
@SaCheckPermission("hq:hallReservation:remove")
@Log(title = "报告厅预约", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(hqHallReservationService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 审核报告厅预约(批准/拒绝)
*/
@SaCheckPermission("hq:hallReservation:audit")
@Log(title = "报告厅预约审核", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/audit")
public R<Void> audit(@RequestBody HqHallReservationAuditBo auditBo) {
return toAjax(hqHallReservationService.auditByIds(auditBo));
}
}
@@ -0,0 +1,105 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatCategoryVo;
import org.dromara.zhxy.domain.bo.HqSatCategoryBo;
import org.dromara.zhxy.service.IHqSatCategoryService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 题目分类
*
* @author wuzilong
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satCategory")
public class HqSatCategoryController extends BaseController {
private final IHqSatCategoryService hqSatCategoryService;
/**
* 查询题目分类列表
*/
@SaCheckPermission("hq:satCategory:list")
@GetMapping("/list")
public TableDataInfo<HqSatCategoryVo> list(HqSatCategoryBo bo, PageQuery pageQuery) {
return hqSatCategoryService.queryPageList(bo, pageQuery);
}
/**
* 导出题目分类列表
*/
@SaCheckPermission("hq:satCategory:export")
@Log(title = "题目分类", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatCategoryBo bo, HttpServletResponse response) {
List<HqSatCategoryVo> list = hqSatCategoryService.queryList(bo);
ExcelUtil.exportExcel(list, "题目分类", HqSatCategoryVo.class, response);
}
/**
* 获取题目分类详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satCategory:query")
@GetMapping("/{id}")
public R<HqSatCategoryVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(hqSatCategoryService.queryById(id));
}
/**
* 新增题目分类
*/
@SaCheckPermission("hq:satCategory:add")
@Log(title = "题目分类", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatCategoryBo bo) {
return toAjax(hqSatCategoryService.insertByBo(bo));
}
/**
* 修改题目分类
*/
@SaCheckPermission("hq:satCategory:edit")
@Log(title = "题目分类", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatCategoryBo bo) {
return toAjax(hqSatCategoryService.updateByBo(bo));
}
/**
* 删除题目分类
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satCategory:remove")
@Log(title = "题目分类", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(hqSatCategoryService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,105 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatDetailVo;
import org.dromara.zhxy.domain.bo.HqSatDetailBo;
import org.dromara.zhxy.service.IHqSatDetailService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 答题详情
*
* @author wuzilong
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satDetail")
public class HqSatDetailController extends BaseController {
private final IHqSatDetailService hqSatDetailService;
/**
* 查询答题详情列表
*/
@SaCheckPermission("hq:satDetail:list")
@GetMapping("/list")
public TableDataInfo<HqSatDetailVo> list(HqSatDetailBo bo, PageQuery pageQuery) {
return hqSatDetailService.queryPageList(bo, pageQuery);
}
/**
* 导出答题详情列表
*/
@SaCheckPermission("hq:satDetail:export")
@Log(title = "答题详情", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatDetailBo bo, HttpServletResponse response) {
List<HqSatDetailVo> list = hqSatDetailService.queryList(bo);
ExcelUtil.exportExcel(list, "答题详情", HqSatDetailVo.class, response);
}
/**
* 获取答题详情详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satDetail:query")
@GetMapping("/{id}")
public R<HqSatDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(hqSatDetailService.queryById(id));
}
/**
* 新增答题详情
*/
@SaCheckPermission("hq:satDetail:add")
@Log(title = "答题详情", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatDetailBo bo) {
return toAjax(hqSatDetailService.insertByBo(bo));
}
/**
* 修改答题详情
*/
@SaCheckPermission("hq:satDetail:edit")
@Log(title = "答题详情", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatDetailBo bo) {
return toAjax(hqSatDetailService.updateByBo(bo));
}
/**
* 删除答题详情
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satDetail:remove")
@Log(title = "答题详情", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(hqSatDetailService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,105 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatOptionVo;
import org.dromara.zhxy.domain.bo.HqSatOptionBo;
import org.dromara.zhxy.service.IHqSatOptionService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 评分选项
*
* @author Lion Li
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satOption")
public class HqSatOptionController extends BaseController {
private final IHqSatOptionService hqSatOptionService;
/**
* 查询评分选项列表
*/
@SaCheckPermission("hq:satOption:list")
@GetMapping("/list")
public TableDataInfo<HqSatOptionVo> list(HqSatOptionBo bo, PageQuery pageQuery) {
return hqSatOptionService.queryPageList(bo, pageQuery);
}
/**
* 导出评分选项列表
*/
@SaCheckPermission("hq:satOption:export")
@Log(title = "评分选项", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatOptionBo bo, HttpServletResponse response) {
List<HqSatOptionVo> list = hqSatOptionService.queryList(bo);
ExcelUtil.exportExcel(list, "评分选项", HqSatOptionVo.class, response);
}
/**
* 获取评分选项详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satOption:query")
@GetMapping("/{id}")
public R<HqSatOptionVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(hqSatOptionService.queryById(id));
}
/**
* 新增评分选项
*/
@SaCheckPermission("hq:satOption:add")
@Log(title = "评分选项", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatOptionBo bo) {
return toAjax(hqSatOptionService.insertByBo(bo));
}
/**
* 修改评分选项
*/
@SaCheckPermission("hq:satOption:edit")
@Log(title = "评分选项", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatOptionBo bo) {
return toAjax(hqSatOptionService.updateByBo(bo));
}
/**
* 删除评分选项
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satOption:remove")
@Log(title = "评分选项", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(hqSatOptionService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,123 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatQuestionVo;
import org.dromara.zhxy.domain.bo.HqSatQuestionBo;
import org.dromara.zhxy.service.IHqSatQuestionService;
import org.dromara.zhxy.service.IHqSatOptionService;
import org.dromara.zhxy.domain.vo.HqSatOptionVo;
import org.dromara.zhxy.domain.bo.HqSatOptionBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 题库
*
* @author wuzilong
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satQuestion")
public class HqSatQuestionController extends BaseController {
private final IHqSatQuestionService hqSatQuestionService;
private final IHqSatOptionService hqSatOptionService;
/**
* 查询题库列表
*/
@SaCheckPermission("hq:satQuestion:list")
@GetMapping("/list")
public TableDataInfo<HqSatQuestionVo> list(HqSatQuestionBo bo, PageQuery pageQuery) {
return hqSatQuestionService.queryPageList(bo, pageQuery);
}
/**
* 导出题库列表
*/
@SaCheckPermission("hq:satQuestion:export")
@Log(title = "题库", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatQuestionBo bo, HttpServletResponse response) {
List<HqSatQuestionVo> list = hqSatQuestionService.queryList(bo);
ExcelUtil.exportExcel(list, "题库", HqSatQuestionVo.class, response);
}
/**
* 获取题库详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satQuestion:query")
@GetMapping("/{id}")
public R<HqSatQuestionVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(hqSatQuestionService.queryById(id));
}
/**
* 新增题库
*/
@SaCheckPermission("hq:satQuestion:add")
@Log(title = "题库", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatQuestionBo bo) {
return toAjax(hqSatQuestionService.insertByBo(bo));
}
/**
* 修改题库
*/
@SaCheckPermission("hq:satQuestion:edit")
@Log(title = "题库", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatQuestionBo bo) {
return toAjax(hqSatQuestionService.updateByBo(bo));
}
/**
* 删除题库
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satQuestion:remove")
@Log(title = "题库", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(hqSatQuestionService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取题目的选项列表
* 注:选项针对所有单选题和多选题通用,不区分具体题目
*
* @param questionId 题目ID(预留参数,当前不使用)
*/
@SaCheckPermission("hq:satQuestion:query")
@GetMapping("/{questionId}/options")
public R<List<HqSatOptionVo>> getQuestionOptions(@PathVariable Long questionId) {
// 获取所有启用状态的选项
HqSatOptionBo bo = new HqSatOptionBo();
bo.setStatus("0"); // 启用状态
List<HqSatOptionVo> options = hqSatOptionService.queryList(bo);
return R.ok(options);
}
}
@@ -0,0 +1,105 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatRecordVo;
import org.dromara.zhxy.domain.bo.HqSatRecordBo;
import org.dromara.zhxy.service.IHqSatRecordService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 答题记录
*
* @author wuzilong
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satRecord")
public class HqSatRecordController extends BaseController {
private final IHqSatRecordService hqSatRecordService;
/**
* 查询答题记录列表
*/
@SaCheckPermission("hq:satRecord:list")
@GetMapping("/list")
public TableDataInfo<HqSatRecordVo> list(HqSatRecordBo bo, PageQuery pageQuery) {
return hqSatRecordService.queryPageList(bo, pageQuery);
}
/**
* 导出答题记录列表
*/
@SaCheckPermission("hq:satRecord:export")
@Log(title = "答题记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatRecordBo bo, HttpServletResponse response) {
List<HqSatRecordVo> list = hqSatRecordService.queryList(bo);
ExcelUtil.exportExcel(list, "答题记录", HqSatRecordVo.class, response);
}
/**
* 获取答题记录详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satRecord:query")
@GetMapping("/{id}")
public R<HqSatRecordVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(hqSatRecordService.queryById(id));
}
/**
* 新增答题记录
*/
@SaCheckPermission("hq:satRecord:add")
@Log(title = "答题记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatRecordBo bo) {
return toAjax(hqSatRecordService.insertByBo(bo));
}
/**
* 修改答题记录
*/
@SaCheckPermission("hq:satRecord:edit")
@Log(title = "答题记录", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatRecordBo bo) {
return toAjax(hqSatRecordService.updateByBo(bo));
}
/**
* 删除答题记录
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satRecord:remove")
@Log(title = "答题记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(hqSatRecordService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,180 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatSurveyVo;
import org.dromara.zhxy.domain.bo.HqSatSurveyBo;
import org.dromara.zhxy.service.IHqSatSurveyService;
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 问卷
*
* @author wuzilong
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satSurvey")
public class HqSatSurveyController extends BaseController {
private final IHqSatSurveyService hqSatSurveyService;
/**
* 查询问卷列表
*/
@SaCheckPermission("hq:satSurvey:list")
@GetMapping("/list")
public TableDataInfo<HqSatSurveyVo> list(HqSatSurveyBo bo, PageQuery pageQuery) {
// 从params中获取权限过滤参数
if (bo.getParams() != null) {
// 处理目标群体IN查询
if (bo.getParams().containsKey("targetGroupIn")) {
Object targetGroupInObj = bo.getParams().get("targetGroupIn");
if (targetGroupInObj instanceof List<?>) {
@SuppressWarnings("unchecked")
List<String> targetGroups = (List<String>) targetGroupInObj;
bo.setTargetGroupIn(targetGroups);
}
}
// 处理状态IN查询
if (bo.getParams().containsKey("statusIn")) {
Object statusInObj = bo.getParams().get("statusIn");
if (statusInObj instanceof List<?>) {
@SuppressWarnings("unchecked")
List<String> statuses = (List<String>) statusInObj;
bo.setStatusIn(statuses);
}
}
}
return hqSatSurveyService.queryPageList(bo, pageQuery);
}
/**
* 导出问卷列表
*/
@SaCheckPermission("hq:satSurvey:export")
@Log(title = "问卷", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatSurveyBo bo, HttpServletResponse response) {
List<HqSatSurveyVo> list = hqSatSurveyService.queryList(bo);
ExcelUtil.exportExcel(list, "问卷", HqSatSurveyVo.class, response);
}
/**
* 获取问卷详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satSurvey:query")
@GetMapping("/{id}")
public R<HqSatSurveyVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(hqSatSurveyService.queryById(id));
}
/**
* 新增问卷
*/
@SaCheckPermission("hq:satSurvey:add")
@Log(title = "问卷", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatSurveyBo bo) {
return toAjax(hqSatSurveyService.insertByBo(bo));
}
/**
* 修改问卷
*/
@SaCheckPermission("hq:satSurvey:edit")
@Log(title = "问卷", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatSurveyBo bo) {
return toAjax(hqSatSurveyService.updateByBo(bo));
}
/**
* 删除问卷
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satSurvey:remove")
@Log(title = "问卷", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(hqSatSurveyService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取问卷关联的题目列表
*
* @param surveyId 问卷ID
*/
@SaCheckPermission("hq:satSurvey:question")
@GetMapping("/{surveyId}/questions")
public R<List<HqSatSurveyQuestionVo>> getSurveyQuestions(
@NotNull(message = "问卷ID不能为空") @PathVariable Long surveyId) {
return R.ok(hqSatSurveyService.getSurveyQuestions(surveyId));
}
/**
* 批量更新问卷题目关联
*
* @param surveyId 问卷ID
* @param questions 题目列表
*/
@SaCheckPermission("hq:satSurvey:question")
@Log(title = "问卷题目管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/{surveyId}/questions")
public R<Void> updateSurveyQuestions(@NotNull(message = "问卷ID不能为空") @PathVariable Long surveyId,
@RequestBody List<Object> questions) {
return toAjax(hqSatSurveyService.updateSurveyQuestions(surveyId, questions));
}
/**
* 发布问卷
*
* @param id 问卷ID
*/
@SaCheckPermission("hq:satSurvey:publish")
@Log(title = "发布问卷", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/{id}/publish")
public R<Void> publishSurvey(@NotNull(message = "问卷ID不能为空") @PathVariable Long id) {
return toAjax(hqSatSurveyService.publishSurvey(id));
}
/**
* 撤销发布问卷
*
* @param id 问卷ID
*/
@SaCheckPermission("hq:satSurvey:unpublish")
@Log(title = "撤销发布问卷", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/{id}/unpublish")
public R<Void> unpublishSurvey(@NotNull(message = "问卷ID不能为空") @PathVariable Long id) {
return toAjax(hqSatSurveyService.unpublishSurvey(id));
}
}
@@ -0,0 +1,105 @@
package org.dromara.zhxy.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
import org.dromara.zhxy.domain.bo.HqSatSurveyQuestionBo;
import org.dromara.zhxy.service.IHqSatSurveyQuestionService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 问卷题目关联
*
* @author wuzilong
* @date 2025-06-24
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hq/satSurveyQuestion")
public class HqSatSurveyQuestionController extends BaseController {
private final IHqSatSurveyQuestionService hqSatSurveyQuestionService;
/**
* 查询问卷题目关联列表
*/
@SaCheckPermission("hq:satSurveyQuestion:list")
@GetMapping("/list")
public TableDataInfo<HqSatSurveyQuestionVo> list(HqSatSurveyQuestionBo bo, PageQuery pageQuery) {
return hqSatSurveyQuestionService.queryPageList(bo, pageQuery);
}
/**
* 导出问卷题目关联列表
*/
@SaCheckPermission("hq:satSurveyQuestion:export")
@Log(title = "问卷题目关联", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HqSatSurveyQuestionBo bo, HttpServletResponse response) {
List<HqSatSurveyQuestionVo> list = hqSatSurveyQuestionService.queryList(bo);
ExcelUtil.exportExcel(list, "问卷题目关联", HqSatSurveyQuestionVo.class, response);
}
/**
* 获取问卷题目关联详细信息
*
* @param id 主键
*/
@SaCheckPermission("hq:satSurveyQuestion:query")
@GetMapping("/{id}")
public R<HqSatSurveyQuestionVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(hqSatSurveyQuestionService.queryById(id));
}
/**
* 新增问卷题目关联
*/
@SaCheckPermission("hq:satSurveyQuestion:add")
@Log(title = "问卷题目关联", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody HqSatSurveyQuestionBo bo) {
return toAjax(hqSatSurveyQuestionService.insertByBo(bo));
}
/**
* 修改问卷题目关联
*/
@SaCheckPermission("hq:satSurveyQuestion:edit")
@Log(title = "问卷题目关联", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody HqSatSurveyQuestionBo bo) {
return toAjax(hqSatSurveyQuestionService.updateByBo(bo));
}
/**
* 删除问卷题目关联
*
* @param ids 主键串
*/
@SaCheckPermission("hq:satSurveyQuestion:remove")
@Log(title = "问卷题目关联", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(hqSatSurveyQuestionService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,53 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import java.io.Serial;
/**
* 报告厅信息对象 hq_hall_info
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_hall_info")
public class HqHallInfo extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 报告厅名称
*/
private String hallName;
/**
* 报告厅图片
*/
private String hallImage;
/**
* 报告厅状态
*/
private String hallStatus;
/**
* 报告厅描述
*/
private String hallDescription;
}
@@ -0,0 +1,97 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* 报告厅预约对象 hq_hall_reservation
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_hall_reservation")
public class HqHallReservation extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 报告厅ID
*/
private Long hallId;
/**
* 报告厅名称
*/
private String hallName;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 预约用途
*/
private String reservationPurpose;
/**
* 申请人id
*/
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 申请人电话
*/
private String applicantPhone;
/**
* 预约状态
*/
private String status;
/**
* 审核人id
*/
private Long auditBy;
/**
* 审核人
*/
private String auditName;
/**
* 审核时间
*/
private Date auditTime;
/**
* 审核备注
*/
private String auditRemark;
}
@@ -0,0 +1,66 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 题目分类对象 hq_sat_category
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_category")
public class HqSatCategory extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 分类编码
*/
private String categoryCode;
/**
* 分类名称
*/
private String categoryName;
/**
* 分类描述
*/
private String categoryDesc;
/**
* 排序
*/
private Long sortOrder;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,56 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 答题详情对象 hq_sat_detail
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_detail")
public class HqSatDetail extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 答题记录ID
*/
private Long recordId;
/**
* 题目ID
*/
private Long questionId;
/**
* 答案分数
*/
private Long answerScore;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,61 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 评分选项对象 hq_sat_option
*
* @author Lion Li
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_option")
public class HqSatOption extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 选项文本
*/
private String optionText;
/**
* 选项分值
*/
private Long optionValue;
/**
* 排序
*/
private Long sortOrder;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,76 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 题库对象 hq_sat_question
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_question")
public class HqSatQuestion extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 题目标题
*/
private String questionTitle;
/**
* 题目内容
*/
private String questionContent;
/**
* 所属分类ID
*/
private Long categoryId;
/**
* 题目类型
*/
private String questionType;
/**
* 是否必填
*/
private Long isRequired;
/**
* 排序
*/
private Long sortOrder;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,78 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* 答题记录对象 hq_sat_record
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_record")
public class HqSatRecord extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 问卷ID
*/
private Long surveyId;
/**
* 答题用户ID
*/
private Long userId;
/**
* 用户类型
*/
private String userType;
/**
* 用户部门
*/
private String userDept;
/**
* 提交时间
*/
private Date submitTime;
/**
* 总分
*/
private Long totalScore;
/**
* 平均分
*/
private Long avgScore;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,98 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* 问卷对象 hq_sat_survey
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_survey")
public class HqSatSurvey extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 问卷编号
*/
private String surveyCode;
/**
* 问卷标题
*/
private String surveyTitle;
/**
* 问卷描述
*/
private String surveyDesc;
/**
* 填写说明
*/
private String surveyInstruction;
/**
* 目标群体
*/
private String targetGroup;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 最大提交次数
*/
private Long maxSubmitTimes;
/**
* 是否匿名
*/
private Long isAnonymous;
/**
* 是否发布
*/
private Long isPublished;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,66 @@
package org.dromara.zhxy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 问卷题目关联对象 hq_sat_survey_question
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hq_sat_survey_question")
public class HqSatSurveyQuestion extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 问卷ID
*/
private Long surveyId;
/**
* 题目ID
*/
private Long questionId;
/**
* 题目顺序
*/
private Long sortOrder;
/**
* 题目权重
*/
private Long weight;
/**
* 是否必填
*/
private Long isRequired;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,54 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqHallInfo;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
/**
* 报告厅信息业务对象 hq_hall_info
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqHallInfo.class, reverseConvertGenerate = false)
public class HqHallInfoBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 报告厅名称
*/
@NotBlank(message = "报告厅名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String hallName;
/**
* 报告厅图片
*/
private String hallImage;
/**
* 报告厅状态
*/
@NotBlank(message = "报告厅状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String hallStatus;
/**
* 报告厅描述
*/
private String hallDescription;
}
@@ -0,0 +1,33 @@
package org.dromara.zhxy.domain.bo;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
/**
* 报告厅预约审核业务对象
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
public class HqHallReservationAuditBo {
/**
* 预约记录ID列表
*/
@NotEmpty(message = "预约记录ID不能为空")
private List<Long> ids;
/**
* 审核状态:1-批准,2-拒绝
*/
@NotBlank(message = "审核状态不能为空")
private String status;
/**
* 审核备注
*/
private String auditRemark;
}
@@ -0,0 +1,116 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqHallReservation;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 报告厅预约业务对象 hq_hall_reservation
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqHallReservation.class, reverseConvertGenerate = false)
public class HqHallReservationBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 报告厅ID
*/
@NotNull(message = "报告厅ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long hallId;
/**
* 报告厅名称
*/
@NotBlank(message = "报告厅名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String hallName;
/**
* 开始时间(用于新增和编辑)
*/
@NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 结束时间(用于新增和编辑)
*/
@NotNull(message = "结束时间不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**
* 开始时间查询参数(字符串格式)
*/
private String startTimeStr;
/**
* 结束时间查询参数(字符串格式)
*/
private String endTimeStr;
/**
* 预约用途
*/
@NotBlank(message = "预约用途不能为空", groups = { AddGroup.class, EditGroup.class })
private String reservationPurpose;
/**
* 申请人id
*/
@NotNull(message = "申请人id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 申请人姓名
*/
@NotBlank(message = "申请人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String applicantName;
/**
* 申请人电话
*/
private String applicantPhone;
/**
* 预约状态
*/
@NotBlank(message = "预约状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String status;
/**
* 审核人id
*/
private Long auditBy;
/**
* 审核人
*/
private String auditName;
/**
* 审核时间
*/
private Date auditTime;
/**
* 审核备注
*/
private String auditRemark;
}
@@ -0,0 +1,67 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatCategory;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 题目分类业务对象 hq_sat_category
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatCategory.class, reverseConvertGenerate = false)
public class HqSatCategoryBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 分类编码
*/
@NotBlank(message = "分类编码不能为空", groups = { AddGroup.class, EditGroup.class })
private String categoryCode;
/**
* 分类名称
*/
@NotBlank(message = "分类名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String categoryName;
/**
* 分类描述
*/
private String categoryDesc;
/**
* 排序
*/
private Long sortOrder;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,57 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatDetail;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 答题详情业务对象 hq_sat_detail
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatDetail.class, reverseConvertGenerate = false)
public class HqSatDetailBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 答题记录ID
*/
@NotNull(message = "答题记录ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long recordId;
/**
* 题目ID
*/
@NotNull(message = "题目ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long questionId;
/**
* 答案分数
*/
private Long answerScore;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,62 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatOption;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 评分选项业务对象 hq_sat_option
*
* @author Lion Li
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatOption.class, reverseConvertGenerate = false)
public class HqSatOptionBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 选项文本
*/
@NotBlank(message = "选项文本不能为空", groups = { AddGroup.class, EditGroup.class })
private String optionText;
/**
* 选项分值
*/
@NotNull(message = "选项分值不能为空", groups = { AddGroup.class, EditGroup.class })
private Long optionValue;
/**
* 排序
*/
private Long sortOrder;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,77 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatQuestion;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 题库业务对象 hq_sat_question
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatQuestion.class, reverseConvertGenerate = false)
public class HqSatQuestionBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 题目标题
*/
@NotBlank(message = "题目标题不能为空", groups = { AddGroup.class, EditGroup.class })
private String questionTitle;
/**
* 题目内容
*/
private String questionContent;
/**
* 所属分类ID
*/
@NotNull(message = "所属分类ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long categoryId;
/**
* 题目类型
*/
private String questionType;
/**
* 是否必填
*/
private Long isRequired;
/**
* 排序
*/
private Long sortOrder;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,80 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatRecord;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 答题记录业务对象 hq_sat_record
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatRecord.class, reverseConvertGenerate = false)
public class HqSatRecordBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 问卷ID
*/
@NotNull(message = "问卷ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long surveyId;
/**
* 答题用户ID
*/
private Long userId;
/**
* 用户类型
*/
@NotBlank(message = "用户类型不能为空", groups = { AddGroup.class, EditGroup.class })
private String userType;
/**
* 用户部门
*/
private String userDept;
/**
* 提交时间
*/
@NotNull(message = "提交时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date submitTime;
/**
* 总分
*/
private Long totalScore;
/**
* 平均分
*/
private Long avgScore;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,112 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatSurvey;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 问卷业务对象 hq_sat_survey
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatSurvey.class, reverseConvertGenerate = false)
public class HqSatSurveyBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 问卷编号
*/
@NotBlank(message = "问卷编号不能为空", groups = { AddGroup.class, EditGroup.class })
private String surveyCode;
/**
* 问卷标题
*/
@NotBlank(message = "问卷标题不能为空", groups = { AddGroup.class, EditGroup.class })
private String surveyTitle;
/**
* 问卷描述
*/
private String surveyDesc;
/**
* 填写说明
*/
private String surveyInstruction;
/**
* 目标群体
*/
@NotBlank(message = "目标群体不能为空", groups = { AddGroup.class, EditGroup.class })
private String targetGroup;
/**
* 开始时间
*/
@NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date startTime;
/**
* 结束时间
*/
@NotNull(message = "结束时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date endTime;
/**
* 最大提交次数
*/
private Long maxSubmitTimes;
/**
* 是否匿名
*/
private Long isAnonymous;
/**
* 是否发布
*/
private Long isPublished;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 目标群体IN查询(用于权限过滤)
*/
private List<String> targetGroupIn;
/**
* 状态IN查询(用于状态过滤)
*/
private List<String> statusIn;
}
@@ -0,0 +1,67 @@
package org.dromara.zhxy.domain.bo;
import org.dromara.zhxy.domain.HqSatSurveyQuestion;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 问卷题目关联业务对象 hq_sat_survey_question
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HqSatSurveyQuestion.class, reverseConvertGenerate = false)
public class HqSatSurveyQuestionBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 删除标志
*/
private Long deleted;
/**
* 问卷ID
*/
@NotNull(message = "问卷ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long surveyId;
/**
* 题目ID
*/
@NotNull(message = "题目ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long questionId;
/**
* 题目顺序
*/
private Long sortOrder;
/**
* 题目权重
*/
private Long weight;
/**
* 是否必填
*/
private Long isRequired;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,70 @@
package org.dromara.zhxy.domain.vo;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.zhxy.domain.HqHallInfo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 报告厅信息视图对象 hq_hall_info
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqHallInfo.class)
public class HqHallInfoVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 报告厅名称
*/
@ExcelProperty(value = "报告厅名称")
private String hallName;
/**
* 报告厅图片
*/
@ExcelProperty(value = "报告厅图片")
private String hallImage;
/**
* 报告厅图片Url
*/
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "hallImage")
private String hallImageUrl;
/**
* 报告厅状态
*/
@ExcelProperty(value = "报告厅状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "hall_status")
private String hallStatus;
/**
* 报告厅描述
*/
@ExcelProperty(value = "报告厅描述")
private String hallDescription;
}
@@ -0,0 +1,116 @@
package org.dromara.zhxy.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.zhxy.domain.HqHallReservation;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 报告厅预约视图对象 hq_hall_reservation
*
* @author wuzilong
* @date 2025-06-23
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqHallReservation.class)
public class HqHallReservationVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 报告厅ID
*/
@ExcelProperty(value = "报告厅ID")
private Long hallId;
/**
* 报告厅名称
*/
@ExcelProperty(value = "报告厅名称")
private String hallName;
/**
* 开始时间
*/
@ExcelProperty(value = "开始时间")
private Date startTime;
/**
* 结束时间
*/
@ExcelProperty(value = "结束时间")
private Date endTime;
/**
* 预约用途
*/
@ExcelProperty(value = "预约用途")
private String reservationPurpose;
/**
* 申请人id
*/
@ExcelProperty(value = "申请人id")
private Long applicantId;
/**
* 申请人姓名
*/
@ExcelProperty(value = "申请人姓名")
private String applicantName;
/**
* 申请人电话
*/
@ExcelProperty(value = "申请人电话")
private String applicantPhone;
/**
* 预约状态
*/
@ExcelProperty(value = "预约状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "hall_reserv_status")
private String status;
/**
* 审核人id
*/
@ExcelProperty(value = "审核人id")
private Long auditBy;
/**
* 审核人
*/
@ExcelProperty(value = "审核人")
private String auditName;
/**
* 审核时间
*/
@ExcelProperty(value = "审核时间")
private Date auditTime;
/**
* 审核备注
*/
@ExcelProperty(value = "审核备注")
private String auditRemark;
}
@@ -0,0 +1,82 @@
package org.dromara.zhxy.domain.vo;
import org.dromara.zhxy.domain.HqSatCategory;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 题目分类视图对象 hq_sat_category
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatCategory.class)
public class HqSatCategoryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_delete")
private Long deleted;
/**
* 分类编码
*/
@ExcelProperty(value = "分类编码")
private String categoryCode;
/**
* 分类名称
*/
@ExcelProperty(value = "分类名称")
private String categoryName;
/**
* 分类描述
*/
@ExcelProperty(value = "分类描述")
private String categoryDesc;
/**
* 排序
*/
@ExcelProperty(value = "排序")
private Long sortOrder;
/**
* 状态
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_enable")
private String status;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,69 @@
package org.dromara.zhxy.domain.vo;
import org.dromara.zhxy.domain.HqSatDetail;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 答题详情视图对象 hq_sat_detail
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatDetail.class)
public class HqSatDetailVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_delete")
private Long deleted;
/**
* 答题记录ID
*/
@ExcelProperty(value = "答题记录ID")
private Long recordId;
/**
* 题目ID
*/
@ExcelProperty(value = "题目ID")
private Long questionId;
/**
* 答案分数
*/
@ExcelProperty(value = "答案分数")
private Long answerScore;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,76 @@
package org.dromara.zhxy.domain.vo;
import org.dromara.zhxy.domain.HqSatOption;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 评分选项视图对象 hq_sat_option
*
* @author Lion Li
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatOption.class)
public class HqSatOptionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_delete")
private Long deleted;
/**
* 选项文本
*/
@ExcelProperty(value = "选项文本")
private String optionText;
/**
* 选项分值
*/
@ExcelProperty(value = "选项分值")
private Long optionValue;
/**
* 排序
*/
@ExcelProperty(value = "排序")
private Long sortOrder;
/**
* 状态
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_enable")
private String status;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,96 @@
package org.dromara.zhxy.domain.vo;
import org.dromara.zhxy.domain.HqSatQuestion;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 题库视图对象 hq_sat_question
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatQuestion.class)
public class HqSatQuestionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_delete")
private Long deleted;
/**
* 题目标题
*/
@ExcelProperty(value = "题目标题")
private String questionTitle;
/**
* 题目内容
*/
@ExcelProperty(value = "题目内容")
private String questionContent;
/**
* 所属分类ID
*/
@ExcelProperty(value = "所属分类ID")
private Long categoryId;
/**
* 题目类型
*/
@ExcelProperty(value = "题目类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_question_category")
private String questionType;
/**
* 是否必填
*/
@ExcelProperty(value = "是否必填", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_required")
private Long isRequired;
/**
* 排序
*/
@ExcelProperty(value = "排序")
private Long sortOrder;
/**
* 状态
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_enable")
private String status;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,95 @@
package org.dromara.zhxy.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.zhxy.domain.HqSatRecord;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 答题记录视图对象 hq_sat_record
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatRecord.class)
public class HqSatRecordVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志")
private Long deleted;
/**
* 问卷ID
*/
@ExcelProperty(value = "问卷ID")
private Long surveyId;
/**
* 答题用户ID
*/
@ExcelProperty(value = "答题用户ID")
private Long userId;
/**
* 用户类型
*/
@ExcelProperty(value = "用户类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "survey_people_category")
private String userType;
/**
* 用户部门
*/
@ExcelProperty(value = "用户部门")
private String userDept;
/**
* 提交时间
*/
@ExcelProperty(value = "提交时间")
private Date submitTime;
/**
* 总分
*/
@ExcelProperty(value = "总分")
private Long totalScore;
/**
* 平均分
*/
@ExcelProperty(value = "平均分")
private Long avgScore;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,119 @@
package org.dromara.zhxy.domain.vo;
import org.dromara.zhxy.domain.HqSatSurveyQuestion;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 问卷题目关联视图对象 hq_sat_survey_question
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatSurveyQuestion.class)
public class HqSatSurveyQuestionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_delete")
private Long deleted;
/**
* 问卷ID
*/
@ExcelProperty(value = "问卷ID")
private Long surveyId;
/**
* 题目ID
*/
@ExcelProperty(value = "题目ID")
private Long questionId;
/**
* 题目顺序
*/
@ExcelProperty(value = "题目顺序")
private Long sortOrder;
/**
* 题目权重
*/
@ExcelProperty(value = "题目权重")
private Long weight;
/**
* 是否必填
*/
@ExcelProperty(value = "是否必填", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_required")
private Long isRequired;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
// ==================== 以下为题目详细信息字段 ====================
/**
* 题目标题
*/
@ExcelProperty(value = "题目标题")
private String questionTitle;
/**
* 题目内容
*/
@ExcelProperty(value = "题目内容")
private String questionContent;
/**
* 所属分类ID
*/
@ExcelProperty(value = "所属分类ID")
private Long categoryId;
/**
* 题目类型
*/
@ExcelProperty(value = "题目类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_question_type")
private String questionType;
/**
* 分类名称
*/
@ExcelProperty(value = "分类名称")
private String categoryName;
/**
* 题目状态
*/
@ExcelProperty(value = "题目状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;
}
@@ -0,0 +1,123 @@
package org.dromara.zhxy.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.zhxy.domain.HqSatSurvey;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 问卷视图对象 hq_sat_survey
*
* @author wuzilong
* @date 2025-06-24
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HqSatSurvey.class)
public class HqSatSurveyVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 删除标志
*/
@ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_item_delete")
private Long deleted;
/**
* 问卷编号
*/
@ExcelProperty(value = "问卷编号")
private String surveyCode;
/**
* 问卷标题
*/
@ExcelProperty(value = "问卷标题")
private String surveyTitle;
/**
* 问卷描述
*/
@ExcelProperty(value = "问卷描述")
private String surveyDesc;
/**
* 填写说明
*/
@ExcelProperty(value = "填写说明")
private String surveyInstruction;
/**
* 目标群体
*/
@ExcelProperty(value = "目标群体", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_survey_people")
private String targetGroup;
/**
* 开始时间
*/
@ExcelProperty(value = "开始时间")
private Date startTime;
/**
* 结束时间
*/
@ExcelProperty(value = "结束时间")
private Date endTime;
/**
* 最大提交次数
*/
@ExcelProperty(value = "最大提交次数")
private Long maxSubmitTimes;
/**
* 是否匿名
*/
@ExcelProperty(value = "是否匿名", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_yesno")
private Long isAnonymous;
/**
* 是否发布
*/
@ExcelProperty(value = "是否发布", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_yesno")
private Long isPublished;
/**
* 状态
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_survey_status")
private String status;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqHallInfo;
import org.dromara.zhxy.domain.vo.HqHallInfoVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 报告厅信息Mapper接口
*
* @author wuzilong
* @date 2025-06-23
*/
public interface HqHallInfoMapper extends BaseMapperPlus<HqHallInfo, HqHallInfoVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqHallReservation;
import org.dromara.zhxy.domain.vo.HqHallReservationVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 报告厅预约Mapper接口
*
* @author wuzilong
* @date 2025-06-23
*/
public interface HqHallReservationMapper extends BaseMapperPlus<HqHallReservation, HqHallReservationVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqSatCategory;
import org.dromara.zhxy.domain.vo.HqSatCategoryVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 题目分类Mapper接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface HqSatCategoryMapper extends BaseMapperPlus<HqSatCategory, HqSatCategoryVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqSatDetail;
import org.dromara.zhxy.domain.vo.HqSatDetailVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 答题详情Mapper接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface HqSatDetailMapper extends BaseMapperPlus<HqSatDetail, HqSatDetailVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqSatOption;
import org.dromara.zhxy.domain.vo.HqSatOptionVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 评分选项Mapper接口
*
* @author Lion Li
* @date 2025-06-24
*/
public interface HqSatOptionMapper extends BaseMapperPlus<HqSatOption, HqSatOptionVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqSatQuestion;
import org.dromara.zhxy.domain.vo.HqSatQuestionVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 题库Mapper接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface HqSatQuestionMapper extends BaseMapperPlus<HqSatQuestion, HqSatQuestionVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqSatRecord;
import org.dromara.zhxy.domain.vo.HqSatRecordVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 答题记录Mapper接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface HqSatRecordMapper extends BaseMapperPlus<HqSatRecord, HqSatRecordVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.zhxy.mapper;
import org.dromara.zhxy.domain.HqSatSurvey;
import org.dromara.zhxy.domain.vo.HqSatSurveyVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 问卷Mapper接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface HqSatSurveyMapper extends BaseMapperPlus<HqSatSurvey, HqSatSurveyVo> {
}
@@ -0,0 +1,26 @@
package org.dromara.zhxy.mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.zhxy.domain.HqSatSurveyQuestion;
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
/**
* 问卷题目关联Mapper接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface HqSatSurveyQuestionMapper extends BaseMapperPlus<HqSatSurveyQuestion, HqSatSurveyQuestionVo> {
/**
* 联表查询问卷题目详情
*
* @param surveyId 问卷ID
* @return 问卷题目详情列表
*/
List<HqSatSurveyQuestionVo> selectSurveyQuestionsWithDetail(@Param("surveyId") Long surveyId);
}
@@ -0,0 +1,68 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqHallInfoVo;
import org.dromara.zhxy.domain.bo.HqHallInfoBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 报告厅信息Service接口
*
* @author wuzilong
* @date 2025-06-23
*/
public interface IHqHallInfoService {
/**
* 查询报告厅信息
*
* @param id 主键
* @return 报告厅信息
*/
HqHallInfoVo queryById(Long id);
/**
* 分页查询报告厅信息列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 报告厅信息分页列表
*/
TableDataInfo<HqHallInfoVo> queryPageList(HqHallInfoBo bo, PageQuery pageQuery);
/**
* 查询符合条件的报告厅信息列表
*
* @param bo 查询条件
* @return 报告厅信息列表
*/
List<HqHallInfoVo> queryList(HqHallInfoBo bo);
/**
* 新增报告厅信息
*
* @param bo 报告厅信息
* @return 是否新增成功
*/
Boolean insertByBo(HqHallInfoBo bo);
/**
* 修改报告厅信息
*
* @param bo 报告厅信息
* @return 是否修改成功
*/
Boolean updateByBo(HqHallInfoBo bo);
/**
* 校验并批量删除报告厅信息信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,77 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqHallReservationVo;
import org.dromara.zhxy.domain.bo.HqHallReservationBo;
import org.dromara.zhxy.domain.bo.HqHallReservationAuditBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 报告厅预约Service接口
*
* @author wuzilong
* @date 2025-06-23
*/
public interface IHqHallReservationService {
/**
* 查询报告厅预约
*
* @param id 主键
* @return 报告厅预约
*/
HqHallReservationVo queryById(Long id);
/**
* 分页查询报告厅预约列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 报告厅预约分页列表
*/
TableDataInfo<HqHallReservationVo> queryPageList(HqHallReservationBo bo, PageQuery pageQuery);
/**
* 查询符合条件的报告厅预约列表
*
* @param bo 查询条件
* @return 报告厅预约列表
*/
List<HqHallReservationVo> queryList(HqHallReservationBo bo);
/**
* 新增报告厅预约
*
* @param bo 报告厅预约
* @return 是否新增成功
*/
Boolean insertByBo(HqHallReservationBo bo);
/**
* 修改报告厅预约
*
* @param bo 报告厅预约
* @return 是否修改成功
*/
Boolean updateByBo(HqHallReservationBo bo);
/**
* 校验并批量删除报告厅预约信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 批量审核报告厅预约
*
* @param auditBo 审核信息
* @return 是否审核成功
*/
Boolean auditByIds(HqHallReservationAuditBo auditBo);
}
@@ -0,0 +1,68 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatCategoryVo;
import org.dromara.zhxy.domain.bo.HqSatCategoryBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 题目分类Service接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface IHqSatCategoryService {
/**
* 查询题目分类
*
* @param id 主键
* @return 题目分类
*/
HqSatCategoryVo queryById(Long id);
/**
* 分页查询题目分类列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 题目分类分页列表
*/
TableDataInfo<HqSatCategoryVo> queryPageList(HqSatCategoryBo bo, PageQuery pageQuery);
/**
* 查询符合条件的题目分类列表
*
* @param bo 查询条件
* @return 题目分类列表
*/
List<HqSatCategoryVo> queryList(HqSatCategoryBo bo);
/**
* 新增题目分类
*
* @param bo 题目分类
* @return 是否新增成功
*/
Boolean insertByBo(HqSatCategoryBo bo);
/**
* 修改题目分类
*
* @param bo 题目分类
* @return 是否修改成功
*/
Boolean updateByBo(HqSatCategoryBo bo);
/**
* 校验并批量删除题目分类信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,68 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatDetailVo;
import org.dromara.zhxy.domain.bo.HqSatDetailBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 答题详情Service接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface IHqSatDetailService {
/**
* 查询答题详情
*
* @param id 主键
* @return 答题详情
*/
HqSatDetailVo queryById(Long id);
/**
* 分页查询答题详情列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 答题详情分页列表
*/
TableDataInfo<HqSatDetailVo> queryPageList(HqSatDetailBo bo, PageQuery pageQuery);
/**
* 查询符合条件的答题详情列表
*
* @param bo 查询条件
* @return 答题详情列表
*/
List<HqSatDetailVo> queryList(HqSatDetailBo bo);
/**
* 新增答题详情
*
* @param bo 答题详情
* @return 是否新增成功
*/
Boolean insertByBo(HqSatDetailBo bo);
/**
* 修改答题详情
*
* @param bo 答题详情
* @return 是否修改成功
*/
Boolean updateByBo(HqSatDetailBo bo);
/**
* 校验并批量删除答题详情信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,68 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatOptionVo;
import org.dromara.zhxy.domain.bo.HqSatOptionBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 评分选项Service接口
*
* @author Lion Li
* @date 2025-06-24
*/
public interface IHqSatOptionService {
/**
* 查询评分选项
*
* @param id 主键
* @return 评分选项
*/
HqSatOptionVo queryById(Long id);
/**
* 分页查询评分选项列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 评分选项分页列表
*/
TableDataInfo<HqSatOptionVo> queryPageList(HqSatOptionBo bo, PageQuery pageQuery);
/**
* 查询符合条件的评分选项列表
*
* @param bo 查询条件
* @return 评分选项列表
*/
List<HqSatOptionVo> queryList(HqSatOptionBo bo);
/**
* 新增评分选项
*
* @param bo 评分选项
* @return 是否新增成功
*/
Boolean insertByBo(HqSatOptionBo bo);
/**
* 修改评分选项
*
* @param bo 评分选项
* @return 是否修改成功
*/
Boolean updateByBo(HqSatOptionBo bo);
/**
* 校验并批量删除评分选项信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,68 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatQuestionVo;
import org.dromara.zhxy.domain.bo.HqSatQuestionBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 题库Service接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface IHqSatQuestionService {
/**
* 查询题库
*
* @param id 主键
* @return 题库
*/
HqSatQuestionVo queryById(Long id);
/**
* 分页查询题库列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 题库分页列表
*/
TableDataInfo<HqSatQuestionVo> queryPageList(HqSatQuestionBo bo, PageQuery pageQuery);
/**
* 查询符合条件的题库列表
*
* @param bo 查询条件
* @return 题库列表
*/
List<HqSatQuestionVo> queryList(HqSatQuestionBo bo);
/**
* 新增题库
*
* @param bo 题库
* @return 是否新增成功
*/
Boolean insertByBo(HqSatQuestionBo bo);
/**
* 修改题库
*
* @param bo 题库
* @return 是否修改成功
*/
Boolean updateByBo(HqSatQuestionBo bo);
/**
* 校验并批量删除题库信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,68 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatRecordVo;
import org.dromara.zhxy.domain.bo.HqSatRecordBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 答题记录Service接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface IHqSatRecordService {
/**
* 查询答题记录
*
* @param id 主键
* @return 答题记录
*/
HqSatRecordVo queryById(Long id);
/**
* 分页查询答题记录列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 答题记录分页列表
*/
TableDataInfo<HqSatRecordVo> queryPageList(HqSatRecordBo bo, PageQuery pageQuery);
/**
* 查询符合条件的答题记录列表
*
* @param bo 查询条件
* @return 答题记录列表
*/
List<HqSatRecordVo> queryList(HqSatRecordBo bo);
/**
* 新增答题记录
*
* @param bo 答题记录
* @return 是否新增成功
*/
Boolean insertByBo(HqSatRecordBo bo);
/**
* 修改答题记录
*
* @param bo 答题记录
* @return 是否修改成功
*/
Boolean updateByBo(HqSatRecordBo bo);
/**
* 校验并批量删除答题记录信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,76 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
import org.dromara.zhxy.domain.bo.HqSatSurveyQuestionBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 问卷题目关联Service接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface IHqSatSurveyQuestionService {
/**
* 查询问卷题目关联
*
* @param id 主键
* @return 问卷题目关联
*/
HqSatSurveyQuestionVo queryById(Long id);
/**
* 分页查询问卷题目关联列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 问卷题目关联分页列表
*/
TableDataInfo<HqSatSurveyQuestionVo> queryPageList(HqSatSurveyQuestionBo bo, PageQuery pageQuery);
/**
* 查询符合条件的问卷题目关联列表
*
* @param bo 查询条件
* @return 问卷题目关联列表
*/
List<HqSatSurveyQuestionVo> queryList(HqSatSurveyQuestionBo bo);
/**
* 新增问卷题目关联
*
* @param bo 问卷题目关联
* @return 是否新增成功
*/
Boolean insertByBo(HqSatSurveyQuestionBo bo);
/**
* 修改问卷题目关联
*
* @param bo 问卷题目关联
* @return 是否修改成功
*/
Boolean updateByBo(HqSatSurveyQuestionBo bo);
/**
* 校验并批量删除问卷题目关联信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取问卷关联的题目详情列表(包含题目详细信息)
*
* @param surveyId 问卷ID
* @return 题目详情列表
*/
List<HqSatSurveyQuestionVo> getSurveyQuestionsWithDetail(Long surveyId);
}
@@ -0,0 +1,102 @@
package org.dromara.zhxy.service;
import org.dromara.zhxy.domain.vo.HqSatSurveyVo;
import org.dromara.zhxy.domain.bo.HqSatSurveyBo;
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 问卷Service接口
*
* @author wuzilong
* @date 2025-06-24
*/
public interface IHqSatSurveyService {
/**
* 查询问卷
*
* @param id 主键
* @return 问卷
*/
HqSatSurveyVo queryById(Long id);
/**
* 分页查询问卷列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 问卷分页列表
*/
TableDataInfo<HqSatSurveyVo> queryPageList(HqSatSurveyBo bo, PageQuery pageQuery);
/**
* 查询符合条件的问卷列表
*
* @param bo 查询条件
* @return 问卷列表
*/
List<HqSatSurveyVo> queryList(HqSatSurveyBo bo);
/**
* 新增问卷
*
* @param bo 问卷
* @return 是否新增成功
*/
Boolean insertByBo(HqSatSurveyBo bo);
/**
* 修改问卷
*
* @param bo 问卷
* @return 是否修改成功
*/
Boolean updateByBo(HqSatSurveyBo bo);
/**
* 校验并批量删除问卷信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取问卷关联的题目列表
*
* @param surveyId 问卷ID
* @return 题目列表
*/
List<HqSatSurveyQuestionVo> getSurveyQuestions(Long surveyId);
/**
* 批量更新问卷题目关联
*
* @param surveyId 问卷ID
* @param questions 题目列表
* @return 是否更新成功
*/
Boolean updateSurveyQuestions(Long surveyId, List<Object> questions);
/**
* 发布问卷
*
* @param id 问卷ID
* @return 是否发布成功
*/
Boolean publishSurvey(Long id);
/**
* 撤销发布问卷
*
* @param id 问卷ID
* @return 是否撤销成功
*/
Boolean unpublishSurvey(Long id);
}
@@ -0,0 +1,133 @@
package org.dromara.zhxy.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.zhxy.domain.bo.HqHallInfoBo;
import org.dromara.zhxy.domain.vo.HqHallInfoVo;
import org.dromara.zhxy.domain.HqHallInfo;
import org.dromara.zhxy.mapper.HqHallInfoMapper;
import org.dromara.zhxy.service.IHqHallInfoService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 报告厅信息Service业务层处理
*
* @author wuzilong
* @date 2025-06-23
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class HqHallInfoServiceImpl implements IHqHallInfoService {
private final HqHallInfoMapper baseMapper;
/**
* 查询报告厅信息
*
* @param id 主键
* @return 报告厅信息
*/
@Override
public HqHallInfoVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 分页查询报告厅信息列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 报告厅信息分页列表
*/
@Override
public TableDataInfo<HqHallInfoVo> queryPageList(HqHallInfoBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HqHallInfo> lqw = buildQueryWrapper(bo);
Page<HqHallInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的报告厅信息列表
*
* @param bo 查询条件
* @return 报告厅信息列表
*/
@Override
public List<HqHallInfoVo> queryList(HqHallInfoBo bo) {
LambdaQueryWrapper<HqHallInfo> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<HqHallInfo> buildQueryWrapper(HqHallInfoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HqHallInfo> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HqHallInfo::getCreateTime);
lqw.like(StringUtils.isNotBlank(bo.getHallName()), HqHallInfo::getHallName, bo.getHallName());
lqw.eq(StringUtils.isNotBlank(bo.getHallStatus()), HqHallInfo::getHallStatus, bo.getHallStatus());
return lqw;
}
/**
* 新增报告厅信息
*
* @param bo 报告厅信息
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(HqHallInfoBo bo) {
HqHallInfo add = MapstructUtils.convert(bo, HqHallInfo.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改报告厅信息
*
* @param bo 报告厅信息
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(HqHallInfoBo bo) {
HqHallInfo update = MapstructUtils.convert(bo, HqHallInfo.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(HqHallInfo entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除报告厅信息信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}
@@ -0,0 +1,226 @@
package org.dromara.zhxy.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.zhxy.domain.bo.HqHallReservationBo;
import org.dromara.zhxy.domain.bo.HqHallReservationAuditBo;
import org.dromara.zhxy.domain.vo.HqHallReservationVo;
import org.dromara.zhxy.domain.HqHallReservation;
import org.dromara.zhxy.mapper.HqHallReservationMapper;
import org.dromara.zhxy.service.IHqHallReservationService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
import org.dromara.common.satoken.utils.LoginHelper;
/**
* 报告厅预约Service业务层处理
*
* @author wuzilong
* @date 2025-06-23
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class HqHallReservationServiceImpl implements IHqHallReservationService {
private final HqHallReservationMapper baseMapper;
/**
* 查询报告厅预约
*
* @param id 主键
* @return 报告厅预约
*/
@Override
public HqHallReservationVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 分页查询报告厅预约列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 报告厅预约分页列表
*/
@Override
public TableDataInfo<HqHallReservationVo> queryPageList(HqHallReservationBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HqHallReservation> lqw = buildQueryWrapper(bo);
Page<HqHallReservationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的报告厅预约列表
*
* @param bo 查询条件
* @return 报告厅预约列表
*/
@Override
public List<HqHallReservationVo> queryList(HqHallReservationBo bo) {
LambdaQueryWrapper<HqHallReservation> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<HqHallReservation> buildQueryWrapper(HqHallReservationBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HqHallReservation> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HqHallReservation::getCreateTime);
// 调试日志:记录查询条件
log.info("报告厅预约查询条件 - applicantId: {}, hallId: {}, hallName: {}, status: {}",
bo.getApplicantId(), bo.getHallId(), bo.getHallName(), bo.getStatus());
lqw.eq(bo.getHallId() != null, HqHallReservation::getHallId, bo.getHallId());
lqw.like(StringUtils.isNotBlank(bo.getHallName()), HqHallReservation::getHallName, bo.getHallName());
// 处理时间查询参数:优先使用字符串格式的时间参数
Date startTimeQuery = null;
Date endTimeQuery = null;
// 如果有字符串格式的时间参数,优先使用
if (StringUtils.isNotBlank(bo.getStartTimeStr())) {
startTimeQuery = parseDateTime(bo.getStartTimeStr());
} else if (bo.getStartTime() != null) {
startTimeQuery = bo.getStartTime();
}
if (StringUtils.isNotBlank(bo.getEndTimeStr())) {
endTimeQuery = parseDateTime(bo.getEndTimeStr());
} else if (bo.getEndTime() != null) {
endTimeQuery = bo.getEndTime();
}
// 时间范围查询逻辑:
// 如果用户选择了开始时间,查找预约开始时间 >= 用户选择时间的记录
// 这样查询出的记录的开始时间都会比用户选择的时间晚或相等(用户选择的时间比数据库中的早)
lqw.ge(startTimeQuery != null, HqHallReservation::getStartTime, startTimeQuery);
// 如果用户选择了结束时间,查找预约结束时间 <= 用户选择时间的记录
// 这样查询出的记录的结束时间都会比用户选择的时间早或相等(用户选择的时间比数据库中的晚)
lqw.le(endTimeQuery != null, HqHallReservation::getEndTime, endTimeQuery);
lqw.eq(bo.getApplicantId() != null, HqHallReservation::getApplicantId, bo.getApplicantId());
lqw.like(StringUtils.isNotBlank(bo.getApplicantName()), HqHallReservation::getApplicantName,
bo.getApplicantName());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), HqHallReservation::getStatus, bo.getStatus());
return lqw;
}
/**
* 解析日期时间字符串
*/
private Date parseDateTime(String dateTimeStr) {
if (StringUtils.isBlank(dateTimeStr)) {
return null;
}
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(dateTimeStr);
} catch (ParseException e) {
log.warn("日期格式解析失败: {}", dateTimeStr, e);
return null;
}
}
/**
* 新增报告厅预约
*
* @param bo 报告厅预约
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(HqHallReservationBo bo) {
HqHallReservation add = MapstructUtils.convert(bo, HqHallReservation.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改报告厅预约
*
* @param bo 报告厅预约
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(HqHallReservationBo bo) {
HqHallReservation update = MapstructUtils.convert(bo, HqHallReservation.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(HqHallReservation entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除报告厅预约信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 批量审核报告厅预约
*
* @param auditBo 审核信息
* @return 是否审核成功
*/
@Override
public Boolean auditByIds(HqHallReservationAuditBo auditBo) {
log.info("开始审核报告厅预约,审核信息:{}", auditBo);
// 获取当前用户ID作为审核人
Long currentUserId = LoginHelper.getUserId();
String currentUserName = LoginHelper.getLoginUser().getNickname();
Date auditTime = new Date();
// 构建更新条件
List<HqHallReservation> updateList = auditBo.getIds().stream()
.map(id -> {
HqHallReservation entity = new HqHallReservation();
entity.setId(id);
entity.setStatus(auditBo.getStatus());
entity.setAuditBy(currentUserId);
entity.setAuditName(currentUserName);
entity.setAuditTime(auditTime);
entity.setAuditRemark(auditBo.getAuditRemark());
return entity;
})
.toList();
// 批量更新
boolean result = baseMapper.updateBatchById(updateList);
log.info("审核完成,审核状态:{},影响记录数:{}", auditBo.getStatus(), updateList.size());
return result;
}
}
@@ -0,0 +1,137 @@
package org.dromara.zhxy.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.zhxy.domain.bo.HqSatCategoryBo;
import org.dromara.zhxy.domain.vo.HqSatCategoryVo;
import org.dromara.zhxy.domain.HqSatCategory;
import org.dromara.zhxy.mapper.HqSatCategoryMapper;
import org.dromara.zhxy.service.IHqSatCategoryService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 题目分类Service业务层处理
*
* @author wuzilong
* @date 2025-06-24
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class HqSatCategoryServiceImpl implements IHqSatCategoryService {
private final HqSatCategoryMapper baseMapper;
/**
* 查询题目分类
*
* @param id 主键
* @return 题目分类
*/
@Override
public HqSatCategoryVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询题目分类列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 题目分类分页列表
*/
@Override
public TableDataInfo<HqSatCategoryVo> queryPageList(HqSatCategoryBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HqSatCategory> lqw = buildQueryWrapper(bo);
Page<HqSatCategoryVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的题目分类列表
*
* @param bo 查询条件
* @return 题目分类列表
*/
@Override
public List<HqSatCategoryVo> queryList(HqSatCategoryBo bo) {
LambdaQueryWrapper<HqSatCategory> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<HqSatCategory> buildQueryWrapper(HqSatCategoryBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HqSatCategory> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(HqSatCategory::getId);
lqw.eq(bo.getDeleted() != null, HqSatCategory::getDeleted, bo.getDeleted());
lqw.eq(StringUtils.isNotBlank(bo.getCategoryCode()), HqSatCategory::getCategoryCode, bo.getCategoryCode());
lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), HqSatCategory::getCategoryName, bo.getCategoryName());
lqw.eq(StringUtils.isNotBlank(bo.getCategoryDesc()), HqSatCategory::getCategoryDesc, bo.getCategoryDesc());
lqw.eq(bo.getSortOrder() != null, HqSatCategory::getSortOrder, bo.getSortOrder());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), HqSatCategory::getStatus, bo.getStatus());
return lqw;
}
/**
* 新增题目分类
*
* @param bo 题目分类
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(HqSatCategoryBo bo) {
HqSatCategory add = MapstructUtils.convert(bo, HqSatCategory.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改题目分类
*
* @param bo 题目分类
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(HqSatCategoryBo bo) {
HqSatCategory update = MapstructUtils.convert(bo, HqSatCategory.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(HqSatCategory entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除题目分类信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}
Loaded 100 of 1319 files, more files were not shown because too many files have changed in this diff. Show more