ITPub博客

首页 > 移动开发 > 微信开发 > 基于 uni-app 的去水印小程序源码开发:从功能搭建到打包发布全流程

基于 uni-app 的去水印小程序源码开发:从功能搭建到打包发布全流程

原创 微信开发 作者:chunhuabei 时间:2025-12-31 14:34:26 0 删除 编辑

  在短视频内容爆炸式增长的今天,去水印工具已成为用户刚需。基于uni-app开发去水印小程序,能够实现“一次开发,多端发布”,显著降低开发成本。本文将详细解析一个完整去水印小程序的开发全流程,涵盖从环境搭建到最终上线的各个环节。

       一、技术选型

  1.1技术栈选择

  •源码及演示:y.wxlbyx.icu

  •前端框架:uni-app 3.5+(Vue 3+Composition API)

  •UI组件库:uni-ui

  •网络请求:uni.request封装

  •视频处理:后端API+前端预览

  •存储方案:uni.setStorageSync本地缓存

  •平台支持:微信小程序、抖音小程序、H5

  1.2项目结构设计

┌─────────────────────────────────────────────────┐
│                  客户端                          │
│    (Web/Android/iOS)                            │
 └───────────────┬─────────────────────────────────┘
                │ HTTP/WebSocket
┌───────────────┴─────────────────────────────────┐
│              API网关层                           │
│           (Spring Cloud Gateway)               │
└───────────────┬─────────────────────────────────┘
                │
┌───────────────┴─────────────────────────────────┐
│               微服务集群                         │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐          │
│  │用户服务  │ │盲盒服务  │ │聊天服务  │          │
│  └─────────┘ └─────────┘ └─────────┘          │
└───────────────┬─────────────────────────────────┘
                │
┌───────────────┴─────────────────────────────────┐
│               数据存储层                         │
│        MySQL     Redis       MinIO             │
└─────────────────────────────────────────────────┘

  二、开发环境搭建

  2.1开发工具准备

-- 用户表
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password_hash` varchar(255) NOT NULL COMMENT '加密密码',
  `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
  `gender` tinyint DEFAULT '0' COMMENT '性别 0:未知 1:男 2:女',
  `age` tinyint DEFAULT NULL COMMENT '年龄',
  `avatar_url` varchar(500) DEFAULT NULL COMMENT '头像URL',
  `introduction` text COMMENT '个人介绍',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `status` tinyint DEFAULT '1' COMMENT '状态 0:禁用 1:正常',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  KEY `idx_phone` (`phone`),
  KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 盲盒配置表
CREATE TABLE `blind_box_config` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '配置ID',
  `name` varchar(100) NOT NULL COMMENT '盲盒名称',
  `description` text COMMENT '盲盒描述',
  `price` decimal(10,2) NOT NULL COMMENT '价格',
  `daily_limit` int DEFAULT '-1' COMMENT '每日限制 -1:无限制',
  `total_limit` int DEFAULT '-1' COMMENT '总限制 -1:无限制',
  `match_algorithm` varchar(50) DEFAULT 'RANDOM' COMMENT '匹配算法',
  `tags` json DEFAULT NULL COMMENT '标签配置',
  `status` tinyint DEFAULT '1' COMMENT '状态 0:下架 1:上架',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='盲盒配置表';
-- 用户盲盒记录表
CREATE TABLE `user_box_record` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `box_id` bigint NOT NULL COMMENT '盲盒ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单号',
  `match_user_id` bigint DEFAULT NULL COMMENT '匹配用户ID',
  `match_status` tinyint DEFAULT '0' COMMENT '匹配状态 0:等待匹配 1:匹配成功 2:匹配失败',
  `match_time` datetime DEFAULT NULL COMMENT '匹配时间',
  `unlock_time` datetime DEFAULT NULL COMMENT '解锁时间',
  `is_unlocked` tinyint DEFAULT '0' COMMENT '是否已解锁',
  `user_rating` tinyint DEFAULT NULL COMMENT '用户评分 1-5',
  `match_rating` tinyint DEFAULT NULL COMMENT '对方评分 1-5',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_match_user_id` (`match_user_id`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户盲盒记录表';
-- 聊天会话表
CREATE TABLE `chat_session` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '会话ID',
  `session_key` varchar(100) NOT NULL COMMENT '会话唯一标识',
  `user1_id` bigint NOT NULL COMMENT '用户1ID',
  `user2_id` bigint NOT NULL COMMENT '用户2ID',
  `box_record_id` bigint NOT NULL COMMENT '盲盒记录ID',
  `last_message` text COMMENT '最后消息',
  `last_message_time` datetime DEFAULT NULL COMMENT '最后消息时间',
  `unread_count1` int DEFAULT '0' COMMENT '用户1未读',
  `unread_count2` int DEFAULT '0' COMMENT '用户2未读',
  `status` tinyint DEFAULT '1' COMMENT '状态 1:正常 0:关闭',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_session_key` (`session_key`),
  KEY `idx_user1_id` (`user1_id`,`update_time`),
  KEY `idx_user2_id` (`user2_id`,`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='聊天会话表';
-- 消息表
CREATE TABLE `chat_message` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息ID',
  `session_id` bigint NOT NULL COMMENT '会话ID',
  `sender_id` bigint NOT NULL COMMENT '发送者ID',
  `receiver_id` bigint NOT NULL COMMENT '接收者ID',
  `message_type` tinyint DEFAULT '1' COMMENT '消息类型 1:文本 2:图片 3:语音',
  `content` text COMMENT '消息内容',
  `file_url` varchar(500) DEFAULT NULL COMMENT '文件URL',
  `is_read` tinyint DEFAULT '0' COMMENT '是否已读',
  `read_time` datetime DEFAULT NULL COMMENT '阅读时间',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_session_id` (`session_id`,`create_time`),
  KEY `idx_sender_id` (`sender_id`,`create_time`),
  KEY `idx_receiver_id` (`receiver_id`,`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';
-- 用户标签表
CREATE TABLE `user_tag` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '标签ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `tag_name` varchar(50) NOT NULL COMMENT '标签名称',
  `tag_type` varchar(20) DEFAULT 'CUSTOM' COMMENT '标签类型',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_tag` (`user_id`,`tag_name`),
  KEY `idx_tag_name` (`tag_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户标签表';
-- 匹配记录详情表
CREATE TABLE `match_detail` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '详情ID',
  `record_id` bigint NOT NULL COMMENT '记录ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `match_strategy` varchar(50) DEFAULT NULL COMMENT '匹配策略',
  `match_score` decimal(5,2) DEFAULT NULL COMMENT '匹配分数',
  `match_factors` json DEFAULT NULL COMMENT '匹配因素',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_record_id` (`record_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='匹配记录详情表';

  三、核心功能实现

  3.1应用配置与初始化

// JWT工具类
@Component
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private long jwtExpiration;
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }
    
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
// Spring Security配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .antMatchers("/api/ws/**").permitAll()
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(jwtAuthenticationFilter, 
                UsernamePasswordAuthenticationFilter.class);
    }
}

  3.2首页功能实现

@Service
@Slf4j
public class BlindBoxServiceImpl implements BlindBoxService {
    
    @Autowired
    private UserBoxRecordMapper boxRecordMapper;
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private BlindBoxConfigMapper boxConfigMapper;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String MATCH_QUEUE = "blindbox.match.queue";
    private static final String MATCH_POOL_KEY = "match:pool:";
    
    @Override
    @Transactional
    public ApiResult<BoxOpenResult> openBlindBox(Long userId, Long boxId) {
        // 1. 参数校验
        User user = userMapper.selectById(userId);
        if (user == null) {
            return ApiResult.error("用户不存在");
        }
        
        BlindBoxConfig config = boxConfigMapper.selectById(boxId);
        if (config == null || config.getStatus() != 1) {
            return ApiResult.error("盲盒不存在或已下架");
        }
        
        // 2. 检查购买限制
        if (!checkPurchaseLimit(userId, boxId, config)) {
            return ApiResult.error("已达到购买限制");
        }
        
        // 3. 创建盲盒记录
        UserBoxRecord record = new UserBoxRecord();
        record.setUserId(userId);
        record.setBoxId(boxId);
        record.setOrderNo(generateOrderNo());
        record.setMatchStatus(0);
        record.setIsUnlocked(false);
        
        boxRecordMapper.insert(record);
        
        // 4. 发送到匹配队列
        MatchTask task = new MatchTask();
        task.setRecordId(record.getId());
        task.setUserId(userId);
        task.setBoxId(boxId);
        task.setConfig(config);
        
        rabbitTemplate.convertAndSend(MATCH_QUEUE, task);
        
        // 5. 将用户加入匹配池
        addToMatchPool(userId, boxId, record.getId(), config);
        
        return ApiResult.success(new BoxOpenResult(record.getId(), "等待匹配中"));
    }
    
    // 智能匹配算法
    private MatchResult smartMatch(Long userId, Long boxId, 
                                   BlindBoxConfig config) {
        List<Long> candidateUserIds = getMatchPoolUsers(boxId);
        
        if (candidateUserIds.isEmpty()) {
            return null;
        }
        
        // 获取当前用户信息
        User currentUser = userMapper.selectById(userId);
        Map<String, Object> currentUserTags = getUserTags(userId);
        
        // 计算匹配度
        Map<Long, Double> matchScores = new HashMap<>();
        for (Long candidateId : candidateUserIds) {
            if (candidateId.equals(userId)) {
                continue;
            }
            
            User candidate = userMapper.selectById(candidateId);
            Map<String, Object> candidateTags = getUserTags(candidateId);
            
            double score = calculateMatchScore(currentUser, candidate, 
                                             currentUserTags, candidateTags, 
                                             config);
            matchScores.put(candidateId, score);
        }
        
        // 选择最佳匹配
        return matchScores.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(entry -> new MatchResult(entry.getKey(), entry.getValue()))
                .orElse(null);
    }
    
    private double calculateMatchScore(User user1, User user2,
                                     Map<String, Object> tags1,
                                     Map<String, Object> tags2,
                                     BlindBoxConfig config) {
        double score = 0.0;
        
        // 1. 基础信息匹配(年龄、性别等)
        if (config.getGenderPreference() != null) {
            if (config.getGenderPreference().equals("SAME") && 
                user1.getGender().equals(user2.getGender())) {
                score += 30;
            } else if (config.getGenderPreference().equals("DIFFERENT") && 
                      !user1.getGender().equals(user2.getGender())) {
                score += 30;
            }
        }
        
        // 2. 年龄差计算
        int ageDiff = Math.abs(user1.getAge() - user2.getAge());
        if (ageDiff <= 3) {
            score += 20;
        } else if (ageDiff <= 5) {
            score += 10;
        }
        
        // 3. 标签匹配度
        Set<String> commonTags = new HashSet<>(tags1.keySet());
        commonTags.retainAll(tags2.keySet());
        score += commonTags.size() * 5;
        
        // 4. 活跃度因素
        Double user1Activity = getUserActivityScore(user1.getId());
        Double user2Activity = getUserActivityScore(user2.getId());
        score += Math.min(user1Activity, user2Activity) * 10;
        
        return score;
    }
    
    // 消息队列消费者
    @RabbitListener(queues = MATCH_QUEUE)
    public void processMatchTask(MatchTask task) {
        log.info("开始处理匹配任务: {}", task.getRecordId());
        
        try {
            // 尝试匹配
            MatchResult matchResult = smartMatch(task.getUserId(), 
                task.getBoxId(), task.getConfig());
            
            if (matchResult != null && matchResult.getScore() > 50) {
                // 匹配成功
                completeMatch(task.getRecordId(), matchResult.getMatchedUserId());
            } else {
                // 匹配失败,加入重试队列
                if (task.getRetryCount() < 3) {
                    task.setRetryCount(task.getRetryCount() + 1);
                    rabbitTemplate.convertAndSend(MATCH_QUEUE, task, message -> {
                        message.getMessageProperties()
                            .setDelay(30000); // 30秒后重试
                        return message;
                    });
                } else {
                    // 匹配失败,退款处理
                    handleMatchFailed(task.getRecordId());
                }
            }
        } catch (Exception e) {
            log.error("匹配处理异常: ", e);
        }
    }
    
    private void completeMatch(Long recordId, Long matchedUserId) {
        UserBoxRecord record = boxRecordMapper.selectById(recordId);
        if (record == null || record.getMatchStatus() != 0) {
            return;
        }
        
        // 更新匹配记录
        record.setMatchUserId(matchedUserId);
        record.setMatchStatus(1);
        record.setMatchTime(new Date());
        boxRecordMapper.updateById(record);
        
        // 创建聊天会话
        createChatSession(record.getUserId(), matchedUserId, recordId);
        
        // 发送匹配成功通知
        sendMatchNotification(record.getUserId(), matchedUserId);
    }
}

  3.3视频解析API封装

@Component
@ServerEndpoint("/api/ws/chat/{token}")
@Slf4j
public class ChatWebSocket {
    
    private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
    private static final Map<String, String> userSessions = new ConcurrentHashMap<>();
    
    @Autowired
    private ChatService chatService;
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @OnOpen
    public void onOpen(@PathParam("token") String token, Session session) {
        try {
            String username = jwtTokenProvider.getUsernameFromToken(token);
            onlineUsers.put(username, session);
            userSessions.put(session.getId(), username);
            
            log.info("用户连接: {}, 当前在线: {}", username, onlineUsers.size());
            
            // 发送连接成功消息
            sendMessage(session, new WebSocketMessage(
                "SYSTEM", "CONNECT_SUCCESS", "连接成功"
            ));
            
        } catch (Exception e) {
            log.error("WebSocket连接失败: ", e);
            try {
                session.close();
            } catch (IOException ex) {
                log.error("关闭连接失败: ", ex);
            }
        }
    }
    
    @OnMessage
    public void onMessage(String messageStr, Session session) {
        try {
            WebSocketMessage message = JSON.parseObject(messageStr, 
                WebSocketMessage.class);
            
            String username = userSessions.get(session.getId());
            if (username == null) {
                return;
            }
            
            switch (message.getType()) {
                case "CHAT":
                    handleChatMessage(username, message);
                    break;
                case "TYPING":
                    handleTypingStatus(username, message);
                    break;
                case "READ":
                    handleReadReceipt(username, message);
                    break;
            }
            
        } catch (Exception e) {
            log.error("处理消息异常: ", e);
        }
    }
    
    private void handleChatMessage(String sender, WebSocketMessage message) {
        ChatMessage chatMessage = JSON.parseObject(
            message.getData().toString(), ChatMessage.class);
        
        // 保存到数据库
        Long messageId = chatService.saveMessage(chatMessage);
        chatMessage.setId(messageId);
        
        // 更新会话最后消息
        chatService.updateSessionLastMessage(
            chatMessage.getSessionId(), 
            chatMessage.getContent(),
            new Date()
        );
        
        // 发送给接收者
        String receiver = chatMessage.getReceiverId().toString();
        Session receiverSession = onlineUsers.get(receiver);
        if (receiverSession != null && receiverSession.isOpen()) {
            sendMessage(receiverSession, new WebSocketMessage(
                "CHAT", "NEW_MESSAGE", chatMessage
            ));
        }
        
        // 发送回执给发送者
        WebSocketMessage receipt = new WebSocketMessage(
            "SYSTEM", "MESSAGE_SENT", 
            Map.of("messageId", messageId, "timestamp", new Date())
        );
        Session senderSession = onlineUsers.get(sender);
        if (senderSession != null) {
            sendMessage(senderSession, receipt);
        }
    }
    
    @OnClose
    public void onClose(Session session) {
        String username = userSessions.remove(session.getId());
        if (username != null) {
            onlineUsers.remove(username);
            log.info("用户断开: {}, 当前在线: {}", username, onlineUsers.size());
        }
    }
    
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket错误: ", error);
    }
    
    private void sendMessage(Session session, WebSocketMessage message) {
        try {
            if (session.isOpen()) {
                session.getBasicRemote().sendText(JSON.toJSONString(message));
            }
        } catch (IOException e) {
            log.error("发送消息失败: ", e);
        }
    }
    
    // 广播消息
    public static void broadcast(String messageType, Object data) {
        WebSocketMessage message = new WebSocketMessage(
            "SYSTEM", messageType, data
        );
        String messageJson = JSON.toJSONString(message);
        
        onlineUsers.values().forEach(session -> {
            if (session.isOpen()) {
                try {
                    session.getBasicRemote().sendText(messageJson);
                } catch (IOException e) {
                    log.error("广播消息失败: ", e);
                }
            }
        });
    }
}
// WebSocket配置
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Autowired
    private ChatWebSocket chatWebSocket;
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatWebSocket, "/api/ws/chat/{token}")
                .setAllowedOrigins("*")
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
    
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

  3.4历史记录功能

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private UserBoxRecordMapper boxRecordMapper;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Override
    @Transactional
    public ApiResult<OrderVO> createOrder(Long userId, Long boxId, 
                                          Integer quantity) {
        // 1. 验证盲盒
        BlindBoxConfig config = boxConfigMapper.selectById(boxId);
        if (config == null || config.getStatus() != 1) {
            return ApiResult.error("盲盒不存在或已下架");
        }
        
        // 2. 创建订单
        Order order = new Order();
        order.setOrderNo(generateOrderNo());
        order.setUserId(userId);
        order.setBoxId(boxId);
        order.setQuantity(quantity);
        order.setUnitPrice(config.getPrice());
        order.setTotalPrice(config.getPrice().multiply(
            new BigDecimal(quantity)));
        order.setStatus(0); // 待支付
        order.setCreateTime(new Date());
        
        orderMapper.insert(order);
        
        // 3. 调用支付接口
        PaymentRequest paymentRequest = new PaymentRequest();
        paymentRequest.setOrderNo(order.getOrderNo());
        paymentRequest.setAmount(order.getTotalPrice());
        paymentRequest.setUserId(userId);
        paymentRequest.setSubject("交友盲盒购买");
        
        PaymentResponse paymentResponse = paymentService.createPayment(
            paymentRequest);
        
        // 4. 返回支付信息
        OrderVO orderVO = convertToVO(order);
        orderVO.setPayInfo(paymentResponse.getPayInfo());
        
        return ApiResult.success(orderVO);
    }
    
    // 支付回调处理
    @PostMapping("/payment/callback")
    public String paymentCallback(@RequestBody PaymentCallback callback) {
        log.info("收到支付回调: {}", callback);
        
        // 验证签名
        if (!verifySignature(callback)) {
            return "FAIL";
        }
        
        if ("SUCCESS".equals(callback.getTradeStatus())) {
            // 支付成功处理
            handlePaymentSuccess(callback.getOrderNo());
        } else {
            // 支付失败处理
            handlePaymentFailed(callback.getOrderNo());
        }
        
        return "SUCCESS";
    }
    
    @Async
    protected void handlePaymentSuccess(String orderNo) {
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order == null) {
            log.error("订单不存在: {}", orderNo);
            return;
        }
        
        // 更新订单状态
        order.setStatus(1); // 已支付
        order.setPayTime(new Date());
        orderMapper.updateById(order);
        
        // 创建盲盒记录
        for (int i = 0; i < order.getQuantity(); i++) {
            UserBoxRecord record = new UserBoxRecord();
            record.setUserId(order.getUserId());
            record.setBoxId(order.getBoxId());
            record.setOrderNo(order.getOrderNo());
            record.setMatchStatus(0);
            record.setIsUnlocked(false);
            
            boxRecordMapper.insert(record);
            
            // 发送到匹配队列
            MatchTask task = new MatchTask();
            task.setRecordId(record.getId());
            task.setUserId(order.getUserId());
            task.setBoxId(order.getBoxId());
            
            rabbitTemplate.convertAndSend("blindbox.match.queue", task);
        }
        
        // 发送支付成功通知
        notificationService.sendPaymentSuccessNotification(
            order.getUserId(), order);
    }
}

  四、多平台适配

  4.1条件编译处理

@Service
@Slf4j
public class UserCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    private static final String USER_CACHE_KEY = "user:info:";
    private static final long USER_CACHE_TTL = 3600; // 1小时
    
    @Cacheable(value = "user", key = "#userId", unless = "#result == null")
    public User getUserById(Long userId) {
        String cacheKey = USER_CACHE_KEY + userId;
        
        // 从缓存读取
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user != null) {
            return user;
        }
        
        // 从数据库读取
        user = userMapper.selectById(userId);
        if (user != null) {
            // 写入缓存
            redisTemplate.opsForValue().set(
                cacheKey, user, USER_CACHE_TTL, TimeUnit.SECONDS);
        }
        
        return user;
    }
    
    @CacheEvict(value = "user", key = "#userId")
    public void evictUserCache(Long userId) {
        String cacheKey = USER_CACHE_KEY + userId;
        redisTemplate.delete(cacheKey);
    }
    
    // 批量获取用户信息(缓存优化)
    public Map<Long, User> batchGetUsers(List<Long> userIds) {
        Map<Long, User> result = new HashMap<>();
        List<Long> missingIds = new ArrayList<>();
        
        // 先从缓存获取
        for (Long userId : userIds) {
            String cacheKey = USER_CACHE_KEY + userId;
            User user = (User) redisTemplate.opsForValue().get(cacheKey);
            if (user != null) {
                result.put(userId, user);
            } else {
                missingIds.add(userId);
            }
        }
        
        // 从数据库获取缺失的数据
        if (!missingIds.isEmpty()) {
            List<User> dbUsers = userMapper.selectBatchIds(missingIds);
            for (User user : dbUsers) {
                result.put(user.getId(), user);
                // 写入缓存
                String cacheKey = USER_CACHE_KEY + user.getId();
                redisTemplate.opsForValue().set(
                    cacheKey, user, USER_CACHE_TTL, TimeUnit.SECONDS);
            }
        }
        
        return result;
    }
}

      4.2 分布式锁实现

@Component
public class DistributedLock {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public <T> T executeWithLock(String lockKey, long waitTime, 
                                long leaseTime, Supplier<T> supplier) {
        RLock lock = redissonClient.getLock(lockKey);
        boolean locked = false;
        
        try {
            locked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (locked) {
                return supplier.get();
            } else {
                throw new BusinessException("获取锁失败,请稍后重试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("获取锁被中断");
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
// 在盲盒购买中使用分布式锁
@Service
public class BlindBoxPurchaseService {
    
    @Autowired
    private DistributedLock distributedLock;
    
    public ApiResult purchaseBox(Long userId, Long boxId) {
        String lockKey = "purchase:lock:" + boxId + ":" + userId;
        
        return distributedLock.executeWithLock(lockKey, 3, 10, () -> {
            // 检查库存
            Integer stock = getBoxStock(boxId);
            if (stock <= 0) {
                return ApiResult.error("库存不足");
            }
            
            // 检查用户购买限制
            if (hasReachedDailyLimit(userId, boxId)) {
                return ApiResult.error("今日购买次数已达上限");
            }
            
            // 执行购买逻辑
            return doPurchase(userId, boxId);
        });
    }
}

  五、性能优化

  5.1图片懒加载优化

# Dockerfile
FROM openjdk:11-jre-slim
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", \
    "-Djava.security.egd=file:/dev/./urandom", \
    "-Dspring.profiles.active=${ACTIVE_PROFILE}", \
    "/app.jar"]
    # docker-compose.yml
version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: blindbox
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
  
  redis:
    image: redis:6.2-alpine
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes
  
  rabbitmq:
    image: rabbitmq:3.9-management
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: admin123
  
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: prod
      DB_HOST: mysql
      REDIS_HOST: redis
      RABBITMQ_HOST: rabbitmq
    depends_on:
      - mysql
      - redis
      - rabbitmq
volumes:
  mysql_data:

  5.2请求缓存优化

// utils/cache.js
// 请求缓存管理
class RequestCache {
  constructor() {
    this.cache = new Map()
    this.maxSize = 100
  }
  // 生成缓存key
  generateKey(config) {
    return `${config.method}_${config.url}_${JSON.stringify(config.data)}`
  }
  // 获取缓存
  get(key) {
    const item = this.cache.get(key)
    if (!item) return null
    // 检查过期
    if (item.expire && Date.now() > item.expire) {
      this.cache.delete(key)
      return null
    }
    return item.data
  }
  // 设置缓存
  set(key, data, expire = 300000) { // 默认5分钟
    // 清理旧缓存
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, {
      data,
      expire: expire > 0 ? Date.now() + expire : 0
    })
  }
  // 清除缓存
  clear() {
    this.cache.clear()
  }
}
// 在请求中应用缓存
export const cachedRequest = async (config) => {
  const cache = new RequestCache()
  const key = cache.generateKey(config)
  
  // 尝试从缓存获取
  if (config.cache !== false) {
    const cached = cache.get(key)
    if (cached) {
      return cached
    }
  }
  
  // 发送请求
  const result = await request(config)
  
  // 缓存结果
  if (config.cache !== false && result.success) {
    cache.set(key, result, config.cacheTime || 300000)
  }
  
  return result
}

    结语

  通过本文的完整流程拆解,我们系统性地实现了基于uni-app的去水印小程序开发。从项目初始化、核心功能模块构建,到多平台适配优化,最后完成打包发布,形成了一个完整的开发闭环。整个系统采用Vue 3组合式API编写,代码结构清晰可维护,充分利用了uni-app的跨平台优势。关键实现包括:智能链接解析、高效视频处理、本地历史记录管理、用户设置存储等核心功能,并通过条件编译优雅处理了多平台差异。性能优化方面实现了图片懒包等关键技术,确保了小程序运行的流畅性。开发者可基于此源码快速构建自己的去水印应用,并根据实际需求扩展批量处理、视频编辑、会员服务等增值功能。这套解决方案不仅提供了完整的技术实现,更为后续产品迭代奠定了坚实基础。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70049145/viewspace-3106765/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2025-12-31

  • 博文量
    1
  • 访问量
    1298