开源项目eladmin--笔记
eladmin笔记---未完待续
- 一、配置
- 1、yml文件的内容
- 详细结构
- 二、控制
- 1、代码生成器可一键生成前后端代码
- 系统代码生成( eladmin-generator 系统代码生成模块)
- 2、支持数据字典,可方便地对一些状态进行管理(这个需要看看)
- 3、支持接口限流,避免恶意请求导致服务层压力过大
- 4、支持接口级别的功能权限与数据权限,可自定义操作
- 5、自定义权限注解与匿名接口注解,可快速对接口拦截与放行
- 6、对一些常用地前端组件封装:表格数据请求、数据字典等
- 7、前后端统一异常拦截处理,统一输出异常,避免繁琐的判断(每个项目必备)
- 8、支持在线用户管理与服务器性能监控,支持限制单用户登录
- 在线用户管理
- 9、支持运维管理,可方便地对远程服务器的应用进行部署与管理
- 三、数据接口
- 1、用户管理:提供用户的相关配置,新增用户后,默认密码为123456
- 2、角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限
- 3、菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单
- 4、部门管理:可配置系统组织架构,树形表格展示
- 5、岗位管理:配置各个部门的职位
- 6、字典管理:可维护常用一些固定的数据,如:状态,性别等
- 7、系统日志:记录用户操作日志与异常日志,方便开发人员定位排错
- 8、SQL监控:采用druid 监控数据库访问性能,默认用户名admin,密码123456
- 9、定时任务:整合Quartz做定时任务,加入任务日志,任务运行情况一目了然
- 10、代码生成:高灵活度生成前后端代码,减少大量重复的工作任务
- 11、邮件工具:配合富文本,发送html格式的邮件
- 12、七牛云存储:可同步七牛云存储的数据到系统,无需登录七牛云直接操作云数据
- 13、支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试
- 14、服务监控:监控服务器的负载情况
- 15、运维管理:一键部署你的应用
- 四、懵逼之处
- 1、对自动生成前后端代码不太了解
- 2、对于ResponseEntity没有使用过,一般都是项目内自定义的返回结果工具类
一、配置
1、yml文件的内容
#项目端口号
server:port: 8000#springboot整合freemarker模板引擎
# 是否检查模板位置是否存在。
spring:freemarker:check-template-location: false#profiles: active 动态加载配置文件profiles:active: dev#设置时区,东八区,(其实也可以设置时间格式spring.jackson.date-format=yyyy-MM-dd HH:mm:ss,然后在代码中就可以使用@JsonFormat)jackson:time-zone: GMT+8#是否支持对Redis的数据进行操作?暂时先这样认为data:redis:repositories:enabled: false#springboot整合JPA,jpa就是一个ORM框架。Jpa(Java Persistence API)Java持久化API,可以简化建表#配置 Jpajpa:properties:hibernate:ddl-auto: nonedialect: org.hibernate.dialect.MySQL5InnoDBDialect #Hibernate只是ORM框架的一种(对象关系映射)open-in-view: trueredis:#数据库索引database: ${REDIS_DB:0} #redis 本身支持16个数据库,通过 数据库id 设置,默认为0 (第几个数据库)host: ${REDIS_HOST:127.0.0.1}port: ${REDIS_PORT:6379}password: ${REDIS_PWD:}#连接超时时间timeout: 5000#任务调度配置
task:pool:# 核心线程池大小core-pool-size: 10# 最大线程数max-pool-size: 30# 活跃时间keep-alive-seconds: 60# 队列容量queue-capacity: 50#七牛云
qiniu:# 文件大小 /Mmax-size: 15#邮箱验证码有效时间/秒
code:expiration: 300#密码加密传输,前端公钥加密,后端私钥解密
rsa:private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
dev
#配置数据源
spring:datasource:druid:db-type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.jdbc.Driverurl: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:eladmin}?characterEncoding=UTF-8&useUnicode=true&useSSL=falseusername: ${DB_USER:root}password: ${DB_PWD:123456}# 初始连接数initial-size: 5# 最小连接数min-idle: 15# 最大连接数max-active: 30# 超时时间(以秒数为单位)remove-abandoned-timeout: 180# 获取连接超时时间max-wait: 3000# 连接有效性检测时间time-between-eviction-runs-millis: 60000# 连接在池中最小生存的时间min-evictable-idle-time-millis: 300000# 连接在池中最大生存的时间max-evictable-idle-time-millis: 900000# 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除test-while-idle: true# 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个test-on-borrow: true# 是否在归还到池中前进行检验test-on-return: false# 检测连接是否有效validation-query: select 1# 配置监控统计webStatFilter:enabled: truestat-view-servlet:enabled: trueurl-pattern: '/druid/* 'reset-enable: falsefilter:stat:enabled: true# 记录慢SQLlog-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: truelogin: # 登录相关配置# 登录缓存cache-enable: true# 是否限制单用户登录single-login: false# 验证码login-code:# 验证码类型配置 查看 LoginProperties 类code-type: arithmetic# 登录图形验证码有效时间/分钟expiration: 2# 验证码高度width: 111# 验证码宽度height: 36# 内容长度length: 2# 字体名称,为空则使用默认字体font-name:# 字体大小font-size: 25#jwt
jwt:header: Authorization# 令牌前缀token-start-with: Bearer# 必须使用最少88位的Base64对该令牌进行编码base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=# 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.htmltoken-validity-in-seconds: 14400000# 在线用户keyonline-key: online-token-# 验证码code-key: code-key-# token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期detect: 1800000# 续期时间范围,默认1小时,单位毫秒renew: 3600000#是否允许生成代码,生产环境设置为false
generator:enabled: true#是否开启 swagger-ui
swagger:enabled: true# IP 本地解析
ip:local-parsing: true# 文件存储路径
file:mac:path: ~/file/avatar: ~/avatar/linux:path: /home/eladmin/file/avatar: /home/eladmin/avatar/windows:path: C:\eladmin\file\avatar: C:\eladmin\avatar\# 文件大小 /MmaxSize: 100avatarMaxSize: 5
详细结构
- eladmin-common 公共模块
- annotation 为系统自定义注解
- aspect 自定义注解的切面
- base 提供了Entity、DTO基类和mapstruct的通用mapper
- config 自定义权限实现、redis配置、swagger配置、Rsa配置等
- exception 项目统一异常的处理
- utils 系统通用工具类
- eladmin-system 系统核心模块(系统启动入口)
- config 配置跨域与静态资源,与数据权限
- thread 线程池相关
- modules 系统相关模块(登录授权、系统监控、定时任务、运维管理等)
- config 配置跨域与静态资源,与数据权限
- eladmin-logging 系统日志模块
- eladmin-tools 系统第三方工具模块
- eladmin-generator 系统代码生成模块
二、控制
1、代码生成器可一键生成前后端代码
系统代码生成( eladmin-generator 系统代码生成模块)
系统代码生成器的作用:主要是为了方便生成已有的数据库表的操作,生成简单的增删改查的操作,并生成前后端的代码
模块结构
简单分析一下(没有接触过这个自动生成代码的功能模块):
-domain:
- ColumbInfo:数据库表中字段的相关信息(字段名称、字段数据类型)
- code_gen_config:表的配置(表名、包路径、表前缀、前端文件路径等)
-vo:
- tableInfo:表的信息(表的引擎类型、编码集等)
-utils:
- ColUtil:前端表单中的字段转成java类型
- GenUtil:获取前后端模板,生成后端代码,根据路径生成文件和请求路径
其他是具体的实现.在这里我纠结了半天,这个是怎么实现自动生成controller还有service以及dao层的,后面才醒悟,原来是需要配置了模板才会有的,模板在eladmin-system模块下
2、支持数据字典,可方便地对一些状态进行管理(这个需要看看)
- 作用:数据字典的作用是对一些经常变动的业务字段进行统一的管理,这里没体会到感觉出来实用的地方,类似于下方这种,就比较明显
3、支持接口限流,避免恶意请求导致服务层压力过大
- 作用:顾名思义,就是防止恶意刷接口,导致服务器压力过大而崩溃(个人理解)
- 实现方法:通过自定义注解来实现
/**应用在业务中的方法上*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {// 资源名称,用于描述接口功能String name() default "";// 资源 keyString key() default "";// key prefixString prefix() default "";// 时间的,单位秒int period();// 限制访问次数int count();// 限制类型LimitType limitType() default LimitType.CUSTOMER;}
- 例子:表示在60秒内,这个方法被同一个ip只能访问三次,保存在Redis中的值的key为limit_test
- 限流的切面类:
**@Aspect 表示是一个切面类*/
@Aspect
@Component
public class LimitAspect {private final RedisTemplate<Object,Object> redisTemplate;private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);public LimitAspect(RedisTemplate<Object,Object> redisTemplate) {this.redisTemplate = redisTemplate;}/**表示只要在标注了@Limit注解的方法上去执行这个操作*/@Pointcut("@annotation(me.zhengjie.annotation.Limit)")public void pointcut() {}/*** 在切入点的方法上会对访问次数进行检测,是否满足限制条件* 但是这里用@Around 是不是合适的呢?* @Before是在所拦截方法执行之前执行一段逻辑。@After 是在所拦截方法执行之后执行一段逻辑。@Around是可以同时在所拦截方法的前后执行一段逻辑* */@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {HttpServletRequest request = RequestHolder.getHttpServletRequest();MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method signatureMethod = signature.getMethod();Limit limit = signatureMethod.getAnnotation(Limit.class);LimitType limitType = limit.limitType();String key = limit.key();if (StringUtils.isEmpty(key)) {if (limitType == LimitType.IP) {key = StringUtils.getIp(request);} else {key = signatureMethod.getName();}}ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_")));String luaScript = buildLuaScript();RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());if (null != count && count.intValue() <= limit.count()) {logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());return joinPoint.proceed();} else {throw new BadRequestException("访问次数受限制");}}/*** 限流脚本*/private String buildLuaScript() {return "local c" +"\nc = redis.call('get',KEYS[1])" +"\nif c and tonumber(c) > tonumber(ARGV[1]) then" +"\nreturn c;" +"\nend" +"\nc = redis.call('incr',KEYS[1])" +"\nif tonumber(c) == 1 then" +"\nredis.call('expire',KEYS[1],ARGV[2])" +"\nend" +"\nreturn c;";}
}
4、支持接口级别的功能权限与数据权限,可自定义操作
- 该系统实现系统安全是集成了Spring Security安全框架(重点了解),这个后续必须要有系统的对springsecurity框架去了解,实现原理
- springsecurity的配置在我这种不懂的人看来简直是一团麻,头皮发麻,不太懂需要哪些参数,为什么这么配置,有的大概懂一点点,可怕先贴出来再说,里面加上自己的粗浅理解
- 这个权限的流程在我现在看来,配置了SpringSecurityConfig配置类 ,继承了WebSercurityConfigurerAdapter 类,通过一系列的过滤器,在spring容器加载时,对权限进行限制,一般来说,允许匿名访问的都只是登录接口吧.关于springsecurity框架的底层加载,过后再看,现在这个项目脑子里的这个部门流程还算是清晰的
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {private final TokenProvider tokenProvider; //token生成器private final CorsFilter corsFilter;//跨域过滤器private final JwtAuthenticationEntryPoint authenticationErrorHandler;//验证权限private final JwtAccessDeniedHandler jwtAccessDeniedHandler;//验证权限,拒绝控制(响应错误信息)//spring框架是通过配置文件描述bean与bean之间的依赖关系(通过底层的反射实例化bean,并建立依赖)private final ApplicationContext applicationContext; //ApplicationContext 是:应用上下文,也叫spring容器, 通过spring容器获取对象private final SecurityProperties properties; //jwt配置参数信息private final OnlineUserService onlineUserService; // 在线的用户private final UserCacheClean userCacheClean; //用户登录信息缓存@BeanGrantedAuthorityDefaults grantedAuthorityDefaults() {// 去除 ROLE_ 前缀return new GrantedAuthorityDefaults("");}@Beanpublic PasswordEncoder passwordEncoder() {// 密码加密方式return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// 搜寻匿名标记 url: @AnonymousAccessRequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();// 获取匿名标记(只要在方法上加了@AnonymousAccess注解,请求路径就被获取)Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);httpSecurity// 禁用 CSRF.csrf().disable()//表示在每次跨域请求时,需要先验证用户名和密码?.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)// 授权异常.exceptionHandling()//部分资源不可访问,需要权限认证(@PreAuthorize).authenticationEntryPoint(authenticationErrorHandler)//这个是拒绝策略?.accessDeniedHandler(jwtAccessDeniedHandler)// 防止iframe 造成跨域.and().headers().frameOptions().disable()// 不创建会话,这个作用在哪儿?不创建会话目的是什么? 不创建会话的目的是为了可以让匿名访问吗?// (会话(session)就是无状态的 HTTP 实现用户状态可维持的一种解决方案。// HTTP 本身的无状态使得用户在与服务器的交互过程中,每个请求之间都没有关联性。这意味着用户的访问没有身份记录,站点也无法为用户提供个性化的服务。).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 静态资源等等.antMatchers(HttpMethod.GET,"/*.html","/**/*.html","/**/*.css","/**/*.js","/webSocket/**").permitAll()// 放行 swagger 文档.antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll()// 文件.antMatchers("/avatar/**").permitAll().antMatchers("/file/**").permitAll()// 阿里巴巴 druid.antMatchers("/druid/**").permitAll()// 放行OPTIONS请求,OPTIONS请求是浏览器对于复杂的跨域请求的一种处理方式,会事先先像服务器发送一次参数为options的预请求,如果返回的请求状态为拒绝性质的比如500,那么就不会发送真的请求.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型 (只要有@AnonymousAccess 注解的接口都可以匿名访问)// GET.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()// POST.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()// PUT.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()// PATCH.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()// DELETE.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()// 所有类型的接口都放行.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()// 所有请求都需要认证.anyRequest().authenticated()//所有请求都需要提供token.and().apply(securityConfigurerAdapter());}//携带tokenprivate TokenConfigurer securityConfigurerAdapter() {return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean);}/*** 获取所有的匿名请求路径* @param handlerMethodMap* @return*/private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {Map<String, Set<String>> anonymousUrls = new HashMap<>(6);Set<String> get = new HashSet<>();Set<String> post = new HashSet<>();Set<String> put = new HashSet<>();Set<String> patch = new HashSet<>();Set<String> delete = new HashSet<>();Set<String> all = new HashSet<>();for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {HandlerMethod handlerMethod = infoEntry.getValue();AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);if (null != anonymousAccess) {List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());switch (Objects.requireNonNull(request)) {case GET:get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case POST:post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case PUT:put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case PATCH:patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case DELETE:delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;default:all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;}}}anonymousUrls.put(RequestMethodEnum.GET.getType(), get);anonymousUrls.put(RequestMethodEnum.POST.getType(), post);anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);return anonymousUrls;}
}
5、自定义权限注解与匿名接口注解,可快速对接口拦截与放行
- 这个和springsecurity是互相依赖的,通过自定义注解@AnonymousAccess 以及对REST 风格中 四种常用的请求方式 进行放行
//自定义注解类
/*** @author jacky* 用于标记匿名访问方法*/
@Inherited
@Documented
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}/**这里是springsecurity里配置的允许匿名访问的接口请求类型*/
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型 (只要有@AnonymousAccess 注解的接口都可以匿名访问)// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
//所有请求都需要提供token
.and().apply(securityConfigurerAdapter());
6、对一些常用地前端组件封装:表格数据请求、数据字典等
- 这个暂时没有看,第一时间拉下来的版本是没有的对前端组件进行封装
7、前后端统一异常拦截处理,统一输出异常,避免繁琐的判断(每个项目必备)
- 首先需要确定某些范围的接口,如果产生异常,这个项目中的是ApiError这个类(作用是返回的异常信息中包括哪些信息),和一个全局异常处理类
@Data
class ApiError {private Integer status = 400;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;private String message;private ApiError() {timestamp = LocalDateTime.now();}public static ApiError error(String message){ApiError apiError = new ApiError();apiError.setMessage(message);return apiError;}public static ApiError error(Integer status, String message){ApiError apiError = new ApiError();apiError.setStatus(status);apiError.setMessage(message);return apiError;}
}
======================================================
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 处理所有不可知的异常*/@ExceptionHandler(Throwable.class)public ResponseEntity<ApiError> handleException(Throwable e){// 打印堆栈信息log.error(ThrowableUtil.getStackTrace(e));return buildResponseEntity(ApiError.error(e.getMessage()));}/*** BadCredentialsException* 登录异常*/@ExceptionHandler(BadCredentialsException.class)public ResponseEntity<ApiError> badCredentialsException(BadCredentialsException e){// 打印堆栈信息String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage();log.error(message);return buildResponseEntity(ApiError.error(message));}/*** 处理自定义异常* 请求错误异常*/@ExceptionHandler(value = BadRequestException.class)public ResponseEntity<ApiError> badRequestException(BadRequestException e) {// 打印堆栈信息log.error(ThrowableUtil.getStackTrace(e));return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage()));}/*** 处理 EntityExist*/@ExceptionHandler(value = EntityExistException.class)public ResponseEntity<ApiError> entityExistException(EntityExistException e) {// 打印堆栈信息log.error(ThrowableUtil.getStackTrace(e));return buildResponseEntity(ApiError.error(e.getMessage()));}/*** 处理 EntityNotFound*/@ExceptionHandler(value = EntityNotFoundException.class)public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) {// 打印堆栈信息log.error(ThrowableUtil.getStackTrace(e));return buildResponseEntity(ApiError.error(NOT_FOUND.value(),e.getMessage()));}/*** 处理所有接口数据验证异常*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){// 打印堆栈信息log.error(ThrowableUtil.getStackTrace(e));String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();String msg = "不能为空";if(msg.equals(message)){message = str[1] + ":" + message;}return buildResponseEntity(ApiError.error(message));}/*** 统一返回*/private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));}
}
8、支持在线用户管理与服务器性能监控,支持限制单用户登录
在线用户管理
- 1.在用户登录的时候在Redis里保存用户的登录信息
//登录授权接口里的String token = tokenProvider.createToken(authentication);final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();// 保存在线信息onlineUserService.save(jwtUserDto, token, request);
/*** 保存在线用户信息* @param jwtUserDto /* @param token /* @param request /*/public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){String dept = jwtUserDto.getUser().getDept().getName();String ip = StringUtils.getIp(request);String browser = StringUtils.getBrowser(request);String address = StringUtils.getCityInfo(ip);OnlineUserDto onlineUserDto = null;try {onlineUserDto = new OnlineUserDto(jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());} catch (Exception e) {log.error(e.getMessage(),e);}redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000);}
- 2.查询在线用户接口
/*** 查询全部数据* @param filter /* @param pageable /* @return /*/public Map<String,Object> getAll(String filter, Pageable pageable){List<OnlineUserDto> onlineUserDtos = getAll(filter);return PageUtil.toPage(PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos),onlineUserDtos.size());}/*** 查询全部数据,不分页* @param filter /* @return /*/public List<OnlineUserDto> getAll(String filter){List<String> keys = redisUtils.scan(properties.getOnlineKey() + "*");Collections.reverse(keys);List<OnlineUserDto> onlineUserDtos = new ArrayList<>();for (String key : keys) {OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key);if(StringUtils.isNotBlank(filter)){if(onlineUserDto.toString().contains(filter)){onlineUserDtos.add(onlineUserDto);}} else {onlineUserDtos.add(onlineUserDto);}}onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));return onlineUserDtos;}
- 3.单点登录,
9、支持运维管理,可方便地对远程服务器的应用进行部署与管理
三、数据接口
1、用户管理:提供用户的相关配置,新增用户后,默认密码为123456
2、角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限
3、菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单
4、部门管理:可配置系统组织架构,树形表格展示
5、岗位管理:配置各个部门的职位
6、字典管理:可维护常用一些固定的数据,如:状态,性别等
7、系统日志:记录用户操作日志与异常日志,方便开发人员定位排错
8、SQL监控:采用druid 监控数据库访问性能,默认用户名admin,密码123456
9、定时任务:整合Quartz做定时任务,加入任务日志,任务运行情况一目了然
10、代码生成:高灵活度生成前后端代码,减少大量重复的工作任务
11、邮件工具:配合富文本,发送html格式的邮件
12、七牛云存储:可同步七牛云存储的数据到系统,无需登录七牛云直接操作云数据
13、支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试
14、服务监控:监控服务器的负载情况
15、运维管理:一键部署你的应用
四、懵逼之处
1、对自动生成前后端代码不太了解
2、对于ResponseEntity没有使用过,一般都是项目内自定义的返回结果工具类
对于这点,可以查看使用ResponseEntity处理API返回
开源项目eladmin--笔记相关推荐
- 在Ubuntu 16.04.04 LTS上调研QUIC开源项目minq笔记
minq项目的主旨是minimum quic(最小化实现的QUIC),指最简单的QUIC项目,目前只实现了IETF QUIC的draft 5部分功能,远不成熟,不能用于生产环境. 它内部依赖LTS 1 ...
- TouTiao开源项目 分析笔记10 实现通用普通文章片段页面
1.RxJava的Observable数据操作符总结 1.1.Map操作符 Map操作符对原始Observable发射的没一项数据应用一个你选择的函数, 然后返回一个发射这些结果的Observable ...
- 开源项目学习笔记(1)——狗屁不通文章生成器(BullshitGenerator)
参考资料:1.Python中用json.load() json.loads()加载json数据的方法:https://blog.csdn.net/xiongchengluo1129/article/d ...
- 【媒体控制器】开源项目学习笔记(基于Arduino Micro开发板)
☑️ 首先说明:本项目基于Arduino Micro 开发板开发的,外设只用到了EC11E1534408无定位旋转编码器. 项目来源:[DIY]自制PC外设-媒体控制器,在英国_哔哩哔哩_bilibi ...
- TouTiao开源项目 分析笔记7 加载数据的过程
1.以新闻页中的段子数据显示为例 1.1.首先执行InitApp==>SplashActivity. 因为在AndroidManifest.xml中定义了一个<intent-filter& ...
- TouTiao开源项目 分析笔记9 实现一个问答主页面
1.根据API返回创建几个基础的Bean 1.1.WendaArticleDataBean类 API返回的数据如下: /*** cell_type : 36* extra : {"wenda ...
- TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现
1.预览效果 1.1.首先看一下需要实现的效果. 第一种,文字类型新闻. 第二种,图片类型新闻. 1.2.在NewsArticleTextViewBinder中设置了点击事件 RxView.click ...
- TouTiao开源项目 分析笔记19 问答内容
1.真实页面预览 1.1.成果预览 首先是问答列表 然后每个item设置点击事件,进入问答内容列表 然后每一个问答内容也设置点击事件,进入问答详情 1.2.触发事件. 在WendaArticleOne ...
- TouTiao开源项目 分析笔记12 从总体到局部 构建视频主页面
1.构建视频主列表的整体碎片VideoTabLayout 1.1.首先创建一个VideoTabLayout package com.jasonjan.headnews.module.video;imp ...
- Android开源项目SlidingMenu本学习笔记(两)
我们已经出台SlidingMenu使用:Android开源项目SlidingMenu本学习笔记(一个),接下来再深入学习下.依据滑出项的Menu切换到相应的页面 文件夹结构: 点击Bluetooth能 ...
最新文章
- C++继承时的名字遮蔽(二)
- Spring-AOP 静态正则表达式方法匹配切面
- TS基础2(泛型、枚举、元组)-学习笔记
- 《乌合之众》读书笔记(part5)--名望的特性就是阻止我们看到事物的原本面目,彻底麻痹我们的判断力
- pdo连接mysql数据库(简洁明了)
- react-native页面间传递数据的几种方式
- 前端学习(2324):angular初步使用
- pkcs1转pkcs8 php,openssl RSA密钥格式PKCS1和PKCS8相互转换
- Elasticsearch内存
- java静态代码块和构造方法_Java静态代码块和构造方法执行顺序
- 移动App测试实战 第2章 功能测试自动化
- 服务器配置tomcat,使用浏览器访问服务器资源
- Linux上mysql忘记密码重置密码
- ztree树默认根据ID默认选中该条数据
- MacOS Aria2GUI配置
- 关于CSS中背景图片透明度问题
- “腾源虎”表情包超萌上线,更有4000份定制红包封面免费送!
- 高富帅的颜色插值方法:在视觉感知线性变化的色彩空间中进行颜色插值
- Vmware设置网络设置
- 1 核 2G 服务器安装 gitlab-ce