文章目录

spring-boot 常用配置及代码样例_spring

常用maven 包

<!--webjar-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.34</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>js-cookie</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.2.1</version>
</dependency>

<!--redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<!--start 依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--test类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--netty 导入-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>
<!--WEB socket 对象-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- RabbitMQ Starter Dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--netty 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>

<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>

<!-- Spring Data JPA 依赖 :: 数据持久层框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- h2 数据源依赖 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
<!--热部署依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-cron</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>

<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>

yml

server:
port: 3000
undertow:
io-threads: 2
worker-threads: 36
buffer-size: 1024
directBuffers: true
servlet:
session:
timeout: 86400s

logging:
file: ./logs/log.log

spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/theatre?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
druid:
validation-query: SELECT 1
initial-size: 1
min-idle: 1
max-active: 20
filters: stat
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 3000000
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 20

redis:
host: 127.0.0.1
password:
port: 6379
timeout: 12000
database: 0
lettuce:
pool:
max-active: 300
max-idle: 100
min-idle: 8
max-wait: -1
thymeleaf:
mode: HTML
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
servlet:
content-type: text/html
cache: false
enabled: true
messages:
basename: i18n/messages
encoding: UTF-8
jpa:
hibernate:
show-sql: true
ddl-auto: update

swagger:
base

#debug: true

mvc 配置

package com.devin.config;

import com.devin.web.interceptor.LocaleInterceptor;
import com.devin.web.interceptor.LoginInterceptor;
import org.apache.catalina.filters.RequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.WebJarsResourceResolver;

import java.util.Locale;

/**
* User: devin
* Date: 2020/2/19
* Time: 19:27
* Description: No Description
*/

@SpringBootConfiguration
public class SpringMvcConfig implements WebMvcConfigurer {

@Autowired
private LocaleInterceptor localeInterceptor;

@Autowired
private LoginInterceptor loginInterceptor;
/**
* 注册拦截器
*
* @param registry registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/user.*")
.addPathPatterns("/user/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/getLogin")
.excludePathPatterns("/static/**");

registry.addInterceptor(localeInterceptor)
.addPathPatterns("/user.*")
.addPathPatterns("/user/**")
.addPathPatterns("/install");
registry.addInterceptor(localeChangeInterceptor())
.addPathPatterns("/install");
}

/**
* 配置静态资源路径
*
* @param registry registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/templates/**");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("/webjars/").resourceChain(false)
.addResolver(new WebJarsResourceResolver())
.addResolver(new PathResourceResolver());;

}


/**
* 国际化设置
*
* @return LocaleResolver
*/
@Bean
public LocaleResolver localeResolver() {
final SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.CHINA);
return slr;
}

/**
* 国际化参数拦截器
*
* @return LocaleChangeInterceptor
*/
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
final LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}



}

swagger

package com.devin.config;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
* @author: simpl
* @date: 2020/2/27
* @time: 10:17
* @description:
*/

@SpringBootConfiguration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket apiConfig() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.devin.web.controller"))
.paths(PathSelectors.any())
.build();
}

public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.contact(new Contact("devin","","1241169737@"))
.title("剧场接口文档")
.version("v1.0.0")
.build();
}
}

bean 配置

package com.devin.config;

import com.devin.util.JwtUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

/**
* @author: simpl
* @date: 2020/3/16
* @time: 13:02
* @description:
*/
@Component
public class BeanConfig {

@Bean
@Lazy
public JwtUtil jjwtUtil() {
return JwtUtil.getInstance();
}


/**
* 配置redisTemplate
* @param lettuceConnectionFactory lettuceConnection工厂
* @return 配置成功返回可以使用RedisTemplate<String, Object> 类型
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
// key序列化
redisTemplate.setKeySerializer(stringSerializer);
// value序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// Hash key序列化
redisTemplate.setHashKeySerializer(stringSerializer);
// Hash value序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

websocket

package com.devin.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
* @author heenanagpal
*
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/channel");
}
}

用户实体类

package com.devin.model.domain;

import lombok.Data;

import javax.persistence.*;
import java.util.Date;

/**
* @author: simpl
* @date: 2020/3/13
* @time: 20:23
* @description:
*/

@Data
@Entity
@Table(name = "theatre_movie")
public class Movie {


private static final long serialVersionUID = -5144055068797033748L;


/*
* 电影的id
* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

/*
* 影片类型
* */
private String kind;

/*
* 电影名字
* */
private String title;

/*
* 创建日期
* */
private Date createDate;

/*
* 电影的链接
* */
private String url;

/*
* 电影的封面
* */
private String faceUrl;


/*
* 上传者名字
* */
private String createName;

}

DTO

public class ChatMessage {

public enum MessageType {
CHAT, JOIN, LEAVE
}

private MessageType messageType;
private String content;
private String sender;

public MessageType getType() {
return messageType;
}

public void setType(MessageType messageType) {
this.messageType = messageType;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getSender() {
return sender;
}

public void setSender(String sender) {
this.sender = sender;
}
}

enum

package com.devin.model.enums;

/**
* <pre>
* 文章类型enum
* </pre>
*
* @author : RYAN0UP
* @date : 2018/7/1
*/
public enum PostTypeEnum {

/**
* 文章
*/
POST_TYPE_POST("post"),

/**
* 页面
*/
POST_TYPE_PAGE("page");

private String desc;

PostTypeEnum(String desc) {
this.desc = desc;
}

public String getDesc() {
return desc;
}
}

base repository

package com.devin.repository.base;

import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.lang.NonNull;

import java.util.List;

/**
* Base repository interface contains some common methods.
*
* @param <DOMAIN> doamin type
* @param <ID> id type
* @author johnniang
*/
@NoRepositoryBean
public interface BaseRepository<DOMAIN, ID> extends JpaRepository<DOMAIN, ID> {

/**
* Finds all domain by id list and the specified sort.
*
* @param ids id list of domain must not be null
* @param sort the specified sort must not be null
* @return a list of domains
*/
@NonNull
List<DOMAIN> findAllByIdIn(@NonNull Iterable<ID> ids, @NonNull Sort sort);

/**
* Deletes by id list.
*
* @param ids id list of domain must not be null
* @return number of rows affected
*/
long deleteByIdIn(@NonNull Iterable<ID> ids);
}

impl repository

  • 需要在启动类加 否则实体管理器无法启动
    ​​​@EnableJpaAuditing @EnableJpaRepositories(basePackages = "com.devin.repository", repositoryBaseClass = BaseRepositoryImpl.class)​
package com.devin.repository.base;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Implementation of base repository.
*
* @param <DOMAIN> domain type
* @param <ID> id type
* @author johnniang
*/
@Slf4j
public class BaseRepositoryImpl<DOMAIN, ID> extends SimpleJpaRepository<DOMAIN, ID> implements BaseRepository<DOMAIN, ID> {

// private final Logger log = Logger.getLogger(getClass());

private final JpaEntityInformation<DOMAIN, ID> entityInformation;

private final EntityManager entityManager;

public BaseRepositoryImpl(JpaEntityInformation<DOMAIN, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
this.entityManager = entityManager;
}

/**
* Finds all domain by id list and the specified sort.
*
* @param ids id list of domain must not be null
* @param sort the specified sort must not be null
* @return a list of domains
*/
@Override
public List<DOMAIN> findAllByIdIn(Iterable<ID> ids, Sort sort) {
Assert.notNull(ids, "The given Iterable of Id's must not be null!");

log.debug("Customized findAllById method was invoked");

if (!ids.iterator().hasNext()) {
return Collections.emptyList();
}
if (!this.entityInformation.hasCompositeId()) {
ByIdsSpecification<DOMAIN> specification = new ByIdsSpecification<>(this.entityInformation);
TypedQuery<DOMAIN> query = super.getQuery(specification, sort);
return query.setParameter(specification.parameter, ids).getResultList();
} else {
List<DOMAIN> results = new ArrayList<>();

ids.forEach(id -> super.findById(id).ifPresent(results::add));

return results;
}
}

/**
* Deletes by id list.
*
* @param ids id list of domain must not be null
* @return number of rows affected
*/
@Override
public long deleteByIdIn(Iterable<ID> ids) {

log.debug("Customized deleteByIdIn method was invoked");
// Find all domains
List<DOMAIN> domains = findAllById(ids);

// Delete in batch
deleteInBatch(domains);

// Return the size of domain deleted
return domains.size();
}

private static final class ByIdsSpecification<T> implements Specification<T> {
private static final long serialVersionUID = 1L;
private final JpaEntityInformation<T, ?> entityInformation;
@Nullable
ParameterExpression<Iterable> parameter;

ByIdsSpecification(JpaEntityInformation<T, ?> entityInformation) {
this.entityInformation = entityInformation;
}

@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<?> path = root.get(this.entityInformation.getIdAttribute());
this.parameter = cb.parameter(Iterable.class);
return path.in(this.parameter);
}
}
}

UserRepository

package cc.ryanc.halo.repository;

import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.repository.base.BaseRepository;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* <pre>
* 用户持久层
* </pre>
*
* @author : RYAN0UP
* @date : 2017/11/14
*/
public interface UserRepository extends BaseRepository<User, Long> {

/**
* 根据用户名和密码查询
*
* @param userName userName
* @param userPass userPass
* @return User
*/
User findByUserNameAndUserPass(String userName, String userPass);

/**
* 根据邮箱和密码查询
*
* @param userEmail userEmail
* @param userPass userPass
* @return User
*/
User findByUserEmailAndUserPass(String userEmail, String userPass);

/**
* 根据用户编号和密码查询
*
* @param userId userId
* @param userPass userpass
* @return User
*/
User findByUserIdAndUserPass(Long userId, String userPass);
}

repostiory

package com.devin.repository;

import com.devin.model.domain.User;
import com.devin.repository.base.BaseRepository;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* <pre>
* 用户持久层
* </pre>
*
* @author : RYAN0UP
* @date : 2017/11/14
*/
public interface UserRepository extends BaseRepository<User, Long> {

/**
* 根据用户名和密码查询
*
* @param userName userName
* @param userPass userPass
* @return User
*/
User findByUserNameAndUserPass(String userName, String userPass);

/**
* 根据邮箱和密码查询
*
* @param userEmail userEmail
* @param userPass userPass
* @return User
*/
User findByUserEmailAndUserPass(String userEmail, String userPass);

/**
* 根据用户编号和密码查询
*
* @param userId userId
* @param userPass userpass
* @return User
*/
User findByUserIdAndUserPass(Long userId, String userPass);
}

CrudService

package cc.ryanc.halo.service.base;

import cc.ryanc.halo.exception.NotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
* CrudService interface contains some common methods.
*
* @param <DOMAIN> domain type
* @param <ID> id type
* @author johnniang
*/
public interface CrudService<DOMAIN, ID> {


/**
* List All
*
* @return List
*/
@NonNull
List<DOMAIN> listAll();

/**
* List all by sort
*
* @param sort sort
* @return List
*/
@NonNull
List<DOMAIN> listAll(@NonNull Sort sort);

/**
* List all by pageable
*
* @param pageable pageable
* @return Page
*/
@NonNull
Page<DOMAIN> listAll(@NonNull Pageable pageable);

/**
* List all by ids
*
* @param ids ids
* @return List
*/
@NonNull
List<DOMAIN> listAllByIds(@NonNull Collection<ID> ids);

/**
* List all by ids and sort
*
* @param ids ids
* @param sort sort
* @return List
*/
@NonNull
List<DOMAIN> listAllByIds(@NonNull Collection<ID> ids, @NonNull Sort sort);

/**
* Fetch by id
*
* @param id id
* @return Optional
*/
@NonNull
Optional<DOMAIN> fetchById(@NonNull ID id);

/**
* Get by id
*
* @param id id
* @return DOMAIN
* @throws NotFoundException If the specified id does not exist
*/
@NonNull
DOMAIN getById(@NonNull ID id);

/**
* Gets domain of nullable by id.
*
* @param id id
* @return DOMAIN
*/
@Nullable
DOMAIN getByIdOfNullable(@NonNull ID id);

/**
* Exists by id.
*
* @param id id
* @return boolean
*/
boolean existsById(@NonNull ID id);

/**
* Must exist by id, or throw NotFoundException.
*
* @param id id
* @throws NotFoundException If the specified id does not exist
*/
void mustExistById(@NonNull ID id);

/**
* count all
*
* @return long
*/
long count();

/**
* save by domain
*
* @param domain domain
* @return DOMAIN
*/
@NonNull
DOMAIN create(@NonNull DOMAIN domain);

/**
* save by domains
*
* @param domains domains
* @return List
*/
@NonNull
List<DOMAIN> createInBatch(@NonNull Collection<DOMAIN> domains);

/**
* Updates by domain
*
* @param domain domain
* @return DOMAIN
*/
@NonNull
DOMAIN update(@NonNull DOMAIN domain);

/**
* Updates by domains
*
* @param domains domains
* @return List
*/
@NonNull
List<DOMAIN> updateInBatch(@NonNull Collection<DOMAIN> domains);

/**
* Removes by id
*
* @param id id
* @return DOMAIN
* @throws NotFoundException If the specified id does not exist
*/
@NonNull
DOMAIN removeById(@NonNull ID id);

/**
* Removes by id if present.
*
* @param id id
* @return DOMAIN
*/
@Nullable
DOMAIN removeByIdOfNullable(@NonNull ID id);

/**
* Remove by domain
*
* @param domain domain
*/
void remove(@NonNull DOMAIN domain);

/**
* Remove by ids
*
* @param ids ids
*/
void removeInBatch(@NonNull Collection<ID> ids);

/**
* Remove all by domains
*
* @param domains domains
*/
void removeAll(@NonNull Collection<DOMAIN> domains);

/**
* Remove all
*/
void removeAll();
}

AbstractCrudService

package cc.ryanc.halo.service.base;

import cc.ryanc.halo.exception.NotFoundException;
import cc.ryanc.halo.logging.Logger;
import cc.ryanc.halo.repository.base.BaseRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
* Abstract service implementation.
*
* @param <DOMAIN> domain type
* @param <ID> id type
* @author johnniang
*/
public abstract class AbstractCrudService<DOMAIN, ID> implements CrudService<DOMAIN, ID> {

private final Logger log = Logger.getLogger(getClass());

private final String domainName;

private final BaseRepository<DOMAIN, ID> repository;

protected AbstractCrudService(BaseRepository<DOMAIN, ID> repository) {
this.repository = repository;

// Get domain name
Class<DOMAIN> domainClass = (Class<DOMAIN>) fetchType(0);
domainName = domainClass.getSimpleName();
}

/**
* Gets actual generic type.
*
* @param index generic type index
* @return real generic type will be returned
*/
private Type fetchType(int index) {
Assert.isTrue(index >= 0 && index <= 1, "type index must be between 0 to 1");

return ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[index];
}

/**
* List All
*
* @return List
*/
@Override
public List<DOMAIN> listAll() {
return repository.findAll();
}

/**
* List all by sort
*
* @param sort sort
* @return List
*/
@Override
public List<DOMAIN> listAll(Sort sort) {
Assert.notNull(sort, "Sort info must not be null");

return repository.findAll(sort);
}

/**
* List all by pageable
*
* @param pageable pageable
* @return Page
*/
@Override
public Page<DOMAIN> listAll(Pageable pageable) {
Assert.notNull(pageable, "Pageable info must not be null");

return repository.findAll(pageable);
}

/**
* List all by ids
*
* @param ids ids
* @return List
*/
@Override
public List<DOMAIN> listAllByIds(Collection<ID> ids) {
return CollectionUtils.isEmpty(ids) ? Collections.emptyList() : repository.findAllById(ids);
}

/**
* List all by ids and sort
*
* @param ids ids
* @param sort sort
* @return List
*/
@Override
public List<DOMAIN> listAllByIds(Collection<ID> ids, Sort sort) {
Assert.notNull(sort, "Sort info must not be null");

return CollectionUtils.isEmpty(ids) ? Collections.emptyList() : repository.findAllByIdIn(ids, sort);
}

/**
* Fetch by id
*
* @param id id
* @return Optional
*/
@Override
public Optional<DOMAIN> fetchById(ID id) {
Assert.notNull(id, domainName + " id must not be null");

return repository.findById(id);
}

/**
* Get by id
*
* @param id id
* @return DOMAIN
* @throws NotFoundException If the specified id does not exist
*/
@Override
public DOMAIN getById(ID id) {
return fetchById(id).orElseThrow(() -> new NotFoundException(domainName + " was not found"));
}

/**
* Gets domain of nullable by id.
*
* @param id id
* @return DOMAIN
*/
@Override
public DOMAIN getByIdOfNullable(ID id) {
return fetchById(id).orElse(null);
}

/**
* Exists by id.
*
* @param id id
* @return boolean
*/
@Override
public boolean existsById(ID id) {
Assert.notNull(id, domainName + " id must not be null");

return repository.existsById(id);
}

/**
* Must exist by id, or throw NotFoundException.
*
* @param id id
* @throws NotFoundException If the specified id does not exist
*/
@Override
public void mustExistById(ID id) {
if (!existsById(id)) {
throw new NotFoundException(domainName + " was not exist");
}
}

/**
* count all
*
* @return long
*/
@Override
public long count() {
return repository.count();
}

/**
* save by domain
*
* @param domain domain
* @return DOMAIN
*/
@Override
public DOMAIN create(DOMAIN domain) {
Assert.notNull(domain, domainName + " data must not be null");

return repository.save(domain);
}

/**
* save by domains
*
* @param domains domains
* @return List
*/
@Override
public List<DOMAIN> createInBatch(Collection<DOMAIN> domains) {
return CollectionUtils.isEmpty(domains) ? Collections.emptyList() : repository.saveAll(domains);
}

/**
* Updates by domain
*
* @param domain domain
* @return DOMAIN
*/
@Override
public DOMAIN update(DOMAIN domain) {
Assert.notNull(domain, domainName + " data must not be null");

return repository.saveAndFlush(domain);
}

/**
* Updates by domains
*
* @param domains domains
* @return List
*/
@Override
public List<DOMAIN> updateInBatch(Collection<DOMAIN> domains) {
return CollectionUtils.isEmpty(domains) ? Collections.emptyList() : repository.saveAll(domains);
}

/**
* Removes by id
*
* @param id id
* @return DOMAIN
* @throws NotFoundException If the specified id does not exist
*/
@Override
public DOMAIN removeById(ID id) {
// Get non null domain by id
DOMAIN domain = getById(id);

// Remove it
remove(domain);

// return the deleted domain
return domain;
}

/**
* Removes by id if present.
*
* @param id id
* @return DOMAIN
*/
@Override
public DOMAIN removeByIdOfNullable(ID id) {
return fetchById(id).map(domain -> {
remove(domain);
return domain;
}).orElse(null);
}

/**
* Remove by domain
*
* @param domain domain
*/
@Override
public void remove(DOMAIN domain) {
Assert.notNull(domain, domainName + " data must not be null");

repository.delete(domain);
}

/**
* Remove by ids
*
* @param ids ids
*/
@Override
public void removeInBatch(Collection<ID> ids) {
if (CollectionUtils.isEmpty(ids)) {
log.warn(domainName + " id collection is empty");
return;
}

repository.deleteByIdIn(ids);
}

/**
* Remove all by domains
*
* @param domains domains
*/
@Override
public void removeAll(Collection<DOMAIN> domains) {
if (CollectionUtils.isEmpty(domains)) {
log.warn(domainName + " collection is empty");
return;
}
repository.deleteInBatch(domains);
}

/**
* Remove all
*/
@Override
public void removeAll() {
repository.deleteAll();
}
}

UserService

package cc.ryanc.halo.service;

import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.service.base.CrudService;

import java.util.Date;

/**
* <pre>
* 用户业务逻辑接口
* </pre>
*
* @author : RYAN0UP
* @date : 2017/11/14
*/
public interface UserService extends CrudService<User, Long> {

/**
* 根据用户名和密码查询,用于登录
*
* @param userName userName
* @param userPass userPass
* @return User
*/
User userLoginByName(String userName, String userPass);

/**
* 根据邮箱和密码查询,用户登录
*
* @param userEmail userEmail
* @param userPass userPass
* @return User
*/
User userLoginByEmail(String userEmail, String userPass);

/**
* 查询所有用户
*
* @return User
*/
User findUser();

/**
* 根据用户编号和密码查询
*
* @param userId userid
* @param userPass userpass
* @return User
*/
User findByUserIdAndUserPass(Long userId, String userPass);

/**
* 修改禁用状态
*
* @param enable enable
*/
void updateUserLoginEnable(String enable);

/**
* 修改最后登录时间
*
* @param lastDate 最后登录时间
* @return User
*/
User updateUserLoginLast(Date lastDate);

/**
* 增加登录错误次数
*
* @return 登录错误次数
*/
Integer updateUserLoginError();

/**
* 修改用户的状态为正常
*
* @return User
*/
User updateUserNormal();
}

UserServiceImpl

package cc.ryanc.halo.service.impl;

import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.model.enums.TrueFalseEnum;
import cc.ryanc.halo.repository.UserRepository;
import cc.ryanc.halo.service.UserService;
import cc.ryanc.halo.service.base.AbstractCrudService;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
* <pre>
* 用户业务逻辑实现类
* </pre>
*
* @author : RYAN0UP
* @date : 2017/11/14
*/
@Service
public class UserServiceImpl extends AbstractCrudService<User, Long> implements UserService {

private final UserRepository userRepository;

public UserServiceImpl(UserRepository userRepository) {
super(userRepository);
this.userRepository = userRepository;
}

/**
* 根据用户名和密码查询
*
* @param userName userName
* @param userPass userPass
* @return User
*/
@Override
public User userLoginByName(String userName, String userPass) {
return userRepository.findByUserNameAndUserPass(userName, userPass);
}

/**
* 根据邮箱和密码查询,用户登录
*
* @param userEmail userEmail
* @param userPass userPass
* @return User
*/
@Override
public User userLoginByEmail(String userEmail, String userPass) {
return userRepository.findByUserEmailAndUserPass(userEmail, userPass);
}

/**
* 查询所有用户
*
* @return User
*/
@Override
public User findUser() {
final List<User> users = userRepository.findAll();
if (users != null && users.size() > 0) {
return users.get(0);
} else {
return new User();
}
}

/**
* 验证修改密码时,密码是否正确
*
* @param userId userId
* @param userPass userPass
* @return User
*/
@Override
public User findByUserIdAndUserPass(Long userId, String userPass) {
return userRepository.findByUserIdAndUserPass(userId, userPass);
}

/**
* 修改禁用状态
*
* @param enable enable
*/
@Override
public void updateUserLoginEnable(String enable) {
final User user = this.findUser();
user.setLoginError(0);
user.setLoginEnable(enable);

// Update user
update(user);
}

/**
* 修改最后登录时间
*
* @param lastDate 最后登录时间
* @return User
*/
@Override
public User updateUserLoginLast(Date lastDate) {
final User user = this.findUser();
user.setLoginLast(lastDate);

// Update user
return update(user);
}

/**
* 增加登录错误次数
*
* @return 登录错误次数
*/
@Override
public Integer updateUserLoginError() {
final User user = this.findUser();
user.setLoginError((user.getLoginError() == null ? 0 : user.getLoginError()) + 1);

// Update user
update(user);

// Return login error times
return user.getLoginError();
}

/**
* 修改用户的状态为正常
*
* @return User
*/
@Override
public User updateUserNormal() {
final User user = this.findUser();
user.setLoginEnable(TrueFalseEnum.TRUE.getDesc());
user.setLoginError(0);
user.setLoginLast(new Date());

return update(user);
}
}

UserController

package cc.ryanc.halo.web.controller.admin;

import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.model.dto.JsonResult;
import cc.ryanc.halo.model.enums.ResultCodeEnum;
import cc.ryanc.halo.service.UserService;
import cc.ryanc.halo.utils.LocaleMessageUtil;
import cn.hutool.crypto.SecureUtil;
import freemarker.template.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;
import javax.validation.Valid;

import static cc.ryanc.halo.model.dto.HaloConst.USER_SESSION_KEY;

/**
* <pre>
* 后台用户管理控制器
* </pre>
*
* @author : RYAN0UP
* @date : 2017/12/24
*/
@Slf4j
@Controller
@RequestMapping(value = "/admin/profile")
public class UserController {

@Autowired
private UserService userService;

@Autowired
private Configuration configuration;

@Autowired
private LocaleMessageUtil localeMessageUtil;

/**
* 获取用户信息并跳转
*
* @return 模板路径admin/admin_profile
*/
@GetMapping
public String profile() {
return "admin/admin_profile";
}

/**
* 处理修改用户资料的请求
*
* @param user user
* @param session session
* @return JsonResult
*/
@PostMapping(value = "save")
@ResponseBody
public JsonResult saveProfile(@Valid @ModelAttribute User user, BindingResult result, HttpSession session) {
try {
if (result.hasErrors()) {
for (ObjectError error : result.getAllErrors()) {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), error.getDefaultMessage());
}
}
userService.create(user);
configuration.setSharedVariable("user", userService.findUser());
session.removeAttribute(USER_SESSION_KEY);
} catch (Exception e) {
log.error("Failed to modify user profile: {}", e.getMessage());
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.common.edit-failed"));
}
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.common.edit-success"));
}

/**
* 处理修改密码的请求
*
* @param beforePass 旧密码
* @param newPass 新密码
* @param userId 用户编号
* @param session session
* @return JsonResult
*/
@PostMapping(value = "changePass")
@ResponseBody
public JsonResult changePass(@ModelAttribute("beforePass") String beforePass,
@ModelAttribute("newPass") String newPass,
@ModelAttribute("userId") Long userId,
HttpSession session) {
try {
final User user = userService.findByUserIdAndUserPass(userId, SecureUtil.md5(beforePass));
if (null != user) {
user.setUserPass(SecureUtil.md5(newPass));
userService.update(user);
session.removeAttribute(USER_SESSION_KEY);
} else {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.user.old-password-error"));
}
} catch (Exception e) {
log.error("Password change failed: {}", e.getMessage());
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.user.update-password-failed"));
}
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.user.update-password-success"));
}
}

登录拦截器

package com.devin.web.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static com.devin.model.dto.TheatreConst.USER_SESSION_KEY;

/**
* <pre>
* 后台登录控制器
* </pre>
*
* @author : RYAN0UP
* @date : 2017/12/13
*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final Object obj = request.getSession().getAttribute(USER_SESSION_KEY);
//如果user不为空则放行
log.info("进入登入拦截器");
if (null != obj) {
log.info("拦截器返回true");
return true;
}
//否则拦截并跳转到登录
log.info("拦截器返回false");
response.sendRedirect("/user/login");
return false;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

AOP

package com.devin.aop;



import com.devin.model.enums.DateStyleEnum;
import com.devin.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;


@Aspect
@Component
@Slf4j
public class ReControllerLogAdvice {

/** 进入方法时间戳 */
private Long startTime;

/** 方法结束时间戳(计时) */
private Long endTime;

public ReControllerLogAdvice() {}

/**
* 定义请求日志切入点,其切入点表达式有多种匹配方式,这里是指定路径
*/
@Pointcut("execution(public * com.devin.web.*.*.*(..))")
public void webLogPointcut() {}

/**
* 前置通知:
* 1. 在执行目标方法之前执行,比如请求接口之前的登录验证;
* 2. 在前置通知中设置请求日志信息,如开始时间,请求参数,注解内容等
*
* @param joinPoint 切点
*/
@Before("webLogPointcut()")
public void doBefore(JoinPoint joinPoint) {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
//打印请求的内容
startTime = System.currentTimeMillis();
log.info("请求开始时间:{}", DateUtil.formatDate(new Date(), DateStyleEnum.yyyy_MM_dd_HH_mm_ss));
log.info("请求Url : {}", request.getRequestURL().toString());
log.info("请求方式 : {}", request.getMethod());
log.info("请求ip : {}", request.getRemoteAddr());
log.info("请求方法 : {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("请求参数 : {}", Arrays.toString(joinPoint.getArgs()));
}

/**
* 返回通知:
* 1. 在目标方法正常结束之后执行
* 1. 在返回通知中补充请求日志信息,如返回时间,方法耗时,返回值,并且保存日志信息
*
* @param ret 返回参数
*/
@AfterReturning(returning = "ret", pointcut = "webLogPointcut()")
public void doAfterReturning(Object ret) {
endTime = System.currentTimeMillis();
log.info("请求结束时间:{}", DateUtil.formatDate(new Date(), DateStyleEnum.yyyy_MM_dd_HH_mm_ss));
log.info("请求耗时:{}", (endTime - startTime));
// 处理完请求,返回内容
log.info("请求返回 : {}", ret);
}

/**
* 异常通知:
* 1. 在目标方法非正常结束,发生异常或者抛出异常时执行
* 1. 在异常通知中设置异常信息,并将其保存
*
* @param throwable 异常
*/
@AfterThrowing(value = "webLogPointcut()", throwing = "throwable")
public void doAfterThrowing(Throwable throwable) {
// 保存异常日志记录
log.error("发生异常时间:{}", DateUtil.formatDate(new Date(), DateStyleEnum.yyyy_MM_dd_HH_mm_ss));
log.error("抛出异常:{}", throwable.getMessage());
}
}

html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>Spring Boot WebSocket Chat Application | CalliCoder</title>
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/webjars/bootstrap/4.4.1-1/css/bootstrap.min.css" />
</head>
<body>
<noscript>
<h2>Sorry! Your browser doesn't support Javascript</h2>
</noscript>

<div id="username-page">
<div class="username-page-container">
<h1 class="title">Type your username</h1>
<form id="usernameForm" name="usernameForm">
<div class="form-group">
<input type="text" id="name" placeholder="Username" autocomplete="off" class="form-control" />
</div>
<div class="form-group">
<button type="submit" class="accent username-submit">Start Chatting</button>
</div>
</form>
</div>
</div>

<div id="chat-page" class="hidden">
<div class="chat-container">
<div class="chat-header">
<h2>Spring WebSocket Chat Demo</h2>
</div>
<div class="connecting">
Connecting...
</div>
<ul id="messageArea">

</ul>
<form id="messageForm" name="messageForm" nameForm="messageForm">
<div class="form-group">
<div class="input-group clearfix">
<input type="text" id="message" placeholder="Type a message..." autocomplete="off" class="form-control"/>
<button type="submit" class="primary">Send</button>
</div>
</div>
</form>
</div>
</div>static
<!--导入可以不用 模板的语法 静态配置了 这里就不需要/static-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script src="/js/main.js"></script>
<script src="webjars/layui/src/layui.js"></script>
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/webjars/js-cookie/js.cookie.js"></script>
<script src="index.js" type="text/javascript" ></script>
<script src="js/main.js" type="text/javascript" ></script>
</body>
</html>

日志配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/home/ljt/webfile/re/log" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--<withJansi>true</withJansi>-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%red([%d{yyyy-MM-dd HH:mm:ss}]) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{50}) - %cyan(%msg) %n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
<maxFileSize>20MB</maxFileSize>
<totalSizeCap>2GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 配置mybatis的日志输出级别为INFO -->
<logger name="mybatis.sql" level="INFO"/>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
</configuration>