从瑞吉到苍穹:外卖系统开发必须掌握的5个企业级技术(含WebSocket实战)

从瑞吉到苍穹:外卖系统开发必须掌握的5个企业级技术(含WebSocket实战)

如果你已经跟着“瑞吉外卖”这类经典入门项目走了一遍,成功搭建了一个能跑通的基础外卖系统,那么恭喜你,你已经迈出了坚实的第一步。但当你望向那些真正在线上稳定运行、支撑海量订单的商业系统时,可能会感到一丝迷茫:我的项目和它们之间,究竟隔着哪些看不见的鸿沟?从“瑞吉”到“苍穹”,这个命名上的跃迁,恰恰象征着一个开发者从掌握基础CRUD到驾驭企业级架构的关键进化路径。今天,我们就来深入聊聊,在构建一个真正可靠、高效、可扩展的外卖平台时,你必须攻克的五个核心技术堡垒,并用一个完整的WebSocket实战案例,告诉你如何将理论落地为代码。

1. 身份认证的进化:从Session到JWT的无状态实践

在“瑞吉外卖”中,用户登录后,服务器通常会创建一个Session来保存用户状态,并通过Cookie将Session ID传递给浏览器。这种方式在单体应用时代简单有效,但一旦系统走向分布式,问题就来了:Session存储在哪里?如果有多台应用服务器,如何保证用户请求能路由到存有他Session的那台机器?虽然可以通过Session复制或集中存储(如Redis)来解决,但无疑增加了系统的复杂度和网络开销。

这时,JWT(JSON Web Token) 作为一种无状态的认证方案,其价值就凸显出来了。它本质上是一个经过数字签名的JSON对象,包含了用户标识、过期时间等声明(Claim)。服务器在验证用户凭证(如用户名密码)后,生成一个JWT令牌返回给客户端。客户端在后续请求中,只需在HTTP头部(如Authorization: Bearer <token>)带上这个令牌,服务器验证签名有效且未过期后,即可信任其中的用户信息。

为什么JWT更适合分布式场景?

  • 无状态性:服务器不需要存储会话信息,减轻了存储压力,也避免了分布式Session同步的难题。
  • 自包含性:令牌本身包含了用户信息,减少了查询数据库的次数。
  • 跨域友好:天然支持跨域资源共享(CORS),非常适合前后端分离以及微服务间的调用。

实战:在Spring Boot中集成JWT

首先,引入依赖(以jjwt为例):

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

然后,创建一个工具类来负责JWT的生成和解析:

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {
    // 使用一个足够安全的密钥,生产环境应从配置中心读取
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("your-256-bit-secret-your-256-bit-secret".getBytes());
    private static final long EXPIRATION = 86400000L; // 24小时

    public static String generateToken(String userId, String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
                .compact();
    }

    public static Claims parseToken(String token) {
        try {
            return Jwts.parserBuilder()
                    .setSigningKey(SECRET_KEY)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException e) {
            // 处理令牌过期、签名无效等异常
            throw new RuntimeException("无效的JWT令牌", e);
        }
    }
}

最后,你需要一个拦截器(Interceptor)或过滤器(Filter)来验证请求头中的Token:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            try {
                Claims claims = JwtUtil.parseToken(token);
                // 将用户信息存入SecurityContext或Request属性,供后续使用
                String userId = claims.get("userId", String.class);
                // ... 设置认证信息
            } catch (RuntimeException e) {
                // 验证失败,返回401
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }
        chain.doFilter(request, response);
    }
}

注意:JWT一旦签发,在有效期内无法主动使其失效。如需实现“登出”或封禁用户,通常需要结合一个短期的黑名单(如Redis,存储已被注销但未过期的Token)或采用更短的Token有效期并配合Refresh Token机制。

2. 海量文件的优雅存储:告别本地磁盘,拥抱对象存储

在“瑞吉外卖”中,菜品图片很可能直接上传到了项目的staticupload目录下。这种方式在开发阶段没问题,但在生产环境会面临巨大挑战:

  • 磁盘空间限制:图片、视频等文件增长飞快,服务器磁盘很快会满。
  • 备份与扩容困难:文件分散在应用服务器上,备份和水平扩展应用实例变得复杂。
  • 访问性能瓶颈:应用服务器同时处理业务逻辑和静态文件IO,压力大,且难以利用CDN加速。

对象存储服务(如阿里云OSS、腾讯云COS) 正是为此而生。它将文件作为“对象”存储在扁平的命名空间中(桶Bucket),提供海量、安全、高可靠、低成本的存储,并天然具备高并发访问能力和CDN集成。

核心优势对比:

特性本地磁盘存储对象存储 (如阿里云OSS)
容量受单机磁盘限制理论上无限扩展
可靠性依赖本地RAID或备份,风险较高数据多副本/纠删码存储,设计耐久性高达99.999999999%
扩展性难以扩展,需停机扩容弹性伸缩,无需干预
访问方式通过应用服务器路由,增加负载提供独立的HTTP/HTTPS访问域名,可与CDN结合
成本前期硬件投入高按实际使用量付费,无运维成本

实战:Spring Boot集成阿里云OSS上传

  1. 引入SDK

    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.15.1</version>
    </dependency>
    
  2. 配置OSS客户端(使用@ConfigurationProperties绑定配置更佳):

    @Bean
    public OSS ossClient(@Value("${aliyun.oss.endpoint}") String endpoint,
                         @Value("${aliyun.oss.access-key-id}") String accessKeyId,
                         @Value("${aliyun.oss.access-key-secret}") String accessKeySecret) {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }
    
  3. 实现文件上传服务

    @Service
    @Slf4j
    public class FileUploadService {
        @Autowired
        private OSS ossClient;
        @Value("${aliyun.oss.bucket-name}")
        private String bucketName;
        @Value("${aliyun.oss.domain}")
        private String domain; // 自定义域名或OSS默认域名
    
        public String upload(MultipartFile file, String filePath) {
            String originalFilename = file.getOriginalFilename();
            // 生成唯一文件名,防止覆盖
            String fileName = filePath + "/" + UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
            try {
                ossClient.putObject(bucketName, fileName, file.getInputStream());
                // 返回文件的完整访问URL
                return "https://" + domain + "/" + fileName;
            } catch (IOException e) {
                log.error("文件上传OSS失败", e);
                throw new RuntimeException("上传失败");
            }
        }
    }
    

    在实际业务中,前端上传文件时,更好的实践是让后端生成一个预签名URL(Presigned URL)返回给前端,前端直接向OSS上传,这样能避免文件流经过应用服务器,极大减轻后端压力。

3. 定时任务的标准化:Spring Task与分布式调度考量

外卖业务中有很多定时触发的逻辑,比如:

  • 自动取消超时未支付的订单。
  • 定时给商家发送营业日报。
  • 定期清理临时缓存数据。

在“瑞吉外卖”中,你可能用@Scheduled注解简单实现了一个定时任务。但在集群部署时,如果不加控制,每台服务器上的任务都会同时执行,导致重复处理(如重复取消同一订单)。

Spring Task 是Spring框架内置的轻量级定时任务工具,使用简单:

@Component
public class OrderTask {
    private static final Logger log = LoggerFactory.getLogger(OrderTask.class);

    // 每5分钟执行一次
    @Scheduled(cron = "0 */5 * * * ?")
    public void cancelUnpaidOrders() {
        log.info("开始扫描超时未支付订单...");
        // 1. 查询超过30分钟未支付的订单
        // 2. 将这些订单状态更新为“已取消”
        // 3. 释放库存(如套餐库存)
    }
}

要启用定时任务,别忘了在主类上添加@EnableScheduling注解。

提示:cron表达式非常灵活,但需要确保语法正确。可以使用在线工具辅助生成和校验。

然而,正如前面所说,Spring Task本身不具备分布式协调能力。在集群环境下,你需要引入分布式调度中间件来保证任务在同一时间只被一个实例执行。常见的方案有:

  • Elastic-Job / XXL-Job:国产优秀的分布式任务调度框架,提供Web管理界面。
  • Quartz Cluster:老牌框架,通过数据库锁实现分布式调度。
  • 基于Redis的分布式锁:自己实现,相对轻量,但需要处理好锁的续期和释放。

例如,使用Redisson实现分布式锁来包装任务:

@Scheduled(cron = "0 */5 * * * ?")
public void distributedCancelUnpaidOrders() {
    String lockKey = "task:order:cancelUnpaid";
    RLock lock = redissonClient.getLock(lockKey);
    // 尝试获取锁,等待5秒,锁持有30秒后自动释放
    boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS);
    if (!isLocked) {
        log.info("未获取到分布式锁,任务跳过执行");
        return;
    }
    try {
        // 执行核心任务逻辑
        cancelUnpaidOrders();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

4. 报表导出与数据交换:Apache POI与HttpClient的应用

企业级应用离不开数据导出和外部系统集成。

Apache POI 用于操作Microsoft Office格式文件。在外卖系统中,商家可能需要导出某日的订单明细报表为Excel。

@Service
public class ReportService {
    public void exportOrderReport(HttpServletResponse response, LocalDate date) throws IOException {
        // 1. 查询数据
        List<OrderReportDTO> orderList = orderMapper.selectReportByDate(date);
        
        // 2. 创建Workbook和Sheet
        Workbook workbook = new XSSFWorkbook(); // HSSFWorkbook for .xls
        Sheet sheet = workbook.createSheet(date.toString() + "订单报表");
        
        // 3. 创建表头
        Row headerRow = sheet.createRow(0);
        String[] headers = {"订单号", "用户", "金额", "状态", "下单时间"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }
        
        // 4. 填充数据
        int rowNum = 1;
        for (OrderReportDTO order : orderList) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(order.getOrderNumber());
            row.createCell(1).setCellValue(order.getUsername());
            row.createCell(2).setCellValue(order.getAmount().doubleValue());
            row.createCell(3).setCellValue(order.getStatus());
            row.createCell(4).setCellValue(order.getCreateTime().toString());
        }
        
        // 5. 设置响应头,触发浏览器下载
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=order_report.xlsx");
        workbook.write(response.getOutputStream());
        workbook.close();
    }
}

HttpClient 则用于在服务端发起HTTP调用。在外卖场景中,一个典型的应用是调用第三方支付接口(如微信支付、支付宝)。

@Component
public class PaymentService {
    @Autowired
    private CloseableHttpClient httpClient;

    public PaymentResponse createWechatPayment(PaymentRequest request) throws IOException {
        // 1. 构建请求体(通常是JSON)
        String jsonBody = objectMapper.writeValueAsString(request);
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        httpPost.setHeader("Content-Type", "application/json");
        // 设置认证头部(如API密钥)
        httpPost.setHeader("Authorization", "Bearer " + generateToken());
        httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));

        // 2. 执行请求
        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
            String responseBody = EntityUtils.toString(response.getEntity());
            // 3. 解析响应
            if (response.getStatusLine().getStatusCode() == 200) {
                return objectMapper.readValue(responseBody, PaymentResponse.class);
            } else {
                // 处理错误
                throw new RuntimeException("支付接口调用失败: " + responseBody);
            }
        }
    }
}

使用HttpClient时,务必使用连接池管理,并合理配置超时时间、重试策略,以提升性能和稳定性。

5. 实时通信的核心:WebSocket构建即时通知系统

这是从“能用”到“好用”的关键一跃。想象一下,用户下单后,商家后台能否立刻响起“新订单”提示?用户催单后,客服能否实时收到消息?这种即时性体验,依赖于双向全双工通信的WebSocket协议,它克服了HTTP轮询带来的延迟高、资源浪费的问题。

WebSocket vs. 轮询/长轮询:

  • HTTP轮询:客户端定期向服务器询问“有新消息吗?”,无论是否有数据,都会产生请求,延迟高且浪费资源。
  • HTTP长轮询:客户端发起请求,服务器持有连接直到有数据或超时才返回。减少了无效请求,但连接管理复杂。
  • WebSocket:在初次HTTP握手升级后,建立持久连接,双方可以随时主动发送数据,真正实现低延迟、高效率的双向通信。

实战:Spring Boot整合WebSocket实现来单提醒

我们将构建一个简单的来单提醒系统,包含服务端和前端(以Vue为例)的代码。

服务端实现:

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    
  2. 配置WebSocket

    @Configuration
    @EnableWebSocketMessageBroker // 启用消息代理
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            // 指定WebSocket连接端点,前端通过 ws://your-domain/ws 连接
            registry.addEndpoint("/ws")
                    .setAllowedOriginPatterns("*") // 生产环境应指定具体域名
                    .withSockJS(); // 启用SockJS后备选项,在不支持WS的浏览器上降级
        }
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            // 客户端订阅消息的前缀,例如客户端订阅 /topic/newOrder
            registry.enableSimpleBroker("/topic");
            // 客户端发送消息到服务端的前缀,例如客户端发送消息到 /app/alert
            registry.setApplicationDestinationPrefixes("/app");
        }
    }
    
  3. 创建消息控制器

    @Controller
    public class OrderAlertController {
        // SimpMessagingTemplate用于向特定主题发送消息
        @Autowired
        private SimpMessagingTemplate messagingTemplate;
    
        /**
         * 模拟新订单产生,向所有订阅了`/topic/newOrder`的客户端广播消息
         * @param orderId 订单ID
         */
        @PostMapping("/api/order/{orderId}/alert")
        @ResponseBody
        public String sendNewOrderAlert(@PathVariable String orderId) {
            Map<String, Object> message = new HashMap<>();
            message.put("type", "NEW_ORDER");
            message.put("orderId", orderId);
            message.put("content", "您有新的外卖订单,请及时处理!");
            message.put("time", LocalDateTime.now().toString());
            
            // 关键:向主题 /topic/newOrder 发送消息
            messagingTemplate.convertAndSend("/topic/newOrder", message);
            return "提醒已发送";
        }
    
        /**
         * 处理来自特定商家的催单消息,并定向回复
         * @param message 催单消息体
         */
        @MessageMapping("/user/remind") // 客户端发送到 /app/user/remind
        public void handleUserRemind(RemindMessage message) {
            // 假设message中包含商家ID和催单内容
            String shopId = message.getShopId();
            Map<String, Object> alert = new HashMap<>();
            alert.put("type", "USER_REMIND");
            alert.put("fromUser", message.getUserId());
            alert.put("content", "用户催单:" + message.getContent());
            // 定向发送给该商家:/topic/shop/{shopId}
            messagingTemplate.convertAndSend("/topic/shop/" + shopId, alert);
        }
    }
    

前端(Vue + SockJS + STOMP)实现:

  1. 安装依赖

    npm install sockjs-client stompjs
    
  2. 创建WebSocket工具类

    // websocket.js
    import SockJS from 'sockjs-client';
    import Stomp from 'stompjs';
    
    let stompClient = null;
    const serverUrl = 'http://localhost:8080/ws'; // 对应后端端点
    
    export function connectWebSocket(onConnected, onError) {
        const socket = new SockJS(serverUrl);
        stompClient = Stomp.over(socket);
        // 禁用调试信息(生产环境)
        stompClient.debug = null;
        
        stompClient.connect({}, (frame) => {
            console.log('WebSocket连接成功: ' + frame);
            if (onConnected) onConnected(stompClient);
        }, (error) => {
            console.error('WebSocket连接失败: ', error);
            if (onError) onError(error);
        });
    }
    
    export function subscribeToNewOrder(callback) {
        if (stompClient && stompClient.connected) {
            // 订阅服务端广播新订单的主题
            return stompClient.subscribe('/topic/newOrder', (message) => {
                const orderAlert = JSON.parse(message.body);
                callback(orderAlert);
            });
        }
        return null;
    }
    
    export function sendRemind(shopId, content) {
        if (stompClient && stompClient.connected) {
            stompClient.send('/app/user/remind', {}, JSON.stringify({
                shopId: shopId,
                content: content
            }));
        }
    }
    
    export function disconnect() {
        if (stompClient !== null) {
            stompClient.disconnect();
        }
    }
    
  3. 在Vue组件中使用

    <template>
      <div>
        <h2>商家管理后台</h2>
        <div v-if="alerts.length">
          <div v-for="(alert, index) in alerts" :key="index" class="alert">
            【{{ alert.type }}】 {{ alert.content }} (订单号: {{ alert.orderId }}) - {{ alert.time }}
          </div>
        </div>
        <button @click="simulateNewOrder">模拟新订单(测试用)</button>
      </div>
    </template>
    
    <script>
    import { connectWebSocket, subscribeToNewOrder, disconnect } from '@/utils/websocket';
    
    export default {
      name: 'MerchantDashboard',
      data() {
        return {
          alerts: []
        };
      },
      mounted() {
        connectWebSocket(() => {
          console.log('连接成功,开始订阅新订单');
          // 订阅新订单提醒
          this.subscription = subscribeToNewOrder((alert) => {
            this.alerts.unshift(alert); // 将新提醒添加到列表顶部
            // 可以在这里触发浏览器通知或播放提示音
            if (Notification.permission === 'granted') {
              new Notification('新订单提醒', { body: alert.content });
            }
          });
        }, (error) => {
          console.error('连接失败', error);
        });
      },
      beforeUnmount() {
        if (this.subscription) {
          this.subscription.unsubscribe();
        }
        disconnect();
      },
      methods: {
        simulateNewOrder() {
          // 调用后端API触发新订单提醒(实际业务中由下单逻辑触发)
          this.$http.post('/api/order/123456/alert').then(() => {
            console.log('测试提醒已发送');
          });
        }
      }
    };
    </script>
    

关键点与优化:

  • 连接管理:确保组件销毁时断开连接和取消订阅,避免内存泄漏。
  • 心跳与重连:STOMP协议有心跳机制,但网络不稳定时仍需在前端实现自动重连逻辑。
  • 安全性:WebSocket连接也可以进行认证(如连接时传递JWT Token)。生产环境务必限制setAllowedOriginPatterns,防止跨站WebSocket劫持。
  • 扩展性:当单台服务器无法支撑大量连接时,需要考虑使用STOMP消息代理(如RabbitMQ、ActiveMQ)进行集群部署,让WebSocket连接可以分散到不同应用服务器,而消息通过中央代理进行广播。

从Session到JWT,从本地存储到OSS,从单机任务到分布式调度,再到与外部系统的集成和实时通信的建立,这五个技术点的深入理解和实践,正是将一个“课程Demo”级别的外卖项目,打磨成具备企业级应用雏形的关键。每一步的升级,都对应着解决一个真实生产环境中会遇到的问题。当你把这些点串联起来,并融入到“苍穹外卖”这样的项目实践中时,你会发现自己的技术视野和架构能力已经悄然上了一个台阶。剩下的,就是在更复杂的业务场景中,去反复运用和深化这些知识了。

摘 要 随着互联网影视产业的迅速发展,电影资源呈指数级增长,类型也愈加多样化,但是用户面对如此庞大的影片库时,会遇到信息过载、筛选效率低下、观影决策成本高这些难题,而传统的电影平台大多只是对基本的分类和热度进行展示,并没有提供个性化的推荐服务,社交互动以及一体化管理的能力也比较薄弱。 该系统使用的是Spring Boot+Vue前后端分离的方式进行开发,设计并开发了一个电影推荐和评论系统。开发系统中存在三个问题,即无法准确地判断出用户的喜好、电影推荐功能上线之初没有用户数据,因此推荐效果不佳、大量用户同时评论打分时系统容易出现不稳定的情况。为了克服上述问题,本文查阅相关资料,不断迭代开发原型,并进行实际测试,完成整个系统的全部过程,即系统需求分析、架构设计、功能开发、系统测试。系统分为普通用户和管理员两种身份,具有注册登录、电影浏览、电影推荐、评分评论、影片收藏、个人中心、后台管理等功能,采用结合用户喜好和电影热度的简单推荐方式,提供热门电影推荐和个性化推荐,很好地解决了推荐功能初始没有数据、数据较少的问题。项目用接口来完成前后端的数据交互,使用MySQL数据库存储用户、电影、评论、收藏等各种数据,并且加入权限验证、密码加密等安全措施,保证系统安全稳定并且便于后期扩展。除此之外,系统还增加了电影资讯查看、首页轮播图设置、编辑资讯等功能,使整个电影平台的服务更加全面,使用更加高效。经过全面的功能测试、接口测试和性能测试,系统各个模块运行稳定,推荐接口响应时间小于300ms,主要的互动操作成功率大于98%,可以大大降低用户的选片成本,提高观影决策的速度和社区互动的效果。 本文给出一套轻量级、易部署、可复用的电影推荐类Web应用工程实现方案,相比于传统的单一列表展示型平台,个性化服务、社交互动体验和后台管理效率都有明显的提高,可以给影视文化数字化传播、推荐算法轻量化工程实践
源码直接下载地址: https://pan.quark.cn/s/a4b39357ea24 在 Excel 中进行阳历与阴历的相互转换,对于处理集体信息(例如通讯录)统计工作具有显著的实用性。本文将具体阐述如何借助 VBA 编辑器在 Excel 环境下完成阳历与阴历的互换转换。首先,需要打开相应的 Excel 文件,通过按 Alt+F11 激活 VBA 编辑器,随后选择插入菜单下的模块选项,将提供的代码片段复制到新模块中,并保存更改后关闭编辑器。完成上述步骤后,即可在指定单元格中调用以下四个函数以达成转换目标。 1. 阴阳历转换功能: 函数 `Lunar(SolarDate[, Part = 0 | 1 | 2 | 3])` 负责将阳历日期转换为对应的阴历日期。参数 `Part` 决定转换内容的详略程度,其值可为 0、1、2 或 3,分别对应完整日期、阴历年、阴历月及阴历日的转换。 函数 `Solar(LunarDate[, LunarMonth = 0 | 1])` 适用于将阴历日期转换为阳历日期。参数 `LunarMonth` 用于指定月份的归属,可为 0 或 1,分别代表转换至阳历月份或保留阴历月份。 2. 生日日期转换功能: 函数 `lunarbirth("1975-5-6")` 能够计算出阴历生日所对应的阳历日期。相对地,函数 `solarbirth("1975-5-6")` 用于推算阳历生日对应的阴历日期。 3. 日期信息计算功能: 函数 `LunarData(q_year)` 提供阴历日期的详细数据,涵盖阴历年、阴历月、阴历日等信息。函数 `ConvDataA` 存储了阴历与阳历的日期数据,包括阴历年、阴历月、阴历日、阳历月、阳历日等字段。 4. 应用...
下载代码方式:https://pan.quark.cn/s/a4b39357ea24 在信息技术行业中,特别是在人工智能(AI)的子领域——计算机视觉方面,动作分析是一个至关重要的组成部分。这一议题与“Python-PyTorch动作分析模型库”有着紧密的联系,它涵盖了运用Python编程语言和PyTorch框架来构建和应用深度学习模型,旨在解析和判定视频中的行为。接下来,我们将详细研究这一领域的重要概念。 **PyTorch** 是由Facebook开发的一个开源且功能强大的深度学习平台,它具备动态计算图特性,从而让模型构建和调试过程更加便捷。PyTorch的关键在于Tensor类,该类是数值运算的基础,同时支持自动计算梯度,为神经网络的训练提供了便利。 **动作分析** 是计算机视觉中的一个核心任务,其目的是识别视频中的特定行为,例如奔跑、跳跃、招手等。这项任务通常包括从视频材料中提取图像帧,然后对单个帧或帧序列进行特征提取,最终借助已训练的模型进行分类。 在描述中提及的“流行动作分析模型”,或许涵盖了当前研究领域的主流模型,例如**双流卷积网络**,这种模型融合了空间和时间信息,通过分别处理RGB图像和光流图像来提高行为分析的精确度。还可能包括**时间分割网络(TSN)**,它通过跨长时间范围的样本选择来把握行为的整体特征。另外,更前沿的模型如**时间迁移模块(TSM)** 和 **非局部神经网络** 也可能被纳入其中,这些模型通过创新的网络构造来更有效地捕捉时间序列中的动态变化。 **某类数据集** 可能是指像UCF-101或Kinetics这样的标准动作分析数据集,它们包了大量标注好的视频片段,用于模型的训练和性能评估。这些数据集在动作分析研究中被...
打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 MATLAB深度学习工具箱是为在MATLAB平台中开展深度学习活动而研发的一套功能完备的软件库,其内置了大量的函数和类,使用户能够轻松地建立、训练并实施各类深度学习模型。该工具箱涵盖了神经网络、卷积神经网络(CNN)、循环神经网络(RNN)、长短时记忆网络(LSTM)等多种深度学习体系结构,适用于图像识别、语音识别、自然语言处理等多种应用场景。 1. **神经网络基础**:MATLAB深度学习工具箱能够支持构建和训练基础的前馈神经网络(Feedforward Networks)。这些网络可用于执行简单的分类和回归任务,通过设定层数、节点数、激活函数(如sigmoid、ReLU等)以及优化器(如梯度下降、Adam等)来调整网络构造和性能表现。 2. **卷积神经网络(CNN)**:CNN是图像处理中的核心架构,工具箱提供了构建、训练和应用CNN的功能。用户可以定义不同类型的卷积层、池化层、全连接层,以及借助数据增强技术来提升模型的泛化性能。 3. **循环神经网络(RNN)与LSTM**:RNN及其变体LSTM在序列数据处理中展现出优异的性能,例如文本分析和语音识别。MATLAB深度学习工具箱提供了构建和训练RNN及LSTM网络的接口,允许用户处理变长输入序列,并能捕捉长期的依赖关系。 4. **预训练模型**:工具箱内集成了预训练的深度学习模型,如VGG、ResNet等,可以直接应用于图像分类任务,或者作为迁移学习的基础,通过微调来适应特定任务需求。 5. **自动求梯度**:MATLAB深度学习工具箱支持自动求梯度,这是反向传播算法的关键环节,使得用户无需手动计算梯度,能更...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值