SpringBoot校园失物招领信息平台
SpringBoot校园失物招领信息平台
文章目录
- SpringBoot校园失物招领信息平台
- 1、技术栈
- 2、项目说明
- 2.1、登录注册
- 2.2、管理员端截图
- 2.3、用户端截图
- 3、核心代码实现
- 3.1、前端首页
- 3.2、前端招领广场
- 3.3、后端业务处理
1、技术栈
本项目采用前后端分离的架构,前端和后端分别使用了不同的技术栈,以实现高效的开发和良好的用户体验。
前端:
- Vue: 作为核心的渐进式 JavaScript 框架,Vue 提供了强大的组件化开发能力,使得前端界面的构建更加灵活和高效。通过 Vue 的响应式数据绑定和组件系统,可以轻松构建复杂的用户界面,并实现数据的实时更新。
- axios: 一个基于 Promise 的 HTTP 客户端,用于前端与后端进行数据交互。axios 提供了简洁易用的 API,支持请求拦截、响应拦截、请求取消等功能,方便地处理前后端通信。
- Element UI: 一套基于 Vue 2.0 的桌面端组件库。Element UI 提供了丰富的 UI 组件,如表格、表单、按钮、弹窗等,可以快速构建美观且功能完善的用户界面,提升开发效率。
- Apache ECharts: 一个强大、灵活且易用的数据可视化库。在本项目中,ECharts 用于生成各种图表,例如平台物品数量统计、失物招领物品数量占比等,以直观地展示平台数据,帮助管理员更好地了解平台运营情况。
后端:
- Springboot: 基于 Spring 框架的快速开发框架。Springboot 简化了 Spring 应用的搭建和开发过程,提供了自动配置、内嵌式服务器等功能,使得后端开发更加便捷高效。
- MySQL: 一款流行的关系型数据库管理系统。MySQL 用于存储平台的所有数据,包括用户信息、失物信息、招领信息、公告信息等。其稳定性和可靠性为平台的正常运行提供了保障。
- Mybatis: 一款优秀的持久层框架。Mybatis 通过 XML 或注解的方式配置 SQL 语句,将 Java 对象与数据库记录进行映射,简化了数据库操作,提高了开发效率。
- 阿里云 OSS: 阿里云对象存储服务。OSS 用于存储用户上传的失物和招领物品的图片,提供了高可用、高可靠、安全的数据存储服务,确保图片的稳定存储和访问。
2、项目说明
失物招领管理系统采用前后端分离的设计思想,清晰地划分了管理员和用户两种角色,为不同用户提供定制化的功能和服务。
- 角色划分:
- 管理员: 拥有平台的最高权限,负责维护平台信息,包括发布和管理公告、查看和管理失物信息、查看和管理招领信息、管理用户等。管理员通过后台管理界面对平台进行全面的管理和监控。
- 用户: 普通用户可以在平台上发布失物信息和招领信息,查找自己丢失的物品或捡到的物品。用户可以浏览公告、查看失物广场和招领广场的信息,并与物品发布者进行联系。
2.1、登录注册
系统提供了完善的登录注册功能,确保用户身份的合法性。
- 登录: 用户通过输入用户名、密码和选择角色(管理员或用户)进行登录。登录成功后,系统会根据用户的角色跳转到相应的界面。登录界面设计简洁明了,背景图采用了充满科技感的火箭发射场景,寓意着平台的快速发展和便捷服务。
- 注册: 新用户可以通过注册功能创建自己的账号。注册时需要填写用户名、密码等信息。注册成功后,用户即可使用新创建的账号登录平台。注册功能为用户提供了便捷的平台入口。
登录
注册
2.2、管理员端截图
管理员端提供了丰富的功能模块,方便管理员对平台进行全面的管理。
- 系统首页: 系统首页提供了平台数据的概览,通过 ECharts 图表直观展示了平台所有物品数量的统计、平台招领物品数量的占比、失物广告物品数量的占比等数据,帮助管理员快速了解平台的运营状况。
- 信息管理:
- 公告信息: 管理员可以在此模块发布、编辑和删除平台公告。公告信息会显示在用户端的主页,方便用户及时了解平台的重要通知和活动。
- 失物信息: 管理员可以查看所有用户发布的失物信息,包括物品名称、描述、图片、状态(丢失中、已找回)、发布时间等。管理员可以对失物信息进行编辑或删除操作。
- 招领信息: 管理员可以查看所有用户发布的招领信息,包括物品名称、描述、图片、状态(未找到失主、已找到失主)、发布时间等。管理员可以对招领信息进行编辑或删除操作。
- 平台建议: 用户可以通过反馈建议模块向平台提交建议,管理员可以在此查看和处理用户的建议。
- 用户管理: 管理员可以查看所有注册用户的信息,包括用户名、密码、头像、身份(普通用户)、注册时间等。管理员可以对用户信息进行编辑或删除操作。
系统首页
公告信息
失物信息
用户管理
2.3、用户端截图
用户端提供了便捷的功能,方便用户发布和查找失物招领信息。
- 主页: 用户登录后的主页展示了最新的平台公告和最新发布的失物信息,方便用户快速获取重要信息和关注最新动态。主页设计简洁友好,提供了失物广场、招领广场和反馈建议的入口。
- 失物广场: 失物广场展示了所有用户发布的失物信息,用户可以通过搜索功能查找特定的失物。每条失物信息都包含物品名称、图片、描述、发布时间等信息。用户可以点击“查看详情”查看更详细的信息,或点击“联系失主”与物品发布者取得联系。
- 招领广场: 招领广场展示了所有用户发布的招领信息,用户可以通过搜索功能查找特定的招领物品。每条招领信息都包含物品名称、图片、描述、发布时间等信息。用户可以点击“查看详情”查看更详细的信息,或点击“联系ta”与物品发布者取得联系。
- 反馈建议: 用户可以在此模块向平台提交反馈和建议,帮助平台改进服务。
- 个人中心: 用户可以在个人中心查看和修改自己的信息,包括用户名、密码、头像等。
主页
失物广场
招领广场
反馈建议
个人中心
3、核心代码实现
3.1、前端首页
创建父组件
<template><div class="common-layout"><el-container><el-header class="header"><div class="header-left"><img src="@/assets/lostLogo.png" alt="logo" class="logo" width="40px" height="40px"><span class="logo-text">失物招领平台</span></div><div class="nav-links"><router-link to="/home"><el-button type="button" class="nav-button">首页</el-button></router-link><router-link to="/LostPropertySquare"><el-button class="nav-button">失物广场</el-button></router-link><router-link to="/FoundPropertySquare"><el-button class="nav-button" >招领广场</el-button></router-link><router-link to="/feedback"><el-button class="nav-button" >反馈建议</el-button></router-link></div><div><AuthButtons v-if="token === null" /><ExitsLogin v-else /></div></el-header><el-main><div class="centered-content" style="width: 80%; height: 100%; margin: 0 auto;"><router-view></router-view></div></el-main></el-container></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import AuthButtons from '@/views/AuthButtons.vue'
import ExitsLogin from '@/views/ExitsLogin.vue'const router = useRouter()
const token = ref<string | null>(null)onMounted(() => {token.value = localStorage.getItem('token')router.push('/home')console.log('token:',token.value)
})</script><style scoped>
.header {background-color: #f8cf47;color: white;display: flex;align-items: center;justify-content: space-between;padding: 0 20px;
}
.header-left {display: flex;align-items: center;
}
.logo {margin-right: 10px;
}
.nav-links {display: flex;
}
.nav-button {font-size: 18px;margin: 0 10px;background-color: transparent;color: #ffffff;border: none;right: 250px;position: relative;transition: color 0.3s;
}
.nav-button:hover {background-color: transparent;color: white;font-weight: bold;
}
.nav-button::after {content: '';display: block;width: 100%;height: 2px;background-color: white;position: absolute;bottom: -5px;left: 0;transform: scaleX(0);transition: transform 0.3s;
}
.nav-button:hover::after {transform: scaleX(1);
}
.header-right {display: flex;align-items: center;
}
.header-right el-button {background-color: transparent;color: #f0f0f0;border: none;
}
.header-right el-button:hover {background-color: #f0f0f0;
}
.logo-text {color: #fdfdfd;font-size: 20px;font-weight: bold;margin-left: 5px;/* 垂直居中 */display: flex;justify-content: center;
}
.centered-content {text-align: center;background-color: transparent;border-radius: 8px;
}
.info-container {display: flex;justify-content: space-between;width: 80%;margin: 20px auto;gap: 10px;min-height: 300px;
}
.announcement-board {width: 48%;margin: 0;
}
.announcement-card {background-color: #fff;border-radius: 8px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);height: 100%;
}
.announcement-header {display: flex;align-items: center;gap: 12px;font-size: 18px;font-weight: bold;color: #333;padding: 10px 15px;
}
.announcement-icon {font-size: 20px;color: #f8cf47;
}
.announcement-content {padding: 15px 0;min-height: 250px;
}
.announcement-item {display: flex;align-items: center;gap: 10px;padding: 12px 15px;border-bottom: 1px solid #eee;font-size: 15px;text-align: left;
}
.announcement-item:last-child {border-bottom: none;
}
.announcement-date {margin-left: auto;color: #999;font-size: 12px;
}
.latest-lost {width: 48%;
}
.latest-card {background-color: #fff;border-radius: 8px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);height: 100%;
}
.latest-header {display: flex;align-items: center;gap: 12px;font-size: 18px;font-weight: bold;color: #333;padding: 10px 15px;
}
.latest-icon {font-size: 20px;color: #f8cf47;
}
.latest-content {padding: 15px 0;min-height: 250px;
}
.latest-item {display: flex;align-items: center;gap: 12px;padding: 10px 15px;border-bottom: 1px solid #eee;
}
.latest-item:last-child {border-bottom: none;
}
.item-image {width: 50px;height: 50px;border-radius: 4px;
}
.item-info {flex: 1;
}
.item-title {font-size: 14px;color: #333;margin-bottom: 4px;
}
.item-time {font-size: 12px;color: #999;
}
</style>
创建子组件
<template><div><div class="block text-center"><el-carousel height="350px" width="100%"><el-carousel-item v-for="(image, index) in images" :key="index"><img :src="image" alt="carousel image" class="carousel-image" /></el-carousel-item></el-carousel></div><div class="info-container"><div class="announcement-board"><el-card class="announcement-card"><template #header><div class="announcement-header"><el-icon class="announcement-icon"><Bell /></el-icon><span>公告栏</span></div></template><div class="announcement-content"><el-scrollbar height="200px"><div v-for="(item, index) in announcements" :key="index" class="announcement-item"><el-icon><InfoFilled /></el-icon><span class="clickable" @click="showAnnouncementDetail(item)">{{ item.title }}</span><span class="announcement-date">{{ formatDateTime(item.createTime) }}</span></div></el-scrollbar></div></el-card></div><div class="latest-lost"><el-card class="latest-card"><template #header><div class="latest-header"><el-icon class="latest-icon"><Timer /></el-icon><span>最新发布</span></div></template><div class="latest-content"><el-scrollbar height="200px"><div v-for="(item, index) in latestItems" :key="index" class="latest-item"><el-image :src="item.image" fit="cover" class="item-image"></el-image><div class="item-info"><div class="item-title">{{ item.name }}</div><div class="item-time">{{ formatDateTime(item.date) }}</div><div class="item-description">{{ item.description }}</div></div></div></el-scrollbar></div></el-card></div></div><!-- Add dialog component --><el-dialogv-model="dialogVisible":title="selectedAnnouncement.title"width="30%"><span>{{ selectedAnnouncement.content }}</span><template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">关闭</el-button></span></template></el-dialog></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getBanner, getAnnouncement,getNewLost } from '@/api/api';
import { Bell, InfoFilled, Timer } from '@element-plus/icons-vue'const images = ref<string[]>([]);
// Add announcement and latest items data
const announcements = ref([{id: 1,title: '欢迎使用失物招领平台!', content: '欢迎使用失物招领平台!', createTime: '2024-03-20',userId: 1 },{id: 2,title: '请文明发布信息,共建和谐社区。', content: '请文明发布信息,共建和谐社区。', createTime: '2024-03-19',userId: 2 },{id: 3,title: '新功能上线:现在可以更方便地搜索失物了!', content: '新功能上线:现在可以更方便地搜索失物了!', createTime: '2024-03-18',userId: 3 },
]);const latestItems = ref([{ name: '蓝色钱包',date: [1,2,20, 15, 30],image: 'path/to/image1.jpg',description: '蓝色钱包,内有身份证、银行卡等重要物品。'},{ name: '学生证',date: [1,2,20, 15, 30],image: 'path/to/image2.jpg',description: '学生证,内有学生姓名、学号、照片等信息。' },
]);const formatDateTime = (timeArray: number[]) => {if (!Array.isArray(timeArray)) return '';const [year, month, day, hour, minute, second] = timeArray;return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
}// Add new refs for dialog
const dialogVisible = ref(false);
const selectedAnnouncement = ref({title: '',content: ''
});// Add click handler
const showAnnouncementDetail = (item: any) => {selectedAnnouncement.value = item;dialogVisible.value = true;
};onMounted(async () => {let res = await getBanner();let res2 = await getAnnouncement();let res3 = await getNewLost();images.value = res.data.data;announcements.value = res2.data.data;latestItems.value = res3.data.data;
});</script><style scoped>
.demonstration {color: var(--el-text-color-secondary);
}.el-carousel__item h3 {color: #475669;opacity: 0.75;line-height: 150px;margin: 0;text-align: center;
}.el-carousel__item:nth-child(2n) {background-color: #ffffff;
}.el-carousel__item:nth-child(2n + 1) {background-color: #d3dce6;
}.carousel-image {width: 100%;height: 100%;object-fit: cover;display: block;margin: 0 auto;
}.info-container {display: flex;justify-content: space-between;width: 100%;margin: 20px auto;gap: 20px;
}.announcement-board {flex: 1;min-height: 300px;margin-right: auto;max-width: 49%;
}.latest-lost {flex: 1;min-height: 300px;margin-left: auto;max-width: 49%;
}.announcement-header, .latest-header {display: flex;align-items: center;gap: 8px;font-size: 16px;font-weight: bold;padding: 20px;
}.announcement-icon, .latest-icon {font-size: 24px;
}.announcement-item {display: flex;align-items: center;gap: 8px;padding: 20px 0;border-bottom: 1px solid #eee;font-size: 16px;
}.announcement-item:last-child {border-bottom: none;
}.announcement-date {margin-left: auto;color: #999;font-size: 14px;
}.latest-item {display: flex;align-items: center;gap: 12px;padding: 20px 0;border-bottom: 1px solid #eee;text-align: left;font-size: 16px;
}.latest-item:last-child {border-bottom: none;
}.item-image {width: 80px;height: 80px;border-radius: 4px;
}.item-info {flex: 1;text-align: left;
}.item-title {font-size: 16px;margin-bottom: 4px;
}.item-time {font-size: 14px;color: #999;
}.announcement-content {padding: 10px 20px;text-align: left;
}.latest-content {padding: 10px 20px;text-align: left;
}.latest-header {justify-content: flex-start;
}/* 添加失败状态样式 */
.failed {color: #F56C6C;font-size: 12px;
}/* 增加滚动区域高度 */
:deep(.el-scrollbar) {height: 250px !important;
}/* 增加卡片内容区域的内边距 */
.announcement-content, .latest-content {padding: 10px 20px;
}/* 增加标题区域的内边距 */
.announcement-header, .latest-header {padding: 20px;font-size: 20px;
}/* 调整内容项的间距 */
.announcement-item, .latest-item {padding: 20px 0;font-size: 16px;
}/* 调整图片大小 */
.item-image {width: 80px;height: 80px;
}/* 调整文字大小 */
.item-title {font-size: 16px;
}.item-time, .announcement-date {font-size: 14px;
}.clickable {cursor: pointer;&:hover {color: var(--el-color-primary);}
}
</style>
3.2、前端招领广场
<template><div style="text-align: left; margin-bottom: 10px;"><el-button type="success" plain @click="handleAdd">新增失物</el-button></div><el-table :data="paginatedData" :default-sort="{ prop: 'date', order: 'descending' }" style="width: 100%"><el-table-column label="序号" width="80"><template #default="scope">{{ (currentPage - 1) * pageSize + scope.$index + 1 }}</template></el-table-column><el-table-column prop="name" label="物件名称" /><el-table-column prop="description" label="物件描述" /><el-table-column prop="image" label="图片"><template #default="scope"><img :src="scope.row.image" alt="Image" style="width: 100px; height: auto;" /></template></el-table-column><el-table-column prop="status" label="状态"><template #default="scope">{{ scope.row.status === 1 ? '招领中' : '已归还' }}</template></el-table-column><el-table-column prop="date" label="发布时间" sortable><template #default="scope">{{ scope.row.date }}</template></el-table-column><!-- 操作 --><el-table-column label="操作" width="200"><template #default="scope"><el-button type="primary" @click="handleEdit(scope.row)">编辑</el-button><el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button></template></el-table-column></el-table><el-pagination @current-change="handleCurrentChange" :current-page="currentPage" :page-size="pageSize":total="tableData.length" layout="total, prev, pager, next, jumper" /><el-dialog v-model="isUpdateDialogVisible" title="编辑招领物件" width="35%"><el-form :model="updateForm" label-width="auto"><el-form-item label="物件名称"><el-input v-model="updateForm.name" /></el-form-item><el-form-item label="物件描述"><el-input v-model="updateForm.description" /></el-form-item><el-form-item label="物件图片"><el-upload class="avatar-uploader" :http-request="uploadImage":show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload"><img v-if="imageUrl" :src="imageUrl" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="物件状态"><el-select v-model="updateForm.status" placeholder="请选择状态"><el-option label="招领中" value="1" /><el-option label="已归还" value="2" /></el-select></el-form-item><el-form-item><el-button type="primary" @click="handleUpdate">确认修改</el-button><el-button @click="isUpdateDialogVisible = false">取消</el-button></el-form-item></el-form></el-dialog><el-dialog v-model="isAddDialogVisible" title="新增招领物件" width="35%"><el-form :model="addForm" label-width="auto"><el-form-item label="物件名称"><el-input v-model="addForm.name" /></el-form-item><el-form-item label="物件描述"><el-input v-model="addForm.description" /></el-form-item><el-form-item label="物件图片"><el-upload class="avatar-uploader" :http-request="uploadImage":show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload"><img v-if="imageUrl" :src="imageUrl" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="物件状态"><el-select v-model="addForm.status" placeholder="请选择状态"><el-option label="招领中" value="1" /><el-option label="已归还" value="2" /></el-select></el-form-item><el-form-item><el-button type="primary" @click="handleAddSubmit">确认新增</el-button><el-button @click="isAddDialogVisible = false">取消</el-button></el-form-item></el-form></el-dialog>
</template><script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import { getFoundAll, deleteFound, updateFound, addFound } from '@/api/foundApi'
import { ElMessage } from 'element-plus'
import axios from 'axios'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus'interface Found {id: numbername: stringdescription: stringimage: stringstatus: numberdate: number[]
}// Define tableData as a ref
const tableData = ref<Found[]>([])onMounted(async () => {let res = await getFoundAll()console.log(res.data.data)tableData.value = res.data.data // Use .value to assign data
})const handleDelete = async (id: number) => {try {let res = await deleteFound(id)if (res.data.code === 1) {ElMessage.success('删除成功')}} catch (error) {ElMessage.error('删除失败')} finally {// Refresh data regardless of success or failurelet res = await getFoundAll()tableData.value = res.data.data}
}let isUpdateDialogVisible = ref(false)const updateForm = ref<{id: number;name: string;description: string;status: number | null; // Adjusted to allow nullimage: string | null; // Adjusted to allow null
}>({id: 0,name: '',description: '',status: null,image: null,
})const handleEdit = (row: Found) => {updateForm.value.id = row.idupdateForm.value.name = row.nameupdateForm.value.description = row.descriptionupdateForm.value.image = row.imageupdateForm.value.status = row.statusisUpdateDialogVisible.value = true // Open the dialog after setting the form values
}const handleUpdate = async () => {let res = await updateFound(updateForm.value.id, updateForm.value as Found)if (res.data.code === 1) {ElMessage.success('修改成功')let res = await getFoundAll()tableData.value = res.data.dataisUpdateDialogVisible.value = false}
}const currentPage = ref(1)
const pageSize = ref(5)const paginatedData = computed(() => {const start = (currentPage.value - 1) * pageSize.valuereturn tableData.value.slice(start, start + pageSize.value).map(item => ({...item,date: formatDate(item.date) // 格式化日期})) // Use .value here as well
})const handleCurrentChange = (page: number) => {currentPage.value = page
}const uploadImage = async (options: any) => {const { file, onSuccess, onError } = optionsconst formData = new FormData()formData.append('file', file)try {const response = await axios.post('http://localhost:8080/upload', formData, {headers: {'Content-Type': 'multipart/form-data',},})if (response.data.code === 1) {onSuccess(response.data.data)updateForm.value.image = response.data.data} else {onError(new Error(response.data.msg || '图片上传失败'))}} catch (error) {onError(error)}
}const imageUrl = ref('')const handleAvatarSuccess: UploadProps['onSuccess'] = (response,uploadFile
) => {imageUrl.value = URL.createObjectURL(uploadFile.raw!)
}const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {ElMessage.error('文件格式错误,请上传 JPG or PNG 格式文件!')return false} else if (rawFile.size / 1024 / 1024 > 2) {ElMessage.error('文件大小不能超过 2MB!')return false}return true
}// 新增一个格式化日期的函数
const formatDate = (dateArray: number[]) => {const [year, month, day] = dateArrayreturn `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}let isAddDialogVisible = ref(false)const handleAdd = () => {isAddDialogVisible.value = true
}const addForm = ref<{name: string | null;description: string | null;image: string | null;status: number | null;
}>({name: null,description: null,image: null,status: null,
})const handleAddSubmit = async () => {addForm.value.image = imageUrl.valuelet res = await addFound(addForm.value as Found)if (res.data.code === 1) {ElMessage.success('新增成功')let res = await getFoundAll()tableData.value = res.data.dataimageUrl.value = res.data.data.imageisAddDialogVisible.value = false}
}</script><style>
.avatar-uploader .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);
}.avatar-uploader .el-upload:hover {border-color: var(--el-color-primary);
}.el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;text-align: center;
}
</style>
3.3、后端业务处理
@RequiredArgsConstructor
@RestController
@RequestMapping("/home")
@Slf4j
public class HomeController {private final LostService lostService;private final HomeService homeService;@GetMapping("getLostAll")public Result getLostAll(){log.info("获取全部");List<LostVO> list = lostService.getLostAll();return Result.success(list);}@GetMapping("getFoundAll")public Result getFoundAll(){log.info("获取全部");List<FoundVO> list = lostService.getFoundAll();return Result.success(list);}@GetMapping("/getAnnouncement")public Result getAnnouncement(){log.info("获取公告");List<BulletinBoard> list = homeService.getAnnouncement();return Result.success(list);}@GetMapping("/getNewLost")public Result getNewLost(){log.info("获取最新失物");List<LostVO> newLost = homeService.getNewLost();return Result.success(newLost);}@GetMapping("/getFriendshipLinks")public Result<List<FriendshipLinks>> getFriendshipLinks(){log.info("获取友情链接");List<FriendshipLinks> list = homeService.getFriendshipLinks();return Result.success(list);}@PutMapping("/savoOrUpdateFriendshipLinks")public Result<Void> savoOrUpdateFriendshipLinks(@RequestBody FriendshipLinks friendshipLinks){log.info("更新或新增:{}",friendshipLinks);homeService.savoOrUpdateFriendshipLinks(friendshipLinks);return Result.success();}@DeleteMapping("/deleteFriendshipLinks/{id}")public Result<Void> deleteFriendshipLinks(@PathVariable Long id){log.info("删除:{}",id);homeService.deleteFriendshipLinks(id);return Result.success();}}
@RequiredArgsConstructor
@RestController
@Slf4j
@RequestMapping("/lost")
public class LostController {private final LostService lostService;/*** 查询所有发布* @return*/@GetMapping("getLostAll")public Result<List<Lost>> getAllLost() {log.info("查询所有丢失物件");List<Lost> lostList =lostService.getAllLost();return Result.success(lostList);}@DeleteMapping("/deleteLost/{id}")public Result deleteLostById(@PathVariable Long id) {lostService.deleteLostById(id);return Result.success();}@PutMapping("/updateLost/{id}")public Result updateLostById(@PathVariable("id") Integer id,@RequestBody LostDTO dto){log.info("id,dto:{},{}",id,dto);lostService.updateLostById(id,dto);return Result.success();}@PostMapping("/addLost")public Result addLost(@RequestBody LostDTO dto){log.info("新增dto:{}",dto);lostService.addLost(dto);return Result.success();}@PostMapping("/feedbackLostSave")public Result feedbackLostSave(@RequestBody ContactInfoDTO dto){lostService.feedbackLostSave(dto);return Result.success();}}
@Service
@RequiredArgsConstructor
public class HomeServiceImpl implements HomeService {private final BulletinBoardMapper bulletinBoardMapper;private final LostService lostService;private final FriendshipLinksMapper friendshipLinksMapper;/*** 获取公告** @return*/@Overridepublic List<BulletinBoard> getAnnouncement() {return bulletinBoardMapper.getList();}/*** 获取最新失物* @return*/@Overridepublic List<LostVO> getNewLost() {// 获取所有的 LostVO 数据List<LostVO> lostAll = lostService.getLostAll();// 按照 date 字段降序排序,并取前 10 条return lostAll.stream().sorted((o1, o2) -> o2.getDate().compareTo(o1.getDate())) // 降序排序.limit(10).collect(Collectors.toList());// 取前 10 // 收集为 List}/*** 获取友情链接** @return*/@Overridepublic List<FriendshipLinks> getFriendshipLinks() {//获取友情链接return friendshipLinksMapper.getFriendshipLinks();}/*** 更新或新增** @param friendshipLinks*/@Overridepublic void savoOrUpdateFriendshipLinks(FriendshipLinks friendshipLinks) {//如果id等于null是新增if (friendshipLinks.getId() == null) {friendshipLinksMapper.insert(friendshipLinks);} else {friendshipLinksMapper.update(friendshipLinks);}}/*** 删除** @param id*/@Overridepublic void deleteFriendshipLinks(Long id) {friendshipLinksMapper.deleteFriendshipLinks(id);}
}
相关文章:
SpringBoot校园失物招领信息平台
SpringBoot校园失物招领信息平台 文章目录 SpringBoot校园失物招领信息平台1、技术栈2、项目说明2.1、登录注册2.2、管理员端截图2.3、用户端截图 3、核心代码实现3.1、前端首页3.2、前端招领广场3.3、后端业务处理 1、技术栈 本项目采用前后端分离的架构,前端和后…...
代码随想录算法训练营第三十八天
LeetCode题目: 1143. 最长公共子序列1035. 不相交的线53. 最大子数组和392. 判断子序列2094. 找出 3 位偶数(每日一题) 其他: 今日总结 往期打卡 1143. 最长公共子序列 跳转: 1143. 最长公共子序列 学习: 代码随想录公开讲解 问题: 给定两个字符串 text1 和 text2࿰…...
Nginx stream模块是连接级别的负载均衡
在Nginx的stream模块中,upstream的权重配置实现的是连接级别的负载均衡,这和http模块不同。 当客户端发起一个新的TCP连接时,Nginx根据各upstream的权重值选择其中一个upstream建立连接,之后该连接上的所有数据传输都由这个upstre…...
贝叶斯算法
贝叶斯算法是一类基于贝叶斯定理的机器学习算法,它们在分类任务中表现出色,尤其在处理具有不确定性和 probabilistic 关系的数据时具有独特优势。本文将深入探讨贝叶斯算法的核心原理、主要类型以及实际应用案例,带你领略贝叶斯算法在概率推理…...
计算机网络:CPU与时钟的关系
在计算机中,CPU(中央处理器)与时钟的关系是核心且密不可分的。时钟信号是驱动CPU运行的“心跳”,决定了计算机执行指令的节奏和协调性。以下是两者的关键关系及作用: 1. 时钟信号:CPU的“节拍器” 时钟频率(Clock Speed) CPU的时钟频率(如3.5 GHz)表示每秒的时钟周期…...
java中强引用、软应用、弱应用、虚引用
在Java中,引用类型决定了对象的生命周期和垃圾回收的时机。Java提供了四种不同的引用类型:强引用、软引用、弱引用和虚引用。每种引用类型的行为和用途不同,了解这些差异对优化内存管理和垃圾回收非常重要。 1. 强引用(Strong Re…...
分析红黑树工程实用的特点
🧭 本节目标 理解红黑树在工程中的优劣势对比红黑树与其他数据结构(AVL 树、跳表、哈希表等)分析红黑树为何成为内核级应用(如 Linux CFS、内存管理)首选总结红黑树工程上的典型使用建议 一、红黑树工程级使用的主要特…...
C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )
一、引言:理解内存管理的核心价值 在系统级编程领域,内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言,赋予开发者直接操作内存的能力,却也要求开发者深入理解内存布局与生命周期管理。本文将从内…...
如何使用主机名在 CMD 中查找 IP 地址?
在网络中,每个系统都有一个由几位数字组成的唯一标识,称为 IP 地址。然而,记住它们可能是一项艰巨的任务,尤其是当系统数量众多时。例如,互联网上运行的每个网站都有一个 IP 地址,以便其他系统在需要时可以调用它们,但你认为记住我们访问的每个网站的长串数字是可行的吗…...
解读RTOS:第二篇 · 线程/任务管理与调度策略
1. 引言 在 RTOS 中,线程(Task)是最基本的执行单元,它封装了应用功能、资源使用和优先级属性。任务管理与调度策略决定了系统在多任务场景下的响应速度、资源分配效率与实时性保证。理解并掌握任务创建、状态转换、优先级设计和调度算法,是 RTOS 应用开发的核心内容。 2…...
linux下minio的进程管理脚本
准备工作: 参考链接: Deploy MinIO: Single-Node Single-Drive — MinIO Object Storage for Linux 下载: wget https://dl.min.io/server/minio/release/linux-amd64/minio kill-app.sh #!/bin/bash # 文件名: kill-app.sh…...
论文学习_A Survey of Binary Code Similarity
摘要:二进制代码相似性方法的主要目的是比较两个或多个二进制代码片段,以识别它们之间的相似性与差异(研究背景)。由于在许多实际场景中源代码往往不可获取,因此具备比较二进制代码的能力显得尤为重要,例如…...
python标准库--sys - 系统相关功能在算法比赛的应用
目录 1. 快速输入输出 2. 调整递归深度限制 1. 快速输入输出 算法比赛中,大量数据的读写可能成为瓶颈。sys.stdin和sys.stdout比内置的input()和print()效率更高。 import sys# 读取多行输入(每行一个整数) n int(sys.stdin.readline()) …...
运算放大器相关的电路
1运算放大器介绍 解释:运算放大器本质就是一个放大倍数很大的元件,就如上图公式所示 Vp和Vn相差很小但是放大后输出还是会很大。 运算放大器不止上面的三个引脚,他需要独立供电; 如图比较器: 解释:Vp&…...
进程和线程
目录 1. 基本定义 2. 核心区别 3. 优缺点对比 进程和线程是操作系统中用于实现并发执行的两个核心概念,它们既有相似之处,又有明显的区别。下面从多个维度对它们进行对比分析: 1. 基本定义 进程(Process) 进程是程…...
生成对抗网络(GAN)深度解析:理论、技术与应用全景
生成对抗网络(Generative Adversarial Networks,GAN)作为深度学习领域的重要突破,通过对抗训练框架实现了强大的生成能力。本文从理论起源、数学建模、网络架构、工程实现到行业应用,系统拆解GAN的核心机制,涵盖基础理…...
Java面试全记录:Spring Cloud+Kafka+Redis实战解析
Java面试全记录:Spring CloudKafkaRedis实战解析 人物设定 姓名:张伟(随机生成唯一姓名) 年龄:28岁 学历:硕士 工作年限:5年 工作内容: 基于Spring Cloud搭建微服务架构使用Kafka…...
人脸识别deepface相关笔记
人脸识别deepface相关笔记 项目地址项目结构 项目地址 https://github.com/serengil/deepface.git 项目结构...
量子加密通信:守护信息安全的未来之盾
摘要 在数字化时代,信息安全成为全球关注的焦点。传统加密技术面临着被量子计算破解的风险,而量子加密通信作为一种基于量子力学原理的新型加密技术,提供了理论上无条件安全的通信保障。本文将详细介绍量子加密通信的基本原理、技术实现、应用…...
三、transformers基础组件之Model
1. 什么是Model Head Model Head 是连接在模型后的层,通常为1个或多个全连接层Model Head 将模型的编码的表示结果进行映射,以解决不同类型的任务 不同的任务会有不同的Model Head。 2. 模型加载 2.1 在线加载 预训练模型的加载与Tokenizer类似,我们只需要指定想…...
【语法】C++的多态
目录 虚函数的重写: 虚函数 重写(覆盖) 虚函数重写的两个例外: 协变: 析构函数的重写: 练习: final和override关键字 抽象类 接口继承和实现继承 虚函数重写的原理: 打印虚函数表: …...
WebGIS开发新突破:揭秘未来地理信息系统的神秘面纱
你有没有想过,未来的地理信息系统(GIS)会是什么样子?是像电影里那样,一块透明屏幕就能呈现整个城市的实时动态?还是像《钢铁侠》中那样,一个手势就能操控全球地图? 其实,…...
JVM类加载
JVM类加载 1. 类的生命周期(类加载过程)类加载的五个阶段: 2. 类加载器的分类3. 双亲委派模型4. 类的卸载与热加载5.类加载器命名空间隔离 1. 类的生命周期(类加载过程) 类加载的五个阶段: 加载ÿ…...
AD开启交叉选择功能,只选中器件,不选中网络、焊盘
AD开启交叉选择功能,只选中器件,不选中网络、焊盘。 一、打开首选项 二、打开System→Navigationg,配置如下。 三、最后点击OK即可。...
机器学习——集成学习基础
一、鸢尾花数据训练模型 1. 使用鸢尾花数据分别训练集成模型:AdaBoost模型,Gradient Boosting模型 2. 对别两个集成模型的准确率以及报告 3. 两个模型的预测结果进行可视化 需要进行降维处理,两个图像显示在同一个坐标系中 代码展示&…...
C++匿名函数
C 中的匿名函数(Lambda 表达式)是 C11 引入的一项重要特性,它允许你在需要的地方定义一个临时的、无名的函数对象,使代码更加简洁和灵活。 1. 基本语法 Lambda 表达式的基本结构: [capture list](parameter list) -…...
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通 😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。 ❤️ 3. Python爬虫专栏…...
神经网络是如何工作的
人工智能最核心的技术之一,就是神经网络(Neural Networks)。但很多初学者会觉得它是个黑盒:为什么神经网络能识别图片、翻译语言,甚至生成文章? 本文用图解最小代码实现的方式,带你深入理解&am…...
Kubernetes控制平面组件:Kubelet详解(二):核心功能层
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...
【android bluetooth 框架分析 02】【Module详解 13】【CounterMetrics 模块介绍】
1. CounterMetrics 介绍 CounterMetrics 模块代码很少, 我简单介绍一下。 // system/gd/metrics/counter_metrics.cc #define LOG_TAG "BluetoothCounterMetrics"#include "metrics/counter_metrics.h"#include "common/bind.h" #i…...
Matlab自学笔记五十四:符号数学工具箱和符号运算、符号求解、绘图
1.什么是符号数学工具箱? 符号数学工具箱是Matlab针对符号对象的运算功能,它引入了一种特殊的数据类型 - 符号对象; 该数据类型包括符号数字,符号变量,符号表达式和符号函数,还包含符号矩阵,以…...
Matlab 模糊控制平行侧边自动泊车
1、内容简介 Matlab 233-模糊控制平行侧边自动泊车 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...
新书速览|纯血鸿蒙HarmonyOS NEXT原生开发之旅
《纯血鸿蒙HarmonyOS NEXT原生开发之旅》 本书内容 《纯血鸿蒙HarmonyOS NEXT原生开发之旅》全面系统地介绍了基于HarmonyOS NEXT系统进行原生应用开发的实用技巧。全书共12章,内容涵盖从基础工具使用到高级功能实现的各个方面。第1章详细介绍了开发环境的搭建、Ar…...
tinyint(3)数据类型讲解
TINYINT(3) 是数据库中用于定义字段数据类型的一种写法,常见于 MySQL 等数据库系统。下面来详细了解其含义和作用: 数据类型本质 TINYINT 属于整数类型,在不同的数据库系统中,它所占用的存储空间和表示范围通常是固定的。以 MyS…...
manjaro系统详解
1. Manjaro 概述 Manjaro 是一款基于 Arch Linux 的滚动更新发行版,以 用户友好、易用性 和 硬件兼容性 为核心设计理念。它继承了 Arch 的灵活性和软件丰富性,同时通过图形化工具和稳定的更新策略降低了使用门槛,适合从新手到高级用户的广泛…...
# 实时英文 OCR 文字识别:从摄像头到 PyQt5 界面的实现
实时英文 OCR 文字识别:从摄像头到 PyQt5 界面的实现 引言 在数字化时代,文字识别技术(OCR)在众多领域中发挥着重要作用。无论是文档扫描、车牌识别还是实时视频流中的文字提取,OCR 技术都能提供高效且准确的解决方案…...
9.3.云原生架构模式
目录 一、云原生架构核心概念 云原生定义与核心原则 • 四大核心要素:容器化、微服务、DevOps、持续交付 • 核心原则:弹性、可观测性、自动化、不可变基础设施 云原生技术矩阵 • 容器与编排:Docker、Kubernetes、CRI-O • 服务治理&#…...
现代化水库运行管理矩阵平台如何建设?
政策背景 2023年8月24日,水利部发布的水利部关于加快构建现代化水库运行管理矩阵的指导意见中指出,在全面推进水库工程标准化管理的基础上,强化数字赋能,加快构建以推进全覆盖、全要素、全天候、全周期“四全”管理,完…...
木马查杀引擎—关键流程图
记录下近日研究的木马查杀引擎,将关键的实现流程图画下来 PHP AST通道实现 木马查杀调用逻辑 模型训练流程...
基于libevent的异步事件驱动型线程池实现
----------------------| IFoxThread | ← 抽象线程接口|----------------------|| dispatch() || start() || stop() || ... |----------^-----------|--------------------|----------------------| …...
ArcGIS+InVEST+RUSLE:水土流失模拟与流域管理的高效解决方案;水土保持专题地图制作
在全球生态与环境面临严峻挑战的当下,水土流失问题已然成为制约可持续发展的重要因素之一。水土流失不仅影响土地资源的可持续利用,还对生态环境、农业生产以及区域经济发展带来深远影响。因此,科学、精准地模拟与评估水土流失状况࿰…...
#S4U2SELF#S4U2Proxy#CVE-2021-42278/42287
#S4U2SELF Win08创建普通用户 s4u2 xwj456 可以看到普通用户是没用委托属性的 Win08手动赋予委托服务属性 setspn -A wsw/wsw.com s4u2 Win10身份验证 s4u2 xwj456 AS请求 两个勾 两个勾和include-pac记得按上(蓝色) ,发包之前把wiresh…...
利用基于LLM的概念提取和FakeCTI数据集提升网络威胁情报对抗虚假信息活动的能力
摘要 虚假新闻和虚假信息宣传活动的迅速蔓延对公众信任、政治稳定和网络安全构成了重大威胁。传统的网络威胁情报(CTI)方法依赖于域名和社交媒体账号等低级指标,很容易被频繁修改其在线基础设施的对手规避。为了解决这些局限性,我…...
uniapp|实现手机通讯录、首字母快捷导航功能、多端兼容(H5、微信小程序、APP)
基于uniapp实现带首字母快捷导航的通讯录功能,通过拼音转换库实现汉字姓名首字母提取与分类,结合uniapp的scroll-view组件与pageScrollTo API完成滚动定位交互,并引入uni-indexed-list插件优化索引栏性能。 目录 核心功能实现动态索引栏生成联系人列表渲染滚动定位联动性…...
使用PhpStudy搭建Web测试服务器
一、安装PhpStudy 从以下目录下载PhpStudy安装文件 Windows版phpstudy下载 - 小皮面板(phpstudy) (xp.cn) 安装成功之后打开如下界面 点击启动Apache 查看网站地址 在浏览器中输入localhost:88,出现如下页面就ok了 二、与Unity交互 1.配置下载文件路径,点击…...
Qt/C++面试【速通笔记九】—视图框架机制
在Qt中,QGraphicsView和QGraphicsScene是用于构建二维图形界面的核心组件。它们的设计使得开发者能够高效地管理和渲染图形项,支持丰富的用户交互,例如缩放、旋转、平移等。 1. QGraphicsScene和QGraphicsView的基本概念 QGraphicsScene QG…...
react-diff-viewer 如何实现语法高亮
前言 react-diff-viewer 是一个很好的 diff 展示库,但是也有一些坑点和不完善的地方,本文旨在描述如何在这个库中实现自定义语法高亮。 Syntax highlighting is a bit tricky when combined with diff. Here, React Diff Viewer provides a simple rend…...
Python实例题:Django搭建简易博客
目录 Python实例题 题目 1. 创建 Django 项目和应用 2. 配置项目 3. 设计模型 blog_app templates blog_app post_list.html admin.py models.py urls.py views.py blog_project urls.py 代码解释 models.py: admin.py: urls.py&…...
Kotlin 异步初始化值
在一个类初始化的时候或者方法执行的时候,总有一些值是需要的但是不是立即需要的,并且在需要的时候需要阻塞流程来等待值的计算,这时候异步的形式创建这个值是毋庸置疑最好的选择。 为了更好的创建值需要使用 Kotlin 的协程来创建࿰…...
扩展:React 项目执行 yarn eject 后的 config 目录结构详解
扩展:React 项目执行 yarn eject 后的 config 目录结构详解 什么是 yarn eject?React 项目执行 yarn eject 后的 config 目录结构详解📁 config 目录结构各文件作用详解env.jsgetHttpsConfig.jsmodules.jspaths.jswebpack.config.jswebpackDe…...