网站建设_app开发云虚拟主机和云服务器有什么区别
2026/1/8 11:20:14 网站建设 项目流程
网站建设_app开发,云虚拟主机和云服务器有什么区别,温州市住房建设局网站,大连百度快速排名优化摘要 本文详细介绍了一个功能完整的校园综合服务平台的开发过程。该项目采用前后端分离架构#xff0c;后端使用Spring Boot Spring Security JPA MySQL技术栈#xff0c;前端使用Vue3 TypeScript Element Plus技术栈。平台实现了用户管理、课程管理、校园活动、二手市…摘要本文详细介绍了一个功能完整的校园综合服务平台的开发过程。该项目采用前后端分离架构后端使用Spring Boot Spring Security JPA MySQL技术栈前端使用Vue3 TypeScript Element Plus技术栈。平台实现了用户管理、课程管理、校园活动、二手市场、失物招领等核心功能为校园信息化建设提供了一个完整的解决方案。一、项目背景与意义1.1 项目背景随着高校信息化建设的不断深入传统的校园管理模式已无法满足师生多样化的需求。校园中存在多个独立的信息系统如教务系统、活动报名系统、二手交易平台等但这些系统往往数据孤岛严重用户体验不佳。1.2 项目意义一站式服务整合校园各类服务提供统一入口提高效率简化业务流程提升管理效率数据互通打破数据孤岛实现数据共享移动优先响应式设计支持多终端访问二、技术栈选型2.1 后端技术栈技术版本说明Spring Boot3.1.5快速开发框架Spring Security6.1.5安全认证框架Spring Data JPA3.1.5数据持久层MySQL8.0关系型数据库JWT0.11.5令牌认证Lombok1.18.28代码简化工具MapStruct1.5.5对象映射Redis7.0缓存数据库SpringDoc2.2.0API文档2.2 前端技术栈技术版本说明Vue33.3.4前端框架TypeScript5.1.6类型安全Element Plus2.3.8UI组件库Pinia2.1.6状态管理Axios1.5.0HTTP客户端Vue Router4.2.4路由管理VueUse10.3.0组合式API工具集ECharts5.4.3数据可视化Socket.IO4.7.2实时通信2.3 开发工具IntelliJ IDEA Ultimate (后端开发)VS Code / WebStorm (前端开发)MySQL Workbench (数据库管理)Postman (API测试)Git Git Flow (版本控制)Docker Docker Compose (容器化)Jenkins (CI/CD)三、系统架构设计3.1 整体架构text复制下载┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 前端层 │ │ 网关层 │ │ 后端层 │ │ Vue3 TS │───▶│ Nginx/CORS │───▶│ Spring Boot │ │ Element Plus │ │ 网关限流 │ │ 安全认证 │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ▼ ┌─────────────────┐ │ 服务层 │ │ Redis缓存 │ │ ElasticSearch │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ 数据层 │ │ MySQL主从 │ │ MongoDB │ └─────────────────┘3.2 微服务架构设计扩展方案yaml复制下载# 微服务拆分方案 services: - auth-service: 认证授权服务 - user-service: 用户信息服务 - course-service: 课程管理服务 - activity-service: 活动管理服务 - market-service: 二手市场服务 - message-service: 消息通知服务 - file-service: 文件存储服务 - gateway-service: API网关服务3.3 数据库设计优化3.3.1 分表策略sql复制下载-- 历史数据分表示例 CREATE TABLE course_history_2023 ( LIKE courses INCLUDING ALL, archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 按月分区 CREATE TABLE user_logs ( id BIGINT PRIMARY KEY, user_id BIGINT, action VARCHAR(100), created_at TIMESTAMP ) PARTITION BY RANGE (YEAR(created_at) * 100 MONTH(created_at));四、核心功能实现4.1 高级用户认证与授权4.1.1 多因素认证实现java复制下载Service RequiredArgsConstructor public class MultiFactorAuthService { private final UserRepository userRepository; private final RedisTemplateString, String redisTemplate; private final JavaMailSender mailSender; public void sendVerificationCode(String email, AuthType type) { // 生成6位验证码 String code RandomStringUtils.randomNumeric(6); // 存储到Redis5分钟有效 redisTemplate.opsForValue().set( auth:code: type : email, code, 5, TimeUnit.MINUTES ); // 发送邮件 SimpleMailMessage message new SimpleMailMessage(); message.setTo(email); message.setSubject(校园平台验证码); message.setText(您的验证码是 code 5分钟内有效); mailSender.send(message); } public boolean verifyCode(String email, String code, AuthType type) { String storedCode redisTemplate.opsForValue() .get(auth:code: type : email); return code.equals(storedCode); } public enum AuthType { LOGIN, REGISTER, RESET_PASSWORD, TRANSACTION } }4.1.2 OAuth2.0第三方登录java复制下载Configuration public class OAuth2Config { Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository( ClientRegistration.withRegistrationId(github) .clientId(github-client-id) .clientSecret(github-client-secret) .scope(read:user) .authorizationUri(https://github.com/login/oauth/authorize) .tokenUri(https://github.com/login/oauth/access_token) .userInfoUri(https://api.github.com/user) .userNameAttributeName(id) .clientName(GitHub) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri({baseUrl}/login/oauth2/code/{registrationId}) .build() ); } }4.2 智能课程推荐系统4.2.1 推荐算法实现java复制下载Service Slf4j public class CourseRecommendationService { private final CourseRepository courseRepository; private final UserBehaviorRepository behaviorRepository; /** * 基于协同过滤的课程推荐 */ Async public CompletableFutureListCourse recommendCourses(Long userId, int limit) { // 1. 获取用户历史行为 ListUserBehavior userBehaviors behaviorRepository .findByUserIdAndActionIn(userId, Arrays.asList(ENROLL, VIEW, LIKE)); // 2. 寻找相似用户 ListLong similarUserIds findSimilarUsers(userId, userBehaviors); // 3. 获取相似用户喜欢的课程 ListCourse recommendedCourses courseRepository .findRecommendedCourses(similarUserIds, userId, limit); // 4. 基于内容过滤课程标签匹配 ListString userTags extractUserTags(userBehaviors); ListCourse contentBased contentBasedRecommendation(userTags, limit); // 5. 混合推荐 return CompletableFuture.completedFuture( hybridRecommendation(recommendedCourses, contentBased, limit) ); } /** * 基于用户画像的推荐 */ public ListCourse recommendByUserProfile(User user) { return courseRepository.createNativeQuery( SELECT c.* FROM courses c WHERE c.college :college AND c.major :major AND c.grade_level :gradeLevel AND c.status OPEN AND c.enrolled_count c.capacity * 0.8 ORDER BY c.rating DESC, c.enrolled_count DESC LIMIT 10 ) .setParameter(college, user.getCollege()) .setParameter(major, user.getMajor()) .setParameter(gradeLevel, calculateGradeLevel(user.getGrade())) .getResultList(); } private ListLong findSimilarUsers(Long userId, ListUserBehavior behaviors) { // 实现基于行为的相似度计算 // 可以使用Jaccard相似度或余弦相似度 return behaviorRepository.findSimilarUsers( userId, behaviors.stream() .map(UserBehavior::getCourseId) .collect(Collectors.toList()) ); } }4.2.2 推荐服务REST接口java复制下载RestController RequestMapping(/api/recommend) RequiredArgsConstructor public class RecommendationController { private final CourseRecommendationService recommendationService; private final RedisTemplateString, String redisTemplate; GetMapping(/courses) Cacheable(value recommendations, key user: #userId) public ResponseEntityListCourseResponse getRecommendations( RequestParam(defaultValue 10) int limit, AuthenticationPrincipal UserDetails userDetails) { Long userId Long.parseLong(userDetails.getUsername()); // 尝试从缓存获取 String cacheKey recommend:user: userId; String cached redisTemplate.opsForValue().get(cacheKey); if (cached ! null) { return ResponseEntity.ok( objectMapper.readValue(cached, new TypeReference() {}) ); } // 实时计算推荐 ListCourse courses recommendationService .recommendCourses(userId, limit).join(); ListCourseResponse response courses.stream() .map(courseMapper::toResponse) .collect(Collectors.toList()); // 缓存结果有效期1小时 redisTemplate.opsForValue().set( cacheKey, objectMapper.writeValueAsString(response), 1, TimeUnit.HOURS ); return ResponseEntity.ok(response); } PostMapping(/feedback) public ResponseEntity? feedback( RequestBody RecommendationFeedbackRequest request) { // 收集用户反馈优化推荐算法 recommendationService.recordFeedback( request.getUserId(), request.getCourseId(), request.getAction(), request.getScore() ); return ResponseEntity.ok().build(); } }4.3 实时通知系统4.3.1 WebSocket配置java复制下载Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker(/topic, /queue); config.setApplicationDestinationPrefixes(/app); config.setUserDestinationPrefix(/user); } Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws) .setAllowedOriginPatterns(*) .withSockJS() .setWebSocketEnabled(true) .setHeartbeatTime(25000) .setDisconnectDelay(5000); } Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new AuthChannelInterceptor()); } } Component RequiredArgsConstructor public class NotificationService { private final SimpMessagingTemplate messagingTemplate; /** * 发送个人通知 */ public void sendPersonalNotification(Long userId, NotificationMessage message) { messagingTemplate.convertAndSendToUser( userId.toString(), /queue/notifications, message ); } /** * 发送广播通知 */ public void sendBroadcastNotification(String topic, NotificationMessage message) { messagingTemplate.convertAndSend(/topic/ topic, message); } /** * 发送课程相关通知 */ public void sendCourseNotification(Long courseId, NotificationType type, Object data) { NotificationMessage message NotificationMessage.builder() .type(type) .title(课程通知) .content(buildCourseContent(type, data)) .timestamp(LocalDateTime.now()) .data(data) .build(); messagingTemplate.convertAndSend(/topic/course. courseId, message); } Data Builder public static class NotificationMessage { private NotificationType type; private String title; private String content; private LocalDateTime timestamp; private Object data; private String link; } public enum NotificationType { SYSTEM, COURSE, ACTIVITY, MARKET, MESSAGE, EMERGENCY } }4.3.2 前端WebSocket集成typescript复制下载// websocket.service.ts import { ref, onUnmounted } from vue import Stomp from stompjs import SockJS from sockjs-client import { useAuthStore } from /stores/auth export class WebSocketService { private stompClient: any null private subscriptions: Mapstring, any new Map() private isConnected ref(false) constructor() { this.initConnection() } private initConnection() { const authStore useAuthStore() const socket new SockJS(/api/ws) this.stompClient Stomp.over(socket) // 添加认证头 this.stompClient.connect({ X-Authorization: Bearer ${authStore.token} }, () { console.log(WebSocket connected) this.isConnected.value true this.setupSubscriptions() }, (error: any) { console.error(WebSocket connection error:, error) this.isConnected.value false setTimeout(() this.initConnection(), 5000) }) } private setupSubscriptions() { // 订阅个人通知 this.subscribe(/user/queue/notifications, (message) { this.handleNotification(JSON.parse(message.body)) }) // 订阅课程通知 this.subscribe(/topic/course.*, (message) { this.handleCourseNotification(JSON.parse(message.body)) }) } public subscribe(destination: string, callback: Function): string { if (!this.stompClient || !this.isConnected.value) { console.warn(WebSocket not connected) return } const subscription this.stompClient.subscribe(destination, (msg: any) { callback(msg) }) const subscriptionId sub-${Date.now()} this.subscriptions.set(subscriptionId, subscription) return subscriptionId } public unsubscribe(subscriptionId: string) { const subscription this.subscriptions.get(subscriptionId) if (subscription) { subscription.unsubscribe() this.subscriptions.delete(subscriptionId) } } public send(destination: string, body: any) { if (this.stompClient this.isConnected.value) { this.stompClient.send(destination, {}, JSON.stringify(body)) } } public disconnect() { if (this.stompClient) { this.stompClient.disconnect() this.isConnected.value false this.subscriptions.clear() } } private handleNotification(notification: any) { // 显示通知 ElNotification({ title: notification.title, message: notification.content, type: this.getNotificationType(notification.type), duration: 5000, onClick: () { if (notification.link) { router.push(notification.link) } } }) // 存储到本地 this.storeNotification(notification) } private getNotificationType(type: string): any { const typeMap: Recordstring, any { SYSTEM: info, COURSE: success, ACTIVITY: warning, EMERGENCY: error } return typeMap[type] || info } private storeNotification(notification: any) { const notifications JSON.parse(localStorage.getItem(notifications) || []) notifications.unshift({ ...notification, read: false, id: Date.now() }) // 只保留最近100条 if (notifications.length 100) { notifications.pop() } localStorage.setItem(notifications, JSON.stringify(notifications)) } } // 使用组合式API封装 export const useWebSocket () { const notifications refany[]([]) const unreadCount ref(0) const wsService new WebSocketService() const loadNotifications () { const stored localStorage.getItem(notifications) if (stored) { notifications.value JSON.parse(stored) unreadCount.value notifications.value.filter(n !n.read).length } } const markAsRead (id: number) { const index notifications.value.findIndex(n n.id id) if (index ! -1) { notifications.value[index].read true saveNotifications() unreadCount.value notifications.value.filter(n !n.read).length } } const markAllAsRead () { notifications.value.forEach(n n.read true) saveNotifications() unreadCount.value 0 } const saveNotifications () { localStorage.setItem(notifications, JSON.stringify(notifications.value)) } onUnmounted(() { wsService.disconnect() }) return { notifications, unreadCount, loadNotifications, markAsRead, markAllAsRead, sendMessage: wsService.send.bind(wsService), subscribe: wsService.subscribe.bind(wsService), unsubscribe: wsService.unsubscribe.bind(wsService) } }4.4 数据分析与可视化4.4.1 数据统计服务java复制下载Service Transactional(readOnly true) RequiredArgsConstructor public class AnalyticsService { private final CourseRepository courseRepository; private final EventRepository eventRepository; private final MarketRepository marketRepository; private final UserRepository userRepository; /** * 获取平台数据概览 */ public PlatformOverview getPlatformOverview() { PlatformOverview overview new PlatformOverview(); // 用户统计 overview.setTotalUsers(userRepository.count()); overview.setActiveUsers(getActiveUserCount()); overview.setNewUsersToday(getNewUserCountToday()); // 课程统计 overview.setTotalCourses(courseRepository.count()); overview.setActiveCourses(courseRepository.countByStatus(OPEN)); overview.setTotalEnrollments(courseRepository.sumEnrolledCount()); // 活动统计 overview.setTotalEvents(eventRepository.count()); overview.setUpcomingEvents(eventRepository.countUpcomingEvents()); // 市场统计 overview.setTotalProducts(marketRepository.count()); overview.setSoldProducts(marketRepository.countByStatus(SOLD)); return overview; } /** * 获取用户活跃度分析 */ public UserActivityAnalysis getUserActivityAnalysis(LocalDate startDate, LocalDate endDate) { return userRepository.createNativeQuery( SELECT DATE(created_at) as date, COUNT(*) as new_users, SUM(CASE WHEN last_login_at DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 1 ELSE 0 END) as active_users, AVG(login_count) as avg_logins FROM users WHERE created_at BETWEEN :startDate AND :endDate GROUP BY DATE(created_at) ORDER BY date ) .setParameter(startDate, startDate.atStartOfDay()) .setParameter(endDate, endDate.atTime(LocalTime.MAX)) .getResultStream() .collect(Collectors.toMap( row - ((Date) row[0]).toLocalDate(), row - new DailyActivity( ((Number) row[1]).longValue(), ((Number) row[2]).longValue(), ((Number) row[3]).doubleValue() ) )); } /** * 课程热度分析 */ public ListCoursePopularity getCoursePopularityRanking(int limit) { return courseRepository.createNativeQuery( SELECT c.id, c.course_name, c.course_code, c.enrolled_count, COUNT(DISTINCT sc.student_id) as total_students, AVG(sc.score) as avg_score, COUNT(DISTINCT cr.id) as review_count, AVG(cr.rating) as avg_rating FROM courses c LEFT JOIN student_courses sc ON c.id sc.course_id LEFT JOIN course_reviews cr ON c.id cr.course_id GROUP BY c.id, c.course_name, c.course_code, c.enrolled_count ORDER BY (c.enrolled_count * 0.3 COUNT(DISTINCT sc.student_id) * 0.2 AVG(sc.score) * 0.2 AVG(cr.rating) * 0.3) DESC LIMIT :limit ) .setParameter(limit, limit) .getResultList(); } /** * 生成数据分析报告 */ Async public CompletableFutureAnalysisReport generateMonthlyReport(int year, int month) { return CompletableFuture.supplyAsync(() - { AnalysisReport report new AnalysisReport(); // 数据收集 report.setPlatformOverview(getPlatformOverview()); report.setUserActivity(getUserActivityAnalysis( LocalDate.of(year, month, 1), LocalDate.of(year, month, 1).withDayOfMonth( LocalDate.of(year, month, 1).lengthOfMonth() ) )); report.setCourseAnalysis(getCoursePopularityRanking(20)); report.setMarketAnalysis(getMarketTransactionAnalysis(year, month)); // 生成见解 report.setInsights(generateInsights(report)); // 生成建议 report.setRecommendations(generateRecommendations(report)); return report; }); } private ListString generateInsights(AnalysisReport report) { ListString insights new ArrayList(); // 基于数据分析生成见解 if (report.getPlatformOverview().getNewUsersToday() 100) { insights.add(今日新增用户超过100人用户增长迅速); } if (report.getCourseAnalysis().stream() .anyMatch(c - c.getEnrolledCount() c.getCapacity() * 0.9)) { insights.add(部分热门课程接近满员建议增加容量或开设新班); } return insights; } }4.4.2 前端数据可视化vue复制下载template div classanalytics-dashboard !-- 数据概览卡片 -- el-row :gutter20 classoverview-cards el-col :xs24 :sm12 :md6 v-forcard in overviewCards :keycard.title el-card classstat-card shadowhover div classcard-content div classstat-icon :style{ backgroundColor: card.color } el-icon :size24 component :iscard.icon / /el-icon /div div classstat-info h3{{ card.value }}/h3 p{{ card.title }}/p div classtrend :classcard.trend 0 ? up : down el-icon TrendingUp v-ifcard.trend 0 / TrendingDown v-else / /el-icon span{{ Math.abs(card.trend) }}%/span /div /div /div /el-card /el-col /el-row !-- 图表区域 -- el-row :gutter20 classchart-row el-col :xs24 :lg12 el-card template #header div classchart-header h3用户活跃度趋势/h3 el-select v-modelactiveDays sizesmall el-option label最近7天 :value7 / el-option label最近30天 :value30 / el-option label最近90天 :value90 / /el-select /div /template div refuserChartRef styleheight: 300px;/div /el-card /el-col el-col :xs24 :lg12 el-card template #header h3课程热度分布/h3 /template div refcourseChartRef styleheight: 300px;/div /el-card /el-col /el-row !-- 详细数据表格 -- el-card classdetail-table template #header h3详细数据/h3 /template el-table :datadetailedData v-loadingloading el-table-column propdate label日期 width120 / el-table-column propnewUsers label新增用户 width100 / el-table-column propactiveUsers label活跃用户 width100 / el-table-column propcourseEnrollments label课程报名 width100 / el-table-column propeventRegistrations label活动报名 width100 / el-table-column propmarketTransactions label交易数量 width100 / el-table-column label操作 width100 template #defaultscope el-button typetext clickviewDetails(scope.row) 详情 /el-button /template /el-table-column /el-table div classtable-footer el-pagination v-model:current-pagecurrentPage v-model:page-sizepageSize :totaltotal current-changeloadData / /div /el-card /div /template script setup langts import { ref, onMounted, watch, nextTick } from vue import * as echarts from echarts import { User, Book, Calendar, ShoppingCart, TrendingUp, TrendingDown } from element-plus/icons-vue import { analyticsApi } from /api/analytics // 响应式数据 const overviewCards ref([ { title: 总用户数, value: 0, icon: User, color: #409eff, trend: 0 }, { title: 总课程数, value: 0, icon: Book, color: #67c23a, trend: 0 }, { title: 活动数量, value: 0, icon: Calendar, color: #e6a23c, trend: 0 }, { title: 交易数量, value: 0, icon: ShoppingCart, color: #f56c6c, trend: 0 } ]) const activeDays ref(7) const userChartRef refHTMLElement() const courseChartRef refHTMLElement() const detailedData refany[]([]) const loading ref(false) const currentPage ref(1) const pageSize ref(10) const total ref(0) let userChart: echarts.ECharts | null null let courseChart: echarts.ECharts | null null // 初始化图表 const initCharts () { if (userChartRef.value) { userChart echarts.init(userChartRef.value) window.addEventListener(resize, () userChart?.resize()) } if (courseChartRef.value) { courseChart echarts.init(courseChartRef.value) window.addEventListener(resize, () courseChart?.resize()) } } // 加载概览数据 const loadOverview async () { try { const data await analyticsApi.getPlatformOverview() overviewCards.value[0].value data.totalUsers.toLocaleString() overviewCards.value[0].trend data.userGrowthRate || 0 overviewCards.value[1].value data.totalCourses.toLocaleString() overviewCards.value[1].trend data.courseGrowthRate || 0 overviewCards.value[2].value data.totalEvents.toLocaleString() overviewCards.value[2].trend data.eventGrowthRate || 0 overviewCards.value[3].value data.totalTransactions.toLocaleString() overviewCards.value[3].trend data.transactionGrowthRate || 0 } catch (error) { console.error(加载概览数据失败:, error) } } // 加载图表数据 const loadChartData async () { try { const userActivity await analyticsApi.getUserActivity(activeDays.value) // 更新用户活跃度图表 if (userChart) { const option { tooltip: { trigger: axis }, legend: { data: [新增用户, 活跃用户] }, xAxis: { type: category, data: userActivity.map((item: any) item.date) }, yAxis: { type: value }, series: [ { name: 新增用户, type: line, smooth: true, data: userActivity.map((item: any) item.newUsers), itemStyle: { color: #409eff } }, { name: 活跃用户, type: line, smooth: true, data: userActivity.map((item: any) item.activeUsers), itemStyle: { color: #67c23a } } ] } userChart.setOption(option) } // 更新课程热度图表 const coursePopularity await analyticsApi.getCoursePopularity() if (courseChart) { const option { tooltip: { trigger: item }, legend: { orient: vertical, left: left }, series: [ { type: pie, radius: 50%, data: coursePopularity.map((item: any) ({ name: item.courseName, value: item.enrolledCount })), emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: rgba(0, 0, 0, 0.5) } } } ] } courseChart.setOption(option) } } catch (error) { console.error(加载图表数据失败:, error) } } // 加载详细数据 const loadDetailedData async () { loading.value true try { const response await analyticsApi.getDetailedData({ page: currentPage.value, size: pageSize.value }) detailedData.value response.data total.value response.total } catch (error) { console.error(加载详细数据失败:, error) } finally { loading.value false } } // 查看详情 const viewDetails (row: any) { // 实现详情查看逻辑 console.log(查看详情:, row) } // 监听activeDays变化 watch(activeDays, () { loadChartData() }) // 初始化 onMounted(async () { await nextTick() initCharts() await Promise.all([ loadOverview(), loadChartData(), loadDetailedData() ]) }) // 清理 onUnmounted(() { if (userChart) { userChart.dispose() } if (courseChart) { courseChart.dispose() } }) /script style scoped langscss .analytics-dashboard { padding: 20px; .overview-cards { margin-bottom: 20px; .stat-card { .card-content { display: flex; align-items: center; .stat-icon { width: 48px; height: 48px; border-radius: 8px; display: flex; align-items: center; justify-content: center; margin-right: 16px; color: white; } .stat-info { flex: 1; h3 { margin: 0 0 4px 0; font-size: 24px; color: #303133; } p { margin: 0 0 8px 0; color: #909399; font-size: 14px; } .trend { display: flex; align-items: center; font-size: 12px; .up { color: #67c23a; } .down { color: #f56c6c; } .el-icon { margin-right: 4px; } } } } } } .chart-row { margin-bottom: 20px; .chart-header { display: flex; justify-content: space-between; align-items: center; } } .detail-table { .table-footer { margin-top: 20px; display: flex; justify-content: center; } } } /script4.5 高级搜索功能4.5.1 使用ElasticSearch实现全文搜索java复制下载Configuration EnableElasticsearchRepositories public class ElasticsearchConfig extends AbstractElasticsearchConfiguration { Value(${elasticsearch.host}) private String host; Value(${elasticsearch.port}) private int port; Override public RestHighLevelClient elasticsearchClient() { ClientConfiguration clientConfiguration ClientConfiguration.builder() .connectedTo(host : port) .build(); return RestClients.create(clientConfiguration).rest(); } } Document(indexName courses) Data public class CourseIndex { Id private Long id; Field(type FieldType.Text, analyzer ik_max_word, searchAnalyzer ik_smart) private String courseName; Field(type FieldType.Keyword) private String courseCode; Field(type FieldType.Text, analyzer ik_max_word) private String description; Field(type FieldType.Keyword) private String teacherName; Field(type FieldType.Keyword) private String college; Field(type FieldType.Integer) private Integer credit; Field(type FieldType.Integer) private Integer capacity; Field(type FieldType.Integer) private Integer enrolledCount; Field(type FieldType.Date) private LocalDateTime createdAt; } Repository public

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询