1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 谷粒学苑-项目搭建 讲师前后端 课程分类前后端 OSS EasyExcel

谷粒学苑-项目搭建 讲师前后端 课程分类前后端 OSS EasyExcel

时间:2021-06-17 05:05:40

相关推荐

谷粒学苑-项目搭建 讲师前后端 课程分类前后端 OSS EasyExcel

谷粒学苑

url: jdbc:mysql://localhost:3306/guli?useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC

vod依赖解决详细可看这/Airuiliya520/article/details/109017091

阿里云jar包下载

网址

mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.11 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.11.jar

cmd运行这个命令,然后重启idea。

忽略

aaaaaaaachenggong!

数据库表新加了几个字段,时间的关于

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gimFQCX-1661002567764)(谷粒学苑.assets/image-0712110231248.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJt7mEdo-1661002567765)(谷粒学苑.assets/image-0712110246601.png)]

后端项目搭建

注:为知笔记,.ziw笔记修改后缀为.zip,解压打开,为html格式,方便观看;但是粘贴不便

一.数据库设计

表模型

1、库名与应用名称尽量一致2、表名、字段名必须使用小写字母或数字,禁止出现数字开头,3、表名不使用复数名词4、表的命名最好是加上“业务名称_表的作用”。如,edu_teacher5、表必备三字段:id, gmt_create, gmt_modified说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。(如果使用分库分表集群部署,则id类型为verchar,非自增,业务中使用分布式id生成器)gmt_create, gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。6、单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。 7、表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。 说明:任何字段如果为非负数,必须是 unsigned。 注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的 命名方式是为了明确其取值含义与取值范围。 正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。 8、小数类型为 decimal,禁止使用 float 和 double。 说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。9、如果存储的字符串长度几乎相等,使用 char 定长字符串类型。 10、varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。11、唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。说明:uk_ 即 unique key;idx_ 即 index 的简称12、不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度

二.项目结构

父工程模块

版本使用:2.2.1.RELEASE

版本统一管理

common模块

common-util:工具类模块,所有模块都可以依赖于它service-base:service服务的base包,包含service服务的公共配置类,所有service模块依赖于它spring-security:认证与授权模块,需要认证授权的service服务依赖于它

service模块

service-acl:用户权限管理api接口服务(用户管理、角色管理和权限管理等)service-cms:cms api接口服务service-edu:教学相关api接口服务service-msm:短信api接口服务service-order:订单相关api接口服务service-oss:阿里云oss api接口服务service-statistics:统计报表api接口服务service-ucenter:会员api接口服务service-vod:视频点播api接口服务

service_gateWay模块

feign_client 调用模块

model模块

所有实体类,返回前端的vo类,参数类(也在vo里)都在此

三.前置配置

1.配置文件

可能有没对齐

# 服务端口server:port: 8001# 服务名spring:application:name: service-edu# mysql数据库连接datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: 1234hikari:connection-test-query: SELECT 1connection-timeout: 60000idle-timeout: 500000max-lifetime: 540000maximum-pool-size: 12minimum-idle: 10pool-name: GuliHikariPool#时间格式解析jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8

时间格式解析

2.代码生成器

public class CodeGenerator {@Testpublic void run() {// 1、创建代码生成器AutoGenerator mpg = new AutoGenerator();// 2、全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");// gc.setOutputDir(projectPath + "/src/main/java"); //尽量写绝对路径gc.setOutputDir("E:soft\\mine\\code\\guliClass\\guli_parent\\service\\service_edu" + "/src/main/java");gc.setAuthor("xiaoxin"); //作者gc.setOpen(false); //生成后是否打开资源管理器gc.setFileOverride(false); //重新生成时文件是否覆盖gc.setServiceName("%sService");//去掉Service接口的首字母Igc.setIdType(IdType.ID_WORKER); //主键策略gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型gc.setSwagger2(true);//开启Swagger2模式mpg.setGlobalConfig(gc);// 3、数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3308/guli?serverTimezone=GMT%2B8");dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("root");dsc.setDbType(DbType.MYSQL);mpg.setDataSource(dsc);// 4、包配置PackageConfig pc = new PackageConfig();pc.setModuleName("eduservice"); //模块名pc.setParent("com.xiaoxin");pc.setController("controller");pc.setEntity("entity");pc.setService("service");pc.setMapper("mapper");mpg.setPackageInfo(pc);// 5、策略配置StrategyConfig strategy = new StrategyConfig();strategy.setInclude("edu_teacher");strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作strategy.setRestControllerStyle(true); //restful api风格控制器strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符mpg.setStrategy(strategy);// 6、执行mpg.execute();}}

3.Swagger集成

guli_project\guli_parent\common\service_base\config

通用模板

设置包扫描规则,为了扫描到swagger的配置类

//记得在启动类扫描@ComponentScan(basePackages ={"com.xiaoxin"})

@Configuration@EnableSwagger2//记得加这个public class SwaggerConfig {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select().paths(Predicates.not(PathSelectors.regex("/admin/.*"))).paths(Predicates.not(PathSelectors.regex("/error.*"))).build();}private ApiInfo webApiInfo(){return new ApiInfoBuilder().title("网站-课程中心API文档").description("本文档描述了课程中心微服务接口定义").version("1.0").contact(new Contact("Helen", "", "839623440@")).build();}}

4.统一结果返回对象

Result 其中的return this为了支持链式调用(一种设计模式)

result中一般(模板)包含 状态码 是否成功 返回消息 数据(一般是map)

@Datapublic class Result {@ApiModelProperty(value = "是否成功")private Boolean success;@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private Map<String, Object> data = new HashMap<String, Object>();private Result(){}public static Result ok(){Result r = new Result();r.setSuccess(true);r.setCode(ResultCode.SUCCESS);r.setMessage("成功");return r;}public static Result error(){Result r = new Result();r.setSuccess(false);r.setCode(ResultCode.ERROR);r.setMessage("失败");return r;}public Result success(Boolean success){this.setSuccess(success);return this;}public Result message(String message){this.setMessage(message);return this;}public Result code(Integer code){this.setCode(code);return this;}public Result data(String key, Object value){this.data.put(key, value);return this;}public Result data(Map<String, Object> map){this.setData(map);return this;}}

ResultCodeEnum (配合GuliException)使用

package com.xxx.result;import lombok.Getter;import lombok.experimental.Accessors;/*** 统一返回结果状态信息类*/@Getter@Accessors(chain = true)public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(201, "失败"),PARAM_ERROR( 202, "参数不正确"),SERVICE_ERROR(203, "服务异常"),DATA_ERROR(204, "数据异常"),DATA_UPDATE_ERROR(205, "数据版本异常"),LOGIN_AUTH(206, "未登陆"),PERMISSION(207, "没有权限"),CODE_ERROR(208, "验证码错误"),SEND_CODE_ERROR(209, "验证码发送失败"),LOGIN_INFO_ERROR(210, "账号或密码为空"),LOGIN_MOBLE_ERROR(211, "账号不正确"),LOGIN_PASSWORD_ERROR(212, "密码不正确"),LOGIN_DISABLED_ERROR(213, "该用户已被禁用"),REGISTER_MOBLE_ERROR(214, "手机号已被使用"),LOGIN_AURH(215, "需要登录"),LOGIN_ACL(216, "没有权限"),TOKEN_OVERDUE( 224,"登录时间过期"),URL_ENCODE_ERROR( 217, "URL编码失败"),ILLEGAL_CALLBACK_REQUEST_ERROR( 218, "非法回调请求"),FETCH_ACCESSTOKEN_FAILD( 219, "获取accessToken失败"),FETCH_USERINFO_ERROR( 220, "获取用户信息失败"),LOGIN_ERROR( 221, "登录失败"),PAY_RUN(222, "支付中"),PAY_ERROR(225, "支付失败"),CANCEL_ORDER_FAIL(223, "取消订单失败");private final Integer code;private final String message;ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}}

ResultCode接口 (用于结果类的静态方法成功、失败)

package com.xxx.result;public interface ResultCode {public static Integer SUCCESS = 200;public static Integer ERROR = 201;}

5.MybatisPlus配置类

MybatisPlusConfig

@Configuration@EnableTransactionManagement@MapperScan("com.xxx.edu.mapper")public class MyBatisPlusConfig {/*** SQL 执行性能分析插件* 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长*//*@Bean@Profile({"dev","test"})// 设置 dev test 环境开启public PerformanceInterceptor performanceInterceptor() {PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();performanceInterceptor.setMaxTime(1000);//ms,超过此处设置的ms则sql不执行performanceInterceptor.setFormat(true);return performanceInterceptor;}*/@Beanpublic ISqlInjector sqlInjector() {return new LogicSqlInjector();}/*** 分页插件*/@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}}

自动填充

在实体类属性上添加

@TableField(fill = FieldFill.INSERT)@TableField(fill = FieldFill.INSERT_UPDATE)

@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.setFieldValByName("gmtCreate", new Date(), metaObject);this.setFieldValByName("gmtModified", new Date(), metaObject);this.setFieldValByName("loginTime", new Date(), metaObject);this.setFieldValByName("viewTime", new Date(), metaObject);this.setFieldValByName("buyTime", new Date(), metaObject);}@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("gmtModified", new Date(), metaObject);}}

逻辑删除插件在3.1之后不用配置

1.插件:配置类

2.添加@Tablelogic

6.统一异常处理

全局异常

/*** @program: guli_parent* @description: 自定义异常类* @author:xiaoxin* @create: -05-08 11:42**/@EqualsAndHashCode(callSuper = true)@Data@AllArgsConstructor@NoArgsConstructorpublic class GuliException extends RuntimeException {@ApiModelProperty(value = "异常状态码")private Integer code;/*** 通过状态码和错误消息创建异常对象* @param message* @param code*/public GuliException(String message, Integer code) {super(message);this.code = code;}/*** 接收枚举类型对象* @param resultCodeEnum*/public GuliException(ResultCodeEnum resultCodeEnum) {super(resultCodeEnum.getMessage());this.code = resultCodeEnum.getCode();}@Overridepublic String toString() {return "GuliException{" +"code=" + code +", message=" + this.getMessage() +'}';}}

7.统一日志处理

配置日志级别

日志记录器(Logger)的行为是分等级的。如下表所示:

分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别

# 设置日志级别logging.level.root=WARN

这种方式只能将日志打印在控制台上

Logback

spring boot内部使用Logback作为日志实现的框架。

logback相对于log4j的一些优点:/caisini_vc/article/details/48551287

1、配置logback日志

删除application.properties中的日志配置安装idea彩色日志插件:grep-consoleresources 中创建 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?><configuration scan="true" scanPeriod="10 seconds"><!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --><!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --><!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --><!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --><contextName>logback</contextName><!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --><property name="log.path" value="logs/guli_log/edu" /><!-- 彩色日志 --><!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 --><!-- magenta:洋红 --><!-- boldMagenta:粗红--><!-- cyan:青色 --><!-- white:白色 --><!-- magenta:洋红 --><property name="CONSOLE_LOG_PATTERN"value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/><!--输出到控制台--><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--><!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 --><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUG</level></filter><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!-- 设置字符集 --><charset>UTF-8</charset></encoder></appender><!--输出到文件--><!-- 时间滚动输出 level为 INFO 日志 --><appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/log_info.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 每天日志归档路径以及格式 --><fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy><!-- 此日志文件只记录info级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 时间滚动输出 level为 WARN 日志 --><appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/log_warn.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy><!-- 此日志文件只记录warn级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>warn</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 时间滚动输出 level为 ERROR 日志 --><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/log_error.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy><!-- 此日志文件只记录ERROR级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。--><!--使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:--><!--开发环境:打印控制台--><springProfile name="dev"><!--可以输出项目中的debug日志,包括mybatis的sql日志--><logger name="com.xxx" level="DEBUG" /><!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG可以包含零个或多个appender元素。--><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="WARN_FILE" /><appender-ref ref="ERROR_FILE" /></root></springProfile><!--生产环境:输出到文件--><springProfile name="pro"><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="DEBUG_FILE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="ERROR_FILE" /><appender-ref ref="WARN_FILE" /></root></springProfile></configuration>

2、将错误日志输出到文件

GlobalExceptionHandler.java 中

类上添加注解@Slf4j

异常输出语句

log.error(e.getMessage());

3、将日志堆栈信息输出到文件

定义工具类common_untils下创建util包,创建ExceptionUtil.java工具类

public class ExceptionUtil {public static String getMessage(Exception e) {StringWriter sw = null;PrintWriter pw = null;try {sw = new StringWriter();pw = new PrintWriter(sw);// 将出错的栈信息输出到printWriter中e.printStackTrace(pw);pw.flush();sw.flush();} finally {if (sw != null) {try {sw.close();} catch (IOException e1) {e1.printStackTrace();}}if (pw != null) {pw.close();}}return sw.toString();}}

调用

log.error(ExceptionUtil.getMessage(e));

GuliException中创建toString方法(前面已经写了)

8.MybatisPlus学习

建议官方文档学习或者尚硅谷视频/我的博客

讲师管理模块

一.后端

数据模型

1.根据ID查询

@GetMapping("/findByIdTeacher/{id}")@ApiOperation("根据Id查询讲师")public Result findByIdTeacher(@ApiParam(name = "id",value = "讲师Id",required = true) @PathVariable("id") String id){Teacher teacher = teacherService.getById(id);log.info("获取讲师信息,teacher =》{}",teacher);return Result.ok().data("teacher",teacher);}

2.查询所有

@GetMapping("/getTeacherList")@ApiOperation(value = "所有讲师列表")public Result getTeacherList(){List<Teacher> list = teacherService.list(null);return Result.ok().data("items",list);}@DeleteMapping("{id}")@ApiOperation(value = "逻辑删除讲师")//public Result removeById(@ApiParam(name = "id",value = "讲师Id",readOnly = true) @PathVariable("id")String id){// boolean b = teacherService.removeById(id);// if (b){// return Result.ok();// }else {// return Result.error();// }//}//三元表达式优化return teacherService.removeById(id)? Result.ok():Result.error();

3.逻辑删除

@DeleteMapping("{id}")@ApiOperation(value = "逻辑删除讲师")public Result removeById(@ApiParam(name = "id",value = "讲师Id",readOnly = true) @PathVariable("id")String id){boolean b = teacherService.removeById(id);if (b){return Result.ok();}else {return Result.error();}}

4.分页条件查询

Teachercontroller

@PostMapping("/pageTeacherQuery/{page}/{limit}")@ApiOperation(value = "分页条件查询讲师")public Result pageTeacherQuery(@ApiParam(name = "page",value = "当前页码",required = true) @PathVariable("page")Long page,@ApiParam(name = "limit",value = "每页记录数",required = true) @PathVariable("limit")Long limit,@ApiParam(name = "teacherVo",value = "条件查询实体类",required = false) @RequestBody(required = false) TeacherVo teacherVo){Page<Teacher> pageParam = new Page<>(page, limit);teacherService.pageQuery(pageParam, teacherVo);List<Teacher> records = pageParam.getRecords();long total = pageParam.getTotal();return Result.ok().data("total", total).data("rows", records);}//使用@Requestbody注解修饰参数(把json数据封装到对应的对象),需要请求方式为post,get得不到(不用注解使用get方式也可以)还要加required=false,默认为true;不加上的话不行(因为是条件查询,可以没条件)//这里swagger的界面也变了,从一个框一个框变成一个大框,写json数据

TeacherServiceImpl

也可使用LambdaQueryWrapper,此方法可优化

@Overridepublic void pageQuery(Page<Teacher> pageParam, TeacherVo teacherQuery) {QueryWrapper<Teacher> queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("gmt_create","sort");if (teacherQuery == null){baseMapper.selectPage(pageParam, queryWrapper);return;}String name = teacherQuery.getName();Integer level = teacherQuery.getLevel();String begin = teacherQuery.getBegin();String end = teacherQuery.getEnd();if (!StringUtils.isEmpty(name)) {queryWrapper.like("name", name);}if (!StringUtils.isEmpty(level) ) {queryWrapper.eq("level", level);}if (!StringUtils.isEmpty(begin)) {queryWrapper.ge("gmt_create", begin);}if (!StringUtils.isEmpty(end)) {queryWrapper.le("gmt_create", end);}baseMapper.selectPage(pageParam, queryWrapper);}

@RequestParam和@Pathvariable区别

常见判空方法

1.Stringutils.isnotblank2.!Stringutils.isempty3.Objectt.nonnull4.collertorutils.isempty(判断集合的)

LambdaQueryWrapper忘记加泛型,会导致Teacher::getName不能用

LambdaQueryWrapper<Teacher> queryWrapper = new LambdaQueryWrapper<>();//判空,拼接条件 queryWrapper.like(!StringUtils.isEmpty(teacherQuery.getName()),Teacher::getName,teacherQuery.getName());Teacher::getName 映射到数据库中的字段属性,防止你写错

5.新增讲师接口

@PostMapping("/addTeacher")@ApiOperation(value = "添加讲师")public Result addTeacher(@ApiParam(name = "teacher",value = "讲师实体类") @RequestBody Teacher teacher){boolean save = teacherService.save(teacher);if (save) {log.info("添加讲师信息,teacher =》{}",teacher);return Result.ok();}else {return Result.error();}}

6.修改讲师接口

@PutMapping("/updateTeacher")@ApiOperation(value = "修改讲师")public Result updateTeacher(@ApiParam(name = "teacher",value = "讲师实体类") @RequestBody Teacher teacher){boolean b = teacherService.updateById(teacher);if (b) {log.info("修改讲师信息,teacher =》{}",teacher);return Result.ok();}else {return Result.error();}}}

批量删除

待开发,尚医通有

二.前端

1.后台项目模板

vue-element-admin GitHub地址

vue-admin-template GitHub地址

项目在线预览:地址

elementui(基于vue2x) 地址

建议:你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来

//克隆工程 有关git可以看[这篇文章](/9qbIk)git clone /PanJiaChen/vue-admin-template.git//下载相关依赖npm install//运行npm run dev

如果下载以依赖出现node-sass类错,尝试先执行下面的代码

npm i -g node-sass --sass_binary_site=/mirrors/node-sass/

结构

vue-damin-temeplatebulid:构建相关config:全局配置src:源代码api:所有请求assets:主题 字体等静态资源components:全局公共组件icons:项目所有svg iconsrouter:路由store:全局store管理styles:全局样式utils:全局公用方法views:视图App.vue:入口页面main.js:入口 加载组件 初始化等permission.js:权限管理static:静态资源.babelrc:babel-loader配置.eslintrc.js:eslint配置项.gitignore:git忽略项package.json:依赖管理

最终界面侧边栏展示

2.开发流程

二次开发:根据原有项目,增加模块;

修改请求地址

3.登录改造

login返回token值

info返回信息roles name avatar

前端

user.js中

注意login.js中的url路径和后端要匹配

actions: {// 登录Login({commit }, userInfo) {const username = userInfo.username.trim()return new Promise((resolve, reject) => {login(username, userInfo.password).then(response => {const data = response.datasetToken(data.token)commit('SET_TOKEN', data.token)resolve()}).catch(error => {reject(error)})})},// 获取用户信息GetInfo({commit, state }) {return new Promise((resolve, reject) => {getInfo(state.token).then(response => {const data = response.dataif (data.roles && data.roles.length > 0) {// 验证返回的roles是否是一个非空数组commit('SET_ROLES', data.roles)} else {reject('getInfo: roles must be a non-null array !')}commit('SET_NAME', data.name)commit('SET_AVATAR', data.avatar)resolve(response)}).catch(error => {reject(error)})})},

后端模拟

后面会用Spring security

@RestController("adminLogin")@RequestMapping("/user")public class LoginController {@PostMapping("/login")@ApiOperation("用户登录接口")public Result login(@ApiParam(name = "username",value = "用户名",required = true) String username,@ApiParam(name = "password",value = "密码",required = true) String password){log.info("用户登录");return Result.ok().data("token","admin");}@GetMapping("/info")@ApiOperation("获取用户信息接口")public Result info(@ApiParam(name = "token",value = "token",required = true) String token){log.info("获取用户信息");return Result.ok().data("roles","[admin]").data("name","admin").data("avatar","/f778738c-e4f8-4870-b634-56703b4acafe.gif");}}

跨域解决

1.@CrossOrigin(先用这个)在controller上

2.网关配置

4.讲师列表

创建路由

{path: '/teacher',component: Layout,redirect: '/teacher/list',name: '讲师管理',meta: {title: '讲师管理', icon: 'teacher' },children: [{path: 'list',name: '讲师列表',component: () => import('@/views/edu/teacher/list'),meta: {title: '讲师列表', icon: 'list' }},{path: 'save',name: '添加讲师',component: () => import('@/views/edu/teacher/save'),meta: {title: '添加讲师', icon: 'teacher_edit' }//icon为对性的图标,在icons文件里},{path: 'edit/:id',name: '修改讲师',component: () => import('@/views/edu/teacher/save'),meta: {title: '修改讲师', icon: 'teacher_edit' },hidden:true}]},

创建api

后端用Requestbody获取数据,前端请求(data:teacherVo)要这样写,加个data,表示使用json方式传递数据;如果写param,则前面不用加data

import request from '@/utils/request'const frontUrl = '/edu/teacher'export default {// 1、讲师列表,条件查询带分页pageTeacherQuery(page, limit, teacherVo) {return request({url: `${frontUrl}/pageTeacherQuery/${page}/${limit}`,method: 'post',data: teacherVo})},}

页面、js

**table表格 **

<!--表格--><el-tablev-loading="listLoading":data="list"element-loading-text="数据加载中"fithighlight-current-rowstyle="width: 100%;"><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="120" align="center"/><el-table-column prop="avatar" label="头像" width="120" align="center"><template slot-scope="scope"><el-avatar :src="scope.row.avatar" size="large"/></template></el-table-column><el-table-column label="头衔" width="120" align="center"><template slot-scope="scope">{{scope.row.level===1?'高级讲师':'首席讲师' }}</template></el-table-column><el-table-column prop="career" label="讲师资历" width="200" align="center"/><el-table-column prop="intro" label="讲师简介" width="500" align="center" show-overflow-tooltip/><el-table-column prop="gmtCreate" label="添加时间" width="200" align="center"/></el-table>

data 封装好了,帮你遍历 (:data=list)

prop 即properties(属性)data了的key,会显示对应value

整个表格是scope,scope.row 每行中的内容

<template slot-scope="scope">//整个表格域{{scope.row.level===1?'高级讲师':'首席讲师' }}</template>

分页组件

<el-pagination :current-page="page" :page-size="limit" :total="total" :hide-on-single-page="false"style="padding: 30px 0;text-align: center;" :page-sizes="[5, 10, 50, 100]"layout="total, sizes, prev, pager, next, jumper" //显示内容,前页后叶等等@size-change="handleSizeChange" //会传递参数给这个函数(封装了单击事件,点2传2)@current-change="fetchData" //分页切换 我还没搞懂fetchData函数是干嘛的(懂了,配合下面的条件查询)/>

分页查询条件

找个一行显示的 inline=true

el-form el-form-item 类比 el-table el-table-column

<div style="margin: 3vh auto 0;text-align:center;width: 100%"><!--查询表单--><el-form :inline="true" class="demo-form-inline"><el-form-item label="讲师名:"><el-input v-model="teacherVo.name" placeholder="讲师名"/></el-form-item><el-form-item label="讲师头衔:"><el-select v-model="teacherVo.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="teacherVo.begin" type="datetime" placeholder="选择开始时间" value-format="yyyy-MM-dd HH:mm:ss" default-time="00:00:00"/><el-date-picker v-model="teacherVo.end" type="datetime" placeholder="选择截止时间" value-format="yyyy-MM-dd HH:mm:ss" default-time="00:00:00"/>--><el-date-picker v-model="value1" :default-time="['00:00:00']" align="right" type="datetimerange" start-placeholder="选择开始时间" end-placeholder="选择截止时间" value-format="yyyy-MM-dd HH:mm:ss"/></el-form-item><el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button><el-button type="default" @click="resetData()">清空</el-button></el-form></div>

页面逻辑js

import teacher from '@/api/edu/teacher'export default {name: 'List',data() {return {value1: '',listLoading: true, // 是否显示loading信息page: 1, // 当前页limit: 5, // 每页显示记录数total: 0, // 总记录数teacherVo: {}, // 条件查询参数list: [] // 查询出结果}},created() {// 页面渲染之前执行,一般执行methods中的方法this.getList()},methods: {//fetchData(page) {this.teacherVo.begin = this.value1[0]this.teacherVo.end = this.value1[1]this.getList(page, this.limit)},handleSizeChange(limit) {this.getList(this.page, limit)},resetData() {this.value1 = ''this.teacherVo = {}this.fetchData()},getList(page = 1, limit = 5) {// 为了使能查不同页的数据this.page = pagethis.limit = limitthis.listLoading = trueconsole.log(this)teacher.pageTeacherQuery(this.page, this.limit, this.teacherVo).then(res => {if (res.success === true) {this.list = res.data.rowsthis.total = res.data.total}this.listLoading = false}).catch(error => {this.$message.error('加载失败,请联系管理员') })},}}

5.讲师删除

1.每条记录后添加按钮(修改/删除)

2.按钮绑定删除事件(removeDataById(id))

2.1找个删除弹框,提高用户体验MessageBox

3.给事件方法传递讲师id(scope.row.id)区别于序号哦

4.teacher.js中定义删除接口

创建api

// 根据Id删除讲师removeById(id) {return request({url: `${frontUrl}/${id}`,method: 'delete'})},

页面、js

<el-table-column fixed="right" label="操作" width="200" align="center"><template slot-scope="scope"><router-link :to="'/teacher/edit/'+scope.row.id"><el-link type="primary" icon="el-icon-edit">修改</el-link></router-link><el-link type="danger" icon="el-icon-delete-solid" @click="removeDataById(scope.row.id)">删除</el-link></template></el-table-column>

views/edu/teacher/list.vue

removeDataById(id){//element-ui粘贴来的this.$confirm('此操作将永久删除该讲师信息, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {teacher.removeById(id).then(res =>{if (res.success === true) {this.$message({type: 'success',message: '删除成功!'});this.fetchData(this.page)}}).catch(error=>{this.$message.error('加载失败,请联系管理员');})}).catch(() => {this.$message({type: 'info',message: '已取消删除'});});

.catch不写也可以,request.js已经封装好了,做了输出

6.讲师添加

views/edu/teacher/save.vue

1.点击添加讲师,进入添加表单页面(添加修改共用一个界面)

2.调用添加成功之后,弹出提示信息

3.路由跳转回到列表页面(this.$roter.push({path:‘/teacher/table’})

创建api

// 添加讲师addTeacher(teacher) {return request({url: `${frontUrl}/addTeacher`,method: 'post',data: teacher})},// 根据id查询讲师findByIdTeacher(id) {return request({url: `${frontUrl}/findByIdTeacher/${id}`,method: 'get'})},

页面、js

<div class="app-container" ><el-form ref="teacher" :model="teacher" :rules="rules" label-width="120px" size="" ><el-form-item label="讲师名称" prop="name"><el-input v-model="teacher.name" style="width: 300px;"/></el-form-item><el-form-item label="讲师排序" prop="sort"><el-input-number v-model="teacher.sort" :min="0" controls-position="right" style="width: 300px"/></el-form-item><el-form-item label="讲师头衔" prop="level"><el-select v-model="teacher.level" clearable placeholder="请选择" style="width: 300px"><!--数据类型一定要和取出的json中的一致,否则没法回填因此,这里value使用动态绑定的值,保证其数据类型是number--><el-option :value="1" label="高级讲师"/><el-option :value="2" label="首席讲师"/></el-select></el-form-item><el-form-item label="讲师资历" prop="career"><el-input v-model="teacher.career" style="width: 300px"/></el-form-item><el-form-item label="讲师简介" prop="intro"><el-input v-model="teacher.intro" :rows="5" type="textarea" style="width: 300px"/></el-form-item><el-form-item><el-button @click="resetForm('teacher')">重置</el-button><el-button @click="setCokkie">设置token</el-button><el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate('teacher')">保存</el-button></el-form-item></el-form></div>

页面逻辑js

import teacherApi from '@/api/edu/teacher'import cookie from 'js-cookie'export default {name: 'Save',data() {return {teacher: {name: '',sort: 0,level: '',career: '',intro: '',avatar: ''},fileUrl: process.env.BASE_API + '/oss/fileOss/avatarUpload',saveBtnDisabled: false, // 保存按钮是否禁用,dialogVisible: false,rules: {name: [{required: true, message: '请输入讲师名称', trigger: 'blur' }],sort: [{required: true, message: '请输入讲师排序', trigger: 'blur' },{type: 'number', message: '讲师排序为数字值' }],level: [{required: true, message: '请选择讲师头衔', trigger: 'change' }],career: [{required: true, message: '请输入讲师资历', trigger: 'blur' }],intro: [{required: true, message: '请输入讲师简介', trigger: 'blur' }]}}},watch: {// 监听$route(to, from) {// 路由变化的方式。。路由发生变化后,就执行this.init()}},created() {this.init()},methods: {init() {if (this.$route.params && this.$route.params.id) {this.dialogVisible = truethis.getInfo(this.$route.params.id)} else {this.dialogVisible = falsethis.teacher = {}}},resetForm(formName) {this.$refs[formName].resetFields()},saveOrUpdate(formName) {this.$refs[formName].validate((valid) => {if (valid) {this.saveBtnDisabled = trueif (this.teacher.id) {this.updateTeacher()} else {this.saveData()}} else {this.$message({message: '提交失败,请检查填写信息',type: 'warning'})return false}})},// 保存saveData() {teacherApi.addTeacher(this.teacher).then(res => {if (res.success === true) {this.$message({type: 'success',message: '添加成功!'})setTimeout(() => {this.$router.push('/teacher/list')}, 1000)}}).catch(error => {this.$message.error('添加失败!'); this.saveBtnDisabled = false })},getInfo(id) {teacherApi.findByIdTeacher(id).then(res => {if (res.success === true) {this.teacher = res.data.teacher}}).catch(error => {this.$message.error('加载失败,请联系管理员') })},setCokkie() {cookie.set('name', JSON.stringify('token'), {domain: 'localhost' })}}}

7.讲师修改

views/edu/teacher/save.vue

1.添加修改按钮

2.数据回显(根据id查询数据库显示数据,但是不查好像也可以,页面的list有)

3.通过路由跳转进入数据回显页面

4.(根据有没有id值判断调用)this.$router.params 得到路由的参数值,页面加载完成后钩子函数里执行init函数,完成数据回显

5.修改,传入data(为Requestbody),请求接口修改

创建路由

隐藏路由

//添加路由{path: 'edit/:id', //id占位符,讲师idname: '修改讲师',component: () => import('@/views/edu/teacher/save'),meta: {title: '修改讲师', icon: 'teacher_edit' },hidden:true //不显示}

//用router-link-to跳转;前面已经写过<router-link :to="'/teacher/edit/'+scope.row.id"><el-link type="primary" icon="el-icon-edit">修改</el-link></router-link>

创建api

// 修改讲师信息updateTeacher(teacher) {return request({url: `${frontUrl}/updateTeacher`,method: 'put',data: teacher})},// 根据id查询讲师findByIdTeacher(id) {return request({url: `${frontUrl}/findByIdTeacher/${id}`,method: 'get'})},

页面、js

init(){if(this.$route.params && this.$route.params.id){this.dialogVisible = truethis.getInfo(this.$route.params.id)}else {this.dialogVisible = falsethis.teacher = {}}},getInfo(id){teacherApi.findByIdTeacher(id).then(res =>{if(res.success === true){this.teacher = res.data.teacher}}).catch(error=>{this.$message.error('加载失败,请联系管理员');})},//修改updateTeacher(){teacherApi.updateTeacher(this.teacher).then(res =>{if (res.success === true) {this.$message({type: 'success',message: '修改成功!'});setTimeout(() => {this.$router.push("/teacher/list")}, 1000)}}).catch(error=>{this.$message.error('修改失败!');this.saveBtnDisabled = false})},

路由切换问题

要解决的问题是,点击修改,页面显示回显数据,但是在点击添加,数据没有清楚,还是之前的

解决方案:添加的时候清空表单数据 this.teacher = {}

注意:添加修改在同一页面。函数若在created里,这里面只会执行一次(多次路由跳转到同一页面)

再次解决:使用监听watch

watch: {//监听$route(to, from) {//路由变化的方式。。路由发生变化后,就执行this.init()}},

二.OSS

为了上传头像做准备

OSS官网

创建

1.bucket创建;类似建个文件夹或者包

2.标准存储(读取多)、低频存储(读取少)、归档存储(只存)

3.读写权限(私有:只有自己能访问到;公共读:别人可以读;公共读写:尽量别用;后面的都不开通

4.创建之后测试

使用流程

1.获取id和密钥

2.学习 路径

3.依赖(7.14 最新)

<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version></dependency>

maven示例工程demo

4.使用

快速入门

域节点(新建了访问路径才有的)oss-cn-

上传头像后端

依赖可放在service中,但是只有oss模块用到,所以单独放到这里就行(版本还是guli_parent统一控制)

<!-- 阿里云oss依赖 --><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId></dependency>

配置文件

server:port: 8002spring:application:name: service-ossprofiles:active: devcloud:nacos:discovery:server-addr: 127.0.0.1:8848aliyun:oss:file:endpoint: oss-cn-keyId: keySecret: bucketName: xiaoxin-gulistudy

启动类配置注解

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)因为启动时会找数据库配置,但是这个模块不需要操作数据库,解决方式1.加上这个exclude(默认不去加载数据库配置)2.配置数据库

创建常量类

读取配置的oss id、密钥等

/*** @program: guli_parent* @description: 读取配置文件的属性工具类* @author: xiaoxin* @create: -05-12 15:11**/@Data@Componentpublic class ConstantPropertiesUtils 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;@Overridepublic void afterPropertiesSet() throws Exception {END_POINT = endpoint;KEY_ID = keyId;KEY_SECRET = keySecret;BUCKET_NAME = bucketName;}}

头像上传接口(后端)

@RestController@RequestMapping("/oss/fileOss")public class OssController {@Autowiredprivate OssService ossService;@PostMapping("/avatarUpload")@ApiOperation("/头像上传接口")public Result avatarUpload(MultipartFile file){//获取文件上传 MultipartFile//方法返回urlString url = ossService.pictureUpload(file,"avatar");return Result.ok().data("url",url);}

service实现类

用官方的上传文件流方式比较适合

@Overridepublic String pictureUpload(MultipartFile file,String name) {//获取配置信息String endPoint = ConstantPropertiesUtils.END_POINT;String keyId = ConstantPropertiesUtils.KEY_ID;String keySecret = ConstantPropertiesUtils.KEY_SECRET;String bucketName = ConstantPropertiesUtils.BUCKET_NAME;//创建OSS示例OSS ossClient = new OSSClientBuilder().build(endPoint,keyId,keySecret);try {//上传文件流InputStream inputStream = file.getInputStream();//获取file名称String fileName = file.getOriginalFilename();//生成随机性唯一值,使用uuid,添加到名称里面//erw55-4sfsd-df555如//String uuid = UUID.randomUUID().toString().replaceAll("-","");//if (fileName != null && fileName.length() < 20) {//不懂这句什么屁用// fileName = uuid + fileName;}//按照当前日期,创建文件夹,上传到创建的文件夹里String timeUrl = new DateTime().toString("yyyy/MM/dd");//joda-tmie工具类,如果没有要用SimpleFormate//这样是按时间分类,也可以自定义,如aa/bb/1.jpg//二选一即可,可以不拼接,也可用uuid那个filename;时间的这个更好用fileName = timeUrl+"/"+fileName;/*调用oss方法上传文件1、第一个参数,bucketName2、第二个参数:上传到oss文件的路径和文件的名称3.第三个参数:文件上传输入流*/ossClient.putObject(bucketName,fileName,inputStream);log.info("fileName => {}",fileName);//获取url路径String url;url = "https://" + bucketName + "." + endPoint + "/" + fileName;log.info("url =>{}",url);return url;} catch (Exception e) {e.printStackTrace();}finally {//关闭ossossClient.shutdown();}return "";}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2DFVya8E-1661002567777)(谷粒学苑.assets/EA35C4AA69330F6B391B5DE7E8C702E1.jpg)]

上传头像前端

在edu/teacher/save.vue

页面、js

<!-- 讲师头像: --><el-form-item label="讲师头像" prop="avatar"><el-upload//发送请求:action="fileUrl":show-file-list="false":on-success="handleAvatarSuccess":before-upload="beforeAvatarUpload"class="avatar-uploader"><div class="upload-inner-wrapper"><imgv-if="dialogVisible":src="teacher.avatar"class="avatar"alt=""><i v-else class="el-icon-plus avatar-uploader-icon"/></div></el-upload></el-form-item>

// fileUrl: process.env.BASE_API + '/oss/fileOss/avatarUpload',handleAvatarSuccess(response, file) {if (response.code !== 200) {this.$message.error('上传失败')return}// 填充上传文件列表this.teacher.avatar = file.response.data.urlthis.dialogVisible = true},beforeAvatarUpload(file) {const isJPG = file.type === 'image/jpeg'const isLt2M = file.size / 1024 / 1024 < 2if (!isJPG) {this.$message.error('上传头像图片只能是 JPG 格式!')}if (!isLt2M) {this.$message.error('上传头像图片大小不能超过 2MB!')}return isJPG && isLt2M},

三.nigix

1.请求转发

2.负载均衡

动静分离

下载配置

1.启动

(启动后有两个进程,关闭窗口不会停止)

直接点击Nginx目录下的nginx.exe 或者 cmd运行start nginx最好用后者 ;关闭也是

2.关闭

nginx -s stop

nginx -s quit

stop表示立即停止nginx,不保存相关信息

quit表示正常退出nginx,并保存相关信息

3.重启

nginx -s reload

因为改变了配置,需要重启

前端请求转发

BASE_API: '"http://127.0.0.1:8008"',//nigix的,然后统一转发

nginx.conf配置

配置位在http

1.修改nginx默认端口;如:81

2.配置写在http{}里面!

#谷粒学苑配置server {listen 9001;#监听端口server_name localhost;#主机#匹配路径location ~ /eduservice/ { proxy_pass http://localhost:8001;}location ~ /eduoss/ { proxy_pass http://localhost:8002;}}

课程分类管理

一.EasyExcel

从表格读取数据/写入数据

1.概述

Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。面对高访问高并发,一定会OOM或者JVM频繁的full gc (重jc)。

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

github地址

文档地址

因为easyexcel本质是对poi的封装,所以依赖它 版本对应2.1.1–3.17

2.测试

写操作

导入依赖

<!--EasyExcel--><dependencies><!-- /artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency></dependencies>

实体对象

@Datapublic class Stu {//设置表头名称@ExcelProperty("学生编号")private int sno;//设置表头名称@ExcelProperty("学生姓名")private String sname;}

测试类

public class WriteTest {public static void main(String[] args) {String fileName = "E:\\test.xlsx";EasyExcel.write(fileName,Stu.class)//文件名/流,class类型.sheet("学生信息")//设置sheet名.doWrite(data());//传入要写的list集合data}//循环设置要添加的数据,最终封装到list集合中private static List<Stu> data() {List<Stu> list = new ArrayList<Stu>();for (int i = 0; i < 10; i++) {Stu data = new Stu();data.setSno(i);data.setSname("lucy"+i);list.add(data);}return list;}}

读操作

改造实体类,指定对应关系

@Datapublic class Stu {//设置表头名称,指定映射关系(第0列);index即为索引@ExcelProperty(value = "学生编号",index = 0)private int sno;//设置表头名称,指定映射关系(第1列)@ExcelProperty(value = "学生姓名",index = 1)private String sname;}

创建监听器

public class ExcelListener extends AnalysisEventListener<Stu> {//一行一行去读取excle内容,从第二行开始@Overridepublic void invoke(Stu stu, AnalysisContext analysisContext) {System.out.println("stu = " + stu);}//读取excel表头信息@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {System.out.println("表头信息:"+headMap);}//读取完成后执行@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}}

测试

public class ReadTest {public static void main(String[] args) {String fileName = "E:\\test.xlsx";//文件名,class类型,监听器EasyExcel.read(fileName,Stu.class,new ExcelListener()).sheet().doRead();}}

总结:读操作需要配置监听器

二.课程分类添加

后端

开始用到edu_subject表

获取上传文件,把内容读取出来 //MultipartFile 得到上传文件实现上传方法创建对应(表格)实体类

接口

@Api(tags = "课程管理")@Slf4j@RestController@RequestMapping("/edu/subject")public class SubjectController {@Autowiredprivate SubjectService subjectService;@PostMapping("/addSubject")@ApiOperation("通过上传Excel添加课程分类")public Result addSubject(MultipartFile file){subjectService.excelUpload(file,subjectService);return Result.ok();}}

监听器

@service @componoment 把对象创建交给spring管理,但是这个监听器是我们自己new的,不用,所以也自动注入其他对象,也不能实现数据库操作;

解决方案

1.构造器注入;在添加方法中作为参数传过来subjectService;

自动注入会形成循环依赖,listener调用service,service调用listener,所以不能使用同一个service对象

public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {public SubjectService subjectService;public SubjectExcelListener() {}public SubjectExcelListener(SubjectService subjectService) {this.subjectService = subjectService;}//读取excel的内容,一行一行读取@Overridepublic void invoke(SubjectData subjectData, AnalysisContext analysisContext) {if (subjectData == null){//读取内容为空throw new GuliException(ResultCodeEnum.DATA_ERROR);}//还可这样判断//subjectData = Optional.ofNullable(subjectData).orElseThrow(() -> GuliException.from(EduResultCode.FILE_IS_EMPTY));//一行一行读取,每次读取有两个值,第一个值为一级分类,第二个值为二级分类Subject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());if (existOneSubject == null) {//没有一级分类,进行添加existOneSubject = new Subject();existOneSubject.setParentId("0").setTitle(subjectData.getOneSubjectName());subjectService.save(existOneSubject);}//二级分类String pid = existOneSubject.getId();//取一级分类值(数据库没有一级的时候,添加了便生成了id;有一级分类的时候,那当然也有id),然后继续添加对应的二级分类Subject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);if (existTwoSubject == null) {existTwoSubject = new Subject();existTwoSubject.setParentId(pid).setTitle(subjectData.getTwoSubjectName());subjectService.save(existTwoSubject);}}//判断一级分类是否有重复的private Subject existOneSubject(SubjectService service,String name){QueryWrapper<Subject> wrapper = new QueryWrapper<>();wrapper.eq("title",name).eq("parent_id",0);Subject one = service.getOne(wrapper);return one;}//判断二级分类是否有重复的private Subject existTwoSubject(SubjectService service,String name,String pid){QueryWrapper<Subject> wrapper = new QueryWrapper<>();wrapper.eq("title",name).eq("parent_id",pid);Subject one = service.getOne(wrapper);return one;}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}}

回顾知识点

subjectService.save()这个方法,添加了部分属性,其他为null的属性不会覆盖数据库里的数据,所以上面可以分两次添加,成功

方法实现

@Overridepublic void excelUpload(MultipartFile file,SubjectService subjectService) {InputStream in = null;try {//获取文件输入流in = file.getInputStream();//调用方法进行读取 流 实体类 监听器 这是另一种读取方式EasyExcel.read(in, SubjectData.class,new SubjectExcelListener(subjectService)).sheet().doRead();}catch (Exception e){e.printStackTrace();}finally {try {if (in != null) {in.close();}} catch (IOException e) {e.printStackTrace();}}}

实体类

src/main/java/com/xxx/model/excel/SubjectData.java

@Data@EqualsAndHashCode(callSuper = false)@ApiModel(value="Excel读取对象", description="课程分类的表格读取对象")public class SubjectData {@ApiModelProperty(value = "一级分类")@ExcelProperty(index = 0)private String oneSubjectName;@ApiModelProperty(value = "二级分类")@ExcelProperty(index = 1)private String twoSubjectName;}

前端

创建路由

{path: '/subject',component: Layout,redirect: '/subject/list',name: '课程分类管理',meta: {title: '课程分类管理', icon: 'subject' },children: [{path: 'list',name: '课程分类列表',component: () => import('@/views/edu/subject/list'),meta: {title: '课程分类列表', icon: 'list2' }},{path: 'save',name: '添加课程分类',component: () => import('@/views/edu/subject/save'),meta: {title: '添加课程信息', icon: 'excel2' }}]},

页面、js

<div class="app-container"><el-form label-width="120px"><el-form-item label="信息描述"><el-tag type="info">excel模版说明</el-tag><el-tag><i class="el-icon-download"/><a :href="'/static/subject.xls'">点击下载模版</a></el-tag></el-form-item><el-form-item label="选择Excel"><el-uploadref="upload":auto-upload="true":on-success="fileUploadSuccess":on-error="fileUploadError":action="fileUrl"class="upload-demo"dragaccept="application/vnd.ms-excel"multiplename="file"><i class="el-icon-upload"/><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div><div slot="tip" class="el-upload__tip">只能上传xls/xlsx文件,且不超过500m</div></el-upload></el-form-item></el-form></div>

页面逻辑js

export default {name: 'Save',data() {return {fileUrl: process.env.BASE_API + '/edu/subject/addSubject'}},watch: {// 监听},created() {},methods: {fileUploadSuccess(response) {if (response.success === true) {this.$message({type: 'success',message: '添加课程分类成功!'})setTimeout(() => {this.$router.push('/subject/list')}, 1000)} else {this.$message.error('上传失败,请检查Excel表格是否符合规范,再刷新页面重新上传')}},fileUploadError(response) {if (response.success === false) {this.$message.error('上传失败,请检查Excel表格是否符合规范,再重新上传')}}}}

三.课程分类显示

后端接口

用Stream显示更简单

接口

@GetMapping("/getSubjectList")@ApiOperation("获取课程分类集合")public Result getSubjectList(){List<SubjectNestedVo> list = subjectService.getAllOneTwoSubject();return Result.ok().data("list",list);}

方法实现

@Overridepublic List<SubjectNestedVo> getAllOneTwoSubject() {//1、先查询出一级分类的QueryWrapper<Subject> oneWrapper = new QueryWrapper<>();oneWrapper.eq("parent_id",0);List<Subject> oneSubjectList = this.baseMapper.selectList(oneWrapper);//2、再查出来二级分类的QueryWrapper<Subject> twoWrapper = new QueryWrapper<>();twoWrapper.ne("parent_id",0);List<Subject> twoSubjectList = this.baseMapper.selectList(twoWrapper);//3、封装一级分类List<SubjectNestedVo> finalList = new ArrayList<>();if (oneSubjectList != null && oneSubjectList.size() > 0) {oneSubjectList.forEach(s -> {SubjectNestedVo subjectNestedVo = new SubjectNestedVo();BeanUtils.copyProperties(s,subjectNestedVo);//4、封装二级分类List<SubjectVo> subjectVoList = new ArrayList<>();if (twoSubjectList != null && twoSubjectList.size() > 0) {twoSubjectList.forEach(t->{if(s.getId().equalsIgnoreCase(t.getParentId())){SubjectVo subjectVo = new SubjectVo();BeanUtils.copyProperties(t,subjectVo);subjectVoList.add(subjectVo);}});}subjectNestedVo.setChildren(subjectVoList);finalList.add(subjectNestedVo);});}return finalList;}

谷粒商城的递归+stream实现分级参考

/*** 列表*/@RequestMapping("/list/tree")public List<CategoryEntity> list(){List<CategoryEntity> categoryEntities = categoryService.listWithTree();//找到所有的一级分类List<CategoryEntity> level1Menus = categoryEntities.stream().filter(item -> item.getParentCid() == 0).map(menu->{menu.setChildCategoryEntity(getChildrens(menu,categoryEntities));return menu;}).sorted((menu1, menu2) -> {return (menu1.getSort() ==null ? 0:menu1.getSort())- (menu2.getSort()==null?0:menu2.getSort());}).collect(Collectors.toList());return level1Menus;}public List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){List<CategoryEntity> childrens = all.stream().filter(item -> {return item.getParentCid() == root.getCatId();}).map(item -> {item.setChildCategoryEntity(getChildrens(item, all));return item;}).sorted((menu1, menu2) -> {return (menu1.getSort() ==null ? 0:menu1.getSort())- (menu2.getSort()==null?0:menu2.getSort());}).collect(Collectors.toList());return childrens;}

前端

添加api

import request from '@/utils/request'const frontUrl = '/edu/subject'export default {// 1、课程分类列表,条件查询带分页getSubjectList() {return request({async: false,url: `${frontUrl}/getSubjectList`,method: 'get'})}}

页面、js

<div class="app-container"><el-form style="margin: 3vh auto 0" label-width="180px"><el-form-item label="输入关键字进行过滤:"><el-input v-model="filterText" placeholder="输入关键字进行过滤" style="width: 200px"/></el-form-item></el-form><el-treeref="tree":data="list":props="defaultProps":filter-node-method="filterNode"accordionclass="filter-tree"/></div>

label 指定节点标签为节点对象的某个属性值

children 指定子树为节点对象的某个属性值

让输入的过滤值不区分大小写。

data.label.toLowerCase().indexOf(value.toLowerCase())

没见过的函数都是element自带的/封装好的

页面逻辑js

import subject from '@/api/edu/subject'export default {name: 'List',data() {return {list: [], // 查询出结果filterText: '',defaultProps: {children: 'children',label: 'title',value: 'id'}}},watch: {filterText(val) {this.$refs.tree.filter(val)}},created() {this.getList()},methods: {getList() {subject.getSubjectList().then(res => {if (res.success === true) {this.list = res.data.list}}).catch(error => {this.$message.error('加载失败,请联系管理员') })},filterNode(value, data) {if (!value) return truereturn data.label.toLowerCase().indexOf(value.toLowerCase()) !== -1}}}

下一部分

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。