文章目录
- 1.逆向工程 Mybatis-puls 代码生成器
- 2.解决返回格林时间问题
- 3.统一异常处理
- 4.**自定义异常**
- 5.Logback日志
- 6.axios重点
- 7.解决跨域
- 8.前端开发流程
- 9.讲师列表添加
- 10.从配置文件读取常量
- 11.上传文件到阿里云oss
- 12.课程分类 一二级目录
- 13.课程分类列表 树形结构
- 14.课程发布模块
- 15.整合hystrix
- 16.整合nacos
- 17.单点登陆sso
- 18.用邮箱发验证码 把验证码存入redis中设置5秒有效
- 19登陆功能
- 20:注册功能
- 21 tinyint
- 22微信二维码注册登陆
- 23谷粒表分析
- 24.支付模块
- 25.统计数据图表显示
- 26.gateway网关
- 27.mapper加载问题
- 28.swagger
- 29.设置insert upload 时间类
- 30统一返回结果R
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日志
- 删除application.properties中的日志配置
- 安装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:/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.流程
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>
<!-- <!–hystrix依赖,主要是用 @HystrixCommand –>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- <!–服务注册–>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- <!–服务调用–>-->
<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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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;
}