diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java index b6cdb7329..9aa621e14 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java @@ -114,4 +114,11 @@ public interface AuthServiceApi { */ void checkAuth(String token, String requestUrl); + /** + * 取消冻结帐号 + * + * @author xixiaowei + * @date 2022/1/22 16:37 + */ + void cancelFreeze(LoginRequest loginRequest); } diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/LoginCacheConstants.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/LoginCacheConstants.java new file mode 100644 index 000000000..5ab8cfaf1 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/LoginCacheConstants.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kernel.auth.api.constants; + +/** + * 登录前缀相关的常量 + * + * @author xixiaowei + * @date 2022/1/22 17:37 + */ +public interface LoginCacheConstants { + + /** + * 登录最大次数 + */ + Integer MAX_LOGIN_COUNT = 5; + + /** + * 登录冻结缓存前缀 + */ + String LOGIN_CACHE_PREFIX = "login:"; + + /** + * 冻结时间 + */ + Long LOGIN_CACHE_TIMEOUT_SECONDS = 1800L; +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java index ac449cee8..22b60362b 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java @@ -121,7 +121,12 @@ public enum AuthExceptionEnum implements AbstractExceptionEnum { /** * 无法访问未经授权的接口 */ - CANT_REQUEST_UN_OPEN_API(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "17", "无法访问未经授权的接口,未授权url为:{}"); + CANT_REQUEST_UN_OPEN_API(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "17", "无法访问未经授权的接口,未授权url为:{}"), + + /** + * 超过最大登录次数 + */ + EXCEED_MAX_LOGIN_COUNT(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "18", "超过最大登录次数,帐号被锁定"); /** * 错误编码 diff --git a/kernel-d-auth/auth-sdk/pom.xml b/kernel-d-auth/auth-sdk/pom.xml index 853b42278..c98b881c7 100644 --- a/kernel-d-auth/auth-sdk/pom.xml +++ b/kernel-d-auth/auth-sdk/pom.xml @@ -126,6 +126,11 @@ ${roses.version} + + cn.stylefeng.roses + system-business-user + ${roses.version} + diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java index e0938cc9b..1c10fbad0 100644 --- a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java @@ -24,6 +24,7 @@ */ package cn.stylefeng.roses.kernel.auth.auth; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.codec.Base64; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.CharsetUtil; @@ -36,6 +37,7 @@ import cn.hutool.http.HttpResponse; import cn.stylefeng.roses.kernel.auth.api.AuthServiceApi; import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; import cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants; +import cn.stylefeng.roses.kernel.auth.api.constants.LoginCacheConstants; import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum; @@ -47,6 +49,9 @@ import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginWithTokenRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.auth.session.cache.loginuser.RedisLoginUserCache; +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import cn.stylefeng.roses.kernel.cache.memory.operator.DefaultMemoryCacheOperator; import cn.stylefeng.roses.kernel.demo.expander.DemoConfigExpander; import cn.stylefeng.roses.kernel.jwt.JwtTokenOperator; import cn.stylefeng.roses.kernel.jwt.api.context.JwtContext; @@ -66,10 +71,13 @@ import cn.stylefeng.roses.kernel.system.api.ResourceServiceApi; import cn.stylefeng.roses.kernel.system.api.UserServiceApi; import cn.stylefeng.roses.kernel.system.api.enums.UserStatusEnum; import cn.stylefeng.roses.kernel.system.api.pojo.user.UserLoginInfoDTO; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUser; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserService; import cn.stylefeng.roses.kernel.validator.api.exception.enums.ValidatorExceptionEnum; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -119,6 +127,15 @@ public class AuthServiceImpl implements AuthServiceApi { @Resource private ResourceServiceApi resourceServiceApi; + @Resource + private SysUserService sysUserService; + + @Resource + private CacheOperatorApi loginCacheOperatorApi; + + @Value("${login.enable}") + private Boolean enable; + @Override public LoginResponse login(LoginRequest loginRequest) { return loginAction(loginRequest, true, null); @@ -254,6 +271,25 @@ public class AuthServiceImpl implements AuthServiceApi { * @date 2020/10/21 16:59 */ private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword, String caToken) { + SysUser userByAccount = sysUserService.getUserByAccount(loginRequest.getAccount()); + // 判断登录错误检测是否开启 + if (enable) { + // 判断错误次数,超过最大放入缓存中 + if (StrUtil.isBlank(loginCacheOperatorApi.get(userByAccount.getUserId().toString()))) { + if (userByAccount.getLoginCount() > LoginCacheConstants.MAX_LOGIN_COUNT) { + loginCacheOperatorApi.put(userByAccount.getUserId().toString(), "true", 1800L); + throw new AuthException(AuthExceptionEnum.EXCEED_MAX_LOGIN_COUNT); + } + } else { + throw new AuthException(AuthExceptionEnum.EXCEED_MAX_LOGIN_COUNT); + } + } + + // 5. 获取用户密码的加密值和用户的状态 + UserLoginInfoDTO userValidateInfo = userServiceApi.getUserLoginInfo(loginRequest.getAccount()); + + // 8. 获取LoginUser,用于用户的缓存 + LoginUser loginUser = userValidateInfo.getLoginUser(); // 1.参数为空校验 if (validatePassword) { @@ -275,6 +311,8 @@ public class AuthServiceImpl implements AuthServiceApi { throw new AuthException(ValidatorExceptionEnum.CAPTCHA_EMPTY); } if (!captchaApi.validateCaptcha(verKey, verCode)) { + // 登录失败日志 + loginLogServiceApi.loginFail(loginUser.getUserId(), "验证码错误"); throw new AuthException(ValidatorExceptionEnum.CAPTCHA_ERROR); } } @@ -288,6 +326,8 @@ public class AuthServiceImpl implements AuthServiceApi { throw new AuthException(ValidatorExceptionEnum.CAPTCHA_EMPTY); } if (!dragCaptchaApi.validateCaptcha(verKey, Convert.toInt(verXLocationValue))) { + // 登录失败日志 + loginLogServiceApi.loginFail(loginUser.getUserId(), "拖拽验证码错误"); throw new AuthException(ValidatorExceptionEnum.DRAG_CAPTCHA_ERROR); } } @@ -308,13 +348,15 @@ public class AuthServiceImpl implements AuthServiceApi { return new LoginResponse(remoteLoginCode); } - // 5. 获取用户密码的加密值和用户的状态 - UserLoginInfoDTO userValidateInfo = userServiceApi.getUserLoginInfo(loginRequest.getAccount()); - // 6. 校验用户密码是否正确 if (validatePassword) { Boolean checkResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), userValidateInfo.getUserPasswordHexed()); if (!checkResult) { + //更新登录次数 + userByAccount.setLoginCount(userByAccount.getLoginCount() + 1); + sysUserService.updateById(userByAccount); + // 登录失败日志 + loginLogServiceApi.loginFail(loginUser.getUserId(), "帐号或密码错误"); throw new AuthException(AuthExceptionEnum.USERNAME_PASSWORD_ERROR); } } @@ -324,9 +366,6 @@ public class AuthServiceImpl implements AuthServiceApi { throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR, UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus())); } - // 8. 获取LoginUser,用于用户的缓存 - LoginUser loginUser = userValidateInfo.getLoginUser(); - // 9. 生成用户的token DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(loginUser.getUserId(), loginUser.getAccount(), loginRequest.getRememberMe(), caToken); String jwtToken = JwtContext.me().generateTokenDefaultPayload(defaultJwtPayload); @@ -355,6 +394,10 @@ public class AuthServiceImpl implements AuthServiceApi { String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest()); userServiceApi.updateUserLoginInfo(loginUser.getUserId(), new Date(), ip); + //重置登录次数 + userByAccount.setLoginCount(1); + sysUserService.updateById(userByAccount); + // 13.登录成功日志 loginLogServiceApi.loginSuccess(loginUser.getUserId()); } @@ -403,4 +446,13 @@ public class AuthServiceImpl implements AuthServiceApi { return loginCode; } + @Override + public void cancelFreeze(LoginRequest loginRequest) { + SysUser sysUser = sysUserService.getUserByAccount(loginRequest.getAccount()); + sysUser.setLoginCount(1); + // 修改数据库中的登录次数 + sysUserService.updateById(sysUser); + // 删除缓存中的数据 + loginCacheOperatorApi.remove(sysUser.getUserId().toString()); + } } diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/cache/LoginMemoryCache.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/cache/LoginMemoryCache.java new file mode 100644 index 000000000..f4a891061 --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/cache/LoginMemoryCache.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.auth.cache; + +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.auth.api.constants.LoginCacheConstants; +import cn.stylefeng.roses.kernel.cache.memory.AbstractMemoryCacheOperator; + +/** + * 用户帐号冻结的缓存 + * + * @author xixiaowei + * @date 2022/1/22 17:33 + */ +public class LoginMemoryCache extends AbstractMemoryCacheOperator { + + public LoginMemoryCache(TimedCache timedCache) { + super(timedCache); + } + + @Override + public String getCommonKeyPrefix() { + return LoginCacheConstants.LOGIN_CACHE_PREFIX; + } +} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/cache/LoginRedisCache.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/cache/LoginRedisCache.java new file mode 100644 index 000000000..07539c61e --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/cache/LoginRedisCache.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.auth.cache; + +import cn.stylefeng.roses.kernel.auth.api.constants.LoginCacheConstants; +import cn.stylefeng.roses.kernel.cache.redis.AbstractRedisCacheOperator; +import org.springframework.data.redis.core.RedisTemplate; + +/** + * 用户帐号冻结的缓存 + * + * @author xixiaowei + * @date 2022/1/23 23:34 + */ +public class LoginRedisCache extends AbstractRedisCacheOperator { + + public LoginRedisCache(RedisTemplate redisTemplate) { + super(redisTemplate); + } + + @Override + public String getCommonKeyPrefix() { + return LoginCacheConstants.LOGIN_CACHE_PREFIX; + } +} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/config/AccountErrorDetectionConfig.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/config/AccountErrorDetectionConfig.java new file mode 100644 index 000000000..cc8d63ea7 --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/config/AccountErrorDetectionConfig.java @@ -0,0 +1,22 @@ +package cn.stylefeng.roses.kernel.auth.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 控制帐号错误检测开关 + * + * @author xixiaowei + * @date 2022/1/23 23:42 + */ +@Data +@Component +@ConfigurationProperties(prefix = "login") +public class AccountErrorDetectionConfig { + + /** + * 开关:true-开启错误检测,false-关闭错误检测 + */ + private Boolean enable; +} diff --git a/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsLoginCacheAutoConfiguration.java b/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsLoginCacheAutoConfiguration.java new file mode 100644 index 000000000..ea3e2035c --- /dev/null +++ b/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsLoginCacheAutoConfiguration.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.auth.starter; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.auth.api.constants.LoginCacheConstants; +import cn.stylefeng.roses.kernel.auth.cache.LoginMemoryCache; +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 登录缓存的自动配置 + * + * @author xixiaowei + * @date 2022/1/22 17:40 + */ +@Configuration +public class GunsLoginCacheAutoConfiguration { + + /** + * 登录帐号冻结的缓存 + * + * @author xixiaowei + * @date 2022/1/22 17:45 + */ + @Bean + @ConditionalOnMissingBean(name = "loginCacheOperatorApi") + public CacheOperatorApi loginCacheOperatorApi() { + TimedCache loginTimeCache = CacheUtil.newTimedCache(LoginCacheConstants.LOGIN_CACHE_TIMEOUT_SECONDS * 1000); + return new LoginMemoryCache(loginTimeCache); + } +} diff --git a/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories index b97464eef..27e18f974 100644 --- a/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.stylefeng.roses.kernel.auth.starter.GunsAuthAutoConfiguration,\ - cn.stylefeng.roses.kernel.auth.starter.GunsSsoAutoConfiguration \ No newline at end of file + cn.stylefeng.roses.kernel.auth.starter.GunsSsoAutoConfiguration,\ + cn.stylefeng.roses.kernel.auth.starter.GunsLoginCacheAutoConfiguration \ No newline at end of file diff --git a/kernel-d-db/db-spring-boot-starter/pom.xml b/kernel-d-db/db-spring-boot-starter/pom.xml index b0f12032c..851f11cc0 100644 --- a/kernel-d-db/db-spring-boot-starter/pom.xml +++ b/kernel-d-db/db-spring-boot-starter/pom.xml @@ -38,6 +38,11 @@ ${roses.version} + + com.alibaba + druid-spring-boot-starter + 1.2.8 + diff --git a/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/RemoveDruidAdConfig.java b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/RemoveDruidAdConfig.java new file mode 100644 index 000000000..1128773b0 --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/RemoveDruidAdConfig.java @@ -0,0 +1,68 @@ +package cn.stylefeng.roses.kernel.db.starter; + +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.*; +import java.io.IOException; + +@Configuration +@ConditionalOnWebApplication +@AutoConfigureAfter(DruidDataSourceAutoConfigure.class) +@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true) +public class RemoveDruidAdConfig { + + + /** + * 方法名: removeDruidAdFilterRegistrationBean + * 方法描述: 除去页面底部的广告 + * @param properties + * @return org.springframework.boot.web.servlet.FilterRegistrationBean + * @throws + */ + @Bean + public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + + final String filePath = "support/http/resources/js/common.js"; + + //创建filter进行过滤 + Filter filter = new Filter() { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + + @Override + public void destroy() { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} \ No newline at end of file diff --git a/kernel-s-system/system-business-user/pom.xml b/kernel-s-system/system-business-user/pom.xml index 2e47dbeaa..3ff479793 100644 --- a/kernel-s-system/system-business-user/pom.xml +++ b/kernel-s-system/system-business-user/pom.xml @@ -114,11 +114,11 @@ - - cn.stylefeng.roses - auth-sdk - ${roses.version} - + + + + + @@ -126,6 +126,20 @@ spring-boot-starter-web + + + cn.stylefeng.roses + jwt-api + 7.1.6 + + + + + cn.stylefeng.roses + message-api + 7.1.6 + + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java index 8e7b58a4b..57ffd8fc9 100644 --- a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java @@ -143,4 +143,15 @@ public class LoginController { return new SuccessResponseData<>(haveSessionFlag); } + /** + * 取消帐号冻结 + * + * @author xixiaowei + * @date 2022/1/22 16:40 + */ + @PostResource(name = "取消帐号冻结", path = "/cancelFreeze") + public ResponseData cancelFreeze(@RequestBody LoginRequest loginRequest) { + authServiceApi.cancelFreeze(loginRequest); + return new SuccessResponseData<>(); + } } diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java index ee0430b18..d32e1ebc8 100644 --- a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java @@ -154,4 +154,9 @@ public class SysUser extends BaseEntity { @TableField(value = "del_flag", fill = FieldFill.INSERT) private String delFlag; + /** + * 登录次数 + */ + @TableField("login_count") + private Integer loginCount; }