项目开发
1. 开源工具的优缺点选择和抽象方法的建议
简介:讲解开源工具的好处和弊端,如pageHeper分页拦截器,tk自动生成工具,抽象方法的利弊等
1、开源工具
好处:开发方便,使用简单,使用aop方式进行分页,只需要引入相关依赖,然后PageHelper.startPage(page, size); 开启分页
弊端:对于分库分表等情况下使用有问题,深度分页逻辑判断会复杂
mysql资料:
- 深度分页常用案例:
https://www.cnblogs.com/lpfuture/p/5772055.html
https://blog.csdn.net/li772030428/article/details/52839987 - 推荐书籍:
https://book.douban.com/subject/23008813/
2、封装的好坏
关于抽象和不抽象的选择,比如tk这些工具,通用mapper,service,controller
好处:
- 代码量大大减少,开发新模块可以马上进行使用
弊端:
- 对应过度封装,新手等比较难理解,不能保证团队里面所有人都有对应的水平,或者有高度封装的思想,也不是过度封装
课程案例:
- 分页采用pageHelper
- 封装通用工具类,如缓存操作等
- 利于解耦,如切换缓存框架
2. 基础工程搭建(普通的maven工程)
2.1. 创建一个api工程
所有的实体类以及接口放在该工程中
Pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.guangyang.xdclass</groupId>
<artifactId>api</artifactId>
<version>1.0</version>
<parent>
<artifactId>parent</artifactId>
<groupId>com.guangyang.xdclass</groupId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
2.2. 创建一个parent工程
用于进行版本控制
1、这个parent工程是为了保证项目中所有依赖的版本一致性,新建其他子项目的时候直接继承这个项目即可
在gmall-parent工程中定义好技术框架的各种版本
这个是管理整个项目的版本号,需要打包成pom
gmall-parent不存储任何的jar包只负责管理版本号
2、注意事项
在子项目中不是引入这个依赖其他后,其他任何的依赖都不引入,这个工程只是对版本的控制,版本的控制,版本的控制,重要的事情说三遍,子项目还是正常引入依赖只是不用写版本号了,版本号由这个项目进行管理。
pom.xml中需要添加如下
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.guangyang.xdclass</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<commons-lang3.version>3.7</commons-lang3.version>
<mybatis.version>2.1.1</mybatis.version>
</properties>
<dependencyManagement>
<dependencies>
<!--
commons-lang3是用于针对java.lang类中数据基本类型的处理工具包
比如判断字符串是否为空if(StringUtils.isblank(s))
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.3. 创建一个service-util
服务层用到的工具类,以及将工具类整合到springboot中所需要的配置类,都将放在该工程中
2.4. 创建web-util
控制层用到的工具类,以及将工具类整合到springboot中所需要的配置类,都将放在该工程中
2.5. 创建一个common-util
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.guangyang.xdclass</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
<parent>
<artifactId>parent</artifactId>
<groupId>com.guangyang.xdclass</groupId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
控制层和服务层共同使用的工具类
3. IDEA热部署
1、 需要引入的依赖,放到common-util中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
idea里面要设置
1、相关偏好里开启自动编译
2、Shift+Ctrl+Alt+/,选择Registry
选 compiler.automake.allow.when.app.running
重启项目就可以了
参考
https://www.cnblogs.com/aqsunkai/p/6690574.html
4. Mysql逆向工程神器之使用IDEA自动生成实体类
实战使用IDE根据Mysql自动生成java pojo实体类
1、IDEA连接数据库
菜单View→Tool Windows→Database打开数据库工具窗口
2、 生成POJOS.groovy文件
3、 修改POJOS.groovy文件,配置生成的实体类的包路径以及属性类型
4、 生成实体类
5. 接口配置文件自动映射到属性和实体类配置
使用@value注解配置文件自动映射到属性和实体类
1、 添加 @Component或者Configuration 注解;
2、 使用 @PropertySource 注解指定配置文件位置;(属性名称规范: 大模块.子模块.属性名)
#=================================微信相关==================
#公众号
wxpay.appid=wx5beac15ca207cdd40c
wxpay.appsecret=554801238f17fdsdsdd6f96b382fe548215e9
3、必须 通过注入IOC对象Resource 进来 , 才能在类中使用获取的配置文件值。
@Autowired
private WeChatConfig weChatConfig;
示例
@Configuration
@PropertySource(value="classpath:application.properties")
public class WeChatConfig {
@Value("${wxpay.appid}")
private String appId;
}
6. 整合mybatis访问数据库和阿里巴巴数据源
1、 整合mysql 加入mybatis依赖,和加入alibaba druid数据源
<!-- 引入starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL的JDBC驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入第三方数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
2、 加入配置文件
spring:
datasource:
username: guangyang
password: SRNDxrhc=1314
url: jdbc:mysql://192.168.1.200:3306/gmall?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
# 使用我们自己的druid数据源
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 10 #初始化连接个数
minIdle: 5 #最小连接个数
maxActive: 500 #最大连接个数
maxWait: 60000 #最大等待时间
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
# config-location和configuration不能同时配置,否则会抛出异常
# 一般只配置configuration即可,会自动找到mybatis全局配置文件
# config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mapper/*Mapper.xml
# 对应实体类的路径,只能指定具体的包,多个配置可以使用英文逗号隔开
type-aliases-package: com.qinglingwang.gmall.user.entity
configuration:
# Mybatis SQL语句控制台打印
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰命名规则
# 在数据库中字段可以采用驼峰命名规则,mybatis会把 下划线去掉并把下划线后面的首字母认为是大写
map-underscore-to-camel-case: true
3、 启动类增加mapper扫描
@MapperScan("com.guangyang.xdclass.test.mapper")
4、 创建mapper文件
VideoMapper类例子
@Select("SELECT * FROM video")
@Results({
@Result(column = "create_time",property = "createTime") //javaType = java.util.Date.class
})
List<Video> getAll();
7. 使用Mybatis注解开发视频列表增删改查
讲解:使用Mybatis3.x注解方式 增删改查实操, 控制台打印sql语句
1、控制台打印sql语句
#增加打印sql语句,一般用于本地开发测试
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2、增加mapper代码
@Select("select * from video")
// @Results({
// @Result(column = "cover_img",property =
"coverImg"),
// @Result(column = "create_time",property =
"createTime")
// })
List<Video> findAll();
@Select("SELECT * FROM video WHERE id = #{id}")
Video findById(int id);
@Update("UPDATE video SET title=#{title} WHERE id =#{id}")
int update(Video Video);
@Delete("DELETE FROM video WHERE id =#{id}")
int delete(int id);
@Insert("INSERT INTO `video` ( `title`, `summary`, " +
"`cover_img`, `view_num`, `price`, `create_time`," +
" `online`, `point`)" +
"VALUES" +
"(#{title}, #{summary}, #{coverImg}, #{viewNum}, #{price},#{createTime}" +
",#{online},#{point});")
@Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
int save(Video video);
3、保存保存
技巧:保存对象,获取数据库自增id
@Options(useGeneratedKeys=true, keyProperty=“id”, keyColumn=“id”)
4、技巧:
数据库字段下划线和Java实体类映射
mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true
8. 使用Mybatis 的xml完成增删改查
1、 配置文件
mybatis:
# config-location和configuration不能同时配置,否则会抛出异常
# 一般只配置configuration即可,会自动找到mybatis全局配置文件
# config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mapper/*Mapper.xml
# 对应实体类的路径,只能指定具体的包,多个配置可以使用英文逗号隔开
type-aliases-package: com.guangyang.xdclass.bean
configuration:
# Mybatis SQL语句控制台打印
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰命名规则
# 在数据库中字段可以采用驼峰命名规则,mybatis会把 下划线去掉并把下划线后面的首字母认为是大写
map-underscore-to-camel-case: true
2、 在resources中创建mapper目录,将VideoMapper.xml文件放在该目录下
基本结构
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.guangyang.xdclass.test.mapper.VideoMapper">
</mapper>
查找所有的Video实体类
<select id="findVideoById" parameterType="String" resultType="com.guangyang.xdclass.bean.Video">
select * from `video` where id = #{id}
</select>
插入一个对象到数据库中
<insert id="saveVideo" parameterType="com.guangyang.xdclass.bean.Video" >
INSERT INTO video (
title,
summary,
cover_img,
view_num,
price,
create_time,
online,
point
)
VALUES
(
#{title},
#{summary},
#{coverImg},
#{viewNum},
#{price},
#{createTime},
#{online},
#{point}
)
</insert>
注意事项:#{对象属性名,不是数据库中的字段名} ,数据库中的字段没有单引号
更新与删除操作
<update id="updateVideo" parameterType="com.guangyang.xdclass.bean.Video" >
update video set title = #{title} where id = #{id}
</update>
<delete id="deleteVideoById" parameterType="com.guangyang.xdclass.bean.Video">
delete from video where id = #{id}
</delete>
9. 搭建video微服务
1、创建xdclass-video-web以及xdclass-video-service服务
2、引入dubbo框架,实现项目的分布式架构,后期要改成使用使用springcloud
# =========================================== dubbo配置扫描 ===================================
# dubbo中的服务名称,容器名称,一般就是项目名称
dubbo.application.name=xdclass-video-web
# dubbo的通讯协议名称
dubbo.protocol.name=dubbo
# zookeeper的通讯协议名称
dubbo.registry.protocol=zookeeper
#zookeeper注册中心的地址
dubbo.registry.address=192.168.1.200:2181
dubbo.scan.base-packages=com.guangyang.xdclass
#设置消费者与服务提供者连接超时时间(ms)
dubbo.consumer.timeout=600000
#设置在启动的时候是否检查服务存在
dubbo.consumer.check=false
#暴露服务端口(默认端口是20880,修改端口,不同的服务提供者端口不能重复)
dubbo.protocol.port=20880
3、 引入duboo依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<dubbo.version>2.6.5</dubbo.version>
<zkclient.version>0.10</zkclient.version>
<dubbo-starter.version>0.2.1.RELEASE</dubbo-starter.version>
<curator-framework-version>2.12.0</curator-framework-version>
</properties>
<!--整合dubbo需要以下4个依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- zookeeper的客户端jar依赖包 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>${zkclient.version}</version>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo-starter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator-framework-version}</version>
</dependency>
9.1. 配置文件存放问题
Springboot会扫描主函数的目录下所有的文件(包括maven模块),但是不会扫描maven模块中的xml配置文件,xml配置文件需要放在当前springboot模块下
9.2. 日志报错
在common-util中引入的依赖为
<!--===========================整合dubbo需要以下4个依赖============================-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<!--dubbo-springBoot依赖-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- zookeeper的客户端jar依赖包 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
需要排除slf4j-log4j12否则启动项目时候,日志会报错
9.3. Video项目结构
对视频的删除以及保存和更新操作都是管理员才能做的事情,普通用户无法操作,后期会对权限进行管理。
10. 日志配置
# =========================================== 日志配置 ===================================
logging.level.com.atguigu=info
#logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
#logging.file=G:/springboot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.file.path=/var/log
# 在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
11. Vidoe相关接口完善和规范协议
@Controller
public class VideoController {
@Reference
VideoService videoService;
/**
* 分页接口
* @param page 第几页,默认第1页
* @param size 一页多少条,默认10条
* @return
*/
@GetMapping("page")
@ResponseBody
public Video page(@RequestParam(value = "page" , defaultValue = "1") int page,
@RequestParam(value = "size" , defaultValue = "10") int size) {
return null;
}
/**
* 通过id查找视频接口
* @param videoId 视频id
* @return
*/
@GetMapping("find_by_id")
@ResponseBody
public Video findVideoById(@RequestParam(value = "video_id" , required = true)String videoId) {
return videoService.findVideoById(videoId);
}
/**
* 保持视频接口
* @param video
* @return
*/
@PostMapping("save")
@ResponseBody
public int saveVideo(Video video) {
return videoService.saveVideo(video);
}
/**
* 通过id更新视频接口
* @param video
* @return
*/
@PutMapping("update_video")
@ResponseBody
public int updateVideo(@RequestBody Video video) {
return videoService.updateVideo(video);
}
/**
* 通过id删除视频接口
* @param videoId 视频id
* @return
*/
@DeleteMapping("del_id")
@ResponseBody
public int deleteVideoById(@RequestParam(value = "video_id" , required = true)String videoId) {
return videoService.deleteVideoById(videoId);
}
}
删除视频以及报错视频都是管理员才有的权限,将这两个接口,放到controller/admin/AdminVideoController.java中
@Controller
@RequestMapping("/admin/video")
public class AdminVideoController {
@Reference
VideoService videoService;
/**
* 保持视频接口
* @param video
* @return
*/
@PostMapping("save")
@ResponseBody
public int saveVideo(Video video) {
return videoService.saveVideo(video);
}
/**
* 通过id更新视频接口
* @param video
* @return
*/
@PutMapping("update_video")
@ResponseBody
public int updateVideo(@RequestBody Video video) {
return videoService.updateVideo(video);
}
/**
* 通过id删除视频接口
* @param videoId 视频id
* @return
*/
@DeleteMapping("del_id")
@ResponseBody
public int deleteVideoById(@RequestParam(value = "video_id" , required = true)String videoId) {
return videoService.deleteVideoById(videoId);
}
}
12. 动态Sql语句Mybaties SqlProvider
简介:讲解什么是动态sql,及使用
动态sql:当从前端传到后台一个对象时候,里面的属性有的有值有的没有值,这时候我们只希望更新有值的,没有值的不要给我进行更新
1、
@UpdateProvider(type=VideoSqlProvider.class,method="updateVideo") 更新
@InsertProvider 插入
@DeleteProvider 删除
@SelectProvider 查询
2、写法
public String updateVideo(final Video video){
return new SQL(){{
UPDATE("video");
//条件写法.
if(video.getAuthorId()!= null){
SET("author_id=#{authorId}");
}
if(video.getTotalEpisode()!= null){
SET("total_episode=#{totalEpisode}");
}
WHERE("id=#{id}");
}}.toString();
}
12.1. 在api模块中创建provider类
12.2. 反射工具类
按照上面的方法判断类中属性值是否为空,太麻烦了,在这里使用反射机制获取类中的属性和值,工具类如下
package com.guangyang.xdclass.utils;
import com.guangyang.xdclass.bean.Video;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* 反射工具类
*/
public class ReflectionUtil {
/**
* 返回一个类中所有的属性(Attributes)以及值
* 返回值中是一个List集合,集合中是Map集合,map集合只包含两个键值对
* name --- 属性名
* value --- 属性值
* @param object
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
List<Map<String,Object>> getAttributesAndValue(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<Map<String,Object>> list = new ArrayList<>();
// 获取实体类的所有属性,返回Field数组
Field[] field = object.getClass().getDeclaredFields();
for(int i = 0; i < field.length;i++){
Map<String,Object> map = new HashMap<>();
// 获取属性的名字
String name = field[i].getName();
System.out.println(name);
map.put("name",name);
// 将属性的首字母大写
name = name.replaceFirst(name.substring(0,1),name.substring(0,1).toUpperCase());
// 获取属性类型名字
String type = field[i].getGenericType().toString();
System.out.println(type);
if (type.equals("class java.lang.String"))
{
// 如果type是类类型,则前面包含"class ",后面跟类名
Method m = object.getClass().getMethod("get" + name);
// 调用getter方法获取属性值
String value = (String) m.invoke(object);
map.put("value",value);
}
if (type.equals("class java.lang.Integer"))
{
Method m = object.getClass().getMethod("get" + name);
Integer value = (Integer) m.invoke(object);
map.put("value",value);
}
if (type.equals("class java.lang.Short"))
{
Method m = object.getClass().getMethod("get" + name);
Short value = (Short) m.invoke(object);
map.put("value",value);
}
if (type.equals("class java.lang.Double"))
{
Method m = object.getClass().getMethod("get" + name);
Double value = (Double) m.invoke(object);
map.put("value",value);
}
if (type.equals("class java.lang.Boolean"))
{
Method m = object.getClass().getMethod("get" + name);
Boolean value = (Boolean) m.invoke(object);
map.put("value",value);
}
if (type.equals("class java.util.Date"))
{
Method m = object.getClass().getMethod("get" + name);
Date value = (Date) m.invoke(object);
map.put("value",value);
}
list.add(map);
}
return list;
}
// /**
// * 测试反射,获取类中的所有属性和值
// */
// public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
// Video video = new Video();
// video.setCoverImg("哈哈哈");
// video.setPrice("1234");
// List<Map<String, Object>> list = getAttributesAndValue(video);
// for (Map<String, Object> stringObjectMap : list) {
// System.out.println(stringObjectMap.get("name") + " = " + stringObjectMap.get("value"));
// }
// }
}
12.3. 使用反射工具类写的动态sql方法
public class VideoSqlProvider {
public String updateVideo(final Video video){
return new SQL(){{
UPDATE("video");
//条件写法. 这块使用反射机制获取属性名和属性值,并且使用谷歌的驼峰转换工具进行
// 相应的转换
try {
List<Map<String, Object>> attributesAndValue = ReflectionUtil.getAttributesAndValue(video);
for (Map<String, Object> stringObjectMap : attributesAndValue) {
if (stringObjectMap.get("value") != null) {
String name = (String) stringObjectMap.get("name");
// 使用谷歌的驼峰转换工具进行转换,比如属性名为testDate,转换结果为test_date
String caseFormat = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name);
SET(caseFormat + "=#{" + name + "}");
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
WHERE("id=#{id}");
}}.toString();
}
}
12.4. 修改mapper接口
@UpdateProvider(type = VideoProvider.class,method = "updateVideo")
int update(Video Video);
13. PageHelper分页插件使用
1、 基本原理
sqlsessionFactory -> sqlSession-> executor -> mybatis sql statement
通过mybatis plugin 增加拦截器,然后拼装分页
org.apache.ibatis.plugin.Interceptor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pEtuPdtd-1610709121074)(http://img.itvip666.com/blog-img/2021/01/05/1609814873525.png)]
2、 引入依赖
<!-- 分页插件依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.0</version>
</dependency>
3、 配置类
@Configuration
public class MyBatisConfig {
@Bean
public PageHelper pageHelper(){
PageHelper pageHelper = new PageHelper();
Properties p = new Properties();
p.setProperty("offsetAsPageNum","true");
p.setProperty("rowBoundsWithCount","true");
p.setProperty("reasonable","true");
pageHelper.setProperties(p);
return pageHelper;
}
}
4、 包装类
PageHelper.startPage(page, size);
PageInfo<VideoOrder> pageInfo = new PageInfo<>(list);
5、 服务层分页
@Override
public Map<String, Object> page(int page, int size) {
Map<String,Object> map = new HashMap<>();
PageHelper.startPage(page, size);
List<Video> list = videoMapper.findAll();
PageInfo<Video> videoPageInfo = new PageInfo<>(list);
map.put("total_size",videoPageInfo.getTotal()); // 获取总条数
map.put("total_page",videoPageInfo.getPages()); // 总页数
map.put("current_page",page); // 获取当前页
map.put("data",videoPageInfo.getList()); // 获取数据
return map;
}
13.1. 关于报错但不影响正常使用问题
1、 今天使用pagehelper的时候一直出现如下警告,但是系统依旧可以正常使用。
2、 原因:
项目里面使用的是dubbo进行分布式调用,只在服务层引入了pagehelper依赖,表现层没有引入pagehalper依赖,服务层查询到数据之后,先将数据序列化,在表现层需要进行反序列化,但是表现层没有引入pagehelper依赖,反序列化的时候找不到:com.github.pagehelper.Page这个包,所以报出了这个警告,但是项目依旧可以正常使用,因为Page类是ArrayList的子类,ArrayList类是可以找到的,所以依旧可以使用。
3、 解决办法:
在表现层引入pagehelper依赖
14. 单机和分布式应用的登录检验讲解
简介:讲解单机和分布式应用下登录校验,session共享,分布式缓存使用
1、单机tomcat应用登录检验
sesssion保存在浏览器和应用服务器会话之间
用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,
客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId
2、分布式应用中session共享
真实的应用不可能单节点部署,所以就有个多节点登录session共享的问题需要解决
1)tomcat支持session共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐
2)使用redis存储token:
服务端使用UUID生成随机64位或者128位token,放入redis中,然后返回给客户端并存储在cookie中,用户每次访问都携带此token,服务端去redis中校验是否有此用户即可(也有缺点,不太推荐)
15. 微服务下登录检验
简介:微服务下登录检验解决方案 JWT讲解 json wen token
1、JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。
JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名
简单来说,就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息
{
id:888,
name:'小D',
expire:10000
}
funtion 加密(object, appsecret){
xxxx
return base64( token);
}
function 解密(token ,appsecret){
xxxx
//成功返回true,失败返回false
}
优点:
1)生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
2)存储在客户端,不占用服务端的内存资源
缺点:
token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等
2、JWT格式组成 头部、负载、签名
header+payload+signature
头部:主要是描述签名算法。
负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户。
签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token。
3、关于jwt客户端存储
可以存储在cookie,localstorage和sessionStorage里面
16. Jwt通用工具类
package com.qinglingwang.gmall.util;
import io.jsonwebtoken.*;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
// 过期时间为一周
private static final long EXPIRE = 1000*60*60*24*7;
/**
* 加密(默认过期时间为一周)
* @param key 服务器公共密钥
* @param param 用户信息
* @param salt 盐值(ip+time)
* @return token
*/
public static String encode(String key, Map<String,Object> param, String salt){
if(salt!=null){
key+=salt;
}
JwtBuilder jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS256,key)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE ));
jwtBuilder = jwtBuilder.setClaims(param);
String token = jwtBuilder.compact();
return token;
}
/**
* 解密,salt是key的盐值
* @param token
* @param key
* @param salt
* @return
*/
public static Map<String,Object> decode(String token ,String key,String salt){
Claims claims=null;
if (salt!=null){
key+=salt;
}
try {
claims= Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
} catch ( JwtException e) {
return null;
}
return claims;
}
}
17. 微信登录
17.1. 数据信息安全–微信授权一键登录功能介绍
简介:讲解登录方式优缺点和微信授权一键登录功能介绍
1、手机号或者邮箱注册
优点:
1)企业获取了用户的基本资料信息,利于后续业务发展推送营销类信息
2)用户可以用个手机号或者邮箱获取对应的app福利注册送优惠券
3)反馈信息的时候方便,直接报手机号即可账户出问题,被盗等
缺点:
1)步骤多
2)如果站点不安全,如站点被攻击,泄漏了个人信息,如手机号,密码等
3)少量不良企业贩卖个人信息,如手机号
2、OAuth2.0一键授权登录
例子:
豆瓣:www.douban.com
小D课堂:www.xdclass.net
优点:
使用快捷,用户体验好,数据相对安全
缺点:
1、反馈问题麻烦,比较难知道唯一标识
2、如果是企业下面有多个应用,其中有应用不支持Auth2.0登录,则没法做到用户信息打通,积分不能复用等
如app接入了微信授权登录,但是网站没有,则打不通,
或者授权方只提供了一种终端授权,则信息无法打通,
3、选择方式:
1)看企业和实际业务情况
2)务必区分,普通密码和核心密码
17.2. 微信扫一扫开发前期准备
简介:讲解微信扫一扫功能相关开发流程和资料准备
1、 微信开放平台介绍(申请里面的网站应用需要企业资料)
网站:https://open.weixin.qq.com/
2、 什么是appid、appsecret、授权码code
appid和appsecret是 资源所有者向申请人分配的一个id和秘钥
code是授权凭证,A->B 发起授权,想获取授权用户信息,那a必须携带授权码,才可以向B获取授权信息
(你要从我这里拿东西出去,就必须带身份证)
3、 将appid以及appsecret还有开放平台的回调地址写入到配置文件中,当上线时候以便修改,需要使用配置类,使用springboot进行读取相关参数
@Configuration
@PropertySource(value="classpath:application.properties")
public class WeChatConfig {
/**
* 公众号的appid
*/
@Value("${wxpay.appid}")
private String appid;
/**
* 公众号的秘钥
*/
@Value("${wxpay.appsecret}")
private String appsecret;
/**
* 开放平台的appid
*/
@Value("${wxopen.appid}")
private String openAppid;
/**
* 开放平台的appsecret
*/
@Value("${wxopen.appsecret}")
private String openAppsecret;
/**
* 开放平台回调url
*/
@Value("${wxopen.redirect_url}")
private String openRedirectUrl;
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getAppsecret() {
return appsecret;
}
public void setAppsecret(String appsecret) {
this.appsecret = appsecret;
}
public String getOpenAppid() {
return openAppid;
}
public void setOpenAppid(String openAppid) {
this.openAppid = openAppid;
}
public String getOpenAppsecret() {
return openAppsecret;
}
public void setOpenAppsecret(String openAppsecret) {
this.openAppsecret = openAppsecret;
}
public String getOpenRedirectUrl() {
return openRedirectUrl;
}
public void setOpenRedirectUrl(String openRedirectUrl) {
this.openRedirectUrl = openRedirectUrl;
}
}
17.3. 微信Oauth2.0交互流程
参考:
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN
1、区分角色 用户,第三应用,微信开放平台:画图讲解
2、如果想看时序图知识,请跳转到微信支付章节,时序图知识讲解
17.4. 获取微信开放平台APPID/APPsecret
什么是域名映射: 就是当前开发机器的ip 通过和域名绑定,让外部网络可以通过域名就行访问到本地机器接口。
重点看这!!!! 由于Ngrock官方近期改版,导致不能使用。
课程用到ngrock的两个功能: 微信登录 + 微信支付(重点)
无奈微信开放平台对开发者不友好,一定要公司营业执照 + 300元认证认证,才可以认证微信开放平台接入微信登录。
建议:微信登录功能比较简单,可以看课程即可,课程代码也可以直接在自己项目中使用(小D课堂免费使用)实操的话需要使用自己公司认证的微信开发平台账号,如果自己公司有账号则可以直接使用,没的话建议是跳过。
下面的是可以测试使用,出现二维码,但是不能进行回调
狼途
AppID:wx025575eac69a2d5b
AppSecret:f5b6730c592ac15b8b1a5aeb8948a9f3
授权回调域名: http://16webtest.ngrok.xiaomiqiu.cn
建豪器械
AppID:wxe9ef2df5e786b62a
AppSecret:c973a39ce5d2638e95f251a893cdfac4
授权回调域名: http://xdclasstest2.ngrok.xiaomiqiu.cn
微信支付功能可以实操,也是重点功能,小D课堂单独开发了支付网关对接微信支付,
使用免费的域名映射工具,生成的是随机域名。
工具下载:https://natapp.cn/
工具使用文档:https://natapp.cn/article/natapp_newbie
支付回调域名可以自定义,微信支付接入详情
https://mp.weixin.qq.com/s?__biz=MzUyMDg1MDE2MA==&mid=2247484232&idx=1&sn=9adc2c399ae7093d0e9e85f05aaff0e4&chksm=f9e55cd7ce92d5c159e49ed42b6a77f2210434bb105cffcc9670cb24ad46cf4f49cbf7adaa96&mpshare=1&scene=23&srcid=1016NFMGqvbyyORetzSf7n54#rd
17.5. HttpClient4.x工具获取使用
简介:讲解httpClient4.x相关依赖,并封装基本方法。
1、加入依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<!-- gson工具,封装http的时候使用 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>
引入依赖注意事项,在springboot中已经对如下进行了版本控制,我们不需要在我们的自定义的父依赖parent中控制版本,直接在common-util中引入依赖即可(不需要填写版本号)
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
工具类
package net.xdclass.xdvideo.utils;
import com.google.gson.Gson;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 封装http get post
*/
public class HttpUtils {
private static final Gson gson = new Gson();
/**
* get方法
* @param url
* @return
*/
public static Map<String,Object> doGet(String url){
Map<String,Object> map = new HashMap<>();
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000) //连接超时
.setConnectionRequestTimeout(5000)//请求超时
.setSocketTimeout(5000)
.setRedirectsEnabled(true) //允许自动重定向
.build();
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
try{
HttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
String jsonResult = EntityUtils.toString( httpResponse.getEntity());
map = gson.fromJson(jsonResult,map.getClass());
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return map;
}
/**
* 封装post
* @return
*/
public static String doPost(String url, String data,int timeout){
CloseableHttpClient httpClient = HttpClients.createDefault();
//超时设置
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout) //连接超时
.setConnectionRequestTimeout(timeout)//请求超时
.setSocketTimeout(timeout)
.setRedirectsEnabled(true) //允许自动重定向
.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-Type","text/html; chartset=UTF-8");
if(data != null && data instanceof String){ //使用字符串传参
StringEntity stringEntity = new StringEntity(data,"UTF-8");
httpPost.setEntity(stringEntity);
}
try{
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if(httpResponse.getStatusLine().getStatusCode() == 200){
String result = EntityUtils.toString(httpEntity);
return result;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
}
2、封装工具类的使用
封装doGet doPost
17.6. 微信扫码登录回调本地域名映射工具Ngrock(现在不可用了)
简介:讲解微信扫码回调本地域名ngrock讲解
1、为什么要用这个,微信扫码需要配置回调,需要配置对应的域名在本地电脑开发,微信没法回调,所以需要配置个地址映射,就是微信服务器可以通过这个地址访问当前开发电脑的地址
2、使用文档:
https://mp.weixin.qq.com/s/oakwABv56Jc6u450AHt9iA
17.7. 微信扫码登录回调本地域名映射EchoSite
17.7.1. EchoSite配置
官网:https://www.echosite.cn/
1、 点击控制台,然后选择抢占域名,需要付费,一个月3元
2、 点击客户端下载,选择生成链接(需要下载客户端以及配置文件)
3、 修改配置文件
修改手机号以及秘钥
4、 启动echosite
echosite -config=config.yml start web_b
17.7.2. 配置nginx
upstream guangyang.easy.echosite.cn {
#映射到的ip地址以及端口号
server guangyang.easy.echosite.cn:80;
}
server {
listen 80;
# http://16webtest.ngrok.xiaomiqiu.cn/
# server_name需要配置成将要访问的域名
server_name 16webtest.ngrok.xiaomiqiu.cn;
location / {
# 配置域名,注意事项域名不要带www. 否则不好使
#指向上面配置的upstream www.passport.com
proxy_pass http://guangyang.easy.echosite.cn;
index index.html index.htm;
}
}
17.7.3. 本地域名解析配置
如果想要访问16webtest.ngrok.xiaomiqiu.cn映射到guangyang.easy.echosite.cn然后在映射到本地,你需要配置本地域名解析
127.0.0.1 16webtest.ngrok.xiaomiqiu.cn
这回当扫码登录成功后,最终会通过guangyang.easy.echosite.cn映射到本地