文章目录
java后端
jdbc
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/x_admin?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=1507298022
获取连接/关闭资源
// 需要fastjson包
public class JDBCUtil {
private static final ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
private static final String driver = bundle.getString("driver");
private static final String url = bundle.getString("url");
private static final String user = bundle.getString("username");
private static final String password = bundle.getString("password");
private static final ThreadLocal<Connection> conns = new ThreadLocal<>();
static {
// 注册驱动
try {
Class.forName(driver);
// System.out.println("注册驱动成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 创建连接
public static Connection getConnection() throws SQLException {
Connection connection = conns.get();
if (connection == null){
connection = DriverManager.getConnection(url, user, password);
// 将事务改为手动提交
connection.setAutoCommit(false);
conns.set(connection);
}
return connection;
}
// 关闭资源,事务提交
public static void close(){
// 获取操作数据的当前连接
Connection connection = conns.get();
if (connection != null){
try {
// 提交事务
connection.commit();
} catch (Exception e) {
try {
// 回滚数据
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 一定要执行 remove 操作,否则就会出错。(因为 Tomcat 服务器底层使用了线程池技术)
conns.remove();
}
}
执行sql
// 需要 commons-dbutils依赖
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
/**
* 查询所有
* @param sql sql语句
* @param condition 条件
* @param returnType 用于接收结果的实体类的class
* @return 影响条数
*/
protected List<T> query(String sql, List<Object> condition, Class<T> returnType){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 创建连接
connection = JDBCUtil.getConnection();
// 加载sql
preparedStatement = connection.prepareStatement(sql);
// 传值
for (int i = 0; i < condition.size(); i++) {
preparedStatement.setObject(i + 1, condition.get(i));
}
// 执行sql
resultSet = preparedStatement.executeQuery();
// 处理结果集
BeanListHandler<T> bh = new BeanListHandler<T>(returnType);
return bh.handle(resultSet);
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 关闭资源
JDBCUtil.close();
}
return null;
}
protected Integer update(String sql, List<Object> condition){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtil.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < condition.size(); i++) {
preparedStatement.setObject(i + 1, condition.get(i));
}
// 执行sql,可以是增删改操作
return preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.close();
}
return 0;
}
mybatis
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis的核心配置文件-->
<configuration>
<!--引入外部文件-->
<properties resource="jdbc.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/> <!--日志-->
<setting name="mapUnderscoreToCamelCase" value="true"/> <!--驼峰命名-->
</settings>
<typeAliases>
<!--方式二,通过引用包,直接使用类名调用,若实体类有注解@Alias("xxx"),则使用xxx调用-->
<package name="com.feng.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--每一个xxxMapper.xml文件都必须要配置到核心配置文件中-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
获取连接/关闭资源
public class MybatisUtil {
private static final ThreadLocal<SqlSession> conns = new ThreadLocal<>();
private static SqlSessionFactory sqlSessionFactory;
// 只需要一个工厂对象
static {
try {
// 拿到SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession sqlSession = conns.get();
if (sqlSession == null){
sqlSession = sqlSessionFactory.openSession();
conns.set(sqlSession);
return sqlSession;
}
return sqlSession;
}
public static void closeSqlSession(){
SqlSession sqlSession = conns.get();
if (sqlSession != null){
try{
sqlSession.commit(); // 提交事务
}catch (Exception e){
sqlSession.rollback(); // 回滚
}finally {
sqlSession.close(); // 关闭连接,可以不用关闭 或 者在数据库设置关闭时间
}
conns.remove();
}
}
}
资源导出失败问题
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
Tool
统一返回结果封装
public class ResultVo<T> {
private String msg;
private Integer code;
private Integer count;
private T data;
public static <T> ResultVo<T> success(T data){
return new ResultVo<T>(StatusCodeEnum.SUCCESS.getMsg(), StatusCodeEnum.SUCCESS.getCode(), 0, data);
}
public static <T> ResultVo<T> failed() {
return new ResultVo<T>(StatusCodeEnum.FAILED.getMsg(), StatusCodeEnum.FAILED.getCode(), 0, null);
}
public static <T> ResultVo<T> failed(T data) {
return new ResultVo<T>(StatusCodeEnum.FAILED.getMsg(), StatusCodeEnum.FAILED.getCode(), 0, data);
}
public ResultVo<T> message(String msg){
this.setMsg(msg);
return this;
}
public ResultVo<T> code(Integer code){
this.setCode(code);
return this;
}
public ResultVo<T> count(Integer count){
this.count = count;
return this;
}
}
md5加密
public static String md5(String str){
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5.digest(str.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 123456
return "4QrcOUm6Wau+VuBX8g+IPg==";
}
复制集合
public static <S, T> List<T> copyListProperties(List<S> sources, Class<T> targetClass) {
String old = JSON.toJSONString(sources);
return JSON.parseArray(old, targetClass);
}
获取图片的md5值
public static String getFileMD5(File file) {
if (!file.isFile()) {
return null;
}
// 创建MessageDigest对象,添加MD5处理
MessageDigest digest = null;
FileInputStream in = null;
byte[] buffer = new byte[1024];
int len;
try {
digest = MessageDigest.getInstance("MD5");
// 读取图片
in = new FileInputStream(file);
while ((len = in.read(buffer, 0, 1024)) != -1) {
digest.update(buffer, 0, len);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
BigInteger bigInt = new BigInteger(1, digest.digest());
System.out.println(bigInt);
// 返回16进制表示形式
return bigInt.toString(16);
}
逆向工程代码生成器
数据库信息 database.properties
db.url=jdbc:mysql://localhost:3306/nursing_homes
db.username=root
db.password=1507298022
g.output.dir=.\\src\\main\\java
pkg.name=com
pkg.xml.dir=mapper
代码生成工具类 AutoCodeUtil
public class AutoCodeUtil {
private static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
public static void main(String[] args) {
ResourceBundle res = ResourceBundle.getBundle("database");
FastAutoGenerator.create(res.getString("db.url"), res.getString("db.username"), res.getString("db.password"))
// 全局配置
.globalConfig((scanner, builder) -> builder
.author(scanner.apply("请输入作者名称:"))
.fileOverride() //允许重新文件
.disableOpenDir() //禁止生成成功打开文件夹
.outputDir(res.getString("g.output.dir")) //设置输出路径
)
// 包配置
.packageConfig((scanner, builder) -> builder
.parent(res.getString("pkg.name"))
.moduleName(scanner.apply("输入模块名称:"))
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, res.getString("g.output.dir") + "\\..\\resources\\" + res.getString("pkg.xml.dir")))
)
// 策略配置
.strategyConfig((scanner, builder) -> builder
.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔,所有输入all:")))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.mapperBuilder().enableMapperAnnotation()
.entityBuilder().enableLombok().addTableFills(new Column("create_time", FieldFill.INSERT))
.build()
)
.execute();
}
}
spring
mybatis配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置整合mybatis-->
<!--关联数据库文件: 读取配置文件-->
<context:property-placeholder location="classpath:jdbc.properties, classpath:redis.properties"/>
<!--数据库连接池: 这里使用c3p0-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置属性-->
<property name="driverClass" value="${db.driver}"/>
<property name="jdbcUrl" value="${db.url}"/>
<property name="user" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false"/>
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000"/>
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!--配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
<!--整合mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--配置扫描dao包, 动态实现dao接口注入spring容器 就可以不用去实现dao的接口了-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--给出需要扫描Dao接口包-->
<property name="basePackage" value="com.feng.dao"/>
</bean>
</beans>
springmvc
基本配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启mvc注解驱动-->
<mvc:annotation-driven/>
<!--静态资源默认配置-->
<mvc:default-servlet-handler/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--后缀-->
<property name="suffix" value=".html"/>
<!--前缀-->
<!-- <property name="prefix" value="/WEB-INF/jsp/"/>-->
</bean>
<!--扫描controller包下的bean-->
<context:component-scan base-package="com.feng.controller"/>
<!--让全局异常处理注解生效-->
<context:component-scan base-package="com.feng.exception"/>
<context:component-scan base-package="com.feng.util"/>
<!--关于拦截器的配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源执行拦截操作
<mvc:mapping path="/**"/>,
mapping表示要拦截,/**代表所有的请求路径,故拦截所有的请求路径。
-->
<mvc:mapping path="/**"/>
<!--不拦截哪些资源-->
<mvc:exclude-mapping path="/css/**"/>
<mvc:exclude-mapping path="/front/**"/>
<mvc:exclude-mapping path="/html/**"/>
<mvc:exclude-mapping path="/js/**"/>
<mvc:exclude-mapping path="/upload/**"/>
<bean class="com.feng.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
<!-- 文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--上传文件总大小, 单位为字节.-->
<property name="maxUploadSize" value="512000000"/>
<!--上传单个文件的大小, 单位为 字节-->
<!-- <property name="maxUploadSizePerFile" value="5242800"/>-->
<!-- 设置请求的编码格式, 默认为iso-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 设置上传文件的临时路径 -->
<!-- <property name="uploadTempDir" value="upload"/>-->
</bean>
<!--解决json响应乱码-->
<!--解决相应乱码-->
<mvc:annotation-driven>
<!--//响应类型转换器,可配置多个-->
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!--//相当于在servlet中写的response.setContentType("text/html;charset=utf-8")-->
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
处理乱码
post请求乱码:web.xml
文件里配置
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
相应乱码:在核心配置文件中配置
<mvc:annotation-driven>
<mvc:message-converters> <!--//响应类型转换器,可配置多个-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!--//相当于在servlet中写的response.setContentType("text/html;charset=utf-8")-->
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
springboot
Logback
logback.xml:日志的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!-- 日志文件存放路径(日志目录) -->
<property name="PATH" value="log"/>
<!-- 日志文件的相关配置 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${PATH}/spring.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${PATH}/spring.log.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize><!-- 单个文件最大100MB -->
<maxHistory>30</maxHistory><!-- 最多保存30天的纪录-->
<totalSizeCap>3GB</totalSizeCap><!-- 日志最大总量3GB-->
</rollingPolicy>
<encoder>
<!--格式化输出:%d表示日期,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
<pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] --%mdc{client} %msg%n</pattern>
</encoder>
</appender>
<!-- 控制台输出的样式-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] --%mdc{client} %msg%n</pattern>
</encoder>
</appender>
<!--这个表示指定某个包下的日志级别,需要改成自己的包 -->
<logger name="com.feng" level="DEBUG"/>
<!-- 默认 -->
<root>
<level value="INFO" />
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>
swagger
3.x的版本
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
config
// 还需要在主启动类上加上 @EnableWebMvc
@Configuration
@EnableSwagger2 // 开启Swagger
public class Knife4jConfig extends WebMvcConfigurationSupport {
@Bean(value = "dockerBean")
public Docket dockerBean() {
//指定使用Swagger2规范
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//分组名称
.groupName("用户服务")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.feng.controller"))
// 过滤哪些
.paths(PathSelectors.any())
.build();
return docket;
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Knife4j 接口文档")
// 支持md语法
.description("# Knife4j RESTful APIs")
.termsOfServiceUrl("https://doc.xiaominfo.com/")
.contact(new Contact("丰","xiaoymin@foxmail.com", "xiaoymin@foxmail.com"))
.version("1.0")
.build();
}
// 过滤资源,防止资源找不到问题
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
}
knife4j的增强配置
knife4j:
# 开启增强配置
enable: true
# 开启生产环境屏蔽
production: false
# 开启登录认证
basic:
enable: false
username: admin
password: 123456
4.x的版本
不需要再写配置类了,全部的配置写到yaml文件中
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
增强配置
knife4j:
enable: true
openapi:
title: Knife4j官方文档
description: "`Logback,Swagger,Redis`,**学习**
# aaa"
email: 1507298022@qq.com
concat: 丰
url: https://gitee.com/BiBifeng
version: v4.0
license: Apache 2.0
license-url: https://stackoverflow.com/
terms-of-service-url: https://stackoverflow.com/
group:
test1:
group-name: 分组名称
api-rule: package
api-rule-resources:
- com.feng.controller
Redis
自定义RedisTemplate
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置常规key value 的序列化策略
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 这里使用一般的json处理,就不容易存在兼容性问题。否则可能需要对应的json才能解析序列化的数据
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 设置hash类型的序列化策略
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
// 注入连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
yaml配置
# 数据库索引(默认为0)
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
# password: 123456
timeout: 30000
# 建议使用lettuce 可以换成jedis,spring默认集成lettuce
client-type: lettuce
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 最大空闲连接数
max-idle: 8
# 最小空闲连接
min-idle: 0
rabbitMq
定义RabbitTemplate,ConfirmCallback,ReturnsCallback
// 定义一个交换机名称
private final String TOPIC_EXCHANGE = "feng.exchanges.topic";
@Bean
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 确认模式,需要重写确认回调函数
rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
if (!ack){
System.out.println("消息发送到交换机失败,ack=false" + cause);
throw new RuntimeException(cause);
}else {
System.out.println("消息投递到exchange成功");
}
});
rabbitTemplate.setReturnsCallback((ReturnedMessage returned) -> {
System.out.println("消息发到队列失败,请检查交换机或路由:" + returned);
throw new RuntimeException("消息发到队列失败,请检查交换机或路由");
});
// 设置消息转换成json格式,这个配置不是必须
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
// 设置应答时长
rabbitTemplate.setReplyTimeout(6000);
return rabbitTemplate;
}
// 创建交换机
@Bean
public TopicExchange topicExchange(){
// 参数:交换机名称,是否持久化,是否自动删除
return new TopicExchange(TOPIC_EXCHANGE, true, false);
}
// 创建队列
@Bean
public Queue studentQueue(){
return new Queue("feng.students", true);
}
// 创建连接(Binding)
// 话题交换节与学生队列绑定
@Bean
public Binding topicExchangeToStudentQueueBinding(){
return BindingBuilder
.bind(studentQueue())
.to(topicExchange())
.with("feng.student.*");
}
yaml配置
spring:
rabbitmq:
port: 5672
host: 127.0.0.1
username: guest
password: guest
# 这个配置是保证提供者确保消息推送到交换机中,不管成不成功,都会回调
publisher-confirm-type: correlated
# 开启return模式
publisher-returns: true
template:
mandatory: true
邮件发送
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
spring:
application:
name: java-xadmin-spring-boot
mail:
host: smtp.163.com
port: 25
username: qq1507298022@163.com
password:
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试,先放开ssl
enable: false
required: false
#开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误
debug: true
@Autowired
private JavaMailSenderImpl javaMailSender;
@Value("${spring.mail.username}")
private String sendMailer;
public void sendTextMailMessage(String to,String subject,String text){
try {
//true 代表支持复杂的类型
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
//邮件发信人
mimeMessageHelper.setFrom(sendMailer);
//邮件收信人 1或多个
mimeMessageHelper.setTo(to.split(","));
//邮件主题
mimeMessageHelper.setSubject(subject);
//邮件内容
mimeMessageHelper.setText(text);
//邮件发送时间
mimeMessageHelper.setSentDate(new Date());
//发送邮件
javaMailSender.send(mimeMessageHelper.getMimeMessage());
System.out.println("发送邮件成功:"+sendMailer+"->"+to);
} catch (MessagingException e) {
e.printStackTrace();
System.out.println("发送邮件失败:"+e.getMessage());
}
}
elasticsearch
插入数据
List<ProductDto> productDtos = productService.selectAll();
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
for (int i = 0; i < productDtos.size(); i++) {
bulkRequest.add(
new IndexRequest("product_doc")
.id(productDtos.get(i).getId() + "")
.source(JSON.toJSONString(productDtos.get(i)), XContentType.JSON)
);
}
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
查询
/**
* @param condition 分页、查询条件
* @param doc 指定文档
* @param target 需要高亮的字段
* @param clazz 分装的实体类
*/
private ResultVo<List<T>> getUserDoc(PageVo condition, String doc, String target, Class<T> clazz) throws IOException {
SearchRequest searchRequest = new SearchRequest(doc);
// 构建搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 高亮 ,以哪个字段里面构建高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field(target);
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
// 将高亮的条件放到 搜索条件对象里面
sourceBuilder.highlighter(highlightBuilder);
if (condition.getSearch() != null){
MatchQueryBuilder termQueryBuilder = QueryBuilders.matchQuery(target, condition.getSearch());
// 添加延迟时间
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 将精确查询的对象放到 搜索对象里面
sourceBuilder.query(termQueryBuilder);
}else {
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(matchAllQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
}
//分页查询
sourceBuilder.from((condition.getPage() - 1) * condition.getLimit());
sourceBuilder.size(condition.getLimit());
// 放到数据库对象里面
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析搜索出来的数据,变为前端的格式,
List<T> list = new ArrayList<>();
for(SearchHit documentFields :searchResponse.getHits().getHits()){
// 解析高亮的字段
Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
HighlightField title = highlightFields.get(target);
// 得到原来的字段
if(title != null){
Text[] fragments = title.fragments(); // 得到高亮字段的值
String n_title = "";
for (Text text:fragments){
n_title += text;
}
sourceAsMap.put(target, n_title);//替换原来的字段
}
T t = JSON.parseObject(JSON.toJSONString(sourceAsMap), clazz);
list.add(t);
}
return ResultVo.success(list).code(0).count(list.size());
}
jwt+security
授权
// 自定义的登录处理
@Autowired
private MyUserDetailsService userDetailsService;
// Taken过滤器
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/index","/login", "/back/user/login").permitAll()// index 放行
.antMatchers(HttpMethod.OPTIONS).permitAll() //options 方法的请求放行
// 配置权限
.antMatchers("/back/product/**", "/back/category/**", "/back/file/**").hasAuthority("feng:product")
.anyRequest().authenticated()
.and()
.formLogin()
.defaultSuccessUrl("/html/index.html", true)
.loginPage("/html/login.html") // 登录访问的路径
.loginProcessingUrl("/login") // 登录时,发起请求的地址,使用系统默认的
.and()
.csrf().disable()
.sessionManagement()//允许配置会话管理
//Spring Security永远不会创建一个HttpSession,也永远不会使用它获取SecurityContext
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.userDetailsService(userDetailsService); // 使用自己的UserDetailsService;
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
jwtFilter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
// 使用自己的 userDetailService
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")//Authorization
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;//bearer
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
//从 header 中获取 Authorization
String authHeader = request.getHeader(tokenHeader);
// 访问没有资源,就放掉
if (authHeader == null) {
chain.doFilter(request, response);
return;
}
// 判断 authHeader 不为空 并且以 bearer 开头
boolean b1 = StringUtils.startsWithIgnoreCase(authHeader,this.tokenHead);
if (!b1) {
chain.doFilter(request, response);
return;
}
//截取 bearer 后面的字符串 并且 两端去空格(获取token)
String authToken = authHeader.substring(this.tokenHead.length()).trim();// The part after "Bearer "
// 解析taken获取到用户名
String username = jwtTokenUtil.getUserNameFromToken(authToken);
// 用户名不为空 并且SecurityContextHolder.getContext() 存储 权限的容器中没有相关权限则继续
boolean isNotAuthentication = SecurityContextHolder.getContext().getAuthentication() == null;
if (username != null && isNotAuthentication) {
//从数据库读取用户信息
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
//校验token
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
// 一个实现的带用户名和密码以及权限的Authentication(spring 自带的类)
UsernamePasswordAuthenticationToken authentication = null;
authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// 从HttpServletRequest 对象,创建一个WebAuthenticationDetails对象
WebAuthenticationDetails details = new WebAuthenticationDetailsSource().buildDetails(request);
//设置details
authentication.setDetails(details);
//存入本线程的安全容器 在访问接口拿到返回值后 要去主动清除 权限,避免干扰其他的线程
//SecurityContextHolder会把authentication放入到session里,供后面使用
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
JwtTokenUtil
@Component
public class JwtTokenUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;// 盐
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根据 负载(用户名 部门 权限 等) 生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.info("JWT格式验证失败:{}",token);
}
return claims;
}
/**
* 生成token的过期时间 30秒过期
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
* @param token 客户端传入的token
* @param userDetails 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 从token中获取过期时间
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 根据用户信息生成token
*/
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, username);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
跨域配置
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//是否发送 Cookie
config.setAllowCredentials(true);
//放行哪些请求方式
config.addAllowedMethod("*");
//放行哪些原始请求头部信息
config.addAllowedHeader("*");
//暴露哪些头部信息
config.addExposedHeader("*");
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
前端
js
ajax请求
function myAjax(url, data, type){
$.ajax({
url: url,
data: data,
type: type,
dataType: "json",
contentType: "application/json;charset=UTF-8",
headers: {'Accept': 'application/json', 'Authorization': sessionStorage.getItem("token")},
success: function (res) {
if(res.code === 200){
// 将返回的数据封装到session中
sessionStorage.setItem("username", res.data.username)
location.href = "html/index.html"
}else {
layer.msg('用户名或者密码错误');
}
},
error: function (data) {} // 错误回调
})
}
显示动态时间
$(function (){
setInterval(function (){
let date = new Date().Format("yyyy-MM-dd HH:mm:ss");
$("#loginTime").html(date)
}, 1000)
})
Date.prototype.Format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"H+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
layui
监听按键
layui.use('form', function () {
let form = layui.form;
// login对应按键的 属性 lay-filter="login"
form.on('submit(login)', function (data) {
layer.msg('有表情地提示', {icon: 6}); // 提示框
});
});