课程添加完成后可通过我的课程进入课程修改页面,此页面显示我的课程列表,如下图所示,可分页查询。
注意:由于课程图片服务器没有搭建,课程图片暂时无法显示。
上边的的查询要实现分页、多表联合查询,所以使用mybatis实现课程查询。
分页查询使用PageHelper。
PageHelper是mybatis的通用分页插件,通过mybatis的拦截器实现分页功能,拦截sql查询请求,添加分页语句,最终实现分页查询功能。
PageHelper的使用方法及原理如下:在调用dao的service方法中设置分页参数:PageHelper.startPage(page, size),分页参数会设置在ThreadLocal中
PageHelper在mybatis执行sql前进行拦截,从ThreadLocal取出分页参数,修改当前执行的sql语句,添加分页sql。
最后执行添加了分页sql的sql语句,实现分页查询。
我的课程列表使用element 的card组件,如下:
课程名称
新增课程
{{course.name}}
管理课程
使用v-for进行遍历,循环输出courses中的课程。
输入参数:页码,每页显示个数、查询条件
输出结果类型:QueryResponseResult
package com.xuecheng.framework.model.response;
import lombok.Data;
import lombok.ToString;
/**
* @author 98050
*/
@Data
@ToString
public class QueryResponseResult extends ResponseResult {
QueryResult queryResult;
public QueryResponseResult(ResultCode resultCode,QueryResult queryResult){
super(resultCode);
this.queryResult = queryResult;
}
}
查询条件:
package com.xuecheng.framework.domain.course.request;
import com.xuecheng.framework.model.request.RequestData;
import lombok.Data;
import lombok.ToString;
/**
* @author 98050
*/
@Data
@ToString
public class CourseListRequest extends RequestData {
/**
* 公司id
*/
private String companyId;
}
查询本机构开设的所有课程
/**
* 课程列表分页查询
* @param page
* @param size
* @param courseListRequest
* @return
*/
@ApiOperation("课程列表分页查询")
@ApiImplicitParams({
@ApiImplicitParam(name = "page",value = "页码",required = true,paramType = "path",dataType = "int"),
@ApiImplicitParam(name = "size",value = "页大小",required = true,paramType = "path",dataType = "int")
})
@ApiResponses({
@ApiResponse(code = 10000,message = "操作成功"),
@ApiResponse(code = 11111,message = "操作失败")
})
@GetMapping("/list/{page}/{size}")
QueryResponseResult findCourseList(@PathVariable("page") int page, @PathVariable("size") int size, CourseListRequest courseListRequest);
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author 98050
*/
@Data
@ToString
@Entity
@Table(name="course_base")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class CourseBase implements Serializable {
private static final long serialVersionUID = -916357110051689486L;
@Id
@GeneratedValue(generator = "jpa-uuid")
@Column(length = 32)
private String id;
private String name;
private String users;
private String mt;
private String st;
private String grade;
private String studymodel;
private String teachmode;
private String description;
private String status;
@Column(name="company_id")
private String companyId;
@Column(name="user_id")
private String userId;
}
package com.xuecheng.framework.domain.course.ext;
import com.xuecheng.framework.domain.course.CourseBase;
import lombok.Data;
import lombok.ToString;
/**
* @author 98050
*/
@Data
@ToString
public class CourseInfo extends CourseBase {
//课程图片
private String pic;
}
课程图片和课程信息分开管理是为了减轻数据库压力
对应的数据库表
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.domain.course.ext.CourseInfo;
import com.xuecheng.framework.domain.course.request.CourseListRequest;
import org.apache.ibatis.annotations.Mapper;
import com.github.pagehelper.Page;
/**
* @author 98050
*/
@Mapper
public interface CourseMapper {
/**
* 课程分页查询
* @param courseListRequest
* @return
*/
Page<CourseInfo> findCourseListPage(CourseListRequest courseListRequest);
/**
* 根据id查询课程
* @param id
* @return
*/
CourseBase findCourseBaseById(String id);
}
mapper映射文件:
<mapper namespace="com.xuecheng.managecourse.dao.CourseMapper">
<select id="findCourseBaseById" parameterType="java.lang.String"
resultType="com.xuecheng.framework.domain.course.CourseBase">
select * from course_base where id = #{id}
select>
<select id="findCourseListPage" resultType="com.xuecheng.framework.domain.course.ext.CourseInfo" parameterType="com.xuecheng.framework.domain.course.request.CourseListRequest">
SELECT course_base.*,pic
FROM course_base,course_pic
WHERE id = courseid AND course_base.company_id = #{companyId}
select>
mapper>
查询的时候将课程图片也一起查出
接口:
/**
* 分页查询课程
* @param page
* @param size
* @param courseListRequest
* @return
*/
QueryResponseResult queryByPage(int page, int size, CourseListRequest courseListRequest);
实现:
/**
* 分页查询课程
* @param page
* @param size
* @param courseListRequest
* @return
*/
@Override
public QueryResponseResult queryByPage(int page, int size, CourseListRequest courseListRequest) {
//1.分页
PageHelper.startPage(page,size);
//2.构建查询条件
if (StringUtils.isEmpty(courseListRequest.getCompanyId())){
ExceptionCast.cast(CourseCode.COURSE_COMPANYISNULL);
}
Page<CourseInfo> courseInfos = this.courseMapper.findCourseListPage(courseListRequest);
List<CourseInfo> list = courseInfos.getResult();
QueryResult<CourseInfo> queryResult = new QueryResult<>();
queryResult.setTotal(courseInfos.getTotal());
queryResult.setList(list);
return new QueryResponseResult(CommonCode.SUCCESS,queryResult);
}
@Override
@GetMapping("coursebase/list/{page}/{size}")
public QueryResponseResult findCourseList(@PathVariable("page") int page, @PathVariable("size") int size, CourseListRequest courseListRequest) {
return this.courseService.queryByPage(page, size, courseListRequest);
}
export const findCourseList = (page, size, params) => {
// 使用工具类将json对象转成key/value
let queries = querystring.stringify(params)
return http.requestQuickGet(apiUrl + '/course/coursebase/list/' + page + '/' + size + '?' + queries)
}
课程管理端的登录会放在后面完成,当用户登录后就可以拿到用户所对应的公司id,然后查询该公司下所开设的所有课程。
//获取课程列表
getCourse() {
courseApi.findCourseList(this.page,this.size,{companyId : 1}).then((res) => {
console.log(res);
if(res.success){
this.total = res.queryResult.total;
this.courses = res.queryResult.list;
}
});
}
mounted() {
//查询我的课程
this.getCourse();
}
由于没有搭建图片服务器,所以图片暂时无法显示
用户操作流程如下:
1、用户进入“我的课程”页面,点击“新增课程”,进入新增课程页面
2、填写课程信息,选择课程分类、课程等级、学习模式等
3、信息填写完毕,点击“提交”,课程添加成功或失败,失败的时候返回提示信息。
需要解决的是在新增页面上输入的信息:
1、课程分类
多级分类,方便用户去选择
2、课程等级、学习模式等这些选项建议是可以配置的。
在新增课程界面需要选择课程所属分类, 分类信息是整个项目非常重要的信息,课程即商品,分类信息设置的好坏直接影响用户访问量。
分类信息在哪里应用?
1、首页分类导航
2、课程的归属地
添加课程时要选择课程的所属分类
还是树形结构的数据
在添加课程时需要选择课程所属的分类,这里需要定义课程分类查询接口。
接口格式要根据前端需要的数据格式来定义,前端展示课程分类使用elemenet-ui的cascader(级联选择器)组件。
数据格式例子如下:
[{
value: 'shejiyuanze',
label: '设计原则',
children: [{
value: 'yizhi',
label: '一致'
}, {
value: 'fankui',
label: '反馈'
}, {
value: 'xiaolv',
label: '效率'
}, {
value: 'kekong',
label: '可控'
}]
}]
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author 98050
*/
@Data
@ToString
@Entity
@Table(name="category")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class Category implements Serializable {
private static final long serialVersionUID = -906357110051689484L;
@Id
@GeneratedValue(generator = "jpa-assigned")
@Column(length = 32)
private String id;
private String name;
private String label;
private String parentid;
private String isshow;
private Integer orderby;
private String isleaf;
}
package com.xuecheng.framework.domain.course.ext;
import com.xuecheng.framework.domain.course.Category;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* @author 98050
*/
@Data
@ToString
public class CategoryNode extends Category {
List<CategoryNode> children;
}
package com.xuecheng.api.course;
import com.xuecheng.framework.domain.course.ext.CategoryNode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* @Author: 98050
* @Time: 2019-04-08 15:53
* @Feature:
*/
@Api(value = "课程分类管理",description = "课程分类管理",tags = {"课程分类管理"})
@RequestMapping("category")
public interface CategoryControllerApi {
/**
* 课程分类查询
* @return
*/
@ApiOperation("查询分类")
@GetMapping("list")
List<CategoryNode> findList();
}
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.ext.CategoryNode;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @Author: 98050
* @Time: 2019-04-08 16:57
* @Feature:
*/
@Mapper
public interface CategoryMapper {
/**
* 查询所有课程分类
* @return
*/
List<CategoryNode> selectList();
}
映射文件:
<mapper namespace="com.xuecheng.managecourse.dao.CategoryMapper">
<resultMap id="categoryMap" type="com.xuecheng.framework.domain.course.ext.CategoryNode">
<id property="id" column="one_id">id>
<result property="name" column="one_name">result>
<result property="label" column="one_label">result>
<collection property="children" ofType="com.xuecheng.framework.domain.course.ext.CategoryNode">
<id property="id" column="two_id">id>
<result property="name" column="two_name">result>
<result property="label" column="two_label">result>
collection>
resultMap>
<select id="selectList" resultMap="categoryMap">
SELECT a.id one_id,a.`name` one_name,a.label one_label,b.id two_id,b.`name` two_name,b.label two_label
FROM category a LEFT JOIN category b ON a.id = b.parentid
WHERE a.parentid = '1'
select>
mapper>
接口:
package com.xuecheng.managecourse.service;
import com.xuecheng.framework.domain.course.ext.CategoryNode;
import java.util.List;
/**
* @Author: 98050
* @Time: 2019-04-08 16:32
* @Feature:
*/
public interface CategoryService {
/**
* 查询所有课程分类
* @return
*/
List<CategoryNode> findCourseCategoryList();
}
实现:
package com.xuecheng.managecourse.service.impl;
import com.xuecheng.framework.domain.course.ext.CategoryNode;
import com.xuecheng.managecourse.dao.CategoryMapper;
import com.xuecheng.managecourse.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author: 98050
* @Time: 2019-04-08 16:55
* @Feature:
*/
@Service
public class CategoryServiceImpl implements CategoryService {
private final CategoryMapper categoryMapper;
@Autowired
public CategoryServiceImpl(CategoryMapper categoryMapper) {
this.categoryMapper = categoryMapper;
}
@Override
public List<CategoryNode> findCourseCategoryList() {
return this.categoryMapper.selectList();
}
}
package com.xuecheng.managecourse.controller;
import com.xuecheng.api.course.CategoryControllerApi;
import com.xuecheng.framework.domain.course.ext.CategoryNode;
import com.xuecheng.managecourse.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author: 98050
* @Time: 2019-04-08 17:30
* @Feature:
*/
@RestController
@RequestMapping("category")
public class CategoryController implements CategoryControllerApi {
private final CategoryService categoryService;
@Autowired
public CategoryController(CategoryService categoryService) {
this.categoryService = categoryService;
}
@Override
@GetMapping("list")
public List<CategoryNode> findList() {
return this.categoryService.findCourseCategoryList();
}
}
// 查询课程分类
export const categoryFindlist = () => {
return http.requestQuickGet(apiUrl + '/category/list')
}
在mounted方法中调用
// 查询课程分类
courseApi.categoryFindlist().then(res=>{
this.categoryList = res;
console.log(this.categoryList)
})
在新增课程界面需要选择课程等级、课程状态等,这些信息统一采用数据字典管理的方式。
本项目对一些业务的分类配置信息,比如:课程等级、课程状态、用户类型、用户状态等进行统一管理,通过在数据库创建数据字典表来维护这些分类信息。
数据字典对系统的业务分类进行统一管理,并且也可以解决硬编码问题,比如添加课程时选择课程等级,下拉框中的课程等级信息如果在页面硬编码将造成不易修改维护的问题,所以从数据字典表中获取,如果要修改名称则在数据字典修改即可,提高系统的可维护性。
在mongodb中创建数据字典表sys_dictionary
一个字典的信息如下:
{
"_id" : ObjectId("5a7e8d2dd019f15418fa2b71"),
"d_name" : "课程等级",
"d_type" : "200",
"d_value" : [
{
"sd_name" : "低级",
"sd_id" : "200001",
"sd_status" : "1"
},
{
"sd_name" : "中级",
"sd_id" : "200002",
"sd_status" : "1"
},
{
"sd_name" : "高级",
"sd_id" : "200003",
"sd_status" : "1"
}
]
}
字段说明:
d_name:字典名称
d_type:字典分类
d_value:字典数据
sd_name:项目名称
sd_id:项目id
sd_status:项目状态(1:可用,0不可用)
数据模型:
package com.xuecheng.framework.domain.system;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.List;
/**
* @author 98050
*/
@Data
@ToString
@Document(collection = "sys_dictionary")
public class SysDictionary {
@Id
private String id;
@Field("d_name")
private String dName;
@Field("d_type")
private String dType;
@Field("d_value")
private List<SysDictionaryValue> dValue;
}
SysDictionaryValue:
package com.xuecheng.framework.domain.system;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* @author 98050
*/
@Data
@ToString
public class SysDictionaryValue {
@Field("sd_id")
private String sdId;
@Field("sd_name")
private String sdName;
@Field("sd_status")
private String sdStatus;
}
package com.xuecheng.api.system;
import com.xuecheng.framework.domain.system.SysDictionary;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Author: 98050
* @Time: 2019-04-08 16:21
* @Feature:
*/
@Api(value = "数据字典接口",description = "提供数据字典接口的管理、查询功能")
@RequestMapping("sys")
public interface SysDictionaryControllerApi {
/**
* 数据字典查询接口
* @param type
* @return
*/
@ApiOperation(value = "数据字典查询接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "type",value = "类型",required = true,paramType = "path",dataType = "String"),
})
@ApiResponses({
@ApiResponse(code = 10000,message = "操作成功"),
@ApiResponse(code = 11111,message = "操作失败")
})
@GetMapping("/dictionary/{type}")
SysDictionary getByType(@PathVariable("type") String type);
}
因为数据字典在将来要进行统一管理,所以需要搭建xc-service-manage-sys-dictionary工程,
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xc-framework-parentartifactId>
<groupId>com.xuechenggroupId>
<version>1.0-SNAPSHOTversion>
<relativePath>../xc-framework-parent/pom.xmlrelativePath>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>xc‐service‐manage‐cms‐clientartifactId>
<dependencies>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-framework-modelartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-ioartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
dependencies>
project>
package com.xuecheng.sys.dictionary;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
/**
* 扫描实体类
*/
@EntityScan("com.xuecheng.framework.domain.system")
/**
* 扫描接口,swagger接口文档
*/
@ComponentScan(basePackages = {"com.xuecheng.api"})
/**
* 扫描本项目下的所有包
*/
@ComponentScan(basePackages = {"com.xuecheng.sys.dictionary"})
/**
* 扫描common工程下的类
*/
@ComponentScan(basePackages = {"com.xuecheng.framework"})
/**
* @Author: 98050
* @Time: 2019-04-08 21:52
* @Feature:
*/
public class ManageDictionaryApplication {
public static void main(String[] args) {
SpringApplication.run(ManageDictionaryApplication.class,args);
}
}
server:
port: 32000
spring:
application:
name: xc-service-sys-dictionary
data:
mongodb:
uri: mongodb://root:root@localhost:27017
database: xc_cms
package com.xuecheng.sys.dictionary.dao;
import com.xuecheng.framework.domain.system.SysDictionary;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @Author: 98050
* @Time: 2019-04-08 22:03
* @Feature:
*/
public interface SysDictionaryRepository extends MongoRepository<SysDictionary,String> {
/**
* 根据类型查找
* @param type
* @return
*/
SysDictionary findByDType(String type);
}
接口:
package com.xuecheng.sys.dictionary.service;
import com.xuecheng.framework.domain.system.SysDictionary;
/**
* @Author: 98050
* @Time: 2019-04-08 22:09
* @Feature:
*/
public interface SysDictionaryService {
/**
* 根据类型查询数据字典
* @param type
* @return
*/
SysDictionary findDictionaryByType(String type);
}
实现:
package com.xuecheng.sys.dictionary.service.impl;
import com.xuecheng.framework.domain.system.SysDictionary;
import com.xuecheng.framework.domain.system.response.SysDictionaryCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.sys.dictionary.dao.SysDictionaryRepository;
import com.xuecheng.sys.dictionary.service.SysDictionaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: 98050
* @Time: 2019-04-08 22:10
* @Feature:
*/
@Service
public class SysDictionaryServiceImpl implements SysDictionaryService {
private final SysDictionaryRepository sysDictionaryRepository;
@Autowired
public SysDictionaryServiceImpl(SysDictionaryRepository sysDictionaryRepository) {
this.sysDictionaryRepository = sysDictionaryRepository;
}
@Override
public SysDictionary findDictionaryByType(String type) {
SysDictionary dictionary = this.sysDictionaryRepository.findByDType(type);
if (dictionary == null){
ExceptionCast.cast(SysDictionaryCode.SYS_DICTIONARY_IS_NULL);
}
return dictionary;
}
}
package com.xuecheng.sys.dictionary.controller;
import com.xuecheng.api.system.SysDictionaryControllerApi;
import com.xuecheng.framework.domain.system.SysDictionary;
import com.xuecheng.sys.dictionary.service.SysDictionaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: 98050
* @Time: 2019-04-08 22:05
* @Feature:
*/
@RestController
@RequestMapping("sys")
public class SysDictionaryController implements SysDictionaryControllerApi {
private final SysDictionaryService sysDictionaryService;
@Autowired
public SysDictionaryController(SysDictionaryService sysDictionaryService) {
this.sysDictionaryService = sysDictionaryService;
}
@Override
@GetMapping("/dictionary/{type}")
public SysDictionary getByType(@PathVariable("type") String type) {
return this.sysDictionaryService.findDictionaryByType(type);
}
}
修改config目录下的index.js,将包含api/sys的请求转发到http://localhost:32000
修改完一定要重启项目
修改src/base/api下的system.js
// 数据字典
export const sysGetDictionary = type => {
return http.requestQuickGet(apiUrl + '/sys/dictionary/' + type)
}
数据模型:
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author 98050
*/
@Data
@ToString
@Entity
@Table(name="course_base")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class CourseBase implements Serializable {
private static final long serialVersionUID = -916357110051689486L;
@Id
@GeneratedValue(generator = "jpa-uuid")
@Column(length = 32)
private String id;
private String name;
private String users;
private String mt;
private String st;
private String grade;
private String studymodel;
private String teachmode;
private String description;
private String status;
@Column(name="company_id")
private String companyId;
@Column(name="user_id")
private String userId;
}
/**
* 课程信息添加
* @param courseBase
* @return
*/
@ApiOperation("课程信息添加")
@PostMapping("/coursebase/add")
AddCourseResult courseAdd(@RequestBody CourseBase courseBase);
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.CourseBase;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author 98050
*/
public interface CourseBaseRepository extends JpaRepository<CourseBase,String> {
}
接口:
/**
* 添加课程
* @param courseBase
* @return
*/
AddCourseResult courseAdd(CourseBase courseBase);
实现:
/**
* 课程添加
* @param courseBase
* @return
*/
@Override
public AddCourseResult courseAdd(CourseBase courseBase) {
if (courseBase == null){
ExceptionCast.cast(CourseCode.COURSE_ADD_PARAMTERISNULL);
}
CourseBase save = this.courseBaseRepository.save(courseBase);
return new AddCourseResult(CommonCode.SUCCESS, save.getId());
}
@Override
@PostMapping("/coursebase/add")
public AddCourseResult courseAdd(@RequestBody CourseBase courseBase) {
return this.courseService.courseAdd(courseBase);
}
// 添加课程基础信息
export const addCourseBase = params => {
return http.requestPost(apiUrl + '/course/coursebase/add', params)
}
save () {
//处理课程分类
// 选择课程分类存储到categoryActive
this.courseForm.mt= this.categoryActive[0]//大分类
this.courseForm.st= this.categoryActive[1]//小分类
console.log(this.courseForm)
courseApi.addCourseBase(this.courseForm).then(res=>{
if(res.success){
this.$message.success("提交成功")
//跳转到我的课程
this.$router.push({ path: '/course/list'})
}else{
this.$message.error(res.message)
}
})
}
课程添加成功进入课程管理页面,通过课程管理页面修改课程的基本信息、编辑课程图片、编辑课程营销信息等
数据模型:
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author 98050
*/
@Data
@ToString
@Entity
@Table(name="course_base")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class CourseBase implements Serializable {
private static final long serialVersionUID = -916357110051689486L;
@Id
@GeneratedValue(generator = "jpa-uuid")
@Column(length = 32)
private String id;
private String name;
private String users;
private String mt;
private String st;
private String grade;
private String studymodel;
private String teachmode;
private String description;
private String status;
@Column(name="company_id")
private String companyId;
@Column(name="user_id")
private String userId;
}
对应的数据库表:
1、定义course_manage.vue
为课程管理导航页面
课程首页
基本信息
课程图片
课程营销
课程计划
教师信息
发布课程
2、创建各种信息管理页面
通过管理页面的导航可以进入各各信息管理页面,这里先创建各各信息管理页面,页面内容暂时为空,待开发时再完善,在本模块的page目录下创建course_manage目录,此目录存放各各信息管理页面,页面明细如下:
3、创建路由
import Home from '@/module/home/page/home.vue';
import course_list from '@/module/course/page/course_list.vue';
import course_add from '@/module/course/page/course_add.vue';
import course_manage from '@/module/course/page/course_manage.vue';
import course_summary from '@/module/course/page/course_manage/course_summary.vue';
import course_picture from '@/module/course/page/course_manage/course_picture.vue';
import course_baseinfo from '@/module/course/page/course_manage/course_baseinfo.vue';
import course_marketinfo from '@/module/course/page/course_manage/course_marketinfo.vue';
import course_teacher from '@/module/course/page/course_manage/course_teacher.vue';
import course_plan from '@/module/course/page/course_manage/course_plan.vue';
import course_pub from '@/module/course/page/course_manage/course_pub.vue';
export default [
{
path: '/course',
component: Home,
name: '课程管理',
hidden: false,
iconCls: 'el-icon-document',
children: [
{ path: '/course/list', name: '我的课程',component: course_list,hidden: false },
{ path: '/course/add/base', name: '新增课程',component: course_add,hidden: true },
{ path: '/course/manager/:courseid', name: '管理课程',component: course_manage,hidden: true ,
children: [
{ path: '/course/manage/plan/:courseid', name: '课程计划',component: course_plan,hidden: false },
{ path: '/course/manage/baseinfo/:courseid', name: '基本信息',component: course_baseinfo,hidden: false },
{ path: '/course/manage/picture/:courseid', name: '课程图片',component: course_picture,hidden: false },
{ path: '/course/manage/marketinfo/:courseid', name: '营销信息',component: course_marketinfo,hidden: false },
{ path: '/course/manage/teacher/:courseid', name: '教师信息',component: course_teacher,hidden: false},
{ path: '/course/manage/pub/:courseid', name: '发布课程',component: course_pub,hidden: false},
{ path: '/course/manage/summary/:courseid', name: '课程首页',component: course_summary,hidden: false }
]}
]
}
]
修改课程需要如下接口:
1、根据id查询课程信息
2、修改课程提交
接口定义如下:
/**
* 根据id查询课程信息
* @param id
* @return
*/
@ApiOperation("根据id查询课程信息")
@GetMapping("/coursebase/{id}")
CourseBase getCoureseById(@PathVariable("id") String id);
/**
* 更新课程信息
* @param courseBase
* @return
*/
@ApiOperation("更新课程信息")
@PutMapping("/coursebase/update")
UpdateCourseResult updateCourse(@RequestBody CourseBase courseBase);
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.CourseBase;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author 98050
*/
public interface CourseBaseRepository extends JpaRepository<CourseBase,String> {
}
接口:
/**
* 根据id查询课程
* @param id
* @return
*/
CourseBase getCourseById(String id);
/**
* 修改课程信息
* @param courseBase
* @return
*/
UpdateCourseResult courseUpdate(CourseBase courseBase);
实现:
/**
* 根据id查询课程信息
* @param id
* @return
*/
@Override
public CourseBase getCourseById(String id) {
Optional<CourseBase> optional = this.courseBaseRepository.findById(id);
if (!optional.isPresent()){
ExceptionCast.cast(CourseCode.COURSE_GET_ISNULL);
}
return optional.get();
}
/**
* 修改课程信息
* @param courseBase
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public UpdateCourseResult courseUpdate(CourseBase courseBase) {
if (courseBase == null){
ExceptionCast.cast(CourseCode.COURSE_GET_ISNULL);
}
Optional<CourseBase> optional = this.courseBaseRepository.findById(courseBase.getId());
if (!optional.isPresent()){
ExceptionCast.cast(CourseCode.COURSE_GET_ISNULL);
}
CourseBase update = optional.get();
if (StringUtils.isEmpty(courseBase.getCompanyId())){
ExceptionCast.cast(CourseCode.COURSE_COMPANYISNULL);
}else {
update.setCompanyId(courseBase.getCompanyId());
}
if (StringUtils.isEmpty(courseBase.getDescription())){
ExceptionCast.cast(CourseCode.COURSE_DESCRIPTION_ISNULL);
}else {
update.setDescription(courseBase.getDescription());
}
if (StringUtils.isEmpty(courseBase.getGrade())){
ExceptionCast.cast(CourseCode.COURSE_GRADE_ISNULL);
}else {
update.setGrade(courseBase.getGrade());
}
if (StringUtils.isEmpty(courseBase.getMt())){
ExceptionCast.cast(CourseCode.COURSE_MT_ISNULL);
}else {
update.setMt(courseBase.getMt());
}
if (StringUtils.isEmpty(courseBase.getName())){
ExceptionCast.cast(CourseCode.COURSE_NAME_ISNULL);
}else {
update.setName(courseBase.getName());
}
if (StringUtils.isEmpty(courseBase.getSt())){
ExceptionCast.cast(CourseCode.COURSE_ST_ISNULL);
}else {
update.setSt(courseBase.getSt());
}
if (StringUtils.isEmpty(courseBase.getStatus())){
ExceptionCast.cast(CourseCode.COURSE_STATUS_ISNULL);
}else {
update.setStatus(courseBase.getStatus());
}
if (StringUtils.isEmpty(courseBase.getStudymodel())){
ExceptionCast.cast(CourseCode.COURSE_STUDYMODEL_ISNULL);
}else {
update.setStudymodel(courseBase.getStudymodel());
}
if (StringUtils.isEmpty(courseBase.getUsers())){
ExceptionCast.cast(CourseCode.COURSE_USERS_ISNULL);
}else {
update.setUsers(courseBase.getUsers());
}
CourseBase base = this.courseBaseRepository.saveAndFlush(update);
return new UpdateCourseResult(base, CommonCode.SUCCESS);
}
package com.xuecheng.framework.domain.course.response;
import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
/**
* @Author: 98050
* @Time: 2019-04-11 16:50
* @Feature:
*/
public class UpdateCourseResult extends ResponseResult {
private CourseBase courseBase;
public UpdateCourseResult(CourseBase courseBase, ResultCode resultCode) {
super(resultCode);
this.courseBase = courseBase;
}
}
@Override
@PutMapping("/coursebase/update")
public UpdateCourseResult updateCourse(CourseBase courseBase) {
return this.courseService.courseUpdate(courseBase);
}
// 查询课程信息
export const getCoursebaseById = courseId => {
return http.requestGet(apiUrl + '/course/coursebase/' + courseId)
}
// 修改课程基础信息
export const updateCoursebase = params => {
return http.requestPut(apiUrl + '/course/coursebase/update', params)
}
{{grade.sdName}}
{{studymodel_v.sdName}}
从“我的课程”页面进入具体的课程:
点击提交
课程营销信息包括课程价格、课程有效期等信息。
课程营销信息使用course_market表存储
数据模型如下:
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* Created by admin on 2018/2/10.
*/
@Data
@ToString
@Entity
@Table(name="course_market")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class CourseMarket implements Serializable {
private static final long serialVersionUID = -916357110051689486L;
@Id
@GeneratedValue(generator = "jpa-assigned")
@Column(length = 32)
private String id;
private String charge;
private String valid;
private String qq;
private Float price;
private Float price_old;
@Column(name = "start_time")
private Date startTime;
@Column(name = "end_time")
private Date endTime;
}
课程营销信息需要定义如下接口:
/**
* 根据课程id查询课程营销信息
* @param courseId
* @return
*/
@ApiOperation("获取课程营销信息")
@GetMapping("/coursemarket/{id}")
CourseMarket getCourseMarketById(@PathVariable("id") String courseId);
/**
* 更新或者新增课程营销信息
* @param courseId
* @param courseMarket
* @return
*/
@ApiOperation("更新课程营销信息")
@PutMapping("/coursemarket/{id}")
CourseMarketResult updateOrInsertCourseMarket(@PathVariable("id") String courseId,CourseMarket courseMarket);
接口实现可以采用先查询课程营销信息,如果存在则更新信息,否则添加课程营销信息的方法。
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.CourseMarket;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @Author: 98050
* @Time: 2019-04-12 16:24
* @Feature:
*/
public interface CourseMarketRepository extends JpaRepository<CourseMarket,String> {
}
接口:
/**
* 根据课程id查询课程营销信息
* @param id
* @return
*/
CourseMarket getCourseMarketById(String id);
/**
* 根据课程id查询营销信息,查到就做修改,查不到就做插入
* @param id
* @param courseMarket
* @return
*/
CourseMarketResult updateOrInsertCourseMarket(String id,CourseMarket courseMarket);
实现:
/**
* 根据课程id查询课程营销信息
* @param id 课程id
* @return
*/
@Override
public CourseMarket getCourseMarketById(String id) {
if (id == null){
ExceptionCast.cast(CourseCode.COURSE_ID_ISNULL);
}
Optional<CourseMarket> optional = this.courseMarketRepository.findById(id);
if (!optional.isPresent()){
ExceptionCast.cast(CourseCode.COURSE_MARKET_INFO_ISNULL);
}
return optional.get();
}
/**
* 根据课程id插入或者更新营销信息
* @param id 课程id
* @param courseMarket 营销信息
* @return
*/
@Override
public CourseMarketResult updateOrInsertCourseMarket(String id, CourseMarket courseMarket) {
Optional<CourseMarket> optional = this.courseMarketRepository.findById(id);
if (optional.isPresent()){
CourseMarket temp = optional.get();
//1.执行更新
if (StringUtils.isEmpty(courseMarket.getCharge())){
ExceptionCast.cast(CourseCode.COURSE_MARKET_INFO_CHARGE_ISNULL);
}else {
temp.setCharge(courseMarket.getCharge());
}
if (StringUtils.isEmpty(courseMarket.getId())){
ExceptionCast.cast(CourseCode.COURSE_ID_ISNULL);
}else {
temp.setId(courseMarket.getId());
}
if (StringUtils.isEmpty(courseMarket.getQq())){
ExceptionCast.cast(CourseCode.COURSE_MARKET_INFO_QQ_ISNULL);
}else {
temp.setQq(courseMarket.getQq());
}
if (StringUtils.isEmpty(courseMarket.getValid())){
ExceptionCast.cast(CourseCode.COURSE_MARKET_INFO_VALID_ISNULL);
}else {
temp.setValid(courseMarket.getValid());
}
temp.setPrice(courseMarket.getPrice());
temp.setPrice_old(courseMarket.getPrice_old());
temp.setStartTime(courseMarket.getStartTime());
temp.setEndTime(courseMarket.getEndTime());
CourseMarket save = this.courseMarketRepository.saveAndFlush(temp);
return new CourseMarketResult(save, CommonCode.SUCCESS);
}else {
//2.执行插入
CourseMarket save = this.courseMarketRepository.save(courseMarket);
return new CourseMarketResult(save, CommonCode.SUCCESS);
}
}
@Override
@GetMapping("/coursemarket/{id}")
public CourseMarket getCourseMarketById(@PathVariable("id") String courseId) {
return this.courseService.getCourseMarketById(courseId);
}
@Override
@PutMapping("/coursemarket/{id}")
public CourseMarketResult updateOrInsertCourseMarket(@PathVariable("id") String courseId, @RequestBody CourseMarket courseMarket) {
return this.courseService.updateOrInsertCourseMarket(courseId, courseMarket);
}
// 查询课程营销信息
export const getCourseMarketById = courseId => {
return http.requestGet(apiUrl + '/course/coursemarket/', courseId)
}
// 更新或者新增课程营销信息
export const updateCourseMarket = (courseId, coursemarket) => {
return http.requestPut(apiUrl + '/course/coursemarket/' + courseId, coursemarket)
}
{{charge.sdName}}
金额(元):
{{valid.sdName}}
开始时间:
结束时间:
页面加载的时候查询数据字典
查询:
在很多系统都有上传图片/上传文件的需求,比如:上传课程图片、上传课程资料、上传用户头像等,为了提供系统的可重用性专门设立文件系统服务承担图片/文件的管理,文件系统服务实现对文件的上传、删除、查询等功能进行管理。
各个子系统不再开发上传文件的请求,全部通过文件系统服务进行文件的上传、删除等操作。文件系统服务最终会将文件存储到FastDFS文件系统中。
下图是各个子系统与文件系统服务之间的关系:
下图是课程管理中上传图片流程:
执行流程如下:
1、管理员进入教学管理前端,点击上传图片
2、图片上传至文件系统服务,文件系统请求fastDFS上传文件
3、文件系统将文件入库,存储到文件系统服务数据库中。
4、文件系统服务向前端返回文件上传结果,如果成功则包括文件的Url路径。
5、课程管理前端请求课程管理进行保存课程图片信息到课程数据库。
6、课程管理服务将课程图片保存在课程数据库
1.工程目录结构
2.启动器
package com.xuecheng.filesystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
/**
* @author 98050
*/
@SpringBootApplication//扫描所在包及子包的bean,注入到ioc中
@EntityScan("com.xuecheng.framework.domain.filesystem")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.api"})//扫描接口
@ComponentScan(basePackages={"com.xuecheng.framework"})//扫描framework中通用类
@ComponentScan(basePackages={"com.xuecheng.filesystem"})//扫描本项目下的所有类
public class FileSystemApplication {
public static void main(String[] args) {
SpringApplication.run(FileSystemApplication.class,args);
}
}
3.pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xc-framework-parentartifactId>
<groupId>com.xuechenggroupId>
<version>1.0-SNAPSHOTversion>
<relativePath>../xc-framework-parent/pom.xmlrelativePath>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>xc-service-base-filesystemartifactId>
<dependencies>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-service-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-framework-modelartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-framework-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>net.oschina.zcx7878groupId>
<artifactId>fastdfs-client-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-ioartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
dependencies>
project>
4.配置文件
server:
port: 22100
spring:
application:
name: xc-service-base-filesystem
#mongo配置
data:
mongodb:
database: xc_fs
uri: mongodb://root:root@localhost:27017
#SpringMVC上传文件配置
servlet:
multipart:
#默认支持文件上传.
enabled: true
#支持文件写入磁盘.
file-size-threshold: 0
# 上传文件的临时目录
location:
# 最大支持文件大小
max-file-size: 1MB
# 最大支持请求大小
max-request-size: 30MB
xuecheng:
fastdfs:
connect_timeout_in_seconds: 5
network_timeout_in_seconds: 30
charset: UTF-8
tracker_servers: 192.168.19.121:22122 #多个 trackerServer中间以逗号分隔
系统的文件信息(图片、文档等小文件的信息)在mongodb中存储,下边是文件信息的模型类。
package com.xuecheng.framework.domain.filesystem;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Map;
/**
* @author 98050
*/
@Data
@ToString
@Document(collection = "filesystem")
public class FileSystem {
@Id
private String fileId;
//文件请求路径
private String filePath;
//文件大小
private long fileSize;
//文件名称
private String fileName;
//文件类型
private String fileType;
//图片宽度
private int fileWidth;
//图片高度
private int fileHeight;
//用户id,用于授权
private String userId;
//业务key
private String businesskey;
//业务标签
private String filetag;
//文件元信息
private Map metadata;
}
说明:
fileId:fastDFS返回的文件ID。
filePath:请求fastDFS浏览文件URL。
filetag:文件标签,由于文件系统服务是公共服务,文件系统服务会为使用文件系统服务的子系统分配文件标签,用于标识此文件来自哪个系统。
businesskey:文件系统服务为其它子系统提供的一个业务标识字段,各子系统根据自己的需求去使用,比如:课程管理会在此字段中存储课程id用于标识该图片属于哪个课程。
metadata:文件相关的元信息
在mongodb创建数据库xc_fs(文件系统数据库),并创建集合 filesystem。
在api工程下创建com.xuecheng.api.filesystem包
package com.xuecheng.api.filesystem;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author: 98050
* @Time: 2019-04-17 17:27
* @Feature:
*/
@RequestMapping("/filesystem")
@Api(value = "文件系统管理接口",description = "文件系统管理接口",tags = {"文件系统管理接口"})
public interface FileSystemControllerApi {
/**
* 上传文件
* @param multipartFile 文件
* @param filetag 文件标签
* @param businesskey 业务key
* @param metadata 元信息,json格式
* @return
*/
@ApiOperation("上传文件")
UploadFileResult upload(MultipartFile multipartFile,String filetag,String businesskey,String metadata);
}
package com.xuecheng.framework.domain.filesystem.response;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
/**
* @author 98050
*/
@Data
@ToString
public class UploadFileResult extends ResponseResult{
@ApiModelProperty(value = "文件信息", example = "true", required = true)
FileSystem fileSystem;
public UploadFileResult(ResultCode resultCode, FileSystem fileSystem) {
super(resultCode);
this.fileSystem = fileSystem;
}
}
模型类:
package com.xuecheng.framework.domain.filesystem;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Map;
/**
* @author 98050
*/
@Data
@ToString
@Document(collection = "filesystem")
public class FileSystem {
@Id
private String fileId;
//文件请求路径
private String filePath;
//文件大小
private long fileSize;
//文件名称
private String fileName;
//文件类型
private String fileType;
//图片宽度
private int fileWidth;
//图片高度
private int fileHeight;
//用户id,用于授权
private String userId;
//业务key
private String businesskey;
//业务标签
private String filetag;
//文件元信息
private Map metadata;
}
dao:
package com.xuecheng.filesystem.dao;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @Author: 98050
* @Time: 2019-04-17 17:35
* @Feature:
*/
public interface FileSystemRepository extends MongoRepository<FileSystem,String> {
}
接口:
package com.xuecheng.filesystem.service;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author: 98050
* @Time: 2019-04-18 15:12
* @Feature:
*/
public interface FileSystemService {
/**
* 上传文件
* @param multipartFile 文件
* @param filetag 文件标签
* @param businesskey 业务key
* @param metadata 元信息,json格式
* @return
*/
UploadFileResult upload(MultipartFile multipartFile, String filetag, String businesskey, String metadata);
}
实现:
package com.xuecheng.filesystem.service.impl;
import com.alibaba.fastjson.JSON;
import com.xuecheng.filesystem.dao.FileSystemRepository;
import com.xuecheng.filesystem.service.FileSystemService;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import com.xuecheng.framework.domain.filesystem.response.FileSystemCode;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import org.apache.commons.lang3.StringUtils;
import org.csource.common.MyException;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Map;
/**
* @Author: 98050
* @Time: 2019-04-18 15:25
* @Feature:
*/
@Service
public class FileSystemServiceImpl implements FileSystemService {
private static final Logger logger= LoggerFactory.getLogger(FileSystemServiceImpl.class);
@Value("${xuecheng.fastdfs.tracker_servers}")
private String tracker_servers;
@Value("${xuecheng.fastdfs.connect_timeout_in_seconds}")
private int connect_timeout_in_seconds;
@Value("${xuecheng.fastdfs.network_timeout_in_seconds}")
private int network_timeout_in_seconds;
@Value("${xuecheng.fastdfs.charset}")
private String charset;
@Autowired
private FileSystemRepository fileSystemRepository;
/**
* 加载fdfs的配置
*/
private void initFdfsConfig(){
try {
ClientGlobal.initByTrackers(tracker_servers);
ClientGlobal.setG_connect_timeout(connect_timeout_in_seconds);
ClientGlobal.setG_network_timeout(network_timeout_in_seconds);
ClientGlobal.setG_charset(charset);
} catch (IOException | MyException e) {
e.printStackTrace();
//初始化文件系统出错
ExceptionCast.cast(FileSystemCode.FS_INITFDFSERROR);
}
}
/**
* 上传文件
* @param multipartFile 文件
* @param filetag 文件标签
* @param businesskey 业务key
* @param metadata 元信息,json格式
* @return
*/
@Override
public UploadFileResult upload(MultipartFile multipartFile, String filetag, String businesskey, String metadata) {
if (multipartFile == null){
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_FILEISNULL);
}
//1.上传文件
String fileId = fdfsUpload(multipartFile);
//2.创建文件信息对象
FileSystem fileSystem = new FileSystem();
//2.1 文件id
fileSystem.setFileId(fileId);
//2.2 文件在文件系统中的路径
fileSystem.setFilePath(fileId);
//2.3 业务标识
fileSystem.setBusinesskey(businesskey);
//2.3 文件标签
fileSystem.setFiletag(filetag);
//2.4 元数据
if (StringUtils.isNotEmpty(metadata)){
try {
Map map = JSON.parseObject(metadata,Map.class);
fileSystem.setMetadata(map);
}catch (Exception e){
e.printStackTrace();
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_METAERROR);
}
}
//2.5 名称
fileSystem.setFileName(multipartFile.getOriginalFilename());
//2.6 大小
fileSystem.setFileSize(multipartFile.getSize());
//2.7 文件类型
fileSystem.setFileType(multipartFile.getContentType());
//3.保存
FileSystem save = fileSystemRepository.save(fileSystem);
return new UploadFileResult(CommonCode.SUCCESS, save);
}
/**
* 上传文件到fdfs
* @param multipartFile
* @return
*/
private String fdfsUpload(MultipartFile multipartFile) {
try {
//1.加载配置
initFdfsConfig();
//2.创建tracker client
TrackerClient trackerClient = new TrackerClient();
//3. 获取trackerServer
TrackerServer trackerServer = trackerClient.getConnection();
//4. 获取storage
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
//5. 创建storage client
StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);
//6. 上传文件
//6.1 文件字节
byte[] bytes = multipartFile.getBytes();
//6.2 文件原始名称
String name = multipartFile.getOriginalFilename();
if (name == null){
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_NAMEISNULL);
}
//6.3 文件扩展名
String extName = name.substring(name.lastIndexOf(".") + 1);
//6.4 文件id
return storageClient1.upload_file1(bytes, extName, null);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
package com.xuecheng.filesystem.controller;
import com.xuecheng.api.filesystem.FileSystemControllerApi;
import com.xuecheng.filesystem.service.FileSystemService;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author: 98050
* @Time: 2019-04-18 16:41
* @Feature:
*/
@RestController
@RequestMapping("/filesystem")
public class FileSystemController implements FileSystemControllerApi {
private final FileSystemService fileSystemService;
@Autowired
public FileSystemController(FileSystemService fileSystemService) {
this.fileSystemService = fileSystemService;
}
@Override
@PostMapping("/upload")
public UploadFileResult upload(@RequestParam("multipartFile") MultipartFile multipartFile,
@RequestParam(value = "filetag") String filetag,
@RequestParam(value = "businesskey",required = false) String businesskey,
@RequestParam(value = "metadata",required = false)String metadata) {
return fileSystemService.upload(multipartFile, filetag, businesskey, metadata);
}
}
返回结果
访问图片地址:
使用Element-UI的Upload上传组件实现
el-upload参数说明:
<script>
import * as sysConfig from '@/../config/sysConfig';
import * as courseApi from '../../api/course';
import utilApi from '../../../../common/utils';
import * as systemApi from '../../../../base/api/system';
export default {
data() {
return {
picmax:1,//最大上传文件的数量
courseid:'',
dialogImageUrl: '',
dialogVisible: false,
fileList:[],
uploadval:{filetag:"course"},//上传提交的额外的数据 ,将uploadval转成key/value提交给服务器
imgUrl:sysConfig.imgUrl
}
},
methods: {
//超出文件上传个数提示信息
rejectupload(){
this.$message.error("最多上传"+this.picmax+"个图片");
},
//在上传前设置上传请求的数据
setuploaddata(){
},
//删除图片
handleRemove(file, fileList) {
console.log(file)
},
//上传成功的钩子方法
handleSuccess(response, file, fileList){
console.log(response)
},
//上传失败执行的钩子方法
handleError(err, file, fileList){
this.$message.error('上传失败');
//清空文件队列
this.fileList = []
}
},
mounted(){
//课程id
this.courseid = this.$route.params.courseid;
}
}
</script>
图片上传到文件系统后,其它子系统如果想使用图片可以引用图片的地址,课程管理模块使用图片的方式是将图片地址保存到课程数据库中。业务流程如下:
1、上传图片到文件系统服务
2、保存图片地址到课程管理服
在课程管理服务创建保存课程与图片对应关系的表 course_pic。
3、在course_pic保存图片成功后方可查询课程图片信息。
通过查询course_pic表数据则查询到某课程的图片信息。
课程管理需要使用图片则在课程管理服务中要提供保存课程图片的api。
/**
* 课程图片信息保存
* @param courseId
* @param pic
* @return
*/
@ApiOperation("添加课程图片")
@ApiImplicitParams({
@ApiImplicitParam(name = "courseId",value = "课程id",required = true,paramType = "path",dataType = "String"),
@ApiImplicitParam(name = "pic",value = "图片url",required = true,paramType = "path",dataType = "String")
})
ResponseResult addCoursePic(String courseId,String pic);
模型:
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author 98050
*/
@Data
@ToString
@Entity
@Table(name="course_pic")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class CoursePic implements Serializable {
private static final long serialVersionUID = -916357110051689486L;
@Id
@GeneratedValue(generator = "jpa-assigned")
private String courseid;
private String pic;
}
dao:
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.CoursePic;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @Author: 98050
* @Time: 2019-04-18 19:51
* @Feature:
*/
public interface CoursePicRepository extends JpaRepository<CoursePic,String> {
}
接口:
/**
* 添加课程图片
* @param courseId 课程id
* @param pic 图片地址
* @return
*/
ResponseResult saveCoursePic(String courseId,String pic);
实现:
/**
* @param courseId 课程id
* @param pic 图片地址
* @return
*/
@Override
public ResponseResult saveCoursePic(String courseId, String pic) {
//1.查询课程图片
Optional<CoursePic> optional = this.coursePicRepository.findById(courseId);
CoursePic coursePic = null;
if (optional.isPresent()){
coursePic = optional.get();
}
//2.没有课程图片则新建对象
if (coursePic == null){
coursePic = new CoursePic();
}
coursePic.setCourseid(courseId);
coursePic.setPic(pic);
this.coursePicRepository.save(coursePic);
return new ResponseResult(CommonCode.SUCCESS);
}
@Override
@PostMapping("/coursepic/add")
public ResponseResult addCoursePic(@RequestParam("courseId") String courseId, @RequestParam("pic") String pic) {
return this.courseService.saveCoursePic(courseId, pic);
}
前端需要在上传图片成功后保存课程图片信息
// 保存课程图片地址到课程数据库
export const addCoursePic = (courseId, pic) => {
return http.requestPost(apiUrl + '/course/coursepic/add?courseId=' + courseId + '&pic=' + pic)
}
实现钩子函数:
上传成功::on-success="handleSuccess"
上传失败::on-error="handleError"
//上传成功的钩子方法
handleSuccess(response, file, fileList){
console.log(response)
//调用课程管理的保存图片接口,将图片信息保存到课程管理数据库course_pic中
//从response得到新的图片文件的地址
if(response.success){
let fileId = response.fileSystem.fileId;
courseApi.addCoursePic(this.courseid,fileId).then(res=>{
if(res.success){
this.$message.success("上传图片成功!")
}else{
this.$message.error(res.message)
}
})
}
},
//上传失败执行的钩子方法
handleError(err, file, fileList){
this.$message.error('上传失败');
//清空文件队列
this.fileList = []
}
课程图片上传成功,再次进入课程页面应该显示已经上传的图片
在课程管理服务定义查询方法:
/**
* 课程图片查询
* @param courseId 课程id
* @return
*/
@ApiOperation("课程图片查询")
@GetMapping("/coursepic/list/{courseId}")
CoursePic findCoursePic(@PathVariable("courseId") String courseId);
接口:
/**
* 查询课程图片
* @param courseId 课程id
* @return
*/
CoursePic findCoursePic(String courseId);
实现:
/**
* 查询课程图片
* @param courseId 课程id
* @return
*/
@Override
public CoursePic findCoursePic(String courseId) {
Optional<CoursePic> optional = this.coursePicRepository.findById(courseId);
if (!optional.isPresent()){
ExceptionCast.cast(CourseCode.COURSE_PICISNULL);
}
return optional.get();
}
@Override
@GetMapping("/coursepic/list/{courseId}")
public CoursePic findCoursePic(String courseId) {
return this.courseService.findCoursePic(courseId);
}
// 查询课程图片
export const findCoursePicList = courseId => {
return http.requestQuickGet(apiUrl + '/course/coursepic/list/' + courseId)
}
在课程图片页面的mounted钩子方法 中查询课程图片信息,并将图片地址赋值给数据对象
mounted(){
//课程id
this.courseid = this.$route.params.courseid;
//查询课程
courseApi.findCoursePicList(this.courseid).then(res=>{
if(res && res.pic){
let imgUrl = this.imgUrl+res.pic;
//将图片地址设置到
this.fileList.push({name:'pic',url:imgUrl,fileId:res.pic})
}
})
}
sysConfig
:
let sysConfig = {
xcApiUrlPre: '/api',
// xcApiUrlPre: '',
xcApiUrl: 'http://api.xuecheng.com',
imgUrl: 'http://img.xuecheng.com/',
videoUrl: 'http://video.xuecheng.com',
openAuthenticate: false,
openAuthorize: false
}
module.exports = sysConfig
修改host:
启动虚拟机上的fdfs和nginx
刷新页面:
课程图片上传成功后,可以重新上传,方法是先删除现有图片再上传新图片。
注意:此删除只删除课程数据库的课程图片信息,不去删除文件数据库的文件信息及文件系统服务器上的文件,由于课程图片来源于该用户的文件库,所以此图片可能存在多个地方共用的情况,所以要删除文件系统中的文件需要到图片库由用户确认后再删除。
在课程管理服务添加删除课程图片api:
/**
* 课程图片查询
* @param courseId 课程id
* @return
*/
@ApiOperation("课程图片查询")
@DeleteMapping("/coursepic/delete/{courseId}")
ResponseResult deleteCoursePic(@PathVariable("courseId") String courseId);
CoursePicRepository父类提供的delete方法没有返回值,无法知道是否删除成功,这里我们在
CoursePicRepository下自定义方法:
package com.xuecheng.managecourse.dao;
import com.xuecheng.framework.domain.course.CoursePic;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @Author: 98050
* @Time: 2019-04-18 19:51
* @Feature:
*/
public interface CoursePicRepository extends JpaRepository<CoursePic,String> {
/**
* 删除成功返回1否则返回0
* @param courseId
* @return
*/
long deleteByCourseid(String courseId);
}
接口:
/**
* 删除课程图片
* @param courseId 课程id
* @return
*/
ResponseResult deleteCoursePic(String courseId);
删除:
执行delete语句,使用时在Repository或者更上层需要@Transactional注解。
/**
* 删除课程图片
* @param courseId 课程id
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseResult deleteCoursePic(String courseId) {
long result = this.coursePicRepository.deleteByCourseid(courseId);
if (result > 0){
return new ResponseResult(CommonCode.SUCCESS);
}else {
return new ResponseResult(CommonCode.FAIL);
}
}
@Override
@DeleteMapping("/coursepic/delete/{courseId}")
public ResponseResult deleteCoursePic(@PathVariable("courseId") String courseId) {
return this.courseService.deleteCoursePic(courseId);
}
// 删除课程图片
export const deleteCoursePic = courseId => {
return http.requestDelete(apiUrl + '/course/coursepic/delete/' + courseId)
}
实现钩子函数:
:before-remove="handleRemove"
before-remove说明:删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被reject,则停止删除。
定义handleRemove方法进行测试:
handleRemove 返回true则删除页面的图片,返回false则停止删除页面的图
//删除图片
handleRemove(file, fileList) {
//调用服务端去删除课程图片信息,如果返回false,前端停止删除
//异步调用e
courseApi.deleteCoursePic('1').then(res=>{
if(res.success){
this.$message.success('删除成功')
}else{
this.$message.error("删除失败");
return false;
}
})
}
在上边代码中将提交的课程id故意写错,按照我们预期应该是删除失败,而测试结果却是图片在页面上删除成功。
问题原因:通过查询deleteCoursePic方法的底层代码,deleteCoursePic最终返回一个promise对象。
Promise是ES6提供的用于异步处理的对象,因为axios提交是异步提交,这里使用promise作为返回值。Promise的使用方法如下:
Promise对象在处理过程中有三种状态:
pending:进行中
resolved:操作成功
rejected: 操作失败
Promise的构建方法如下:
const promise = new Promise((resolve,reject)=>{
if(){
//成功了
resolve(value);
}else{
//失败了
reject(error);
}
})
上边的构造方法function(resolve,reject)执行流程如下:
1)方法执行一些业务逻辑。
2)如果操作成功将Promise的状态由pending变为resolved,并将操作结果传出去
3)如果操作失败会将promise的状态由pending变为rejected,并将失败结果传出去。
上边说的操作成功将操作结果传给谁了呢?操作失败将失败结果传给谁了呢?
通过promise的then、catch来指定
promise.then(function (result) {
console.log('操作成功:' + result)
})
promise.catch(function (result) {
console.log('操作失败:' + result)
})
示例:
1、定义一个方法,返回promise对象
testpromise(i){
return new Promise((resolve,reject)=>{
if(i % 2 === 0){
resolve('成功了')
}else{
reject('拒绝了')
}
})
}
2、调用此方法
// 测试调用promise方法,then中写的成功后的回调方法,
this.testpromise(3).then(res=>{
alert(res)
}).catch(res=>{//catch就是执行失败的回调方法
alert("失败了。。。。。")
alert(res)
})
3、最终修改handleRemove方法如下
handleRemove(file, fileList) {
//调用服务端去删除课程图片信息,如果返回false,前端停止删除
//异步调用e
return new Promise((resolve,reject)=>{
courseApi.deleteCoursePic(this.courseid).then(res=>{
if(res.success){
this.$message.success('删除成功')
resolve()
}else{
this.$message.error("删除失败");
reject()
}
})
})
}