1、项目的背景,包括:是自研还是外包、什么业务、服务的客户群是谁、谁去运营等问题。
2、项目的业务流程
3、项目的功能模块
4、项目的技术架构
5、个人工作职责
6、个人负责模块的详细说明,包括模块的设计,所用到的技术,技术的实现方案等。
一个例子:
我最近参与的项目是我们公司自研的专门针对成人职业技能教育的网络课堂系统,网站提供了成人职业技能培训的相关课程,如:软件开发培训、职业资格证书培训、成人学历教育培训等课程。项目基于B2B2C的业务模式,培训机构可以在平台入驻、发布课程,我们公司作为运营方由专门的人员对发布的课程进行审核,审核通过后课程才可以发布成功,课程包括免费和收费两种形式,对于免费课程普通用户可以直接选课学习,对于收费课程在选课后需要支付成功才可以继续学习。
本项目包括用户端、机构端、运营端三个端。
核心模块包括:内容管理、媒资管理、课程搜索、订单支付、选课管理、认证授权等。
本项目采用前后端分离架构,后端采用SpringBoot、SpringCloud技术栈开发,数据库使用了MySQL,还使用的Redis、消息队列、分布式文件系统、Elasticsearch等中间件系统。
划分的微服务包括:内容管理服务、媒资管理服务、搜索服务、订单支付服务、 学习中心服务、系统管理服务、认证授权服务、网关服务、注册中心服务、配置中心服务等。
我在这个项目中负责了内容管理、媒资管理、订单支付模块的设计与开发。
内容管理模块,是对平台上的课程进行管理,课程的相关信息比较多这里在数据库设计了课程基本信息表、课程营销表、课程计划、课程师资表进行存储 ,培训机构要发布一门课程需要填写课程基本信息、课程营销信息、课程计划信息、课程师资信息,填写完毕后需要提交审核,由运营人员进行课程信息的审核,整个审核过程是程序自动审核加人工确认的方式,通常24小时审核完成。课程审核通过即可发布课程,课程的相关信息会聚合到课程发布表中,这里不仅要将课程信息写到课程发布表还要将课程信息写到索引库、分布式文件系统中,所以这里存在分布式事务的问题,项目使用本地消息表加任务调度的方式去解决这里的分布式事务,保存数据的最终一致性。
B2B2C是一种电子商务类型的网络购物商业模式,B是Business的简称,C是Consumer的简称,第一个B指的是商品或服务的供应商,第二个B指的是从事电子商务的企业,C则是表示消费者。
学成在线采用当前流行的前后端分离架构开发,由用户层、UI层、微服务层、数据层等部分组成,为PC、App、
H5等客户端用户提供服务。下图是系统的技术架构图:
各模块说明如下:
重点了解微服务技术栈:
学成在线服务端基于Spring Boot构建,采用Spring Cloud微服务框架。
持久层:MySQL、MongoDB、Redis、ElasticSearch
数据访问层:使用Spring Data JPA 、Mybatis、Spring Data Mongodb等
业务层:Spring IOC、Aop事务控制、Spring Task任务调度、Feign、Ribbon、Spring AMQP、Spring Data Redis
等。
控制层:Spring MVC、FastJSON、RestTemplate、Spring Security Oauth2+JWT等
微服务治理:Eureka、Zuul、Hystrix、Spring Cloud Config等
在提交的时候,如果提交的版本和远程仓库版本的代码冲突,需要手动解决,可以用图形化界面merge自己手动解决冲突代码。
在开发中,通常我们不会再主分支开发,而是会另外在一个分支开发,等完成需求后再合并代码。
比如说,项目需要依赖A,和C,A依赖B1.0,C依赖B2.0,这样就会造成B版本冲突,解决的方法有两个。
第一:可以用exclusion排除依赖,比如我们需要的是B1.0版本,那么可以在C添加exclusion B
第二:可以在父工程指定B的版本号
CMS是什么 ?
CMS (Content Management System)即内容管理系统,不同的项目对CMS的定位不同,比如:一个在线教育网
站,有些公司认为CMS系统是对所有的课程资源进行管理,而在早期网站刚开始盛行时很多公司的业务是网站制
作,当时对CMS的定位是创建网站,即对网站的页面、图片等静态资源进行管理。
本项目CMS的定位是什么?
本项目作为一个大型的在线教育平台,对CMS系统的定位是对各各网站(子站点)页面的管理,主要管理由于运营
需要而经常变动的页面,从而实现根据运营需要快速进行页面开发、上线的需求。
使用ssh客户端工具FinalShell远程 连接 虚拟机中的CentOS系统。
IP地址:192.168.101.65
账号与密码为:root/centos
执行 systemctl start docker 启动docker。
运行: sh /data/soft/restart.sh
查询docker容器:docker ps
用navicat连接Linux的mysql
用户名:root
密码: mysql
package com.xuecheng.generator;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Arrays;
/**
* MyBatis-Plus 代码生成类
*/
public class ContentCodeGenerator {
// TODO 修改服务名以及数据表名
private static final String SERVICE_NAME = "content";
//数据库账号
private static final String DATA_SOURCE_USER_NAME = "root";
//数据库密码
private static final String DATA_SOURCE_PASSWORD = "mysql";
//生成的表
private static final String[] TABLE_NAMES = new String[]{
// "mq_message",
// "mq_message_history"
"course_base",
"course_market",
"course_teacher",
"course_category",
"teachplan",
"teachplan_media",
"course_publish",
"course_publish_pre"
};
// TODO 默认生成entity,需要生成DTO修改此变量
// 一般情况下要先生成 DTO类 然后修改此参数再生成 PO 类。
private static final Boolean IS_DTO = false;
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 选择 freemarker 引擎,默认 Velocity
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setFileOverride(true);
//生成路径
gc.setOutputDir(System.getProperty("user.dir") + "/xuecheng-plus-generator/src/main/java");
gc.setAuthor("itcast");
gc.setOpen(false);
gc.setSwagger2(false);
gc.setServiceName("%sService");
gc.setBaseResultMap(true);
gc.setBaseColumnList(true);
if (IS_DTO) {
gc.setSwagger2(true);
gc.setEntityName("%sDTO");
}
mpg.setGlobalConfig(gc);
// 数据库配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setUrl("jdbc:mysql://192.168.101.65:3306/xc402_" + SERVICE_NAME
+ "?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername(DATA_SOURCE_USER_NAME);
dsc.setPassword(DATA_SOURCE_PASSWORD);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(SERVICE_NAME);
pc.setParent("com.xuecheng");
pc.setServiceImpl("service.impl");
pc.setXml("mapper");
pc.setEntity("model.po");
mpg.setPackageInfo(pc);
// 设置模板
TemplateConfig tc = new TemplateConfig();
mpg.setTemplate(tc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(TABLE_NAMES);
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
// Boolean类型字段是否移除is前缀处理
strategy.setEntityBooleanColumnRemoveIsPrefix(true);
strategy.setRestControllerStyle(true);
// 自动填充字段配置
strategy.setTableFillList(Arrays.asList(
new TableFill("create_date", FieldFill.INSERT),
new TableFill("change_date", FieldFill.INSERT_UPDATE),
new TableFill("modify_date", FieldFill.UPDATE)
));
mpg.setStrategy(strategy);
mpg.execute();
}
}
yml配置:
server:
servlet:
context-path: /content
port: 63040
#微服务配置
spring:
application:
name: content-api
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.101.65:3306/xc402_content?serverTimezone=UTC&userUnicode=true&useSSL=false&
username: root
password: mysql
# 日志文件配置路径
logging:
config: classpath:log4j2-dev.xml
swagger:
title: "学成在线内容管理系统"
description: "内容系统管理系统对课程相关信息进行管理"
base-package: com.xuecheng.content
enabled: true
version: 1.0.0
package com.xuecheng.content.api;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.content.model.dto.QueryCourseParamsDto;
import com.xuecheng.content.model.po.CourseBase;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* @author Mr.M
* @version 1.0
* @description TODO
* @date 2023/2/11 15:44
*/
(value = "课程信息管理接口",tags = "课程信息管理接口")
public class CourseBaseInfoController {
("课程查询接口")
("/course/list")
public PageResult<CourseBase> list(PageParams pageParams, (required=false) QueryCourseParamsDto queryCourseParamsDto) {
CourseBase courseBase = new CourseBase();
courseBase.setName("测试名称");
courseBase.setCreateDate(LocalDateTime.now());
List<CourseBase> courseBases = new ArrayList();
courseBases.add(courseBase);
PageResult pageResult = new PageResult<CourseBase>(courseBases,10,1,10);
return pageResult;
}
}