1.教育平台两种模式

1. C2C

平台把自己的资源和用户转卖给视频或者直播内容提供者 通过出售内容获利

2.B2C模式

商家把自己制作大量版权视频上传到自己的网站 通过按月付费或者年付费

3. B2B2C

平台链接第三方教育机构和用户,平台一般不直接提供课程内容,而是更多承担教育的互联网载体角 色,为教学过程各个环节提供全方位支持和服务。

2.Myabaties

1. 配置类或者启动类添加@MapperScan 接口扫描

  1. @Configuration
  2. @MapperScan("com/atguigu/eduservice/mapper")
  3. public class MyConfig {

2. 接口继承BaseMapper即可实现数据库操作

public interface EduTeacherMapper extends BaseMapper<EduTeacher> {

}

3. 一般不直接使用Mapper 需要写Server实现BaseMapper

public interface EduTeacherService extends IService<EduTeacher> {
    //这里可以添加方法给Service实现
}

@Service
public class EduTeacherServiceImpl extends ServiceImpl<EduTeacherMapper, EduTeacher> implements EduTeacherService {

}

4. 日志

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

5. 主键策略

默认是全局id  一种基于雪花算法的主键自增

// 设置主键策略
@TableId(type = IdType.AUTO)
private Long id;

6. 自动填充

在User表中添加datetime类型的新的字段 create_time、update_time
// 在字段上面设置什么时候需要填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

// 配置类里配置自动填充类
@Component
public class MyMetObjHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // 传属性名称 不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

7. 实现乐观锁

取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败

// 字段上添加@Version
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
需要设置默认值  或者 在自动填充时设置值
this.setFieldValByName("version", 1, metaObject);

//注册乐观锁插件
@EnableTransactionManagement
@Configuration
@MapperScan("com.atguigu.mybatis_plus.mapper")
public class MybatisPlusConfig {
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
  return new OptimisticLockerInterceptor();
   }
}

8. 分页插件

@Bean
public PaginationInterceptor paginationInterceptor() {
  return new PaginationInterceptor();
}
//测试
Page<User> page = new Page<>(1,5);  // page对象 当前页数 当前一页几行数据
userMapper.selectPage(page, null);

9. 逻辑删除

@TableLogic  // 逻辑删除
@TableField(fill = FieldFill.INSERT) // 创建记录时 设置默认值
private Integer deleted;

this.setFieldValByName("deleted", 0, metaObject)

在 MybatisPlusConfig 中注册 逻辑删除Bean
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}

配置逻辑删除默认值 可有可无 如果不想改的的话

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

10. 性能分析

   参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题。<br /> 参数:format: SQL是否格式化,默认false。  
/**
* SQL 执行性能分析插件
* 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
*/
@Bean
@Profile({"dev","test"})// 设置 dev(生产模式) test(测试环境) 环境开启
public PerformanceInterceptor performanceInterceptor() {
  PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
  performanceInterceptor.setMaxTime(100);//ms,超过此处设置的ms则sql不执行
  performanceInterceptor.setFormat(true);
  return performanceInterceptor;
}

Spring Boot 中设置dev环境
#环境设置:dev、test、prod
spring.profiles.active=dev

image.png

11. Wapper

ge小于等于、gt、le大于、lt、isNull、isNotNull eq、ne between、notBetween like、notLike、likeLeft、likeRight
示例代码:

@Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}

嵌套 or and

userUpdateWrapper
 .like("name", "h")
 .or(i -> i.eq("name", "李白").ne("age", 20));

12. 统一格式的json返回格式

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

3. 后台开发

1. application.properties 数据库日期不对设置 Jackson

# 服务端口
server.port=8001
# 服务名
spring.application.name=service-edu
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
======================
jackson:
 date-format: yyyy-MM-dd HH:mm:ss

2. Mybaties代码生成器

/**
 * @author
 * @since 2018/12/13
 */
public class CodeGenerator {

    @Test
    public void run() {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("D:\\java_code\\GrainAcademy\\guli_parent\\service\\service_edu" + "/src/main/java");
        gc.setAuthor("testjava");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖

        gc.setServiceName("%sService");    //去掉Service接口的首字母I
        gc.setIdType(IdType.ID_WORKER_STR); //主键策略
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/guli_edu?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("zc0011..");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("eduservice"); //模块名
        pc.setParent("com.atguigu");
        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. 跨域配置

什么时跨域?
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域 。前后端 分离开发中,需要考虑ajax跨域的问题。
配置:
在Controller类上添加注解 @CrossOrigin //跨域

4. Swagger2

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
地址:http://localhost:8001/swagger-ui.html#
配置:

<!--swagger-->
   <dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <scope>provided </scope>
 </dependency>

Swagger配置类:

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public 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("@Z", "http://atguigu.com",
                        "3277324588@qq.com"))
                .build();
    }
}

如果通过导入其他模块 那么需要在启动类上扫描导入模块的配置类

@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
@EnableSwagger2
public class EduTeacherApplication {

Api模型:
定义样例数据
image.png
image.png
接口说明:
定义在类上:@Api 定义在方法上:@ApiOperation 定义在参数上:@ApiParam
@Api(description=”讲师管理”) @ApiOperation(value = “所有讲师列表”) @ApiOperation(value = “根据ID删除讲师”)

5. 后台统一返回数据格式

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

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

创建接口定义方法

public interface ResultCode {
   public static Integer SUCCESS = 20000;
   public static Integer ERROR = 20001;
}

创建接口类 这样写是为了能够使用链式编程

@Data
public class Res {

    @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>();

    /**
     * 私有构造器  不允许别人创建
     * 链式编程  构造器私有化 不允许别人创建 方法总是返回 this
     */
    private Res(){}

    public static Res ok(){
        Res r = new Res();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }
    public static Res error(){
        Res r = new Res();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    public Res success(Boolean success){
        this.setSuccess(success);
        return this;
    }
    public Res message(String message){
        this.setMessage(message);
        return this;
    }
    public Res code(Integer code){
        this.setCode(code);
        return this;
    }
    public Res data(String key, Object value){
        this.data.put(key, value);
        return this;
    }
    public Res data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

6. 异常

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 指定出现什么异常 执行此方法
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Res error(Exception e) {
        log.error(e.getMessage());
        return Res.error().message("出现了异常");
    }

    // 特定异常 全局异常小于特定异常
    @ResponseBody
    @ExceptionHandler(ArithmeticException.class)
    public Res error(ArithmeticException e) {
        log.error(e.getMessage());
        return Res.error().message("数学运算异常");
    }

自定义异常
创建异常类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GuliException extends RuntimeException {
    @ApiModelProperty(value = "状态码")
    private Integer code;
    private String msg;
}

添加到异常处理Controller

// 自定义异常处理  创建异常类继承Runtime  捕捉异常 返回
    @ResponseBody
    @ExceptionHandler(GuliException.class)
    public Res error(GuliException g){
        log.error(g.getMsg());
        return Res.error().code(g.getCode()).message(g.getMsg());
    }

7. 日志记录器

日志记录器(Logger)的行为是分等级的。如下表所示: 分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
设置日志级别 # 设置日志级别 logging.level.root=WARN 这种方式只能将日志打印在控制台上
Log back 日志
配置日志:删除Application日志配置
idea安装彩色日志插件 grep-console
resources 中创建 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="D:/temp/guli_code/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>INFO</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.guli" level="INFO" />

        <!--
            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>

将错误日志输出到文件
类上加@slf4j
异常输出语句 log.error(e.getMessage);
配置输出工具类:

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));  

8. OOS 文件上传 2.8.3

@Service
public class OssServiceImpl implements OssService {
    @Override
    public String uploadFileAvatar(MultipartFile file) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(ConstantPropertiesUtils.END_POINT, ConstantPropertiesUtils.KEY_ID, ConstantPropertiesUtils.KEY_SECRET);

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = null;
            String fileName = file.getOriginalFilename() + UUID.randomUUID() + toString().replace("-", "");
            putObjectRequest = new PutObjectRequest(ConstantPropertiesUtils.BUCKET_NAME, fileName, file.getInputStream());

            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            ossClient.putObject(putObjectRequest);
            return "https://" + ConstantPropertiesUtils.BUCKET_NAME + "." + ConstantPropertiesUtils.END_POINT + "/" + fileName;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }
}

OOS会根据你的路径创建文件夹 如2019/1/1 那么就会创建三层目录

joda-time 这个工具包 
String fileName = UUID.randomUUID() + toString().replace("-", "")+file.getOriginalFilename();
// 获取当前日期
String dateNow = new DateTime().toString("yyyy/MM/dd");
fileName = dateNow + "/" + fileName;

9.EasyExcel

根据excel表格内容 定义一个实体类 加上@ExcelProperty(“名字”)

@Data
@AllArgsConstructor  写是用得着 都必须有 
@NoArgsConstructor  读时用得着
public class ExcelTest {
    @ExcelProperty(value = "一级分类", index = 0)
    private String sno;
    @ExcelProperty(value = "二级分类", index = 1)
    private String sname;
}

写操作:

  public static void main(String[] args) {
//        实现excel写操作
//         设置写入文件夹地址和excel名称
        String fileName = "d:\\temp\\test.xlsx";
        ArrayList arrayList = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            arrayList.add(new ExcelTest("前端", "vue" + i));
        }
        EasyExcel.write(fileName, ExcelTest.class).sheet("学生列表").doWrite(arrayList);

读操作:
监听器


public class ExcelLinstener  extends AnalysisEventListener<ExcelTest> {

    // 一行一行的读取
    @Override
    public void invoke(ExcelTest excelTest, AnalysisContext analysisContext) {
        System.out.println("*******" + excelTest);
    }

    // 读取表头
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头" + headMap);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 完成之后
    }
}

在实体类加上 index= 0 代表该属性对应的excel列
调用

// 实现excel读操作
        String fileName = "d:\\temp\\test.xlsx";
        EasyExcel.read(fileName, ExcelTest.class, new ExcelLinstener()).sheet().doRead();

==========================================================

4. 前端开发

1. 箭头函数

// 传统
var f1 = function(a){
 return a
}
console.log(f1(1))

// ES6
var f2 = a => a
console.log(f2(1))

2. Vue.js

 Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系 统 这里的核心思想就是没有繁琐的DOM操作,例如jQuery中,我们需要先找到div节点,获取到DOM对  象,然后进行一系列的节点操作。

1. 基本语法

基本数据渲染指令:v-bind 或者 {{ }} 他可以把data里定义的数据渲染到页面
双向绑定数据 v-model 相当于维护了一个静态变量 任何一个地方修改 使用这个值的地方都会修改
事件 : v-on:事件 = 方法 或者 @ 事件= 方法 事件响应时会执行定义在 vue里的方法
修饰符 组织事件的默认行为
条件渲染 v-if v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销
毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会
开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基
于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频
繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

列表渲染 v-for :

<li v-for="(n, index) in 5">{{ n }} - {{ index }} </li>

<tr v-for="(item, index) in userList">
   <td>{{index}}</td>
   <td>{{item.id}}</td>
   <td>{{item.username}}</td>
   <td>{{item.age}}</td>
 </tr>

3.生命周期

//===创建时的四个事件
beforeCreate() { // 第一个被执行的钩子方法:实例被创建出来之前执行
 // beforeCreate执行时,data 和 methods 中的 数据都还没有没初始化
},

created() { // 第二个被执行的钩子方法
 // created执行时,data 和 methods 都已经被初始化好了!
 // 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
},

beforeMount() { // 第三个被执行的钩子方法
 // beforeMount执行时,模板已经在内存中编辑完成了,尚未被渲染到页面中
},

mounted() { // 第四个被执行的钩子方法
 // 内存中的模板已经渲染到页面,用户已经可以看见内容
},

4. 路由

Vue.js 路由允许我们通过不同的 URL 访问不同的内容。 通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。 Vue.js 路由需要载入 vue-router 库

// 跳转
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/student">会员管理</router-link>

  <!-- 路由出口 -->
 <!-- 路由匹配到的组件将渲染在这里 -->
 <router-view></router-view>

5. axios

 axios是独立于vue的一个项目,基于promise用于浏览器和node.js的http客户端 在浏览器中可以帮助我们完成 ajax请求的发送<br />在node.js中可以向远程接口发送请求  
axios.get('http://localhost:8081/admin/ucenter/member')
 .then(response => {
     console.log(response)
     this.memberList = response.data.data.items
 })
 .catch(error => {
     console.log(error)
 })

2. Node.js

 如果你是一个前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服 务,那么Node.js是一个非常好的选择。 Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js。 当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。  

终端目录下 node xx.js 执行js文件

1. 什么是npm ?

我们通过npm 可以很方便地下载js库,管理前端工程。 Node.js默认安装的npm包和工具的位置:Node.js目录\node_modules 在这个目录下你可以看见 npm目录,npm本身就是被NPM包管理器管理的一个工具,说明 Node.js已经集成了npm工具

2. 项目初始化

npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称 #version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml #我们之后也可以根据需要进行修改。

3. npm install 命令 npm install jquery npm install jquery@2.1.x

     #使用 -D参数将依赖添加到devDependencies节点 npm install --save-dev eslint  <br />#一些命令行工具常使用全局安装的方式 npm install -g webpack  <br />#卸载包 npm uninstall 包名 #全局卸载 npm uninstall -g 包名  

3. 打包工具 babel npm install —global babel-cli

image.png
image.png

1.安装解码器 npm install —save-dev babel-preset-es2015

2. 转码

# 转码结果写入一个文件
mkdir dist1
# --out-file 或 -o 参数指定输出文件
babel src/example.js --out-file dist1/compiled.js
# 或者
babel src/example.js -o dist1/compiled.js
# 整个目录转码
mkdir dist2
# --out-dir 或 -d 参数指定输出目录
babel src --out-dir dist2
# 或者
babel src -d dist2

4. 模块化开发

导出模块:export default { 方法 }
导入模块 import teacher from”@/api/edu/teacher”; 调用 teacher . 方法

5. 模块开发 npm install -g webpack webpack-cli

webpack目录下创建配置文件webpack.config.js :

const path = require("path"); //Node.js内置模块
module.exports = {
     entry: './src/main.js', //配置入口文件   需要自己创建并调用
     output: {
         path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径
         filename: 'bundle.js' //输出文件

     }
}

执行编译命令

webpack #有黄色警告
webpack --mode=development #没有警告
#执行后查看bundle.js 里面包含了上面两个js文件的内容并惊醒了代码压缩
 也可以配置项目的npm运行命令,修改package.json文件  
"scripts": {
     //...,
     "dev": "webpack --mode=development"
 }

运行npm命令执行打包 npm run dev

6. Nginx 请求转发 负载均衡 动静分离

1. 请求转发

image.png

2. 负载均衡 平分请求

image.png
用法:官网下载 解压 文件夹下cmd nginx.exe启动
停止 nginx.exe -s stop

3. nginx 配置

    开通9001端口  location配置当路径有这个字段  就转发到 proxy_pass http://192.168.2.12:8001 地址
      ~ 代表通过正则匹配
  server {
        listen       9001;
        server_name  localhost;

        location ~ /eduservice/{
            proxy_pass http://192.168.2.12:8001;
        }
        location ~ /eduoss/{
            proxy_pass http://192.168.2.12:8002;
         }
    }

=================================================================================

5. spring技巧

1. 文件夹错误 代码不能编译

右键模块:image.png
根据文件夹点击 相互对应
image.png

2. Bean加载 初始化方法

/**
 * 当spring容器配置加载好后  会执行这个接口 InitializingBean
 */
@Component
public class ConstantPropertiesUtils implements InitializingBean {

================================================================

6. 前端开发技巧

1. 路由

// 讲师管理
  {
    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: 'tree',
        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
      }
    ]
  },

2. 监听方法

watch:{ //监听
    $route(to, from){ //路由变化方式  当路由请求方式变化会响应  项目中一个是使用 link-to跳转  另外一个是 from 
      this.init()
    }
  },