项目整体总结

一、项目介绍

1、网站介绍

我的项目是一个在线教育网站,分为前台用户系统和后台管理系统;前台用户系统面向学员用户,后台管理系统面向网站运营管理员。

前台用户系统主要功能包括:

  • 1、通过课程列表获取课程的价格、讲师、介绍、章节等详情信息,点击章节可以播放课程视频。
  • 2、通过讲师列表查找讲师的教的课程和本人介绍、评级等信息。

后台管理系统主要功能包括:

  • 课程管理模块,包括课程的上传和删除、课程的分类
  • 讲师管理模块,包括讲师信息的添加和删除
  • 统计分析模块,统计分析课程播放量、订单量等信息
  • 订单管理模块,
  • 权限管理模块,

2、技术栈

后端:springboot、springcloud、Mybatis-Plus、springsecurity、redis、nginx、jwt、cannal、Nacos

前端:vue、element-ui

使用到的API:阿里云oss对象存储服务、阿里云视频点播服务、阿里云短信服务、微信支付

3、项目模块

 

 

 4、系统架构

前后端分离的架构

二、数据库设计

  • 使用MySQL数据库

1、表的设计规范

表的设计依据:《阿里巴巴Java开发手册》中规定的

每张表都有:主键id、is_deleted逻辑删除(tinyint类型,01逻辑删除)、gmt_create创建时间(datetime类型)、gmt_modified(datetime类型)更新时间 这几个字段。

如果使用分库分表集群部署,则id类型为verchar,非自增,业务中使用分布式id生成器

2、各表之间的关系 

3、课程分类——表的设计

  • 课程的  一级分类和二级分类  都放在一张表中

         表中有parent_id、id、分类名字段,如果parent_id为0,说明是一级分类。二级分类的parent_id是对应的一级分类的id,这样完成了课程的一级分类和二级分类之间的关系。(给面试官讲的时候要把具体的一级二级分类有哪些说出来,要不然面试官容易听不懂!

三、跨域的解决方案 

1、什么是跨域

        跨域是因为浏览器的同源策略引起的,当一个请求的url的协议、域名、端口任意一个与当前页面的url不同就是跨域

2、解决方案一

  • 在Controller类上添加注解@CrossOrigin
  • @RestController
    @CrossOrigin //解决跨域问题
    @RequestMapping("/eduservice/edu-teacher")
    public class EduTeacherController {}
    

3、解决方案二

  • springcloud的解决方案

四、Swagger 

  • 前后端分离开发模式中,api文档是最好的前后端沟通方式,通过Swagger生成api文档。

1、Swagger的配置

  • common包中编写一个Swagger的配置类

2、Swagger的使用

  •  在其他模块使用时,先引入依赖
<dependencies>
    <dependency>
        <artifactId>service-base</artifactId>
        <groupId>com.achang</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>
  • 在controller层的接口类和方法上使用注解:@Api@ApiOperation,注解中写入接口的说明

五、接口返回统一的数据格式

  • 项目中我们会将接口的响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android,Web)对数据的操作更一致、轻松。
  • 一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。
  • 但是一般会包含状态码返回消息数据这几部分内容。

1、接口返回的统一数据格式

{
"success": 布尔, //响应是否成功
"code": 数字, //响应码
"message": 字符串, //返回的文字消息
"data": HashMap //返回数据,放在键值对中
}

2、统一封装结果的类——R

  • R来自common的common_utils
  • 返回的data用Map数据结构来存放
  • @Data
    public class R {
        @ApiModelProperty("是否成功")
        private boolean success;
    
        @ApiModelProperty("响应码")
        private Integer code;
    
        @ApiModelProperty("返回信息")
        private String message;
    
        @ApiModelProperty("返回数据")
        private Map<String, Object> data = new HashMap<String, Object>();
    
        //无参构造方法私有
        private R() {
        }
    
        //成功 静态方法
        public static R ok(){
            R r = new R();
            r.setSuccess(true);
            r.setCode(ResultCode.SUCCESS);
            r.setMessage("成功。。。");
            return r;
        }
    
        //失败 静态方法
        public static R error(){
            R r = new R();
            r.setSuccess(false);
            r.setCode(ResultCode.ERROR);
            r.setMessage("失败");
            return r;
        }
    
        public R success(Boolean success){
            this.setSuccess(success);
            return this;
        }
    
        public R code(Integer code){
            this.setCode(code);
            return this;
        }
    
        public R message(String message){
            this.setMessage(message);
            return this;
        }
    
        public R data(String key,Object value){
            this.data.put(key,value);
            return this;
        }
    
        public R data(Map<String,Object> map){
            this.setData(map);
            return this;
        }
    
    }
    

3、其他模块的使用 

  • 添加依赖即可导入R 
    <dependency>
        <groupId>com.achang</groupId>
        <artifactId>common-utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    

六、阿里云OSS对象云存储服务

  • 为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案 - 阿里云 OSS 

 1、阿里云设置

  • 开通阿里云OSS对象存储服务
  • 创建一个Bucket存储空间
  • 获取阿里云OSS许可证id和密钥),用于访问OSS存储空间

 2、使用OSS对象存储服务

  • pom.xml中添加阿里云OSS服务的依赖
  •  在  后台管理系统  讲师管理模块  添加讲师  时有一个  上传头像  按钮,会请求这个接口,接口返回一个存储头像的url
@Api(description="阿里云文件管理")
@CrossOrigin //跨域
@RestController
@RequestMapping("/edu_oss/fileoss")
public class OssController {

    @Autowired
    private OssService ossService;

    //上传头像
    @ApiOperation(value = "文件上传")
    @PostMapping("/upload")
    public R uploadOssFile(@RequestParam("file") MultipartFile file){
        //获取上传的文件

        //返回上传到oss的路径
        String url = ossService.uploadFileAvatar(file);

        //返回r对象
        return R.ok().data("url",url).message("文件上传成功");
    }

}
  • 根据存储空间(Bucket)的 地址、bucket名、id和密钥 即可完成存储过程,并得到存储的url

@Service
public class OssServiceImpl implements OssService{

    //上传文件到阿里云oss
    @Override
    public String uploadFileAvatar(MultipartFile file){

        // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
        String endpoint = ConstandPropertiesUtils.END_POINT;
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = ConstandPropertiesUtils.KEY_ID;
        String accessKeySecret = ConstandPropertiesUtils.KEY_SECRET;
        String buketName = ConstandPropertiesUtils.BUCKET_NAME;




        //获取上传文件的输入流
        InputStream inputStream = null;

        try {
            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

            inputStream = file.getInputStream();

            //获取文件名称
            String fileName = file.getOriginalFilename();

            //在文件名里添加一个随机唯一的值,否则同名文件上传到阿里云oss会覆盖
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            fileName = uuid + fileName;


            //把文件按日期进行分类,也就是说路径上自动添加日期2021/11/27/图片.JPG,会在oss服务器内创建对应文件夹,这样方便按日期管理文件
            String datePath = new DateTime().toString("yyyy/MM/dd");//获取当前日期
            fileName = datePath + "/" + fileName;
            //真实上传oss的文件路径"https://hankong-edu-1010.oss-cn-beijing.aliyuncs.com/2021/11/27/aliyun.PNG90ef72b0ef7a41da9c0fed3d206ff425"

            // 依次填写Bucket名称和Object完整路径。Object完整路径中不能包含Bucket名称。
            ossClient.putObject(buketName, fileName, inputStream);

            // 关闭OSSClient。
            ossClient.shutdown();

            //返回文件路径
            //需要把上传到阿里云oss路径手动拼接出来
            //https://achang-edu.oss-cn-hangzhou.aliyuncs.com/default.gif
            return "https://"+buketName+"."+endpoint+"/"+fileName;

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

    }

}

七、阿里云视频点播服务——上传视频到阿里云

使用服务端SDK

  • 简介:sdk的方式将api进行了进一步的封装,不用自己创建工具类。

  • 我们可以基于服务端SDK编写代码来调用点播API,实现对点播产品和服务的快速操作
  • 功能介绍:SDK封装了对API的调用请求和响应,避免自行计算较为繁琐的 API签名。支持所有点播服务的API,并提供了相应的示例代码。
     

 VodController

@RestController
@CrossOrigin
@RequestMapping("/eduvod/video")
public class VodController {

    @Autowired
    private VodService vodService;

    //上传视频到阿里云
    @PostMapping("/uploadAliyunVideo")
    public R  uploadAliyunVideo(MultipartFile file){
        //返回上传视频的id
        String videoId = vodService.uploadVideoAliyun(file);
        return R.ok().data("videoId",videoId);
    }

}

 VodServiceImpl

@Service
public class VodServiceImpl implements VodService {
    @Override
    public String uploadVideoAliyun(MultipartFile file) {

        try {
            //accessKeyId,accessKeySecret

            //fileName:上传文件原始名称
            String fileName = file.getOriginalFilename();

            //title:上传之后显示名称
            String title = fileName.substring(0,fileName.lastIndexOf("."));

            //inputStream:上传文件的输入流
            InputStream inputStream = file.getInputStream();

            UploadStreamRequest request = new UploadStreamRequest(ConstantVodUtils.ACCESSKEY_ID
                    , ConstantVodUtils.ACCESSKEY_SECRET
                    , title, fileName
                    , inputStream);

            UploadVideoImpl uploader = new UploadVideoImpl();
            UploadStreamResponse response = uploader.uploadStream(request);
            System.out.print("RequestId=" + response.getRequestId() + "\n");  //请求视频点播服务的请求ID
            String videoId = null;
            if (response.isSuccess()) {
                videoId = response.getVideoId();
            } else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
                videoId = response.getVideoId();
            }
            return videoId;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}

七、阿里云视频点播服务——视频播放器

  • 点击立即播放按钮,播放视频

 

 1、播放方式一:播放地址播放(不推荐)

//播放方式一:此种方式不能播放加密视频
source : '你的视频播放地址',

2、播放方式二:播放凭证播放(推荐)

         阿里云播放器支持通过播放凭证自动换取播放地址进行播放,接入方式更为简单,且安全性更高。播放凭证默认时效为100秒(最大为3000秒),只能用于获取指定视频的播放地址,不能混用或重复使用。如果凭证过期则无法获取播放地址,需要重新获取凭证。

  •  前端

 3、后端

//根据视频id获取视频凭证
@GetMapping("/getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id){
    try {
        String playAuth = vodService.getPlayAuth(id);
        return R.ok().data("PlayAuth",playAuth);
    } catch (Exception e) {
        e.printStackTrace();
        throw new AchangException(20001,"获取视频凭证失败");
    }

}
    //根据视频id获取视频凭证
    @Override
    public String getPlayAuth(String id) {
        String accesskeyId = ConstantVodUtils.ACCESSKEY_ID;
        String accesskeySecret = ConstantVodUtils.ACCESSKEY_SECRET;

        try {
            //创建初始化对象
            DefaultAcsClient cl = InitObject.initVodClient(accesskeyId,accesskeySecret);
            //创建获取视频地址request对象和response对象
            GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
            //向request对象设置视频id值
            request.setVideoId(id);

            GetVideoPlayAuthResponse response = cl.getAcsResponse(request);

            //获取视频播放凭证
            return response.getPlayAuth();

        } catch (ClientException e) {
            e.printStackTrace();
            throw new AchangException(20001,"获取视频凭证失败");
        }
        
    }
//初始化类
public class InitObject {
    public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
        String regionId = "cn-shanghai";  // 点播服务接入区域
        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        return client;
    }
}

 

八、项目集成Redis

1、添加redis配置类

@Configuration //配置类
@EnableCaching //开启缓存
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config =
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofSeconds(600)) //设置缓存存在的时间 600s
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                        .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }


}

 2、修改redis.conf文件,在阿里云服务器上启动redis

redis配置文件修改:

  • 允许远程连接
  • 开机自启动

启动redis:

3、.properties配置文件

spring.redis.host=192.168.44.132 
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#spring.redis.password=你设置的redis密码,没有可以不写

#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

4、在对应方法上添加redis缓存注解:@Cacheable

  •  @Cacheable

        根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。

 5、哪些数据要缓存

        由于首页数据变化不是很频繁,而且首页访问量相对较大,所以我们有必要把首页接口数据缓存到redis缓存中,减少数据库压力和提高访问速度。

        主要是首页的幻灯片轮播图和首页中的热门课程数据需要缓存。

九、登录业务 

登录业务

微信二维码登录

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值