JAVA记录媒体视频播放进度开发

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: JAVA记录媒体视频播放进度开发

搭建环境:springBoot + apache-maven-3.6.3 + mysql + Redisson3.15.4


项目背景


项目需要做一个记录视频播放进度的功能,有以下几点需要着重注意:

  1. 1、点击视频,播放到几小时几分几秒,下次同一个人点击进来依然是当前时间段
  2. 2、当一个维度下有多个视频可以看,分开记录当前视频或者文档是否已经看完。比如学习维度下有两个视频,一个文档,文档或者视频看完,直接显示当前视频已看完,但学习维度还显示正在学习,除非当前维度下的所有视频或者文档全部显示为已学完。
  3. 3、文档点击进入页面可以直接认定为已看完,视频看完必须要看完时长的三分之二,才能显示当前视频已看完。
  4. 4、记录当前天每个人实际学习时间总时长(额外需求开发)。
  5. 5、 单个媒体视频学完之后,直接已学完,这种逻辑很好实现。多个媒体在同一个维度下,比如娱乐维度下有三个视频,学习维度下有五个学习视频,只要当前维度下的所有视频全部显示已学完,才可以让当前维度更新数据库显示已学完,怎么统计呢?解决办法:当前维度下的单个视频,每次第一次进入就去更新是否已学完。还有当单个媒体最新状态显示已学完,只有这两个情况才进入方法去更新当前维度下的学习状态。


需求分析


会出现同一个人在不同的页面或者不同的浏览器打开同一门课的情况,当前情况可以能用redis分布式锁解决。


首先记录学习【媒体播放进度表】,应该包含:

  • 已学习时长:用于判断当前是否已学完,当时长等于总长度三分二时。
  • 媒体播放进度:用于返回给前端,上次已播放的位子。
  • 媒体视频总时长:用于判断是否已学完,还是正在学。
  • 学习状态:已学完COMPALETED,正在学LEARNING。
  • 课程维度主键id:唯一性。
  • 课程下媒体或者文档主键id:唯一性。
  • 学习用户。
  • 创建时间。
  • 修改时间。

【课程维度】表,上面表记录单个视频是否已学完,课程维度表记录当前维度下的所有视频是否已学完。

  • 课程主键id。
  • 课程下媒体最后学习id。
  • 用户id。
  • 已学完总的视频媒体数量。
  • 学习状态:已学完,未学完。
  • 创建时间。
  • 修改时间。

【学习时长】表,应该有

  • 总学习时间。
  • 当前日期。
  • 记录当前天开始学习时间。
  • 为了考虑延伸扩展性,加一个存储类型字段,当前类型可以根据天,周,月,年等维度来统计时长。
  • 学习用户。
  • 创建时间。
  • 修改时间。

这样设计的目的,当前学习时长表不光在此需求中可以使用,以后在其他需求,也可以用此表。

既然是记录视频学习时长,所以前端肯定需要写一个定时器传json数据,为了防止调用频率太高,但又不能记录不到播放记录,所以定时时间定位15s一次。

  • SectionId:代表媒体视频或者文档的id。
  • CourseId:代表媒体视频或者文档属于哪个维度下的id,比如学习维度,娱乐维度。
  • sectionType:类型,代表当前是媒体视频还是文档。
  • deltaDuration:增量时间,非视频为0,视频就传新增的看视频时长。
  • mediaProgress:视频播放节点,非视频为0,视频就传已看到的视频节点。
  • first:是否第一次打开当前媒体。(方便以后扩展使用)


代码开发


先创建学习进度表和学习总时长表:

//学习进度表
create table user_learn_stats(
  `id` int not null auto_increment,
  course_id int comment '课程id',
  section_id int comment '节id',
  user_id varchar(32) comment '用户id',
  learned_duration bigint comment '已学习时长',
  media_progress bigint comment '媒体进度',
  media_duration bigint comment '媒体总时长',
  learned_status varchar(32) comment '已学完,学习中',
  create_date datetime,
  update_date datetime,
  primary key(id)
);
//学习总时长表
create table user_learned(
    `id` int not null auto_increment,
    user_id varchar(32) comment '用户id',
    duration bigint comment '学习总时长',
    learn_status varchar(32) comment '学习类型',
    create_date datetime,
    update_date datetime,
    primary key (id)
);

建立对应两个表的实体类。



/**
 * @author keying
 */
@Data
public class UserLearned {
    private Long id;
    private Long courseId;
    private Long sectionId;
    private String userId;
    private Long duration;
    private String learnStats;
    private Date createDate;
    private Date updateDate;
}
/**
 * @author keying
 */
@Data
public class UserLearnStats {
    private Long id;
    private Long courseId;
    private Long sectionId;
    private String userId;
    private Long learnedDuration;
    private Long mediaProgress;
    /**
     * 总时长
     */
    private Long mediaDuration;
    private String learnedStatus;
    private Date createDate;
    private Date updateDate;
}

前端需要传递过来的json参数,

/**
 * 媒体播放进度
 *
 * @author keying
 */
@Data
public class ProgressRequest {
    /**
     * 节id(媒体视频id)
     */
    private Long sectionId;
    /**
     * 代表媒体视频或者文档属于哪个维度下的id,比如学习维度,娱乐维度
     */
    @NotNull
    private Long courseId;
    /**
     * 类型,代表当前是媒体视频还是文档
     */
    private String sectionType;
    /**
     * 增量时间,非视频为0,视频就传新增的看视频时长
     */
    private Long deltaDuration;
    /**
     * 视频播放节点,非视频为0,视频就传已看到的视频节点
     */
    private Long mediaProgress;
    /**
     * 是否第一次打开当前媒体。(方便以后扩展使用)
     */
    private Boolean first;
}

准备完毕开始写接口,因为是一个节的进度记录,所以接口名称这样设计更合理:





/**
 * 媒体进度
 *
 * @author keying
 */
@RestController
@RequestMapping("/section")
public class SectionController {
    @Autowired
    private ProgressService progressService;
    @PostMapping("/{id}/progress")
    public void progress(@PathVariable Long id, @RequestBody @Valid ProgressRequest request) {
        progressService.progress(id, request);
    }
}

接下来看业务层:




/**
 * @author keying
 */
@Service
public class ProgressServiceImpl implements ProgressService {
    //默认用户
    public final String NAME = "keying";
    @Autowired
    private UserLearnStatsMapper userLearnStatsMapper;
    @Autowired
    private UserLearnedMapper userLearnedMapper;
    @Override
    public void progress(Long id, ProgressRequest request) {
        //存入学习总时长
        progressUserLearned(id, request);
        //统计当前课程 & 节是否已学完
        insertUserLearnStats(id, request);
    }
    private void progressUserLearned(Long id, ProgressRequest request) {
        //总学习时长增加
        UserLearned userLearnedSelect = new UserLearned();
        userLearnedSelect.setCourseId(request.getCourseId());
        userLearnedSelect.setSectionId(id);
        userLearnedSelect.setUserId(NAME);
        //获取到历史数据
        UserLearned userLearned = userLearnedMapper.selectOne(userLearnedSelect);
        if (!Objects.isNull(userLearned)) {
            //修改学习时长
            UserLearned userLearnedUpdate = new UserLearned();
            userLearnedUpdate.setId(userLearned.getId());
            userLearnedUpdate.setUpdateDate(new Date());
            userLearnedUpdate.setDuration(userLearned.getDuration() + request.getDeltaDuration());
            userLearnedMapper.updateById(userLearnedUpdate);
            return;
        }
        //新增学习时长
        UserLearned userLearnedInsert = new UserLearned();
        userLearnedInsert.setUserId(NAME);
        userLearnedInsert.setCourseId(request.getCourseId());
        userLearnedInsert.setSectionId(id);
        userLearnedInsert.setCreateDate(new Date());
        userLearnedInsert.setUpdateDate(new Date());
        userLearnedInsert.setDuration(request.getDeltaDuration());
        userLearnedMapper.insert(userLearnedInsert);
    }
    private void insertUserLearnStats(Long id, ProgressRequest request) {
        //处理节,媒体是否已学完
        UserLearnStats userLearnStatsSelect = new UserLearnStats();
        userLearnStatsSelect.setUserId(NAME);
        userLearnStatsSelect.setSectionId(id);
        userLearnStatsSelect.setCourseId(request.getCourseId());
        //获取到历史数据
        UserLearnStats userLearnStats = userLearnStatsMapper.selectOne(userLearnStatsSelect);
        //节状态
        if (!Objects.isNull(userLearnStats)) {
            //修改
            UserLearnStats updateUserLearnStats = new UserLearnStats();
            updateUserLearnStats.setId(userLearnStats.getId());
            updateUserLearnStats.setUpdateDate(new Date());
            //获取是否已学完
            boolean flag = calculateLearned(request, updateUserLearnStats.getMediaDuration(),
                request.getMediaProgress() + request.getMediaProgress());
            updateUserLearnStats.setLearnedStatus("LEARNING");
            if (flag) {
                updateUserLearnStats.setLearnedStatus("COMPALETE");
            }
            userLearnStatsMapper.updateById(updateUserLearnStats);
            return;
        }
        //无历史数据则创建
        UserLearnStats insertUserLearnStats = new UserLearnStats();
        insertUserLearnStats.setCourseId(request.getCourseId());
        insertUserLearnStats.setCreateDate(new Date());
        insertUserLearnStats.setUpdateDate(new Date());
        insertUserLearnStats.setLearnedDuration(request.getDeltaDuration());
        //总时长默认都60*1000毫秒
        insertUserLearnStats.setMediaDuration(60 * 1000L);
        //获取是否已学完
        boolean flag = calculateLearned(request, insertUserLearnStats.getMediaDuration(),
            request.getMediaProgress() + request.getMediaProgress());
        insertUserLearnStats.setLearnedStatus("LEARNING");
        if (flag) {
            insertUserLearnStats.setLearnedStatus("COMPALETE");
        }
        insertUserLearnStats.setMediaProgress(request.getMediaProgress());
        insertUserLearnStats.setSectionId(id);
        insertUserLearnStats.setUserId(NAME);
        userLearnStatsMapper.insert(insertUserLearnStats);
        //处理课程 是否学完
    }
    private boolean calculateLearned(ProgressRequest request, Long allDuration, Long learnedDuration) {
        if (request.getSectionType().equals("TEXT")) {
            return Boolean.TRUE;
        }
        if (request.getSectionType().equals("VIDEO")) {
            if (learnedDuration >= (allDuration * 2 / 3)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }
}

前面每日学习时长 和 单个视频,文档已学完都已经记录完毕,那维度下多个视频学完怎么处理呢?先建一个课程表和实体类:




mysql> create table user_course_learned(
    -> `id` int not null auto_increment,
    -> course_id int comment '课程id',
    -> last_section_id int comment '最后学习节id',
    -> learned_section_count bigint comment '学习完节数量',
    -> learn_status varchar(32) comment '已学完compalate,学习ing',
    -> create_date datetime,
    -> update_date datetime,
    -> primary key(id)
    -> );
Query OK, 0 rows affected (0.04 sec)
//实体类
/**
 * @author keying
 */
@Data
public class UserCourseLearned {
    private Long id;
    private Long courseId;
    private Long lastSectionId;
    private String userId;
    private Long learnedSectionCount;
    private String learnedStatus;
    private Date createDate;
    private Date updateDate;
}

先引入redisson包,这里就不详细说明了,有需要的可以看看之前的文章怎么用redisson。

private void courseRedisson(Boolean aFalse, ProgressRequest request, String oldLearnedStatus,
        String newLearnedStatus) {
        // 默认连接上127.0.0.1:6379
        RedissonClient redissonClient = Redisson.create();
        // 一个分布式锁,指明锁的名称
        RLock rLock = redissonClient.getLock(NAME + request.getCourseId());
        try {
            if (rLock.tryLock(1, 1, TimeUnit.MINUTES)) {
                log.info("获取到锁");
                courseProgress(Boolean.FALSE, request, oldLearnedStatus,
                    newLearnedStatus);
            }
        } catch (Exception e) {
        } finally {
            rLock.unlock();
        }
    }

之后当获取到锁的页面会进入业务层:



package com.alibaba.first.service.lmpl;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import com.alibaba.first.mapper.UserCourseLearnedMapper;
import com.alibaba.first.mapper.UserLearnStatsMapper;
import com.alibaba.first.mapper.UserLearnedMapper;
import com.alibaba.first.model.ProgressRequest;
import com.alibaba.first.model.UserCourseLearned;
import com.alibaba.first.model.UserLearnStats;
import com.alibaba.first.model.UserLearned;
import com.alibaba.first.service.ProgressService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author keying
 * @date 2021/9/1
 */
@Service
@Slf4j
public class ProgressServiceImpl implements ProgressService {
    //默认用户
    public final String NAME = "keying";
    @Autowired
    private UserLearnStatsMapper userLearnStatsMapper;
    @Autowired
    private UserLearnedMapper userLearnedMapper;
    @Autowired
    private UserCourseLearnedMapper userCourseLearnedMapper;
    @Override
    public void progress(Long id, ProgressRequest request) {
        //存入学习总时长
        progressUserLearned(id, request);
        //统计当前课程 & 节是否已学完
        insertUserLearnStats(id, request);
    }
    private void progressUserLearned(Long id, ProgressRequest request) {
        //总学习时长增加
        UserLearned userLearnedSelect = new UserLearned();
        userLearnedSelect.setCourseId(request.getCourseId());
        userLearnedSelect.setSectionId(id);
        userLearnedSelect.setUserId(NAME);
        //获取到历史数据
        UserLearned userLearned = userLearnedMapper.selectOne(userLearnedSelect);
        if (!Objects.isNull(userLearned)) {
            //修改学习时长
            UserLearned userLearnedUpdate = new UserLearned();
            userLearnedUpdate.setId(userLearned.getId());
            userLearnedUpdate.setUpdateDate(new Date());
            userLearnedUpdate.setDuration(userLearned.getDuration() + request.getDeltaDuration());
            userLearnedMapper.updateById(userLearnedUpdate);
            return;
        }
        //新增学习时长
        UserLearned userLearnedInsert = new UserLearned();
        userLearnedInsert.setUserId(NAME);
        userLearnedInsert.setCourseId(request.getCourseId());
        userLearnedInsert.setSectionId(id);
        userLearnedInsert.setCreateDate(new Date());
        userLearnedInsert.setUpdateDate(new Date());
        userLearnedInsert.setDuration(request.getDeltaDuration());
        userLearnedMapper.insert(userLearnedInsert);
    }
    private void insertUserLearnStats(Long id, ProgressRequest request) {
        //处理节,媒体是否已学完
        UserLearnStats userLearnStatsSelect = new UserLearnStats();
        userLearnStatsSelect.setUserId(NAME);
        userLearnStatsSelect.setSectionId(id);
        userLearnStatsSelect.setCourseId(request.getCourseId());
        //获取到历史数据
        UserLearnStats userLearnStats = userLearnStatsMapper.selectOne(userLearnStatsSelect);
        //节状态
        if (!Objects.isNull(userLearnStats)) {
            //修改
            UserLearnStats updateUserLearnStats = new UserLearnStats();
            updateUserLearnStats.setId(userLearnStats.getId());
            updateUserLearnStats.setUpdateDate(new Date());
            //获取是否已学完
            boolean flag = calculateLearned(request, updateUserLearnStats.getMediaDuration(),
                request.getMediaProgress() + request.getMediaProgress());
            updateUserLearnStats.setLearnedStatus("LEARNING");
            if (flag) {
                updateUserLearnStats.setLearnedStatus("COMPALETE");
            }
            userLearnStatsMapper.updateById(updateUserLearnStats);
            //处理课程 是否学完
            courseRedisson(Boolean.FALSE, request, userLearnStats.getLearnedStatus(),
                updateUserLearnStats.getLearnedStatus());
            return;
        }
        //无历史数据则创建
        UserLearnStats insertUserLearnStats = new UserLearnStats();
        insertUserLearnStats.setCourseId(request.getCourseId());
        insertUserLearnStats.setCreateDate(new Date());
        insertUserLearnStats.setUpdateDate(new Date());
        insertUserLearnStats.setLearnedDuration(request.getDeltaDuration());
        //总时长默认都60*1000毫秒
        insertUserLearnStats.setMediaDuration(60 * 1000L);
        //获取是否已学完
        boolean flag = calculateLearned(request, insertUserLearnStats.getMediaDuration(),
            request.getMediaProgress() + request.getMediaProgress());
        insertUserLearnStats.setLearnedStatus("LEARNING");
        if (flag) {
            insertUserLearnStats.setLearnedStatus("COMPALETE");
        }
        insertUserLearnStats.setMediaProgress(request.getMediaProgress());
        insertUserLearnStats.setSectionId(id);
        insertUserLearnStats.setUserId(NAME);
        userLearnStatsMapper.insert(insertUserLearnStats);
        //处理课程 是否学完
        courseRedisson(Boolean.TRUE, request, null,
            insertUserLearnStats.getLearnedStatus());
    }
    private void courseRedisson(Boolean aFalse, ProgressRequest request, String oldLearnedStatus,
        String newLearnedStatus) {
        // 默认连接上127.0.0.1:6379
        RedissonClient redissonClient = Redisson.create();
        // 一个分布式锁,指明锁的名称
        RLock rLock = redissonClient.getLock(NAME + request.getCourseId());
        try {
            if (rLock.tryLock(1, 1, TimeUnit.MINUTES)) {
                log.info("获取到锁");
                courseProgress(Boolean.FALSE, request, oldLearnedStatus,
                    newLearnedStatus);
            }
        } catch (Exception e) {
        } finally {
            rLock.unlock();
        }
    }
    /**
     * @param aFalse           是否第一次进入
     * @param request
     * @param oldLearnedStatus 老的学习状态
     * @param newLearnedStatus 新的学习状态
     */
    private void courseProgress(Boolean aFalse, ProgressRequest request, String oldLearnedStatus,
        String newLearnedStatus) {
        //限制进入条件:
        // 当是第一次进入的时候,前面的百分百是false,导致全部为false。
        //当第二次进入的时候,前面的百分之百为true。后面的就必须为false,才能跳过执行业务代码。
        boolean flag = !aFalse && (newLearnedStatus.equals("LEARNING") || oldLearnedStatus.equals("COMPALETE")
            && newLearnedStatus.equals("COMPALETE"));
        //当为false的时候跳过,不执行return
        if (flag) {
            return;
        }
        Date now = new Date();
        //查询是否是第一次
        UserCourseLearned userCourseLearnedSelect = new UserCourseLearned();
        userCourseLearnedSelect.setCourseId(request.getCourseId());
        userCourseLearnedSelect.setUserId(NAME);
        UserCourseLearned userCourseLearnedOne = userCourseLearnedMapper.selectOne(userCourseLearnedSelect);
        //存在则修改
        if (!Objects.isNull(userCourseLearnedOne)) {
            UserCourseLearned updateUserCourseLearned = new UserCourseLearned();
            updateUserCourseLearned.setId(userCourseLearnedOne.getId());
            updateUserCourseLearned.setUpdateDate(now);
            updateUserCourseLearned.setLastSectionId(request.getSectionId());
            //填充学习状态和学习完的节数量
            populateCourseStatus(updateUserCourseLearned, userCourseLearnedOne.getLearnedSectionCount(), newLearnedStatus);
            userCourseLearnedMapper.updateById(updateUserCourseLearned);
            return;
        }
        //不存在则新增
        UserCourseLearned updateUserCourseInsert = new UserCourseLearned();
        updateUserCourseInsert.setUserId(NAME);
        updateUserCourseInsert.setCourseId(request.getCourseId());
        updateUserCourseInsert.setCreateDate(now);
        updateUserCourseInsert.setLastSectionId(request.getSectionId());
        updateUserCourseInsert.setUpdateDate(now);
        //填充学习状态和学习完的节数量
        populateCourseStatus(updateUserCourseInsert, 0L, newLearnedStatus);
        userCourseLearnedMapper.insert(updateUserCourseInsert);
    }
    /**
     * @param updateUserCourseInsert 参数
     * @param count                  旧的已学完节
     * @param newLearnedStatus       新的学习状态
     */
    private void populateCourseStatus(UserCourseLearned updateUserCourseInsert, Long count, String newLearnedStatus) {
        //已学完
        if (newLearnedStatus.equals("COMPALETE")) {
            //新增一节数量
            updateUserCourseInsert.setLearnedSectionCount(count + 1L);
            //先查询课程下的总节数是多少,这里代码演示,没有建立课程表,直接 写死每个课程总节数是5
            Long sectionLearnedCount = getSectionLearnedCount();
            if(updateUserCourseInsert.getLearnedSectionCount() >= sectionLearnedCount){
                updateUserCourseInsert.setLearnedStatus("COMPALETE");
            }else{
                updateUserCourseInsert.setLearnedStatus("LEARNING");
            }
            return;
        }
        //未学完
        updateUserCourseInsert.setLearnedSectionCount(count);
        updateUserCourseInsert.setLearnedStatus("LEARNING");
    }
    private Long getSectionLearnedCount() {
        return 5L;
    }
    private boolean calculateLearned(ProgressRequest request, Long allDuration, Long learnedDuration) {
        if (request.getSectionType().equals("TEXT")) {
            return Boolean.TRUE;
        }
        if (request.getSectionType().equals("VIDEO")) {
            if (learnedDuration >= (allDuration * 2 / 3)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }
}

里面代码比较复杂,都写了注释详解,有不明白的可以联系本人,欢迎大家一起来沟通技术。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
Java API Maven
如何使用Java开发抖音API接口?
在数字化时代,社交媒体平台如抖音成为生活的重要部分。本文详细介绍了如何用Java开发抖音API接口,从创建开发者账号、申请API权限、准备开发环境,到编写代码、测试运行及注意事项,全面覆盖了整个开发流程。
109 10
|
29天前
|
监控 Java API
如何使用Java语言快速开发一套智慧工地系统
使用Java开发智慧工地系统,采用Spring Cloud微服务架构和前后端分离设计,结合MySQL、MongoDB数据库及RESTful API,集成人脸识别、视频监控、设备与环境监测等功能模块,运用Spark/Flink处理大数据,ECharts/AntV G2实现数据可视化,确保系统安全与性能,采用敏捷开发模式,提供详尽文档与用户培训,支持云部署与容器化管理,快速构建高效、灵活的智慧工地解决方案。
|
19天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
38 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
6天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
52 13
|
11天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
44 10
|
5天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
41 2
|
14天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
42 2
|
1月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
1月前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
64 1
下一篇
DataWorks