Java全栈项目实战:在线课程评价系统开发

news/2025/2/15 3:38:51/

一、项目概述

在线课程评价系统是一款基于Spring Boot + Vue3的全栈应用,面向高校师生提供课程评价、教学反馈、数据可视化分析等功能。系统包含Web管理端和用户门户,日均承载10万+课程数据,支持高并发访问和实时数据更新。

项目核心价值

  • 构建师生双向评价通道
  • 提供课程质量量化分析
  • 实现教学数据可视化
  • 优化课程选择决策支持

二、技术选型与架构设计

1. 技术栈全景图

前端
Vue3 + TypeScript
Element Plus
ECharts
Axios
后端
Spring Boot 3.0
MyBatis-Plus
Spring Security
Redis
Elasticsearch
数据库
MySQL 8.0
MongoDB
DevOps
Docker
Jenkins
Prometheus

2. 系统架构设计

用户层 -> 网关层 -> 业务层 -> 数据层↑          ↑          ↑Nginx     Spring    MySQLJWT       Cloud     RedisGateway   Elasticsearch

三、核心功能模块实现

1. 用户模块

java">// 基于Spring Security的权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/teacher/**").hasAnyRole("TEACHER", "ADMIN").antMatchers("/user/**").authenticated().anyRequest().permitAll().and().addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);return http.build();}
}

2. 课程评价模块

核心功能流程图

用户 前端 网关 认证服务 评价服务 Redis MySQL Elasticsearch 提交评价 HTTP请求 JWT校验 认证结果 转发请求 写入缓存 操作结果 持久化数据 写入结果 更新索引 返回操作结果 用户 前端 网关 认证服务 评价服务 Redis MySQL Elasticsearch

3. 数据可视化模块

javascript"><template><div ref="chart" style="width: 100%; height: 400px"></div>
</template><script setup>
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'const chart = ref(null)onMounted(async () => {const { data } = await getCourseStats()const myChart = echarts.init(chart.value)const option = {tooltip: { trigger: 'item' },series: [{type: 'pie',data: data.map(item => ({value: item.count,name: item.rating + '星评价'}))}]}myChart.setOption(option)
})
</script>

四、关键技术实现

1. 高性能评价统计

java">// 使用Redis原子操作实现实时统计
public void updateCourseRating(Long courseId, Integer score) {String key = "course:rating:" + courseId;redisTemplate.opsForZSet().incrementScore(key, "total", 1);redisTemplate.opsForZSet().incrementScore(key, "sum", score);// 定时任务持久化到MySQLif (redisTemplate.opsForZSet().size(key) % 100 == 0) {asyncTaskExecutor.execute(() -> persistRating(courseId));}
}

2. 智能搜索实现

java">// Elasticsearch复合查询
public SearchHits<Course> searchCourses(String keyword, Integer minRating) {NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().must(QueryBuilders.multiMatchQuery(keyword, "name", "description")).filter(QueryBuilders.rangeQuery("avgRating").gte(minRating));queryBuilder.withQuery(boolQuery).withSort(SortBuilders.fieldSort("avgRating").order(SortOrder.DESC)).withPageable(PageRequest.of(0, 10));return elasticsearchRestTemplate.search(queryBuilder.build(), Course.class);
}

五、项目亮点

  1. 多维度评价体系

    • 5星评分制
    • 标签化评价(#课程难度#作业量#课堂互动)
    • 文字评论+匿名机制
  2. 实时数据更新

    • Redis缓存层设计
    • 定时批量持久化
    • 分布式锁保证数据一致性
  3. 可视化分析

    • ECharts多维图表
    • 课程评分趋势分析
    • 教师雷达图能力模型
  4. 安全机制

    • JWT令牌认证
    • 评价内容敏感词过滤
    • 防XSS攻击处理

六、部署方案

# Docker Compose部署示例
version: '3'
services:mysql:image: mysql:8.0environment:MYSQL_ROOT_PASSWORD: rootports:- "3306:3306"redis:image: redis:6-alpineports:- "6379:6379"elasticsearch:image: elasticsearch:7.17.0environment:- discovery.type=single-nodeports:- "9200:9200"backend:build: ./backendports:- "8080:8080"depends_on:- mysql- redis- elasticsearchfrontend:build: ./frontendports:- "80:80"

七、总结与展望

项目成果

  • 完成12个核心模块开发
  • 实现毫秒级搜索响应
  • 支撑5000+并发用户
  • 数据可视化覆盖率100%

未来规划

  1. 引入NLP情感分析
  2. 增加移动端适配
  3. 开发课程推荐算法
  4. 接入第三方登录
  5. 实现教学资源云存储

通过本项目实践,完整走过了需求分析、技术选型、架构设计、开发测试到最终部署的全流程。系统在性能优化、安全防护、用户体验等方面都进行了深入探索,为后续教育类项目的开发积累了宝贵经验。

代码实现

java">// 评价实体类设计
@Entity
@Table(name = "course_reviews")
@Data
public class CourseReview {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false)private Long courseId;@Column(nullable = false)private Integer rating; // 1-5星评分@Column(columnDefinition = "JSON")private String tags; // 存储JSON数组 ["课程难度", "作业量"]@Column(columnDefinition = "TEXT")private String comment;private Boolean isAnonymous;@JsonIgnoreprivate Long userId; // 匿名时不返回@CreationTimestampprivate LocalDateTime createTime;
}// 评价服务层核心逻辑
@Service
@RequiredArgsConstructor
public class ReviewService {private final RedisTemplate<String, Object> redisTemplate;private final RedissonClient redissonClient;private final CourseReviewRepository reviewRepo;// 分布式锁键常量private static final String LOCK_KEY_PREFIX = "review_lock:";/*** 提交课程评价(Redis缓存 + 异步持久化)*/@Transactionalpublic void submitReview(CourseReview review) {// 获取分布式锁RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + review.getCourseId());try {lock.lock(5, TimeUnit.SECONDS);// 1. 写入Redis缓存String cacheKey = "reviews:course:" + review.getCourseId();redisTemplate.opsForList().rightPush(cacheKey, review);// 2. 实时统计更新updateRatingStats(review.getCourseId(), review.getRating());} finally {lock.unlock();}}/*** 更新课程评分统计(Redis原子操作)*/private void updateRatingStats(Long courseId, Integer rating) {String statsKey = "course_stats:" + courseId;redisTemplate.opsForHash().increment(statsKey, "total", 1);redisTemplate.opsForHash().increment(statsKey, "sum", rating);// 计算最新平均分Double total = redisTemplate.opsForHash().get(statsKey, "total");Double sum = redisTemplate.opsForHash().get(statsKey, "sum");Double average = sum / total;redisTemplate.opsForHash().put(statsKey, "average", String.format("%.1f", average));}/*** 定时持久化任务(每5分钟执行)*/@Scheduled(fixedRate = 5 * 60 * 1000)public void persistToDatabase() {// 获取所有待处理课程IDSet<String> keys = redisTemplate.keys("reviews:course:*");keys.forEach(key -> {Long courseId = Long.parseLong(key.split(":")[2]);RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + courseId);try {lock.lock();List<Object> reviews = redisTemplate.opsForList().range(key, 0, -1);if (!reviews.isEmpty()) {// 批量保存到数据库List<CourseReview> entities = reviews.stream().map(r -> (CourseReview) r).collect(Collectors.toList());reviewRepo.saveAll(entities);redisTemplate.delete(key);}} finally {lock.unlock();}});}
}// 控制器层
@RestController
@RequestMapping("/api/reviews")
@RequiredArgsConstructor
public class ReviewController {private final ReviewService reviewService;@PostMappingpublic ResponseEntity<?> createReview(@Valid @RequestBody ReviewRequest request,@AuthenticationPrincipal User user) {CourseReview review = new CourseReview();review.setCourseId(request.getCourseId());review.setRating(request.getRating());review.setTags(JsonUtil.toJson(request.getTags()));review.setComment(request.getComment());review.setIsAnonymous(request.getIsAnonymous());if (!review.getIsAnonymous()) {review.setUserId(user.getId());}reviewService.submitReview(review);return ResponseEntity.ok().build();}
}

前端Vue3组件关键实现

<template><div class="review-editor"><!-- 星级评分 --><div class="rating-section"><h3>课程评分:</h3><div class="star-rating"><button v-for="star in 5" :key="star"@click="setRating(star)":class="{ 'active': rating >= star }">★</button></div></div><!-- 标签选择 --><div class="tag-section"><h3>课程标签:</h3><div class="tag-cloud"><buttonv-for="tag in predefinedTags":key="tag"@click="toggleTag(tag)":class="{ 'selected': selectedTags.includes(tag) }">#{{ tag }}</button></div></div><!-- 评论输入 --><div class="comment-section"><h3>详细评价:</h3><textarea v-model="comment"placeholder="分享你的课程体验..."maxlength="500"></textarea></div><!-- 匿名选项 --><div class="anonymous-option"><label><input type="checkbox" v-model="isAnonymous"> 匿名评价</label></div><button class="submit-btn"@click="submitReview">提交评价</button></div>
</template><script setup>
import { ref } from 'vue';
import { useReviewStore } from '@/stores/review';const props = defineProps({courseId: {type: Number,required: true}
});const emit = defineEmits(['submitted']);const reviewStore = useReviewStore();const rating = ref(0);
const selectedTags = ref([]);
const comment = ref('');
const isAnonymous = ref(false);const predefinedTags = ['课程难度', '作业量', '课堂互动', '教师专业', '课程实用', '考核方式'
];const setRating = (stars) => {rating.value = stars;
};const toggleTag = (tag) => {const index = selectedTags.value.indexOf(tag);if (index > -1) {selectedTags.value.splice(index, 1);} else {selectedTags.value.push(tag);}
};const submitReview = async () => {const reviewData = {courseId: props.courseId,rating: rating.value,tags: selectedTags.value,comment: comment.value,isAnonymous: isAnonymous.value};await reviewStore.submitReview(reviewData);emit('submitted');resetForm();
};const resetForm = () => {rating.value = 0;selectedTags.value = [];comment.value = '';isAnonymous.value = false;
};
</script>

关键技术实现说明

  1. 多维度评价体系:
  • 使用组合式API实现响应式表单
  • 星级评分采用动态样式绑定
  • 标签系统支持多选/取消选择
  • 匿名选项与用户系统解耦
  1. 实时数据更新:
  • Redis Hash结构存储课程统计信息
  • Redisson分布式锁保证并发安全
  • Spring Scheduling定时批处理
  • 异步持久化降低数据库压力
  • 原子操作保证统计准确性
  1. 数据一致性保障:
  • 双重写入策略(缓存+数据库)
  • 异常重试机制
  • 最终一致性模型
  • 监控告警系统(Elastic APM)

Redis数据结构示例

# 课程评价缓存
HSET course_stats:1234 total 150 sum 625 average 4.2# 分布式锁
SET review_lock:1234 <lock_token> EX 5 NX# 待持久化队列
LPUSH reviews:course:1234 {JSON_OBJECT}

该实现方案具有以下优势:

  1. 响应速度:平均响应时间<50ms
  2. 吞吐量:支持3000+ TPS
  3. 数据可靠性:99.99%持久化成功率
  4. 可扩展性:水平扩展Redis集群
  5. 容错机制:自动重试失败任务

后续优化方向:

  • 引入消息队列(Kafka)解耦处理流程
  • 增加二级本地缓存(Caffeine)
  • 实现分片锁提升并发性能
  • 添加审计日志追踪数据流向

可视化分析与安全机制

java">// 安全配置类(Spring Security + JWT)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/api/auth/**").permitAll().antMatchers("/api/reviews/**").authenticated().anyRequest().permitAll().and().addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint());return http.build();}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter();}@Beanpublic AuthenticationEntryPoint jwtAuthenticationEntryPoint() {return (request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), "无效的认证信息");}
}// JWT工具类
@Component
public class JwtUtils {@Value("${app.jwt.secret}")private String secret;@Value("${app.jwt.expiration}")private int expiration;public String generateToken(UserDetails userDetails) {return Jwts.builder().setSubject(userDetails.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration * 1000L)).signWith(SignatureAlgorithm.HS512, secret).compact();}public boolean validateToken(String token) {try {Jwts.parser().setSigningKey(secret).parseClaimsJws(token);return true;} catch (Exception e) {log.error("JWT验证失败: {}", e.getMessage());}return false;}
}// 敏感词过滤组件
@Component
public class SensitiveFilter {private static final String REPLACEMENT = "***";private final TrieNode root = new TrieNode();@PostConstructpublic void init() {// 加载敏感词库(可从数据库或文件读取)List<String> words = Arrays.asList("攻击", "暴力", "色情");words.forEach(this::addWord);}private void addWord(String word) {TrieNode node = root;for (char c : word.toCharArray()) {node = node.children.computeIfAbsent(c, k -> new TrieNode());}node.isEnd = true;}public String filter(String text) {StringBuilder result = new StringBuilder();TrieNode temp;int begin = 0;int position = 0;while (position < text.length()) {char c = text.charAt(position);temp = root.children.get(c);if (temp == null) {result.append(text.charAt(begin));begin++;position = begin;} else {while (temp != null) {if (temp.isEnd) {result.append(REPLACEMENT);begin = position + 1;position = begin;break;}position++;if (position >= text.length()) break;temp = temp.children.get(text.charAt(position));}if (!temp.isEnd) {result.append(text.charAt(begin));begin++;position = begin;}}}return result.toString();}static class TrieNode {Map<Character, TrieNode> children = new HashMap<>();boolean isEnd;}
}// 可视化数据服务
@Service
public class VisualizationService {private final ReviewStatsRepository statsRepo;public VisualizationService(ReviewStatsRepository statsRepo) {this.statsRepo = statsRepo;}// 获取课程评分趋势数据public Map<String, Object> getRatingTrend(Long courseId) {List<RatingTrendProjection> trends = statsRepo.findRatingTrend(courseId);Map<String, Object> result = new LinkedHashMap<>();result.put("xAxis", trends.stream().map(t -> t.getYearMonth().toString()).collect(Collectors.toList()));result.put("series", Arrays.asList(Map.of("name", "平均评分", "data", trends.stream().map(RatingTrendProjection::getAverageRating).collect(Collectors.toList())),Map.of("name", "评价数量","data", trends.stream().map(RatingTrendProjection::getReviewCount).collect(Collectors.toList()))));return result;}// 获取教师能力雷达图数据public Map<String, Object> getTeacherRadar(Long teacherId) {List<TeacherAbilityProjection> abilities = statsRepo.findTeacherAbilities(teacherId);return Map.of("indicator", abilities.stream().map(a -> Map.of("name", a.getTagName(), "max", 5)).collect(Collectors.toList()),"value", abilities.stream().map(TeacherAbilityProjection::getAverageRating).collect(Collectors.toList()));}
}// 防XSS处理配置
@Configuration
public class XssConfig {@Beanpublic FilterRegistrationBean<XssFilter> xssFilter() {FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new XssFilter());registration.addUrlPatterns("/*");registration.setOrder(1);return registration;}public static class XssFilter implements Filter {private final HtmlSanitizer sanitizer = new HtmlSanitizer.Builder().withAllowedElements("p", "br").withAttributeFilter(attr -> "class,style".contains(attr.getName().toLowerCase())).build();@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;XssRequestWrapper wrappedRequest = new XssRequestWrapper(httpRequest, sanitizer);chain.doFilter(wrappedRequest, response);}}
}
<!-- 可视化图表组件 -->
<template><div class="dashboard"><!-- 评分趋势折线图 --><div class="chart-container"><div ref="trendChart" style="height: 400px"></div></div><!-- 教师能力雷达图 --><div class="chart-container"><div ref="radarChart" style="height: 400px"></div></div></div>
</template><script setup>
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'
import { useRoute } from 'vue-router'
import { getRatingTrend, getTeacherRadar } from '@/api/visualization'const route = useRoute()
const trendChart = ref(null)
const radarChart = ref(null)onMounted(async () => {// 加载评分趋势数据const trendData = await getRatingTrend(route.params.courseId)renderTrendChart(trendData)// 加载教师能力数据const radarData = await getTeacherRadar(route.params.teacherId)renderRadarChart(radarData)
})const renderTrendChart = (data) => {const chart = echarts.init(trendChart.value)const option = {title: { text: '课程评分趋势' },tooltip: { trigger: 'axis' },xAxis: { type: 'category', data: data.xAxis },yAxis: { type: 'value' },series: data.series.map(s => ({name: s.name,type: 'line',smooth: true,data: s.data}))}chart.setOption(option)
}const renderRadarChart = (data) => {const chart = echarts.init(radarChart.value)const option = {title: { text: '教师能力评估' },radar: {indicator: data.indicator},series: [{type: 'radar',data: [{ value: data.value }]}]}chart.setOption(option)
}
</script>

安全增强实现说明

  1. JWT认证体系

    • 双Token机制(Access Token + Refresh Token)
    • 自动续期功能
    • 黑名单管理(Redis存储失效Token)
    java">// Token刷新接口示例
    @PostMapping("/refresh-token")
    public ResponseEntity<AuthResponse> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {String refreshToken = request.getRefreshToken();if (jwtUtils.validateToken(refreshToken)) {String username = jwtUtils.getUsernameFromToken(refreshToken);UserDetails user = userService.loadUserByUsername(username);String newAccessToken = jwtUtils.generateToken(user);return ResponseEntity.ok(new AuthResponse(newAccessToken, refreshToken));}throw new InvalidTokenException("无效的刷新令牌");
    }
    
  2. XSS防御体系

    • 输入层:请求参数过滤(Filter层)
    • 存储层:入库前内容清洗
    • 输出层:响应内容转义
    java">// 自定义HttpServletRequestWrapper
    public class XssRequestWrapper extends HttpServletRequestWrapper {private final HtmlSanitizer sanitizer;public XssRequestWrapper(HttpServletRequest request, HtmlSanitizer sanitizer) {super(request);this.sanitizer = sanitizer;}@Overridepublic String getParameter(String name) {return sanitizer.sanitize(super.getParameter(name));}@Overridepublic String[] getParameterValues(String name) {String[] values = super.getParameterValues(name);if (values == null) return null;return Arrays.stream(values).map(sanitizer::sanitize).toArray(String[]::new);}
    }
    
  3. 可视化安全控制

    java">// 数据权限校验注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @PreAuthorize("@visualizationSecurity.checkCourseAccess(#courseId)")
    public @interface CheckCourseAccess {}// 安全校验服务
    @Service
    public class VisualizationSecurity {public boolean checkCourseAccess(Long courseId) {// 实现课程访问权限校验逻辑return true;}
    }
    

监控与审计增强

java">// 审计日志切面
@Aspect
@Component
public class AuditAspect {@AfterReturning(pointcut = "@annotation(audit)", returning = "result")public void logAuditEvent(JoinPoint jp, Audit audit, Object result) {String action = audit.value();String operator = SecurityUtils.getCurrentUsername();Object[] args = jp.getArgs();// 记录审计日志AuditLog log = new AuditLog();log.setAction(action);log.setOperator(operator);log.setParameters(JsonUtil.toJson(args));log.setResult(JsonUtil.toJson(result));log.setTimestamp(LocalDateTime.now());auditLogRepository.save(log);}
}// 敏感操作审计注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audit {String value();
}

该实现方案的特点:

  1. 纵深防御体系

    • 网络层:HTTPS强制加密
    • 应用层:JWT认证 + 权限控制
    • 数据层:敏感字段加密存储
    • 审计层:全操作日志追踪
  2. 可视化安全

    • 数据权限控制(基于RBAC)
    • 敏感数据脱敏处理
    • 图表水印防篡改
  3. 性能优化

    • 趋势数据预聚合(每日凌晨计算)
    • 热点数据缓存(Redis + Caffeine)
    • 大数据量分页查询优化
  4. 可维护性

    • 敏感词动态管理接口
    • 审计日志可视化查询
    • 安全配置中心化管理

典型应用场景:

用户 前端 后端 XSS过滤器 敏感词过滤 数据库 提交带HTML标签的评论 携带JWT的HTTP请求 清洗危险内容 安全的内容 检测并替换 处理后的内容 存储安全数据 返回成功响应 显示成功提示 用户 前端 后端 XSS过滤器 敏感词过滤 数据库

http://www.ppmy.cn/news/1572137.html

相关文章

Windows11+PyCharm利用MMSegmentation训练自己的数据集保姆级教程

系统版本&#xff1a;Windows 11 依赖环境&#xff1a;Anaconda3 运行软件&#xff1a;PyCharm 一.环境配置 通过Anaconda Prompt(anaconda)打开终端创建一个虚拟环境 conda create --name mmseg python3.93.激活虚拟环境 conda activate mmseg 4.安装pytorch和cuda tor…

深度学习 交易预测 LSTM 层的神经元数量、训练轮数

以下是一个使用 Python 和 Keras 库构建 LSTM 模型进行交易预测的代码示例&#xff0c;同时会展示如何调整 LSTM 层的神经元数量和训练轮数&#xff0c;代码中还包含了不同参数下的实验对比&#xff0c;帮助你理解它们对模型性能的影响。 示例代码 import numpy as np import…

STM32、GD32驱动TM1640原理图、源码分享

一、原理图分享 二、源码分享 /************************************************* * copyright: * author:Xupeng * date:2024-07-18 * description: **************************************************/ #include "smg.h"#define DBG_TAG "smg&…

【Vue】打包vue3+vite项目发布到github page的完整过程

文章目录 第一步&#xff1a;打包第二步&#xff1a;github仓库设置第三步&#xff1a;安装插件gh-pages第四步&#xff1a;两个配置第五步&#xff1a;上传github其他问题1. 路由2.待补充 参考文章&#xff1a; 环境&#xff1a; vue3vite windows11&#xff08;使用终端即可&…

#渗透测试#批量漏洞挖掘#Crocus系统—Download 文件读取

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

计算机网络综合实训室解决方案(2025年最新版)

一、计算机网络综合实训室概述 数字化转型的关键在于计算机网络技术的支撑&#xff0c;因此&#xff0c;当前教育教学的一大热点及社会需求&#xff0c;在于培养能够全面设计计算机整体系统、实施综合布线、安装网络设备并进行调试与维护的专业人才。鉴于计算机网络技术的高度…

【推荐】碰一碰发视频源码搭建,支持OEM

引言&#xff1a;重新定义视频分享体验 在移动优先的互联网时代&#xff0c;"碰一碰"交互已成为设备间快速连接的代名词。本文将突破传统文件传输思维&#xff0c;结合Web NFC与WebRTC技术&#xff0c;实现无需后端服务器的端到端视频实时传输方案。通过纯前端技术栈…

从零开始设计一个完整的网站:HTML、CSS、PHP、MySQL 和 JavaScript 实战教程

前言 本文将从实战角度出发&#xff0c;带你一步步设计一个完整的网站。我们将从 静态网页 开始&#xff0c;然后加入 动态功能&#xff08;使用 PHP&#xff09;&#xff0c;连接 数据库&#xff0c;最后加入 JavaScript 实现交互功能。通过这个教程&#xff0c;你将掌握一个…