写在前面 非常有必要说明一下,这个前端学的真的有点小乱小迷糊,因为它的代码太跳跃了,东一个西一个,虽然我知道都是封装好的但是还是得想好一会,也许是我没有系统的学习Vue吧,前端的知识基本上都是在写项目的过程中积累的,但没啥基础,能看懂但是不会写。还有前端的不规范性真的很不习惯,写错单词也不会报错,找错误就得好一会,也许多加字母了,也许漏写了,都不会给你提示报错,真的还挺痛苦的。 这两天有点像是大杂烩,实现了前端的讲师管理代码还有阿里云的云存储以及上传图片的后端代码,知识点还挺多。前端的我就直接挂源码了,看得懂就行。后端的知识得好好说一说。
前端讲师页面 讲师路由 在老师给的前端页面资料中,在src > router > index.js里面添加讲师路由,实现在操作中的页面跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { path : '/teacher' , component : Layout , redirect : '/teacher/table' , name : '讲师管理' , meta : { title : '讲师管理' , icon : 'example' }, children : [ { path : 'table' , name : '讲师列表' , component : () => import ('@/views/edu/teacher/list' ), meta : { title : '讲师列表' , icon : 'table' } }, { path : 'save' , name : '添加讲师' , component : () => import ('@/views/edu/teacher/save' ), meta : { title : '添加讲师' , icon : 'tree' } }, { path : 'edit/:id' , name : 'EduTeacherEdit' , component : () => import ('@/views/edu/teacher/save' ), meta : { title : '编辑讲师' , noCache : true }, hidden : true } ] },
axios的封装 老师将axios的代码全都封装了,真的很不习惯,很难看懂它是怎么样封装的 在src > utils > request.js下实现axios封装,节省了很多代码量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import axios from 'axios' import { Message , MessageBox } from 'element-ui' import store from '../store' import { getToken } from '@/utils/auth' const service = axios.create ({ baseURL : process.env .BASE_API , timeout : 5000 }) service.interceptors .request .use ( config => { if (store.getters .token ) { config.headers ['X-Token' ] = getToken () } return config }, error => { console .log (error) Promise .reject (error) } ) service.interceptors .response .use ( response => { const res = response.data if (res.code !== 20000 ) { Message ({ message : res.message , type : 'error' , duration : 5 * 1000 }) if (res.code === 50008 || res.code === 50012 || res.code === 50014 ) { MessageBox .confirm ( '你已被登出,可以取消继续留在该页面,或者重新登录' , '确定登出' , { confirmButtonText : '重新登录' , cancelButtonText : '取消' , type : 'warning' } ).then (() => { store.dispatch ('FedLogOut' ).then (() => { location.reload () }) }) } return Promise .reject ('error' ) } else { return response.data } }, error => { console .log ('err' + error) Message ({ message : error.message , type : 'error' , duration : 5 * 1000 }) return Promise .reject (error) } ) export default service
讲师的前端请求 老师单独写在了 src > api > edu > teacher.js下,前端请求接口一目了然
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import request from '@/utils/request' export default { getTeacherListPage (current,limit,teacherQuery ){ return request ({ url : `/eduservice/teacher/pageTeacherCondition/${current} /${limit} ` , method : 'post' , data : teacherQuery }) }, deleteTeacherId (id ){ return request ({ url : `/eduservice/teacher/${id} ` , method : 'delete' , }) }, addTeacher (teacher ){ return request ({ url : `/eduservice/teacher/addTeacher` , method : 'post' , data : teacher }) }, getTeacherInfo (id ) { return request ({ url : `/eduservice/teacher/getTeacher/${id} ` , method : 'get' }) }, updateTeacher (teacher ){ return request ({ url : `/eduservice/teacher/updateTeacher` , method : 'put' , data : teacher }) } }
前端页面的展示部分 没事就多看看这两个vue代码,基本上这两个看会了vue前端也就差不多了,记录一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 <template > <div class ="app-container" > <el-form :inline ="true" class ="demo-form-inline" > <el-form-item > <el-input v-model ="teacherQuery.name" placeholder ="讲师名" /> </el-form-item > <el-form-item > <el-select v-model ="teacherQuery.level" clearable placeholder ="讲师头衔" > <el-option :value ="1" label ="高级讲师" /> <el-option :value ="2" label ="首席讲师" /> </el-select > </el-form-item > <el-form-item label ="添加时间" > <el-date-picker v-model ="teacherQuery.begin" type ="datetime" placeholder ="选择开始时间" value-format ="yyyy-MM-dd HH:mm:ss" default-time ="00:00:00" /> </el-form-item > <el-form-item > <el-date-picker v-model ="teacherQuery.end" type ="datetime" placeholder ="选择截止时间" value-format ="yyyy-MM-dd HH:mm:ss" default-time ="00:00:00" /> </el-form-item > <el-button type ="primary" icon ="el-icon-search" @click ="getList()" > 查询</el-button > <el-button type ="default" @click ="resetData()" > 清空</el-button > </el-form > <el-table v-loading ="listLoading" :data ="list" element-loading-text ="数据加载中" border fit highlight-current-row > <el-table-column label ="序号" width ="70" align ="center" > <template slot-scope ="scope" > {{ (page - 1) * limit + scope.$index + 1 }} </template > </el-table-column > <el-table-column prop ="name" label ="名称" width ="80" /> <el-table-column label ="头衔" width ="80" > <template slot-scope ="scope" > {{ scope.row.level===1?'高级讲师':'首席讲师' }} </template > </el-table-column > <el-table-column prop ="intro" label ="资历" /> <el-table-column prop ="gmtCreate" label ="添加时间" width ="160" /> <el-table-column prop ="sort" label ="排序" width ="60" /> <el-table-column label ="操作" width ="200" align ="center" > <template slot-scope ="scope" > <router-link :to ="'/teacher/edit/'+scope.row.id" > <el-button type ="primary" size ="mini" icon ="el-icon-edit" > 修改</el-button > </router-link > <el-button type ="danger" size ="mini" icon ="el-icon-delete" @click ="removeDataById(scope.row.id)" > 删除</el-button > </template > </el-table-column > </el-table > <el-pagination :current-page ="page" :page-size ="limit" :total ="total" style ="padding: 30px 0; text-align: center;" layout ="total, prev, pager, next, jumper" @current-change ="getList" /> </div > </template > <script > import teacher from '@/api/edu/teacher.js' export default { data ( ) { return { list : null , page : 1 , limit : 10 , total : 0 , teacherQuery : {} } }, created ( ) { this .getList () }, methods : { getList (page = 1 ) { this .page = page teacher.getTeacherListPage (this .page ,this .limit ,this .teacherQuery ) .then (response => { this .list = response.data .records this .total = response.data .total console .log (this .list ) console .log (this .total ) }) .catch (error => { console .log (error) }) }, resetData ( ) { this .teacherQuery = {} this .getList () }, removeDataById (id ) { this .$confirm('此操作将永久删除讲师记录, 是否继续?' , '提示' , { confirmButtonText : '确定' , cancelButtonText : '取消' , type : 'warning' }).then (() => { teacher.deleteTeacherId (id) .then (response => { this .$message({ type : 'success' , message : '删除成功!' }); this .getList () }) }) } } } </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 <template > <div class ="app-container" > <el-form label-width ="120px" > <el-form-item label ="讲师名称" > <el-input v-model ="teacher.name" /> </el-form-item > <el-form-item label ="讲师排序" > <el-input-number v-model ="teacher.sort" controls-position ="right" min ="0" /> </el-form-item > <el-form-item label ="讲师头衔" > <el-select v-model ="teacher.level" clearable placeholder ="请选择" > <el-option :value ="1" label ="高级讲师" /> <el-option :value ="2" label ="首席讲师" /> </el-select > </el-form-item > <el-form-item label ="讲师资历" > <el-input v-model ="teacher.career" /> </el-form-item > <el-form-item label ="讲师简介" > <el-input v-model ="teacher.intro" :rows ="10" type ="textarea" /> </el-form-item > <el-form-item label ="讲师头像" > <pan-thumb :image ="teacher.avatar" /> <el-button type ="primary" icon ="el-icon-upload" @click ="imagecropperShow=true" > 更换头像 </el-button > <image-cropper v-show ="imagecropperShow" :width ="300" :height ="300" :key ="imagecropperKey" :url ="BASE_API+'/eduoss/fileoss'" field ="file" @close ="close" @crop-upload-success ="cropSuccess" /> </el-form-item > <el-form-item > <el-button :disabled ="saveBtnDisabled" type ="primary" @click ="saveOrUpdate" > 保存</el-button > </el-form-item > </el-form > </div > </template > <script > import teacherApi from '@/api/edu/teacher.js' import ImageCropper from '@/components/ImageCropper' import PanThumb from '@/components/PanThumb' export default { components : { ImageCropper , PanThumb }, data ( ) { return { teacher : { name : '' , sort : 0 , level : 1 , career : '' , intro : '' , avatar : '' }, imagecropperShow :false , imagecropperKey :0 , BASE_API :process.env .BASE_API , saveBtnDisabled :false } }, created ( ) { this .init () }, watch : { $route(to,from ) { this .init () } }, methods : { close ( ) { this .imagecropperShow =false this .imagecropperKey = this .imagecropperKey +1 }, cropSuccess (data ) { this .imagecropperShow =false this .teacher .avatar =data.url this .imagecropperKey = this .imagecropperKey +1 }, init ( ) { console .log ('created' ) if (this .$route .params && this .$route .params .id ) { const id = this .$route .params .id this .getInfo (id) }else { this .teacher = {} } }, getInfo (id ){ teacherApi.getTeacherInfo (id) .then (response => { this .teacher = response.data .teacher }) }, updateTeacher (teacher ) { teacherApi.updateTeacher (this .teacher ) .then (response => { this .$message({ type : 'success' , message : '修改成功!' }); this .$router .push ({path :'/teacher/table' }) }) }, saveOrUpdate ( ) { if (!this .teacher .id ){ this .saveTeacher () }else { this .updateTeacher () } }, saveTeacher ( ) { teacherApi.addTeacher (this .teacher ) .then (response => { this .$message({ type : 'success' , message : '添加成功!' }); this .$router .push ({path :'/teacher/table' }) }) } } } </script >
搭建阿里云oss操作项目环境 基础配置 基本上就和第一天的一致,就改成8002端口然后不需要使用数据库所以就controller和service以及utils包就足够。 重点讲一下这个报错问题: 当这个oss模块不会使用到数据库操作时而且配置文件也没有配置数据库,那么就会报错Description: Failed to configure a DataSource: url' attribute is not specif Reason: Failed to determine a suitable driver class 原因就是启动的时候,找数据库配置,但是现在模块因为不需要数据库,只是做上传到oss功能。没有配置数据库,spring找不到数据库配置就会报错
解决方式: 1、添加上数据库配置 2、在启动类添加属性,默认不去加载数据库配置
1 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
将图片上传到阿里oss 首先写了一个工具类,将阿里密钥和一些服务器ip用静态方式得到 很有意思的是看起来是私有的变量实际上用静态公有来得到,原因就是要想用springboot配置文件的参数,就必须用@value来得到,但是私有成员变量用静态就取不到value里面的值,所以只能多加公有静态变量直接后面可以直接.出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.atguigu.oss.utils;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Component public class ConstAndPropertiesUtils implements InitializingBean { @Value("${aliyun.oss.file.endpoint}") private String endpoint; @Value("${aliyun.oss.file.keyid}") private String keyId; @Value("${aliyun.oss.file.keysecret}") private String keySecret; @Value("${aliyun.oss.file.bucketname}") private String bucketName; public static String END_POINT; public static String KEY_ID; public static String KEY_SECRET; public static String BUCKET_NAME; @Override public void afterPropertiesSet () throws Exception { END_POINT = endpoint; KEY_ID = keyId; KEY_SECRET = keySecret; BUCKET_NAME = bucketName; } }
核心代码就在OssServiceImpl,我就直接贴上来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.atguigu.oss.service.impl;import com.aliyun.oss.OSS;import com.aliyun.oss.OSSClientBuilder;import com.atguigu.oss.service.OssService;import com.atguigu.oss.utils.ConstAndPropertiesUtils;import org.joda.time.DateTime;import org.springframework.stereotype.Service;import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;import java.util.UUID;@Service public class OssServiceImpl implements OssService { @Override public String uploadFileAvatar (MultipartFile file) { String endpoint = ConstAndPropertiesUtils.END_POINT; String accessKeyId = ConstAndPropertiesUtils.KEY_ID; String accessKeySecret = ConstAndPropertiesUtils.KEY_SECRET; String bucketName = ConstAndPropertiesUtils.BUCKET_NAME; OSS ossClient = new OSSClientBuilder ().build(endpoint, accessKeyId, accessKeySecret); try { InputStream inputStream = file.getInputStream(); String fileName = file.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replace("-" ,"" ); fileName = uuid+fileName; String nowTime = new DateTime ().toString("yyyy/MM/dd" ); fileName = nowTime+"/" +fileName; ossClient.putObject(bucketName, fileName, inputStream); ossClient.shutdown(); String url = "https://" + bucketName + "." + endpoint + "/" + fileName; return url; } catch (Exception oe) { oe.printStackTrace(); return null ; } } }
课程分类模块 创建edu_subject数据库 终于到了新的功能区,知识点还是很多的,干货满满
1 2 3 4 5 6 7 8 9 10 CREATE TABLE `edu_subject` ( `id` char (19 ) NOT NULL COMMENT '课程类别ID' , `title` varchar (10 ) NOT NULL COMMENT '类别名称' , `parent_id` char (19 ) NOT NULL DEFAULT '0' COMMENT '父ID' , `sort` int (10 ) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段' , `gmt_create` datetime NOT NULL COMMENT '创建时间' , `gmt_modified` datetime NOT NULL COMMENT '更新时间' , PRIMARY KEY (`id`), KEY `idx_parent_id` (`parent_id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8mb4 ROW_FORMAT= COMPACT COMMENT= '课程科目' ;
基础框架搭建 和第一天的创建方法一致,代码生成器一键生成,还是在service_edu模块下,非常香!!所以不多说了嘻嘻
EasyExcel的使用(技术点) 之前我就有了解过,现在正式学,收获很多很多
创建表头类 1 2 3 4 5 6 7 8 9 10 11 import com.alibaba.excel.annotation.ExcelProperty;import lombok.Data;@Data public class DemoData { @ExcelProperty(value = "学生编号",index = 0) private Integer sno; @ExcelProperty(value = "学生姓名",index = 1) private String name; }
读操作必备监听器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.event.AnalysisEventListener;import java.util.Map;public class ExcelListener extends AnalysisEventListener <DemoData> { @Override public void invoke (DemoData demoData, AnalysisContext analysisContext) { System.out.println("***" + demoData); } @Override public void invokeHeadMap (Map<Integer,String > headMap,AnalysisContext context) { System.out.println("表头:" +headMap); } @Override public void doAfterAllAnalysed (AnalysisContext analysisContext) { } }
EasyExcel方法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.atguigu.demo.excel;import com.alibaba.excel.EasyExcel;import java.util.ArrayList;import java.util.List;public class TestEasyExcel { public static void main (String[] args) { String filename = "D:\\student.xlsx" ; EasyExcel.write(filename, DemoData.class).sheet("学生列表" ).doWrite(getData()); String filename1 = "D:\\student.xlsx" ; EasyExcel.read(filename1, DemoData.class,new ExcelListener ()).sheet().doRead(); } private static List<DemoData> getData () { List<DemoData> list = new ArrayList <>(); for (int i = 0 ; i < 10 ; i++) { DemoData data = new DemoData (); data.setSno(i); data.setName("herry" + i); list.add(data); } return list; } }
EasyExcel实战 需求:实现使用excel表格的形式添加一级课程和二级课程到数据库中 基本思路: 实际上是一个读的操作,那么就需要表头类和监听器,主要的功能在监听器中实现,在这过程中有一个细节就是监听器类不能够交给spring管理,只能够手动的调用service类,然后用有参构造方法赋值来得到。详细见代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Autowired private EduSubjectService subjectService; @PostMapping("addSubject") public R addSubject (MultipartFile file) { subjectService.saveSubject(file,subjectService); return R.ok(); }
下面是服务层接口
1 2 3 4 public interface EduSubjectService extends IService <EduSubject> { void saveSubject (MultipartFile file,EduSubjectService subjectService) ; }
实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class EduSubjectServiceImpl extends ServiceImpl <EduSubjectMapper, EduSubject> implements EduSubjectService { @Override public void saveSubject (MultipartFile file, EduSubjectService subjectService) { try { InputStream inputStream = file.getInputStream(); EasyExcel.read(inputStream, SubjectDemo.class,new SubjectExcelListener (subjectService)).sheet().doRead(); }catch (Exception e){ e.printStackTrace(); } } }
监听器中实现核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class SubjectExcelListener extends AnalysisEventListener <SubjectDemo> { public EduSubjectService subjectService; public SubjectExcelListener () {} public SubjectExcelListener (EduSubjectService subjectService) { this .subjectService = subjectService; } @Override public void invoke (SubjectDemo subjectDemo, AnalysisContext analysisContext) { if (subjectDemo == null ){ throw new GuliException (20001 ,"文件数据为空" ); } EduSubject existOneSubject = this .existOneSubject(subjectService, subjectDemo.getOneSubjectName()); if (existOneSubject == null ){ existOneSubject = new EduSubject (); existOneSubject.setParentId("0" ); existOneSubject.setTitle(subjectDemo.getOneSubjectName()); subjectService.save(existOneSubject); } String pid = existOneSubject.getId(); EduSubject existTowSubject = this .existTowSubject(subjectService, subjectDemo.getTwoSubjectName(), pid); if (existTowSubject == null ){ existTowSubject = new EduSubject (); existTowSubject.setParentId(pid); existTowSubject.setTitle(subjectDemo.getTwoSubjectName()); subjectService.save(existTowSubject); } } private EduSubject existOneSubject (EduSubjectService subjectService,String name) { QueryWrapper<EduSubject> wrapper =new QueryWrapper <>(); wrapper.eq("title" ,name); wrapper.eq("parent_id" ,"0" ); EduSubject oneSubject = subjectService.getOne(wrapper); return oneSubject; } private EduSubject existTowSubject (EduSubjectService subjectService,String name,String pid) { QueryWrapper<EduSubject> wrapper =new QueryWrapper <>(); wrapper.eq("title" ,name); wrapper.eq("parent_id" ,pid); EduSubject twoSubject = subjectService.getOne(wrapper); return twoSubject; } @Override public void doAfterAllAnalysed (AnalysisContext analysisContext) { } }
最后 要记录一下吧,今天是我的生日啦,写完这篇我就要去许愿啦,真的希望能够实现叭 嘉楠,祝你生日快乐 一切的沉淀终将会有收获