fix: [临时提交]
This commit is contained in:
parent
37a7af487b
commit
bebac77236
|
@ -0,0 +1,61 @@
|
|||
import request from '@/utils/request'
|
||||
import type { Exam } from '@/types/api'
|
||||
|
||||
// Define interface for query parameters
|
||||
export interface ExamQueryParams {
|
||||
page?: number
|
||||
size?: number
|
||||
title?: string
|
||||
timeLimit?: number
|
||||
}
|
||||
|
||||
// Define interface for the paginated response
|
||||
export interface ExamListResponse {
|
||||
list: Exam[]
|
||||
total: number
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
// Get exam list (paginated)
|
||||
export function getExamList(params: ExamQueryParams) {
|
||||
return request<ExamListResponse>({
|
||||
url: '/admin/exams',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// Get single exam details
|
||||
export function getExam(id: string) {
|
||||
return request<Exam>({
|
||||
url: `/admin/exams/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// Create a new exam
|
||||
export function createExam(data: Partial<Exam>) {
|
||||
return request({
|
||||
url: '/admin/exams',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// Update an existing exam
|
||||
export function updateExam(id: string, data: Partial<Exam>) {
|
||||
return request({
|
||||
url: `/admin/exams/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// Delete an exam
|
||||
export function deleteExam(id: string) {
|
||||
return request({
|
||||
url: `/admin/exams/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
|
@ -4,6 +4,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue'
|
|||
import DashboardView from '@/views/DashboardView.vue'
|
||||
import CategoryListView from '@/views/category/CategoryListView.vue'
|
||||
import CourseListView from '@/views/course/CourseListView.vue'
|
||||
import ExamListView from '@/views/exam/index.vue'
|
||||
|
||||
// Restore the routes array definition
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
|
@ -29,6 +30,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||
name: 'CourseList',
|
||||
component: CourseListView,
|
||||
meta: { title: '课程管理', icon: 'Reading' }
|
||||
},
|
||||
{
|
||||
path: 'exams',
|
||||
name: 'ExamList',
|
||||
component: ExamListView,
|
||||
meta: { title: '考试管理', icon: 'Document' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1 +1,11 @@
|
|||
|
||||
// Exam entity type definition
|
||||
export interface Exam {
|
||||
id: string
|
||||
title: string
|
||||
timeLimit: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
totalTime: number
|
||||
totalScore: number
|
||||
qualifyScore: number
|
||||
}
|
|
@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus'
|
|||
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // API 基础路径,稍后在 .env 文件中配置
|
||||
baseURL: 'http://localhost:8084' || '/api', // API 基础路径,稍后在 .env 文件中配置
|
||||
timeout: 10000, // 请求超时时间
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
<template>
|
||||
<div class="exam-container">
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="考试名称">
|
||||
<el-input v-model="queryParams.title" placeholder="请输入考试名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否限时">
|
||||
<el-select v-model="queryParams.timeLimit" placeholder="请选择" clearable>
|
||||
<el-option :value="1" label="是" />
|
||||
<el-option :value="0" label="否" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="action-container">
|
||||
<el-button type="primary" @click="handleAdd">新增考试</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="examList" border style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="150" />
|
||||
<el-table-column prop="title" label="考试名称" min-width="120" />
|
||||
<el-table-column label="是否限时" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.timeLimit === 1 ? '是' : '否' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考试时间" min-width="280">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.timeLimit === 1">
|
||||
{{ formatDateTime(scope.row.startTime) }} 至 {{ formatDateTime(scope.row.endTime) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ scope.row.totalTime }} 分钟
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalScore" label="考试总分" width="100" />
|
||||
<el-table-column prop="qualifyScore" label="及格分数" width="100" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.size"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑对话框 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="examFormRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="考试名称" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入考试名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否限时" prop="timeLimit">
|
||||
<el-radio-group v-model="formData.timeLimit">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<template v-if="formData.timeLimit === 1">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
v-model="formData.startTime"
|
||||
type="datetime"
|
||||
placeholder="选择开始时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
v-model="formData.endTime"
|
||||
type="datetime"
|
||||
placeholder="选择结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="考试时长(分钟)" prop="totalTime">
|
||||
<el-input-number v-model="formData.totalTime" :min="1" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="考试总分" prop="totalScore">
|
||||
<el-input-number v-model="formData.totalScore" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="及格分数" prop="qualifyScore">
|
||||
<el-input-number
|
||||
v-model="formData.qualifyScore"
|
||||
:min="1"
|
||||
:max="formData.totalScore || 100"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { getExamList, getExam, createExam, updateExam, deleteExam } from '@/api/exam'
|
||||
import type { Exam, ExamQueryParams } from '@/api/exam'
|
||||
|
||||
// 状态定义
|
||||
const loading = ref(false)
|
||||
const examList = ref<Exam[]>([])
|
||||
const total = ref(0)
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const examFormRef = ref<FormInstance>()
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<ExamQueryParams>({
|
||||
page: 1,
|
||||
size: 10,
|
||||
title: '',
|
||||
timeLimit: undefined
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const defaultFormData = {
|
||||
id: '',
|
||||
title: '',
|
||||
timeLimit: 1,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
totalTime: 90,
|
||||
totalScore: 100,
|
||||
qualifyScore: 60
|
||||
}
|
||||
const formData = reactive<Partial<Exam>>({ ...defaultFormData })
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive({
|
||||
title: [{ required: true, message: '请输入考试名称', trigger: 'blur' }],
|
||||
timeLimit: [{ required: true, message: '请选择是否限时', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
totalTime: [{ required: true, message: '请输入考试时长', trigger: 'blur' }],
|
||||
totalScore: [{ required: true, message: '请输入考试总分', trigger: 'blur' }],
|
||||
qualifyScore: [{ required: true, message: '请输入及格分数', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const dialogTitle = computed(() => {
|
||||
return dialogType.value === 'add' ? '添加考试' : '编辑考试'
|
||||
})
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
fetchExamList()
|
||||
})
|
||||
|
||||
// 方法定义
|
||||
const fetchExamList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getExamList(queryParams)
|
||||
examList.value = res.list
|
||||
total.value = res.total
|
||||
} catch (error) {
|
||||
console.error('获取考试列表失败', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
queryParams.page = 1
|
||||
fetchExamList()
|
||||
}
|
||||
|
||||
const resetQuery = () => {
|
||||
queryParams.title = ''
|
||||
queryParams.timeLimit = undefined
|
||||
queryParams.page = 1
|
||||
fetchExamList()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
queryParams.size = size
|
||||
fetchExamList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
queryParams.page = page
|
||||
fetchExamList()
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
dialogType.value = 'add'
|
||||
Object.assign(formData, defaultFormData)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = async (row: Exam) => {
|
||||
dialogType.value = 'edit'
|
||||
dialogVisible.value = true
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getExam(row.id)
|
||||
Object.assign(formData, res)
|
||||
} catch (error) {
|
||||
console.error('获取考试详情失败', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (row: Exam) => {
|
||||
ElMessageBox.confirm(
|
||||
`确认删除考试 "${row.title}" 吗?`,
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
await deleteExam(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchExamList()
|
||||
} catch (error) {
|
||||
console.error('删除失败', error)
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!examFormRef.value) return
|
||||
|
||||
await examFormRef.value.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
try {
|
||||
if (dialogType.value === 'add') {
|
||||
await createExam(formData)
|
||||
ElMessage.success('添加成功')
|
||||
} else {
|
||||
await updateExam(formData.id as string, formData)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchExamList()
|
||||
} catch (error) {
|
||||
console.error('保存失败', error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formatDateTime = (time: string) => {
|
||||
if (!time) return ''
|
||||
const date = new Date(time)
|
||||
return date.toLocaleString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.exam-container {
|
||||
padding: 20px;
|
||||
}
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.action-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
11
pom.xml
11
pom.xml
|
@ -484,6 +484,12 @@
|
|||
</exclusions>-->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.0.0-beta2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
|
@ -507,6 +513,11 @@
|
|||
<version>4.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
package com.guwan.backend.Handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
import com.guwan.backend.annotation.RecoverIfDeleted;
|
||||
import com.guwan.backend.util.ReflectUtil;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
|
||||
System.out.println("metaObject = " + metaObject);
|
||||
|
||||
this.strictInsertFill(metaObject, "createdTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updatedTime", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
|
@ -18,4 +29,6 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
|
|||
this.strictUpdateFill(metaObject, "lastLoginTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictUpdateFill(metaObject, "updatedTime", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -11,18 +11,42 @@ import java.io.InputStream;
|
|||
import java.net.URL;
|
||||
|
||||
public class VideoDuration {
|
||||
// public static void main(String[] args) {
|
||||
// /* String preSignedUrl = "http://localhost:9000/videos/ffffad37-9804-4765-ae18-3f8dcda9bea8.mp4"; // Minio 预签名 URL
|
||||
//
|
||||
// try (InputStream stream = new URL(preSignedUrl).openStream()) {
|
||||
// Metadata metadata = new Metadata();
|
||||
// Parser parser = new AutoDetectParser();
|
||||
// parser.parse(stream, new BodyContentHandler(), metadata, new ParseContext());
|
||||
//
|
||||
// String duration = metadata.get("duration");
|
||||
// System.out.println("视频时长(毫秒): " + duration);
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }*/
|
||||
// int i = 0;
|
||||
// while (i< 5){
|
||||
// if (i ==3){
|
||||
// i++;
|
||||
// continue;
|
||||
// }
|
||||
// System.out.println(i);
|
||||
// i++;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
static boolean foo(char x) {
|
||||
System.out.print(x);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String preSignedUrl = "http://localhost:9000/videos/ffffad37-9804-4765-ae18-3f8dcda9bea8.mp4"; // Minio 预签名 URL
|
||||
|
||||
try (InputStream stream = new URL(preSignedUrl).openStream()) {
|
||||
Metadata metadata = new Metadata();
|
||||
Parser parser = new AutoDetectParser();
|
||||
parser.parse(stream, new BodyContentHandler(), metadata, new ParseContext());
|
||||
|
||||
String duration = metadata.get("duration");
|
||||
System.out.println("视频时长(毫秒): " + duration);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
int i = 0;
|
||||
for (foo('A'); foo('B') && (i < 2); foo('C')) {
|
||||
i++;
|
||||
foo('D');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.guwan.backend.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD) // 这个注解应用于字段
|
||||
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效
|
||||
public @interface CheckExistence {
|
||||
String message() default "Field value already exists"; // 错误提示消息
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.guwan.backend.annotation;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import com.guwan.backend.annotation.CheckExistence;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class CheckExistenceProcessor {
|
||||
|
||||
// 检查该字段值是否在数据库中存在
|
||||
public static <T> boolean checkFieldExistence(T entity, IService<T> service) throws Exception {
|
||||
// 获取实体类的所有字段
|
||||
Field[] fields = entity.getClass().getDeclaredFields();
|
||||
|
||||
for (Field field : fields) {
|
||||
// 如果字段上有 @CheckExistence 注解
|
||||
if (field.isAnnotationPresent(CheckExistence.class)) {
|
||||
field.setAccessible(true); // 设置字段可访问
|
||||
Object value = field.get(entity); // 获取字段值
|
||||
|
||||
// 如果字段值为空,不进行检查
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取字段的名称
|
||||
String fieldName = field.getName();
|
||||
|
||||
// 使用 MyBatis-Plus 查询是否已存在该值
|
||||
LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 将字段通过 SFunction 引入
|
||||
SFunction<T, ?> fieldExpression = getFieldExpression(entity, fieldName);
|
||||
|
||||
if (fieldExpression != null) {
|
||||
queryWrapper.eq(fieldExpression, value);
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
long count = service.count(queryWrapper); // 使用 count 而不是 list,避免不必要的数据加载
|
||||
if (count > 0) {
|
||||
// 如果数据库中已经存在该值,返回 true
|
||||
System.out.println("Error: " + fieldName + " value already exists.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // 没有找到重复值
|
||||
}
|
||||
|
||||
// 通过反射获取字段的 SFunction 表达式
|
||||
private static <T> SFunction<T, ?> getFieldExpression(T entity, String fieldName) {
|
||||
try {
|
||||
// 根据字段名生成 SFunction 表达式
|
||||
return (SFunction<T, ?>) entity.getClass().getDeclaredField(fieldName).get(null);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.guwan.backend.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RecoverIfDeleted {
|
||||
String value() default "deleted"; // 逻辑删除字段
|
||||
String businessKey() default "uniqueKey"; // 业务唯一键
|
||||
}
|
|
@ -115,13 +115,13 @@ public class OperationLogAspect {
|
|||
*/
|
||||
private String getIpAddress(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
// 多个代理的情况,第一个IP为客户端真实IP
|
||||
|
|
|
@ -20,7 +20,6 @@ public interface SimpleTTSClient {
|
|||
@PostMapping(value = "/v1/audio/speech")
|
||||
byte[] saveAudio(String jsonInputString);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import com.guwan.backend.store.PersistentChatMemoryStore;
|
||||
import dev.langchain4j.memory.ChatMemory;
|
||||
import dev.langchain4j.memory.chat.ChatMemoryProvider;
|
||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
||||
import dev.langchain4j.model.chat.ChatLanguageModel;
|
||||
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import dev.langchain4j.service.MemoryId;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import dev.langchain4j.service.UserMessage;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class Aiconfig {
|
||||
public interface Assistant {
|
||||
String chat(@MemoryId String memoryId, @UserMessage String message);
|
||||
|
||||
// 流式响应
|
||||
TokenStream stream(@MemoryId String memoryId, @UserMessage String message);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Assistant assistant(ChatLanguageModel qwenchatModel,
|
||||
StreamingChatLanguageModel qwenstreamingchatModel) {
|
||||
|
||||
|
||||
PersistentChatMemoryStore store = new PersistentChatMemoryStore();
|
||||
|
||||
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
|
||||
.id(memoryId)
|
||||
.maxMessages(10)
|
||||
.chatMemoryStore(store)
|
||||
.build();
|
||||
|
||||
return AiServices.builder(Assistant.class)
|
||||
.chatLanguageModel(qwenchatModel)
|
||||
.streamingChatLanguageModel(qwenstreamingchatModel)
|
||||
.chatMemoryProvider(memoryId ->
|
||||
MessageWindowChatMemory.builder().maxMessages(10)
|
||||
.id(memoryId).build()
|
||||
)
|
||||
.chatMemoryProvider(chatMemoryProvider)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -11,8 +11,6 @@ public class CorsConfig {
|
|||
|
||||
//get请求变成了options 后端预检过不了怎么改
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
|
|
@ -23,7 +23,7 @@ public class MybatisPlusConfig {
|
|||
return new MyMetaObjectHandler();
|
||||
}
|
||||
|
||||
//mybatisplus分页流程为:全量查询 =》本地分页
|
||||
//mybatisplus分页流程为:全量查询 => 本地分页
|
||||
// 而如果不对查询结果进行拦截,mybatis将不能执行分页操作,自然也拿不到页数和总数的数据了。
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Ac{
|
||||
List<String> ids;
|
||||
String str;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
|
||||
|
||||
import com.guwan.backend.annotation.OperationLog;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.pojo.entity.BaseExam;
|
||||
import com.guwan.backend.service.BaseExamService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* (BaseExam)表控制层
|
||||
*
|
||||
* @author Guwan
|
||||
* @since 2025-04-19 22:54:02
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("baseExam")
|
||||
public class BaseExamController {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@Autowired
|
||||
private BaseExamService baseExamService;
|
||||
|
||||
@OperationLog
|
||||
@GetMapping
|
||||
public Result queryByPage(@RequestParam(name = "page") Integer page,
|
||||
@RequestParam(name = "page") Integer pageSize,
|
||||
@RequestParam(name = "query") String query) {
|
||||
|
||||
return Result.success(baseExamService.queryByPage(page, pageSize, query));
|
||||
|
||||
// return Result.success();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@GetMapping("{id}")
|
||||
public Result<BaseExam> queryById(@PathVariable("id") String id) {
|
||||
return Result.success(this.baseExamService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param baseExam 实体
|
||||
* @return 新增结果
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<BaseExam> add(BaseExam baseExam) {
|
||||
this.baseExamService.save(baseExam);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑数据
|
||||
*
|
||||
* @param baseExam 实体
|
||||
* @return 编辑结果
|
||||
*/
|
||||
@PutMapping
|
||||
public Result<BaseExam> edit(BaseExam baseExam) {
|
||||
//return Result.success(this.baseExamService.update(baseExam));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 删除是否成功
|
||||
*/
|
||||
@DeleteMapping
|
||||
public Result<Boolean> deleteById(String id) {
|
||||
return Result.success(this.baseExamService.removeById(id));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,10 +5,7 @@ import com.guwan.backend.annotation.OperationLog;
|
|||
import com.guwan.backend.client.SimpleTTSClient;
|
||||
import com.guwan.backend.client.VoiceServiceClient;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.mongodb.EveryReadDetailOfMongodb;
|
||||
import com.guwan.backend.mongodb.EveryReadDetailOfMongodbService;
|
||||
import com.guwan.backend.mongodb.MongodbUserService;
|
||||
import com.guwan.backend.mongodb.User;
|
||||
import com.guwan.backend.mongodb.*;
|
||||
import com.guwan.backend.pojo.entity.BookContent;
|
||||
import com.guwan.backend.util.MinioUtil;
|
||||
import dev.langchain4j.community.model.dashscope.QwenChatModel;
|
||||
|
@ -41,8 +38,13 @@ import java.io.FileInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -70,6 +72,9 @@ public class CommonController {
|
|||
private final QwenChatModel qwenChatModel;
|
||||
private final QwenStreamingChatModel qwenStreamingChatModel;
|
||||
|
||||
private final TestDateRepository testDateRepository;
|
||||
|
||||
private final TestDateDao testDateDao;
|
||||
@PostMapping("/uploadFile")
|
||||
public Result<String> uploadFile(String bucketName, MultipartFile file){
|
||||
return Result.success(minioUtil.getUrl(minioUtil.getFileUrl
|
||||
|
@ -498,10 +503,7 @@ public class CommonController {
|
|||
System.out.println("str = " + str);
|
||||
}
|
||||
|
||||
@PostMapping("/testPostUseBody")
|
||||
public void testPostUseBody(@RequestBody Ac ac){
|
||||
System.out.println("ac = " + ac);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/testGetParam")
|
||||
public void testGetParam(@RequestParam("ids") List<String> ids,
|
||||
|
@ -511,6 +513,48 @@ public class CommonController {
|
|||
}
|
||||
|
||||
|
||||
@GetMapping("/testDate")
|
||||
public void testDate(){
|
||||
TestDate testDate = new TestDate();
|
||||
testDate.setId(UUID.randomUUID().toString());
|
||||
Instant now = Instant.now();
|
||||
//Instant now = Instant.now();
|
||||
|
||||
// 获取当前系统默认时区
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
|
||||
// 获取今天的 00:00:00
|
||||
Instant todayStart = LocalDate.now(zoneId)
|
||||
.atStartOfDay(zoneId)
|
||||
.toInstant();
|
||||
|
||||
// 假设 testDate 有一个 setDate(Instant instant) 方法
|
||||
testDate.setDate(Date.from(todayStart));
|
||||
// testDate.setDate(new Date());
|
||||
testDateRepository.insert(testDate);
|
||||
}
|
||||
|
||||
@GetMapping("/testDateFind")
|
||||
public void testDateFind(){
|
||||
TestDate testDate = new TestDate();
|
||||
testDate.setId(UUID.randomUUID().toString());
|
||||
Instant now = Instant.now();
|
||||
//Instant now = Instant.now();
|
||||
|
||||
// 获取当前系统默认时区
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
|
||||
// 获取今天的 00:00:00
|
||||
Instant todayStart = LocalDate.now(zoneId)
|
||||
.atStartOfDay(zoneId)
|
||||
.toInstant();
|
||||
Date from = Date.from(todayStart);
|
||||
|
||||
List<TestDate> byDate = testDateDao.findByDate(from);
|
||||
System.out.println("byDate = " + byDate);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -68,8 +68,6 @@ public class CourseController {
|
|||
|
||||
courseService.getCourseDetail(courseId);
|
||||
|
||||
|
||||
|
||||
return Result.success();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,77 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.config.Aiconfig;
|
||||
import com.guwan.backend.security.CustomUserDetails;
|
||||
import dev.langchain4j.community.model.dashscope.QwenChatModel;
|
||||
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* ai 聊天控制层
|
||||
*
|
||||
* 1.聊天
|
||||
* 2.会话管理
|
||||
*
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class GPTController {
|
||||
|
||||
private final QwenChatModel qwenChatModel;
|
||||
private final QwenStreamingChatModel qwenStreamingChatModel;
|
||||
private final Aiconfig.Assistant assistant;
|
||||
|
||||
@GetMapping("/testQwen")
|
||||
public Result chatWithQwen(@RequestParam(value = "message", required = false) String message) {
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
//Java 16+ 模式匹配写法
|
||||
if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails userDetails) {
|
||||
// sysLog.setUserId(userDetails.getUserId());
|
||||
// sysLog.setUsername(userDetails.getUsername());
|
||||
}
|
||||
|
||||
var response = assistant.chat("userId+chatId", message);
|
||||
System.out.println("response = " + response);
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping(value = "/testQwenStreaming", produces = "text/steam;charset=UTF-8")
|
||||
public Flux<String> testQwenStreaming(@RequestParam(value = "message", required = false) String message) {
|
||||
|
||||
Flux<String> flux = Flux.create(fluxSink -> {
|
||||
qwenStreamingChatModel.chat(message, new StreamingChatResponseHandler() {
|
||||
//每一次流式响应的文本
|
||||
@Override
|
||||
public void onPartialResponse(String partialResponse) {
|
||||
fluxSink.next(partialResponse);
|
||||
}
|
||||
|
||||
//响应结束的文本
|
||||
@Override
|
||||
public void onCompleteResponse(ChatResponse chatResponse) {
|
||||
fluxSink.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
fluxSink.error(throwable);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
return flux;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.guwan.backend.pojo.entity.BaseExam;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* @author 12455
|
||||
* @description 针对表【base_exam】的数据库操作Mapper
|
||||
* @createDate 2025-04-19 22:52:21
|
||||
* @Entity com.guwan.backend.pojo.entity.BaseExam
|
||||
*/
|
||||
public interface BaseExamMapper extends BaseMapper<BaseExam> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -2,6 +2,7 @@ package com.guwan.backend.model.exam.controller;
|
|||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.core.api.ApiRest;
|
||||
import com.guwan.backend.core.api.controller.BaseController;
|
||||
import com.guwan.backend.core.api.dto.BaseIdReqDTO;
|
||||
|
@ -9,6 +10,8 @@ import com.guwan.backend.core.api.dto.BaseIdsReqDTO;
|
|||
import com.guwan.backend.core.api.dto.BaseStateReqDTO;
|
||||
import com.guwan.backend.core.api.dto.PagingReqDTO;
|
||||
import com.guwan.backend.model.exam.dto.ExamDTO;
|
||||
import com.guwan.backend.model.exam.dto.ExamResponseDTO;
|
||||
import com.guwan.backend.model.exam.dto.QuestionDTO;
|
||||
import com.guwan.backend.model.exam.dto.request.ExamSaveReqDTO;
|
||||
import com.guwan.backend.model.exam.dto.response.ExamOnlineRespDTO;
|
||||
import com.guwan.backend.model.exam.dto.response.ExamReviewRespDTO;
|
||||
|
@ -22,7 +25,9 @@ import io.swagger.annotations.ApiOperation;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -64,4 +69,54 @@ public class ExamController extends BaseController {
|
|||
}
|
||||
|
||||
|
||||
@GetMapping("/exam")
|
||||
public Result getExam() {
|
||||
// 模拟数据,可以从数据库中查询
|
||||
ExamResponseDTO response = new ExamResponseDTO();
|
||||
response.setId("1");
|
||||
response.setTitle("模拟考试试卷 (含新题型)");
|
||||
response.setTotalScore(100);
|
||||
response.setTotalTime(120);
|
||||
response.setLeftSeconds(7180);
|
||||
response.setExamStartTime("2025-04-17 00:10:24");
|
||||
response.setExamDuration(60 * 60 * 2);
|
||||
|
||||
// 填充题数据
|
||||
List<QuestionDTO> fillList = new ArrayList<>();
|
||||
fillList.add(new QuestionDTO("5001", "501", 0, false, "填空题1"));
|
||||
fillList.add(new QuestionDTO("5002", "502", 1, false, "填空题2"));
|
||||
response.setFillList(fillList);
|
||||
|
||||
// 判断题数据
|
||||
List<QuestionDTO> judgeList = new ArrayList<>();
|
||||
judgeList.add(new QuestionDTO("3001", "301", 2, false, "判断题1"));
|
||||
response.setJudgeList(judgeList);
|
||||
|
||||
// 单选题数据
|
||||
List<QuestionDTO> radioList = new ArrayList<>();
|
||||
radioList.add(new QuestionDTO("1001", "101", 3, false, "单选题1"));
|
||||
radioList.add(new QuestionDTO("1002", "102", 4, false, "单选题2"));
|
||||
response.setRadioList(radioList);
|
||||
|
||||
// 多选题数据
|
||||
List<QuestionDTO> multiList = new ArrayList<>();
|
||||
multiList.add(new QuestionDTO("2001", "201", 5, false, "多选题1"));
|
||||
response.setMultiList(multiList);
|
||||
|
||||
// 简答题数据
|
||||
List<QuestionDTO> saqList = new ArrayList<>();
|
||||
saqList.add(new QuestionDTO("6001", "601", 6, false, "简答题1"));
|
||||
response.setSaqList(saqList);
|
||||
|
||||
// 编程题数据
|
||||
List<QuestionDTO> programmingList = new ArrayList<>();
|
||||
programmingList.add(new QuestionDTO("4001", "401", 7, false, "两数之和"));
|
||||
programmingList.add(new QuestionDTO("4002", "402", 8, false, "反转链表"));
|
||||
response.setProgrammingList(programmingList);
|
||||
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package com.guwan.backend.model.exam.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ExamResponseDTO {
|
||||
|
||||
private String id;
|
||||
private String title;
|
||||
private int totalScore;
|
||||
private int totalTime;
|
||||
private int leftSeconds;
|
||||
private String examStartTime; // 这里用字符串格式而不是标准时间
|
||||
private int examDuration;
|
||||
private List<QuestionDTO> fillList;
|
||||
private List<QuestionDTO> judgeList;
|
||||
private List<QuestionDTO> radioList;
|
||||
private List<QuestionDTO> multiList;
|
||||
private List<QuestionDTO> saqList;
|
||||
private List<QuestionDTO> programmingList;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.guwan.backend.model.exam.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class QuestionDTO {
|
||||
private String id;
|
||||
private String quId;
|
||||
private int sort;
|
||||
private boolean answered;
|
||||
private String title;
|
||||
}
|
|
@ -38,8 +38,6 @@ public class FileStorageService {
|
|||
|
||||
Query query = new Query(Criteria.where("_id").is(objectId));
|
||||
|
||||
|
||||
|
||||
// 根据文件 ID 获取文件
|
||||
GridFSFile gridFSFile = gridFsTemplate.findOne(query);
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package com.guwan.backend.mongodb;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MongoTestDateDao implements TestDateDao{
|
||||
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
// 可以定义一些自定义查询方法
|
||||
public List<TestDate> findByDate(Date date) {
|
||||
|
||||
Query query = new Query();
|
||||
query.addCriteria(Criteria.where("date").is(date));
|
||||
|
||||
return mongoTemplate.find(query, TestDate.class);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.guwan.backend.mongodb;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Document("testDate")
|
||||
@Data
|
||||
public class TestDate {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private Date date;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.guwan.backend.mongodb;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public interface TestDateDao {
|
||||
// 可以定义一些自定义查询方法
|
||||
List<TestDate> findByDate(Date date);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.guwan.backend.mongodb;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
public interface TestDateRepository extends MongoRepository<TestDate, String> {
|
||||
// 可以定义一些自定义查询方法
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package com.guwan.backend.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
*
|
||||
* @TableName base_exam
|
||||
*/
|
||||
@TableName(value ="base_exam")
|
||||
@Data
|
||||
public class BaseExam implements Serializable {
|
||||
/**
|
||||
* 试题id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 考试名称
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 是否限时
|
||||
*/
|
||||
private Integer timeLimit;
|
||||
|
||||
/**
|
||||
* 考试开始时间
|
||||
*/
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 考试结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
|
||||
/**
|
||||
* 考试时间总长
|
||||
*/
|
||||
private Integer totalTime;
|
||||
|
||||
/**
|
||||
* 考试总分
|
||||
*/
|
||||
private Integer totalScore;
|
||||
|
||||
/**
|
||||
* 及格分数
|
||||
*/
|
||||
private Integer qualifyScore;
|
||||
|
||||
/**
|
||||
* 删除标志位
|
||||
*/
|
||||
private Integer deleted;
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -43,6 +43,10 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
|||
|
||||
private UserDetails convertToUserDetails(User user) {
|
||||
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
|
||||
//TODO 在这加权限校验
|
||||
//user 表里去查 然后拿到 再 add
|
||||
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
|
||||
return new CustomUserDetails(
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.guwan.backend.service;
|
||||
|
||||
import com.guwan.backend.pojo.entity.BaseExam;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 12455
|
||||
* @description 针对表【base_exam】的数据库操作Service
|
||||
* @createDate 2025-04-19 22:52:21
|
||||
*/
|
||||
public interface BaseExamService extends IService<BaseExam> {
|
||||
|
||||
List<BaseExam> queryByPage(Integer page, Integer pageSize, String query);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.guwan.backend.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.guwan.backend.annotation.CheckExistenceProcessor;
|
||||
import com.guwan.backend.pojo.entity.BaseExam;
|
||||
import com.guwan.backend.service.BaseExamService;
|
||||
import com.guwan.backend.mapper.BaseExamMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 12455
|
||||
* @description 针对表【base_exam】的数据库操作Service实现
|
||||
* @createDate 2025-04-19 22:52:21
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BaseExamServiceImpl extends ServiceImpl<BaseExamMapper, BaseExam>
|
||||
implements BaseExamService{
|
||||
|
||||
private final BaseExamMapper baseExamMapper;
|
||||
|
||||
@Override
|
||||
public List<BaseExam> queryByPage(Integer page, Integer pageSize, String query) {
|
||||
baseExamMapper.insert(new BaseExam());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(BaseExam entity) {
|
||||
try {
|
||||
// 调用检查方法
|
||||
if (CheckExistenceProcessor.checkFieldExistence(entity, this)) {
|
||||
return false; // 如果字段值已存在,返回 false,阻止插入
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果没有重复数据,执行插入操作
|
||||
return super.save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
package com.guwan.backend.store;
|
||||
|
||||
import dev.langchain4j.data.message.*;
|
||||
|
||||
public class ChatMessageUtils {
|
||||
|
||||
private ChatMessageUtils() {
|
||||
// 工具类,不允许实例化
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 根据类型和文本内容,动态构造 ChatMessage 实例
|
||||
*//*
|
||||
|
||||
public static ChatMessage build(ChatMessageType type, String text) {
|
||||
switch (type) {
|
||||
case USER:
|
||||
return new UserMessage(text);
|
||||
case AI:
|
||||
return new AiMessage(text);
|
||||
case SYSTEM:
|
||||
return new SystemMessage(text);
|
||||
case TOOL_EXECUTION_RESULT:
|
||||
return new ToolExecutionResultMessage(text);
|
||||
case CUSTOM:
|
||||
return new CustomMessage(text);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported ChatMessageType: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 获取 ChatMessage 的 ChatMessageType
|
||||
*//*
|
||||
|
||||
public static ChatMessageType typeOf(ChatMessage message) {
|
||||
if (message instanceof UserMessage) {
|
||||
return ChatMessageType.USER;
|
||||
} else if (message instanceof AiMessage) {
|
||||
return ChatMessageType.AI;
|
||||
} else if (message instanceof SystemMessage) {
|
||||
return ChatMessageType.SYSTEM;
|
||||
} else if (message instanceof ToolExecutionResultMessage) {
|
||||
return ChatMessageType.TOOL_EXECUTION_RESULT;
|
||||
} else if (message instanceof CustomMessage) {
|
||||
return ChatMessageType.CUSTOM;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown message class: " + message.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 获取 ChatMessage 的文本内容
|
||||
*//*
|
||||
|
||||
public static String textOf(ChatMessage message) {
|
||||
if (message instanceof UserMessage) {
|
||||
return ((UserMessage) message).text();
|
||||
} else if (message instanceof AiMessage) {
|
||||
return ((AiMessage) message).text();
|
||||
} else if (message instanceof SystemMessage) {
|
||||
return ((SystemMessage) message).text();
|
||||
} else if (message instanceof ToolExecutionResultMessage) {
|
||||
return ((ToolExecutionResultMessage) message).text();
|
||||
} else if (message instanceof CustomMessage) {
|
||||
return ((CustomMessage) message).text();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown message class: " + message.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 获取 ChatMessage 的角色 (user / assistant / system / tool_execution_result / custom)
|
||||
*//*
|
||||
|
||||
public static String roleOf(ChatMessage message) {
|
||||
ChatMessageType type = typeOf(message);
|
||||
switch (type) {
|
||||
case USER:
|
||||
return "user";
|
||||
case AI:
|
||||
return "assistant";
|
||||
case SYSTEM:
|
||||
return "system";
|
||||
case TOOL_EXECUTION_RESULT:
|
||||
return "tool_execution_result";
|
||||
case CUSTOM:
|
||||
return "custom";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported ChatMessageType: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 从角色字符串恢复 ChatMessageType
|
||||
*//*
|
||||
|
||||
public static ChatMessageType typeOfRole(String role) {
|
||||
switch (role) {
|
||||
case "user":
|
||||
return ChatMessageType.USER;
|
||||
case "assistant":
|
||||
return ChatMessageType.AI;
|
||||
case "system":
|
||||
return ChatMessageType.SYSTEM;
|
||||
case "tool_execution_result":
|
||||
return ChatMessageType.TOOL_EXECUTION_RESULT;
|
||||
case "custom":
|
||||
return ChatMessageType.CUSTOM;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported role: " + role);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,32 @@
|
|||
package com.guwan.backend.store;
|
||||
|
||||
import dev.langchain4j.data.message.AiMessage;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ai消息存储
|
||||
*/
|
||||
public class PersistentChatMemoryStore implements ChatMemoryStore {
|
||||
@Override
|
||||
public List<ChatMessage> getMessages(Object memoryId) {
|
||||
ChatMessage message1 = new UserMessage("你好,请自我介绍一下");
|
||||
ChatMessage message2 = new AiMessage("你好,我是你的智能助手。");
|
||||
ChatMessage message3 = new SystemMessage("你是一个有礼貌的助手,请简洁回答用户问题。");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessages(Object memoryId, List<ChatMessage> list) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMessages(Object memoryId) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.guwan.backend.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectUtil {
|
||||
|
||||
// 安全获取字段值
|
||||
public static Object getFieldValue(Object obj, String fieldName) {
|
||||
try {
|
||||
Field field = obj.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException("反射获取字段失败: " + fieldName, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 安全设置字段值
|
||||
public static void setFieldValue(Object obj, String fieldName, Object value) {
|
||||
try {
|
||||
Field field = obj.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(obj, value);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException("反射设置字段失败: " + fieldName, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 通过Getter/Setter方法操作(更安全)
|
||||
public static Object invokeGetter(Object obj, String fieldName) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod("get" + capitalize(fieldName));
|
||||
return method.invoke(obj);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("调用Getter失败: " + fieldName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void invokeSetter(Object obj, String fieldName, Object value) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod("set" + capitalize(fieldName), value.getClass());
|
||||
method.invoke(obj, value);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("调用Setter失败: " + fieldName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String capitalize(String str) {
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
}
|
|
@ -207,8 +207,6 @@ logging:
|
|||
host: localhost
|
||||
port: 5044
|
||||
|
||||
|
||||
|
||||
xxl:
|
||||
job:
|
||||
enabled: false # 控制是否启用xxl-job
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.guwan.backend.mapper.BaseExamMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.guwan.backend.pojo.entity.BaseExam">
|
||||
<id property="id" column="id" jdbcType="VARCHAR"/>
|
||||
<result property="title" column="title" jdbcType="VARCHAR"/>
|
||||
<result property="timeLimit" column="time_limit" jdbcType="TINYINT"/>
|
||||
<result property="startTime" column="start_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="endTime" column="end_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="totalTime" column="total_time" jdbcType="INTEGER"/>
|
||||
<result property="totalScore" column="total_score" jdbcType="INTEGER"/>
|
||||
<result property="qualifyScore" column="qualify_score" jdbcType="INTEGER"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,title,time_limit,
|
||||
start_time,end_time,total_time,
|
||||
total_score,qualify_score
|
||||
</sql>
|
||||
</mapper>
|
Loading…
Reference in New Issue