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框架。JpaJava 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 系统相关模块(登录授权、系统监控、定时任务、运维管理等)
  • 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--笔记相关推荐

  1. 在Ubuntu 16.04.04 LTS上调研QUIC开源项目minq笔记

    minq项目的主旨是minimum quic(最小化实现的QUIC),指最简单的QUIC项目,目前只实现了IETF QUIC的draft 5部分功能,远不成熟,不能用于生产环境. 它内部依赖LTS 1 ...

  2. TouTiao开源项目 分析笔记10 实现通用普通文章片段页面

    1.RxJava的Observable数据操作符总结 1.1.Map操作符 Map操作符对原始Observable发射的没一项数据应用一个你选择的函数, 然后返回一个发射这些结果的Observable ...

  3. 开源项目学习笔记(1)——狗屁不通文章生成器(BullshitGenerator)

    参考资料:1.Python中用json.load() json.loads()加载json数据的方法:https://blog.csdn.net/xiongchengluo1129/article/d ...

  4. 【媒体控制器】开源项目学习笔记(基于Arduino Micro开发板)

    ☑️ 首先说明:本项目基于Arduino Micro 开发板开发的,外设只用到了EC11E1534408无定位旋转编码器. 项目来源:[DIY]自制PC外设-媒体控制器,在英国_哔哩哔哩_bilibi ...

  5. TouTiao开源项目 分析笔记7 加载数据的过程

    1.以新闻页中的段子数据显示为例 1.1.首先执行InitApp==>SplashActivity. 因为在AndroidManifest.xml中定义了一个<intent-filter& ...

  6. TouTiao开源项目 分析笔记9 实现一个问答主页面

    1.根据API返回创建几个基础的Bean 1.1.WendaArticleDataBean类 API返回的数据如下: /*** cell_type : 36* extra : {"wenda ...

  7. TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现

    1.预览效果 1.1.首先看一下需要实现的效果. 第一种,文字类型新闻. 第二种,图片类型新闻. 1.2.在NewsArticleTextViewBinder中设置了点击事件 RxView.click ...

  8. TouTiao开源项目 分析笔记19 问答内容

    1.真实页面预览 1.1.成果预览 首先是问答列表 然后每个item设置点击事件,进入问答内容列表 然后每一个问答内容也设置点击事件,进入问答详情 1.2.触发事件. 在WendaArticleOne ...

  9. TouTiao开源项目 分析笔记12 从总体到局部 构建视频主页面

    1.构建视频主列表的整体碎片VideoTabLayout 1.1.首先创建一个VideoTabLayout package com.jasonjan.headnews.module.video;imp ...

  10. Android开源项目SlidingMenu本学习笔记(两)

    我们已经出台SlidingMenu使用:Android开源项目SlidingMenu本学习笔记(一个),接下来再深入学习下.依据滑出项的Menu切换到相应的页面 文件夹结构: 点击Bluetooth能 ...

最新文章

  1. C++继承时的名字遮蔽(二)
  2. Spring-AOP 静态正则表达式方法匹配切面
  3. TS基础2(泛型、枚举、元组)-学习笔记
  4. 《乌合之众》读书笔记(part5)--名望的特性就是阻止我们看到事物的原本面目,彻底麻痹我们的判断力
  5. pdo连接mysql数据库(简洁明了)
  6. react-native页面间传递数据的几种方式
  7. 前端学习(2324):angular初步使用
  8. pkcs1转pkcs8 php,openssl RSA密钥格式PKCS1和PKCS8相互转换
  9. Elasticsearch内存
  10. java静态代码块和构造方法_Java静态代码块和构造方法执行顺序
  11. 移动App测试实战 第2章 功能测试自动化
  12. 服务器配置tomcat,使用浏览器访问服务器资源
  13. Linux上mysql忘记密码重置密码
  14. ztree树默认根据ID默认选中该条数据
  15. MacOS Aria2GUI配置
  16. 关于CSS中背景图片透明度问题
  17. “腾源虎”表情包超萌上线,更有4000份定制红包封面免费送!
  18. 高富帅的颜色插值方法:在视觉感知线性变化的色彩空间中进行颜色插值
  19. Vmware设置网络设置
  20. 1 核 2G 服务器安装 gitlab-ce

热门文章

  1. 面向对象程序设计c++版董正言张聪课本课后习题答案第四章
  2. 伦敦银xag是什么意思?
  3. 【15W字长文】主从复制高可用Redis集群,完整包含Redis所有知识点
  4. 企业管理的智能化趋势
  5. 【微信小程序】学习笔记-----navigation-bar导航栏
  6. 【C++习题笔记】谭浩强C++程序设计(第三版)第七章
  7. windows7 为系统盘瘦身
  8. 一款SpringBoot轻量级物联网综合业务支撑平台,附源码
  9. 调试Kubernetes集群中的网络停顿问题
  10. SpringMVC 关于日期(Date)的接收与返回