diff --git a/src/main/java/com/zhgd/jeecg/common/execption/CodeEnum.java b/src/main/java/com/zhgd/jeecg/common/execption/CodeEnum.java index 10150d868..0b6b2ea66 100644 --- a/src/main/java/com/zhgd/jeecg/common/execption/CodeEnum.java +++ b/src/main/java/com/zhgd/jeecg/common/execption/CodeEnum.java @@ -43,6 +43,10 @@ public enum CodeEnum { CREDENTIALS_EXPIRED(3003, "credentials_expired"), ACCOUNT_LOCKED(3004, "account_locked"), USERNAME_NOT_FOUND(3005, "username_not_found"), + /** + * 需要验证码 + */ + NEED_CODE(3006, "need_code"), /** * 请求错误 diff --git a/src/main/java/com/zhgd/mybatis/DataScopeHandler.java b/src/main/java/com/zhgd/mybatis/DataScopeHandler.java index 431c9045f..37d479145 100644 --- a/src/main/java/com/zhgd/mybatis/DataScopeHandler.java +++ b/src/main/java/com/zhgd/mybatis/DataScopeHandler.java @@ -21,12 +21,17 @@ import com.zhgd.xmgl.modules.xz.service.impl.XzSupplierQualificationApplyService import com.zhgd.xmgl.security.entity.UserInfo; import com.zhgd.xmgl.security.util.SecurityUtils; import com.zhgd.xmgl.util.EnvironmentUtil; +import com.zhgd.xmgl.util.MapBuilder; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.*; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; @@ -41,10 +46,12 @@ import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.PlainSelect; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @@ -70,6 +77,9 @@ public class DataScopeHandler implements DataPermissionHandler { @Lazy @Autowired private IOcrBuildLogService ocrBuildLogService; + @Lazy + @Autowired + private IXzSecurityQualityInspectionEnterpriseService xzSecurityQualityInspectionEnterpriseService; @Override public Expression getSqlSegment(Expression where, String mappedStatementId) { @@ -143,10 +153,6 @@ public class DataScopeHandler implements DataPermissionHandler { //} } - @Lazy - @Autowired - private IXzSecurityQualityInspectionEnterpriseService xzSecurityQualityInspectionEnterpriseService; - private PlainSelect dataScopeFilterByProject(PlainSelect plainSelect, UserInfo user, Object obj) { JSONObject jo = (JSONObject) obj; Object parameter = jo.get("parameter"); @@ -154,8 +160,28 @@ public class DataScopeHandler implements DataPermissionHandler { init(plainSelect); //expressions List expressions = new ArrayList<>(); + Long userId = SecurityUtils.getUser().getUserId(); if (!DataScopeInterceptor.findIgnoreDataScope(parameter, ds)) { - if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.PROJECT_SUB_ACCOUNT.getValue())) { + if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.ENTERPRISE_ADMINISTRATOR_ACCOUNT.getValue())) { + filterCompany(plainSelect, ds, expressions, userId, (userFilterItem) -> get1CompanySql(userId, userFilterItem)); + } else if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.ENTERPRISE_DISTRICT_ACCOUNT.getValue())) { + filterCompany(plainSelect, ds, expressions, userId, (userFilterItem) -> get2CompanySql(userId, userFilterItem)); + } else if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.ENTERPRISE_CITY_ACCOUNT.getValue())) { + filterCompany(plainSelect, ds, expressions, userId, (userFilterItem) -> get3CompanySql(userId, userFilterItem)); + } else if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.ENTERPRISE_SUB_ACCOUNT.getValue())) { + filterCompany(plainSelect, ds, expressions, userId, (userFilterItem) -> get4CompanySql(userId, userFilterItem)); + } else if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.PROJECT_ACCOUNT.getValue())) { + List userFilterItems = getAuthUserFilterItem(plainSelect, ds); + for (String userFilterItem : userFilterItems) { + String sql = StrUtil.format(" \n" + + " {} in (\n" + + " select u.sn\n" + + " from system_user u\n" + + " WHERE u.user_id={}\n" + + " )", userFilterItem, userId); + expressions.add(parseCondExpression(sql)); + } + } else if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.PROJECT_SUB_ACCOUNT.getValue())) { List authEnterpriseIds = userEnterpriseService.getEnterpriseIdsIfSubProject(); authEnterpriseIds.add("0"); List filterEnterprises = getNeedFilterLeftExpression(plainSelect, getFieldEnterpriseTables(), ds); @@ -182,14 +208,8 @@ public class DataScopeHandler implements DataPermissionHandler { videoItems = videoItems.stream().map(s -> "'" + s + "'").collect(Collectors.toList()); for (String filterAi : filterAis) { String sql = StrUtil.format(" ({}.hardware_id in ({}) OR ({}.quality_region_id in (select distinct quality_region_id from quality_region_to_user where user_id = {}))) ", - filterAi, StrUtil.join(",", videoItems), filterAi, SecurityUtils.getUser().getUserId()); - Expression expression = null; - try { - expression = CCJSqlParserUtil.parseCondExpression(sql); - expressions.add(expression); - } catch (JSQLParserException e) { - log.error(e.getMessage(), e); - } + filterAi, StrUtil.join(",", videoItems), filterAi, userId); + expressions.add(parseCondExpression(sql)); } } @@ -201,7 +221,7 @@ public class DataScopeHandler implements DataPermissionHandler { for (String filterEnterprise : filterOcrBuildLogTables) { String uploaderIdField = StrUtil.subBefore(filterEnterprise, ".", false) + "." + "uploader_id"; String sql = StrUtil.format(" ( ({} in (select ocr_build_log_id from ocr_build_log_enterprise where enterprise_id in ({}))) OR ( {} = {}))", - filterEnterprise, StrUtil.join(",", authEnterpriseIds), uploaderIdField, SecurityUtils.getUser().getUserId()); + filterEnterprise, StrUtil.join(",", authEnterpriseIds), uploaderIdField, userId); try { Expression expression = CCJSqlParserUtil.parseCondExpression(sql); expressions.add(expression); @@ -228,7 +248,7 @@ public class DataScopeHandler implements DataPermissionHandler { } else if (Objects.equals(user.getAccountType(), SystemUserAccountTypeEnum.SUPPLIER.getValue())) { List filterEnterprises = getNeedFilterLeftExpression(plainSelect, getFieldEnterpriseTables(), ds); - EnterpriseInfo ei = enterpriseInfoMapper.getXzSupplierByUserId(SecurityUtils.getUser().getUserId()); + EnterpriseInfo ei = enterpriseInfoMapper.getXzSupplierByUserId(userId); Long id; if (ei == null) { id = -1L; @@ -239,13 +259,7 @@ public class DataScopeHandler implements DataPermissionHandler { String sql = StrUtil.format(" ({} = {} OR {} IN ( SELECT DISTINCT t.enterprise_id FROM " + "(SELECT t.id FROM project_enterprise t WHERE t.enterprise_id = {}) t2 join project_enterprise t on find_in_set( t2.id, ancestors ) )) ", filterEnterprise, id, filterEnterprise, id); - Expression expression = null; - try { - expression = CCJSqlParserUtil.parseCondExpression(sql); - expressions.add(expression); - } catch (JSQLParserException e) { - log.error(e.getMessage(), e); - } + expressions.add(parseCondExpression(sql)); } //解析ai预警 @@ -258,17 +272,10 @@ public class DataScopeHandler implements DataPermissionHandler { videoItems = videoItems.stream().map(s -> "'" + s + "'").collect(Collectors.toList()); for (String filterAi : filterAis) { String sql = StrUtil.format(" ({}.hardware_id in ({}) OR ({}.quality_region_id in (select distinct quality_region_id from quality_region_to_user where user_id = {}))) ", - filterAi, StrUtil.join(",", videoItems), filterAi, SecurityUtils.getUser().getUserId()); - Expression expression = null; - try { - expression = CCJSqlParserUtil.parseCondExpression(sql); - expressions.add(expression); - } catch (JSQLParserException e) { - log.error(e.getMessage(), e); - } + filterAi, StrUtil.join(",", videoItems), filterAi, userId); + expressions.add(parseCondExpression(sql)); } } - } if (expressions.size() > 0) { Expression dataExpression; @@ -339,6 +346,155 @@ public class DataScopeHandler implements DataPermissionHandler { return plainSelect; } + /** + * 过滤企业的用户权限 + * + * @param plainSelect + * @param ds + * @param expressions + * @param userId + * @param companySqlProvider + */ + private void filterCompany(PlainSelect plainSelect, DataScope ds, List expressions, Long userId, Function companySqlProvider) { + List userFilterItems = getAuthUserFilterItem(plainSelect, ds); + List ownFilterItems = getOwnUserFilterItem(plainSelect, ds); + for (int i = 0; i < userFilterItems.size(); i++) { + String userFilterItem = userFilterItems.get(i); + String sql = StrUtil.format(" (({}) OR {}={})", + companySqlProvider.apply(userFilterItem), + ownFilterItems.get(i), + userId); + expressions.add(parseCondExpression(sql)); + } + } + + @NotNull + private List getOwnUserFilterItem(PlainSelect plainSelect, DataScope ds) { + List ownFilterItems = getNeedFilterLeftExpression(plainSelect, new MapBuilder() + .put("system_user", "user_id") + .build(), ds); + return ownFilterItems; + } + + /** + * 获取企业查询的用户权限的sql的表和字段 + * + * @param plainSelect + * @param ds + * @return + */ + @NotNull + private List getAuthUserFilterItem(PlainSelect plainSelect, DataScope ds) { + List userFilterItems = getNeedFilterLeftExpression(plainSelect, new MapBuilder() + .put("system_user", "sn") + .build(), ds); + return userFilterItems; + } + + private String get1CompanySql(Long userId, String userFilterItem) { + String sql = StrUtil.format(" \n" + + " {} in (\n" + + " SELECT DISTINCT\n" + + " c.company_sn \n" + + " FROM\n" + + " company c\n" + + " JOIN company head ON c.headquarters_sn = head.company_sn\n" + + " JOIN system_user u ON head.company_sn = u.sn \n" + + " WHERE\n" + + " u.user_id ={}\n" + + " UNION ALL\n" + + " select a.project_sn\n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " INNER JOIN company b ON cp.parent_id = b.company_id\n" + + " INNER JOIN company f ON b.parent_id = f.company_id\n" + + " JOIN system_user u on f.headquarters_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " )", userFilterItem, userId, userId); + return sql; + } + + private String get2CompanySql(Long userId, String userFilterItem) { + String sql = StrUtil.format(" \n" + + " {} in (\n" + + " SELECT DISTINCT\n" + + " cp.company_sn \n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " INNER JOIN company b ON cp.parent_id = b.company_id\n" + + " INNER JOIN company f ON b.parent_id = f.company_id\n" + + " JOIN system_user u on f.company_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " UNION ALL\n" + + " SELECT DISTINCT\n" + + " b.company_sn \n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " INNER JOIN company b ON cp.parent_id = b.company_id\n" + + " INNER JOIN company f ON b.parent_id = f.company_id\n" + + " JOIN system_user u on f.company_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " UNION ALL\n" + + " select a.project_sn\n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " INNER JOIN company b ON cp.parent_id = b.company_id\n" + + " INNER JOIN company f ON b.parent_id = f.company_id\n" + + " JOIN system_user u on f.company_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " )", userFilterItem, userId, userId, userId); + return sql; + } + + private String get3CompanySql(Long userId, String userFilterItem) { + String sql = StrUtil.format(" \n" + + " {} in (\n" + + " SELECT DISTINCT\n" + + " cp.company_sn \n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " INNER JOIN company b ON cp.parent_id = b.company_id\n" + + " JOIN system_user u on b.company_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " UNION ALL\n" + + " select a.project_sn\n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " INNER JOIN company b ON cp.parent_id = b.company_id\n" + + " JOIN system_user u on b.company_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " )", userFilterItem, userId, userId); + return sql; + } + + private String get4CompanySql(Long userId, String userFilterItem) { + String sql = StrUtil.format(" \n" + + " {} in (\n" + + " select a.project_sn\n" + + " FROM project a\n" + + " INNER JOIN company cp ON a.company_sn = cp.company_sn\n" + + " JOIN system_user u on cp.company_sn=u.sn\n" + + " WHERE u.user_id={}\n" + + " )", userFilterItem, userId); + return sql; + } + + /** + * 获取转换后的sql表达式 + * + * @param sql + * @return + */ + private Expression parseCondExpression(String sql) { + Expression expression = null; + try { + expression = CCJSqlParserUtil.parseCondExpression(sql); + } catch (JSQLParserException e) { + log.error(e.getMessage(), e); + } + return expression; + } + /** * 获取需要过滤的表别名或加字段 * diff --git a/src/main/java/com/zhgd/xmgl/config/OperLogAspect.java b/src/main/java/com/zhgd/xmgl/config/OperLogAspect.java index 60c9fd795..d86f90694 100644 --- a/src/main/java/com/zhgd/xmgl/config/OperLogAspect.java +++ b/src/main/java/com/zhgd/xmgl/config/OperLogAspect.java @@ -9,6 +9,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; diff --git a/src/main/java/com/zhgd/xmgl/constant/Cts.java b/src/main/java/com/zhgd/xmgl/constant/Cts.java index 09498e45e..509c041f9 100644 --- a/src/main/java/com/zhgd/xmgl/constant/Cts.java +++ b/src/main/java/com/zhgd/xmgl/constant/Cts.java @@ -116,4 +116,8 @@ public interface Cts { * 手机号登录的验证码前缀 */ String LOGIN_VERIFICATION_CODE = "LOGIN_VERIFICATION_CODE_"; + /** + * 敏感字符 + */ + String SENSITIVE_CHAR = "******"; } diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/LoginController.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/LoginController.java index cb988964f..dc70eb0ad 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/LoginController.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/LoginController.java @@ -1,6 +1,8 @@ package com.zhgd.xmgl.modules.basicdata.controller; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -26,11 +28,10 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.MapUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.*; /** @@ -104,6 +105,33 @@ public class LoginController { return Result.success(resultMap); } + @ApiOperation(value = "获取登录的图片验证码", notes = "获取登录的图片验证码", httpMethod = "GET") + @GetMapping("/login/captcha") + public void getCaptcha(HttpServletResponse response) throws IOException { + String captchaId = UUID.randomUUID().toString(); + LineCaptcha captcha = CaptchaUtil.createLineCaptcha(100, 40, 4, 50); + redisRepository.setExpire("CAPTCHA:" + captchaId, captcha.getCode(), 300); + response.setHeader("captcha-id", captchaId); + response.setHeader("access-control-expose-headers", "captcha-id"); + response.setContentType("image/png"); + captcha.write(response.getOutputStream()); + } + + @OperLog(operModul = "用户登录", operType = "账号密码登录(带验证码)", operDesc = "账号密码登录(带验证码)") + @ApiOperation(value = "账号密码登录(带验证码)", notes = "账号密码登录(带验证码)", httpMethod = "POST") + @ApiImplicitParams({ + @ApiImplicitParam(name = "account", required = true, value = "登录账号", paramType = "body"), + @ApiImplicitParam(name = "md5Password", required = true, value = "账号md5密码登录", paramType = "body"), + @ApiImplicitParam(name = "timestamp", required = true, value = "时间戳", paramType = "body"), + @ApiImplicitParam(name = "code", required = true, value = "验证码", paramType = "body"), + }) + @PostMapping(value = "/verify/login") + public Result> verifyLogin(@RequestBody Map map, @RequestHeader("captcha-id") String captchaId) { + map.put("captchaId", captchaId); + Map resultMap = systemUserService.verifyLogin(map); + return Result.success(resultMap); + } + @OperLog(operModul = "用户登录", operType = "用户id登录", operDesc = "用户id登录") @ApiOperation(value = "用户id登录", notes = "用户id登录", httpMethod = "POST") @ApiImplicitParams({ diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/UploadFileController.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/UploadFileController.java index 839d113a7..9679adce1 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/UploadFileController.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/UploadFileController.java @@ -42,10 +42,11 @@ public class UploadFileController { } Map resultMap = new HashMap<>(16); try { - resultMap = uploadFileService.uploadImage(files); + resultMap = uploadFileService.uploadImageSafety(files); + } catch (SecurityException e) { + throw new OpenAlertException("安全限制: " + e.getMessage(), e); } catch (Exception e) { - log.error("error:", e); - + throw new OpenAlertException("上传失败: " + e.getMessage(), e); } return resultMap; } diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/mapper/SystemUserMapper.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/mapper/SystemUserMapper.java index 791011389..e6d8514cd 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/mapper/SystemUserMapper.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/mapper/SystemUserMapper.java @@ -21,6 +21,7 @@ import java.util.*; * @version: V1.0 */ @Mapper +@DataScope(includeTable = {"system_user"}) public interface SystemUserMapper extends BaseMapper { /** * 根据企业或项目SN查找账号列表 @@ -37,6 +38,7 @@ public interface SystemUserMapper extends BaseMapper { * @param page * @return */ + @DataScope(includeTable = {"system_user"}) Page getSystemUsersBySn(@Param("param") Map map, Page page); /** diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/ISystemUserService.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/ISystemUserService.java index f44e79474..26522d05a 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/ISystemUserService.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/ISystemUserService.java @@ -381,4 +381,11 @@ public interface ISystemUserService extends IService { */ Map getUserMapByProjectSn(String projectSn); + /** + * 带验证码登录 + * + * @param map + * @return + */ + Map verifyLogin(Map map); } diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/UploadFileService.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/UploadFileService.java index 75a590244..8d883bff0 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/UploadFileService.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/UploadFileService.java @@ -25,8 +25,11 @@ import java.util.Map; * @date 2017年7月20日 下午4:38:27 */ public interface UploadFileService { + Map uploadImageSafety(MultipartFile[] files) throws Exception; + /** * 文件上传 + * * @param files * @return * @throws Exception diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/SystemUserServiceImpl.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/SystemUserServiceImpl.java index bd71e344b..0d30ef5c6 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/SystemUserServiceImpl.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/SystemUserServiceImpl.java @@ -19,8 +19,10 @@ import com.wflow.bean.entity.WflowModels; import com.wflow.mapper.WflowModelsMapper; import com.zhgd.exception.CustomException; import com.zhgd.jeecg.common.api.vo.Result; +import com.zhgd.jeecg.common.execption.CodeEnum; import com.zhgd.jeecg.common.execption.OpenAlertException; import com.zhgd.jeecg.common.mybatis.EntityMap; +import com.zhgd.jeecg.common.util.SpringContextUtils; import com.zhgd.redis.lock.RedisRepository; import com.zhgd.xmgl.entity.sj.JwtPayloadUserInfo; import com.zhgd.xmgl.modules.basicdata.entity.*; @@ -74,6 +76,7 @@ import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.flowable.engine.HistoryService; import org.flowable.engine.history.HistoricProcessInstance; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; @@ -230,20 +233,20 @@ public class SystemUserServiceImpl extends ServiceImpl= i) { - throw new OpenAlertException("您已登录失败次数达到5次,锁定账号时间10分钟,请稍后重试"); + throw new OpenAlertException("账户或密码错误,登录失败次数超出阈值,请10分钟之后再尝试"); } if (systemUser == null || systemUser.getUserId() == null) { log.info("查询不到systemUser"); - failedPrompt(key, num); + failedPrompt(failedCountKey, num); } else { if (!passwordEncoder.matches(password, systemUser.getPassword())) { log.info("密码不正确,原:{},现:{}", password, systemUser.getPassword()); - failedPrompt(key, num); + failedPrompt(failedCountKey, num); } } @@ -266,6 +269,17 @@ public class SystemUserServiceImpl extends ServiceImpl= loginNum) { - throw new OpenAlertException("您已登录失败次数达到5次,锁定账号时间10分钟,请稍后重试"); + throw new OpenAlertException("用户名或密码错误,您已登录失败次数达到5次,锁定时间10分钟,请稍后重试"); } if (systemUser == null || systemUser.getUserId() == null) { @@ -1610,19 +1624,21 @@ public class SystemUserServiceImpl extends ServiceImpl() .eq(SystemUser::getAccount, systemUser.getAccount()))); if (count == 0) { - throw new OpenAlertException("账号不存在"); + //账号不存在 + throw new OpenAlertException("用户名或密码错误"); } } @@ -1719,7 +1737,8 @@ public class SystemUserServiceImpl extends ServiceImpl() .eq(SystemUser::getAccount, systemUser.getAccount())); if (su == null) { - throw new OpenAlertException("账号不存在"); + //账号不存在 + throw new OpenAlertException("用户名或密码错误"); } String code = NumberUtils.randomNum(6); redisRepository.set(UPDATE_PW_EMAIL_CODE + su.getAccount(), code, 300L); @@ -1881,4 +1900,44 @@ public class SystemUserServiceImpl extends ServiceImpl() .eq(SystemUser::getSn, projectSn)).stream().collect(Collectors.toMap(SystemUser::getUserId, Function.identity(), (o1, o2) -> o1)); } + + @Override + public Map verifyLogin(Map map) { + String account = MapUtils.getString(map, "account"); + String code = MapUtils.getString(map, "code"); + // 从缓存获取失败次数 + String failedCountKey = getFailedCountKey(account); + Integer failCount = (Integer) redisRepository.get(failedCountKey); + // 如果失败次数≥3,但请求没带验证码或验证码错误,则拒绝 + if (failCount != null && failCount >= 2) { + if (StrUtil.isBlank(code)) { + throw new OpenAlertException(CodeEnum.NEED_CODE.getCode(), "请输入验证码"); + } + //校验验证码 + validCode(code, MapUtils.getString(map, "captchaId")); + } + try { + return md5Login(map); + } catch (Exception e) { + throw new OpenAlertException(e.getMessage()); + } + } + + /** + * 校验验证码 + * + * @param code + * @param captchaId + */ + private void validCode(String code, String captchaId) { + String key = "CAPTCHA:" + captchaId; + String storedCode = (String) redisRepository.get(key); + if (storedCode == null) { + throw new OpenAlertException("验证码过期"); + } + if (!storedCode.equalsIgnoreCase(code)) { + redisRepository.del(key); + throw new OpenAlertException("验证码错误"); + } + } } diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/UploadFileServiceImpl.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/UploadFileServiceImpl.java index 9fd088cdb..55ecdbe29 100644 --- a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/UploadFileServiceImpl.java +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/UploadFileServiceImpl.java @@ -7,11 +7,13 @@ import com.zhgd.file.FileUtil; import com.zhgd.jeecg.common.api.vo.Result; import com.zhgd.xmgl.modules.basicdata.entity.vo.UploadImageVo; import com.zhgd.xmgl.modules.basicdata.service.UploadFileService; +import com.zhgd.xmgl.util.FileSecurityUtil; import com.zhgd.xmgl.util.MessageUtil; import com.zhgd.xmgl.util.UrlUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -19,6 +21,14 @@ import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; @@ -40,6 +50,164 @@ public class UploadFileServiceImpl implements UploadFileService { @Autowired private FileStorageService fileStorageService; + @Lazy + @Autowired + private FileSecurityUtil fileSecurityUtil; + + /** + * 安全文件上传方法 + */ + public Map uploadImageSafety(MultipartFile[] files) throws Exception { + Map result = new HashMap<>(); + List> dataList = new ArrayList<>(); + try { + for (MultipartFile file : files) { + // 1. 安全检查 + if (!validateFile(file)) { + throw new SecurityException("文件安全检查未通过: " + file.getOriginalFilename()); + } + + // 2. 生成安全文件名 + String safeFilename = fileSecurityUtil.generateSafeFilename(file.getOriginalFilename()); + + // 3. 保存文件 + String filePath = saveFileSecurely(file, safeFilename); + + // 4. 构建返回数据(保持原有格式) + Map dataMap = new HashMap<>(); + dataMap.put("filename", safeFilename); + dataMap.put("imageUrl", safeFilename); // 只返回文件名,隐藏路径 + + // 构建fileInfo对象 + Map fileInfo = buildFileInfo(file, safeFilename, filePath); + dataMap.put("fileInfo", fileInfo); + + dataList.add(dataMap); + } + + result.put("data", dataList); + result.put("status", "SUCCESS"); + + } catch (Exception ex) { + result.put("status", "ERROR"); + result.put("data", Collections.emptyList()); + result.put("msg", "文件上传失败: " + ex.getMessage()); + throw ex; + } + + return result; + } + + /** + * 构建fileInfo对象(保持原有结构) + */ + private Map buildFileInfo(MultipartFile file, String safeFilename, String filePath) { + Map fileInfo = new HashMap<>(); + String originalFilename = file.getOriginalFilename(); + String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase(); + + fileInfo.put("id", null); + fileInfo.put("url", safeFilename); // 只返回文件名,隐藏真实路径 + fileInfo.put("size", String.valueOf(file.getSize())); + fileInfo.put("filename", safeFilename); + fileInfo.put("originalFilename", originalFilename); + fileInfo.put("basePath", ""); // 隐藏真实路径 + fileInfo.put("path", ""); // 隐藏真实路径 + fileInfo.put("ext", ext); + fileInfo.put("contentType", file.getContentType()); + fileInfo.put("platform", "local"); + fileInfo.put("thUrl", null); + fileInfo.put("thFilename", null); + fileInfo.put("thSize", null); + fileInfo.put("thContentType", null); + fileInfo.put("objectId", null); + fileInfo.put("objectType", null); + fileInfo.put("attr", new HashMap<>()); + fileInfo.put("createTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + + return fileInfo; + } + + /** + * 全面的文件验证 + */ + private boolean validateFile(MultipartFile file) throws IOException { + // 1. 文件大小检查 + if (!fileSecurityUtil.isValidSize(file)) { + throw new IllegalArgumentException("文件大小超过限制"); + } + + // 2. 扩展名检查 + if (!fileSecurityUtil.isValidExtension(file.getOriginalFilename())) { + throw new SecurityException("不支持的文件类型"); + } + + // 3. MIME类型检查 + if (!fileSecurityUtil.isValidMimeType(file)) { + throw new SecurityException("文件MIME类型不匹配"); + } + + // 4. 内容安全检查 + if (!fileSecurityUtil.isFileContentSafe(file)) { + throw new SecurityException("文件内容安全检查未通过"); + } + + return true; + } + + /** + * 安全保存文件 + */ + private String saveFileSecurely(MultipartFile file, String filename) + throws IOException { + + Path uploadPath = Paths.get(basePath); + + // 确保目录存在且权限正确 + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + // 设置目录权限(禁止执行) + setDirectoryPermissions(uploadPath); + + Path filePath = uploadPath.resolve(filename); + + // 保存文件 + Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); + + // 设置文件权限(只读) + setFilePermissions(filePath); + + return filePath.toString(); + } + + /** + * 设置目录权限(禁止执行) + */ + private void setDirectoryPermissions(Path path) throws IOException { + // Linux系统设置权限 + if (!System.getProperty("os.name").toLowerCase().contains("win")) { + Set perms = new HashSet<>(); + perms.add(PosixFilePermission.OWNER_READ); + perms.add(PosixFilePermission.OWNER_WRITE); + perms.add(PosixFilePermission.GROUP_READ); + Files.setPosixFilePermissions(path, perms); + } + } + + /** + * 设置文件权限(只读) + */ + private void setFilePermissions(Path path) throws IOException { + // Linux系统设置权限 + if (!System.getProperty("os.name").toLowerCase().contains("win")) { + Set perms = new HashSet<>(); + perms.add(PosixFilePermission.OWNER_READ); + perms.add(PosixFilePermission.GROUP_READ); + Files.setPosixFilePermissions(path, perms); + } + } /** * @throws Exception 2017年7月20日 下午5:17:36 @throws diff --git a/src/main/java/com/zhgd/xmgl/util/FileSecurityUtil.java b/src/main/java/com/zhgd/xmgl/util/FileSecurityUtil.java new file mode 100644 index 000000000..46e50320d --- /dev/null +++ b/src/main/java/com/zhgd/xmgl/util/FileSecurityUtil.java @@ -0,0 +1,126 @@ +package com.zhgd.xmgl.util; + +import cn.hutool.core.util.StrUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Component +public class FileSecurityUtil { + // 最大文件大小 10000MB + public static final long MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024L; + // 允许的文件类型白名单 + public static Set ALLOWED_EXTENSIONS = new HashSet(); + // 允许的MIME类型 + public static Set ALLOWED_MIME_TYPES = new HashSet<>(); + + @Value("${file.upload.allowed-extensions}") + public void setAllowedExtensions(String allowedExtensions) { + ALLOWED_EXTENSIONS.addAll(StrUtil.split(allowedExtensions, ",")); + } + + @Value("${file.upload.allowed-mime-types}") + public void setAllowedMimeTypes(String allowedMimeTypes) { + ALLOWED_MIME_TYPES.addAll(StrUtil.split(allowedMimeTypes, ",")); + } + + /** + * 检查文件扩展名 + */ + public boolean isValidExtension(String filename) { + if (filename == null) return false; + String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(); + return ALLOWED_EXTENSIONS.contains(ext); + } + + /** + * 检查MIME类型 + */ + public boolean isValidMimeType(MultipartFile file) { + try { + String mimeType = file.getContentType(); + return ALLOWED_MIME_TYPES.contains(mimeType); + } catch (Exception e) { + return false; + } + } + + /** + * 检查文件大小 + */ + public boolean isValidSize(MultipartFile file) { + return file.getSize() <= MAX_FILE_SIZE; + } + + /** + * 安全重命名文件 + */ + public String generateSafeFilename(String originalFilename) { + String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); + String baseName = UUID.randomUUID().toString().replace("-", ""); + return baseName + ext.toLowerCase(); + } + + /** + * 文件内容安全检查 + */ + public boolean isFileContentSafe(MultipartFile file) { + try { + // 1. 检查文件头是否符合声明类型 + if (!validateFileHeader(file)) { + return false; + } + +// // 2. 病毒扫描(如果有的话) +// if (antivirusService != null) { +// return antivirusService.scanFile(file.getBytes()); +// } + + // 3. 检查是否包含可疑内容(简单示例) + return !containsSuspiciousContent(file); + + } catch (Exception e) { + return false; + } + } + + private boolean validateFileHeader(MultipartFile file) throws IOException { + byte[] header = new byte[20]; + try (InputStream is = file.getInputStream()) { + is.read(header); + } + + // 简单的文件头验证逻辑 + String mimeType = file.getContentType(); + if (mimeType.startsWith("image/")) { + return isImageFile(header); + } + + return true; // 对于非图片文件,可以放宽检查 + } + + private boolean isImageFile(byte[] header) { + // 简单的图片文件头检查 + return (header[0] == (byte) 0xFF && header[1] == (byte) 0xD8) || // JPEG + (header[0] == (byte) 0x89 && header[1] == (byte) 0x50 && // PNG + header[2] == (byte) 0x4E && header[3] == (byte) 0x47); + } + + private boolean containsSuspiciousContent(MultipartFile file) throws IOException { + // 检查是否包含可执行文件特征(简单示例) + byte[] content = file.getBytes(); + String contentStr = new String(content).toLowerCase(); + +// return contentStr.contains("