项目总结谷粒学

文章目录

1.逆向工程 Mybatis-puls 代码生成器

1.1 源码:

<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency> 
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

# 服务端口
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=user
spring.datasource.password=111111


#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/xian/eduservice/mapper/xml/*.xml
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

spring.cloud.nacos.discovery.server-addr=localhost:8848
feign.hystrix.enabled=true

package com.xian.demo;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
 * @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:\\01guli\\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?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("user");
        dsc.setPassword("111111");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("eduservice"); //模块名
        pc.setParent("com.xian");

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8q7M4YyA-1652549498381)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220315160527852.png)]


2.解决返回格林时间问题

1.1 原返回

2019-11-15T13:47:12.000+0000

{"id":"1195337453429129218","name":"test","intro":"sdfsdf","career":"sdfdf","level":1,"avatar":"https://guli-file-190513.oss-cn-beijing.aliyuncs.com/avatar/default.jpg","sort":0,"isDeleted":1,"gmtCreate":"2019-11-15T13:47:12.000+0000","gmtModified":"2019-11-15T13:47:27.000+0000"}

1.2 修改

更改配置文件

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

修改后返回2019-11-15 21:47:12

{"id":"1195337453429129218","name":"test","intro":"sdfsdf","career":"sdfdf","level":1,"avatar":"https://guli-file-190513.oss-cn-beijing.aliyuncs.com/avatar/default.jpg","sort":0,"isDeleted":1,"gmtCreate":"2019-11-15 21:47:12","gmtModified":"2019-11-15 21:47:27"}

3.统一异常处理

1、创建统一异常处理器

/**
 * 统一异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(Exception.class)
	@ResponseBody
	public R error(Exception e){
		e.printStackTrace();
		return R.error();
	}
}

4.自定义异常

1、创建自定义异常类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException {

    @ApiModelProperty(value = "状态码")
    private Integer code;

    private String msg;
    
}

2、业务中需要的位置抛出GuliException

try {
    int a = 10/0;
}catch(Exception e) {
    throw new GuliException(20001,"出现自定义异常");
}

3、添加异常处理方法

@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
    e.printStackTrace();
    return R.error().message(e.getMsg()).code(e.getCode());
}

5.Logback日志

1、配置logback日志

  1. 删除application.properties中的日志配置
  2. 安装idea彩色日志插件:grep-console
  3. 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:/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>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>

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

1.GlobalExceptionHandler.java 中类上添加注解

 @Slf4j

2.异常输出语句

 log.error(e.getMessage());

3.示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqDD5PES-1652549498382)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317124316877.png)]


6.axios重点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LWTPDPoN-1652549498383)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317134247998.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BSJzSOlt-1652549498383)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317134424232.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LBRiyjOq-1652549498384)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317134924337.png)]


7.解决跨域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GJTbSBof-1652549498384)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317160957645.png)]

1.常见加入注解

@CrossOrigin //解决跨域

2.使用网关Getaway


8.前端开发流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23KjJhKc-1652549498385)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317163644749.png)]

9.讲师列表添加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtKxxtlm-1652549498385)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220317165037782.png)]


10.从配置文件读取常量

使用@Value读取application.properties里的配置内容

用spring的 InitializingBean 的 afterPropertiesSet 来初始化配置信息,这个方法将在所有的属性被初始化后调用。

package com.xian.oss.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
 * 常量类,读取配置文件application.properties中的配置
 */
@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 ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;
    public static String BUCKET_NAME;

    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = endpoint;
        ACCESS_KEY_ID = keyId;
        ACCESS_KEY_SECRET = keySecret;
        BUCKET_NAME = bucketName;
    }
}

实现InitializingBean接口后 加载完配置信息后 执行afterPropertiesSet()方法 后把配置信息注给static变量

重点: 当spring加载时就会执行afterPropertiesSet()


11.上传文件到阿里云oss

1.把文件上传到阿里云oss中

2.pom

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

    <!-- 日期工具栏依赖 -->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>
</dependencies>

3.编写controller

把上传的文件封装到MultipartFile类中

@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
public class OssController {

    @Autowired
    private OssService ossService;

    //上传头像
    @PostMapping("upload")
    public R uploadOssFile(MultipartFile file){
        //获取上传文件 file
        //返回上传到oss的路径
        String url=ossService.uploadOssFileAvatar(file);
        return R.ok().data("url",url);
    }
}

4.常量类

写properties

#服务端口
server.port=8002
#服务名
spring.application.name=service-oss

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

#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=LTAI5tFCTbYFwkW5qxUoqpqQ
aliyun.oss.file.keysecret=DkYefjokTt7T7Wiionf2fUMTT8mbYt
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=edu-11131

常量类

package com.xian.oss.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
 * 常量类,读取配置文件application.properties中的配置
 */
@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 ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;
    public static String BUCKET_NAME;

    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = endpoint;
        ACCESS_KEY_ID = keyId;
        ACCESS_KEY_SECRET = keySecret;
        BUCKET_NAME = bucketName;
    }
}

5.serviceImpl

package com.xian.oss.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.xian.oss.service.OssService;
import com.xian.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 uploadOssFileAvatar(MultipartFile file) {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = ConstandPropertiesUtils.END_POINT;
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = ConstandPropertiesUtils.ACCESS_KEY_ID;
        String accessKeySecret = ConstandPropertiesUtils.ACCESS_KEY_SECRET;
        // 填写Bucket名称,例如examplebucket。
        String bucketName = ConstandPropertiesUtils.BUCKET_NAME;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        String filename;
        try {
            //获取文件输入流
            InputStream inputStream = file.getInputStream();
            //获取文件名
            filename = file.getOriginalFilename();

            //生成uuid
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            filename=uuid+filename;

            //生成当前时间
            String s = new DateTime().toString("yyyy/MM/dd");
            // 2022/03/20/8fca6904087147d6a311e3cd245e047f照片.jpg"
            filename=s+"/"+filename;

            // 创建PutObject请求。
            ossClient.putObject(bucketName, filename, inputStream);
            ossClient.shutdown();
            //拼接文件名
            String url="http://" + bucketName + "." + endpoint + "/" + filename;;
            return url;
        } catch (Exception oe) {
            oe.printStackTrace();
            return null;
        }
    }
}

重要的

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            //获取文件输入流
            InputStream inputStream = file.getInputStream();
            //获取文件名
            String filename = file.getOriginalFilename();
            // 创建PutObject请求。
            ossClient.putObject(bucketName, filename, inputStream);
            ossClient.shutdown();
            //拼接文件名
            String url="http://" + bucketName + "." + endpoint + "/" + filename;;
            return url;
 			//生成uuid
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            filename=uuid+filename;

            //生成当前时间
            String s = new DateTime().toString("yyyy/MM/dd");
            // 2022/03/20/8fca6904087147d6a311e3cd245e047f照片.jpg"
            filename=s+"/"+filename;

启动类上加入注解要不然会报错

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = {"com.xian"})
public class OssApplication {
    public static void main(String[] args) {
        SpringApplication.run(OssApplication.class,args);
    }
}

12.课程分类 一二级目录

1.pom

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

2.entity

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="EduSubject对象", description="课程科目")
public class EduSubject implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "课程类别ID")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "类别名称")
    private String title;

    @ApiModelProperty(value = "父ID")
    private String parentId;

    @ApiModelProperty(value = "排序字段")
    private Integer sort;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)3.
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
    
}

3.excel entity

package com.xian.eduservice.entity.excel;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class SubjectData {
    @ExcelProperty(index = 0)
    private String oneSubjectName;

    @ExcelProperty(index = 1)
    private String twoSubjectName;
}

4.service

@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {

    //添加课程分类
    @Override
    public void saveSubject(MultipartFile file,EduSubjectService subjectService) {
        //读取excel
        try {
            EasyExcel.read(file.getInputStream(), SubjectData.class,new SubjectExcelListener(subjectService)).sheet().doRead();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.listener

把excel表数据 对应到excel entity表中 所以要把SubjectData类传入进去

package com.xian.eduservice.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xian.eduservice.entity.EduSubject;
import com.xian.eduservice.entity.excel.SubjectData;
import com.xian.eduservice.service.EduSubjectService;
import com.xian.eduservice.service.EduTeacherService;
import com.xian.servicebase.exceptionhandler.GuliException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
    //因为AnalysisEventListener不能交给spring管理 所以不能注入 需要自己new

    public EduSubjectService subjectService;

    public SubjectExcelListener(EduSubjectService subjectService) {
        this.subjectService=subjectService;
    }

    public SubjectExcelListener() {
    }

    @Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        //一行一行读取
        if (subjectData==null){
            throw new GuliException(20001,"没有信息");
        }
        //判断一级分类是否重复
        EduSubject oneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
        if (oneSubject==null){
            oneSubject=new EduSubject();
            oneSubject.setParentId("0");
            oneSubject.setTitle(subjectData.getOneSubjectName());
            subjectService.save(oneSubject);
        }
        //判断二级分类是否重复
        //获取一级分类id (把一级id存入二级parentId中)
        String parentId = oneSubject.getId();
        EduSubject twoSubject = this.existsTwoSubject(subjectService, subjectData.getTwoSubjectName(), parentId);
        if(twoSubject==null){
            twoSubject=new EduSubject();
            twoSubject.setParentId(parentId);
            twoSubject.setTitle(subjectData.getTwoSubjectName());
            subjectService.save(twoSubject);
        }
    }

    //通过title 查询数据库有无 相同的一级分类的数据
    public EduSubject existOneSubject(EduSubjectService subjectService,String name){
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_id","0");
        wrapper.eq("title",name);
        EduSubject subjectServiceOne = subjectService.getOne(wrapper);
        return subjectServiceOne;
    }

    //通过title parent_id 查询数据库有无 相同的二级分类的数据
    public EduSubject existsTwoSubject(EduSubjectService subjectService,String name,String parentId){
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_id",parentId);
        wrapper.eq("title",name);
        EduSubject subjectServiceTwo = subjectService.getOne(wrapper);
        return subjectServiceTwo;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    }
}

重要:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKR5WjWw-1652549498385)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220320164530139.png)]

invoke方法

如果excel没有数据执行throw new GuliException(20001,“没有信息”);

把excel表数据 对应到excel entity表中 在获取exel表中的信息 在通过查询数据库有无相同信息 没有就插入

@Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        //一行一行读取
        if (subjectData==null){
            throw new GuliException(20001,"没有信息");
        }
        //判断一级分类是否重复
        EduSubject oneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
        if (oneSubject==null){
            oneSubject=new EduSubject();
            oneSubject.setParentId("0");
            oneSubject.setTitle(subjectData.getOneSubjectName());
            subjectService.save(oneSubject);
        }
        //判断二级分类是否重复
        //获取一级分类id (把一级id存入二级parentId中)
        String parentId = oneSubject.getId();
        EduSubject twoSubject = this.existsTwoSubject(subjectService, subjectData.getTwoSubjectName(), parentId);
        if(twoSubject==null){
            twoSubject=new EduSubject();
            twoSubject.setParentId(parentId);
            twoSubject.setTitle(subjectData.getTwoSubjectName());
            subjectService.save(twoSubject);
        }
    }
判断一级分类有无相同数据方法

需要传入一级·分类的name 也就是对应guli_subject表的title

执行sql语句select * from guli_subject where parent_id=“0” and title=“?”

没有查到就执行插入数据

//通过title 查询数据库有无 相同的一级分类的数据
public EduSubject existOneSubject(EduSubjectService subjectService,String name){
    QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
    wrapper.eq("parent_id","0");
    wrapper.eq("title",name);
    EduSubject subjectServiceOne = subjectService.getOne(wrapper);
    return subjectServiceOne;
}
判断二级分类有无相同数据方法

需要传入二级·分类的name 和一级分类的id 也就是对应guli_subject表的title parent_id

执行sql语句select * from guli_subject where parent_id=“?” and title=“?”

没有查到就执行插入数据

String parentId = oneSubject.getId();    
//通过title parent_id 查询数据库有无 相同的二级分类的数据
    public EduSubject existsTwoSubject(EduSubjectService subjectService,String name,String parentId){
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_id",parentId);
        wrapper.eq("title",name);
        EduSubject subjectServiceTwo = subjectService.getOne(wrapper);
        return subjectServiceTwo;
    }
插入一级分类

oneSubject==null

先new EduSubject(); 在 oneSubject.setParentId(“0”); oneSubject.setTitle(subjectData.getOneSubjectName());

最后把数据插入进去

        //判断一级分类是否重复
        EduSubject oneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());

        if (oneSubject==null){
            oneSubject=new EduSubject();
            oneSubject.setParentId("0");
            oneSubject.setTitle(subjectData.getOneSubjectName());
            subjectService.save(oneSubject);
        }
插入二级分类

注意 需要把一级数据的id 传入给二级分类parentId中

最后把数据插入进去

 twoSubject=new EduSubject();
            twoSubject.setParentId(parentId);
            twoSubject.setTitle(subjectData.getTwoSubjectName());
            subjectService.save(twoSubject);
//判断二级分类是否重复
        //获取一级分类id (把一级id存入二级parentId中)
        String parentId = oneSubject.getId();
        EduSubject twoSubject = this.existsTwoSubject(subjectService, subjectData.getTwoSubjectName(), parentId);
        if(twoSubject==null){
            twoSubject=new EduSubject();
            twoSubject.setParentId(parentId);
            twoSubject.setTitle(subjectData.getTwoSubjectName());
            subjectService.save(twoSubject);
        }

总结:

把excel的数据对应到一张SubjectData表中 在通过表中的数据信息 如一行一列的数据(oneSubjectName)在通过数据库查询有无数据 没有数据进行添加 而一行二列的数据(twoSubjectName) 也需要先查询数据库判断有无 没有的话 获取一行一列插入数据库的id 并插入到parent_id中


13.课程分类列表 树形结构

1.效果展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzzmFYcc-1652549498386)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220321090157084.png)]

2.按返回的结果封装两个实体类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jWdj6aXY-1652549498386)(D:\02谷粒学院-资料\资料\笔记课件\day07\随堂笔记\03-课程分类列表树形显示.png)]

2.1一级分类:

一个一级分类 有多个二级分类 先创建一个list集合 因为传入的数据也可能是多条数据
private List children;

//一级分类
@Data
public class OneSubject {
    private String id;
    private String title;

    //一个一级分类 有多个二级分类 先创建一个list集合 因为传入的数据也可能是多条数据
    private List<TwoSubject> children;
}
/*
{
        "id": "1178214681118568449",
        "title": "后端开发",
        "children": [
          {
            "id": "1178214681139539969",
            "title": "Java"
          },
          {
            "id": "1178585108407984130",
            "title": "Python"
          }
        ]
      },
 */

2.2二级分类:

一个一级分类有多个二级分类

//二级分类
@Data
public class TwoSubject {
    private String id;
    private String title;
}

3.编写controller

查询课程分类列表 树形结构

//课程分类列表 树形结构
@GetMapping("getAllSubject")
public R getAllSubject(){
    //泛型是一级分类
    List<OneSubject> list=subjectService.getAllOneTwoSubject();
    return R.ok().data("list",list);
}
重点:设计返回结果为一个一级分类 但一级分类包括多个二级分类 在实体类中也进行了封装

private List children;

4.编写impl

    //课程分类列表 树形结构
    @Override
    public List<OneSubject> getAllOneTwoSubject() {
        //查询一级数据 parent_id="0"
        QueryWrapper<EduSubject> oneWrapper = new QueryWrapper<>();
        QueryWrapper<EduSubject> one = oneWrapper.eq("parent_id", "0");
        List<EduSubject> oneSubjects = baseMapper.selectList(oneWrapper);

        //查询二级分类 parent_id!="0"
        QueryWrapper<EduSubject> twoWrapper = new QueryWrapper<>();
        QueryWrapper<EduSubject> two = twoWrapper.ne("parent_id", "0");
        List<EduSubject> twoSubjects = baseMapper.selectList(two);

        //返回一级分类
        List<OneSubject> finalSubjectList=new ArrayList<>();

        //封装一级分类
        for (int i = 0; i < oneSubjects.size(); i++) {
            EduSubject eduSubject = oneSubjects.get(i);
            OneSubject oneSubject = new OneSubject();
            //把eduSubject copy到oneSubject中
            BeanUtils.copyProperties(eduSubject,oneSubject);


            //封装二级分类 在TwoSubject中先创建一个list集合 因为传入的二级数据也可能是多个集合构成 所以在创建finalTwoSubject集合
            List<TwoSubject> finalTwoSubject=new ArrayList<>();
            for (int m = 0; m < twoSubjects.size(); m++) {
                EduSubject subject = twoSubjects.get(m);
                if (oneSubject.getId().equals(subject.getParentId())){
                    TwoSubject twoSubject = new TwoSubject();
                    BeanUtils.copyProperties(subject,twoSubject);
                    finalTwoSubject.add(twoSubject);
                }
            }

            oneSubject.setChildren(finalTwoSubject);
            finalSubjectList.add(oneSubject);
        }
        return finalSubjectList;
    }
4.1编写流程:
1.查询一级数据:
    //查询一级数据 parent_id="0"
    QueryWrapper<EduSubject> oneWrapper = new QueryWrapper<>();
    QueryWrapper<EduSubject> one = oneWrapper.eq("parent_id", "0");
    List<EduSubject> oneSubjects = baseMapper.selectList(oneWrapper);
2.查询二级数据:
        //查询二级分类 parent_id!="0"
        QueryWrapper<EduSubject> twoWrapper = new QueryWrapper<>();
        QueryWrapper<EduSubject> two = twoWrapper.ne("parent_id", "0");
        List<EduSubject> twoSubjects = baseMapper.selectList(two);
3.封装一级数据 在中间嵌套 封装二级数据:

1.先创建返回一级分类集合

List finalSubjectList=new ArrayList<>();

2.遍历把查询出来的一级分类(eduSubject)copy到OneSubject类(oneSubject)中 再把数据存入finalSubjectList集合

3.在一级分类中嵌套二级分类 就是下段代码 但是不是所有二级分类都是一级的 所以需要做一个判断

oneSubject.getId().equals(subject.getParentId())

            List<TwoSubject> finalTwoSubject=new ArrayList<>();
            for (int m = 0; m < twoSubjects.size(); m++) {
                EduSubject subject = twoSubjects.get(m);
                if (oneSubject.getId().equals(subject.getParentId())){
                    TwoSubject twoSubject = new TwoSubject();
                    BeanUtils.copyProperties(subject,twoSubject);
                    finalTwoSubject.add(twoSubject);
                }
            }

            oneSubject.setChildren(finalTwoSubject);
        //返回一级分类
        List<OneSubject> finalSubjectList=new ArrayList<>();

        //封装一级分类
        for (int i = 0; i < oneSubjects.size(); i++) {
            EduSubject eduSubject = oneSubjects.get(i);
            OneSubject oneSubject = new OneSubject();
            //把eduSubject copy到oneSubject中
            BeanUtils.copyProperties(eduSubject,oneSubject);


            //封装二级分类 在TwoSubject中先创建一个list集合 因为传入的二级数据也可能是多个集合构成 所以在创建finalTwoSubject集合
            List<TwoSubject> finalTwoSubject=new ArrayList<>();
            for (int m = 0; m < twoSubjects.size(); m++) {
                EduSubject subject = twoSubjects.get(m);
                if (oneSubject.getId().equals(subject.getParentId())){
                    TwoSubject twoSubject = new TwoSubject();
                    BeanUtils.copyProperties(subject,twoSubject);
                    finalTwoSubject.add(twoSubject);
                }
            }

            oneSubject.setChildren(finalTwoSubject);
            finalSubjectList.add(oneSubject);
        }
        return finalSubjectList;

14.课程发布模块

1.流程

image-20220321094450225

2.表设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTnFEg6u-1652549498386)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220329171137106.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cWWDXJRx-1652549498387)(D:\02谷粒学院-资料\资料\笔记课件\day07\随堂笔记\05-课程相关表关系.png)]

SELECT ec.id,ec.title,ec.price,ec.lesson_num,
ecd.description,
et.`name`,
es1.title as oneSubject,
es2.title as twoSubject 
FROM edu_course ec left JOIN edu_course_description ecd on ec.id=ecd.id
										left join edu_teacher et on ec.teacher_id=et.id
										left join edu_subject es1 on ec.subject_parent_id=es1.id
										left join edu_subject es2 on ec.subject_id=es2.id
										where ec.id='14';

15.整合hystrix

1.主启动

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@ComponentScan(basePackages = "com.xian")
public class EduApplication {
    public static void main(String[] args) {
        SpringApplication.run(EduApplication.class,args);
    }
}

2.远程调用接口

@FeignClient(name = “service-vod”,fallback = VodFileDegradeFeignClient.class)

@Component
@FeignClient(name = "service-vod",fallback = VodFileDegradeFeignClient.class)
public interface VodClient {
    @DeleteMapping("/eduvod/video/removeAlyVideo/{id}")
    public R removeAlyVideo(@PathVariable("id") String id);

    @DeleteMapping("/eduvod/video/delete-batch")
    public R deleteBatch(@RequestParam("videoIdList") List<String> videoIdList);
}

3.实现类

@Component
public class VodFileDegradeFeignClient implements VodClient{
    @Override
    public R removeAlyVideo(String id) {
        System.out.println("删除视频出错了");
        return R.error().message("删除视频出错了");
    }

    @Override
    public R deleteBatch(List<String> videoIdList) {
        return R.error().message("删除多个视频出错了");
    }
}
spring.cloud.nacos.discovery.server-addr=localhost:8848
feign.hystrix.enabled=true

主要: 当服务提供者挂了执行实现类方法

16.整合nacos

0.pom

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
<!--        &lt;!&ndash;hystrix依赖,主要是用  @HystrixCommand &ndash;&gt;-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

<!--        &lt;!&ndash;服务注册&ndash;&gt;-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
<!--        &lt;!&ndash;服务调用&ndash;&gt;-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

1.主启动

注解@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@ComponentScan(basePackages = "com.xian")
public class EduApplication {
    public static void main(String[] args) {
        SpringApplication.run(EduApplication.class,args);
    }
}

2.yaml

spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.application.name=service-edu

17.单点登陆sso

1.三种方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O9FYQLlu-1652549498387)(D:\02谷粒学院-资料\资料\笔记课件\day12\day12笔记\随堂笔记\02 单点登录三种方式介绍.png)]

18.用邮箱发验证码 把验证码存入redis中设置5秒有效

1.开启redis

redis-server /etc/redis.conf 
redis-cli   

2.controller

先判断redis中有无该qq验证码 有就返回欧克 没有就 生成并存入redis中设置五秒有效

@GetMapping("send/{qq}")
    public R sendMsm(@PathVariable String qq){
        //判断redis中有无验证码
        //有就return R.ok();
        //没有就生成验证码 并存在redis中
        String code = redisTemplate.opsForValue().get(qq);
        if (!StringUtils.isEmpty(code)){
            return R.ok();
        }

        //生成验证码
        code = RandomUtil.getFourBitRandom();


        javaMailSender.setPassword("wrnxooqtppltcdig");
        javaMailSender.setHost("smtp.qq.com");
        javaMailSender.setUsername("1984564067@qq.com");
        javaMailSender.setProtocol("smtp");
        //建立邮件消息
        SimpleMailMessage mainMessage = new SimpleMailMessage();
        //发送者
        mainMessage.setFrom("1984564067@qq.com");
        //接收者
        mainMessage.setTo(qq);
        //发送的标题
        mainMessage.setSubject("验证码");
        //发送的内容
        mainMessage.setText(code);
        javaMailSender.send(mainMessage);

        redisTemplate.opsForValue().set(qq,code,5, TimeUnit.SECONDS);
        return R.ok();
    }

19登陆功能

利用token模式 把用户数据封装到jwt中

数据库表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R8g72Ris-1652549498387)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220402181318979.png)]

1.controller

:传入的是手机号密码

    @Autowired
    private UcenterMemberService memberService;

    //登陆
    @PostMapping("login")
    public R login(@RequestBody UcenterMember member){
        //传入的是手机号密码
        //返回token字符串
        String token=memberService.login(member);
        return R.ok().data("token",token);
    }

2.serviceImpl

思路:

1.先获取手机号密码

2.判断手机号密码是否为空 (有一个为空抛出异常)

3.在数据库查询出手机号为mobile的用户 (为空抛出异常)

4.通过手机号查出的用户数据 判断密码 (为空抛出异常)

5.验证成功后 把查出的用户(id,名称)封装成token

@Override
    public String login(UcenterMember member) {
        //获取手机号密码
        String mobile = member.getMobile();
        String password = member.getPassword();

        //判断是否为空 为空抛异常
        if (StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){
            throw new GuliException(20001,"手机号或密码为空");
        }
        //在数据库查询出手机号为mobile的用户 为空抛出异常
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        UcenterMember ucenterMember = baseMapper.selectOne(wrapper);

        //通过手机号查出的用户数据为空抛出异常
        if (ucenterMember==null){
            throw new GuliException(20001,"手机号为空或错误");
        }

        //通过手机号查出的用户数据 判断密码 (当不相等时执行下段代码)
        //因为在数据库的密码都是加密的 所以比较时先把传入的密码先加密
        if (!MD5.encrypt(password).equals(ucenterMember.getPassword())){
            throw new GuliException(20001,"密码错误");
        }

        //登陆成功
        //验证成功后 把查出的用户(id,名称)封装成token
        String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
        return jwtToken;
    }

20:注册功能

注册功能:主要判断手机号发送验证码是否正确 ,手机号是否存在

RegisterVo封装了传入的条件

RegisterVo

@Data
@ApiModel(value="注册对象", description="注册对象")
public class RegisterVo {

    @ApiModelProperty(value = "昵称")
    private String nickname;

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "验证码")
    private String code;
}

1.controller

    //注册
    @PostMapping("register")
    public R register(@RequestBody RegisterVo registerVo){
        memberService.register(registerVo);
        return R.ok();
    }

2.service

 //注册
    @Override
    public void register(RegisterVo registerVo) {
        //获取信息
        String code = registerVo.getCode();//验证码
        String mobile = registerVo.getMobile();//手机号
        String password = registerVo.getPassword();//密码
        String nickname = registerVo.getNickname();//名称

        if (StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)
                ||StringUtils.isEmpty(code)||StringUtils.isEmpty(nickname)){
            throw new GuliException(20001,"register为空");
        }
        
		//通过手机号为key获取redis中的验证码 如果不等就抛出异常
        String o = redisTemplate.opsForValue().get(mobile);
        System.out.println("------------------------"+o);
        if (!code.equals(o)){
            throw new GuliException(20001,"验证码错误");
        }

        //判断手机号在数据库是否存在 如果integer>0说明数据库有注册记录 在抛出异常
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        Integer integer = baseMapper.selectCount(wrapper);
        System.out.println("------------------------"+integer);
        if (integer>0){
            System.out.println("------------------------"+integer);
            throw new GuliException(20001,"手机号已存在");
        }
		
        //把RegisterVo转换为UcenterMember对象并插入数据库 
        //注意存入数据库密码需要加密MD5.encrypt(password)
        UcenterMember ucenterMember = new UcenterMember();
        ucenterMember.setMobile(mobile);
        //存入数据库需要加密
        ucenterMember.setPassword(MD5.encrypt(password));
        ucenterMember.setNickname(nickname);
        //设置不禁用
        ucenterMember.setIsDisabled(false);
        ucenterMember.setIsDeleted(false);
        ucenterMember.setAvatar("https://edu-11131.oss-cn-beijing.aliyuncs.com/2022/03/20/0253cd2760c84dd1a63dc4d6216665d4file.png?versionId=CAEQFhiBgICR.vyW_RciIGRmOWU2ZmM5ZDA5NzRmNTVhNzZlZmRkNjdjOTA2MWQ3");
        baseMapper.insert(ucenterMember);
    }

21 tinyint

image-20220403002651053

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6bO4hVe-1652549498387)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220403002729703.png)]

当设置数据库字段属性为tinyint 在插入数据时true为1 false为0


22微信二维码注册登陆

1.配置:

# 微信开放平台 appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback

2.将配置转换为常量类

@Component
public class ConstandWxUtils implements InitializingBean {
    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
        WX_OPEN_REDIRECT_URL = redirectUrl;
    }
}

3.controller

因为 redirect_uri:http://localhost:8160/api/ucenter/wx/callback
所以 扫码成功后执行上面地址 上面地址又重定向到http://localhost:3000

    @GetMapping("login")
    public String login(){
        // 微信开放平台授权baseUrl
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        //对ConstandWxUtils.WX_OPEN_REDIRECT_URL 进行编码UTF-8
        String redirectUrl = ConstandWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
        } catch (UnsupportedEncodingException e) {
            throw new GuliException(20001, e.getMessage());
        }

        String state = "imhelen";//为了让大家能够使用我搭建的外网的微信回调跳转服务器,这里填写你在ngrok的前置域名

        //对baseUrl字符串 给%s传递参数 返回结果字符串
        String url = String.format(
                baseUrl,
                ConstandWxUtils.WX_OPEN_APP_ID,
                redirectUrl,
                state);
        return "redirect:"+url;
    }

4.controller

@Autowired
    private UcenterMemberService ucenterMemberService;
    @GetMapping("callback")
    public String callback(String code ,String state){
        try {
            //向认证服务器发送请求换取access_token
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";
            String format = String.format(
                    baseAccessTokenUrl,
                    ConstandWxUtils.WX_OPEN_APP_ID,
                    ConstandWxUtils.WX_OPEN_APP_SECRET,
                    code
            );

            //HttpClient发送get请求 得到的是类似json的字符串
            String accessTokenInfo = HttpClientUtils.get(format);
            //gson转换为hashmap类型
            Gson gson = new Gson();
            HashMap<String,String> map = gson.fromJson(accessTokenInfo, HashMap.class);
            String accessToken = map.get("access_token");
            String openid = map.get("openid");


            //访问微信的资源服务器,获取用户信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
            String userInfo = String.format(
                    baseUserInfoUrl,
                    accessToken,
                    openid);
            String user = HttpClientUtils.get(userInfo);
            Gson userInfoGson = new Gson();
            HashMap<String, String> mapUserInfo = userInfoGson.fromJson(user, HashMap.class);
            String nickname = mapUserInfo.get("nickname");
            String headimgurl = mapUserInfo.get("headimgurl");
            
            System.out.println("微信name"+nickname+"==============="+"头像地址"+headimgurl);

            //通过openId判断数据库有无 没有添加到数据库
            UcenterMember member=ucenterMemberService.getOpenIdMeber(openid);
            if (member==null){
                member = new UcenterMember();
                member.setOpenid(openid);
                member.setNickname(nickname);
                member.setAvatar(headimgurl);
                ucenterMemberService.save(member);
            }
			
            //利用token模式 使用jwt封装用户数据
            String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
            return "redirect:http://localhost:3000?token="+jwtToken;
        } catch (Exception e) {
            throw new GuliException(20001,"错误");
        }
    }

5.service

    @Override
    public UcenterMember getOpenIdMeber(String openid) {
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("openid",openid);
        UcenterMember member = baseMapper.selectOne(wrapper);
        return member;
    }

23谷粒表分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8R36MIxg-1652549498388)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220406181250464.png)]

24.支付模块

表1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJuUaLO0-1652549498388)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412152418055.png)]

表2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bz8uQY9V-1652549498388)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412152447515.png)]

(1)如果是付费课程,在用户选择课程,进入到课程详情页面时候,会显示 “立即购买”

(2)点击“立即购买”,会生成课程的订单,跳转到订单页面 (生成课程的订单)

(3) 点击“去支付”,会跳转到支付页面,生成微信扫描的二维码

(4)使用微信扫描支付后,会跳转回到课程详情页面,同时显示“立即观看”

pom

        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

1.根据课程id与token信息生成订单

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGNHFVE0-1652549498388)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412115444706.png)]

当点击立即购买就会直接生成订单

1.controller

​ JwtUtils.getMemberIdByJwtToken(request)获取head中的token信息 在返回该用户在用户表的id

    @PostMapping("createOrder/{courseId}")
    public R saveOrder(@PathVariable("courseId") String courseId, HttpServletRequest request){
        //返回订单号
        log.info("------------------------------"+JwtUtils.getMemberIdByJwtToken(request));
        String orderNo=orderService.createOrders(courseId, JwtUtils.getMemberIdByJwtToken(request));
        return R.ok().data("orderId",orderNo);
    }
2.JwtUtils
/**
 * @author helen
 * @since 2019/10/16
 */
public class JwtUtils {

    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

                .claim("id", id)
                .claim("nickname", nickname)

                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}


3.service createOrders方法
order表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ysNoJaqC-1652549498389)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412110803693.png)]

接口
String createOrders(String courseId, String memberIdByJwtToken);
远程调用
1.根据用户id获取用户信息
//远程调用 根据用户id获取用户信息
//再把查询信息插入到order订单表中
UcenterMemberOrder userInfoOrder = ucenterClient.getUserInfoOrder(memberId);

2.controller
//根据课程id获取课程信息
@PostMapping("getCourseInfoOrder/{id}")
public CourseWebVoOrder getCourseInfoOrder(@PathVariable String id){
    CourseWebVo baseCourseInfo = courseService.getBaseCourseInfo(id);
    CourseWebVoOrder courseWebVoOrder = new CourseWebVoOrder();
    BeanUtils.copyProperties(baseCourseInfo,courseWebVoOrder);
    return courseWebVoOrder;
}
3.serviceImpl
//根据课程id查询课程信息  使用sql
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
    return baseMapper.getBaseCourseInfo(courseId);
}
4.mapper.xml
 <select id="getBaseCourseInfo" resultType="com.xian.eduservice.entity.frontvo.CourseWebVo">
        SELECT
            c.id,
            c.title,
            c.cover,
            c.lesson_num AS lessonNum,
            c.price,
            c.buy_count as buyCount,
            c.view_count as viewCount,

            ecd.description,

            s1.id as subjectLeveloneId,
            s1.title AS subjectLevelOne,
            s2.id as subjectLevelTwoId,
            s2.title AS subjectLevelTwo,

            t.id as teacherId,
            t.intro,
            t.avatar,
            t.name AS teacherName
        FROM
            edu_course c
                LEFT JOIN edu_teacher t ON c.teacher_id = t.id
                LEFT JOIN edu_subject s1 ON c.subject_parent_id = s1.id
                LEFT JOIN edu_subject s2 ON c.subject_id = s2.id
                left join edu_course_description ecd on c.id=ecd.id
        WHERE
            c.id = #{courseId}
    </select>

//远程调用 根据课程id获取课程信息
//把用户信息插入到order订单表
CourseWebVoOrder courseInfoOrder = eduClient.getCourseInfoOrder(courseId);
1.根据课程id获取课程信息
    //根据用户id获取用户信息
    @PostMapping("getUserInfoOrder/{id}")
    public UcenterMemberOrder getUserInfoOrder(@PathVariable String  id){
        UcenterMember member = memberService.getById(id);
        //UcenterMember 复制在common_utils UcenterMemberOrder方便远程调用返回结果
        UcenterMemberOrder ucenterMemberOrder = new UcenterMemberOrder();
        BeanUtils.copyProperties(member,ucenterMemberOrder);
        return ucenterMemberOrder;
    }

createOrders实现方法
@Autowired
    private EduClient eduClient;

    @Autowired
    private UcenterClient ucenterClient;

    //返回订单号
    @Override
    public String createOrders(String courseId, String memberId) {
        //远程调用 根据用户id获取用户信息
        UcenterMemberOrder userInfoOrder = ucenterClient.getUserInfoOrder(memberId);

        //远程调用 根据课程id获取课程信息
        CourseWebVoOrder courseInfoOrder = eduClient.getCourseInfoOrder(courseId);

        Order order = new Order();
        //把得到的信息set在order表中
        //OrderNoUtil.getOrderNo()生成订单号
        order.setOrderNo(OrderNoUtil.getOrderNo());
        order.setCourseId(courseId);
        order.setCourseTitle(courseInfoOrder.getTitle());
        order.setCourseCover(courseInfoOrder.getCover());
        order.setTeacherName(courseInfoOrder.getTeacherName());
        order.setTotalFee(courseInfoOrder.getPrice());
        order.setMemberId(memberId);
        order.setMobile(userInfoOrder.getMobile());
        order.setNickname(userInfoOrder.getNickname());

        order.setStatus(0);//订单状态(0:未支付 1:已支付)
        order.setPayType(1);//支付类型(1:微信 2:支付宝)
        
        baseMapper.insert(order);
        //返回插入数据库中的订单号
        return order.getOrderNo();
    }
4.远程调用实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fcq1muGp-1652549498389)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412114147445.png)]

1.order的application
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

#开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
2.主启动

@EnableDiscoveryClient
@EnableFeignClients

@SpringBootApplication
@ComponentScan("com.xian")
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.xian.eduorder.mapper")
public class OrdersApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrdersApplication.class,args);
    }
}
3.client

注解:@Component @FeignClient(“service-edu”)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxyy7zQW-1652549498389)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412114400990.png)]

@Component
@FeignClient("service-edu")
public interface EduClient {
    //根据课程id获取课程信息
    @PostMapping("/eduservice/coursefront/getCourseInfoOrder/{id}")
    public CourseWebVoOrder getCourseInfoOrder(@PathVariable("id") String id);
}
@Component
@FeignClient("service-ucenter")
public interface UcenterClient {
    //根据用户id获取用户信息
    @PostMapping("/educenter/member/getUserInfoOrder/{id}")
    public UcenterMemberOrder getUserInfoOrder(@PathVariable("id") String  id);
}

2.开发获取订单接口( 根据订单号 查询订单信息)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAHw7KLK-1652549498389)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412115128261.png)]

点击立即购买调用 根据订单号查询订单信息 @GetMapping(“getOrderInfo/{orderId}”) 接口返回订单表数据

1.controller
//根据订单号 查询订单信息
@GetMapping("getOrderInfo/{orderId}")
public R getOrderInfo(@PathVariable String orderId){
    QueryWrapper<Order> wrapper = new QueryWrapper<>();
    wrapper.eq("order_no",orderId);
    Order order = orderService.getOne(wrapper);
    return R.ok().data("item",order);
}

3.点击“去支付”,会跳转到支付页面,生成微信扫描的二维码

1.controller

:需要传入orderNo订单号

@RestController
@RequestMapping("/eduorder/paylog")
@CrossOrigin
public class PayLogController {
    @Autowired
    private PayLogService payLogService;

    //生成微信支付二维码
    @GetMapping("createNative/{orderNo}")
    public R createNative(@PathVariable("orderNo")String orderNo){
        Map map=payLogService.createNative(orderNo);
        System.out.println("******生成微信支付二维码map"+map);
        return R.ok().data(map);
    }
2.serviceImpl

:需要获取订单信息 则要注入OrderService

:需要用到HttpClient工具类

    @Autowired
    private OrderService orderService;

    //生成微信支付二维码
    @Override
    public Map createNative(String orderNo) {
        try {
            //查询订单信息
            QueryWrapper<Order> wrapper = new QueryWrapper<>();
            wrapper.eq("order_no",orderNo);
            Order order = orderService.getOne(wrapper);

            //封装数据
            HashMap<String, String> m = new HashMap<>();
            //1、设置支付参数
            m.put("appid", "wx74862e0dfcf69954");
            m.put("mch_id", "1558950191");
            m.put("nonce_str", WXPayUtil.generateNonceStr());
            m.put("body", order.getCourseTitle());//课程名称
            m.put("out_trade_no", orderNo);//订单号
            m.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue()+"");//价格
            m.put("spbill_create_ip", "127.0.0.1");
            m.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify");
            m.put("trade_type", "NATIVE");

            //发送请求
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            client.setHttps(true);
            client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
            client.post();
            //得到的也是xml格式
            String xml = client.getContent();
            //转换为map
            Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);

            //返回完整信息
            Map map = new HashMap<>();
            map.put("out_trade_no", orderNo);//返回订单号
            map.put("course_id", order.getCourseId());//课程id
            map.put("total_fee", order.getTotalFee());//订单金额
            map.put("result_code", resultMap.get("result_code"));
            map.put("code_url", resultMap.get("code_url"));//二维码地址
            return map;
        } catch (Exception e) {
            throw new GuliException(20001,"createNative出错");
        }
    }
HttpClient工具类
package com.xian.eduorder.utils;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * http请求客户端
 * 
 * @author qy
 * 
 */
public class HttpClient {
	private String url;
	private Map<String, String> param;
	private int statusCode;
	private String content;
	private String xmlParam;
	private boolean isHttps;

	public boolean isHttps() {
		return isHttps;
	}

	public void setHttps(boolean isHttps) {
		this.isHttps = isHttps;
	}

	public String getXmlParam() {
		return xmlParam;
	}

	public void setXmlParam(String xmlParam) {
		this.xmlParam = xmlParam;
	}

	public HttpClient(String url, Map<String, String> param) {
		this.url = url;
		this.param = param;
	}

	public HttpClient(String url) {
		this.url = url;
	}

	public void setParameter(Map<String, String> map) {
		param = map;
	}

	public void addParameter(String key, String value) {
		if (param == null)
			param = new HashMap<String, String>();
		param.put(key, value);
	}

	public void post() throws ClientProtocolException, IOException {
		HttpPost http = new HttpPost(url);
		setEntity(http);
		execute(http);
	}

	public void put() throws ClientProtocolException, IOException {
		HttpPut http = new HttpPut(url);
		setEntity(http);
		execute(http);
	}

	public void get() throws ClientProtocolException, IOException {
		if (param != null) {
			StringBuilder url = new StringBuilder(this.url);
			boolean isFirst = true;
			for (String key : param.keySet()) {
				if (isFirst)
					url.append("?");
				else
					url.append("&");
				url.append(key).append("=").append(param.get(key));
			}
			this.url = url.toString();
		}
		HttpGet http = new HttpGet(url);
		execute(http);
	}

	/**
	 * set http post,put param
	 */
	private void setEntity(HttpEntityEnclosingRequestBase http) {
		if (param != null) {
			List<NameValuePair> nvps = new LinkedList<NameValuePair>();
			for (String key : param.keySet())
				nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
			http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
		}
		if (xmlParam != null) {
			http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
		}
	}

	private void execute(HttpUriRequest http) throws ClientProtocolException,
			IOException {
		CloseableHttpClient httpClient = null;
		try {
			if (isHttps) {
				SSLContext sslContext = new SSLContextBuilder()
						.loadTrustMaterial(null, new TrustStrategy() {
							// 信任所有
							public boolean isTrusted(X509Certificate[] chain,
									String authType)
									throws CertificateException {
								return true;
							}
						}).build();
				SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
						sslContext);
				httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
						.build();
			} else {
				httpClient = HttpClients.createDefault();
			}
			CloseableHttpResponse response = httpClient.execute(http);
			try {
				if (response != null) {
					if (response.getStatusLine() != null)
						statusCode = response.getStatusLine().getStatusCode();
					HttpEntity entity = response.getEntity();
					// 响应内容
					content = EntityUtils.toString(entity, Consts.UTF_8);
				}
			} finally {
				response.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			httpClient.close();
		}
	}

	public int getStatusCode() {
		return statusCode;
	}

	public String getContent() throws ParseException, IOException {
		return content;
	}

}


4.有二维码之后需要查询是否支付 前端调用接口时每三秒访问一次 成功支付后在后端pay表插入数据 并在order表设置支付成功状态

流程前端判断response.data.success

 methods:{
         queryOrderStatus(orderNo) {
             ordersApi.queryPayStatus(orderNo)
                .then(response => {
                     if (response.data.success) {
                        //支付成功,清除定时器
                        clearInterval(this.timer1)
                        //提示
                        this.$message({
                            type: 'success',
                            message: '支付成功!'
                        })
                        //跳转回到课程详情页面
                        this.$router.push({path: '/course/' + this.payObj.course_id})
                     }
                })
         }
     }

后端返回

:response.data.success对应

:success=true, code=20000, message=成功, data={};

    public static R ok(){
        R r = new R();
        r.setCode(ResultCode.SUCCESS);
        r.setSuccess(true);
        r.setMessage("成功");
        return r;
    }
1.controller

:需要写两个方法

//查询订单订单状态
Map<String, String> map=payLogService.queryPayStatus(orderNo);

:返回的key:“trade_state” value:为"SUCCESS"就表示支付成功

//向数据库添加数据
payLogService.updateOrderStatus(map);

:二维码支付成功后需要在pay表中插入数据

:把上面成功的map传入参数

//根据订单号查询状态
@GetMapping("queryPayStatus/{orderNo}")
public R queryPayStatus(@PathVariable("orderNo") String orderNo){
    //查询订单订单状态
    Map<String, String> map=payLogService.queryPayStatus(orderNo);
    System.out.println("******查询支付状态map"+map);
    if (map==null){
        return R.error().message("支付出错");
    }
    if (map.get("trade_state").equals("SUCCESS")){
        //向数据库添加数据
        payLogService.updateOrderStatus(map);
        return R.ok().message("");
    }
    return R.ok().code(25000).message("支付中");
}
1.serviceImpl
//查询订单订单状态
Map<String, String> queryPayStatus(String orderNo);
//查询订单订单状态
    @Override
    public Map<String, String> queryPayStatus(String orderNo) {
        try {
            //1、封装参数
            Map m = new HashMap<>();
            m.put("appid", "wx74862e0dfcf69954");
            m.put("mch_id", "1558950191");
            m.put("out_trade_no", orderNo);//订单号
            m.put("nonce_str", WXPayUtil.generateNonceStr());

            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            client.setHttps(true);
            client.setXmlParam(WXPayUtil.generateSignedXml(m,"T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
            client.post();

            String xml = client.getContent();
            Map<String, String> map = WXPayUtil.xmlToMap(xml);//返回map
            return map;
        } catch (Exception e) {
            return null;
        }
    }

    //向数据库添加数据
    void updateOrderStatus(Map<String, String> orderNo);

成功后向数据表pay添加数据

:把上面成功的map传入参数

:因为是pay表的service 所以需要注入order表的service

 //向数据库添加数据
    @Autowired
    private OrderService orderService;

  //向数据库添加数据
    @Override
    public void updateOrderStatus(Map<String, String> map) {
        //获取订单id
        String orderNo = map.get("out_trade_no");

        //根据orderId查询订单信息
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("order_no",orderNo);
        Order order = orderService.getOne(wrapper);

        if (order.getStatus()==1){//Status==1表示已支付 且 以向支付表添加过数据
            return;
        }
        order.setStatus(1);//设置状态1 为以支付
        orderService.updateById(order);//修改数据库


        PayLog payLog = new PayLog();//在pay表添加一条数据
        payLog.setOrderNo(order.getOrderNo());//支付订单号
        payLog.setPayTime(new Date());
        payLog.setPayType(1);//支付类型
        payLog.setTotalFee(order.getTotalFee());//总金额(分)
        payLog.setTradeState(map.get("trade_state"));//支付状态
        payLog.setTransactionId(map.get("transaction_id"));
        payLog.setAttr(JSONObject.toJSONString(map));//把map转换为json传入
        baseMapper.insert(payLog);
    }

5.在购买完成后需要需要根据课程id用户id查询是否购买购买 显示立即观看 没有需要购买

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ejlpMOYP-1652549498390)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412150902300.png)]

重点:因为是前台课程显示所以需要写在下面(课程详情接口中)位置在远程调用service-order中的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHzMyAsR-1652549498390)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412151246201.png)]

1.CourseFrontController的@GetMapping(“getFrontCourseInfo/{courseInfo}”) public R getFrontCourseInfo接口

:因为需要用户的id 所以需要传入HttpServletRequest request

    @Autowired
    private OrdersClient ordersClient;

//课程详情方法
@GetMapping("getFrontCourseInfo/{courseInfo}")
public R getFrontCourseInfo(@PathVariable("courseInfo")String courseId, HttpServletRequest request){
    //根据课程id查询课程信息  使用sql
    CourseWebVo courseWebVo=courseService.getBaseCourseInfo(courseId);
    //根据课程id查询章节和小节
    List<ChapterVo> chapterVideoList = chapterService.getChapterVideoByCourseId(courseId);

    //根据课程id和用户id查询支付状态
    boolean course = false;
    String memberIdByJwtToken = JwtUtils.getMemberIdByJwtToken(request);
    if (!StringUtils.isEmpty(memberIdByJwtToken)){
         course = ordersClient.isBuyCourse(courseId, JwtUtils.getMemberIdByJwtToken(request));
    }

    return R.ok().data("courseWebVo",courseWebVo).data("chapterVideoList",chapterVideoList).data("isBuy",course);
}
2.order client
@Component
@FeignClient("service-order")
public interface OrdersClient {
    //根据课程id和用户id查询支付状态
    @GetMapping("/eduorder/order/isBuyCourse/{courseId}/{memberId}")
    public boolean isBuyCourse(@PathVariable("courseId") String courseId,
                               @PathVariable("memberId") String memberId);
}
3.service-order 中的接口
    //根据课程id和用户id查询支付状态
    @GetMapping("isBuyCourse/{courseId}/{memberId}")
    public boolean isBuyCourse(@PathVariable("courseId") String courseId,
                         @PathVariable("memberId") String memberId){
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id",courseId);//课程id
        wrapper.eq("member_id",memberId);//会员id
        wrapper.eq("status",1);//支付成功
        int count = orderService.count(wrapper);
        if (count>0){
            return true;
        }else {
            return false;
        }
    }

25.统计数据图表显示

1.后端业务

1.统计某一天注册人数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QRz4M05Z-1652549498390)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412155110193.png)]

1.controller

:传入需要统计时间

    //统计某一天注册人数
    @PostMapping("registerCount/{day}")
    public R registerCount(@PathVariable("day") String day){
        statisticsDailyService.registerCount(day);
        return R.ok();
    }
2.serviceImpl

:因为统计每一天的数据 在表中每一条数据应该只有一条

    @Autowired
    private UcenterClient ucenterClient;

    //统计某一天注册人数
    @Override
    public void registerCount(String day) {
        //先删除当天数据
        QueryWrapper<StatisticsDaily> wrapper = new QueryWrapper<>();
        wrapper.eq("date_calculated",day);
        baseMapper.delete(wrapper);

        //远程调用
        R countRegister = ucenterClient.countRegister(day);
        Integer register = (Integer) countRegister.getData().get("countRegister");

        //把数据添加到数据库
        StatisticsDaily statisticsDaily = new StatisticsDaily();
        statisticsDaily.setRegisterNum(register);//统计人数
        statisticsDaily.setDateCalculated(day);//统计日期

        statisticsDaily.setLoginNum(RandomUtils.nextInt(100,200));
        statisticsDaily.setCourseNum(RandomUtils.nextInt(100,200));
        statisticsDaily.setVideoViewNum(RandomUtils.nextInt(100,200));
        baseMapper.insert(statisticsDaily);
    }
3.client
@Component
@FeignClient("service-ucenter")
public interface UcenterClient {
    //查询某一天注册人数
    @GetMapping("/educenter/member/countRegister/{day}")
    public R countRegister(@PathVariable("day")String day);
}
4.在service-ucenter中接口的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GpTQR680-1652549498390)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412160222136.png)]

controller
//查询某一天注册人数
@GetMapping("countRegister/{day}")
public R countRegister(@PathVariable("day")String day){
    Integer count=memberService.countRegisterDay(day);
    return R.ok().data("countRegister",count);
}
impl
    //查询某一天注册人数
    @Override
    public Integer countRegisterDay(String day) {
        Integer count=baseMapper.countRegisterDay(day);
        return count;
    }
mapper
//查询某一天注册人数
Integer countRegisterDay(@Param("day") String day);
mapper.xml

sql:语句 select count(*) from ucenter_member where DATE(gmt_create)=#{day};

查询#{day}天的

    <select id="countRegisterDay" resultType="java.lang.Integer">
        select count(*) from ucenter_member where DATE(gmt_create)=#{day};
    </select>

2.定时任务统计某一天注册人数

:输入时间 通过远程调用 使用时间查询(ucenter_member表)注册人数 并把人数插入到statistics_daily表中

1.主启动类

@EnableScheduling 定时任务注解

@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan("com.xian")
@MapperScan("com.xian.staservice.mapper")
@EnableFeignClients
@EnableScheduling
public class StaApplication {
    public static void main(String[] args) {
        SpringApplication.run(StaApplication.class,args);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mxdhQSPp-1652549498390)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412161222824.png)]

ScheduledTask类

:每天凌晨一点执行 把前一天数据添加

@Component
public class ScheduledTask {
    @Autowired
    private StatisticsDailyService statisticsDailyService;
    //每天凌晨一点执行 把前一天数据添加
    @Scheduled(cron = "0 0 1 * * ?")
    public void schedule(){
        statisticsDailyService.registerCount(DateUtil.formatDate(DateUtil.addDays(new Date(), -1)));
    }
}

DateUtil.formatDate(DateUtil.addDays(new Date(), -1))获取前一天时间工具类

DateUtil工具类
package com.xian.staservice.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 日期操作工具类
 *
 * @author qy
 * @since 1.0
 */
public class DateUtil {

    private static final String dateFormat = "yyyy-MM-dd";

    /**
     * 格式化日期
     *
     * @param date
     * @return
     */
    public static String formatDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        return sdf.format(date);

    }

    /**
     * 在日期date上增加amount天 。
     *
     * @param date   处理的日期,非null
     * @param amount 要加的天数,可能为负数
     */
    public static Date addDays(Date date, int amount) {
        Calendar now =Calendar.getInstance();
        now.setTime(date);
        now.set(Calendar.DATE,now.get(Calendar.DATE)+amount);
        return now.getTime();
    }

    public static void main(String[] args) {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
        System.out.println(DateUtil.formatDate(new Date()));
        System.out.println(DateUtil.formatDate(DateUtil.addDays(new Date(), -1)));
    }
}

3.传入查询开始结束时间 以及条件因子 返回 时间 与对应条件数据

1.controller
    //传入查询开始结束时间 以及条件因子
    //返回 时间 与对应条件数据

    /**
     *
     * @param type 需要统计的因素 如注册人数 登录人数。。。
     * @param begin 开始时间
     * @param end 结束时间
     * @return
     */
    @GetMapping("showData/{type}/{begin}/{end}")
    public R showDate(@PathVariable("type") String type,
                      @PathVariable("begin") String begin,
                      @PathVariable("end") String end){
        Map<String, Object> map=statisticsDailyService.getShowDate(type,begin,end);
        return R.ok().data(map);
    }
2.serviceImpl
 @Override
    public Map<String, Object> getShowDate(String type, String begin, String end) {
        QueryWrapper<StatisticsDaily> wrapper = new QueryWrapper<>();
        //在statistics_daily表中查询出begin,end之间的"date_calculated",type数据 type表中属性
        wrapper.between("date_calculated",begin,end);
        wrapper.select("date_calculated",type);
        List<StatisticsDaily> list = baseMapper.selectList(wrapper);

        //返回时间list
        List<String> dateList = new ArrayList<>();
        //返回数据list
        ArrayList<Integer> dataList = new ArrayList<>();

        //对list遍历
        for (int i = 0; i < list.size(); i++) {
            StatisticsDaily daily = list.get(i);
            //向dateList设置时间数据
            dateList.add(daily.getDateCalculated());
            //判断type 在向dataList添加数据
            switch(type){
                case "login_num":
                    dataList.add(daily.getLoginNum());
                    break;
                case "register_num":
                    dataList.add(daily.getRegisterNum());
                    break;
                case "video_view_num":
                    dataList.add(daily.getVideoViewNum());
                    break;
                case "course_num":
                    dataList.add(daily.getCourseNum());
                    break;
                default:
                    break;
            }
        }

        HashMap<String, Object> map = new HashMap<>();
        map.put("date_calculatedList",dateList);
        map.put("numDataList",dataList);
        return map;
    }
3.测试返回结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pEfZ6YTY-1652549498391)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220412163956227.png)]

{
  "success": true,
  "code": 20000,
  "message": "成功",
  "data": {
    "date_calculatedList": [
      "2018-12-28",
      "2018-12-29",
      "2018-12-30",
      "2018-12-31",
      "2019-01-01",
      "2019-01-02",
      "2019-01-03"
    ],
    "numDataList": [
      0,
      34,
      34,
      34,
      0,
      0,
      0
    ]
  }
}

26.gateway网关

1.GateWay

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSTgY8do-1652549498391)(D:\02谷粒学院-资料\资料\笔记课件\day17\随堂笔记\05 Gateway网关.png)]

2.负载均衡:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3bgG7oq-1652549498391)(D:\02谷粒学院-资料\资料\笔记课件\day17\随堂笔记\06 Gateway实现负载均衡.png)]

27.mapper加载问题

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/xian/deucms/mapper/xml/*.xml

28.swagger

<!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
package com.xian.dockerboot.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @auther zzyy
 * @create 2021-05-01 16:18
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig
{
    @Value("${spring.swagger2.enabled}")
    private Boolean enabled;

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xian.docker")) //你自己的package
                .paths(PathSelectors.any())
                .build();
    }

    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("尚硅谷Java大厂技术"+"\t"+new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
                .description("docker-compose")
                .version("1.0")
                .termsOfServiceUrl("https://www.atguigu.com/")
                .build();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3MxaOMD-1652549498391)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220415102321770.png)]

其他项目想用的话者需要引入以下pom

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yXxoidE-1652549498392)(C:\Users\19845\AppData\Roaming\Typora\typora-user-images\image-20220415102559891.png)]

29.设置insert upload 时间类

@Component
public class MyMetaObjectHandler 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);
    }
}

30统一返回结果R

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

@Data
public class R {
    @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 R(){
    }

    public static R ok(){
        R r = new R();
        r.setCode(ResultCode.SUCCESS);
        r.setSuccess(true);
        r.setMessage("成功");
        return r;
    }

    public static R error(){
        R r = new R();
        r.setCode(ResultCode.ERROR);
        r.setSuccess(false);
        r.setMessage("失败");
        return r;
    }
    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R code(Integer code){
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

public interface ResultCode {
    public static Integer SUCCESS = 20000;

    public static Integer ERROR = 20001;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值