commit 989f0210f29aefeac6a40d6f59af1fba0cc106be Author: starrySky <1752476835@qq.com> Date: Wed Mar 20 09:28:04 2024 +0800 first commit diff --git a/play-admin/pom.xml b/play-admin/pom.xml new file mode 100644 index 0000000..16cad1d --- /dev/null +++ b/play-admin/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.starry + play-with + 1.0 + + + play-admin + + + 8 + 8 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.starry + play-common + 1.0 + + + + com.starry + play-generator + 1.0 + + + + + io.jsonwebtoken + jjwt + 0.9.0 + + + + mysql + mysql-connector-java + + + + com.alibaba + easyexcel + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.starry + play-weichat + 1.0 + compile + + + + \ No newline at end of file diff --git a/play-admin/src/main/java/com/starry/admin/Application.java b/play-admin/src/main/java/com/starry/admin/Application.java new file mode 100644 index 0000000..ec6c3c9 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/Application.java @@ -0,0 +1,18 @@ +package com.starry.admin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +/** + * @author admin + */ +@SpringBootApplication +@ComponentScan("com.starry") +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/play-admin/src/main/java/com/starry/admin/common/aspect/DataScopeAspect.java b/play-admin/src/main/java/com/starry/admin/common/aspect/DataScopeAspect.java new file mode 100644 index 0000000..967dd92 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/aspect/DataScopeAspect.java @@ -0,0 +1,146 @@ +package com.starry.admin.common.aspect; + +import cn.hutool.core.convert.Convert; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.annotation.DataScope; +import com.starry.common.context.SecurityContextHolder; +import com.starry.common.domain.BaseEntity; +import com.starry.common.utils.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 数据过滤处理 + * + * @author vctgo + */ +@Aspect +@Component +public class DataScopeAspect { + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUserEntity user, String deptAlias, String userAlias, String permission) { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList<>(); + for (SysRoleEntity role : user.getRoles()) { + String dataScope = role.getDataScope(); + if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) { + continue; + } + if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) { + sqlString = new StringBuilder(); + break; + } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } else if (DATA_SCOPE_DEPT.equals(dataScope)) { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } else if (DATA_SCOPE_SELF.equals(dataScope)) { + if (StringUtils.isNotBlank(userAlias)) { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } else { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + conditions.add(dataScope); + } + + if (StringUtils.isNotBlank(sqlString.toString())) { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) { + if (controllerDataScope == null) { + return; + } + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) { + SysUserEntity currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } + +} diff --git a/play-admin/src/main/java/com/starry/admin/common/aspect/LogAspect.java b/play-admin/src/main/java/com/starry/admin/common/aspect/LogAspect.java new file mode 100644 index 0000000..31929e4 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/aspect/LogAspect.java @@ -0,0 +1,168 @@ +package com.starry.admin.common.aspect; + +import cn.hutool.extra.servlet.ServletUtil; +import com.alibaba.fastjson2.JSON; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.system.entity.SysOperationLogEntity; +import com.starry.admin.modules.system.service.ISysOperationLogService; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.annotation.Log; +import com.starry.common.utils.ServletUtils; +import com.starry.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.HandlerMapping; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +/** + * @author admin + */ +@Aspect +@Component +@Slf4j +public class LogAspect { + + @Resource + private ISysOperationLogService operLogService; + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturn(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, jsonResult); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + try { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // 日志记录 + SysOperationLogEntity operLog = new SysOperationLogEntity(); + operLog.setStatus(0); + // 请求的IP地址 + String iP = ServletUtil.getClientIP(ServletUtils.getRequest()); + if ("0:0:0:0:0:0:0:1".equals(iP)) { + iP = "127.0.0.1"; + } + operLog.setOperIp(iP); + operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); + if (loginUser != null) { + operLog.setOperName(loginUser.getUsername()); + } + if (null != null) { + operLog.setStatus(1); + operLog.setErrorMsg(StringUtils.substring(((Exception) null).getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + operLog.setOperTime(new Date()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 保存数据库 + operLogService.save(operLog); + + } catch (Exception exp) { + log.error("异常信息:{}", exp.getMessage()); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperationLogEntity operLog, Object jsonResult) { + // 设置操作业务类型 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 设置参数的信息 + setRequestValue(joinPoint, operLog); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperationLogEntity operLog) { + String requsetMethod = operLog.getRequestMethod(); + if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) { + String params = argsArrayToString(joinPoint.getArgs()); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } else { + Map paramsMap = (Map) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null) { + for (Object object : paramsArray) { + // 不为空 并且是不需要过滤的 对象 + if (StringUtils.isNotNull(object) && !isFilterObject(object)) { + Object jsonObj = JSON.toJSON(object); + params.append(jsonObj.toString()).append(" "); + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param object 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object object) { + Class clazz = object.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) object; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) object; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return object instanceof MultipartFile || object instanceof HttpServletRequest || object instanceof HttpServletResponse || object instanceof BindingResult; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/component/JwtToken.java b/play-admin/src/main/java/com/starry/admin/common/component/JwtToken.java new file mode 100644 index 0000000..d844190 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/component/JwtToken.java @@ -0,0 +1,338 @@ +package com.starry.admin.common.component; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.common.security.entity.JwtUser; +import com.starry.common.constant.CacheConstants; +import com.starry.common.constant.Constants; +import com.starry.common.constant.SecurityConstants; +import com.starry.common.redis.RedisCache; +import com.starry.common.utils.ServletUtils; +import com.starry.common.utils.StringUtils; +import com.starry.common.utils.ip.AddressUtils; +import com.starry.common.utils.ip.IpUtils; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author admin + * token 组件 + * @since 2021/9/6 + */ +@Slf4j +@Component +public class JwtToken { + + private static final String CLAIM_KEY_USERNAME = "sub"; + private static final String CLAIM_KEY_CREATED = "created"; + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + @Value("${jwt.secret}") + private String secret; + @Value("${jwt.expiration}") + private Long expire; + @Value("${jwt.tokenHeader}") + private String tokenHeader; + @Value("${jwt.tokenHead}") + private String tokenHead; + + @Resource + private RedisCache redisCache; + + + /** + * 从token中获取登录用户名 + */ + public String getUserNameFromToken(String token) { + String username; + try { + Claims claims = getClaimsFromToken(token); + username = claims.getSubject(); + } catch (Exception e) { + username = null; + } + return username; + } + + /** + * 校验token + */ + public boolean validateToken(String token, UserDetails userDetails) { + String username = getUserNameFromToken(token); + return username.equals(userDetails.getUsername()) && !isTokenExpired(token); + } + + /** + * 根据用户信息生成token + */ + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); + claims.put(CLAIM_KEY_CREATED, new Date()); + return generateToken(claims); + } + + /** + * 创建令牌 + * + * @param jwtUser 用户信息 + * @return 令牌 + */ + public String createToken(JwtUser jwtUser) { + String token = IdUtil.fastSimpleUUID(); + jwtUser.setToken(token); + setUserAgent(jwtUser); + refresToken(jwtUser); + + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + return createToken(claims); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) { + String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 判断token是否已经失效 + */ + private boolean isTokenExpired(String token) { + Date expiredDate = getClaimsFromToken(token).getExpiration(); + return expiredDate.before(new Date()); + } + + private String generateToken(Map claims) { + return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()) + // 签名算法 + .signWith(SignatureAlgorithm.HS512, secret).compact(); + } + + /** + * 生成token的过期时间 + */ + private Date generateExpirationDate() { + return new Date(System.currentTimeMillis() + expire * 1000); + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims getClaimsFromToken(String token) { + Claims claims = null; + try { + claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } catch (Exception e) { + log.info("JWT格式验证失败:{}", token); + } + return claims; + } + + /** + * 设置用户代理信息 + * + * @param jwtUser 登录信息 + */ + public void setUserAgent(JwtUser jwtUser) { + UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = ServletUtil.getClientIP(ServletUtils.getRequest()); + jwtUser.setIpaddr(ip); + jwtUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip)); + jwtUser.setBrowser(userAgent.getBrowser().getName()); + jwtUser.setOs(userAgent.getOs().getName()); + } + + public void refresToken(JwtUser jwtUser) { + jwtUser.setLoginTime(System.currentTimeMillis()); + jwtUser.setExpireTime(jwtUser.getLoginTime() + expire * 1000); + String userKey = getTokenKey(jwtUser.getToken()); + redisCache.setCacheObject(userKey, jwtUser, expire, TimeUnit.SECONDS); + } + + private String getTokenKey(String uuid) { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } + + /** + * 获取登录用户身份信息 + * + * @return 用户信息 + */ + public JwtUser getLoginUser(HttpServletRequest request) { + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) { + try { + Claims claims = getClaimsFromToken(token); + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + JwtUser user = redisCache.getCacheObject(userKey); + return user; + } catch (Exception e) { + + } + } + return null; + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) { + // 获取请求头 + String token = request.getHeader(tokenHeader); + if (StringUtils.isNotEmpty(token) && token.startsWith(tokenHead)) { + token = token.replace(tokenHead, ""); + } + return token; + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param jwtUser + * @return 令牌 + */ + public void verifyToken(JwtUser jwtUser) { + long expireTime = jwtUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { + refresToken(jwtUser); + } + } + + /** + * 删除用户身份信息 + */ + public void removeJwtUser(String token) { + if (StringUtils.isNotEmpty(token)) { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + */ + public Map createToken(LoginUser loginUser) { + String token = IdUtil.fastSimpleUUID(); + String userId = loginUser.getUser().getUserId(); + String userName = loginUser.getUser().getUserCode(); + String tenantId = loginUser.getUser().getTenantId(); + Long deptId = loginUser.getUser().getDeptId(); + loginUser.setToken(token); + loginUser.setUserId(userId); + loginUser.setUserName(userName); + loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); + // 添加地址信息 + setUserAgent(loginUser); + refreshToken(loginUser); + + // Jwt存储信息 + Map claimsMap = new HashMap<>(8); + claimsMap.put(SecurityConstants.USER_KEY, token); + claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); + claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); + // 租户id + claimsMap.put(SecurityConstants.DETAILS_TENANT_ID, tenantId); + // 部门id + claimsMap.put(SecurityConstants.DETAILS_DEPT_ID, deptId); + // 接口返回信息 + Map rspMap = new HashMap<>(); + rspMap.put("token", this.createToken(claimsMap)); + rspMap.put("expires_in", expire); + rspMap.put("tenant_id", tenantId); + rspMap.put("tokenHead", tokenHead); + return rspMap; + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) { + UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = ServletUtil.getClientIP(ServletUtils.getRequest()); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOs().getName()); + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expire * 1000); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expire, TimeUnit.MINUTES); + } + + /** + * 获取登录用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getNewLoginUser(HttpServletRequest request) { + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) { + try { + Claims claims = getClaimsFromToken(token); + String uuid = (String) claims.get(SecurityConstants.USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser loginUser = redisCache.getCacheObject(userKey); + com.starry.common.context.SecurityContextHolder.set(SecurityConstants.DETAILS_TENANT_ID, loginUser.getUser().getTenantId()); + com.starry.common.context.SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser); + return loginUser; + } catch (Exception e) { + + } + } + return null; + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { + refreshToken(loginUser); + } + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/component/PermissionService.java b/play-admin/src/main/java/com/starry/admin/common/component/PermissionService.java new file mode 100644 index 0000000..fdd8d0c --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/component/PermissionService.java @@ -0,0 +1,149 @@ +package com.starry.admin.common.component; + + +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.utils.SecurityUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.Set; + +/** + * 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author admin + */ +@Service("customSs") +public class PermissionService { + /** + * 所有权限标识 + */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) { + if (StringUtils.isEmpty(permission)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) { + return false; + } + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) { + if (StringUtils.isEmpty(permissions)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) { + return false; + } + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(PERMISSION_DELIMETER)) { + if (permission != null && hasPermissions(authorities, permission)) { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) { + if (StringUtils.isEmpty(role)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { + return false; + } + for (SysRoleEntity sysRoleEntity : loginUser.getUser().getRoles()) { + String roleKey = sysRoleEntity.getRoleKey(); + if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) { + return true; + } + } + return false; + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) { + return hasRole(role) != true; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) { + if (StringUtils.isEmpty(roles)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { + return false; + } + for (String role : roles.split(ROLE_DELIMETER)) { + if (hasRole(role)) { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/domain/LoginUser.java b/play-admin/src/main/java/com/starry/admin/common/domain/LoginUser.java new file mode 100644 index 0000000..2016e9d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/domain/LoginUser.java @@ -0,0 +1,133 @@ +package com.starry.admin.common.domain; + +import com.starry.admin.modules.system.entity.SysUserEntity; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Date; +import java.util.Set; + +/** + * @author admin + */ +@Data +public class LoginUser implements UserDetails { + private static final long serialVersionUID = 1L; + + /** + * 用户唯一标识 + */ + private String token; + /** + * 用户名id + */ + private String userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 角色列表 + */ + private Set roles; + + /** + * 用户信息 + */ + private SysUserEntity user; + + /** + * 租户租赁截止日期--dhr + */ + private Date tenantEndDate; + + /** + * 租户状态 + */ + private Integer tenantStatus; + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return user.getPassWord(); + } + + @Override + public String getUsername() { + return user.getUserCode(); + } + + /** + * 账户是否未过期 + **/ + @Override + public boolean isAccountNonExpired() { + return true; + } + + /** + * 账户是否未锁定 + **/ + @Override + public boolean isAccountNonLocked() { + return true; + } + + /** + * 密码是否未过期 + **/ + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + /** + * 账户是否激活 + **/ + @Override + public boolean isEnabled() { + return user.getStatus() == 0; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/domain/TreeSelect.java b/play-admin/src/main/java/com/starry/admin/common/domain/TreeSelect.java new file mode 100644 index 0000000..be304d0 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/domain/TreeSelect.java @@ -0,0 +1,46 @@ +package com.starry.admin.common.domain; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.starry.admin.modules.system.entity.SysDeptEntity; +import com.starry.admin.modules.system.entity.SysMenuEntity; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Treeselect树结构实体类 + * @since 2022/7/4 + */ +@Data +public class TreeSelect implements Serializable { + + /** + * 节点ID + */ + private Long id; + + /** + * 节点名称 + */ + private String label; + + /** + * 子节点 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect(SysMenuEntity menu) { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysDeptEntity dept) { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/exception/CustomException.java b/play-admin/src/main/java/com/starry/admin/common/exception/CustomException.java new file mode 100644 index 0000000..16d1efd --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/exception/CustomException.java @@ -0,0 +1,10 @@ +package com.starry.admin.common.exception; + +/** + * 自定义异常处理器 * * @author admin + */ +public class CustomException extends RuntimeException { + public CustomException(String msg) { + super(msg); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/exception/ServiceException.java b/play-admin/src/main/java/com/starry/admin/common/exception/ServiceException.java new file mode 100644 index 0000000..7c34a52 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/exception/ServiceException.java @@ -0,0 +1,63 @@ +package com.starry.admin.common.exception; + +/** + * @author 业务异常 + * @since 2023/3/9 + */ +public class ServiceException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() { + } + + public ServiceException(String message) { + this.message = message; + } + + public ServiceException(String message, Integer code) { + this.message = message; + this.code = code; + } + + public String getDetailMessage() { + return detailMessage; + } + + public ServiceException setDetailMessage(String detailMessage) { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public ServiceException setMessage(String message) { + this.message = message; + return this; + } + + public Integer getCode() { + return code; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/exception/handler/GlobalExceptionHandler.java b/play-admin/src/main/java/com/starry/admin/common/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..f118c45 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,71 @@ +package com.starry.admin.common.exception.handler; + + +import com.starry.admin.common.exception.CustomException; +import com.starry.admin.common.exception.ServiceException; +import com.starry.common.result.R; +import com.starry.common.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author 全局异常处理 + * @since 2023/3/9 + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestUrl, e); + return R.error("系统发生未知异常,请联系管理员"); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public R handleException(Exception e, HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestUrl, e); + return R.error("系统出现内部错误,请联系管理员"); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public R methodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { + BindingResult bindingResult = e.getBindingResult(); + StringBuilder errorMessageBuilder = new StringBuilder(); + for (FieldError error : bindingResult.getFieldErrors()) { + errorMessageBuilder.append(error.getDefaultMessage()); + } + return R.error("请求参数异常," + errorMessageBuilder); + } + + @ExceptionHandler(CustomException.class) + public R customException(CustomException e, HttpServletRequest request) { + return R.error(e.getMessage()); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/mybatis/config/MybatisPlusConfig.java b/play-admin/src/main/java/com/starry/admin/common/mybatis/config/MybatisPlusConfig.java new file mode 100644 index 0000000..0d3c5c7 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/mybatis/config/MybatisPlusConfig.java @@ -0,0 +1,79 @@ +package com.starry.admin.common.mybatis.config; + +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import com.starry.admin.common.mybatis.handler.MyTenantLineHandler; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + + +/** + * @author admin + */ +@Configuration +@MapperScan("com.starry.**.mapper") +@EnableTransactionManagement +public class MybatisPlusConfig { + + /** + * druid注入 + * + * @return dataSource + */ + @Bean(name = "dataSource") + @ConfigurationProperties(prefix = "spring.datasource.druid") + public DataSource dataSource() { + return DruidDataSourceBuilder.create().build(); + } + + /** + * 配置事物管理器 + * + * @return DataSourceTransactionManager + */ + @Bean(name = "transactionManager") + public DataSourceTransactionManager transactionManager() { + return new DataSourceTransactionManager(dataSource()); + } + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 租户插件 + interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler())); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + // 阻断插件 + interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); + return interceptor; + } + + /** + * 乐观锁 + */ + @Bean + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + + /** + * 防止全表更新与删除 + */ + @Bean + public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { + return new BlockAttackInnerInterceptor(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyMetaObjectHandler.java b/play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyMetaObjectHandler.java new file mode 100644 index 0000000..cd46206 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyMetaObjectHandler.java @@ -0,0 +1,45 @@ +package com.starry.admin.common.mybatis.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.starry.admin.utils.SecurityUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * @author admin + * 字段默认值处理类 + * @since 2021/9/1 + */ +@Slf4j +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + log.info("start insert fill ...."); + this.setFieldValByName("createdTime", new Date(), metaObject); + this.setFieldValByName("deleted", false, metaObject); + this.setFieldValByName("version", 1L, metaObject); + Object createUser = this.getFieldValByName("createdBy", metaObject); + if (createUser == null) { + if (SecurityUtils.isLogin()) { + this.setFieldValByName("createdBy", SecurityUtils.getUserId(), metaObject); + } + } + } + + @Override + public void updateFill(MetaObject metaObject) { + log.info("start update fill ...."); + this.setFieldValByName("updatedTime", new Date(), metaObject); + Object createUser = this.getFieldValByName("updatedBy", metaObject); + if (createUser == null) { + if (SecurityUtils.isLogin()) { + this.setFieldValByName("updatedBy", SecurityUtils.getUserId(), metaObject); + } + } + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyTenantLineHandler.java b/play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyTenantLineHandler.java new file mode 100644 index 0000000..0b48b22 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyTenantLineHandler.java @@ -0,0 +1,54 @@ +package com.starry.admin.common.mybatis.handler; + +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.NullValue; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +/** + * @author admin + * 多租户处理器 + * @since 2023/3/7 + */ +@Slf4j +@Component +public class MyTenantLineHandler implements TenantLineHandler { + + /** + * 排除过滤的表 + */ + private static final String[] TABLE_FILTER = {"sys_menu", "sys_tenant_package", "sys_tenant", "sys_dict", "sys_dict_data"}; + + /** + * 排除过滤的表前缀 + */ + private static final String[] TABLE_PRE = {"qrtz", "gen"}; + + @Override + public Expression getTenantId() { + // 取出当前请求的服务商ID,通过解析器注入到SQL中。 + Long tenantId = SecurityUtils.getTenantId(); + if (tenantId == null) { + return new NullValue(); + } + return new LongValue(tenantId); + } + + /** + * 跳过不需要加多租户的表 + */ + @Override + public boolean ignoreTable(String tableName) { + String prefix = StringUtils.substringBefore(tableName, "_"); + if (Arrays.asList(TABLE_FILTER).contains(tableName) || Arrays.asList(TABLE_PRE).contains(prefix)) { + return true; + } + return false; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/security/config/SpringSecurityConfig.java b/play-admin/src/main/java/com/starry/admin/common/security/config/SpringSecurityConfig.java new file mode 100644 index 0000000..abc0375 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/security/config/SpringSecurityConfig.java @@ -0,0 +1,124 @@ +package com.starry.admin.common.security.config; + + +import com.starry.admin.common.security.entity.JwtUser; +import com.starry.admin.common.security.filter.JwtAuthenticationTokenFilter; +import com.starry.admin.common.security.handler.CustomAccessDeniedHandler; +import com.starry.admin.common.security.handler.CustomAuthenticationEntryPoint; +import com.starry.admin.common.security.handler.CustomLogoutSuccessHandler; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.service.SysMenuService; +import com.starry.admin.modules.system.service.SysUserService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import javax.annotation.Resource; +import java.util.Set; + +/** + * @author admin + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { + + @Resource + private CustomAccessDeniedHandler customAccessDeniedHandler; + @Resource + private CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + @Resource + private SysUserService sysUserService; + @Resource + private SysMenuService menuService; + @Resource + private CustomLogoutSuccessHandler customLogoutSuccessHandler; + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + httpSecurity.csrf().disable()// 由于使用的是JWT,我们这里不需要csrf + .sessionManagement()// 基于token,所以不需要session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + // 允许对于网站静态资源的无授权访问 + .antMatchers(HttpMethod.GET, + "/", + "/*.html", + "/favicon.ico", + "/**/*.html", + "/**/*.css", + "/**/*.js", + "/swagger-resources/**", + "/v2/api-docs/**" + ).permitAll() + // 对登录注册要允许匿名访问 + .antMatchers("/login", "/captcha/get-captcha").permitAll() + // 跨域请求会先进行一次options请求 + .antMatchers(HttpMethod.OPTIONS).permitAll() + .anyRequest()// 除上面外的所有请求全部需要鉴权认证 + .authenticated(); + // 禁用缓存 + httpSecurity.headers().cacheControl(); + // 添加Logout filter + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(customLogoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); + // 添加自定义未授权和未登录结果返回 + httpSecurity.exceptionHandling() + .accessDeniedHandler(customAccessDeniedHandler) + .authenticationEntryPoint(customAuthenticationEntryPoint); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService()) + .passwordEncoder(passwordEncoder()); + } + + @Bean + @Override + public UserDetailsService userDetailsService() { + // 获取登录用户信息 + return username -> { + SysUserEntity user = sysUserService.selectUserByUserName(username); + if (user != null) { + if (user.getStatus() == 1) { + throw new UsernameNotFoundException("对不起,您的账号:" + username + " 已停用"); + } + // 获取菜单权限 + Set permissionList = menuService.selectMenuPermsByUserId(user.getUserId()); + return new JwtUser(user, permissionList); + } + throw new UsernameNotFoundException("用户名或密码错误"); + }; + } + + /** + * 装载BCrypt密码编码器 + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * JWT filter + */ + @Bean + public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { + return new JwtAuthenticationTokenFilter(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/security/entity/JwtUser.java b/play-admin/src/main/java/com/starry/admin/common/security/entity/JwtUser.java new file mode 100644 index 0000000..ef173fe --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/security/entity/JwtUser.java @@ -0,0 +1,202 @@ +package com.starry.admin.common.security.entity; + +import com.starry.admin.modules.system.entity.SysUserEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Set; + +/** + * @author admin + */ +public class JwtUser implements UserDetails { + + /** + * 用户ID + */ + private Long userId; + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 用户信息 + */ + private SysUserEntity user; + + /** + * 权限列表 + */ + private Set permissions; + + + public JwtUser(SysUserEntity user, Set permissions) { + this.user = user; + this.permissions = permissions; + } + + public JwtUser(Long userId, SysUserEntity user, Set permissions) { + this.userId = userId; + this.user = user; + this.permissions = permissions; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return user.getPassWord(); + } + + @Override + public String getUsername() { + return user.getUserCode(); + } + + /** + * 账户是否未过期 + **/ + @Override + public boolean isAccountNonExpired() { + return true; + } + + /** + * 账户是否未锁定 + **/ + @Override + public boolean isAccountNonLocked() { + return true; + } + + /** + * 密码是否未过期 + **/ + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + /** + * 账户是否激活 + **/ + @Override + public boolean isEnabled() { + return user.getStatus() == 0; + } + + public Long getLoginTime() { + return loginTime; + } + + public void setLoginTime(Long loginTime) { + this.loginTime = loginTime; + } + + public Set getPermissions() { + return permissions; + } + + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + + public SysUserEntity getUser() { + return user; + } + + public void setUser(SysUserEntity user) { + this.user = user; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getIpaddr() { + return ipaddr; + } + + public void setIpaddr(String ipaddr) { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) { + this.loginLocation = loginLocation; + } + + public String getBrowser() { + return browser; + } + + public void setBrowser(String browser) { + this.browser = browser; + } + + public String getOs() { + return os; + } + + public void setOs(String os) { + this.os = os; + } + + public Long getExpireTime() { + return expireTime; + } + + public void setExpireTime(Long expireTime) { + this.expireTime = expireTime; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/security/filter/JwtAuthenticationTokenFilter.java b/play-admin/src/main/java/com/starry/admin/common/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..a8adb5f --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,47 @@ +package com.starry.admin.common.security.filter; + + +import com.starry.admin.common.component.JwtToken; +import com.starry.admin.common.domain.LoginUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.Resource; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author admin + * JWT登录授权过滤器 + * @since 2021/9/6 + */ +@Slf4j +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + + @Value("${jwt.tokenHeader}") + private String tokenHeader; + @Value("${jwt.tokenHead}") + private String tokenHead; + @Resource + private JwtToken jwtToken; + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + LoginUser jwtUser = jwtToken.getNewLoginUser(httpServletRequest); + if (null != jwtUser && null == SecurityContextHolder.getContext().getAuthentication()) { + jwtToken.verifyToken(jwtUser); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAccessDeniedHandler.java b/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..e838468 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAccessDeniedHandler.java @@ -0,0 +1,30 @@ +package com.starry.admin.common.security.handler; + +import cn.hutool.json.JSONUtil; +import com.starry.common.result.R; +import com.starry.common.result.ResultCodeEnum; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author admin + * 当访问接口没有权限时,自定义的返回结果 + * @since 2021/9/6 + */ +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { + httpServletResponse.setCharacterEncoding("UTF-8"); + httpServletResponse.setContentType("application/json"); + httpServletResponse.getWriter().println(JSONUtil.parse(R.error(ResultCodeEnum.FORBIDDEN.getCode(), ResultCodeEnum.FORBIDDEN.getMessage()))); + httpServletResponse.getWriter().flush(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAuthenticationEntryPoint.java b/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..637b4f3 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAuthenticationEntryPoint.java @@ -0,0 +1,29 @@ +package com.starry.admin.common.security.handler; + +import cn.hutool.json.JSONUtil; +import com.starry.common.result.R; +import com.starry.common.result.ResultCodeEnum; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author admin + * 当未登录或者token失效访问接口时,自定义的返回结果 + * @since 2021/9/6 + */ +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { + httpServletResponse.setCharacterEncoding("UTF-8"); + httpServletResponse.setContentType("application/json"); + httpServletResponse.getWriter().println(JSONUtil.parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(), ResultCodeEnum.UNAUTHORIZED.getMessage()))); + httpServletResponse.getWriter().flush(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomLogoutSuccessHandler.java b/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomLogoutSuccessHandler.java new file mode 100644 index 0000000..8922c94 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/common/security/handler/CustomLogoutSuccessHandler.java @@ -0,0 +1,41 @@ +package com.starry.admin.common.security.handler; + +import com.alibaba.fastjson2.JSON; +import com.starry.admin.common.component.JwtToken; +import com.starry.admin.common.security.entity.JwtUser; +import com.starry.admin.manager.AsyncManager; +import com.starry.admin.manager.factory.AsyncFactory; +import com.starry.common.constant.Constants; +import com.starry.common.result.R; +import com.starry.common.utils.ServletUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author admin + * 自定义退出处理类 返回成功 + * @since 2022/7/8 + */ +@Component +public class CustomLogoutSuccessHandler implements LogoutSuccessHandler { + + @Resource + private JwtToken jwtToken; + + @Override + public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) { + JwtUser jwtUser = jwtToken.getLoginUser(httpServletRequest); + if (null != jwtUser) { + // 删除用户缓存记录 + jwtToken.removeJwtUser(jwtUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(jwtUser.getUsername(), Constants.LOGOUT, "退出成功")); + } + ServletUtils.renderString(httpServletResponse, JSON.toJSONString(R.ok("退出成功"))); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/manager/AsyncManager.java b/play-admin/src/main/java/com/starry/admin/manager/AsyncManager.java new file mode 100644 index 0000000..7852f90 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/manager/AsyncManager.java @@ -0,0 +1,53 @@ +package com.starry.admin.manager; + + +import com.starry.common.utils.SpringUtils; +import com.starry.common.utils.ThreadsUtils; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * @author admin + * 异步任务管理器 + * @since 2022/7/25 + */ +public class AsyncManager { + + private static final AsyncManager me = new AsyncManager(); + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + /** + * 异步操作任务调度线程池 + */ + private final ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager() { + } + + public static AsyncManager me() { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() { + ThreadsUtils.shutdownAndAwaitTermination(executor); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/manager/ShutdownManager.java b/play-admin/src/main/java/com/starry/admin/manager/ShutdownManager.java new file mode 100644 index 0000000..adc96a8 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/manager/ShutdownManager.java @@ -0,0 +1,34 @@ +package com.starry.admin.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author ruoyi + */ +@Component +public class ShutdownManager { + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() { + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() { + try { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } +} diff --git a/play-admin/src/main/java/com/starry/admin/manager/factory/AsyncFactory.java b/play-admin/src/main/java/com/starry/admin/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..c18c997 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/manager/factory/AsyncFactory.java @@ -0,0 +1,67 @@ +package com.starry.admin.manager.factory; + +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.starry.admin.modules.system.entity.SysLoginLogEntity; +import com.starry.admin.modules.system.service.ISysLoginLogService; +import com.starry.common.constant.Constants; +import com.starry.common.utils.ServletUtils; +import com.starry.common.utils.SpringUtils; +import com.starry.common.utils.ip.AddressUtils; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.TimerTask; + +/** + * @author admin + */ +@Slf4j +public class AsyncFactory { + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLoginLog(final String username, final String status, final String message, final Object... args) { + // 客户端操作系统、浏览器等信息 + final UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); + // 请求的IP地址 + final String ip = ServletUtil.getClientIP(ServletUtils.getRequest()); + return new TimerTask() { + @Override + public void run() { + String address = AddressUtils.getRealAddressByIp(ip); + // 获取客户端操作系统 + String os = userAgent.getOs().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLoginLogEntity loginLog = new SysLoginLogEntity(); + loginLog.setUserCode(username); + loginLog.setIpaddr(ip); + loginLog.setLoginLocation(address); + loginLog.setBrowser(browser); + loginLog.setOs(os); + loginLog.setMsg(message); + loginLog.setLoginTime(new Date()); + // 日志状态 + if (Constants.LOGIN_FAIL.equals(status)) { + loginLog.setStatus(Integer.valueOf(Constants.FAIL)); + } else { + loginLog.setStatus(Integer.valueOf(Constants.SUCCESS)); + } + // 插入数据 + SpringUtils.getBean(ISysLoginLogService.class).create(loginLog); + } + }; + } + +} + diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/CacheController.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/CacheController.java new file mode 100644 index 0000000..493fe89 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/CacheController.java @@ -0,0 +1,101 @@ +package com.starry.admin.modules.monitor.controller; + + +import com.starry.admin.modules.monitor.entity.Cache; +import com.starry.common.constant.CacheConstants; +import com.starry.common.result.R; +import com.starry.common.utils.StringUtils; +import org.springframework.data.redis.connection.RedisServerCommands; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.*; + +/** + * @author huoqiang + * 缓存监控 + * @since 2022/10/17 + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController { + + private final static List CACHES = new ArrayList<>(); + @Resource + private RedisTemplate redisTemplate; + + { + CACHES.add(new Cache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + CACHES.add(new Cache(CacheConstants.SYS_DICT_KEY, "数据字典")); + CACHES.add(new Cache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @GetMapping() + public R getInfo() throws Exception { + Properties info = (Properties) redisTemplate.execute((RedisCallback) RedisServerCommands::info); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) RedisServerCommands::dbSize); + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return R.ok(result); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public R cache() { + return R.ok(CACHES); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public R getCacheKeys(@PathVariable String cacheName) { + Set cacheKeys = redisTemplate.keys(cacheName + "*"); + return R.ok(cacheKeys); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public R getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + Cache cache = new Cache(cacheName, cacheKey, cacheValue); + return R.ok(cache); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public R clearCacheName(@PathVariable String cacheName) { + Collection cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); + return R.ok(); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public R clearCacheKey(@PathVariable String cacheKey) { + redisTemplate.delete(cacheKey); + return R.ok(); + } + + @PreAuthorize("@customSs.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public R clearCacheAll() { + Collection cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return R.ok(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/ServerController.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/ServerController.java new file mode 100644 index 0000000..2f714d7 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/ServerController.java @@ -0,0 +1,27 @@ +package com.starry.admin.modules.monitor.controller; + + +import com.starry.admin.modules.monitor.entity.Server; +import com.starry.common.result.R; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author huoqiang + * 服务器监控 + * @since 2022/10/17 + */ +@RestController +@RequestMapping("/monitor/server") +public class ServerController { + + @PreAuthorize("@customSs.hasPermi('monitor:server:list')") + @GetMapping() + public R getInfo() throws Exception { + Server server = new Server(); + server.setServeInfo(); + return R.ok(server); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/UserOnlineController.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/UserOnlineController.java new file mode 100644 index 0000000..55188b9 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/controller/UserOnlineController.java @@ -0,0 +1,76 @@ +package com.starry.admin.modules.monitor.controller; + + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.monitor.entity.UserOnline; +import com.starry.admin.modules.monitor.service.UserOnlineService; +import com.starry.common.annotation.Log; +import com.starry.common.constant.CacheConstants; +import com.starry.common.enums.BusinessType; +import com.starry.common.redis.RedisCache; +import com.starry.common.result.R; +import com.starry.common.utils.StringUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author huoqiang + * 在线用户监控 + * @since 2022/10/18 + */ +@RestController +@RequestMapping("/monitor/online") +public class UserOnlineController { + + @Resource + private RedisCache redisCache; + @Resource + private UserOnlineService userOnlineService; + + @PreAuthorize("@customSs.hasPermi('monitor:online:list')") + @GetMapping("/list") + public R list(String ipaddr, String userName) { + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List userOnlineList = new ArrayList<>(); + for (String key : keys) { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + } else if (StringUtils.isNotEmpty(ipaddr)) { + if (StringUtils.equals(ipaddr, user.getIpaddr())) { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + } else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) { + if (StringUtils.equals(userName, user.getUsername())) { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + } else { + userOnlineList.add(userOnlineService.setUserOnline(user)); + } + } + IPage page = new Page<>(); + page.setRecords(userOnlineList); + page.setTotal(userOnlineList.size()); + return R.ok(page); + } + + /** + * 强退用户 + */ + @PreAuthorize("@customSs.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public R forceLogout(@PathVariable String tokenId) { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); + return R.ok(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Cache.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Cache.java new file mode 100644 index 0000000..5df3c14 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Cache.java @@ -0,0 +1,49 @@ +package com.starry.admin.modules.monitor.entity; + + +import com.starry.common.utils.StringUtils; +import lombok.Data; + +/** + * @author admin + * 缓存信息 + * @since 2022/10/18 + */ +@Data +public class Cache { + + /** + * 缓存名称 + */ + private String cacheName = ""; + + /** + * 缓存键名 + */ + private String cacheKey = ""; + + /** + * 缓存内容 + */ + private String cacheValue = ""; + + /** + * 备注 + */ + private String remark = ""; + + public Cache() { + + } + + public Cache(String cacheName, String remark) { + this.cacheName = cacheName; + this.remark = remark; + } + + public Cache(String cacheName, String cacheKey, String cacheValue) { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Cpu.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Cpu.java new file mode 100644 index 0000000..98882e7 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Cpu.java @@ -0,0 +1,91 @@ +package com.starry.admin.modules.monitor.entity; + +import com.starry.common.utils.Arith; +import lombok.Data; + +/** + * @author admin + * cpu相关信息 + * @since 2022/10/17 + */ +@Data +public class Cpu { + + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() { + return cpuNum; + } + + public void setCpuNum(int cpuNum) { + this.cpuNum = cpuNum; + } + + public double getTotal() { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) { + this.total = total; + } + + public double getSys() { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) { + this.sys = sys; + } + + public double getUsed() { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) { + this.used = used; + } + + public double getWait() { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) { + this.wait = wait; + } + + public double getFree() { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) { + this.free = free; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Jvm.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Jvm.java new file mode 100644 index 0000000..988d31c --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Jvm.java @@ -0,0 +1,117 @@ +package com.starry.admin.modules.monitor.entity; + +import cn.hutool.core.date.DateUtil; +import com.starry.common.utils.Arith; + +import java.lang.management.ManagementFactory; +import java.util.Date; + +/** + * @author admin + * 虚拟机相关信息 + * @since 2022/10/17 + */ + +public class Jvm { + + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) { + this.total = total; + } + + public double getMax() { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) { + this.max = max; + } + + public double getFree() { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) { + this.free = free; + } + + public double getUsed() { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getHome() { + return home; + } + + public void setHome(String home) { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() { + return DateUtil.format(new Date(ManagementFactory.getRuntimeMXBean().getStartTime()), "yyyy-MM-dd HH:mm:ss"); + } + + /** + * JDK运行时间 + */ + public String getRunTime() { + return DateUtil.formatBetween(new Date(), new Date(ManagementFactory.getRuntimeMXBean().getStartTime())); + } + + /** + * 运行参数 + */ + public String getInputArgs() { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Mem.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Mem.java new file mode 100644 index 0000000..0642703 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Mem.java @@ -0,0 +1,56 @@ +package com.starry.admin.modules.monitor.entity; + +import com.starry.common.utils.Arith; +import lombok.Data; + +/** + * @author admin + * 内存相关信息 + * @since 2022/10/17 + */ +@Data +public class Mem { + + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) { + this.total = total; + } + + public double getUsed() { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) { + this.used = used; + } + + public double getFree() { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) { + this.free = free; + } + + public double getUsage() { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Server.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Server.java new file mode 100644 index 0000000..4598a39 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Server.java @@ -0,0 +1,168 @@ +package com.starry.admin.modules.monitor.entity; + + +import com.starry.common.utils.Arith; +import com.starry.common.utils.ip.IpUtils; +import lombok.Data; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +/** + * @author admin + * 服务器相关信息 + * @since 2022/10/17 + */ +@Data +public class Server { + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 系统相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList<>(); + + public void setServeInfo() throws Exception { + // 系统信息 + SystemInfo si = new SystemInfo(); + // 获取硬件抽象层 + HardwareAbstractionLayer hal = si.getHardware(); + setCpuInfo(hal.getProcessor()); + setMemInfo(hal.getMemory()); + setSysInfo(); + setJvmInfo(); + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(1000); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() { + Properties properties = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(properties.getProperty("os.name")); + sys.setOsArch(properties.getProperty("os.arch")); + sys.setUserDir(properties.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) { + return String.format("%.1f GB", (float) size / gb); + } else if (size >= mb) { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } else if (size >= kb) { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } else { + return String.format("%d B", size); + } + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Sys.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Sys.java new file mode 100644 index 0000000..163173d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/Sys.java @@ -0,0 +1,37 @@ +package com.starry.admin.modules.monitor.entity; + +import lombok.Data; + +/** + * @author admin + * 系统相关信息 + * @since 2022/10/17 + */ +@Data +public class Sys { + + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/SysFile.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/SysFile.java new file mode 100644 index 0000000..c4500d8 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/SysFile.java @@ -0,0 +1,46 @@ +package com.starry.admin.modules.monitor.entity; + +import lombok.Data; + +/** + * @author admin + * 系统文件相关信息 + */ +@Data +public class SysFile { + + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/UserOnline.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/UserOnline.java new file mode 100644 index 0000000..2575408 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/entity/UserOnline.java @@ -0,0 +1,50 @@ +package com.starry.admin.modules.monitor.entity; + +import lombok.Data; + +/** + * @author admin + * 当前在线会话 + */ +@Data +public class UserOnline { + /** + * 会话编号 + */ + private String tokenId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 用户名称 + */ + private String userName; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地址 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 登录时间 + */ + private Long loginTime; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/service/UserOnlineService.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/service/UserOnlineService.java new file mode 100644 index 0000000..66746d0 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/service/UserOnlineService.java @@ -0,0 +1,48 @@ +package com.starry.admin.modules.monitor.service; + + +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.monitor.entity.UserOnline; + +/** + * @author admin + * @since 2022/10/18 + */ +public interface UserOnlineService { + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + UserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); + + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + UserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + UserOnline selectOnlineByUserName(String userName, LoginUser user); + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + UserOnline setUserOnline(LoginUser user); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/monitor/service/impl/UserOnlineServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/monitor/service/impl/UserOnlineServiceImpl.java new file mode 100644 index 0000000..c18db30 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/monitor/service/impl/UserOnlineServiceImpl.java @@ -0,0 +1,59 @@ +package com.starry.admin.modules.monitor.service.impl; + + +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.monitor.entity.UserOnline; +import com.starry.admin.modules.monitor.service.UserOnlineService; +import com.starry.common.utils.StringUtils; +import org.springframework.stereotype.Service; + +/** + * @author admin + * @since 2022/10/18 + */ +@Service +public class UserOnlineServiceImpl implements UserOnlineService { + + @Override + public UserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) { + return setUserOnline(user); + } + return null; + } + + @Override + public UserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) { + if (StringUtils.equals(ipaddr, user.getIpaddr())) { + return setUserOnline(user); + } + return null; + } + + @Override + public UserOnline selectOnlineByUserName(String userName, LoginUser user) { + if (StringUtils.equals(userName, user.getUsername())) { + return setUserOnline(user); + } + return null; + } + + @Override + public UserOnline setUserOnline(LoginUser user) { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) { + return null; + } + UserOnline userOnline = new UserOnline(); + userOnline.setTokenId(user.getToken()); + userOnline.setUserName(user.getUsername()); + userOnline.setIpaddr(user.getIpaddr()); + userOnline.setLoginLocation(user.getLoginLocation()); + userOnline.setBrowser(user.getBrowser()); + userOnline.setOs(user.getOs()); + userOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) { + userOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return userOnline; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/controller/SysTenantController.java b/play-admin/src/main/java/com/starry/admin/modules/platform/controller/SysTenantController.java new file mode 100644 index 0000000..5c89205 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/controller/SysTenantController.java @@ -0,0 +1,94 @@ +package com.starry.admin.modules.platform.controller; + +import com.starry.admin.modules.platform.entity.SysTenantEntity; +import com.starry.admin.modules.platform.service.ISysTenantService; +import com.starry.admin.modules.platform.vo.SysTenantAddVo; +import com.starry.admin.modules.platform.vo.SysTenantQueryVo; +import com.starry.admin.utils.ExcelUtils; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import com.starry.common.utils.ConvertUtil; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; + + +/** + * 租户表Controller + * + * @author admin + */ +@RestController +@RequestMapping("/platform/tenant") +public class SysTenantController { + @Resource + private ISysTenantService sysTenantService; + + + /** + * 新增租户表 + */ + @PreAuthorize("@customSs.hasPermi('platform:tenant:create')") + @Log(title = "租户表", businessType = BusinessType.INSERT) + @PostMapping("/create") + public R create(@Valid @RequestBody SysTenantAddVo vo) { + SysTenantEntity entity = ConvertUtil.entityToVo(vo, SysTenantEntity.class); + return sysTenantService.addTenant(entity); + } + + + + + /** + * 查询租户表列表 + */ + @PreAuthorize("@customSs.hasPermi('platform:tenant:list')") + @GetMapping("/list") + public R list(SysTenantQueryVo vo) { + return R.ok(sysTenantService.selectSysTenantList(vo)); + } + + /** + * 导出租户表列表 + */ + @PreAuthorize("@customSs.hasPermi('platform:tenant:export')") + @Log(title = "租户表", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysTenantQueryVo sysTenantEntity) { + ExcelUtils.exportEasyExcel(response, SysTenantEntity.class, sysTenantService.selectSysTenantList(sysTenantEntity).getRecords(), "租户表数据"); + } + + /** + * 获取租户表详细信息 + */ + @PreAuthorize("@customSs.hasPermi('platform:tenant:query')") + @GetMapping(value = "/{tenantId}") + public R getInfo(@PathVariable("tenantId") String tenantId) { + return R.ok(sysTenantService.selectSysTenantByTenantId(tenantId)); + } + + + /** + * 修改租户表 + */ + @PreAuthorize("@customSs.hasPermi('platform:tenant:edit')") + @Log(title = "租户表", businessType = BusinessType.UPDATE) + @PostMapping(value = "/update/{tenantId}") + public R update(@PathVariable String tenantId, @RequestBody SysTenantEntity sysTenantEntity) { + return sysTenantService.updateTenant(sysTenantEntity); + } + + /** + * 删除租户表 + */ + @PreAuthorize("@customSs.hasPermi('platform:tenant:remove')") + @Log(title = "租户表", businessType = BusinessType.DELETE) + @DeleteMapping("/{tenantIds}") + public R remove(@PathVariable String[] tenantIds) { + return R.ok(sysTenantService.deleteSysTenantByTenantIds(tenantIds)); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/controller/SysTenantPackageController.java b/play-admin/src/main/java/com/starry/admin/modules/platform/controller/SysTenantPackageController.java new file mode 100644 index 0000000..ade298e --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/controller/SysTenantPackageController.java @@ -0,0 +1,108 @@ +package com.starry.admin.modules.platform.controller; + +import com.starry.admin.modules.platform.entity.SysTenantPackageEntity; +import com.starry.admin.modules.platform.service.ISysTenantPackageService; +import com.starry.admin.modules.platform.vo.SimplePackage; +import com.starry.admin.modules.platform.vo.SysTenantPackageVo; +import com.starry.admin.utils.ExcelUtils; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import com.starry.common.utils.ConvertUtil; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + + +/** + * 租户套餐表Controller + * + * @author admin + */ +@RestController +@RequestMapping("/platform/package") +public class SysTenantPackageController { + @Resource + private ISysTenantPackageService SysTenantPackageService; + + /** + * 查询租户套餐表列表 + */ + @PreAuthorize("@customSs.hasPermi('platform:package:list')") + @GetMapping("/list") + public R list(SysTenantPackageVo sysTenantPackageEntity) { + return R.ok(SysTenantPackageService.selectSysTenantPackageList(sysTenantPackageEntity)); + } + + /** + * 导出租户套餐表列表 + */ + @PreAuthorize("@customSs.hasPermi('platform:package:export')") + @Log(title = "租户套餐表", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysTenantPackageVo sysTenantPackageEntity) { + ExcelUtils.exportEasyExcel(response, SysTenantPackageEntity.class, SysTenantPackageService.selectSysTenantPackageList(sysTenantPackageEntity).getRecords(), "租户套餐表数据"); + } + + /** + * 获取租户套餐表详细信息 + */ + @PreAuthorize("@customSs.hasPermi('platform:package:query')") + @GetMapping(value = "/{packageId}") + public R getInfo(@PathVariable("packageId") String packageId) { + return R.ok(SysTenantPackageService.selectSysTenantPackageByPackageId(packageId)); + } + + /** + * 新增租户套餐表 + */ + @PreAuthorize("@customSs.hasPermi('platform:package:create')") + @Log(title = "租户套餐表", businessType = BusinessType.INSERT) + @PostMapping("/create") + public R create(@RequestBody SysTenantPackageEntity vo) { + SysTenantPackageEntity entity = ConvertUtil.entityToVo(vo, SysTenantPackageEntity.class); + boolean success = SysTenantPackageService.create(entity); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + /** + * 修改租户套餐表 + */ + @PreAuthorize("@customSs.hasPermi('platform:package:edit')") + @Log(title = "租户套餐表", businessType = BusinessType.UPDATE) + @PostMapping(value = "/update/{packageId}") + public R update(@PathVariable String packageId, @RequestBody SysTenantPackageEntity sysTenantPackageEntity) { + + sysTenantPackageEntity.setPackageId(packageId); + boolean success = SysTenantPackageService.update(sysTenantPackageEntity); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + /** + * 删除租户套餐表 + */ + @PreAuthorize("@customSs.hasPermi('platform:package:remove')") + @Log(title = "租户套餐表", businessType = BusinessType.DELETE) + @DeleteMapping("/{packageIds}") + public R remove(@PathVariable String[] packageIds) { + return R.ok(SysTenantPackageService.deleteSysTenantPackageByPackageIds(packageIds)); + } + + /** + * 查询租户套餐精简列表 + */ + @GetMapping("/get-simple-list") + public R getSimpleList() { + List list = SysTenantPackageService.getSimpleList(); + return R.ok(list); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/entity/SysTenantEntity.java b/play-admin/src/main/java/com/starry/admin/modules/platform/entity/SysTenantEntity.java new file mode 100644 index 0000000..0faa218 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/entity/SysTenantEntity.java @@ -0,0 +1,135 @@ +package com.starry.admin.modules.platform.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 租户表对象 sys_tenant + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +@TableName("sys_tenant") +public class SysTenantEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 租户ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "tenant_id", type = IdType.AUTO) + private String tenantId; + + /** + * 租户类型;0:公司;1:部门;2:个人;3:其他 + */ + private String tenantType; + + /** + * 用户状态;0:正常,1:停用 + */ + private String tenantStatus; + + /** + * 编码 + */ + private String tenantCode; + + /** + * 名称 + */ + private String tenantName; + + /** + * logo图 + */ + private String tenantLogo; + + /** + * 显示顺序 + */ + private Long sortOrder; + + /** + * 官方地址 + */ + private String homeUrl; + + /** + * 电话 + */ + private String phone; + + /** + * 传真 + */ + private String fax; + + /** + * 邮箱 + */ + private String email; + + /** + * 地址 + */ + private String address; + + /** + * 管理员账号 + */ + private String userName; + + /** + * 管理员密码 + */ + private String userPwd; + + /** + * 租户套餐 + */ + private String packageId; + + /** + * 租赁到期时间 + */ + private Date tenantTime; + + /** + * 微信公众号ID + */ + private String appId; + + + /** + * 微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + + /** + * 备注 + */ + private String remarks; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/entity/SysTenantPackageEntity.java b/play-admin/src/main/java/com/starry/admin/modules/platform/entity/SysTenantPackageEntity.java new file mode 100644 index 0000000..9555c5d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/entity/SysTenantPackageEntity.java @@ -0,0 +1,52 @@ +package com.starry.admin.modules.platform.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 租户套餐表对象 sys_tenant_package + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +@TableName("sys_tenant_package") +public class SysTenantPackageEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 套餐id + */ + @ApiModelProperty("主键ID") + @TableId(value = "package_id", type = IdType.AUTO) + private String packageId; + + /** + * 名称 + */ + private String packageName; + + /** + * 关联菜单id + */ + private String menuIds; + + /** + * 状态;0:正常,1:停用 + */ + private String status; + + /** + * 备注 + */ + private String remarks; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/mapper/SysTenantMapper.java b/play-admin/src/main/java/com/starry/admin/modules/platform/mapper/SysTenantMapper.java new file mode 100644 index 0000000..9740f3f --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/mapper/SysTenantMapper.java @@ -0,0 +1,43 @@ +package com.starry.admin.modules.platform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.platform.entity.SysTenantEntity; +import com.starry.admin.modules.platform.vo.SysTenantQueryVo; +import com.starry.admin.modules.platform.vo.TenantResultVo; +import org.apache.ibatis.annotations.Param; + +/** + * 租户表Mapper接口 + * + * @author admin + * @since 2023-03-03 + */ +public interface SysTenantMapper extends BaseMapper { + /** + * 查询租户表 + * + * @param tenantId 租户表主键 + * @return 租户表 + */ + SysTenantEntity selectSysTenantByTenantId(String tenantId); + + /** + * 查询租户表列表 + * + * @param page 分页参数 + * @param sysTenantEntity 租户表 + * @return 租户表集合 + */ + IPage selectSysTenantList(Page page, @Param("vo") SysTenantQueryVo sysTenantEntity); + + /** + * 批量删除租户管理 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + int deleteTenantByIds(String[] ids); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/mapper/SysTenantPackageMapper.java b/play-admin/src/main/java/com/starry/admin/modules/platform/mapper/SysTenantPackageMapper.java new file mode 100644 index 0000000..c7348bc --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/mapper/SysTenantPackageMapper.java @@ -0,0 +1,42 @@ +package com.starry.admin.modules.platform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.platform.entity.SysTenantPackageEntity; +import com.starry.admin.modules.platform.vo.SimplePackage; +import com.starry.admin.modules.platform.vo.SysTenantPackageVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 租户套餐表Mapper接口 + * + * @author admin + */ +public interface SysTenantPackageMapper extends BaseMapper { + /** + * 查询租户套餐表 + * + * @param packageId 租户套餐表主键 + * @return 租户套餐表 + */ + SysTenantPackageEntity selectSysTenantPackageByPackageId(String packageId); + + /** + * 查询租户套餐表列表 + * + * @param sysTenantPackageEntity 租户套餐表 + * @return 租户套餐表集合 + */ + IPage selectSysTenantPackageList(Page page, @Param("vo") SysTenantPackageVo sysTenantPackageEntity); + + /** + * 查询租户套餐 + * + * @return 租户套餐 + */ + List getSimpleList(); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/service/ISysTenantPackageService.java b/play-admin/src/main/java/com/starry/admin/modules/platform/service/ISysTenantPackageService.java new file mode 100644 index 0000000..404ee29 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/service/ISysTenantPackageService.java @@ -0,0 +1,74 @@ +package com.starry.admin.modules.platform.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.platform.entity.SysTenantPackageEntity; +import com.starry.admin.modules.platform.vo.SimplePackage; +import com.starry.admin.modules.platform.vo.SysTenantPackageVo; + +import java.util.List; + +/** + * 租户套餐表Service接口 + * + * @author admin + */ +public interface ISysTenantPackageService extends IService { + /** + * 查询租户套餐表 + * + * @param packageId 租户套餐表主键 + * @return 租户套餐表 + */ + SysTenantPackageEntity selectSysTenantPackageByPackageId(String packageId); + + /** + * 查询租户套餐表列表 + * + * @param sysTenantPackageEntity 租户套餐表 + * @return 租户套餐表集合 + */ + IPage selectSysTenantPackageList(SysTenantPackageVo sysTenantPackageEntity); + + /** + * 新增租户套餐表 + * + * @param sysTenantPackageEntity 租户套餐表 + * @return 结果 + */ + boolean create(SysTenantPackageEntity sysTenantPackageEntity); + + /** + * 修改租户套餐表 + * + * @param sysTenantPackageEntity 租户套餐表 + * @return 结果 + */ + boolean update(SysTenantPackageEntity sysTenantPackageEntity); + + /** + * 批量删除租户套餐表 + * + * @param packageIds 需要删除的租户套餐表主键集合 + * @return 结果 + */ + int deleteSysTenantPackageByPackageIds(String[] packageIds); + + /** + * 删除租户套餐表信息 + * + * @param packageId 租户套餐表主键 + * @return 结果 + */ + int deleteSysTenantPackageByPackageId(Long packageId); + + /** + * 查询租户套餐 + * + * @return 租户套餐 + */ + List getSimpleList(); + + + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/service/ISysTenantService.java b/play-admin/src/main/java/com/starry/admin/modules/platform/service/ISysTenantService.java new file mode 100644 index 0000000..58f45b4 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/service/ISysTenantService.java @@ -0,0 +1,90 @@ +package com.starry.admin.modules.platform.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.platform.entity.SysTenantEntity; +import com.starry.admin.modules.platform.vo.SysTenantQueryVo; +import com.starry.admin.modules.platform.vo.TenantResultVo; +import com.starry.common.result.R; + +import java.util.List; + +/** + * 租户表Service接口 + * + * @author admin + */ +public interface ISysTenantService extends IService { + /** + * 查询租户表 + * + * @param tenantId 租户表主键 + * @return 租户表 + */ + SysTenantEntity selectSysTenantByTenantId(String tenantId); + + /** + * 查询租户表列表 + * + * @param sysTenantEntity 租户表 + * @return 租户表集合 + */ + IPage selectSysTenantList(SysTenantQueryVo sysTenantEntity); + + /** + * 新增租户表 + * + * @param sysTenantEntity 租户表 + * @return 结果 + */ + boolean create(SysTenantEntity sysTenantEntity); + + /** + * 修改租户表 + * + * @param sysTenantEntity 租户表 + * @return 结果 + */ + boolean update(SysTenantEntity sysTenantEntity); + + /** + * 批量删除租户表 + * + * @param tenantIds 需要删除的租户表主键集合 + * @return 结果 + */ + int deleteSysTenantByTenantIds(String[] tenantIds); + + /** + * 删除租户表信息 + * + * @param tenantId 租户表主键 + * @return 结果 + */ + int deleteSysTenantByTenantId(Long tenantId); + + /** + * 新增租户 + * + * @param sysTenantEntity 租户表 + * @return 结果 + */ + R addTenant(SysTenantEntity sysTenantEntity); + + /** + * 修改租户 + * + * @param sysTenantEntity 租户表 + * @return 结果 + */ + R updateTenant(SysTenantEntity sysTenantEntity); + + + /** + * 根据套餐ID查询租户 + * @param packageId 套餐ID + * @return + */ + List queryByPackage(String packageId); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/service/impl/SysTenantPackageServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/platform/service/impl/SysTenantPackageServiceImpl.java new file mode 100644 index 0000000..1b7f9c6 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/service/impl/SysTenantPackageServiceImpl.java @@ -0,0 +1,114 @@ +package com.starry.admin.modules.platform.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.common.exception.CustomException; +import com.starry.admin.modules.platform.entity.SysTenantPackageEntity; +import com.starry.admin.modules.platform.mapper.SysTenantPackageMapper; +import com.starry.admin.modules.platform.service.ISysTenantPackageService; +import com.starry.admin.modules.platform.service.ISysTenantService; +import com.starry.admin.modules.platform.vo.SimplePackage; +import com.starry.admin.modules.platform.vo.SysTenantPackageVo; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + * 租户套餐表Service业务层处理 + * + * @author admin + */ +@Service +public class SysTenantPackageServiceImpl extends ServiceImpl implements ISysTenantPackageService { + @Resource + private SysTenantPackageMapper sysTenantPackageMapper; + + @Resource + private ISysTenantService tenantService; + + /** + * 查询租户套餐表 + * + * @param packageId 租户套餐表主键 + * @return 租户套餐表 + */ + @Override + public SysTenantPackageEntity selectSysTenantPackageByPackageId(String packageId) { + return this.baseMapper.selectById(packageId); + } + + /** + * 查询租户套餐表列表 + * + * @param sysTenantPackageEntity 租户套餐表 + * @return 租户套餐表 + */ + @Override + public IPage selectSysTenantPackageList(SysTenantPackageVo sysTenantPackageEntity) { + return sysTenantPackageMapper.selectSysTenantPackageList(new Page<>(sysTenantPackageEntity.getPageNum(), sysTenantPackageEntity.getPageSize()), sysTenantPackageEntity); + } + + /** + * 新增租户套餐表 + * + * @param entity 租户套餐表 + * @return 结果 + */ + @Override + public boolean create(SysTenantPackageEntity entity) { + if (StrUtil.isBlankIfStr(entity.getPackageId())) { + entity.setPackageId(IdUtil.fastSimpleUUID()); + } + return save(entity); + } + + /** + * 修改租户套餐表 + * + * @param sysTenantPackageEntity 租户套餐表 + * @return 结果 + */ + @Override + public boolean update(SysTenantPackageEntity sysTenantPackageEntity) { + return updateById(sysTenantPackageEntity); + } + + /** + * 批量删除租户套餐表 + * + * @param packageIds 需要删除的租户套餐表主键 + * @return 结果 + */ + @Override + public int deleteSysTenantPackageByPackageIds(String[] packageIds) { + //删除前,看套餐是否被租户引用 + for (String packageId : packageIds) { + if (!tenantService.queryByPackage(packageId).isEmpty()) { + throw new CustomException("套餐背应用,无法删除"); + } + } + return sysTenantPackageMapper.deleteBatchIds(Arrays.asList(packageIds)); + } + + /** + * 删除租户套餐表信息 + * + * @param packageId 租户套餐表主键 + * @return 结果 + */ + @Override + public int deleteSysTenantPackageByPackageId(Long packageId) { + return sysTenantPackageMapper.deleteById(packageId); + } + + @Override + public List getSimpleList() { + return sysTenantPackageMapper.getSimpleList(); + } + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/service/impl/SysTenantServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/platform/service/impl/SysTenantServiceImpl.java new file mode 100644 index 0000000..f07fc81 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/service/impl/SysTenantServiceImpl.java @@ -0,0 +1,288 @@ +package com.starry.admin.modules.platform.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.platform.entity.SysTenantEntity; +import com.starry.admin.modules.platform.entity.SysTenantPackageEntity; +import com.starry.admin.modules.platform.mapper.SysTenantMapper; +import com.starry.admin.modules.platform.mapper.SysTenantPackageMapper; +import com.starry.admin.modules.platform.service.ISysTenantService; +import com.starry.admin.modules.platform.vo.SysTenantQueryVo; +import com.starry.admin.modules.platform.vo.TenantResultVo; +import com.starry.admin.modules.system.entity.*; +import com.starry.admin.modules.system.mapper.*; +import com.starry.admin.modules.system.service.SysRoleMenuService; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.constant.CacheConstants; +import com.starry.common.redis.RedisCache; +import com.starry.common.result.R; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 租户表Service业务层处理 + * + * @author admin + * @since 2023-03-03 + */ +@Service +public class SysTenantServiceImpl extends ServiceImpl implements ISysTenantService { + @Resource + private SysTenantMapper sysTenantMapper; + @Resource + private SysTenantPackageMapper tenantPackageMapper; + @Resource + private SysUserMapper sysUserMapper; + @Resource + private SysDeptMapper deptMapper; + @Resource + private SysRoleMapper roleMapper; + @Resource + private SysRoleMenuService roleMenuService; + @Resource + private SysUserRoleMapper userRoleMapper; + @Resource + private SysRoleMenuMapper sysRoleMenuMapper; + @Resource + private RedisCache redisCache; + + /** + * 查询租户表 + * + * @param tenantId 租户表主键 + * @return 租户表 + */ + @Override + public SysTenantEntity selectSysTenantByTenantId(String tenantId) { + return this.baseMapper.selectById(tenantId); + } + + /** + * 查询租户表列表 + * + * @param vo 租户表 + * @return 租户表 + */ + @Override + public IPage selectSysTenantList(SysTenantQueryVo vo) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StrUtil.isNotBlank(vo.getTenantStatus())) { + wrapper.eq(SysTenantEntity::getTenantStatus, vo.getTenantStatus()); + } + if (StrUtil.isNotBlank(vo.getTenantName())) { + wrapper.like(SysTenantEntity::getTenantName, vo.getTenantName()); + } + if (StrUtil.isNotBlank(vo.getBeginTime())) { + wrapper.ge(SysTenantEntity::getTenantTime, vo.getBeginTime()); + } + if (StrUtil.isNotBlank(vo.getEndTime())) { + wrapper.le(SysTenantEntity::getTenantTime, vo.getEndTime()); + } + return sysTenantMapper.selectSysTenantList(new Page<>(vo.getPageNum(), vo.getPageSize()), vo); + } + + /** + * 新增租户表 + * + * @param sysTenantEntity 租户表 + * @return 结果 + */ + @Override + public boolean create(SysTenantEntity sysTenantEntity) { + return save(sysTenantEntity); + } + + /** + * 修改租户表 + * + * @param sysTenantEntity 租户表 + * @return 结果 + */ + @Override + public boolean update(SysTenantEntity sysTenantEntity) { + return updateById(sysTenantEntity); + } + + /** + * 批量删除租户表 + * + * @param tenantIds 需要删除的租户表主键 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int deleteSysTenantByTenantIds(String[] tenantIds) { + int num = sysTenantMapper.deleteTenantByIds(tenantIds); + if (num > 0) { + // 下面才会进行子模块数据的删除 + // 部门模块 + deptMapper.deleteDeptByTenantId(tenantIds); + // 权限 + roleMapper.deleteRoleByTenantId(tenantIds); + sysRoleMenuMapper.deleteRoleMenuByTenantIds(tenantIds); + // 账号 + sysUserMapper.deleteUserByTenantId(tenantIds); + userRoleMapper.deleteUserRoleByTenantId(tenantIds); + } else { + throw new RuntimeException("当前租户已被删除不存在!"); + } + return 1; + } + + /** + * 删除租户表信息 + * + * @param tenantId 租户表主键 + * @return 结果 + */ + @Override + public int deleteSysTenantByTenantId(Long tenantId) { + return sysTenantMapper.deleteById(tenantId); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public R addTenant(SysTenantEntity sysTenantEntity) { + if (StrUtil.isBlankIfStr(sysTenantEntity.getTenantId())) { + sysTenantEntity.setTenantId(IdUtil.fastSimpleUUID()); + } + if (StrUtil.isBlankIfStr(sysTenantEntity.getTenantCode())) { + sysTenantEntity.setTenantCode(IdUtil.fastSimpleUUID()); + } + // 判断账号是否存在 + if (sysUserMapper.checkUserNameUnique(sysTenantEntity.getUserName()) > 0) { + return R.error("管理员账号已存在,请重新设置!"); + } + // 创建租户 + sysTenantMapper.insert(sysTenantEntity); + // 创建默认部门--部门默认名称以租户名称 + Long deptId = createDept(sysTenantEntity); + // 创建默认角色--角色默认为租户名称+管理员 + Long roleId = createRole(sysTenantEntity); + // 创建默认账号 + createUser(sysTenantEntity, deptId, roleId); + return R.ok("租户创建成功!"); + } + + private Long createDept(SysTenantEntity sysTenantEntity) { + // 创建部门 + SysDeptEntity dept = new SysDeptEntity(); + dept.setParentId(0L); + dept.setAncestors("0"); + dept.setDeptName(sysTenantEntity.getTenantName()); + dept.setSort(0); + dept.setLeader(sysTenantEntity.getTenantName() + "管理员"); + dept.setPhone(sysTenantEntity.getPhone()); + dept.setEmail(sysTenantEntity.getEmail()); + dept.setTenantId(sysTenantEntity.getTenantId()); + deptMapper.insert(dept); + return dept.getDeptId(); + } + + private Long createRole(SysTenantEntity sysTenantEntity) { + // 创建角色 + SysRoleEntity role = new SysRoleEntity(); + role.setRoleName(sysTenantEntity.getTenantName() + "管理员"); + role.setRoleKey("admin"); + role.setDataScope("1"); + role.setMenuCheckStrictly(true); + role.setDeptCheckStrictly(true); + role.setDescription("租户管理员"); + role.setTenantId(sysTenantEntity.getTenantId()); + roleMapper.insert(role); + // 根据租户套餐ids查出套餐编码塞入角色-菜单表 + createRoleMenu(sysTenantEntity, role); + return role.getRoleId(); + } + + /** + * 目前为单套餐,跟租户绑定,解耦防止套餐变动影响多个租户 + **/ + private void createRoleMenu(SysTenantEntity sysTenantEntity, SysRoleEntity role) { + SysTenantPackageEntity sysTenantPackage = tenantPackageMapper.selectById(sysTenantEntity.getPackageId()); + List subMeuns = Arrays.asList(sysTenantPackage.getMenuIds().split(",")); + + List roleMenuList = subMeuns.stream().map(menuid -> { + SysRoleMenuEntity entity = new SysRoleMenuEntity(); + entity.setRoleId(role.getRoleId()); + entity.setMenuId(Long.valueOf(menuid)); + entity.setTenantId(sysTenantEntity.getTenantId()); + return entity; + }).collect(Collectors.toList()); + roleMenuService.saveBatch(roleMenuList); + } + + private void createUser(SysTenantEntity sysTenant, Long deptId, Long roleId) { + SysUserEntity user = new SysUserEntity(); + user.setDeptId(deptId); + user.setUserCode(sysTenant.getUserName()); + user.setRealName(sysTenant.getTenantName()); + user.setUserNickname(sysTenant.getTenantName()); + user.setUserType(1); + user.setUserEmail(sysTenant.getEmail()); + user.setMobile(sysTenant.getPhone()); + String password = SecurityUtils.encryptPassword(sysTenant.getUserPwd()); + user.setPassWord(password); + user.setTenantId(sysTenant.getTenantId()); + user.setUserId(IdUtil.fastSimpleUUID()); + sysUserMapper.insert(user); + createUserRole(sysTenant.getTenantId(), user.getUserId(), roleId); + } + + private void createUserRole(String tenantId, String userId, Long roleId) { + SysUserRoleEntity userRole = new SysUserRoleEntity(); + userRole.setUserId(userId); + userRole.setRoleId(roleId); + userRole.setTenantId(tenantId); + userRoleMapper.insert(userRole); + } + + @Override + public R updateTenant(SysTenantEntity sysTenantEntity) { + // 判断最新的租户套餐是否改变 重新授权 租户二级管理员账号需重新分配三级账号权限 + SysTenantEntity newTenant = sysTenantMapper.selectById(sysTenantEntity.getTenantId()); + if (sysTenantEntity.getPackageId() != null && !sysTenantEntity.getPackageId().equals(newTenant.getPackageId())) { + List roleList = roleMapper.queryAdminRole(sysTenantEntity.getTenantId()); + // 正常逻辑下每个租户只有一个二级管理员账号 + SysRoleEntity tRole = roleList.get(0); + if (tRole != null) { + // 删除原租户下所有的角色-菜单信息 + sysRoleMenuMapper.deleteRoleMenuByTenantId(sysTenantEntity.getTenantId()); + // 新增默认角色-菜单信息 + createRoleMenu(sysTenantEntity, tRole); + // 原登录租户账号退出重登 + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + if (CollectionUtil.isNotEmpty(keys)) { + for (String key : keys) { + LoginUser onlineUser = redisCache.getCacheObject(key); + if (onlineUser.getUser().getTenantId() != null && onlineUser.getUser().getTenantId().equals(sysTenantEntity.getTenantId())) { + redisCache.deleteObject(key); + } + } + } + } + } + sysTenantMapper.updateById(sysTenantEntity); + return R.ok(); + } + + + @Override + public List queryByPackage(String packageId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysTenantEntity::getTenantId, packageId); + return this.baseMapper.selectList(queryWrapper); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SimplePackage.java b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SimplePackage.java new file mode 100644 index 0000000..cdf9ab9 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SimplePackage.java @@ -0,0 +1,20 @@ +package com.starry.admin.modules.platform.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author admin + * @since 2023/3/5 20:53 + */ +@ApiModel("管理后台 - 租户套餐精简信息") +@Data +public class SimplePackage { + + @ApiModelProperty(value = "套餐编号", required = true, example = "1024") + private String id; + + @ApiModelProperty(value = "套餐名称", required = true, example = "希留") + private String name; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantAddVo.java b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantAddVo.java new file mode 100644 index 0000000..f5dd3f1 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantAddVo.java @@ -0,0 +1,131 @@ +package com.starry.admin.modules.platform.vo; + +import com.starry.common.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Date; + +/** + * 租户表对象 sys_tenant + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysTenantAddVo extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 租户类型;0:公司;1:部门;2:个人;3:其他 + */ + @NotBlank(message = "租户类型不能为空") + private String tenantType; + + /** + * 用户状态;0:正常,1:停用 + */ + @NotBlank(message = "租户状态不能为空") + private String tenantStatus; + + /** + * 名称 + */ + @NotBlank(message = "租户名称不能为空") + private String tenantName; + + /** + * logo图 + */ + private String tenantLogo; + + /** + * 显示顺序 + */ + private String sortOrder; + + /** + * 官方地址 + */ + private String homeUrl; + + /** + * 电话 + */ + @NotBlank(message = "手机号码不能为空") + private String phone; + + /** + * 传真 + */ + private String fax; + + /** + * 邮箱 + */ + private String email; + + /** + * 地址 + */ + private String address; + + /** + * 管理员账号 + */ + @NotBlank(message = "管理员账号不能为空") + private String userName; + + /** + * 管理员密码 + */ + @NotBlank(message = "管理员账号密码不能为空") + private String userPwd; + + /** + * 租户套餐 + */ + private String packageId; + + /** + * 租赁到期时间 + */ + @NotNull(message = "租赁到期时间不能为空") + private Date tenantTime; + + /** + * 微信公众号ID + */ + @NotBlank(message = "微信公众号ID不能为空") + private String appId; + + + /** + * 微信公众号的app secret + */ + @NotBlank(message = "微信公众号secret不能为空") + private String secret; + + + /** + * 设置微信公众号的token + */ + @NotBlank(message = "微信公众号token不能为空") + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + @NotBlank(message = "微信公众号aesKey不能为空") + private String aesKey; + + /** + * 备注 + */ + private String remarks; + + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantPackageAddVo.java b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantPackageAddVo.java new file mode 100644 index 0000000..361b263 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantPackageAddVo.java @@ -0,0 +1,43 @@ +package com.starry.admin.modules.platform.vo; + +import com.starry.common.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 租户套餐表对象 sys_tenant_package + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysTenantPackageAddVo extends BaseEntity { + + private static final long serialVersionUID = 1L; + + + /** + * 名称 + */ + @NotBlank(message = "套餐名称不能为空") + private String packageName; + + /** + * 关联菜单id + */ + private String menuIds; + + /** + * 状态;0:正常,1:停用 + */ + @NotBlank(message = "套餐状态不能为空") + private String status; + + /** + * 备注 + */ + private String remarks; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantPackageVo.java b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantPackageVo.java new file mode 100644 index 0000000..7a7f1a9 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantPackageVo.java @@ -0,0 +1,48 @@ +package com.starry.admin.modules.platform.vo; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.starry.common.domain.BasePageEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 租户套餐表对象 sys_tenant_package + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysTenantPackageVo extends BasePageEntity { + + private static final long serialVersionUID = 1L; + + /** + * 套餐id + */ + @ApiModelProperty("主键ID") + @TableId(value = "package_id", type = IdType.AUTO) + private Long packageId; + + /** + * 名称 + */ + private String packageName; + + /** + * 关联菜单id + */ + private String menuIds; + + /** + * 状态;0:正常,1:停用 + */ + private String status; + + /** + * 备注 + */ + private String remarks; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantQueryVo.java b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantQueryVo.java new file mode 100644 index 0000000..f91b0a3 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/SysTenantQueryVo.java @@ -0,0 +1,30 @@ +package com.starry.admin.modules.platform.vo; + +import com.starry.common.domain.BasePageEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 租户表对象 sys_tenant + * + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysTenantQueryVo extends BasePageEntity { + + private static final long serialVersionUID = 1L; + + + /** + * 名称 + */ + private String tenantName; + + + /** + * 用户状态;0:正常,1:停用 + */ + private String tenantStatus; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/platform/vo/TenantResultVo.java b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/TenantResultVo.java new file mode 100644 index 0000000..1c809e8 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/platform/vo/TenantResultVo.java @@ -0,0 +1,53 @@ +package com.starry.admin.modules.platform.vo; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.Date; + +/** + * @author 租户列表返回结果VO + * @since 2023/3/7 + */ +@ApiModel("管理后台 - 租户列表返回信息") +@Data +public class TenantResultVo { + + private String tenantId; + + /** + * 租户类型;0:公司;1:部门;2:个人;3:其他 + */ + private String tenantType; + + /** + * 用户状态;0:正常,1:停用 + */ + private String tenantStatus; + + /** + * 名称 + */ + private String tenantName; + + /** + * logo图 + */ + private String tenantLogo; + + /** + * 管理员账号 + */ + private String userName; + + /** + * 租户套餐Id + */ + private String packageId; + + /** + * 租赁到期时间 + */ + private Date tenantTime; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/LoginController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/LoginController.java new file mode 100644 index 0000000..3c6e142 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/LoginController.java @@ -0,0 +1,113 @@ +package com.starry.admin.modules.system.controller; + + +import com.starry.admin.common.component.JwtToken; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.system.entity.SysMenuEntity; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.service.LoginService; +import com.starry.admin.modules.system.service.SysMenuService; +import com.starry.admin.modules.system.vo.LoginVo; +import com.starry.admin.modules.system.vo.RouterVo; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.redis.CaptchaService; +import com.starry.common.result.R; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author admin + */ +@Api(tags = "登录管理") +@RestController +public class LoginController { + + @Value("${jwt.tokenHead}") + private String tokenHead; + @Value(value = "${xl.login.authCode.enable}") + private boolean needAuthCode; + @Resource + private LoginService loginService; + @Resource + private SysMenuService menuService; + @Resource + private CaptchaService captchaService; + @Resource + private JwtToken jwtToken; + + @ApiOperation(value = "登录") + @PostMapping(value = "old-login") + public R login(@RequestBody LoginVo loginVo) { + // 只有开启了验证码功能才需要验证 + if (needAuthCode) { + String msg = captchaService.checkImageCode(loginVo.getNonceStr(), loginVo.getValue()); + if (StringUtils.isNotBlank(msg)) { + return R.error(msg); + } + } + String token = loginService.login(loginVo.getUserName(), loginVo.getPassWord()); + if (StringUtils.isBlank(token)) { + return R.error("用户名或密码错误"); + } + Map tokenMap = new HashMap<>(); + tokenMap.put("token", token); + tokenMap.put("tokenHead", tokenHead); + return R.ok(tokenMap); + } + + @ApiOperation(value = "登录后获取用户信息") + @GetMapping("get-info") + public R getInfo() { + SysUserEntity user = SecurityUtils.getLoginUser().getUser(); + if (user == null) { + return R.unauthorized(); + } + // 权限集合 + Set permissions = loginService.getMenuPermission(user); + // 角色集合 + Set roles = loginService.getRolePermission(user); + Map data = new HashMap<>(); + data.put("userCode", user.getUserCode()); + data.put("name", user.getRealName()); + data.put("avatar", user.getAvatar()); + data.put("roles", roles); + data.put("permissions", permissions); + return R.ok(data); + } + + @ApiOperation(value = "登录后获取路由信息") + @GetMapping("get-routers") + public R getRoutes() { + List menus = menuService.selectMenuTreeByUserId(SecurityUtils.getUserId()); + List routerVos = menuService.buildMenus(menus); + return R.ok(routerVos); + } + + @ApiOperation(value = "登录") + @PostMapping(value = "login") + public R newLogin(@RequestBody LoginVo loginVo) { + // 只有开启了验证码功能才需要验证 + if (needAuthCode) { + String msg = captchaService.checkImageCode(loginVo.getNonceStr(), loginVo.getValue()); + if (StringUtils.isNotBlank(msg)) { + return R.error(msg); + } + } + // 用户登录 + LoginUser userInfo = loginService.newLogin(loginVo.getUserName(), loginVo.getPassWord()); + Map tokenMap = jwtToken.createToken(userInfo); + return R.ok(tokenMap); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDeptController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDeptController.java new file mode 100644 index 0000000..3128fa3 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDeptController.java @@ -0,0 +1,100 @@ +package com.starry.admin.modules.system.controller; + + +import com.starry.admin.common.domain.TreeSelect; +import com.starry.admin.modules.system.entity.SysDeptEntity; +import com.starry.admin.modules.system.service.SysDeptService; +import com.starry.common.constant.UserConstants; +import com.starry.common.result.R; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; + +/** + *

+ * 部门表 前端控制器 + *

+ * + * @author admin + * @since 2022-07-12 + */ +@RestController +@RequestMapping("/ucenter/dept") +public class SysDeptController { + + @Resource + private SysDeptService deptService; + + @ApiOperation("获取部门列表") + @PreAuthorize("@customSs.hasPermi('system:dept:list')") + @GetMapping("/list") + public R list(SysDeptEntity dept) { + List depts = deptService.selectDeptList(dept); + return R.ok(depts); + } + + @ApiOperation("添加部门") + @PreAuthorize("@customSs.hasPermi('system:dept:create')") + @PostMapping("/create") + public R creat(@RequestBody SysDeptEntity dept) { + if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) { + return R.error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + boolean success = deptService.create(dept); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + @ApiOperation("修改部门") + @PreAuthorize("@customSs.hasPermi('system:dept:edit')") + @PostMapping("/update/{id}") + public R update(@PathVariable Long id, @RequestBody SysDeptEntity dept) { + dept.setDeptId(id); + if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) { + return R.error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } else if (dept.getParentId().equals(id)) { + return R.error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + boolean success = deptService.update(dept); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + @ApiOperation("删除部门") + @PreAuthorize("@customSs.hasPermi('system:dept:delete')") + @PostMapping(value = "/delete/{id}") + public R delete(@PathVariable("id") Long deptId) { + if (deptService.hasChildByDeptId(deptId)) { + return R.error("存在下级部门,不允许删除"); + } + boolean success = deptService.delete(Collections.singletonList(deptId)); + if (success) { + return R.ok(); + } + return R.error("删除失败"); + } + + @ApiOperation("获取部门下拉树列表") + @GetMapping("/treeselect") + public R treeselect(SysDeptEntity dept) { + List list = deptService.selectDeptList(dept); + List treeList = deptService.buildDeptTreeSelect(list); + return R.ok(treeList); + } + + @ApiOperation("根据id获取详细信息") + @GetMapping(value = "/{deptId}") + public R getInfo(@PathVariable Long deptId) { + return R.ok(deptService.getById(deptId)); + } + +} + diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictController.java new file mode 100644 index 0000000..cb5eb17 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictController.java @@ -0,0 +1,103 @@ +package com.starry.admin.modules.system.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysDictEntity; +import com.starry.admin.modules.system.service.ISysDictService; +import com.starry.admin.modules.system.vo.SysDictVo; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + + +/** + * 字典表Controller + * + * @author admin + */ +@RestController +@RequestMapping("/system/dict") +public class SysDictController { + @Resource + private ISysDictService xlDictService; + + /** + * 查询字典表列表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:list')") + @GetMapping("/list") + public R list(SysDictVo dictVo) { + Page list = xlDictService.selectXlDictList(dictVo); + return R.ok(list); + } + + + /** + * 获取字典表详细信息 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public R getInfo(@PathVariable("dictId") Long dictId) { + return R.ok(xlDictService.selectXlDictByDictId(dictId)); + } + + /** + * 新增字典表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:create')") + @Log(title = "字典表", businessType = BusinessType.INSERT) + @PostMapping("/create") + public R create(@RequestBody SysDictEntity sysDictEntity) { + boolean success = xlDictService.create(sysDictEntity); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + /** + * 修改字典表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:edit')") + @Log(title = "字典表", businessType = BusinessType.UPDATE) + @PostMapping(value = "/update/{dictId}") + public R update(@PathVariable Long dictId, @RequestBody SysDictEntity sysDictEntity) { + sysDictEntity.setDictId(dictId); + boolean success = xlDictService.update(sysDictEntity); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + /** + * 删除字典表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:remove')") + @Log(title = "字典表", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public R remove(@PathVariable Long[] dictIds) { + return R.ok(xlDictService.deleteXlDictByDictIds(dictIds)); + } + + @GetMapping("/optionselect") + public R optionselect() { + List list = xlDictService.selectDictTypeAll(); + return R.ok(list); + } + + /** + * 刷新字典缓存 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public R refreshCache() { + xlDictService.resetDictCache(); + return R.ok(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictDataController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictDataController.java new file mode 100644 index 0000000..65b4af4 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictDataController.java @@ -0,0 +1,98 @@ +package com.starry.admin.modules.system.controller; + +import cn.hutool.core.collection.CollectionUtil; +import com.starry.admin.modules.system.entity.SysDictDataEntity; +import com.starry.admin.modules.system.service.ISysDictDataService; +import com.starry.admin.modules.system.vo.SysDictDataVo; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + + +/** + * 字典数据表Controller + * + * @author admin + */ +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController { + @Resource + private ISysDictDataService xlDictDataService; + + /** + * 查询字典数据表列表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:list')") + @GetMapping("/list") + public R list(SysDictDataVo sysDictDataEntity) { + return R.ok(xlDictDataService.selectXlDictDataList(sysDictDataEntity)); + } + + + /** + * 获取字典数据表详细信息 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictDataId}") + public R getInfo(@PathVariable("dictDataId") Long dictDataId) { + return R.ok(xlDictDataService.selectXlDictDataByDictDataId(dictDataId)); + } + + /** + * 新增字典数据表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:create')") + @Log(title = "字典数据表", businessType = BusinessType.INSERT) + @PostMapping("/create") + public R create(@RequestBody SysDictDataEntity sysDictDataEntity) { + boolean success = xlDictDataService.create(sysDictDataEntity); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + /** + * 修改字典数据表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:edit')") + @Log(title = "字典数据表", businessType = BusinessType.UPDATE) + @PostMapping(value = "/update/{dictDataId}") + public R update(@PathVariable Long dictDataId, @RequestBody SysDictDataEntity sysDictDataEntity) { + sysDictDataEntity.setDictDataId(dictDataId); + boolean success = xlDictDataService.update(sysDictDataEntity); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + /** + * 删除字典数据表 + */ + @PreAuthorize("@customSs.hasPermi('system:dict:remove')") + @Log(title = "字典数据表", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictDataIds}") + public R remove(@PathVariable Long[] dictDataIds) { + return R.ok(xlDictDataService.deleteXlDictDataByDictDataIds(dictDataIds)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public R getDictDataByType(@PathVariable String dictType) { + List list = xlDictDataService.selectDictDataByType(dictType); + if (CollectionUtil.isEmpty(list)) { + list = new ArrayList<>(); + } + return R.ok(list); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysLoginLogController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysLoginLogController.java new file mode 100644 index 0000000..fb37a49 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysLoginLogController.java @@ -0,0 +1,77 @@ +package com.starry.admin.modules.system.controller; + + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.starry.admin.modules.system.entity.SysLoginLogEntity; +import com.starry.admin.modules.system.entity.SysLoginLogVo; +import com.starry.admin.modules.system.service.ISysLoginLogService; +import com.starry.admin.utils.ExcelUtils; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + + +/** + * 系统访问日志表Controller + * + * @author admin + */ +@RestController +@RequestMapping("/system/loginlog") +public class SysLoginLogController { + @Resource + private ISysLoginLogService xlLoginLogService; + + /** + * 查询系统访问日志表列表 + */ + @PreAuthorize("@customSs.hasPermi('system:loginlog:list')") + @GetMapping("/list") + public R list(SysLoginLogVo vo) { + IPage list = xlLoginLogService.selectXlLoginLogList(vo); + return R.ok(list); + } + + /** + * 导出系统访问日志表列表 + */ + @PreAuthorize("@customSs.hasPermi('system:loginlog:export')") + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysLoginLogVo vo) { + IPage list = xlLoginLogService.selectXlLoginLogList(vo); + ExcelUtils.exportEasyExcel(response, SysLoginLogEntity.class, list.getRecords(), "系统访问日志表数据"); + } + + /** + * 获取系统访问日志表详细信息 + */ + @PreAuthorize("@customSs.hasPermi('system:log:query')") + @GetMapping(value = "/{loginId}") + public R getInfo(@PathVariable("loginId") Long loginId) { + return R.ok(xlLoginLogService.selectXlLoginLogByLoginId(loginId)); + } + + /** + * 删除系统访问日志表 + */ + @PreAuthorize("@customSs.hasPermi('system:loginlog:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{loginIds}") + public R remove(@PathVariable Long[] loginIds) { + return R.ok(xlLoginLogService.deleteXlLoginLogByLoginIds(loginIds)); + } + + @PreAuthorize("@customSs.hasPermi('monitor:logininfor:clean')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public R clean() { + xlLoginLogService.cleanLoginlog(); + return R.ok(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysMenuController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysMenuController.java new file mode 100644 index 0000000..1b488e2 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysMenuController.java @@ -0,0 +1,141 @@ +package com.starry.admin.modules.system.controller; + + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.common.domain.TreeSelect; +import com.starry.admin.modules.system.entity.SysMenuEntity; +import com.starry.admin.modules.system.service.SysMenuService; +import com.starry.admin.modules.system.vo.SimpleMenu; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.constant.UserConstants; +import com.starry.common.result.R; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 菜单表 前端控制器 + *

+ * + * @author admin + * @since 2022-07-03 + */ +@RestController +@RequestMapping("/ucenter/menu") +public class SysMenuController { + + @Resource + private SysMenuService menuService; + + + @ApiOperation("获取菜单列表") + @PreAuthorize("@customSs.hasPermi('system:menu:list')") + @GetMapping("/list") + public R list(SysMenuEntity menu) { + // 获取登录人的userId; + String userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuList(menu, userId); + return R.ok(menus); + } + + @ApiOperation("分页获取菜单列表") + @GetMapping("/list-page") + public R listPage(@RequestParam(value = "menu", required = false) SysMenuEntity menu, + @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize, + @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) { + Page pageMenu = menuService.listPage(menu, SecurityUtils.getUserId(), pageSize, pageNum); + return R.ok(pageMenu); + } + + @ApiOperation("添加菜单") + @PreAuthorize("@customSs.hasPermi('system:menu:create')") + @PostMapping("/create") + public R create(@RequestBody SysMenuEntity menu) { + if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) { + return R.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StrUtil.startWithAny(menu.getPath(), "http://", "https://")) { + return R.error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + boolean success = menuService.create(menu); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + @ApiOperation("修改菜单") + @PreAuthorize("@customSs.hasPermi('system:menu:edit')") + @PostMapping("/update/{id}") + public R update(@PathVariable Long id, @RequestBody SysMenuEntity menu) { + menu.setMenuId(id); + if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) { + return R.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StrUtil.startWithAny(menu.getPath(), "http://", "https://")) { + return R.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } else if (menu.getMenuId().equals(menu.getParentId())) { + return R.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + boolean success = menuService.updateById(menu); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + @ApiOperation("删除菜单") + @PreAuthorize("@customSs.hasPermi('system:menu:delete')") + @PostMapping(value = "/delete/{id}") + public R delete(@PathVariable("id") Long menuId) { + if (menuService.hasChildByMenuId(menuId)) { + return R.error("存在子菜单,不允许删除"); + } + boolean success = menuService.removeByIds(Collections.singletonList(menuId)); + if (success) { + return R.ok(); + } + return R.error("删除失败"); + } + + @ApiOperation("获取菜单下拉树列表") + @GetMapping("/treeselect") + public R treeselect(SysMenuEntity menu) { + List menus = menuService.selectMenuList(menu, SecurityUtils.getUserId()); + List tree = menuService.buildMenuTreeSelect(menus); + return R.ok(tree); + } + + @PreAuthorize("@customSs.hasPermi('system:menu:query')") + @ApiOperation("根据菜单编号获取详细信息") + @GetMapping(value = "/{menuId}") + public R getInfo(@PathVariable Long menuId) { + return R.ok(menuService.selectMenuById(menuId)); + } + + @ApiOperation("获取对应角色菜单列表树") + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public R roleMenuTreeselect(@PathVariable("roleId") Long roleId) { + List menus = menuService.selectMenuList(SecurityUtils.getUserId()); + Map resultMap = new HashMap<>(); + resultMap.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + resultMap.put("menus", menuService.buildMenuTreeSelect(menus)); + return R.ok(resultMap); + } + + @GetMapping("/list-all-simple") + @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" + + "在多租户的场景下,会只返回租户所在套餐有的菜单") + public R getSimpleMenus() { + // 获得菜单列表,只要开启状态的 + List menus = menuService.selectSimpleMenuList(); + return R.ok(menus); + } +} + diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysOperationLogController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysOperationLogController.java new file mode 100644 index 0000000..645cfe9 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysOperationLogController.java @@ -0,0 +1,96 @@ +package com.starry.admin.modules.system.controller; + + +import com.starry.admin.modules.system.entity.SysOperationLogEntity; +import com.starry.admin.modules.system.service.ISysOperationLogService; +import com.starry.admin.modules.system.vo.SysOperationLogVo; +import com.starry.admin.utils.ExcelUtils; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + + +/** + * 操作日志表Controller + * + * @author admin + */ +@RestController +@RequestMapping("/system/operlog") +public class SysOperationLogController { + + @Resource + private ISysOperationLogService xlOperLogService; + + /** + * 查询操作日志表列表 + */ + @PreAuthorize("@customSs.hasPermi('system:operlog:list')") + @GetMapping("/list") + public R list(SysOperationLogVo sysOperationLogEntity) { + return R.ok(xlOperLogService.selectXlOperLogList(sysOperationLogEntity)); + } + + /** + * 获取操作日志表详细信息 + */ + @PreAuthorize("@customSs.hasPermi('system:operlog:query')") + @GetMapping(value = "/{operId}") + public R getInfo(@PathVariable("operId") Long operId) { + return R.ok(xlOperLogService.selectXlOperLogByOperId(operId)); + } + + /** + * 新增操作日志表 + */ + @PreAuthorize("@customSs.hasPermi('system:operlog:create')") + @Log(title = "操作日志表", businessType = BusinessType.INSERT) + @PostMapping + public R create(@RequestBody SysOperationLogEntity sysOperationLogEntity) { + boolean success = xlOperLogService.create(sysOperationLogEntity); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + /** + * 修改操作日志表 + */ + @PreAuthorize("@customSs.hasPermi('system:operlog:edit')") + @Log(title = "操作日志表", businessType = BusinessType.UPDATE) + @PostMapping(value = "/update/{operId}") + public R update(@PathVariable Long operId, @RequestBody SysOperationLogEntity sysOperationLogEntity) { + sysOperationLogEntity.setOperId(operId); + boolean success = xlOperLogService.updateXlOperLog(sysOperationLogEntity); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + /** + * 删除操作日志表 + */ + @PreAuthorize("@customSs.hasPermi('system:operlog:remove')") + @Log(title = "操作日志表", businessType = BusinessType.DELETE) + @DeleteMapping("/{operIds}") + public R remove(@PathVariable Long[] operIds) { + return R.ok(xlOperLogService.deleteXlOperLogByOperIds(operIds)); + } + + /** + * 导出操作日志表 + */ + @PreAuthorize("@customSs.hasPermi('system:operlog:export')") + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperationLogVo sysOperationLogEntity) { + ExcelUtils.exportEasyExcel(response, SysOperationLogEntity.class, xlOperLogService.selectXlOperLogList(sysOperationLogEntity).getRecords(), "系统操作日志表数据"); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysRoleController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysRoleController.java new file mode 100644 index 0000000..02f8007 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysRoleController.java @@ -0,0 +1,230 @@ +package com.starry.admin.modules.system.controller; + + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysDeptEntity; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.entity.SysUserRoleEntity; +import com.starry.admin.modules.system.service.SysDeptService; +import com.starry.admin.modules.system.service.SysRoleService; +import com.starry.admin.modules.system.service.SysUserService; +import com.starry.admin.modules.system.vo.RoleUserResultVo; +import com.starry.admin.modules.system.vo.SysRoleAddVo; +import com.starry.admin.modules.system.vo.SysUserQueryVo; +import com.starry.common.annotation.Log; +import com.starry.common.constant.UserConstants; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.BeanUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 角色表 前端控制器 + *

+ * + * @author admin + * @since 2022-07-01 + */ +@Api(tags = "角色管理") +@RestController +@RequestMapping("/ucenter/role") +public class SysRoleController { + + @Resource + private SysRoleService roleService; + @Resource + private SysDeptService deptService; + @Resource + private SysUserService userService; + + @ApiOperation("添加角色") + @PreAuthorize("@customSs.hasPermi('system:role:create')") + @PostMapping("/create") + public R create(@Valid @RequestBody SysRoleAddVo roleAddVo) { + SysRoleEntity role = new SysRoleEntity(); + BeanUtils.copyProperties(roleAddVo, role); + if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) { + return R.error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) { + return R.error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + boolean success = roleService.create(role); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + @ApiOperation("修改角色") + @PreAuthorize("@customSs.hasPermi('system:role:edit')") + @PostMapping("/update/{id}") + public R update(@PathVariable Long id, @RequestBody SysRoleEntity role) { + role.setRoleId(id); + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) { + return R.error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) { + return R.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + boolean success = roleService.updateById(role); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + @ApiOperation("批量删除角色") + @PreAuthorize("@customSs.hasPermi('system:role:remove')") + @DeleteMapping("/{roleIds}") + public R delete(@PathVariable Long[] roleIds) { + boolean success = roleService.delete(roleIds); + if (success) { + return R.ok(); + } + return R.error("删除失败"); + } + + @ApiOperation("获取所有角色") + @GetMapping("/list-all") + public R listAll() { + List list = roleService.list(); + return R.ok(list); + } + + @ApiOperation("根据角色名称分页获取角色列表") + @GetMapping("/list-page") + public R listPage(@RequestParam(value = "keyword", required = false) String keyword, @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize, @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) { + Page rolePage = roleService.list(keyword, pageSize, pageNum); + return R.ok(rolePage); + } + + @ApiOperation("修改角色状态") + @PreAuthorize("@customSs.hasPermi('system:role:edit')") + @PostMapping("/update-status/{id}") + public R updateStatus(@PathVariable Long id, @RequestParam(value = "status") Integer status) { + SysRoleEntity role = new SysRoleEntity(); + role.setRoleId(id); + role.setStatus(status); + boolean success = roleService.updateById(role); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + @ApiOperation("分配菜单权限") + @PreAuthorize("@customSs.hasPermi('system:role:auth')") + @PostMapping("/auth-role-menu") + public R authRoleMenu(@RequestBody SysRoleEntity role) { + boolean success = roleService.authRoleMenu(role); + if (success) { + return R.ok(); + } + return R.error("分配失败"); + } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@customSs.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public R getInfo(@PathVariable Long roleId) { + roleService.checkRoleDataScope(roleId); + return R.ok(roleService.getById(roleId)); + } + + /** + * 获取对应角色部门树列表 + */ + @ApiOperation("获取对应角色部门树列表") + @PreAuthorize("@customSs.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public R deptTree(@PathVariable("roleId") Long roleId) { + Map resultMap = new HashMap<>(8); + resultMap.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + resultMap.put("depts", deptService.selectDeptTreeList(new SysDeptEntity())); + return R.ok(resultMap); + } + + /** + * 修改保存数据权限 + */ + @ApiOperation("修改保存数据权限") + @PreAuthorize("@customSs.hasPermi('system:role:query')") + @Log(title = "角色管理-保存数据权限", businessType = BusinessType.UPDATE) + @PostMapping("/dataScope") + public R dataScope(@RequestBody SysRoleEntity role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return R.ok(roleService.authDataScope(role)); + } + + /** + * 查询已分配用户角色列表 + */ + @ApiOperation("查询已分配用户角色列表") + @PreAuthorize("@customSs.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public R allocatedList(SysUserQueryVo user) { + IPage list = userService.selectAllocatedList(user); + return R.ok(list); + } + + /** + * 查询未分配用户角色列表 + */ + @ApiOperation("查询未分配用户角色列表") + @PreAuthorize("@customSs.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public R unallocatedList(SysUserQueryVo user) { + return R.ok(userService.selectUnallocatedList(user)); + } + + /** + * 取消授权用户 + */ + @ApiOperation("取消授权用户") + @PreAuthorize("@customSs.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public R cancelAuthUser(@RequestBody SysUserRoleEntity userRole) { + return R.ok(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @ApiOperation("批量取消授权用户") + @PreAuthorize("@customSs.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public R cancelAuthUserAll(Long roleId, String[] userIds) { + return R.ok(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @ApiOperation("批量选择用户授权") + @PreAuthorize("@customSs.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public R selectAuthUserAll(Long roleId, String[] userIds) { + roleService.checkRoleDataScope(roleId); + return R.ok(roleService.insertAuthUsers(roleId, userIds)); + } +} + + \ No newline at end of file diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysUserController.java b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysUserController.java new file mode 100644 index 0000000..690c271 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/controller/SysUserController.java @@ -0,0 +1,148 @@ +package com.starry.admin.modules.system.controller; + + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.starry.admin.common.component.JwtToken; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.service.SysRoleService; +import com.starry.admin.modules.system.service.SysUserService; +import com.starry.admin.modules.system.vo.UserQueryVo; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.annotation.Log; +import com.starry.common.constant.UserConstants; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import com.starry.common.utils.file.CosClientUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

+ * 后台用户表 前端控制器 + *

+ * + * @author admin + * @since 2021-09-03 + */ +@Api(tags = "用户管理") +@RestController +@RequestMapping("/ucenter/user") +public class SysUserController { + + @Resource + private SysUserService userService; + @Resource + private SysRoleService roleService; + @Resource + private JwtToken tokenService; + + @ApiOperation(value = "注册用户") + @PostMapping(value = "register") + public R register(@RequestBody SysUserEntity user) { + SysUserEntity sysUserEntity = userService.register(user); + if (sysUserEntity == null) { + return R.error("注册失败,用户名已存在"); + } + return R.ok(sysUserEntity); + } + + @ApiOperation(value = "分页用户列表") + @GetMapping(value = "/list-page") + public R listPage(UserQueryVo queryVo) { + IPage page = userService.listMemberPage(queryVo); + return R.ok(page); + } + + @ApiOperation(value = "添加用户") + @PostMapping(value = "add") + public R add(@RequestBody SysUserEntity user) { + if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserCode()))) { + return R.error("新增用户'" + user.getUserCode() + "'失败,登录账号已存在"); + } + boolean success = userService.create(user); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + @ApiOperation(value = "修改用户") + @PostMapping(value = "/update/{id}") + public R update(@PathVariable("id") String id, @RequestBody SysUserEntity user) { + user.setUserId(id); + boolean flag = userService.update(user); + if (flag) { + return R.ok(); + } + return R.error("修改失败"); + } + + @ApiOperation(value = "删除用户") + @PostMapping(value = "/delete/{id}") + public R delete(@PathVariable("id") String id) { + boolean flag = userService.delete(Collections.singletonList(id)); + if (flag) { + return R.ok(); + } + return R.error(); + } + + @ApiOperation(value = "登出功能") + @PostMapping(value = "logout") + public R logout() { + return R.ok(); + } + + @ApiOperation(value = "根据用户id获取详细信息") + @GetMapping(value = {"/", "/{userId}"}) + public R getInfo(@PathVariable(value = "userId", required = false) String userId) { + Map map = new HashMap<>(8); + List roles = roleService.selectRoleList(new SysRoleEntity()); + map.put("roles", SysUserEntity.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + if (userId != null) { + SysUserEntity user = userService.selectUserById(userId); + map.put("roleIds", user.getRoles().stream().map(SysRoleEntity::getRoleId).collect(Collectors.toList())); + map.put("user", user); + } + return R.ok(map); + } + + @ApiOperation(value = "获取个人信息") + @GetMapping(value = "/profile") + public R getProfile() { + Map map = new HashMap<>(8); + SysUserEntity loginUser = userService.selectUserById(SecurityUtils.getUserId()); + map.put("user", loginUser); + map.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUserId())); + return R.ok(map); + } + + @ApiOperation(value = "用户头像上传") + @Log(title = "用户头像上传", businessType = BusinessType.UPDATE) + @PostMapping("/profile/avatar") + public R uploadAvatar(@RequestParam("avatarfile") MultipartFile file) throws Exception { + if (!file.isEmpty()) { + String avatar = CosClientUtils.upload(file, "avatar"); + if (userService.updateUserAvatar(SecurityUtils.getUserId(), avatar)) { + // 更新缓存用户头像 + LoginUser loginUser = SecurityUtils.getLoginUser(); + loginUser.getUser().setAvatar(avatar); + tokenService.refreshToken(loginUser); + return R.ok(avatar); + } + } + return R.error("上传头像异常,请联系管理员"); + } +} + diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDeptEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDeptEntity.java new file mode 100644 index 0000000..abcd501 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDeptEntity.java @@ -0,0 +1,71 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 部门表 + *

+ * + * @author admin + * @since 2022-07-12 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_dept") +@ApiModel(value = "XlDept对象", description = "部门表") +public class SysDeptEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键ID") + @TableId(value = "dept_id", type = IdType.AUTO) + private Long deptId; + + @ApiModelProperty("父部门id") + private Long parentId; + + @ApiModelProperty("祖级列表") + private String ancestors; + + @ApiModelProperty("部门名称") + private String deptName; + + @ApiModelProperty("显示顺序") + private Integer sort; + + @ApiModelProperty("负责人") + private String leader; + + @ApiModelProperty("联系电话") + private String phone; + + @ApiModelProperty("邮箱") + private String email; + + @ApiModelProperty("部门状态(0正常 1停用)") + private Integer status; + + @ApiModelProperty("部门级别") + private Integer deptLevel; + + @ApiModelProperty("租户ID") + private String tenantId; + + /** + * 子部门 + */ + @TableField(exist = false) + private List children = new ArrayList<>(); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDictDataEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDictDataEntity.java new file mode 100644 index 0000000..fd2cf74 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDictDataEntity.java @@ -0,0 +1,72 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 字典数据表对象 sys_dict_data + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +@TableName("sys_dict_data") +public class SysDictDataEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "dict_data_id", type = IdType.AUTO) + private Long dictDataId; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 字典名称 + */ + private String dictLabel; + + /** + * 字典值 + */ + private String dictValue; + + /** + * 显示顺序 + */ + private Long sort; + + /** + * 是否默认(Y是 N否) + */ + private String isDefault; + + /** + * 表格回显样式 + */ + private String listClass; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDictEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDictEntity.java new file mode 100644 index 0000000..ca0624a --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysDictEntity.java @@ -0,0 +1,53 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 字典表对象 sys_dict + * + * @author admin + * @since 2022-08-09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +@TableName("sys_dict") +public class SysDictEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "dict_id", type = IdType.AUTO) + private Long dictId; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 字典名称 + */ + private String dictName; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysLoginLogEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysLoginLogEntity.java new file mode 100644 index 0000000..88ba388 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysLoginLogEntity.java @@ -0,0 +1,86 @@ +package com.starry.admin.modules.system.entity; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.admin.modules.system.vo.StatusConverter; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + + +/** + * 系统访问日志表对象 sys_login_log + * + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@ExcelIgnoreUnannotated +@Data +@TableName("sys_login_log") +@ApiModel(value = "XlLoginLog对象", description = "登录日志表") +public class SysLoginLogEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "login_id", type = IdType.AUTO) + private Long loginId; + + /** + * 用户账号 + */ + @ExcelProperty(value = "用户账号") + private String userCode; + + /** + * 登录IP地址 + */ + @ExcelProperty(value = "登录IP地址") + private String ipaddr; + + /** + * 登录地点 + */ + @ExcelProperty(value = "登录地点") + private String loginLocation; + + /** + * 浏览器类型 + */ + @ExcelProperty(value = "浏览器类型") + private String browser; + + /** + * 操作系统 + */ + @ExcelProperty(value = "操作系统") + private String os; + + /** + * 提示消息 + */ + private String msg; + + /** + * 登录状态(0成功 1失败) + */ + @ExcelProperty(value = "登录状态", converter = StatusConverter.class) + private Integer status; + + /** + * 访问时间 + */ + private Date loginTime; + + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysLoginLogVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysLoginLogVo.java new file mode 100644 index 0000000..23d3825 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysLoginLogVo.java @@ -0,0 +1,78 @@ +package com.starry.admin.modules.system.entity; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.starry.admin.modules.system.vo.StatusConverter; +import com.starry.common.domain.BasePageEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + + +/** + * 系统访问日志表对象 sys_login_log + * + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysLoginLogVo extends BasePageEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "login_id", type = IdType.AUTO) + private Long loginId; + + /** + * 用户账号 + */ + @ExcelProperty(value = "用户账号") + private String userCode; + + /** + * 登录IP地址 + */ + @ExcelProperty(value = "登录IP地址") + private String ipaddr; + + /** + * 登录地点 + */ + @ExcelProperty(value = "登录地点") + private String loginLocation; + + /** + * 浏览器类型 + */ + @ExcelProperty(value = "浏览器类型") + private String browser; + + /** + * 操作系统 + */ + @ExcelProperty(value = "操作系统") + private String os; + + /** + * 提示消息 + */ + private String msg; + + /** + * 登录状态(0成功 1失败) + */ + @ExcelProperty(value = "登录状态", converter = StatusConverter.class) + private Integer status; + + /** + * 访问时间 + */ + private Date loginTime; + + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysMenuEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysMenuEntity.java new file mode 100644 index 0000000..a72926c --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysMenuEntity.java @@ -0,0 +1,86 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 菜单表 + *

+ * + * @author admin + * @since 2022-07-03 + */ +@Getter +@Setter +@TableName("sys_menu") +@ApiModel(value = "XlMenu对象", description = "菜单表") +public class SysMenuEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("菜单ID") + @TableId(value = "menu_id", type = IdType.AUTO) + private Long menuId; + + @ApiModelProperty("菜单名") + private String menuName; + + @ApiModelProperty("菜单编码") + private String menuCode; + + @ApiModelProperty("权限字符串") + private String permission; + + @ApiModelProperty("图标") + private String icon; + + @ApiModelProperty("菜单级别") + private Long menuLevel; + + @ApiModelProperty("排序") + private Integer sort; + + @ApiModelProperty("父ID") + private Long parentId; + + @ApiModelProperty("菜单类型;0:目录,1:菜单,2:按钮") + private String menuType; + + @ApiModelProperty("菜单状态;0正常 1停用") + private Integer status; + + @ApiModelProperty("备注") + private String remark; + + @ApiModelProperty("路由地址") + private String path; + + @ApiModelProperty("组件路径") + private String component; + + @ApiModelProperty("路由参数") + private String routerQuery; + + @ApiModelProperty("是否为外链(0否 1是)") + private Integer isFrame; + + @ApiModelProperty("菜单状态;0:隐藏 1:显示") + private Integer visible; + + /** + * 子菜单 + */ + @TableField(exist = false) + private List children = new ArrayList<>(); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysOperationLogEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysOperationLogEntity.java new file mode 100644 index 0000000..07ff316 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysOperationLogEntity.java @@ -0,0 +1,131 @@ +package com.starry.admin.modules.system.entity; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + + +/** + * 操作日志表对象 sys_operation_log + * + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@ExcelIgnoreUnannotated +@Data +@TableName("sys_operation_log") +@ApiModel(value = "SysOperationLog对象", description = "操作日志表") +public class SysOperationLogEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "oper_id", type = IdType.AUTO) + private Long operId; + + /** + * 模块标题 + */ + @ExcelProperty(value = "系统模块") + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + @TableField(exist = false) + private Integer[] businessTypes; + + /** + * 方法名称 + */ + @ExcelProperty(value = "方法名称") + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 请求URL + */ + private String operUrl; + + /** + * 主机地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 部门id + */ + private Long deptId; + + /** + * 租户ID + */ + private Long tenantId; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleDeptEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleDeptEntity.java new file mode 100644 index 0000000..1c080fa --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleDeptEntity.java @@ -0,0 +1,41 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 角色部门表对象 sys_dict_data + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +@TableName("sys_role_dept") +public class SysRoleDeptEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "role_dept_id", type = IdType.AUTO) + private Long roleDeptId; + + @ApiModelProperty("角色ID") + private Long roleId; + + @ApiModelProperty("菜单ID") + private Long deptId; + + @ApiModelProperty("租户ID") + private Long tenantId; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleEntity.java new file mode 100644 index 0000000..bf0a847 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleEntity.java @@ -0,0 +1,90 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.Set; + +/** + *

+ * 角色表 + *

+ * + * @author admin + * @since 2022-07-01 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_role") +@NoArgsConstructor +@ApiModel(value = "XlRole对象", description = "角色表") +public class SysRoleEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("角色ID") + @TableId(value = "role_id", type = IdType.AUTO) + private Long roleId; + + @ApiModelProperty("角色名称") + private String roleName; + + @ApiModelProperty("角色描述") + private String description; + + @ApiModelProperty("状态;0正常 1停用") + private Integer status; + + @ApiModelProperty("数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) ") + private String dataScope; + + @ApiModelProperty("菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)") + private boolean menuCheckStrictly; + + @ApiModelProperty("部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )") + private boolean deptCheckStrictly; + + @ApiModelProperty("角色权限") + private String roleKey; + + @ApiModelProperty("租户ID") + private String tenantId; + + /** + * 菜单组 + */ + @TableField(exist = false) + private Long[] menuIds; + + /** + * 部门组(数据权限) + */ + @TableField(exist = false) + private Long[] deptIds; + + /** + * 角色菜单权限 + */ + @TableField(exist = false) + private Set permissions; + + public SysRoleEntity(Long roleId) { + this.roleId = roleId; + } + + public static boolean isAdmin(Long roleId) { + return roleId != null && 1L == roleId; + } + + public boolean isAdmin() { + return isAdmin(this.roleId); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleMenuEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleMenuEntity.java new file mode 100644 index 0000000..fad492b --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysRoleMenuEntity.java @@ -0,0 +1,42 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 角色和菜单关联表 + *

+ * + * @author admin + * @since 2022-07-06 + */ +@Getter +@Setter +@TableName("sys_role_menu") +@ApiModel(value = "SysRoleMenu对象", description = "角色和菜单关联表") +public class SysRoleMenuEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键id") + @TableId(value = "role_menu_id", type = IdType.AUTO) + private Long roleMenuId; + + @ApiModelProperty("角色ID") + private Long roleId; + + @ApiModelProperty("菜单ID") + private Long menuId; + + @ApiModelProperty("租户ID") + private String tenantId; + + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysUserEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysUserEntity.java new file mode 100644 index 0000000..685d01d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysUserEntity.java @@ -0,0 +1,115 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.Date; +import java.util.List; + +/** + *

+ * 用户表 + *

+ * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "SysUser对象", description = "用户表") +@TableName("sys_user") +public class SysUserEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "用户ID") + @TableId(value = "user_id", type = IdType.AUTO) + private String userId; + + @ApiModelProperty(value = "用户类型;1:admin;2:会员") + private Integer userType; + + @ApiModelProperty(value = "用户名") + private String userCode; + + @ApiModelProperty(value = "密码") + private String passWord; + + @ApiModelProperty(value = "姓名") + private String realName; + + @ApiModelProperty(value = "用户昵称") + private String userNickname; + + @ApiModelProperty(value = "邮箱") + private String userEmail; + + @ApiModelProperty(value = "用户头像") + private String avatar; + + @ApiModelProperty(value = "中国手机不带国家代码,国际手机号格式为:国家代码-手机号") + private String mobile; + + @ApiModelProperty(value = "性别 0:未知;1:男;2:女") + private Integer sex; + + @ApiModelProperty(value = "出生年月") + private Date birthday; + + @ApiModelProperty(value = "用户状态;0正常 1停用") + private Integer status; + + @ApiModelProperty(value = "最后登录时间") + private Date lastLoginTime; + + @ApiModelProperty(value = "最后登录ip") + private String lastLoginIp; + + @ApiModelProperty(value = "所属部门id") + private Long deptId; + + @ApiModelProperty(value = "租户id") + private String tenantId; + + /** + * 角色对象 + */ + @TableField(exist = false) + private List roles; + + /** + * 角色组 + */ + @TableField(exist = false) + private Long[] roleIds; + + /** + * 部门对象 + */ + @TableField(exist = false) + private SysDeptEntity dept; + + /** + * 角色ID + */ + @TableField(exist = false) + private Long roleId; + + public static boolean isAdmin(String userId) { + return "6dcb2da45fef4768a6511f9c14e18072".equals(userId); + } + + public boolean isAdmin() { + return isAdmin(this.userId); + } + + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysUserRoleEntity.java b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysUserRoleEntity.java new file mode 100644 index 0000000..4c8b809 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/entity/SysUserRoleEntity.java @@ -0,0 +1,40 @@ +package com.starry.admin.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 用户角色关联表 + *

+ * + * @author admin + * @since 2022-07-08 + */ +@Getter +@Setter +@TableName("sys_user_role") +@ApiModel(value = "SysUserRole对象", description = "用户角色关联表") +public class SysUserRoleEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键ID") + @TableId(value = "user_role_id", type = IdType.AUTO) + private Long userRoleId; + + @ApiModelProperty("用户ID") + private String userId; + + @ApiModelProperty("角色ID") + private Long roleId; + + @ApiModelProperty(value = "租户id") + private String tenantId; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDeptMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..d166c3e --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDeptMapper.java @@ -0,0 +1,55 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.admin.modules.system.entity.SysDeptEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 部门表 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-12 + */ +@Mapper +public interface SysDeptMapper extends BaseMapper { + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + List selectDeptList(@Param(value = "dept") SysDeptEntity dept); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + List selectChildrenDeptById(Long deptId); + + /** + * 删除部门信息-根据租户ID + * + * @param ids 租户id + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteDeptByTenantId(String[] ids); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictDataMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..2b19388 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictDataMapper.java @@ -0,0 +1,43 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysDictDataEntity; +import com.starry.admin.modules.system.vo.SysDictDataVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典数据表Mapper接口 + * + * @author admin + */ +@Mapper +public interface SysDictDataMapper extends BaseMapper { + /** + * 查询字典数据表 + * + * @param dictDataId 字典数据表主键 + * @return 字典数据表 + */ + SysDictDataEntity selectXlDictDataByDictDataId(Long dictDataId); + + /** + * 查询字典数据表列表 + * + * @param sysDictDataEntity 字典数据表 + * @return 字典数据表集合 + */ + IPage selectXlDictDataList(Page page, @Param("vo") SysDictDataVo sysDictDataEntity); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + List selectDictDataByType(String dictType); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictMapper.java new file mode 100644 index 0000000..b2990ec --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictMapper.java @@ -0,0 +1,34 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysDictEntity; +import com.starry.admin.modules.system.vo.SysDictVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 字典表Mapper接口 + * + * @author admin + */ +@Mapper +public interface SysDictMapper extends BaseMapper { + /** + * 查询字典表 + * + * @param dictId 字典表主键 + * @return 字典表 + */ + SysDictEntity selectXlDictByDictId(Long dictId); + + /** + * 查询字典表列表 + * + * @param sysDictEntity 字典表 + * @return 字典表集合 + */ + Page selectXlDictList(IPage page, @Param(value = "sysDictVo") SysDictVo sysDictEntity); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysLoginLogMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysLoginLogMapper.java new file mode 100644 index 0000000..6952fba --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysLoginLogMapper.java @@ -0,0 +1,41 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysLoginLogEntity; +import com.starry.admin.modules.system.entity.SysLoginLogVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 系统访问日志表Mapper接口 + * + * @author admin + */ +@Mapper +public interface SysLoginLogMapper extends BaseMapper { + /** + * 查询系统访问日志表 + * + * @param loginId 系统访问日志表主键 + * @return 系统访问日志表 + */ + SysLoginLogEntity selectXlLoginLogByLoginId(Long loginId); + + /** + * 查询系统访问日志表列表 + * + * @param sysLoginLogEntity 系统访问日志表 + * @return 系统访问日志表集合 + */ + IPage selectXlLoginLogList(Page page, @Param("vo") SysLoginLogVo sysLoginLogEntity); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + int cleanLoginlog(); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysMenuMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..78c62ab --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysMenuMapper.java @@ -0,0 +1,75 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysMenuEntity; +import com.starry.admin.modules.system.vo.SimpleMenu; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 菜单表 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-03 + */ +@Mapper +public interface SysMenuMapper extends BaseMapper { + + List selectMenuListByUserId(@Param(value = "menu") SysMenuEntity menu, @Param(value = "userId") String userId, Page page); + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + List selectMenuList(SysMenuEntity menu); + + List selectMenuListByUserId(@Param(value = "menu") SysMenuEntity menu, @Param(value = "userId") String userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @InterceptorIgnore(tenantLine = "1") + List selectMenuPermsByUserId(String userId); + + /** + * 查询菜单 + * + * @return 菜单列表 + */ + List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuTreeByUserId(String userId); + + /** + * 查询所有开启状态菜单精简信息 + * + * @return 菜单列表 + */ + List selectSimpleMenuList(); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysOperationLogMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysOperationLogMapper.java new file mode 100644 index 0000000..4eff571 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysOperationLogMapper.java @@ -0,0 +1,34 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysOperationLogEntity; +import com.starry.admin.modules.system.vo.SysOperationLogVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 操作日志表Mapper接口 + * + * @author admin + */ +@Mapper +public interface SysOperationLogMapper extends BaseMapper { + /** + * 查询操作日志表 + * + * @param operId 操作日志表主键 + * @return 操作日志表 + */ + SysOperationLogEntity selectXlOperLogByOperId(Long operId); + + /** + * 查询操作日志表列表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 操作日志表集合 + */ + IPage selectXlOperLogList(Page page, @Param("vo") SysOperationLogVo sysOperationLogEntity); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleDeptMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..0835d80 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,54 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.admin.modules.system.entity.SysRoleDeptEntity; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + + +/** + *

+ * 角色和部门关联表 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-06 + */ +@Mapper +public interface SysRoleDeptMapper extends BaseMapper { + + /** + * 通过租户ID删除角色和部门关联 + * + * @param tenantId 租户ID + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteRoleDeptByTenantId(Long tenantId); + + /** + * 通过角色ID删除角色和部门关联 + * + * @param roleId 角色ID + * @return 结果 + */ + int deleteRoleDeptByRoleId(Long roleId); + + /** + * 批量新增角色部门信息 + * + * @param roleDeptList 角色部门列表 + * @return 结果 + */ + int batchRoleDept(List roleDeptList); + + /** + * 批量删除角色部门关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + int deleteRoleDept(Long[] ids); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..1288979 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMapper.java @@ -0,0 +1,88 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + + +/** + *

+ * 角色表 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-01 + */ +@Mapper +public interface SysRoleMapper extends BaseMapper { + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + SysRoleEntity selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + SysRoleEntity checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + SysRoleEntity checkRoleKeyUnique(String roleKey); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + List selectRolePermissionByUserId(String userId); + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + List selectRoleList(SysRoleEntity role); + + /** + * 根据租户ID查询默认管理员角色 + * + * @param tenantId 租户ID + * @return 角色 + */ + @InterceptorIgnore(tenantLine = "1") + List queryAdminRole(String tenantId); + + /** + * 批量删除角色信息-根据租户 + * + * @param ids 需要删除的租户id + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteRoleByTenantId(String[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + int countUserRoleByRoleId(Long roleId); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMenuMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..95f5c7a --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,45 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.admin.modules.system.entity.SysRoleMenuEntity; +import org.apache.ibatis.annotations.Mapper; + + +/** + *

+ * 角色和菜单关联表 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-06 + */ +@Mapper +public interface SysRoleMenuMapper extends BaseMapper { + + /** + * 通过租户ID删除角色和菜单关联 + * + * @param tenantId 租户ID + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteRoleMenuByTenantId(String tenantId); + + /** + * 通过租户ID删除角色和菜单关联 + * + * @param ids 租户ID + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteRoleMenuByTenantIds(String[] ids); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + int deleteRoleMenu(Long[] ids); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..a4a8cdf --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserMapper.java @@ -0,0 +1,85 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.vo.RoleUserResultVo; +import com.starry.admin.modules.system.vo.SysUserQueryVo; +import com.starry.admin.modules.system.vo.UserQueryVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 后台用户表 Mapper 接口 + *

+ * + * @author admin + * @since 2021-09-03 + */ +@Mapper +public interface SysUserMapper extends BaseMapper { + + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + SysUserEntity selectUserById(String userId); + + /** + * 根据条件分页查询用户列表 + * + * @param queryVo 条件信息 + * @param page 分页信息 + * @return 用户信息集合信息 + */ + IPage selectUserList(IPage page, @Param("user") UserQueryVo queryVo); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @InterceptorIgnore(tenantLine = "1") + SysUserEntity selectUserByUserName(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int checkUserNameUnique(String userName); + + /** + * 批量删除用户信息-根据租户 + * + * @param ids 需要删除的租户ID + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteUserByTenantId(String[] ids); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + IPage selectAllocatedList(Page page, @Param("query") SysUserQueryVo user); + + /** + * 根据条件分页查询未配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + IPage selectUnallocatedList(Page page, @Param("query") SysUserQueryVo user); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserRoleMapper.java b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..8b52a13 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,46 @@ +package com.starry.admin.modules.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.admin.modules.system.entity.SysUserRoleEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + + +/** + *

+ * 用户角色关联表 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-08 + */ +@Mapper +public interface SysUserRoleMapper extends BaseMapper { + + /** + * 批量删除用户和角色关联-根据租户 + * + * @param ids 需要删除的用户租户id + * @return 结果 + */ + @InterceptorIgnore(tenantLine = "1") + int deleteUserRoleByTenantId(String[] ids); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + int deleteUserRoleInfo(SysUserRoleEntity userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") String[] userIds); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictDataService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictDataService.java new file mode 100644 index 0000000..90dbef5 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictDataService.java @@ -0,0 +1,71 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysDictDataEntity; +import com.starry.admin.modules.system.vo.SysDictDataVo; + +import java.util.List; + +/** + * 字典数据表Service接口 + * + * @author admin + */ +public interface ISysDictDataService extends IService { + /** + * 查询字典数据表 + * + * @param dictDataId 字典数据表主键 + * @return 字典数据表 + */ + SysDictDataEntity selectXlDictDataByDictDataId(Long dictDataId); + + /** + * 查询字典数据表列表 + * + * @param sysDictDataEntity 字典数据表 + * @return 字典数据表集合 + */ + IPage selectXlDictDataList(SysDictDataVo sysDictDataEntity); + + /** + * 新增字典数据表 + * + * @param sysDictDataEntity 字典数据表 + * @return 结果 + */ + boolean create(SysDictDataEntity sysDictDataEntity); + + /** + * 修改字典数据表 + * + * @param sysDictDataEntity 字典数据表 + * @return 结果 + */ + boolean update(SysDictDataEntity sysDictDataEntity); + + /** + * 批量删除字典数据表 + * + * @param dictDataIds 需要删除的字典数据表主键集合 + * @return 结果 + */ + int deleteXlDictDataByDictDataIds(Long[] dictDataIds); + + /** + * 删除字典数据表信息 + * + * @param dictDataId 字典数据表主键 + * @return 结果 + */ + int deleteXlDictDataByDictDataId(Long dictDataId); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + List selectDictDataByType(String dictType); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictService.java new file mode 100644 index 0000000..8bbd65c --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictService.java @@ -0,0 +1,87 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysDictEntity; +import com.starry.admin.modules.system.vo.SysDictVo; + +import java.util.List; + +/** + * 字典表Service接口 + * + * @author admin + * @since 2022-08-09 + */ +public interface ISysDictService extends IService { + /** + * 查询字典表 + * + * @param dictId 字典表主键 + * @return 字典表 + */ + SysDictEntity selectXlDictByDictId(Long dictId); + + /** + * 查询字典表列表 + * + * @param sysDictVo 字典表 + * @return 字典表集合 + */ + Page selectXlDictList(SysDictVo sysDictVo); + + /** + * 新增字典表 + * + * @param sysDictEntity 字典表 + * @return 结果 + */ + boolean create(SysDictEntity sysDictEntity); + + /** + * 修改字典表 + * + * @param sysDictEntity 字典表 + * @return 结果 + */ + boolean update(SysDictEntity sysDictEntity); + + /** + * 批量删除字典表 + * + * @param dictIds 需要删除的字典表主键集合 + * @return 结果 + */ + int deleteXlDictByDictIds(Long[] dictIds); + + /** + * 删除字典表信息 + * + * @param dictId 字典表主键 + * @return 结果 + */ + int deleteXlDictByDictId(Long dictId); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + List selectDictTypeAll(); + + /** + * 加载字典缓存数据 + */ + void loadingDictCache(); + + /** + * 清空字典缓存数据 + */ + void clearDictCache(); + + /** + * 重置字典缓存数据 + */ + void resetDictCache(); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysLoginLogService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysLoginLogService.java new file mode 100644 index 0000000..e5470d8 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysLoginLogService.java @@ -0,0 +1,66 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysLoginLogEntity; +import com.starry.admin.modules.system.entity.SysLoginLogVo; + +/** + * 系统访问日志表Service接口 + * + * @author admin + */ +public interface ISysLoginLogService extends IService { + /** + * 查询系统访问日志表 + * + * @param loginId 系统访问日志表主键 + * @return 系统访问日志表 + */ + SysLoginLogEntity selectXlLoginLogByLoginId(Long loginId); + + /** + * 查询系统访问日志表列表 + * + * @param sysLoginLogEntity 系统访问日志表 + * @return 系统访问日志表集合 + */ + IPage selectXlLoginLogList(SysLoginLogVo sysLoginLogEntity); + + /** + * 新增系统访问日志表 + * + * @param sysLoginLogEntity 系统访问日志表 + * @return 结果 + */ + boolean create(SysLoginLogEntity sysLoginLogEntity); + + /** + * 修改系统访问日志表 + * + * @param sysLoginLogEntity 系统访问日志表 + * @return 结果 + */ + boolean update(SysLoginLogEntity sysLoginLogEntity); + + /** + * 批量删除系统访问日志表 + * + * @param loginIds 需要删除的系统访问日志表主键集合 + * @return 结果 + */ + int deleteXlLoginLogByLoginIds(Long[] loginIds); + + /** + * 删除系统访问日志表信息 + * + * @param loginId 系统访问日志表主键 + * @return 结果 + */ + int deleteXlLoginLogByLoginId(Long loginId); + + /** + * 清空系统登录日志 + */ + void cleanLoginlog(); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysOperationLogService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysOperationLogService.java new file mode 100644 index 0000000..c8c882f --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/ISysOperationLogService.java @@ -0,0 +1,61 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysOperationLogEntity; +import com.starry.admin.modules.system.vo.SysOperationLogVo; + +/** + * 操作日志表Service接口 + * + * @author admin + */ +public interface ISysOperationLogService extends IService { + /** + * 查询操作日志表 + * + * @param operId 操作日志表主键 + * @return 操作日志表 + */ + SysOperationLogEntity selectXlOperLogByOperId(Long operId); + + /** + * 查询操作日志表列表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 操作日志表集合 + */ + IPage selectXlOperLogList(SysOperationLogVo sysOperationLogEntity); + + /** + * 新增操作日志表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 结果 + */ + boolean create(SysOperationLogEntity sysOperationLogEntity); + + /** + * 修改操作日志表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 结果 + */ + boolean updateXlOperLog(SysOperationLogEntity sysOperationLogEntity); + + /** + * 批量删除操作日志表 + * + * @param operIds 需要删除的操作日志表主键集合 + * @return 结果 + */ + int deleteXlOperLogByOperIds(Long[] operIds); + + /** + * 删除操作日志表信息 + * + * @param operId 操作日志表主键 + * @return 结果 + */ + int deleteXlOperLogByOperId(Long operId); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/LoginService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/LoginService.java new file mode 100644 index 0000000..a81f6cd --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/LoginService.java @@ -0,0 +1,55 @@ +package com.starry.admin.modules.system.service; + + +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.modules.system.entity.SysUserEntity; + +import java.util.Set; + +/** + * @author admin + */ +public interface LoginService { + + /** + * 登录功能 + * + * @param username 用户名 + * @param password 密码 + * @return 生成的JWT的token + */ + String login(String username, String password); + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + Set getMenuPermission(SysUserEntity user); + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + Set getRolePermission(SysUserEntity user); + + /** + * 登录功能 + * + * @param username 用户名 + * @param password 密码 + * @return 生成的JWT的token + */ + LoginUser newLogin(String username, String password); + + /** + * 新登录功能,增加租户相关 + * + * @param userName 用户名 + * @return 获取信息 + */ + LoginUser getLoginUserInfo(String userName); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/SysDeptService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysDeptService.java new file mode 100644 index 0000000..bc8f1ac --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysDeptService.java @@ -0,0 +1,90 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.common.domain.TreeSelect; +import com.starry.admin.modules.system.entity.SysDeptEntity; + +import java.util.List; + +/** + *

+ * 部门表 服务类 + *

+ * + * @author admin + * @since 2022-07-12 + */ +public interface SysDeptService extends IService { + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + List selectDeptList(SysDeptEntity dept); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + String checkDeptNameUnique(SysDeptEntity dept); + + /** + * 添加部门 + * + * @param dept + * @return boolean + **/ + boolean create(SysDeptEntity dept); + + /** + * 修改部门 + * + * @param dept + * @return boolean + **/ + boolean update(SysDeptEntity dept); + + /** + * 批量删除部门 + * + * @param ids + * @return boolean + **/ + boolean delete(List ids); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + boolean hasChildByDeptId(Long deptId); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + List buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + List selectDeptListByRoleId(Long roleId); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + List selectDeptTreeList(SysDeptEntity dept); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/SysMenuService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysMenuService.java new file mode 100644 index 0000000..576c3a7 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysMenuService.java @@ -0,0 +1,130 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.common.domain.TreeSelect; +import com.starry.admin.modules.system.entity.SysMenuEntity; +import com.starry.admin.modules.system.vo.RouterVo; +import com.starry.admin.modules.system.vo.SimpleMenu; + +import java.util.List; +import java.util.Set; + +/** + *

+ * 菜单表 服务类 + *

+ * + * @author admin + * @since 2022-07-03 + */ +public interface SysMenuService extends IService { + + /** + * 添加菜单 + * + * @param menu + * @return boolean + **/ + boolean create(SysMenuEntity menu); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单 + * @param userId 用户ID + * @param pageSize 页大小 + * @param pageNum 页数 + * @return 分页菜单列表 + */ + Page listPage(SysMenuEntity menu, String userId, Integer pageSize, Integer pageNum); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单 + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuList(SysMenuEntity menu, String userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuList(String userId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + String checkMenuNameUnique(SysMenuEntity menu); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + boolean hasChildByMenuId(Long menuId); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + List buildMenuTreeSelect(List menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + SysMenuEntity selectMenuById(Long menuId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + List selectMenuListByRoleId(Long roleId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + Set selectMenuPermsByUserId(String userId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuTreeByUserId(String userId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + List buildMenus(List menus); + + /** + * 查询所有开启状态菜单精简信息 + * + * @return 菜单列表 + */ + List selectSimpleMenuList(); + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleMenuService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleMenuService.java new file mode 100644 index 0000000..42d3760 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleMenuService.java @@ -0,0 +1,17 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysRoleMenuEntity; + + +/** + *

+ * 角色和菜单关联表 服务类 + *

+ * + * @author admin + * @since 2022-07-06 + */ +public interface SysRoleMenuService extends IService { + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleService.java new file mode 100644 index 0000000..d33a048 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleService.java @@ -0,0 +1,151 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.entity.SysUserRoleEntity; + +import java.util.List; +import java.util.Set; + + +/** + *

+ * 角色表 服务类 + *

+ * + * @author admin + * @since 2022-07-01 + */ +public interface SysRoleService extends IService { + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + String checkRoleNameUnique(SysRoleEntity role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + String checkRoleKeyUnique(SysRoleEntity role); + + /** + * 添加角色 + * + * @param role + * @return boolean + **/ + boolean create(SysRoleEntity role); + + /** + * 修改角色 + * + * @param role + * @return boolean + **/ + boolean updateRole(SysRoleEntity role); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + int countUserRoleByRoleId(Long roleId); + + /** + * 批量删除角色 + * + * @param roleIds + * @return boolean + **/ + boolean delete(Long[] roleIds); + + /** + * 分页获取角色列表 + * + * @param keyword + * @param pageSize + * @param pageNum + * @return Page + **/ + Page list(String keyword, Integer pageSize, Integer pageNum); + + /** + * 分配菜单权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + boolean authRoleMenu(SysRoleEntity role); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + Set selectRolePermissionByUserId(String userId); + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + List selectRoleList(SysRoleEntity role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + void checkRoleAllowed(SysRoleEntity role); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + void checkRoleDataScope(Long roleId); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + int authDataScope(SysRoleEntity role); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + int deleteAuthUser(SysUserRoleEntity userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + int deleteAuthUsers(Long roleId, String[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + int insertAuthUsers(Long roleId, String[] userIds); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserRoleService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserRoleService.java new file mode 100644 index 0000000..2bcc455 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserRoleService.java @@ -0,0 +1,17 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysUserRoleEntity; + + +/** + *

+ * 用户角色关联表 服务类 + *

+ * + * @author admin + * @since 2022-07-08 + */ +public interface SysUserRoleService extends IService { + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserService.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserService.java new file mode 100644 index 0000000..8a93ffb --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserService.java @@ -0,0 +1,126 @@ +package com.starry.admin.modules.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.vo.RoleUserResultVo; +import com.starry.admin.modules.system.vo.SysUserQueryVo; +import com.starry.admin.modules.system.vo.UserQueryVo; + +import java.util.List; + +/** + *

+ * 后台用户表 服务类 + *

+ * + * @author admin + * @since 2021-09-03 + */ +public interface SysUserService extends IService { + + /** + * 注册功能 + * + * @param user + * @return SysUserEntity + */ + SysUserEntity register(SysUserEntity user); + + /** + * 查询用户 + * + * @param userCode + * @return SysUserEntity + */ + SysUserEntity getUserByCode(String userCode); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + SysUserEntity selectUserByUserName(String userName); + + /** + * 获取列表。分页 + * + * @param queryVo 查询参数 + * @return page + */ + IPage listMemberPage(UserQueryVo queryVo); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + String checkUserNameUnique(String userName); + + /** + * 添加用户 + * + * @param user + * @return boolean + **/ + boolean create(SysUserEntity user); + + /** + * 修改用户 + * + * @param user + * @return boolean + **/ + boolean update(SysUserEntity user); + + /** + * 批量删除用户 + * + * @param ids + * @return boolean + **/ + boolean delete(List ids); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + SysUserEntity selectUserById(String userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userId 用户id + * @return 结果 + */ + String selectUserRoleGroup(String userId); + + /** + * 修改用户头像 + * + * @param userId 用户id + * @param avatar 头像地址 + * @return 结果 + */ + boolean updateUserAvatar(String userId, String avatar); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + IPage selectAllocatedList(SysUserQueryVo user); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + IPage selectUnallocatedList(SysUserQueryVo user); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/LoginServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..fb7f225 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/LoginServiceImpl.java @@ -0,0 +1,183 @@ +package com.starry.admin.modules.system.service.impl; + + +import cn.hutool.core.convert.Convert; +import com.starry.admin.common.component.JwtToken; +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.common.exception.ServiceException; +import com.starry.admin.common.security.entity.JwtUser; +import com.starry.admin.manager.AsyncManager; +import com.starry.admin.manager.factory.AsyncFactory; +import com.starry.admin.modules.platform.entity.SysTenantEntity; +import com.starry.admin.modules.platform.service.ISysTenantService; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.service.LoginService; +import com.starry.admin.modules.system.service.SysMenuService; +import com.starry.admin.modules.system.service.SysRoleService; +import com.starry.admin.modules.system.service.SysUserService; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.constant.Constants; +import com.starry.common.constant.UserConstants; +import com.starry.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * @author admin + */ +@Slf4j +@Service +public class LoginServiceImpl implements LoginService { + + @Resource + private PasswordEncoder passwordEncoder; + @Resource + private UserDetailsService userDetailsService; + @Resource + private JwtToken jwtTokenUtil; + @Resource + private SysMenuService menuService; + @Resource + private SysRoleService roleService; + @Resource + private SysUserService sysUserService; + @Resource + private ISysTenantService SysTenantService; + + @Override + public String login(String username, String password) { + String token = null; + try { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + if (!passwordEncoder.matches(password, userDetails.getPassword())) { + throw new BadCredentialsException("密码不正确"); + } + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + // 登录成功记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_SUCCESS, "登录成功")); + JwtUser jwtUser = (JwtUser) authentication.getPrincipal(); + token = jwtTokenUtil.createToken(jwtUser); + } catch (AuthenticationException e) { + log.warn("登录异常:{}", e.getMessage()); + // 登录失败记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, e.getMessage())); + } + return token; + } + + @Override + public Set getMenuPermission(SysUserEntity user) { + Set perms = new HashSet<>(); + // 超级管理员拥有所有权限 + if (SecurityUtils.isAdmin(user.getUserId())) { + perms.add("*:*:*"); + } else { + perms = menuService.selectMenuPermsByUserId(user.getUserId()); + } + return perms; + } + + @Override + public Set getRolePermission(SysUserEntity user) { + Set roles = new HashSet<>(); + // 超级管理员拥有所有权限 + if (SecurityUtils.isAdmin(user.getUserId())) { + roles.add("admin"); + } else { + roles = roleService.selectRolePermissionByUserId(user.getUserId()); + } + return roles; + } + + @Override + public LoginUser newLogin(String username, String password) { + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(username, password)) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户名/密码必须填写")); + throw new ServiceException("用户/密码必须填写"); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户密码不在指定范围")); + throw new ServiceException("用户密码不在指定范围"); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户名不在指定范围")); + throw new ServiceException("用户名不在指定范围"); + } + LoginUser userInfo = this.getLoginUserInfo(username); + if (userInfo == null) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户名不存在")); + throw new ServiceException("用户名不存在"); + } + SysUserEntity user = userInfo.getUser(); + if (!SecurityUtils.matchesPassword(password, user.getPassWord())) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "密码错误")); + throw new ServiceException("密码错误"); + } + // 线程塞入租户ID + SecurityUtils.setTenantId(Convert.toStr(user.getTenantId())); + if (user.getStatus() == 1) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员")); + throw new ServiceException("对不起,您的账号:" + username + " 已停用,请联系管理员"); + } + // 先查询是否被停用了租户 + if (userInfo.getTenantStatus() != null && userInfo.getTenantStatus() == 1) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "当前租户已经被停用,请联系管理员")); + throw new ServiceException("当前租户已经被停用,请联系管理员"); + } + if (userInfo.getTenantEndDate() != null && userInfo.getTenantEndDate().compareTo(new Date()) < 0) { + // 登录记录日志 + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "当前租户已超过租赁日期,请联系管理员")); + throw new ServiceException("当前租户已超过租赁日期,请联系管理员"); + } + AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_SUCCESS, "登录成功")); + return userInfo; + } + + @Override + public LoginUser getLoginUserInfo(String userName) { + SysUserEntity sysUser = sysUserService.selectUserByUserName(userName); + if (StringUtils.isNotNull(sysUser)) { + // 角色集合 + Set roles = this.getRolePermission(sysUser); + // 权限集合 + Set permissions = this.getMenuPermission(sysUser); + // 查询租户信息 + SysTenantEntity tenant = SysTenantService.selectSysTenantByTenantId(sysUser.getTenantId()); + LoginUser sysUserVo = new LoginUser(); + sysUserVo.setUser(sysUser); + sysUserVo.setRoles(roles); + sysUserVo.setPermissions(permissions); + if (tenant != null) { + sysUserVo.setTenantEndDate(tenant.getTenantTime()); + sysUserVo.setTenantStatus(Integer.valueOf(tenant.getTenantStatus())); + } + return sysUserVo; + } + return null; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDeptServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..4f3e92d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,195 @@ +package com.starry.admin.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.common.domain.TreeSelect; +import com.starry.admin.modules.system.entity.SysDeptEntity; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.mapper.SysDeptMapper; +import com.starry.admin.modules.system.mapper.SysRoleMapper; +import com.starry.admin.modules.system.service.SysDeptService; +import com.starry.common.annotation.DataScope; +import com.starry.common.constant.UserConstants; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 部门表 服务实现类 + *

+ * + * @author admin + * @since 2022-07-12 + */ +@Service +public class SysDeptServiceImpl extends ServiceImpl implements SysDeptService { + + @Resource + private SysRoleMapper roleMapper; + + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDeptEntity dept) { + return baseMapper.selectDeptList(dept); + } + + @Override + public String checkDeptNameUnique(SysDeptEntity dept) { + Long deptId = dept.getDeptId() == null ? -1L : dept.getDeptId(); + List infos = list(new LambdaQueryWrapper() + .eq(SysDeptEntity::getDeptName, dept.getDeptName()) + .eq(SysDeptEntity::getParentId, dept.getParentId())); + // 查出有数据 并且不是自己,则不唯一 + if (CollectionUtil.isNotEmpty(infos) && infos.get(0).getDeptId().longValue() != deptId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + @Override + public boolean create(SysDeptEntity dept) { + SysDeptEntity info = getById(dept.getParentId()); + // 若父节点不为正常状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(String.valueOf(info.getStatus()))) { + throw new RuntimeException("部门停用,不允许新增"); + } + dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); + dept.setDeptLevel(info.getDeptLevel() == null ? 0 : info.getDeptLevel() + 1); + return save(dept); + } + + @Override + public boolean update(SysDeptEntity dept) { + // 新的上级部门 + SysDeptEntity newParentDept = getById(dept.getParentId()); + // 当前部门 + SysDeptEntity oldDept = getById(dept.getDeptId()); + if (newParentDept != null && oldDept != null) { + String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + return updateById(dept); + } + + @Override + public boolean delete(List ids) { + return removeBatchByIds(ids); + } + + @Override + public boolean hasChildByDeptId(Long deptId) { + List list = baseMapper.selectList(new LambdaQueryWrapper().eq(SysDeptEntity::getParentId, deptId)); + if (CollectionUtil.isNotEmpty(list)) { + return true; + } + return false; + } + + @Override + public List buildDeptTreeSelect(List depts) { + List deptList = buildDeptTree(depts); + // 转换成树结构的list + return deptList.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public List buildDeptTree(List depts) { + List returnList = new ArrayList<>(); + // 所有部门id + List tempList = new ArrayList<>(); + for (SysDeptEntity dept : depts) { + tempList.add(dept.getDeptId()); + } + for (SysDeptEntity dept : depts) { + // 如若是顶级节点,遍历该父节点下的所有子节点 + if (!tempList.contains(dept.getParentId())) { + recursionFn(depts, dept); + returnList.add(dept); + } + } + if (CollectionUtil.isEmpty(returnList)) { + returnList = depts; + } + return returnList; + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) { + List childrens = baseMapper.selectChildrenDeptById(deptId); + if (CollectionUtil.isNotEmpty(childrens)) { + for (SysDeptEntity child : childrens) { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + updateById(child); + } + } + } + + /** + * 递归列表 + */ + private void recursionFn(List list, SysDeptEntity t) { + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDeptEntity tChild : childList) { + if (hasChild(list, tChild)) { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDeptEntity t) { + List tList = new ArrayList<>(); + for (SysDeptEntity n : list) { + if (n.getParentId() != null && n.getParentId().longValue() == t.getDeptId().longValue()) { + tList.add(n); + } + } + return tList; + } + + /** + * 判断是否还有子节点 + */ + private boolean hasChild(List list, SysDeptEntity t) { + return !getChildList(list, t).isEmpty(); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) { + SysRoleEntity role = roleMapper.selectRoleById(roleId); + return baseMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + @Override + public List selectDeptTreeList(SysDeptEntity dept) { + List depts = this.selectDeptList(dept); + return buildDeptTreeSelect(depts); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictDataServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..8a4636e --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,113 @@ +package com.starry.admin.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysDictDataEntity; +import com.starry.admin.modules.system.mapper.SysDictDataMapper; +import com.starry.admin.modules.system.service.ISysDictDataService; +import com.starry.admin.modules.system.vo.SysDictDataVo; +import com.starry.admin.utils.DictUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + * 字典数据表Service业务层处理 + * + * @author admin + */ +@Service +public class SysDictDataServiceImpl extends ServiceImpl implements ISysDictDataService { + @Resource + private SysDictDataMapper sysDictDataMapper; + + /** + * 查询字典数据表 + * + * @param dictDataId 字典数据表主键 + * @return 字典数据表 + */ + @Override + public SysDictDataEntity selectXlDictDataByDictDataId(Long dictDataId) { + return sysDictDataMapper.selectXlDictDataByDictDataId(dictDataId); + } + + /** + * 查询字典数据表列表 + * + * @param sysDictDataEntity 字典数据表 + * @return 字典数据表 + */ + @Override + public IPage selectXlDictDataList(SysDictDataVo sysDictDataEntity) { + return sysDictDataMapper.selectXlDictDataList(new Page<>(sysDictDataEntity.getPageNum(), sysDictDataEntity.getPageSize()), sysDictDataEntity); + } + + /** + * 新增字典数据表 + * + * @param sysDictDataEntity 字典数据表 + * @return 结果 + */ + @Override + public boolean create(SysDictDataEntity sysDictDataEntity) { + return save(sysDictDataEntity); + } + + /** + * 修改字典数据表 + * + * @param sysDictDataEntity 字典数据表 + * @return 结果 + */ + @Override + public boolean update(SysDictDataEntity sysDictDataEntity) { + return updateById(sysDictDataEntity); + } + + /** + * 批量删除字典数据表 + * + * @param dictDataIds 需要删除的字典数据表主键 + * @return 结果 + */ + @Override + public int deleteXlDictDataByDictDataIds(Long[] dictDataIds) { + return sysDictDataMapper.deleteBatchIds(Arrays.asList(dictDataIds)); + } + + /** + * 删除字典数据表信息 + * + * @param dictDataId 字典数据表主键 + * @return 结果 + */ + @Override + public int deleteXlDictDataByDictDataId(Long dictDataId) { + return sysDictDataMapper.deleteById(dictDataId); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) { + List dictDataList = DictUtils.getDictCache(dictType); + if (CollectionUtil.isNotEmpty(dictDataList)) { + return dictDataList; + } + dictDataList = sysDictDataMapper.selectDictDataByType(dictType); + if (CollectionUtil.isNotEmpty(dictDataList)) { + DictUtils.setDictCache(dictType, dictDataList); + return dictDataList; + } + return null; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictServiceImpl.java new file mode 100644 index 0000000..9c0c0e0 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictServiceImpl.java @@ -0,0 +1,138 @@ +package com.starry.admin.modules.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysDictDataEntity; +import com.starry.admin.modules.system.entity.SysDictEntity; +import com.starry.admin.modules.system.mapper.SysDictDataMapper; +import com.starry.admin.modules.system.mapper.SysDictMapper; +import com.starry.admin.modules.system.service.ISysDictService; +import com.starry.admin.modules.system.vo.SysDictDataVo; +import com.starry.admin.modules.system.vo.SysDictVo; +import com.starry.admin.utils.DictUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 字典表Service业务层处理 + * + * @author admin + */ +@Service +public class SysDictServiceImpl extends ServiceImpl implements ISysDictService { + @Resource + private SysDictMapper sysDictMapper; + @Resource + private SysDictDataMapper sysDictDataMapper; + + /** + * 查询字典表 + * + * @param dictId 字典表主键 + * @return 字典表 + */ + @Override + public SysDictEntity selectXlDictByDictId(Long dictId) { + return sysDictMapper.selectXlDictByDictId(dictId); + } + + /** + * 查询字典表列表 + * + * @param sysDictVo 字典表 + * @return 字典表 + */ + @Override + public Page selectXlDictList(SysDictVo sysDictVo) { + return sysDictMapper.selectXlDictList(new Page<>(sysDictVo.getPageNum(), sysDictVo.getPageSize()), sysDictVo); + } + + /** + * 新增字典表 + * + * @param sysDictEntity 字典表 + * @return 结果 + */ + @Override + public boolean create(SysDictEntity sysDictEntity) { + return save(sysDictEntity); + } + + /** + * 修改字典表 + * + * @param sysDictEntity 字典表 + * @return 结果 + */ + @Override + public boolean update(SysDictEntity sysDictEntity) { + return updateById(sysDictEntity); + } + + /** + * 批量删除字典表 + * + * @param dictIds 需要删除的字典表主键 + * @return 结果 + */ + @Override + public int deleteXlDictByDictIds(Long[] dictIds) { + return sysDictMapper.deleteBatchIds(Arrays.asList(dictIds)); + } + + /** + * 删除字典表信息 + * + * @param dictId 字典表主键 + * @return 结果 + */ + @Override + public int deleteXlDictByDictId(Long dictId) { + return sysDictMapper.deleteById(dictId); + } + + @Override + public List selectDictTypeAll() { + return sysDictMapper.selectList(new LambdaQueryWrapper<>()); + } + + /** + * 加载字典缓存数据 + */ + @Override + public void loadingDictCache() { + SysDictDataVo dictData = new SysDictDataVo(); + dictData.setStatus(0); + // 按字典编码分组,不分页,size < 0 + IPage iPage = sysDictDataMapper.selectXlDictDataList(new Page<>(1, -1), dictData); + Map> dictDataMap = iPage.getRecords().stream().collect(Collectors.groupingBy(SysDictDataEntity::getDictType)); + for (Map.Entry> entry : dictDataMap.entrySet()) { + DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictDataEntity::getSort)).collect(Collectors.toList())); + } + } + + /** + * 清空字典缓存数据 + */ + @Override + public void clearDictCache() { + DictUtils.clearDictCache(); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() { + clearDictCache(); + loadingDictCache(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysLoginLogServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysLoginLogServiceImpl.java new file mode 100644 index 0000000..2ee35b5 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysLoginLogServiceImpl.java @@ -0,0 +1,95 @@ +package com.starry.admin.modules.system.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysLoginLogEntity; +import com.starry.admin.modules.system.entity.SysLoginLogVo; +import com.starry.admin.modules.system.mapper.SysLoginLogMapper; +import com.starry.admin.modules.system.service.ISysLoginLogService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Arrays; + +/** + * 系统访问日志表Service业务层处理 + * + * @author admin + */ +@Service +public class SysLoginLogServiceImpl extends ServiceImpl implements ISysLoginLogService { + @Resource + private SysLoginLogMapper xlLoginLogMapper; + + /** + * 查询系统访问日志表 + * + * @param loginId 系统访问日志表主键 + * @return 系统访问日志表 + */ + @Override + public SysLoginLogEntity selectXlLoginLogByLoginId(Long loginId) { + return xlLoginLogMapper.selectXlLoginLogByLoginId(loginId); + } + + /** + * 查询系统访问日志表列表 + * + * @param vo 系统访问日志表 + * @return 系统访问日志表 + */ + @Override + public IPage selectXlLoginLogList(SysLoginLogVo vo) { + return xlLoginLogMapper.selectXlLoginLogList(new Page<>(vo.getPageNum(), vo.getPageSize()), vo); + } + + /** + * 新增系统访问日志表 + * + * @param sysLoginLogEntity 系统访问日志表 + * @return 结果 + */ + @Override + public boolean create(SysLoginLogEntity sysLoginLogEntity) { + return save(sysLoginLogEntity); + } + + /** + * 修改系统访问日志表 + * + * @param sysLoginLogEntity 系统访问日志表 + * @return 结果 + */ + @Override + public boolean update(SysLoginLogEntity sysLoginLogEntity) { + return updateById(sysLoginLogEntity); + } + + /** + * 批量删除系统访问日志表 + * + * @param loginIds 需要删除的系统访问日志表主键 + * @return 结果 + */ + @Override + public int deleteXlLoginLogByLoginIds(Long[] loginIds) { + return xlLoginLogMapper.deleteBatchIds(Arrays.asList(loginIds)); + } + + /** + * 删除系统访问日志表信息 + * + * @param loginId 系统访问日志表主键 + * @return 结果 + */ + @Override + public int deleteXlLoginLogByLoginId(Long loginId) { + return xlLoginLogMapper.deleteById(loginId); + } + + @Override + public void cleanLoginlog() { + xlLoginLogMapper.cleanLoginlog(); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysMenuServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..f815f2d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,331 @@ +package com.starry.admin.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.common.domain.TreeSelect; +import com.starry.admin.modules.system.entity.SysMenuEntity; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.mapper.SysMenuMapper; +import com.starry.admin.modules.system.mapper.SysRoleMapper; +import com.starry.admin.modules.system.service.SysMenuService; +import com.starry.admin.modules.system.vo.MetaVo; +import com.starry.admin.modules.system.vo.RouterVo; +import com.starry.admin.modules.system.vo.SimpleMenu; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.constant.Constants; +import com.starry.common.constant.UserConstants; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 菜单表 服务实现类 + *

+ * + * @author admin + * @since 2022-07-03 + */ +@Service +public class SysMenuServiceImpl extends ServiceImpl implements SysMenuService { + + @Resource + private SysRoleMapper roleMapper; + + @Override + public boolean create(SysMenuEntity menu) { + return save(menu); + } + + + @Override + public Page listPage(SysMenuEntity menu, String userId, Integer pageSize, Integer pageNum) { + Page page = new Page<>(pageNum, pageSize); + // 超级管理员显示所有菜单信息 + if (SecurityUtils.isAdmin(userId)) { + return baseMapper.selectPage(page, new LambdaQueryWrapper().eq(SysMenuEntity::getStatus, 1)); + } else { + List menuList = baseMapper.selectMenuListByUserId(menu, userId, page); + return page.setRecords(menuList); + } + } + + @Override + public List selectMenuList(SysMenuEntity menu, String userId) { + List menuList; + // 超级管理员显示所有菜单信息 + if (SecurityUtils.isAdmin(userId)) { + menuList = baseMapper.selectMenuList(menu); + } else { + menuList = baseMapper.selectMenuListByUserId(menu, userId); + } + return menuList; + } + + @Override + public List selectMenuList(String userId) { + return selectMenuList(new SysMenuEntity(), userId); + } + + @Override + public String checkMenuNameUnique(SysMenuEntity menu) { + long menuId = menu.getMenuId() == null ? -1L : menu.getMenuId(); + List menus = baseMapper.selectList(new LambdaQueryWrapper() + .eq(SysMenuEntity::getMenuName, menu.getMenuName()) + .eq(SysMenuEntity::getParentId, menu.getParentId())); + if (CollectionUtil.isNotEmpty(menus) && menus.get(0).getMenuId() != menuId) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + @Override + public boolean hasChildByMenuId(Long menuId) { + Long result = baseMapper.selectCount(new LambdaQueryWrapper().eq(SysMenuEntity::getParentId, menuId)); + return result > 0; + } + + @Override + public List buildMenuTreeSelect(List menus) { + List returnList = new ArrayList<>(); + List tempList = new ArrayList<>(); + for (SysMenuEntity menu : menus) { + tempList.add(menu.getMenuId()); + } + for (SysMenuEntity menu : menus) { + // 如果是顶级节点,遍历该父节点所有的子节点 + if (!tempList.contains(menu.getParentId())) { + recursionList(menus, menu); + returnList.add(menu); + } + } + if (CollectionUtil.isEmpty(returnList)) { + returnList = menus; + } + return returnList.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + @Override + public SysMenuEntity selectMenuById(Long menuId) { + return baseMapper.selectById(menuId); + } + + @Override + public List selectMenuListByRoleId(Long roleId) { + SysRoleEntity role = roleMapper.selectRoleById(roleId); + return baseMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + @Override + public Set selectMenuPermsByUserId(String userId) { + // 获取菜单权限集合 + List perms = baseMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StrUtil.isNotEmpty(perm)) { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + @Override + public List selectMenuTreeByUserId(String userId) { + List menus; + if (SecurityUtils.isAdmin(userId)) { + menus = baseMapper.selectMenuTreeAll(); + } else { + menus = baseMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, 0); + } + + @Override + public List buildMenus(List menus) { + List routers = new LinkedList<>(); + for (SysMenuEntity menu : menus) { + RouterVo router = new RouterVo(); + // 0:隐藏 + router.setHidden("0".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setRouterQuery(menu.getRouterQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, menu.getPath())); + List cMenus = menu.getChildren(); + // 有子路由 + if (CollectionUtil.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(String.valueOf(menu.getMenuType()))) { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } else if (isMenuFrame(menu)) { + // 菜单内部跳转 + router.setMeta(null); + List childrenList = new ArrayList<>(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(StringUtils.capitalize(menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, menu.getPath())); + children.setRouterQuery(menu.getRouterQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList<>(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(StringUtils.capitalize(routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + @Override + public List selectSimpleMenuList() { + return baseMapper.selectSimpleMenuList(); + } + + /** + * 递归列表 + * + * @param menus + * @param t + */ + private void recursionList(List menus, SysMenuEntity t) { + // 得到子节点 + List childList = getChildList(menus, t); + t.setChildren(childList); + for (SysMenuEntity tChild : childList) { + if (hasChild(menus, tChild)) { + recursionList(menus, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenuEntity t) { + List tList = new ArrayList<>(); + for (SysMenuEntity n : list) { + if (n.getParentId().longValue() == t.getMenuId().longValue()) { + tList.add(n); + } + } + return tList; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysMenuEntity t) { + return !getChildList(list, t).isEmpty(); + } + + /** + * 获取路由名称 + */ + private String getRouteName(SysMenuEntity menu) { + // 首字母大写 + String routerName = StringUtils.capitalize(menu.getPath()); + // 非外链并且是一级目录(类型为菜单) + if (isMenuFrame(menu)) { + routerName = StringUtils.EMPTY; + } + return routerName; + } + + /** + * 获取路由地址 + */ + private String getRouterPath(SysMenuEntity menu) { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) { + // 内链域名特殊字符替换 + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (menu.getParentId().intValue() == 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) { + routerPath = "/" + menu.getPath(); + } else if (isMenuFrame(menu)) { + routerPath = "/"; + } + return routerPath; + } + + /** + * 是否为菜单内部跳转 + */ + private boolean isMenuFrame(SysMenuEntity menu) { + return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + */ + private boolean isInnerLink(SysMenuEntity menu) { + // 判断是否为http(s)://开头 + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.startsWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS); + } + + /** + * 是否为parent_view组件 (有多级的菜单) + */ + private boolean isParentView(SysMenuEntity menu) { + return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + + /** + * 内链域名特殊字符替换 + */ + private String innerLinkReplaceEach(String path) { + return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS}, + new String[]{"", ""}); + } + + private List getChildPerms(List list, int parentId) { + List returnList = new ArrayList<>(); + for (SysMenuEntity menu : list) { + if (menu.getParentId() == parentId) { + recursionList(list, menu); + returnList.add(menu); + } + } + return returnList; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenuEntity menu) { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) { + component = menu.getComponent(); + } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) { + component = UserConstants.INNER_LINK; + } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) { + component = UserConstants.PARENT_VIEW; + } + return component; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysOperationLogServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysOperationLogServiceImpl.java new file mode 100644 index 0000000..5e86759 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysOperationLogServiceImpl.java @@ -0,0 +1,91 @@ +package com.starry.admin.modules.system.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysOperationLogEntity; +import com.starry.admin.modules.system.mapper.SysOperationLogMapper; +import com.starry.admin.modules.system.service.ISysOperationLogService; +import com.starry.admin.modules.system.vo.SysOperationLogVo; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Arrays; + +/** + * 操作日志表Service业务层处理 + * + * @author admin + */ +@Service +public class SysOperationLogServiceImpl extends ServiceImpl implements ISysOperationLogService { + + @Resource + private SysOperationLogMapper sysOperationLogMapper; + + /** + * 查询操作日志表 + * + * @param operId 操作日志表主键 + * @return 操作日志表 + */ + @Override + public SysOperationLogEntity selectXlOperLogByOperId(Long operId) { + return sysOperationLogMapper.selectXlOperLogByOperId(operId); + } + + /** + * 查询操作日志表列表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 操作日志表 + */ + @Override + public IPage selectXlOperLogList(SysOperationLogVo sysOperationLogEntity) { + return sysOperationLogMapper.selectXlOperLogList(new Page<>(sysOperationLogEntity.getPageNum(), sysOperationLogEntity.getPageSize()), sysOperationLogEntity); + } + + /** + * 新增操作日志表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 结果 + */ + @Override + public boolean create(SysOperationLogEntity sysOperationLogEntity) { + return save(sysOperationLogEntity); + } + + /** + * 修改操作日志表 + * + * @param sysOperationLogEntity 操作日志表 + * @return 结果 + */ + @Override + public boolean updateXlOperLog(SysOperationLogEntity sysOperationLogEntity) { + return updateById(sysOperationLogEntity); + } + + /** + * 批量删除操作日志表 + * + * @param operIds 需要删除的操作日志表主键 + * @return 结果 + */ + @Override + public int deleteXlOperLogByOperIds(Long[] operIds) { + return sysOperationLogMapper.deleteBatchIds(Arrays.asList(operIds)); + } + + /** + * 删除操作日志表信息 + * + * @param operId 操作日志表主键 + * @return 结果 + */ + @Override + public int deleteXlOperLogByOperId(Long operId) { + return sysOperationLogMapper.deleteById(operId); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleMenuServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..1a0937f --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleMenuServiceImpl.java @@ -0,0 +1,20 @@ +package com.starry.admin.modules.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysRoleMenuEntity; +import com.starry.admin.modules.system.mapper.SysRoleMenuMapper; +import com.starry.admin.modules.system.service.SysRoleMenuService; +import org.springframework.stereotype.Service; + +/** + *

+ * 角色和菜单关联表 服务实现类 + *

+ * + * @author admin + * @since 2022-07-06 + */ +@Service +public class SysRoleMenuServiceImpl extends ServiceImpl implements SysRoleMenuService { + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..d919195 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,286 @@ +package com.starry.admin.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.common.exception.ServiceException; +import com.starry.admin.modules.system.entity.*; +import com.starry.admin.modules.system.mapper.SysRoleDeptMapper; +import com.starry.admin.modules.system.mapper.SysRoleMapper; +import com.starry.admin.modules.system.mapper.SysRoleMenuMapper; +import com.starry.admin.modules.system.mapper.SysUserRoleMapper; +import com.starry.admin.modules.system.service.SysRoleMenuService; +import com.starry.admin.modules.system.service.SysRoleService; +import com.starry.admin.modules.system.service.SysUserRoleService; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.constant.UserConstants; +import com.starry.common.utils.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; + +/** + *

+ * 角色表 服务实现类 + *

+ * + * @author admin + * @since 2022-07-01 + */ +@Service +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { + + @Resource + private SysRoleMenuService roleMenuService; + @Resource + private SysRoleDeptMapper roleDeptMapper; + @Resource + private SysRoleMenuMapper roleMenuMapper; + @Resource + private SysUserRoleService userRoleService; + @Resource + private SysUserRoleMapper userRoleMapper; + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public String checkRoleNameUnique(SysRoleEntity role) { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRoleEntity info = baseMapper.checkRoleNameUnique(role.getRoleName()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public String checkRoleKeyUnique(SysRoleEntity role) { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRoleEntity info = baseMapper.checkRoleKeyUnique(role.getRoleKey()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean create(SysRoleEntity role) { + // 先新增角色 + save(role); + return insertRoleMenu(role); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateRole(SysRoleEntity role) { + // 先修改角色 + updateById(role); + // 再删除角色菜单信息 + roleMenuService.remove(new LambdaQueryWrapper().eq(SysRoleMenuEntity::getRoleId, role.getRoleId())); + // 再新增 + return insertRoleMenu(role); + } + + @Override + public int countUserRoleByRoleId(Long roleId) { + return baseMapper.countUserRoleByRoleId(roleId); + } + + @Override + public boolean delete(Long[] roleIds) { + for (Long roleId : roleIds) { + checkRoleAllowed(new SysRoleEntity(roleId)); + checkRoleDataScope(roleId); + SysRoleEntity role = getById(roleId); + if (countUserRoleByRoleId(roleId) > 0) { + throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDept(roleIds); + return removeByIds(Arrays.asList(roleIds)); + } + + @Override + public Page list(String keyword, Integer pageSize, Integer pageNum) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StrUtil.isNotBlank(keyword)) { + wrapper.like(SysRoleEntity::getRoleName, keyword); + } + return page(page, wrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean authRoleMenu(SysRoleEntity role) { + // 先删除 + roleMenuService.remove(new LambdaQueryWrapper().eq(SysRoleMenuEntity::getRoleId, role.getRoleId())); + // 在新增 + return insertRoleMenu(role); + } + + @Override + public Set selectRolePermissionByUserId(String userId) { + List list = baseMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + if (CollectionUtil.isNotEmpty(list)) { + for (SysRoleEntity role : list) { + permsSet.addAll(Arrays.asList(role.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + @Override + public List selectRoleList(SysRoleEntity role) { + return baseMapper.selectRoleList(role); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + public boolean insertRoleMenu(SysRoleEntity role) { + // 新增角色与菜单管理 + List list = new ArrayList<>(); + for (Long menuId : role.getMenuIds()) { + SysRoleMenuEntity rm = new SysRoleMenuEntity(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (!list.isEmpty()) { + return roleMenuService.saveBatch(list); + } + return true; + } + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + @Override + public void checkRoleAllowed(SysRoleEntity role) { + if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) { + if (!SysUserEntity.isAdmin(SecurityUtils.getUserId())) { + SysRoleEntity role = new SysRoleEntity(); + role.setRoleId(roleId); + List roles = this.selectRoleList(role); + if (CollectionUtil.isEmpty(roles)) { + throw new ServiceException("没有权限访问角色数据!"); + } + } + } + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int authDataScope(SysRoleEntity role) { + // 修改角色信息 + this.updateById(role); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); + // 新增角色和部门信息(数据权限) + return insertRoleDept(role); + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + public int insertRoleDept(SysRoleEntity role) { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList<>(); + for (Long deptId : role.getDeptIds()) { + SysRoleDeptEntity rd = new SysRoleDeptEntity(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (!list.isEmpty()) { + rows = roleDeptMapper.batchRoleDept(list); + } + return rows; + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRoleEntity userRole) { + return userRoleMapper.deleteUserRoleInfo(userRole); + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, String[] userIds) { + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, String[] userIds) { + // 新增用户与角色管理 + List list = new ArrayList<>(); + for (String userId : userIds) { + SysUserRoleEntity ur = new SysUserRoleEntity(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleService.saveBatch(list) ? 1 : 0; + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserRoleServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..cb735cc --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,20 @@ +package com.starry.admin.modules.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysUserRoleEntity; +import com.starry.admin.modules.system.mapper.SysUserRoleMapper; +import com.starry.admin.modules.system.service.SysUserRoleService; +import org.springframework.stereotype.Service; + +/** + *

+ * 用户角色关联表 服务实现类 + *

+ * + * @author admin + * @since 2022-07-08 + */ +@Service +public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..910c28a --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,211 @@ +package com.starry.admin.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.admin.modules.system.entity.SysUserEntity; +import com.starry.admin.modules.system.entity.SysUserRoleEntity; +import com.starry.admin.modules.system.mapper.SysRoleMapper; +import com.starry.admin.modules.system.mapper.SysUserMapper; +import com.starry.admin.modules.system.service.SysUserRoleService; +import com.starry.admin.modules.system.service.SysUserService; +import com.starry.admin.modules.system.vo.RoleUserResultVo; +import com.starry.admin.modules.system.vo.SysUserQueryVo; +import com.starry.admin.modules.system.vo.UserQueryVo; +import com.starry.common.annotation.DataScope; +import com.starry.common.constant.UserConstants; +import com.starry.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 后台用户表 服务实现类 + *

+ * + * @author admin + * @since 2021-09-03 + */ +@Slf4j +@Service +public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + + @Resource + private PasswordEncoder passwordEncoder; + @Resource + private SysUserRoleService userRoleService; + @Resource + private SysRoleMapper sysRoleMapper; + + + @Override + public SysUserEntity register(SysUserEntity user) { + SysUserEntity newSysUserEntity = new SysUserEntity(); + BeanUtils.copyProperties(user, newSysUserEntity); + // 查询是否有相同用户名的用户 + List sysUserEntities = this.baseMapper.selectList(new LambdaQueryWrapper().eq(SysUserEntity::getUserCode, newSysUserEntity.getUserCode())); + if (CollectionUtil.isNotEmpty(sysUserEntities)) { + return null; + } + // 将密码进行加密操作 + String encodePassword = passwordEncoder.encode(user.getPassWord()); + newSysUserEntity.setPassWord(encodePassword); + this.baseMapper.insert(newSysUserEntity); + return newSysUserEntity; + } + + @Override + public SysUserEntity getUserByCode(String userCode) { + List users = baseMapper.selectList(new LambdaQueryWrapper().eq(SysUserEntity::getUserCode, userCode)); + if (CollectionUtil.isNotEmpty(users)) { + return users.get(0); + } + return null; + } + + @Override + public SysUserEntity selectUserByUserName(String userName) { + return baseMapper.selectUserByUserName(userName); + } + + @Override + public IPage listMemberPage(UserQueryVo queryVo) { + Page page = new Page<>(queryVo.getPageNum(), queryVo.getPageSize()); + return baseMapper.selectUserList(page, queryVo); + } + + @Override + public String checkUserNameUnique(String userCode) { + // 查询是否有相同用户名的用户 + List userList = this.baseMapper.selectList(new LambdaQueryWrapper().eq(SysUserEntity::getUserCode, userCode)); + if (CollectionUtil.isNotEmpty(userList)) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean create(SysUserEntity user) { + // 将密码进行加密操作 + String encodePassword = passwordEncoder.encode(user.getPassWord()); + user.setPassWord(encodePassword); + if (StrUtil.isBlankIfStr(user.getUserId())) { + user.setUserId(IdUtil.fastSimpleUUID()); + } + boolean flag = save(user); + if (flag) { + // 新增用户角色信息 + flag = insertUserRole(user); + } + return flag; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean update(SysUserEntity user) { + boolean flag = updateById(user); + if (flag) { + // 删除用户角色信息 + userRoleService.remove(new LambdaQueryWrapper().eq(SysUserRoleEntity::getUserId, user.getUserId())); + // 新增用户角色信息 + flag = insertUserRole(user); + } + return flag; + + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean delete(List ids) { + if (CollectionUtil.isNotEmpty(ids)) { + boolean flag = this.removeBatchByIds(ids); + if (flag) { + // 删除用户角色表 + userRoleService.remove(new LambdaQueryWrapper().in(SysUserRoleEntity::getUserId, ids)); + } + } + return true; + } + + @Override + public SysUserEntity selectUserById(String userId) { + return baseMapper.selectUserById(userId); + } + + @Override + public String selectUserRoleGroup(String userId) { + List list = sysRoleMapper.selectRolePermissionByUserId(userId); + if (CollectionUtil.isEmpty(list)) { + return StringUtils.EMPTY; + } + return list.stream().map(SysRoleEntity::getRoleName).collect(Collectors.joining(",")); + } + + @Override + public boolean updateUserAvatar(String userId, String avatar) { + SysUserEntity user = getById(userId); + if (user != null) { + user.setAvatar(avatar); + return updateById(user); + } + return false; + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public boolean insertUserRole(SysUserEntity user) { + if (user.getRoleIds() != null && user.getRoleIds().length > 0) { + // 新增用户与角色管理 + List list = new ArrayList<>(user.getRoleIds().length); + for (Long roleId : user.getRoleIds()) { + SysUserRoleEntity ur = new SysUserRoleEntity(); + ur.setUserId(user.getUserId()); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleService.saveBatch(list); + } + return true; + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param userQueryVo 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public IPage selectAllocatedList(SysUserQueryVo userQueryVo) { + return baseMapper.selectAllocatedList(new Page<>(userQueryVo.getPageNum(), userQueryVo.getPageSize()), userQueryVo); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public IPage selectUnallocatedList(SysUserQueryVo user) { + return baseMapper.selectUnallocatedList(new Page<>(user.getPageNum(), user.getPageSize()), user); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/LoginVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/LoginVo.java new file mode 100644 index 0000000..a541afe --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/LoginVo.java @@ -0,0 +1,26 @@ +package com.starry.admin.modules.system.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author huoqiang + * @since 2021/9/6 + */ +@Data +public class LoginVo implements Serializable { + + @ApiModelProperty(value = "用户名") + private String userName; + + @ApiModelProperty(value = "密码") + private String passWord; + + @ApiModelProperty(value = "验证码随机字符串") + private String nonceStr; + + @ApiModelProperty(value = "验证值") + private String value; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/MemberQueryVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/MemberQueryVo.java new file mode 100644 index 0000000..d7e3ba4 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/MemberQueryVo.java @@ -0,0 +1,19 @@ +package com.starry.admin.modules.system.vo; + + +import com.starry.common.domain.BasePageEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author admin + * @since 2021/9/2 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MemberQueryVo extends BasePageEntity { + + @ApiModelProperty(value = "用户名") + private String userName; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/MetaVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/MetaVo.java new file mode 100644 index 0000000..489fdfc --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/MetaVo.java @@ -0,0 +1,61 @@ +package com.starry.admin.modules.system.vo; + + +import com.starry.common.constant.Constants; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +/** + * @author admin + * 路由显示信息 + * @since 2022/7/6 + */ +@Data +public class MetaVo { + + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + @ApiModelProperty(value = "设置该路由在侧边栏和面包屑中展示的名字") + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + @ApiModelProperty(value = "设置该路由的图标,对应路径src/assets/icons/svg") + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + @ApiModelProperty(value = "设置为true,则不会被 缓存") + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + @ApiModelProperty(value = "内链地址(http(s)://开头)") + private String link; + + public MetaVo(String title, String icon) { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, String link) { + this.title = title; + this.icon = icon; + this.link = link; + } + + + public MetaVo(String title, String icon, boolean noCache, String link) { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS)) { + this.link = link; + } + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/RoleUserResultVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/RoleUserResultVo.java new file mode 100644 index 0000000..77622d6 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/RoleUserResultVo.java @@ -0,0 +1,49 @@ +package com.starry.admin.modules.system.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @author admin + * 角色分配用户精简信息Vo + * @since 2023/3/10 + */ +@ApiModel("管理后台 - 角色分配用户返回信息") +@Data +public class RoleUserResultVo { + + @ApiModelProperty(value = "用户ID") + private Long userId; + + @ApiModelProperty(value = "用户类型;1:admin;2:会员") + private Integer userType; + + @ApiModelProperty(value = "用户名") + private String userCode; + + @ApiModelProperty(value = "姓名") + private String realName; + + @ApiModelProperty(value = "用户昵称") + private String userNickname; + + @ApiModelProperty(value = "邮箱") + private String userEmail; + + @ApiModelProperty(value = "中国手机不带国家代码,国际手机号格式为:国家代码-手机号") + private String mobile; + + @ApiModelProperty(value = "用户状态;0正常 1停用") + private Integer status; + + @ApiModelProperty(value = "所属部门id") + private Long deptId; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建时间") + private Date createdTime; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/RouterVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/RouterVo.java new file mode 100644 index 0000000..7261cd4 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/RouterVo.java @@ -0,0 +1,45 @@ +package com.starry.admin.modules.system.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * @author admin + * 路由配置信息 + * @since 2022/7/6 + */ +@Data +public class RouterVo { + + @ApiModelProperty(value = "路由名字") + private String name; + + @ApiModelProperty(value = "路由地址") + private String path; + + @ApiModelProperty(value = "是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现") + private boolean hidden; + + @ApiModelProperty(value = "重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击") + private String redirect; + + @ApiModelProperty(value = "组件地址") + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String routerQuery; + + @ApiModelProperty(value = "当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面") + private Boolean alwaysShow; + + @ApiModelProperty(value = "其他元素") + private MetaVo meta; + + @ApiModelProperty(value = "子路由") + private List children; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/SimpleMenu.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SimpleMenu.java new file mode 100644 index 0000000..e495ff5 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SimpleMenu.java @@ -0,0 +1,33 @@ +package com.starry.admin.modules.system.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; + +/** + * @author admin + */ +@ApiModel("管理后台 - 菜单精简信息") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SimpleMenu { + + @ApiModelProperty(value = "菜单编号", required = true, example = "1024") + private Long id; + + @ApiModelProperty(value = "菜单名称", required = true, example = "菜单管理") + private String name; + + @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024") + private Long parentId; + + @ApiModelProperty(value = "类型", required = true, example = "1", notes = "参见 MenuTypeEnum 枚举类") + @NotNull(message = "菜单类型不能为空") + private Integer type; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/StatusConverter.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/StatusConverter.java new file mode 100644 index 0000000..4c06bb1 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/StatusConverter.java @@ -0,0 +1,35 @@ +package com.starry.admin.modules.system.vo; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +/** + * @author huoqiang + * 状态字符串处理 + * @since 2022/10/25 + */ +public class StatusConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Integer.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception { + return "失败".equals(cellData.getStringValue()) ? 1 : 0; + } + + @Override + public CellData convertToExcelData(Integer value, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception { + return new CellData(0 == value ? "成功" : "失败"); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictDataVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictDataVo.java new file mode 100644 index 0000000..a560ee5 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictDataVo.java @@ -0,0 +1,69 @@ +package com.starry.admin.modules.system.vo; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.starry.common.domain.BasePageEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典数据表对象 sys_dict_data + * + * @author admin + */ +@Data +@EqualsAndHashCode(callSuper = true) + +public class SysDictDataVo extends BasePageEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "dict_data_id", type = IdType.AUTO) + private Long dictDataId; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 字典名称 + */ + private String dictLabel; + + /** + * 字典值 + */ + private String dictValue; + + /** + * 显示顺序 + */ + private Long sort; + + /** + * 是否默认(Y是 N否) + */ + private String isDefault; + + /** + * 表格回显样式 + */ + private String listClass; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictVo.java new file mode 100644 index 0000000..89caeaf --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictVo.java @@ -0,0 +1,40 @@ +package com.starry.admin.modules.system.vo; + +import com.starry.common.domain.BasePageEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author admin + * @since 2024/3/15 13:58 + **/ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysDictVo extends BasePageEntity { + + /** + * 主键ID + */ + private Long dictId; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 字典名称 + */ + private String dictName; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysOperationLogVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysOperationLogVo.java new file mode 100644 index 0000000..60e2f7b --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysOperationLogVo.java @@ -0,0 +1,125 @@ +package com.starry.admin.modules.system.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.starry.common.domain.BasePageEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + + +/** + * 操作日志表对象 sys_operation_log + * + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysOperationLogVo extends BasePageEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ApiModelProperty("主键ID") + @TableId(value = "oper_id", type = IdType.AUTO) + private Long operId; + + /** + * 模块标题 + */ + @ExcelProperty(value = "系统模块") + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + @TableField(exist = false) + private Integer[] businessTypes; + + /** + * 方法名称 + */ + @ExcelProperty(value = "方法名称") + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 请求URL + */ + private String operUrl; + + /** + * 主机地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 部门id + */ + private Long deptId; + + /** + * 租户ID + */ + private Long tenantId; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysRoleAddVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysRoleAddVo.java new file mode 100644 index 0000000..495806d --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysRoleAddVo.java @@ -0,0 +1,55 @@ +package com.starry.admin.modules.system.vo; + +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.common.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.util.Set; + +/** + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysRoleAddVo extends BaseEntity { + + private static final long serialVersionUID = 1L; + + + private Long roleId; + + @NotBlank(message = "角色名称不能为空") + private String roleName; + + private String description; + + private Integer status; + + private String dataScope; + + private boolean menuCheckStrictly; + + private boolean deptCheckStrictly; + + private String roleKey; + + private Long tenantId; + + /** + * 菜单组 + */ + private Long[] menuIds; + + /** + * 部门组(数据权限) + */ + private Long[] deptIds; + + /** + * 角色菜单权限 + */ + private Set permissions; + +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysUserQueryVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysUserQueryVo.java new file mode 100644 index 0000000..2cdcc40 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/SysUserQueryVo.java @@ -0,0 +1,64 @@ +package com.starry.admin.modules.system.vo; + +import com.starry.admin.modules.system.entity.SysDeptEntity; +import com.starry.admin.modules.system.entity.SysRoleEntity; +import com.starry.common.domain.BasePageEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + +/** + * 用户查询对象 + * + * @author admin + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserQueryVo extends BasePageEntity { + + private static final long serialVersionUID = 1L; + + private Long userId; + + private Integer userType; + + private String userCode; + + private String passWord; + + private String realName; + + private String userNickname; + + private String userEmail; + + + private String avatar; + + private String mobile; + + private Integer sex; + + private Date birthday; + + private Integer status; + + private Date lastLoginTime; + + private String lastLoginIp; + + private Long deptId; + + private Long tenantId; + + private List roles; + + private Long[] roleIds; + + + private SysDeptEntity dept; + + private Long roleId; +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/system/vo/UserQueryVo.java b/play-admin/src/main/java/com/starry/admin/modules/system/vo/UserQueryVo.java new file mode 100644 index 0000000..6496119 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/system/vo/UserQueryVo.java @@ -0,0 +1,25 @@ +package com.starry.admin.modules.system.vo; + + +import com.starry.common.domain.BasePageEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author admin + * @since 2021/9/26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class UserQueryVo extends BasePageEntity { + + @ApiModelProperty(value = "用户id") + private String userId; + + @ApiModelProperty(value = "用户名") + private String userCode; + + @ApiModelProperty(value = "姓名") + private String realName; +} diff --git a/play-admin/src/main/java/com/starry/admin/utils/DictUtils.java b/play-admin/src/main/java/com/starry/admin/utils/DictUtils.java new file mode 100644 index 0000000..2879d37 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/utils/DictUtils.java @@ -0,0 +1,59 @@ +package com.starry.admin.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.starry.admin.modules.system.entity.SysDictDataEntity; +import com.starry.common.constant.CacheConstants; +import com.starry.common.redis.RedisCache; +import com.starry.common.utils.SpringUtils; +import com.starry.common.utils.StringUtils; + +import java.util.Collection; +import java.util.List; + +/** + * @author admin + */ +public class DictUtils { + + /** + * 获取 cache key + * + * @param key 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String key) { + return CacheConstants.SYS_DICT_KEY + key; + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List getDictCache(String key) { + JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) { + return arrayCache.toList(SysDictDataEntity.class); + } + return null; + } + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dataList 字典数据列表 + */ + public static void setDictCache(String key, List dataList) { + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dataList); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() { + Collection keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/utils/ExcelUtils.java b/play-admin/src/main/java/com/starry/admin/utils/ExcelUtils.java new file mode 100644 index 0000000..9b62c05 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/utils/ExcelUtils.java @@ -0,0 +1,26 @@ +package com.starry.admin.utils; + +import com.alibaba.excel.EasyExcel; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * @author admin + */ +public class ExcelUtils { + + public static List importEasyExcel(InputStream is, Class pojoClass) { + return EasyExcel.read(is).head(pojoClass).sheet().doReadSync(); + } + + public static void exportEasyExcel(HttpServletResponse response, Class pojoClass, List list, String sheetName) { + try { + EasyExcel.write(response.getOutputStream(), pojoClass).sheet(sheetName).doWrite(list); + } catch (IOException e) { + throw new RuntimeException(e.getMessage()); + } + } +} diff --git a/play-admin/src/main/java/com/starry/admin/utils/SecurityUtils.java b/play-admin/src/main/java/com/starry/admin/utils/SecurityUtils.java new file mode 100644 index 0000000..37f4ddc --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/utils/SecurityUtils.java @@ -0,0 +1,120 @@ +package com.starry.admin.utils; + + +import com.starry.admin.common.domain.LoginUser; +import com.starry.admin.common.security.entity.JwtUser; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * 安全服务工具类 + * + * @author admin + */ +public class SecurityUtils { + /** + * 用户ID + **/ + public static String getUserId() { + try { + return getLoginUser().getUser().getUserId(); + } catch (Exception e) { + throw new RuntimeException("获取用户ID异常"); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() { + try { + return getLoginUser().getUsername(); + } catch (Exception e) { + throw new RuntimeException("获取用户账户异常"); + } + } + + /** + * 获取用户 + **/ + public static JwtUser getOldLoginUser() { + try { + return (JwtUser) getAuthentication().getPrincipal(); + } catch (Exception e) { + throw new RuntimeException("获取用户信息异常"); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() { + try { + return (LoginUser) getAuthentication().getPrincipal(); + } catch (Exception e) { + throw new RuntimeException("获取用户信息异常"); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 是否登录 + **/ + public static boolean isLogin() { + return getAuthentication() != null && getAuthentication().getPrincipal() != null; + } + + /** + * 是否为超级管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(String userId) { + return "6dcb2da45fef4768a6511f9c14e18072".equals(userId); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 获取租户ID + */ + public static Long getTenantId() { + return com.starry.common.context.SecurityContextHolder.getTenantId(); + } + + /** + * 设置租户ID + */ + public static void setTenantId(String tenantId) { + com.starry.common.context.SecurityContextHolder.setTenantId(tenantId); + } +} diff --git a/play-admin/src/main/resources/application-dev.yml b/play-admin/src/main/resources/application-dev.yml new file mode 100644 index 0000000..79e2477 --- /dev/null +++ b/play-admin/src/main/resources/application-dev.yml @@ -0,0 +1,102 @@ +# 服务名 +spring: + application: + name: admin-tenant + + # druid数据源配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + # 配置MySQL的驱动程序类 + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + # 数据库连接地址(以MySql为例) + url: jdbc:p6spy:mysql://127.0.0.1:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 + # 数据库对应的用户名 + username: root + # 数据库对应的密码 + password: root + druid: + enable: true + db-type: mysql + # 配置监控统计拦截的filters,stat是sql监控,wall是防火墙(如果不添加则监控无效),添加log4j需要引入jar包 + filters: stat,wall,config + # 连接池最大活跃连接数 + max-active: 100 + # 连接池初始化连接数量 + initial-size: 1 + # 配置获取连接等待超时的时间 + max-wait: 60000 + # 连接池最小空闲数 + min-idle: 1 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔 + time-between-eviction-runs-millis: 60000 + # 指定一个空闲连接最少空闲多久后可被清除 + min-evictable-idle-time-millis: 300000 + # 连接是否有效的查询语句 + validation-query: select 'x' + test-while-idle: true + test-on-borrow: false + test-on-return: false + # 打开 PSCache,并且指定每个连接上 PSCache 的大小 + pool-prepared-statements: true + max-open-prepared-statements: 50 + max-pool-prepared-statement-per-connection-size: 20 + # 配置 DruidStatFilter + web-stat-filter: + enabled: true #\u662F\u5426\u542F\u7528StatFilter\u9ED8\u8BA4\u503Ctrue + # 排除一些不必要的url,比如.js,/jslib/等 + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" + # 过滤规则 + url-pattern: /* + # 配置 DruidStatViewServlet + stat-view-servlet: + # 手动重置监控数据 + enabled: true + # IP白名单,没有配置或者为空,则允许所有访问 + allow: 127.0.0.1 + # IP黑名单,若白名单也存在,则优先使用 + deny: + # 配置druid登录用户名、密码 + login-username: admin + login-password: admin + # HTML 中 Reset All 按钮 + reset-enable: true + redis: + host: 127.0.0.1 # Redis服务器地址 + database: 10 # Redis数据库索引(默认为0) + port: 6379 # Redis服务器连接端口 + password: Spinfo@0123 + timeout: 3000ms # 连接超时时间(毫秒) + + + +# 全局日志级别 +logging: + level: + root: info + +jwt: + tokenHeader: X-Token #JWT存储的请求头 + tokenHead: Bearer #JWT负载中拿到开头 + secret: yz-admin-secret #JWT加解密使用的密钥 + expiration: 3600 #JWT的超期限时间秒(60*60*24) + +# xl自定义配置 +xl: + login: + authCode: + # 登录验证码是否开启,开发环境配置false方便测试 + enable: false +# 腾讯云cos配置 +cos: + baseUrl: https://admin-125966.cos.ap-guangzhou.myqcloud.com + secretId: AKIDdHsLgtxoSs3sWw73lz + secretKey: zZxBD0b4QcZGmdFcotm + regionName: ap-guangzhou + bucketName: admin-125966 + folderPrefix: /upload + + diff --git a/play-admin/src/main/resources/application-prod.yml b/play-admin/src/main/resources/application-prod.yml new file mode 100644 index 0000000..f559b6d --- /dev/null +++ b/play-admin/src/main/resources/application-prod.yml @@ -0,0 +1,96 @@ +# 服务名 +spring: + application: + name: admin-ucenter + + # druid数据源配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + # 配置MySQL的驱动程序类 + driver-class-name: com.mysql.cj.jdbc.Driver + # 数据库连接地址(以MySql为例) + url: jdbc:mysql://localhost:3306/admin?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 + # 数据库对应的用户名 + username: root + # 数据库对应的密码 + password: root + druid: + # 配置监控统计拦截的filters,stat是sql监控,wall是防火墙(如果不添加则监控无效),添加log4j需要引入jar包 + filters: stat,wall,config + # 连接池最大活跃连接数 + max-active: 100 + # 连接池初始化连接数量 + initial-size: 1 + # 配置获取连接等待超时的时间 + max-wait: 60000 + # 连接池最小空闲数 + min-idle: 1 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔 + time-between-eviction-runs-millis: 60000 + # 指定一个空闲连接最少空闲多久后可被清除 + min-evictable-idle-time-millis: 300000 + # 连接是否有效的查询语句 + validation-query: select 'x' + test-while-idle: true + test-on-borrow: false + test-on-return: false + # 打开 PSCache,并且指定每个连接上 PSCache 的大小 + pool-prepared-statements: true + max-open-prepared-statements: 50 + max-pool-prepared-statement-per-connection-size: 20 + # 配置 DruidStatFilter + web-stat-filter: + enabled: true #\u662F\u5426\u542F\u7528StatFilter\u9ED8\u8BA4\u503Ctrue + # 排除一些不必要的url,比如.js,/jslib/等 + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" + # 过滤规则 + url-pattern: /* + # 配置 DruidStatViewServlet + stat-view-servlet: + # 手动重置监控数据 + enabled: true + # IP白名单,没有配置或者为空,则允许所有访问 + allow: 127.0.0.1 + # IP黑名单,若白名单也存在,则优先使用 + deny: + # 配置druid登录用户名、密码 + login-username: admin + login-password: admin + # HTML 中 Reset All 按钮 + reset-enable: true + redis: + host: localhost # Redis服务器地址 + database: 0 # Redis数据库索引(默认为0) + port: 6379 # Redis服务器连接端口 + password: yz_admin # Redis服务器连接密码(默认为空) + timeout: 3000ms # 连接超时时间(毫秒) + +# 全局日志级别 +logging: + level: + root: info + +jwt: + tokenHeader: X-Token #JWT存储的请求头 + tokenHead: Bearer #JWT负载中拿到开头 + secret: yz-admin-secret #JWT加解密使用的密钥 + expiration: 3600 #JWT的超期限时间秒(60*60*24) + +# xl自定义配置 +xl: + login: + authCode: + # 登录验证码是否开启,开发环境配置false方便测试 + enable: false +# 腾讯云cos配置 +cos: + baseUrl: https://admin-125966.cos.ap-guangzhou.myqcloud.com + secretId: AKIDdHsLgtxoSs3sWw73lz + secretKey: zZxBD0b4QcZGmdFcotm + regionName: ap-guangzhou + bucketName: admin-125966 + folderPrefix: /upload \ No newline at end of file diff --git a/play-admin/src/main/resources/application.yml b/play-admin/src/main/resources/application.yml new file mode 100644 index 0000000..d44ccc4 --- /dev/null +++ b/play-admin/src/main/resources/application.yml @@ -0,0 +1,36 @@ +server: + port: 7002 +spring: + profiles: + active: dev + +# mybatis日志 +mybatis-plus: + global-config: + # 逻辑删除 + db-config: + logic-delete-field: deleted # 全局逻辑删除的实体字段名 + logic-delete-value: 1 + logic-not-delete-value: 0 + # xml文件路径,classpath* 代表所有模块的resources目录 classpath 不加星号代表当前模块下的resources目录 + mapper-locations: classpath*:mapper/**/*.xml + +wx: + # 公众号配置 + mp: + configs: + - appId: xxxxxxxx + secret: xxxxxxxxxxxxxxxxxxxxxxxxxx + token: xxxxxxxxxx + aesKey: xxxxxxxxxxxxxxxxxxxx + # 小程序配置 + ma: + configs: + - appId: xxxxxxxxxxxxxxxx + secret: xxxxxxxxxxxxx + # 微信支付商户号,请去微信支付平台申请 + mchId: 1588227511 + # 微信支付商户APIv2密钥,请去微信支付平台申请 + mchKey: xxxxxxxxxxxxxxxxxxxxx + # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头) + keyPath: classpath:apiclient_cert.p12 \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/platform/SysTenantMapper.xml b/play-admin/src/main/resources/mapper/platform/SysTenantMapper.xml new file mode 100644 index 0000000..a9c5ce8 --- /dev/null +++ b/play-admin/src/main/resources/mapper/platform/SysTenantMapper.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select tenant_id, + tenant_type, + tenant_status, + tenant_code, + tenant_name, + tenant_logo, + sort_order, + home_url, + phone, + fax, + email, + address, + user_name, + user_pwd, + package_id, + tenant_time, + remarks, + updated_time, + updated_by, + created_time, + created_by, + deleted, + version + from sys_tenant + + + + + + + + delete from sys_tenant where tenant_id in + + #{id} + + + \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/platform/SysTenantPackageMapper.xml b/play-admin/src/main/resources/mapper/platform/SysTenantPackageMapper.xml new file mode 100644 index 0000000..937d5ec --- /dev/null +++ b/play-admin/src/main/resources/mapper/platform/SysTenantPackageMapper.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + select package_id, + package_name, + menu_ids, + status, + remarks, + updated_time, + updated_by, + created_time, + created_by, + deleted, + version + from sys_tenant_package + + + + + + + + \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/system/SysDeptMapper.xml b/play-admin/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..880dd54 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, + d.parent_id, + d.ancestors, + d.dept_name, + d.sort, + d.leader, + d.phone, + d.email, + d.status, + d.dept_level, + d.created_by, + d.created_time + from sys_dept d + + + + + + + + delete from sys_dept where tenant_id in + + #{id} + + + + + diff --git a/play-admin/src/main/resources/mapper/system/SysDictDataMapper.xml b/play-admin/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..87f84bd --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + select dict_data_id, + dict_type, + dict_label, + dict_value, + sort, + is_default, + list_class, + status, + remark, + updated_time, + updated_by, + created_time, + created_by, + deleted, + version + from sys_dict_data + + + + + + + + \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/system/SysDictMapper.xml b/play-admin/src/main/resources/mapper/system/SysDictMapper.xml new file mode 100644 index 0000000..c7e7fc3 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysDictMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + select dict_id, + dict_type, + dict_name, + status, + remark, + updated_time, + updated_by, + created_time, + created_by, + deleted, + version + from sys_dict + + + + + + \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/system/SysLoginLogMapper.xml b/play-admin/src/main/resources/mapper/system/SysLoginLogMapper.xml new file mode 100644 index 0000000..5e3a7dc --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysLoginLogMapper.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + select login_id, + user_code, + ipaddr, + login_location, + browser, + os, + msg, + status, + login_time, + updated_time, + updated_by, + created_time, + created_by, + deleted, + version + from sys_login_log + + + + + + + + truncate table sys_login_log + + \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/system/SysMenuMapper.xml b/play-admin/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..88f7d33 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-admin/src/main/resources/mapper/system/SysOperationLogMapper.xml b/play-admin/src/main/resources/mapper/system/SysOperationLogMapper.xml new file mode 100644 index 0000000..5342870 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysOperationLogMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, + title, + business_type, + method, + request_method, + operator_type, + oper_name, + oper_url, + oper_ip, + oper_location, + oper_param, + json_result, + status, + error_msg, + oper_time, + dept_name, + dept_id, + updated_time, + updated_by, + created_time, + created_by, + deleted, + version + from sys_operation_log + + + + + + \ No newline at end of file diff --git a/play-admin/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/play-admin/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..220e045 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + delete from sys_role_dept where tenant_id in + + #{id} + + + + + delete from sys_role_dept where role_id = #{roleId} + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + diff --git a/play-admin/src/main/resources/mapper/system/SysRoleMapper.xml b/play-admin/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..72b4bcd --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, + r.role_name, + r.role_key, + r.data_scope, + r.menu_check_strictly, + r.dept_check_strictly, + r.status, + r.created_time, + r.description + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id and ur.deleted = 0 + left join sys_user u on u.user_id = ur.user_id and u.deleted = 0 + + + + + + + + + + + + + + + + delete from sys_role where tenant_id in + + #{id} + + + + + diff --git a/play-admin/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/play-admin/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..643e4c1 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + delete from sys_role_menu where tenant_id = #{tenantId} + + + + delete from sys_role_menu where tenant_id in + + #{id} + + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + diff --git a/play-admin/src/main/resources/mapper/system/SysUserMapper.xml b/play-admin/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..ba64e45 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, + u.real_name, + u.user_nickname, + u.user_email, + u.avatar, + u.user_code, + u.pass_word, + u.sex, + u.mobile, + u.status, + u.created_time, + u.dept_id, + u.tenant_id, + d.dept_name, + r.role_id, + r.role_name, + r.role_key, + r.data_scope, + r.status as role_status + from sys_user u + left join sys_user_role ur on u.user_id = ur.user_id and ur.deleted = 0 + left join sys_role r on r.role_id = ur.role_id and r.deleted = 0 + left join sys_dept d on u.dept_id = d.dept_id and d.deleted = 0 + + + + + + + + + + + + delete from sys_user where tenant_id in + + #{id} + + + + + + + diff --git a/play-admin/src/main/resources/mapper/system/SysUserRoleMapper.xml b/play-admin/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..457aa68 --- /dev/null +++ b/play-admin/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + delete from sys_user_role where tenant_id in + + #{id} + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + diff --git a/play-admin/src/main/resources/spy.properties b/play-admin/src/main/resources/spy.properties new file mode 100644 index 0000000..0387344 --- /dev/null +++ b/play-admin/src/main/resources/spy.properties @@ -0,0 +1,31 @@ +#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory + +# 自定义日志打印 +logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger + +#日志输出到控制台 +appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger + +# 使用日志系统记录 sql +#appender=com.p6spy.engine.spy.appender.Slf4JLogger + +# 设置 p6spy driver 代理 +deregisterdrivers=true + +# 取消JDBC URL前缀 +useprefix=true + +# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset + +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss + +# 实际驱动可多个 +#driverlist=org.h2.Driver + +# 是否开启慢SQL记录 +outagedetection=true + +# 慢SQL记录标准 2 秒 +outagedetectioninterval=2 \ No newline at end of file diff --git a/play-common/pom.xml b/play-common/pom.xml new file mode 100644 index 0000000..48c8578 --- /dev/null +++ b/play-common/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + com.starry + play-with + 1.0 + + + play-common + + + 8 + 8 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.projectlombok + lombok + + + + com.baomidou + mybatis-plus-boot-starter + + + javax.persistence + javax.persistence-api + + + + com.alibaba + druid-spring-boot-starter + + + + com.github.xiaoymin + knife4j-spring-boot-starter + + + + org.apache.commons + commons-lang3 + + + + commons-io + commons-io + + + + com.github.oshi + oshi-core + + + + cn.hutool + hutool-all + + + + javax.validation + validation-api + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + com.alibaba.fastjson2 + fastjson2 + + + com.github.gavlyukovskiy + p6spy-spring-boot-starter + + + + com.qcloud + cos_api + + + tencentcloud-sdk-java-common + com.tencentcloudapi + + + gson + com.google.code.gson + + + + + com.google.code.gson + gson + + + + com.qcloud + cos-sts_api + + + + com.alibaba + transmittable-thread-local + + + + \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/annotation/DataScope.java b/play-common/src/main/java/com/starry/common/annotation/DataScope.java new file mode 100644 index 0000000..eac2b19 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/annotation/DataScope.java @@ -0,0 +1,29 @@ +package com.starry.common.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限过滤注解 + * + * @author admin + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope { + /** + * 部门表的别名 + */ + String deptAlias() default ""; + + /** + * 用户表的别名 + */ + String userAlias() default ""; + + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + String permission() default ""; +} diff --git a/play-common/src/main/java/com/starry/common/annotation/Log.java b/play-common/src/main/java/com/starry/common/annotation/Log.java new file mode 100644 index 0000000..5f92c0c --- /dev/null +++ b/play-common/src/main/java/com/starry/common/annotation/Log.java @@ -0,0 +1,36 @@ +package com.starry.common.annotation; + +import com.starry.common.enums.BusinessType; + +import java.lang.annotation.*; + +/** + * @author admin + * 自定义操作日志记录注解 + * @since 2022/7/22 + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) // 注解放置的目标位置,PARAMETER: 可用在参数上 METHOD:可用在方法级别上 +@Retention(RetentionPolicy.RUNTIME) // 指明修饰的注解的生存周期 RUNTIME:运行级别保留 +@Documented +public @interface Log { + + /** + * 模块 + */ + String title() default ""; + + /** + * 功能 + */ + BusinessType businessType() default BusinessType.OTHER; + + /** + * 是否保存请求的参数 + */ + boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + boolean isSaveResponseData() default true; +} diff --git a/play-common/src/main/java/com/starry/common/config/CosConfig.java b/play-common/src/main/java/com/starry/common/config/CosConfig.java new file mode 100644 index 0000000..9894df8 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/CosConfig.java @@ -0,0 +1,56 @@ +package com.starry.common.config; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.region.Region; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author admin + * 腾讯云cos对象存储配置类 + * @since 2022/10/28 + */ +@Data +@Component +@ConfigurationProperties(prefix = "cos") +public class CosConfig { + /** + * 存储桶访问路径 + **/ + private String baseUrl; + /** + * 腾讯云账号秘钥 + **/ + private String secretId; + /** + * 密码秘钥 + **/ + private String secretKey; + /** + * 存储桶地区 + **/ + private String regionName; + /** + * 存储桶名称 + **/ + private String bucketName; + /** + * 上传的根目录 + **/ + private String folderPrefix; + + public COSClient getCosClient() { + // 初始化用户信息 + COSCredentials cosCredentials = new BasicCOSCredentials(this.secretId, this.secretKey); + // 设置地域 + Region region = new Region(this.regionName); + ClientConfig config = new ClientConfig(region); + // 生成COS客户端 + return new COSClient(cosCredentials, config); + } + +} diff --git a/play-common/src/main/java/com/starry/common/config/FastJson2JsonRedisSerializer.java b/play-common/src/main/java/com/starry/common/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..8737bab --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,44 @@ +package com.starry.common.config; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer { + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private final Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length <= 0) { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); + } +} diff --git a/play-common/src/main/java/com/starry/common/config/RedisConfig.java b/play-common/src/main/java/com/starry/common/config/RedisConfig.java new file mode 100644 index 0000000..ad33157 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/RedisConfig.java @@ -0,0 +1,36 @@ +package com.starry.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * @author admin + * redis 配置 + * @since 2022/10/18 + */ + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/play-common/src/main/java/com/starry/common/config/Swagger2Config.java b/play-common/src/main/java/com/starry/common/config/Swagger2Config.java new file mode 100644 index 0000000..a14d13c --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/Swagger2Config.java @@ -0,0 +1,53 @@ +package com.starry.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiKey; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author admin + * swagger配置 + * @since 2021/9/1 + */ +@Configuration +@EnableSwagger2WebMvc +public class Swagger2Config { + + @Bean(value = "defaultApi2") + public Docket defaultApi2() { + Docket docket = new Docket(DocumentationType.SWAGGER_2) + .apiInfo(new ApiInfoBuilder() + .title("接口文档") + .description("# 接口文档") + .termsOfServiceUrl("http://www.xx.com/") + .contact("277769738@qq.com") + .version("1.0") + .build()) + // 分组名称 + .groupName("2.X版本") + .select() + // 这里指定Controller扫描包路径 + .apis(RequestHandlerSelectors.basePackage("com.java.admin")) + .paths(PathSelectors.any()) + .build(); + return docket; + } + + private List securitySchemes() { + // 设置请求头信息 + List result = new ArrayList<>(); + ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header"); + result.add(apiKey); + return result; + } +} diff --git a/play-common/src/main/java/com/starry/common/config/ThreadPoolConfig.java b/play-common/src/main/java/com/starry/common/config/ThreadPoolConfig.java new file mode 100644 index 0000000..012cc8f --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/ThreadPoolConfig.java @@ -0,0 +1,67 @@ +package com.starry.common.config; + + +import com.starry.common.utils.ThreadsUtils; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @author admin + * 线程池配置 + * @since 2022/7/25 + */ +@Configuration +public class ThreadPoolConfig { + + /** + * 核心线程池大小 + **/ + private final int corePoolSize = 50; + /** + * 最大可创建的线程数 + **/ + private final int maxPoolSize = 200; + /** + * 队列最大长度 + **/ + private final int queueCapacity = 1000; + /** + * 线程池维护线程所允许的空闲时间 + **/ + private final int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() { + return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) { + @Override + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + ThreadsUtils.printException(r, t); + } + }; + } + + +} diff --git a/play-common/src/main/java/com/starry/common/config/typehandler/ArrayLongTypeHandler.java b/play-common/src/main/java/com/starry/common/config/typehandler/ArrayLongTypeHandler.java new file mode 100644 index 0000000..88b6239 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/typehandler/ArrayLongTypeHandler.java @@ -0,0 +1,48 @@ +package com.starry.common.config.typehandler; + +import cn.hutool.json.JSONUtil; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * 存储到数据库, 将LONG数组转换成字符串; + * 从数据库获取数据, 将字符串转为LONG数组. + */ +@MappedTypes({Long[].class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class ArrayLongTypeHandler extends BaseTypeHandler { + + private static final Long[] l = new Long[]{}; + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, + Long[] parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, JSONUtil.toJsonStr(parameter)); + } + + @Override + public Long[] getNullableResult(ResultSet rs, String columnName) + throws SQLException { + return JSONUtil.parseArray(rs.getString(columnName)).toArray(l); + } + + @Override + public Long[] getNullableResult(ResultSet rs, int columnIndex) + throws SQLException { + return JSONUtil.parseArray(rs.getString(columnIndex)).toArray(l); + } + + @Override + public Long[] getNullableResult(CallableStatement cs, int columnIndex) + throws SQLException { + return JSONUtil.parseArray(cs.getString(columnIndex)).toArray(l); + } + +} diff --git a/play-common/src/main/java/com/starry/common/config/typehandler/ArrayStringTypeHandler.java b/play-common/src/main/java/com/starry/common/config/typehandler/ArrayStringTypeHandler.java new file mode 100644 index 0000000..7ba226a --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/typehandler/ArrayStringTypeHandler.java @@ -0,0 +1,48 @@ +package com.starry.common.config.typehandler; + +import cn.hutool.json.JSONUtil; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * 存储到数据库, 将String数组转换成字符串; + * 从数据库获取数据, 将字符串转为LONG数组. + */ +@MappedTypes({String[].class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class ArrayStringTypeHandler extends BaseTypeHandler { + + private static final String[] l = new String[]{}; + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, + String[] parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, JSONUtil.toJsonStr(parameter)); + } + + @Override + public String[] getNullableResult(ResultSet rs, String columnName) + throws SQLException { + return JSONUtil.parseArray(rs.getString(columnName)).toArray(l); + } + + @Override + public String[] getNullableResult(ResultSet rs, int columnIndex) + throws SQLException { + return JSONUtil.parseArray(rs.getString(columnIndex)).toArray(l); + } + + @Override + public String[] getNullableResult(CallableStatement cs, int columnIndex) + throws SQLException { + return JSONUtil.parseArray(cs.getString(columnIndex)).toArray(l); + } + +} diff --git a/play-common/src/main/java/com/starry/common/config/typehandler/JsonTypeHandler.java b/play-common/src/main/java/com/starry/common/config/typehandler/JsonTypeHandler.java new file mode 100644 index 0000000..ca95a81 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/config/typehandler/JsonTypeHandler.java @@ -0,0 +1,50 @@ +package com.starry.common.config.typehandler; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * 存储到数据库, 将JSON对象转换成字符串; + * 从数据库获取数据, 将字符串转为JSON对象. + */ +@MappedTypes({JSONObject.class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class JsonTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, JSONObject parameter, + JdbcType jdbcType) throws SQLException { + + ps.setString(i, JSONUtil.toJsonStr(parameter)); + } + + @Override + public JSONObject getNullableResult(ResultSet rs, String columnName) + throws SQLException { + + return JSONUtil.parseObj(rs.getString(columnName)).toBean(JSONObject.class); + } + + @Override + public JSONObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + + return JSONUtil.parseObj(rs.getString(columnIndex)).toBean(JSONObject.class); + } + + @Override + public JSONObject getNullableResult(CallableStatement cs, int columnIndex) + throws SQLException { + + return JSONUtil.parseObj(cs.getString(columnIndex)).toBean(JSONObject.class); + } + +} diff --git a/play-common/src/main/java/com/starry/common/constant/CacheConstants.java b/play-common/src/main/java/com/starry/common/constant/CacheConstants.java new file mode 100644 index 0000000..bbc3ff1 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/constant/CacheConstants.java @@ -0,0 +1,23 @@ +package com.starry.common.constant; + +/** + * @author 缓存常量 + * @since 2022/8/26 + */ +public class CacheConstants { + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; +} diff --git a/play-common/src/main/java/com/starry/common/constant/Constants.java b/play-common/src/main/java/com/starry/common/constant/Constants.java new file mode 100644 index 0000000..3c02722 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/constant/Constants.java @@ -0,0 +1,58 @@ +package com.starry.common.constant; + +/** + * @author admin + * 通用常量信息 + */ +public class Constants { + + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; +} diff --git a/play-common/src/main/java/com/starry/common/constant/HttpStatus.java b/play-common/src/main/java/com/starry/common/constant/HttpStatus.java new file mode 100644 index 0000000..15b381c --- /dev/null +++ b/play-common/src/main/java/com/starry/common/constant/HttpStatus.java @@ -0,0 +1,89 @@ +package com.starry.common.constant; + +/** + * 返回状态码 + * + * @author ruoyi + */ +@SuppressWarnings("") +public class HttpStatus { + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; +} diff --git a/play-common/src/main/java/com/starry/common/constant/SecurityConstants.java b/play-common/src/main/java/com/starry/common/constant/SecurityConstants.java new file mode 100644 index 0000000..6aa934c --- /dev/null +++ b/play-common/src/main/java/com/starry/common/constant/SecurityConstants.java @@ -0,0 +1,39 @@ +package com.starry.common.constant; + +/** + * @author admin + * 权限相关常量 + */ +public class SecurityConstants { + /** + * 租户ID字段 + */ + public static final String DETAILS_TENANT_ID = "tenant_id"; + + /** + * 部门ID字段 + */ + public static final String DETAILS_DEPT_ID = "dept_id"; + + /** + * 用户ID字段 + */ + public static final String DETAILS_USER_ID = "user_id"; + + /** + * 用户名字段 + */ + public static final String DETAILS_USERNAME = "username"; + /** + * 用户标识 + */ + public static final String USER_KEY = "user_key"; + /** + * 登录用户 + */ + public static final String LOGIN_USER = "login_user"; + /** + * 角色权限 + */ + public static final String ROLE_PERMISSION = "role_permission"; +} diff --git a/play-common/src/main/java/com/starry/common/constant/UserConstants.java b/play-common/src/main/java/com/starry/common/constant/UserConstants.java new file mode 100644 index 0000000..14daae4 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/constant/UserConstants.java @@ -0,0 +1,78 @@ +package com.starry.common.constant; + +/** + * @author 用户常量信息 + * @since 2022/7/4 + */ +public class UserConstants { + + /** + * 校验返回结果码 0:唯一 1:不唯一 + */ + public final static String UNIQUE = "0"; + public final static String NOT_UNIQUE = "1"; + + /** + * 是否菜单外链(否) + */ + public static final String NO_FRAME = "0"; + + /** + * 是否菜单外链(是) + */ + public static final String YES_FRAME = "1"; + + /** + * 菜单类型(目录) + */ + public static final String TYPE_DIR = "0"; + + /** + * 菜单类型(菜单) + */ + public static final String TYPE_MENU = "1"; + + /** + * 菜单类型(按钮) + */ + public static final String TYPE_BUTTON = "2"; + + /** + * Layout组件标识 + */ + public final static String LAYOUT = "Layout"; + + /** + * InnerLink组件标识 + */ + public final static String INNER_LINK = "InnerLink"; + + /** + * ParentView组件标识 + */ + public final static String PARENT_VIEW = "ParentView"; + + /** + * 部门正常状态 + */ + public static final String DEPT_NORMAL = "0"; + + /** + * 部门停用状态 + */ + public static final String DEPT_DISABLE = "1"; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + + public static final int PASSWORD_MAX_LENGTH = 20; +} diff --git a/play-common/src/main/java/com/starry/common/context/SecurityContextHolder.java b/play-common/src/main/java/com/starry/common/context/SecurityContextHolder.java new file mode 100644 index 0000000..ccdbd5a --- /dev/null +++ b/play-common/src/main/java/com/starry/common/context/SecurityContextHolder.java @@ -0,0 +1,77 @@ +package com.starry.common.context; + +import cn.hutool.core.convert.Convert; +import com.alibaba.ttl.TransmittableThreadLocal; +import com.starry.common.constant.SecurityConstants; +import com.starry.common.utils.StringUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author admin + * 获取当前线程变量中的 租户id 部门id 用户id、用户名称、Token等信息 + * @since 2023/3/6 + */ +public class SecurityContextHolder { + + private static final TransmittableThreadLocal> THREAD_LOCAL = new TransmittableThreadLocal<>(); + + public static void set(String key, Object value) { + Map map = getLocalMap(); + map.put(key, value == null ? StringUtils.EMPTY : value); + } + + public static String get(String key) { + Map map = getLocalMap(); + return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY)); + } + + + public static T get(String key, Class clazz) { + Map map = getLocalMap(); + return StringUtils.cast(map.getOrDefault(key, null)); + } + + public static Map getLocalMap() { + Map map = THREAD_LOCAL.get(); + if (map == null) { + map = new ConcurrentHashMap<>(); + THREAD_LOCAL.set(map); + } + return map; + } + + public static void setLocalMap(Map threadLocalMap) { + THREAD_LOCAL.set(threadLocalMap); + } + + + public static Long getTenantId() { + return Convert.toLong(get(SecurityConstants.DETAILS_TENANT_ID), 9999L); + } + + public static void setTenantId(String tenantId) { + set(SecurityConstants.DETAILS_TENANT_ID, tenantId); + } + + public static Long getDeptId() { + return Convert.toLong(get(SecurityConstants.DETAILS_DEPT_ID)); + } + + public static void setDeptId(String deptId) { + set(SecurityConstants.DETAILS_DEPT_ID, deptId); + } + + public static String getPermission() { + return get(SecurityConstants.ROLE_PERMISSION); + } + + public static void setPermission(String permissions) { + set(SecurityConstants.ROLE_PERMISSION, permissions); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } +} diff --git a/play-common/src/main/java/com/starry/common/controller/CaptchaController.java b/play-common/src/main/java/com/starry/common/controller/CaptchaController.java new file mode 100644 index 0000000..83d55a4 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/controller/CaptchaController.java @@ -0,0 +1,33 @@ +package com.starry.common.controller; + + +import com.starry.common.domain.Captcha; +import com.starry.common.redis.CaptchaService; +import com.starry.common.result.R; +import io.swagger.annotations.ApiOperation; +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 javax.annotation.Resource; + +/** + * @author admin + * 验证码前端控制器 + * @since 2022/7/7 + */ +@RestController +@RequestMapping("/captcha") +public class CaptchaController { + + @Resource + private CaptchaService captchaService; + + @ApiOperation(value = "生成验证码拼图") + @PostMapping("get-captcha") + public R getCaptcha(@RequestBody Captcha captcha) { + return R.ok(captchaService.getCaptcha(captcha)); + } + +} diff --git a/play-common/src/main/java/com/starry/common/controller/CosController.java b/play-common/src/main/java/com/starry/common/controller/CosController.java new file mode 100644 index 0000000..0047bf3 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/controller/CosController.java @@ -0,0 +1,83 @@ +package com.starry.common.controller; + +import com.starry.common.result.R; +import com.starry.common.utils.file.CosClientUtils; +import com.tencent.cloud.CosStsClient; +import com.tencent.cloud.Response; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.TreeMap; + +/** + * @author admin + * cos存储前端控制器 + * @since 2022/11/13 17:51 + */ +@Slf4j +@RestController +@RequestMapping("/cos") +public class CosController { + + @Value("${cos.secretId}") + private String secretId; + @Value("${cos.secretKey}") + private String secretKey; + @Value("${cos.regionName}") + private String regionName; + @Value("${cos.bucketName}") + private String bucketName; + + @ApiOperation(value = "照片上传") + @PostMapping("/upload/image") + public R uploadImage(MultipartFile file) throws Exception { + if (!file.isEmpty()) { + String avatar = CosClientUtils.upload(file, "house"); + return R.ok(avatar); + } + return R.error("上传照片异常,请联系管理员"); + } + + @ApiOperation(value = "获取cos临时密钥") + @GetMapping("/temp-key") + public R getTempKey() { + TreeMap config = new TreeMap<>(); + try { + // 替换为您的云 api 密钥 SecretId + config.put("secretId", secretId); + // 替换为您的云 api 密钥 SecretKey + config.put("secretKey", secretKey); + // 临时密钥有效时长,单位是秒,默认 1800 秒,目前主账号最长 2 小时(即 7200 秒),子账号最长 36 小时(即 129600)秒 + config.put("durationSeconds", 1800); + // 换成您的 bucket + config.put("bucket", bucketName); + // 换成 bucket 所在地区 + config.put("region", regionName); + + // 只允许用户访问 upload/house 目录下的资源 + config.put("allowPrefixes", new String[]{"upload/house/*"}); + + // 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。 + String[] allowActions = new String[]{ + // 简单上传 + "name/cos:PutObject", + // 表单上传、小程序上传 + "name/cos:PostObject", + // 分块上传 + "name/cos:InitiateMultipartUpload", "name/cos:ListMultipartUploads", "name/cos:ListParts", "name/cos:UploadPart", "name/cos:CompleteMultipartUpload"}; + config.put("allowActions", allowActions); + + Response response = CosStsClient.getCredential(config); + return R.ok(response); + } catch (Exception e) { + log.error("getTempKey error", e); + throw new IllegalArgumentException("no valid secret !"); + } + } +} diff --git a/play-common/src/main/java/com/starry/common/domain/BaseEntity.java b/play-common/src/main/java/com/starry/common/domain/BaseEntity.java new file mode 100644 index 0000000..fd7a314 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/domain/BaseEntity.java @@ -0,0 +1,81 @@ +package com.starry.common.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.Version; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @author admin + * 基类 + * @since 2022/7/14 + */ +@Getter +@Setter +@MappedSuperclass +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "逻辑删除 1已删除 0未删除") + private Boolean deleted; + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建人") + private String createdBy; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间") + private Date createdTime; + + @TableField(fill = FieldFill.UPDATE) + @ApiModelProperty(value = "更新时间") + private Date updatedTime; + + @TableField(fill = FieldFill.UPDATE) + @ApiModelProperty(value = "更新人") + private String updatedBy; + + @Version + @ApiModelProperty(value = "数据版本") + private Long version; + + + /** + * 搜索值 + */ + @TableField(exist = false) + private String searchValue; + + /** + * 开始日期 + */ + @TableField(exist = false) + private String beginTime; + + /** + * 结束日期 + */ + @TableField(exist = false) + private String endTime; + + /** + * 请求参数 + */ + @Transient + @TableField(exist = false) + private Map params = new HashMap<>(); + +} diff --git a/play-common/src/main/java/com/starry/common/domain/BasePageEntity.java b/play-common/src/main/java/com/starry/common/domain/BasePageEntity.java new file mode 100644 index 0000000..728e2ae --- /dev/null +++ b/play-common/src/main/java/com/starry/common/domain/BasePageEntity.java @@ -0,0 +1,73 @@ +package com.starry.common.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.Version; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.Transient; +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +/** + * @author admin + * 分页基类 + * @since 2021/9/2 + */ +@Data +@Accessors(chain = true) +public class BasePageEntity implements Serializable { + + private int currentPage = 1; + + private int pageSize = 10; + + private int pageNum = 1; + + @TableField(fill = FieldFill.INSERT) + private Boolean deleted = Boolean.FALSE; + + @TableField(fill = FieldFill.INSERT) + private Long createdBy; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT) + private Date createdTime; + + @TableField(fill = FieldFill.UPDATE) + private Date updatedTime; + + @TableField(fill = FieldFill.UPDATE) + private Long updatedBy; + + @Version + private Long version; + + /** + * 搜索值 + */ + @TableField(exist = false) + private String searchValue; + + /** + * 开始日期 + */ + @TableField(exist = false) + private String beginTime; + + /** + * 结束日期 + */ + @TableField(exist = false) + private String endTime; + + /** + * 请求参数 + */ + @Transient + @TableField(exist = false) + private Map params; +} diff --git a/play-common/src/main/java/com/starry/common/domain/Captcha.java b/play-common/src/main/java/com/starry/common/domain/Captcha.java new file mode 100644 index 0000000..9d033f6 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/domain/Captcha.java @@ -0,0 +1,60 @@ +package com.starry.common.domain; + +import lombok.Data; + +/** + * @author admin + * 验证码拼图类 + */ +@Data +public class Captcha { + + /** + * 随机字符串 + **/ + private String nonceStr; + /** + * 验证值 + **/ + private String value; + /** + * 生成的画布的base64 + **/ + private String canvasSrc; + /** + * 画布宽度 + **/ + private Integer canvasWidth; + /** + * 画布高度 + **/ + private Integer canvasHeight; + /** + * 生成的阻塞块的base64 + **/ + private String blockSrc; + /** + * 阻塞块宽度 + **/ + private Integer blockWidth; + /** + * 阻塞块高度 + **/ + private Integer blockHeight; + /** + * 阻塞块凸凹半径 + **/ + private Integer blockRadius; + /** + * 阻塞块的横轴坐标 + **/ + private Integer blockX; + /** + * 阻塞块的纵轴坐标 + **/ + private Integer blockY; + /** + * 图片获取位置 + **/ + private Integer place; +} diff --git a/play-common/src/main/java/com/starry/common/enums/BusinessType.java b/play-common/src/main/java/com/starry/common/enums/BusinessType.java new file mode 100644 index 0000000..0aee865 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/enums/BusinessType.java @@ -0,0 +1,53 @@ +package com.starry.common.enums; + +/** + * @author admin + * 业务操作类型 + * @since 2022/7/22 + */ +public enum BusinessType { + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + /** + * 生成代码 + */ + GENCODE, + /** + * 清空数据 + */ + CLEAN, + /** + * 强退 + */ + FORCE, + /** + * 授权 + */ + GRANT, +} diff --git a/play-common/src/main/java/com/starry/common/filter/RepeatableFilter.java b/play-common/src/main/java/com/starry/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..72d33de --- /dev/null +++ b/play-common/src/main/java/com/starry/common/filter/RepeatableFilter.java @@ -0,0 +1,40 @@ +package com.starry.common.filter; + +import com.starry.common.utils.StringUtils; +import org.springframework.http.MediaType; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() { + + } +} diff --git a/play-common/src/main/java/com/starry/common/filter/RepeatedlyRequestWrapper.java b/play-common/src/main/java/com/starry/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..11c6205 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,65 @@ +package com.starry.common.filter; + + +import com.starry.common.utils.http.HttpHelper; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { + super(request); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + body = HttpHelper.getBodyString(request).getBytes("UTF-8"); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + + return new ServletInputStream() { + + @Override + public int read() throws IOException { + return bais.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/play-common/src/main/java/com/starry/common/filter/XssFilter.java b/play-common/src/main/java/com/starry/common/filter/XssFilter.java new file mode 100644 index 0000000..8b03bac --- /dev/null +++ b/play-common/src/main/java/com/starry/common/filter/XssFilter.java @@ -0,0 +1,79 @@ +package com.starry.common.filter; + +import com.starry.common.utils.StringUtils; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter { + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + /** + * xss过滤开关 + */ + public boolean enabled = false; + + @Override + public void init(FilterConfig filterConfig) { + String tempExcludes = filterConfig.getInitParameter("excludes"); + String tempEnabled = filterConfig.getInitParameter("enabled"); + if (StringUtils.isNotEmpty(tempExcludes)) { + String[] url = tempExcludes.split(","); + excludes.addAll(Arrays.asList(url)); + } + if (StringUtils.isNotEmpty(tempEnabled)) { + enabled = Boolean.parseBoolean(tempEnabled); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { + if (!enabled) { + return true; + } + if (excludes == null || excludes.isEmpty()) { + return false; + } + String url = request.getServletPath(); + for (String pattern : excludes) { + Pattern p = Pattern.compile("^" + pattern); + Matcher m = p.matcher(url); + if (m.find()) { + return true; + } + } + return false; + } + + @Override + public void destroy() { + + } +} \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/filter/XssHttpServletRequestWrapper.java b/play-common/src/main/java/com/starry/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..4dc125d --- /dev/null +++ b/play-common/src/main/java/com/starry/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,90 @@ +package com.starry.common.filter; + +import com.starry.common.utils.StringUtils; +import com.starry.common.utils.html.EscapeUtil; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values != null) { + int length = values.length; + String[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) { + // 防xss攻击和过滤前后空格 + escapseValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapseValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 非json类型,直接返回 + if (!isJsonRequest()) { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), StandardCharsets.UTF_8); + if (StringUtils.isEmpty(json)) { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + */ + public boolean isJsonRequest() { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header); + } +} \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/redis/CaptchaService.java b/play-common/src/main/java/com/starry/common/redis/CaptchaService.java new file mode 100644 index 0000000..4e925b2 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/redis/CaptchaService.java @@ -0,0 +1,97 @@ +package com.starry.common.redis; + +import cn.hutool.core.util.StrUtil; +import com.starry.common.constant.CacheConstants; +import com.starry.common.domain.Captcha; +import com.starry.common.utils.CaptchaUtils; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.awt.image.BufferedImage; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 验证码服务 + * + * @author admin + */ +@Service +public class CaptchaService { + /** + * 拼图验证码允许偏差 + **/ + private static final Integer ALLOW_DEVIATION = 3; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 校验验证码 + * + * @param imageKey + * @param imageCode + * @return boolean + **/ + public String checkImageCode(String imageKey, String imageCode) { + ValueOperations ops = stringRedisTemplate.opsForValue(); + String text = ops.get(CacheConstants.CAPTCHA_CODE_KEY + imageKey); + if (StrUtil.isBlank(text)) { + return "验证码已失效"; + } + // 根据移动距离判断验证是否成功 + if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) { + return "验证失败,请控制拼图对齐缺口"; + } + return null; + } + + /** + * 缓存验证码,有效期15分钟 + * + * @param key + * @param code + **/ + public void saveImageCode(String key, String code) { + ValueOperations ops = stringRedisTemplate.opsForValue(); + ops.set(CacheConstants.CAPTCHA_CODE_KEY + key, code, 15, TimeUnit.MINUTES); + } + + /** + * 获取验证码拼图(生成的抠图和带抠图阴影的大图及抠图坐标) + **/ + public Object getCaptcha(Captcha captcha) { + // 参数校验 + CaptchaUtils.checkCaptcha(captcha); + // 获取画布的宽高 + int canvasWidth = captcha.getCanvasWidth(); + int canvasHeight = captcha.getCanvasHeight(); + // 获取阻塞块的宽高/半径 + int blockWidth = captcha.getBlockWidth(); + int blockHeight = captcha.getBlockHeight(); + int blockRadius = captcha.getBlockRadius(); + // 获取资源图 + BufferedImage canvasImage = CaptchaUtils.getBufferedImage(captcha.getPlace()); + // 调整原图到指定大小 + canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight); + // 随机生成阻塞块坐标 + int blockX = CaptchaUtils.getNonceByRange(blockWidth, canvasWidth - blockWidth - 10); + int blockY = CaptchaUtils.getNonceByRange(10, canvasHeight - blockHeight + 1); + // 阻塞块 + BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR); + // 新建的图像根据轮廓图颜色赋值,源图生成遮罩 + CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY); + // 移动横坐标 + String nonceStr = UUID.randomUUID().toString().replaceAll("-", ""); + // 缓存 + saveImageCode(nonceStr, String.valueOf(blockX)); + // 设置返回参数 + captcha.setNonceStr(nonceStr); + captcha.setBlockY(blockY); + captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png")); + captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png")); + return captcha; + } +} diff --git a/play-common/src/main/java/com/starry/common/redis/RedisCache.java b/play-common/src/main/java/com/starry/common/redis/RedisCache.java new file mode 100644 index 0000000..955f76a --- /dev/null +++ b/play-common/src/main/java/com/starry/common/redis/RedisCache.java @@ -0,0 +1,82 @@ +package com.starry.common.redis; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * @author rieds 工具类 + * @since 2022/8/26 + */ +@Component +public class RedisCache { + + @Resource + public RedisTemplate redisTemplate; + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operations = redisTemplate.opsForValue(); + return operations.get(key); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 过期时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public long deleteObject(final Collection collection) { + return redisTemplate.delete(collection); + } +} diff --git a/play-common/src/main/java/com/starry/common/result/IBaseEnum.java b/play-common/src/main/java/com/starry/common/result/IBaseEnum.java new file mode 100644 index 0000000..78db2b6 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/result/IBaseEnum.java @@ -0,0 +1,19 @@ +package com.starry.common.result; + +/** + * @author admin + */ +public interface IBaseEnum { + + /** + * 获取枚举值 + * + */ + int getCode(); + + /** + * 获取枚举描述 + * + */ + String getMessage(); +} diff --git a/play-common/src/main/java/com/starry/common/result/R.java b/play-common/src/main/java/com/starry/common/result/R.java new file mode 100644 index 0000000..30c9a6f --- /dev/null +++ b/play-common/src/main/java/com/starry/common/result/R.java @@ -0,0 +1,133 @@ +package com.starry.common.result; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.beans.ConstructorProperties; +import java.io.Serializable; + +/** + * @author admin + * @since 2021/9/1 + */ +@Data +@ApiModel(value = "全局统一返回结果") +public class R implements Serializable { + + public final static String OK_MSG = "请求成功"; + public final static String FAIL_MSG = "请求失败"; + + @ApiModelProperty(value = "是否成功") + private boolean success; + + @ApiModelProperty(value = "返回码") + private Integer code; + + @ApiModelProperty(value = "返回消息") + private String message; + + @ApiModelProperty(value = "返回数据") + private Object data; + + @ApiModelProperty(value = "总条数") + private Long total; + + @ApiModelProperty(value = "分页信息") + private PageInfo pageInfo; + + private R() { + } + + private R(int code, Boolean success, String msg, Object data) { + this.code = code; + this.success = success; + this.message = msg; + if (data instanceof Page) { + Page page = (Page) data; + this.total = page.getTotal(); + this.data = page.getRecords(); + this.pageInfo = new PageInfo((int) page.getCurrent(), (int) page.getSize(), page.getTotal(), page.getPages()); + } else { + this.data = data; + } + } + + public static R ok() { + R r = new R(); + r.setSuccess(true); + r.setCode(ResultCodeEnum.SUCCESS.getCode()); + r.setMessage(ResultCodeEnum.SUCCESS.getMessage()); + return r; + } + + public static R ok(Object data) { + return new R(ResultCodeEnum.SUCCESS.getCode(), true, ResultCodeEnum.SUCCESS.getMessage(), data); + } + + public static R ok(String msg, Object data) { + return new R(ResultCodeEnum.SUCCESS.getCode(), true, msg, data); + } + + public static R error() { + R r = new R(); + r.setSuccess(false); + r.setCode(ResultCodeEnum.FAILED.getCode()); + r.setMessage(ResultCodeEnum.FAILED.getMessage()); + return r; + } + + public static R error(String msg) { + return new R(ResultCodeEnum.FAILED.getCode(), false, msg, null); + } + + public static R error(int errorCode, String msg) { + return new R(errorCode, false, msg, null); + } + + public static R unauthorized() { + return new R(ResultCodeEnum.UNAUTHORIZED.getCode(), false, ResultCodeEnum.UNAUTHORIZED.getMessage(), null); + } + + public R message(String message) { + this.setMessage(message); + return this; + } + + public R code(Integer code) { + this.setCode(code); + return this; + } + + public R data(Object data) { + this.setData(data); + return this; + } + + @Data + public static class PageInfo { + + @ApiModelProperty("当前页") + protected int currentPage; + @ApiModelProperty("页大小") + protected int pageSize; + @ApiModelProperty("总记录数") + protected long totalCount; + @ApiModelProperty("总页数") + protected long totalPage; + + public PageInfo() { + } + + @ConstructorProperties({"currentPage", "pageSize", "totalCount", "totalPage"}) + public PageInfo(int currentPage, int pageSize, long totalCount, long totalPage) { + this.currentPage = currentPage; + this.pageSize = pageSize; + this.totalCount = totalCount; + this.totalPage = totalPage; + } + } + +} diff --git a/play-common/src/main/java/com/starry/common/result/ResultCodeEnum.java b/play-common/src/main/java/com/starry/common/result/ResultCodeEnum.java new file mode 100644 index 0000000..10f1924 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/result/ResultCodeEnum.java @@ -0,0 +1,32 @@ +package com.starry.common.result; + +/** + * @author admin + * 返回码枚举 + */ +public enum ResultCodeEnum implements IBaseEnum { + + SUCCESS(200, "操作成功"), + FAILED(500, "操作失败"), + VALIDATE_FAILED(404, "参数检验失败"), + UNAUTHORIZED(401, "无效的会话,或者会话已过期,请重新登录。"), + FORBIDDEN(403, "没有相关权限"); + + private final int code; + private final String message; + + ResultCodeEnum(int code, String message) { + this.code = code; + this.message = message; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/play-common/src/main/java/com/starry/common/sensitive/Sensitive.java b/play-common/src/main/java/com/starry/common/sensitive/Sensitive.java new file mode 100644 index 0000000..dd6f5bf --- /dev/null +++ b/play-common/src/main/java/com/starry/common/sensitive/Sensitive.java @@ -0,0 +1,28 @@ +package com.starry.common.sensitive; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 脱敏注解 + * + * @author admin + * + **/ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveSerialize.class) +public @interface Sensitive { + + /** + * 脱敏数据类型 + */ + SensitiveTypeEnum type(); + +} diff --git a/play-common/src/main/java/com/starry/common/sensitive/SensitiveSerialize.java b/play-common/src/main/java/com/starry/common/sensitive/SensitiveSerialize.java new file mode 100644 index 0000000..456c2a9 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/sensitive/SensitiveSerialize.java @@ -0,0 +1,67 @@ +package com.starry.common.sensitive; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.util.Objects; + +/** + * @author admin + * + * 脱敏序列化 + */ +@NoArgsConstructor +@AllArgsConstructor +public class SensitiveSerialize extends JsonSerializer implements ContextualSerializer { + + private SensitiveTypeEnum type; + + @Override + public void serialize(final String originStr, final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider) throws IOException { + switch (type) { + case CHINESE_NAME: + jsonGenerator.writeString(SensitiveUtils.chineseName(originStr)); + break; + case MOBILE_PHONE: + jsonGenerator.writeString(SensitiveUtils.mobilePhone(originStr)); + break; + case EMAIL: + jsonGenerator.writeString(SensitiveUtils.email(originStr)); + break; + case PASSWORD: + jsonGenerator.writeString(SensitiveUtils.password(originStr)); + break; + case KEY: + jsonGenerator.writeString(SensitiveUtils.key(originStr)); + break; + default: + throw new IllegalArgumentException("未定义的敏感信息枚举类" + type); + } + } + + @Override + public JsonSerializer createContextual(final SerializerProvider serializerProvider, final BeanProperty beanProperty) throws JsonMappingException { + if (beanProperty != null) { + if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { + Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class); + if (sensitive == null) { + sensitive = beanProperty.getContextAnnotation(Sensitive.class); + } + if (sensitive != null) { + return new SensitiveSerialize(sensitive.type()); + } + } + return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); + } + return serializerProvider.findNullValueSerializer(null); + } + +} diff --git a/play-common/src/main/java/com/starry/common/sensitive/SensitiveTypeEnum.java b/play-common/src/main/java/com/starry/common/sensitive/SensitiveTypeEnum.java new file mode 100644 index 0000000..aabbd8d --- /dev/null +++ b/play-common/src/main/java/com/starry/common/sensitive/SensitiveTypeEnum.java @@ -0,0 +1,31 @@ +package com.starry.common.sensitive; + +/** + * 敏感信息枚举类 + * + * @author admin + **/ +public enum SensitiveTypeEnum { + + /** + * 用户名, 李*天, 张* + */ + CHINESE_NAME, + /** + * 手机号, 185****1653 + */ + MOBILE_PHONE, + /** + * 电子邮件, r*****o@qq.com + */ + EMAIL, + /** + * 密码, ****** + */ + PASSWORD, + /** + * 密钥, 最后三位其他都是*** + */ + KEY + +} diff --git a/play-common/src/main/java/com/starry/common/sensitive/SensitiveUtils.java b/play-common/src/main/java/com/starry/common/sensitive/SensitiveUtils.java new file mode 100644 index 0000000..60ae016 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/sensitive/SensitiveUtils.java @@ -0,0 +1,155 @@ +package com.starry.common.sensitive; + +import cn.hutool.core.util.StrUtil; + +/** + * 数据脱敏工具类 + * + * @author admin + * @since 2021/7/19 16:21 + */ +public class SensitiveUtils { + + /** + * 默认填充字符 + */ + public static final String DEFAULT_PAD_STR = "*"; + + /** + * 数据脱敏 + */ + public static String process(String data) { + return process(data, 2, 1, DEFAULT_PAD_STR); + } + + /** + * 数据脱敏 + */ + public static String process(String data, Integer leftLen, Integer rightLen) { + return process(data, leftLen, rightLen, DEFAULT_PAD_STR); + } + + /** + * 对字符串进行脱敏操作 + * + * @param originStr 原始字符串 + * @param prefixNoMaskLen 左侧需要保留几位明文字段 + * @param suffixNoMaskLen 右侧需要保留几位明文字段 + * @param maskStr 用于遮罩的字符串, 如'*' + * @return 脱敏后结果 + */ + public static String process(String originStr, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) { + if (originStr == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0, n = originStr.length(); i < n; i++) { + if (i < prefixNoMaskLen) { + sb.append(originStr.charAt(i)); + continue; + } + if (i > (n - suffixNoMaskLen - 1)) { + sb.append(originStr.charAt(i)); + continue; + } + sb.append(maskStr); + } + return sb.toString(); + } + + /** + * 中文姓名只显示最后一个汉字 + * + * @param fullName 姓名 + * @return + */ + public static String chineseName(String fullName) { + if (fullName == null) { + return null; + } + return process(fullName, 0, 1, DEFAULT_PAD_STR); + } + + /** + * 手机号码前三位,后四位,如186****2356 + * + * @param num 手机号码 + * @return + */ + public static String mobilePhone(String num) { + return process(num, 0, 4, DEFAULT_PAD_STR); + } + + /** + * 地址只显示到地区 + * + * @param address 地址 + * @return + */ + public static String address(String address) { + return process(address, 6, 0, DEFAULT_PAD_STR); + } + + /** + * 电子邮箱 仅显示第一个字母,@后面的地址显示,比如:r**@qq.com + * + * @param email 电子邮箱 + * @return + */ + public static String email(String email) { + if (email == null) { + return null; + } + int index = StrUtil.indexOf(email, '@'); + if (index <= 1) { + return email; + } + String preEmail = process(email.substring(0, index), 1, 0, DEFAULT_PAD_STR); + return preEmail + email.substring(index); + + } + + /** + * 密码的全部字符,如:****** + * + * @param password 密码 + * @return + */ + public static String password(String password) { + if (password == null) { + return null; + } + return "******"; + } + + /** + * 密钥除了最后三位,全部,比如:***klo + * + * @param key 密钥 + * @return 结果 + */ + public static String key(String key) { + if (key == null) { + return null; + } + int viewLength = 6; + StringBuilder tmpKey = new StringBuilder(process(key, 0, 3, DEFAULT_PAD_STR)); + if (tmpKey.length() > viewLength) { + return tmpKey.substring(tmpKey.length() - viewLength); + } else if (tmpKey.length() < viewLength) { + int buffLength = viewLength - tmpKey.length(); + for (int i = 0; i < buffLength; i++) { + tmpKey.insert(0, DEFAULT_PAD_STR); + } + return tmpKey.toString(); + } else { + return tmpKey.toString(); + } + } + + public static void main(String[] args) { + String s = mobilePhone("18653653621"); + System.out.println(s); + } +} \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/utils/Arith.java b/play-common/src/main/java/com/starry/common/utils/Arith.java new file mode 100644 index 0000000..e6df8a0 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/Arith.java @@ -0,0 +1,113 @@ +package com.starry.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author ruoyi + */ +public class Arith { + + /** + * 默认除法运算精度 + */ + private static final int DEF_DIV_SCALE = 10; + + /** + * 这个类不能实例化 + */ + private Arith() { + } + + /** + * 提供精确的加法运算。 + * + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + BigDecimal one = BigDecimal.ONE; + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/CaptchaUtils.java b/play-common/src/main/java/com/starry/common/utils/CaptchaUtils.java new file mode 100644 index 0000000..1fe3e8c --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/CaptchaUtils.java @@ -0,0 +1,234 @@ +package com.starry.common.utils; + + +import com.starry.common.domain.Captcha; +import org.apache.commons.lang3.RandomUtils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Base64; +import java.util.Objects; +import java.util.Random; + +/** + * @author 拼图验证码工具类 + * @since 2022/9/6 + */ +public class CaptchaUtils { + + /** + * 网络图片地址 + **/ + private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg"; + + /** + * 本地图片地址 + **/ + private final static String IMG_PATH = "E:/Temp/wallpaper/%s.jpg"; + + /** + * 入参校验设置默认值 + **/ + public static void checkCaptcha(Captcha captcha) { + // 设置画布宽度默认值 + if (captcha.getCanvasWidth() == null) { + captcha.setCanvasWidth(320); + } + // 设置画布高度默认值 + if (captcha.getCanvasHeight() == null) { + captcha.setCanvasHeight(155); + } + // 设置阻塞块宽度默认值 + if (captcha.getBlockWidth() == null) { + captcha.setBlockWidth(65); + } + // 设置阻塞块高度默认值 + if (captcha.getBlockHeight() == null) { + captcha.setBlockHeight(55); + } + // 设置阻塞块凹凸半径默认值 + if (captcha.getBlockRadius() == null) { + captcha.setBlockRadius(9); + } + // 设置图片来源默认值 + if (captcha.getPlace() == null) { + captcha.setPlace(0); + } + } + + /** + * 获取指定范围内的随机数 + **/ + public static int getNonceByRange(int start, int end) { + Random random = new Random(); + return random.nextInt(end - start + 1) + start; + } + + /** + * 获取验证码资源图 + **/ + public static BufferedImage getBufferedImage(Integer place) { + try { + // 随机图片 + int nonce = getNonceByRange(0, 1000); + // 获取网络资源图片 + if (0 == place) { + String imgUrl = String.format(IMG_URL, nonce); + URL url = new URL(imgUrl); + return ImageIO.read(url.openStream()); + } + // 获取本地图片 + else { + String imgPath = String.format(IMG_PATH, nonce); + File file = new File(imgPath); + return ImageIO.read(file); + } + } catch (Exception e) { + System.out.println("获取拼图资源失败"); + // 异常处理 + return null; + } + } + + /** + * 调整图片大小 + **/ + public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) { + Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); + BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics2D = resultImage.createGraphics(); + graphics2D.drawImage(image, 0, 0, null); + graphics2D.dispose(); + return resultImage; + } + + /** + * 抠图,并生成阻塞块 + **/ + public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth, int blockHeight, int blockRadius, int blockX, int blockY) { + BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR); + // 阻塞块的轮廓图 + int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius); + // 创建阻塞块具体形状 + for (int i = 0; i < blockWidth; i++) { + for (int j = 0; j < blockHeight; j++) { + try { + // 原图中对应位置变色处理 + if (blockData[i][j] == 1) { + // 背景设置为黑色 + waterImage.setRGB(i, j, Color.BLACK.getRGB()); + blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j)); + // 轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点 + if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0 || blockData[i][j - 1] == 0) { + blockImage.setRGB(i, j, Color.WHITE.getRGB()); + waterImage.setRGB(i, j, Color.WHITE.getRGB()); + } + } + // 这里把背景设为透明 + else { + blockImage.setRGB(i, j, Color.TRANSLUCENT); + waterImage.setRGB(i, j, Color.TRANSLUCENT); + } + } catch (ArrayIndexOutOfBoundsException e) { + // 防止数组下标越界异常 + } + } + } + // 在画布上添加阻塞块水印 + addBlockWatermark(canvasImage, waterImage, blockX, blockY); + } + + /** + * 构建拼图轮廓轨迹 + **/ + private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) { + int[][] data = new int[blockWidth][blockHeight]; + double po = Math.pow(blockRadius, 2); + // 随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹 + // 凸/凹1 + int face1 = RandomUtils.nextInt(0, 4); + // 凸/凹2 + int face2; + // 保证两个凸/凹不在同一位置 + do { + face2 = RandomUtils.nextInt(0, 4); + } while (face1 == face2); + // 获取凸/凹起位置坐标 + int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius); + int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius); + // 随机凸/凹类型 + int shape = getNonceByRange(0, 1); + // 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆 + // 计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色 + for (int i = 0; i < blockWidth; i++) { + for (int j = 0; j < blockHeight; j++) { + data[i][j] = 0; + // 创建中间的方形区域 + if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius && j <= blockHeight - blockRadius)) { + data[i][j] = 1; + } + double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2); + double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2); + // 创建两个凸/凹 + if (d1 <= po || d2 <= po) { + data[i][j] = shape; + } + } + } + return data; + } + + /** + * 根据朝向获取圆心坐标 + */ + private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) { + // 上 + if (0 == face) { + return new int[]{blockWidth / 2 - 1, blockRadius}; + } + // 左 + else if (1 == face) { + return new int[]{blockRadius, blockHeight / 2 - 1}; + } + // 下 + else if (2 == face) { + return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1}; + } + // 右 + else if (3 == face) { + return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1}; + } + return null; + } + + /** + * 在画布上添加阻塞块水印 + */ + private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) { + Graphics2D graphics2D = canvasImage.createGraphics(); + graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f)); + graphics2D.drawImage(blockImage, x, y, null); + graphics2D.dispose(); + } + + /** + * BufferedImage转BASE64 + */ + public static String toBase64(BufferedImage bufferedImage, String type) { + try { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, type, byteArrayOutputStream); + String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); + return String.format("data:image/%s;base64,%s", type, base64); + } catch (IOException e) { + System.out.println("图片资源转换BASE64失败"); + // 异常处理 + return null; + } + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/ConvertUtil.java b/play-common/src/main/java/com/starry/common/utils/ConvertUtil.java new file mode 100644 index 0000000..432bdee --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/ConvertUtil.java @@ -0,0 +1,50 @@ +package com.starry.common.utils; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author admin + */ +@Slf4j +public class ConvertUtil { + + + public static T entityToVo(Object source, Class target) { + if (source == null) { + return null; + } + T targetObject = null; + try { + targetObject = target.newInstance(); + BeanUtils.copyProperties(source, targetObject); + } catch (Exception e) { + log.error("entityToVo error,source = {},target={}", source, target, e); + } + return targetObject; + } + + public static List entityToVoList(Collection sourceList, Class target) { + if (sourceList == null) { + return null; + } + List targetList = new ArrayList<>(sourceList.size()); + + try { + for (Object source : sourceList) { + T targetObject = target.newInstance(); + BeanUtils.copyProperties(source, targetObject); + targetList.add(targetObject); + } + } catch (Exception e) { + log.error("entityToVo error,source = {},target={}", sourceList, target, e); + } + return targetList; + } + +} diff --git a/play-common/src/main/java/com/starry/common/utils/HttpUtils.java b/play-common/src/main/java/com/starry/common/utils/HttpUtils.java new file mode 100644 index 0000000..fb7d09f --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/HttpUtils.java @@ -0,0 +1,208 @@ +package com.starry.common.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.*; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; + +/** + * 通用http发送方法 + * + * @author ruoyi + */ +public class HttpUtils { + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) { + return sendGet(url, param, "utf-8"); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) { + if (ret != null && !"".equals(ret.trim())) { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } +} \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/utils/ServletUtils.java b/play-common/src/main/java/com/starry/common/utils/ServletUtils.java new file mode 100644 index 0000000..d59b282 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/ServletUtils.java @@ -0,0 +1,164 @@ +package com.starry.common.utils; + + +import cn.hutool.core.convert.Convert; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +public class ServletUtils { + /** + * 获取String参数 + */ + public static String getParameter(String name) { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) { + try { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } catch (IOException e) { + + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + /*public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + }*/ + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) { + try { + return URLEncoder.encode(str, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) { + try { + return URLDecoder.decode(str, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return StringUtils.EMPTY; + } + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/SpringUtils.java b/play-common/src/main/java/com/starry/common/utils/SpringUtils.java new file mode 100644 index 0000000..989eb33 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/SpringUtils.java @@ -0,0 +1,70 @@ +package com.starry.common.utils; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @author admin + * spring工具类 方便在非spring管理环境中获取bean + * @since 2022/7/28 + */ +@Component +public class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { + + /** + * Spring应用上下文环境 + */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws BeansException + */ + public static T getBean(Class clz) throws BeansException { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws BeansException + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException { + return (T) beanFactory.getBean(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) { + return (T) AopContext.currentProxy(); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringUtils.applicationContext = applicationContext; + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/StringUtils.java b/play-common/src/main/java/com/starry/common/utils/StringUtils.java new file mode 100644 index 0000000..d40a405 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/StringUtils.java @@ -0,0 +1,232 @@ +package com.starry.common.utils; + +import cn.hutool.core.text.StrFormatter; + +import java.util.Collection; +import java.util.Map; + +/** + * @author admin + * 字符串工具类 + * @since 2022/7/19 + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + + /** + * 空字符串 + */ + private static final String NULLSTR = ""; + + /** + * 下划线 + */ + private static final char SEPARATOR = '_'; + + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) { + if (str == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (i > 0) { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } else { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { + sb.append(SEPARATOR); + } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) { + return object == null; + } + + /** + * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) { + return !isNull(object); + } + + /** + * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + * * @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) { + return isNull(objects) || (objects.length == 0); + } + + /** + * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) { + return isNull(map) || map.isEmpty(); + } + + /** + * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } + + /** + * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) { + return !isEmpty(coll); + } + + /** + * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) { + return isNull(coll) || coll.isEmpty(); + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) { + if (isEmpty(params) || isEmpty(template)) { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) { + if (isEmpty(collection) || isEmpty(array)) { + return false; + } else { + for (String str : array) { + if (collection.contains(str)) { + return true; + } + } + return false; + } + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) { + if (org.apache.commons.lang3.StringUtils.isNotBlank(s)) { + // 小写 + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + // 大写 + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '_') { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + return s; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 + * 例如:HELLO_WORLD -> HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + StringBuilder result = new StringBuilder(); + if (org.apache.commons.lang3.StringUtils.isBlank(name)) { + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + public static T cast(Object obj) { + return (T) obj; + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/ThreadsUtils.java b/play-common/src/main/java/com/starry/common/utils/ThreadsUtils.java new file mode 100644 index 0000000..8eb0300 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/ThreadsUtils.java @@ -0,0 +1,62 @@ +package com.starry.common.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.*; + +/** + * @author admin + * 线程相关工具类 + * @since 2022/7/25 + */ +public class ThreadsUtils { + private static final Logger logger = LoggerFactory.getLogger(ThreadsUtils.class); + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) { + if (pool != null && !pool.isShutdown()) { + pool.shutdown(); + try { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { + logger.info("Pool did not terminate"); + } + } + } catch (InterruptedException ie) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) { + if (t == null && r instanceof Future) { + try { + Future future = (Future) r; + if (future.isDone()) { + future.get(); + } + } catch (CancellationException ce) { + t = ce; + } catch (ExecutionException ee) { + t = ee.getCause(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + if (t != null) { + logger.error(t.getMessage(), t); + } + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/file/CosClientUtils.java b/play-common/src/main/java/com/starry/common/utils/file/CosClientUtils.java new file mode 100644 index 0000000..512c0a3 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/file/CosClientUtils.java @@ -0,0 +1,95 @@ +package com.starry.common.utils.file; + + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.PutObjectRequest; +import com.qcloud.cos.model.UploadResult; +import com.qcloud.cos.transfer.TransferManager; +import com.qcloud.cos.transfer.Upload; +import com.starry.common.config.CosConfig; +import com.starry.common.utils.SpringUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @author admin + * 腾讯云COS文件上传工具类 + * @since 2022/10/31 + */ +@Slf4j +public class CosClientUtils { + + /** + * 获取配置信息 + */ + private static final CosConfig cosConfig = SpringUtils.getBean(CosConfig.class); + + public static String upload(MultipartFile file, String dir) throws Exception { + String originalFilename = file.getOriginalFilename(); + // 文件名 + String name = FilenameUtils.getBaseName(originalFilename) + "_" + System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf(".")); + // 目录 + String folderName = cosConfig.getFolderPrefix() + "/" + dir + "/"; + String key = folderName + name; + File localFile = null; + try { + localFile = transferToFile(file); + String filePath = uploadFileToCos(localFile, key); + log.info("upload COS successful: {}", filePath); + return filePath; + } catch (Exception e) { + throw new Exception("文件上传失败"); + } finally { + localFile.delete(); + } + } + + /** + * 用缓冲区来创建临时文件 + * 使用 MultipartFile.transferTo() + * + * @param multipartFile + * @return + */ + private static File transferToFile(MultipartFile multipartFile) throws IOException { + String originalFilename = multipartFile.getOriginalFilename(); + String prefix = originalFilename.split("\\.")[0]; + String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); + File file = File.createTempFile(prefix, suffix); + multipartFile.transferTo(file); + return file; + } + + /** + * 上传文件到COS + * + * @param localFile + * @param key + * @return + */ + private static String uploadFileToCos(File localFile, String key) throws InterruptedException { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosConfig.getBucketName(), key, localFile); + // 获取连接 + COSClient cosClient = cosConfig.getCosClient(); + // 创建线程池 + ThreadPoolExecutor threadPool = new ThreadPoolExecutor(8, 16, + 4, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.AbortPolicy()); + // 传入一个threadPool, 若不传入线程池, 默认TransferManager中会生成一个单线程的线程池 + TransferManager transferManager = new TransferManager(cosClient, threadPool); + // 返回一个异步结果Upload, 可同步的调用waitForUploadResult等待upload结束, 成功返回UploadResult, 失败抛出异常 + Upload upload = transferManager.upload(putObjectRequest); + UploadResult uploadResult = upload.waitForUploadResult(); + transferManager.shutdownNow(); + cosClient.shutdown(); + String filePath = cosConfig.getBaseUrl() + uploadResult.getKey(); + return filePath; + } + +} diff --git a/play-common/src/main/java/com/starry/common/utils/file/FileUploadUtils.java b/play-common/src/main/java/com/starry/common/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..0078293 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/file/FileUploadUtils.java @@ -0,0 +1,33 @@ +package com.starry.common.utils.file; + +import org.springframework.web.multipart.MultipartFile; + +import java.util.Objects; + +/** + * @author admin + * 文件上传工具类 + * @since 2022/10/28 + */ +public class FileUploadUtils { + + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + public static String upload(String baseDir, MultipartFile file, String[] allowedExtension) { + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { + throw new RuntimeException("文件名超过默认的最大长度"); + } + return null; + } + + +} diff --git a/play-common/src/main/java/com/starry/common/utils/html/EscapeUtil.java b/play-common/src/main/java/com/starry/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..fd77857 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/html/EscapeUtil.java @@ -0,0 +1,131 @@ +package com.starry.common.utils.html; + + +import com.starry.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author ruoyi + */ +public class EscapeUtil { + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static { + for (int i = 0; i < 64; i++) { + TEXT[i] = new char[]{(char) i}; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 单引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) { + int len; + if ((text == null) || ((len = text.length()) == 0)) { + return StringUtils.EMPTY; + } + StringBuilder buffer = new StringBuilder(len + (len >> 2)); + char c; + for (int i = 0; i < len; i++) { + c = text.charAt(i); + if (c < 64) { + buffer.append(TEXT[c]); + } else { + buffer.append(c); + } + } + return buffer.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) { + if (StringUtils.isEmpty(content)) { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) { + if (content.charAt(pos + 1) == 'u') { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } else { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } else { + if (pos == -1) { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } else { + tmp.append(content, lastPos, pos); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) { + String html = ""; + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println(EscapeUtil.clean(html)); + System.out.println(EscapeUtil.escape(html)); + System.out.println(EscapeUtil.unescape(html)); + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/html/HTMLFilter.java b/play-common/src/main/java/com/starry/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..f24efac --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/html/HTMLFilter.java @@ -0,0 +1,497 @@ +package com.starry.common.utils.html; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author ruoyi + */ +public final class HTMLFilter { + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[]{"img"}; + vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"}; + vDisallowed = new String[]{}; + vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp. + vProtocolAtts = new String[]{"src", "href"}; + vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"}; + vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"}; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + // --------------------------------------------------------------- + + private static boolean inArray(final String s, final String[] array) { + for (String item : array) { + if (item != null && item.equals(s)) { + return true; + } + } + return false; + } + + private void reset() { + vTagCounts.clear(); + } + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() { + return alwaysMakeTags; + } + + public boolean isStripComments() { + return stripComment; + } + + private String escapeComments(final String s) { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) { + if (alwaysMakeTags) { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } else { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) { + for (int ii = 0; ii < vTagCounts.get(key); ii++) { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) { + String result = s; + for (String tag : vRemoveBlanks) { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private String processTag(final String s) { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) { + if (false == inArray(name, vSelfClosingTags)) { + if (vTagCounts.containsKey(name)) { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) { + if (inArray(paramName, vProtocolAtts)) { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\"").append(paramValue).append("\""); + } + } + + if (inArray(name, vSelfClosingTags)) { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) { + ending = ""; + } + + if (ending == null || ending.isEmpty()) { + if (vTagCounts.containsKey(name)) { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } else { + vTagCounts.put(name, 1); + } + } else { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } else { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) { + final String match = m.group(1); + final int decimal = Integer.decode(match); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) { + if (encodeQuotes) { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } else { + return s; + } + } + + private String checkEntity(final String preamble, final String term) { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) { + return inArray(entity, vAllowedEntities); + } + + private boolean allowed(final String name) { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/utils/http/HttpHelper.java b/play-common/src/main/java/com/starry/common/utils/http/HttpHelper.java new file mode 100644 index 0000000..d8415b8 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/http/HttpHelper.java @@ -0,0 +1,44 @@ +package com.starry.common.utils.http; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 通用http工具封装 + * + * @author ruoyi + */ +public class HttpHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + LOGGER.warn("getBodyString出现问题!"); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/http/HttpUtils.java b/play-common/src/main/java/com/starry/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..a24295f --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/http/HttpUtils.java @@ -0,0 +1,199 @@ +package com.starry.common.utils.http; + +import com.starry.common.constant.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.*; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; + +/** + * 通用http发送方法 + * + * @author ruoyi + */ +public class HttpUtils { + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try { + String urlNameString = url + "?" + param; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) { + if (!ret.trim().isEmpty()) { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } +} \ No newline at end of file diff --git a/play-common/src/main/java/com/starry/common/utils/ip/AddressUtils.java b/play-common/src/main/java/com/starry/common/utils/ip/AddressUtils.java new file mode 100644 index 0000000..136ccf4 --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/ip/AddressUtils.java @@ -0,0 +1,47 @@ +package com.starry.common.utils.ip; + + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.starry.common.utils.HttpUtils; +import com.starry.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * @author admin + * 获取地址工具类 + * @since 2022/7/25 + */ +@Slf4j +public class AddressUtils { + + /** + * IP地址查询 + **/ + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + /** + * 未知地址 + **/ + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIp(String ip) { + if ("127.0.0.1".equals(ip)) { + return "内网IP"; + } else { + try { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", "GBK"); + if (StringUtils.isEmpty(rspStr)) { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } catch (Exception e) { + log.error("获取地理位置异常 {}", ip); + } + return UNKNOWN; + } + } +} diff --git a/play-common/src/main/java/com/starry/common/utils/ip/IpUtils.java b/play-common/src/main/java/com/starry/common/utils/ip/IpUtils.java new file mode 100644 index 0000000..36b38cb --- /dev/null +++ b/play-common/src/main/java/com/starry/common/utils/ip/IpUtils.java @@ -0,0 +1,225 @@ +package com.starry.common.utils.ip; + + +import com.starry.common.utils.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils { + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) { + if (request == null) { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) { + if (StringUtils.isNull(addr) || addr.length < 2) { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) { + return true; + } + case SECTION_5: + switch (b1) { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) { + if (text.isEmpty()) { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try { + long l; + int i; + switch (elements.length) { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } catch (NumberFormatException e) { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) { + if (false == isUnknown(subIp)) { + ip = subIp; + break; + } + } + } + return ip; + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } +} \ No newline at end of file diff --git a/play-generator/pom.xml b/play-generator/pom.xml new file mode 100644 index 0000000..4a47381 --- /dev/null +++ b/play-generator/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + com.starry + play-with + 1.0 + + + play-generator + + + 8 + 8 + UTF-8 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + com.starry + play-common + 1.0 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + \ No newline at end of file diff --git a/play-generator/src/main/java/com/starry/generator/config/GenConfig.java b/play-generator/src/main/java/com/starry/generator/config/GenConfig.java new file mode 100644 index 0000000..603830f --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +package com.starry.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * @author admin + * 读取代码生成相关配置 + * @since 2022/7/15 + */ +@Component +@ConfigurationProperties(prefix = "gen") +@PropertySource(value = {"classpath:generator.yml"}) +public class GenConfig { + + /** + * 作者 + */ + public static String author; + + /** + * 生成包路径 + */ + public static String packageName; + + /** + * 自动去除表前缀,默认是false + */ + public static boolean autoRemovePre; + + /** + * 表前缀(类名不会包含表前缀) + */ + public static String tablePrefix; + + public static String getAuthor() { + return author; + } + + @Value("${author}") + public void setAuthor(String author) { + GenConfig.author = author; + } + + public static String getPackageName() { + return packageName; + } + + @Value("${packageName}") + public void setPackageName(String packageName) { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() { + return autoRemovePre; + } + + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() { + return tablePrefix; + } + + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) { + GenConfig.tablePrefix = tablePrefix; + } +} diff --git a/play-generator/src/main/java/com/starry/generator/constant/GenConstants.java b/play-generator/src/main/java/com/starry/generator/constant/GenConstants.java new file mode 100644 index 0000000..8be220c --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/constant/GenConstants.java @@ -0,0 +1,186 @@ +package com.starry.generator.constant; + +/** + * @author admin + * 代码生成通用常量 + */ +public class GenConstants { + + /** + * 单表(增删改查) + */ + public static final String TPL_CRUD = "crud"; + + /** + * 树表(增删改查) + */ + public static final String TPL_TREE = "tree"; + + /** + * 主子表(增删改查) + */ + public static final String TPL_SUB = "sub"; + + /** + * 树编码字段 + */ + public static final String TREE_CODE = "treeCode"; + + /** + * 树父编码字段 + */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** + * 树名称字段 + */ + public static final String TREE_NAME = "treeName"; + + /** + * 上级菜单ID字段 + */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** + * 上级菜单名称字段 + */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** + * 数据库字符串类型 + */ + public static final String[] COLUMN_TYPE_STR = {"char", "varchar", "nvarchar", "varchar2"}; + + /** + * 数据库文本类型 + */ + public static final String[] COLUMN_TYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"}; + + /** + * 数据库时间类型 + */ + public static final String[] COLUMN_TYPE_TIME = {"datetime", "time", "date", "timestamp"}; + + /** + * 数据库数字类型 + */ + public static final String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal"}; + + /** + * 页面不需要编辑字段 + */ + public static final String[] COLUMN_NAME_NOT_EDIT = {"id", "create_by", "create_time", "del_flag"}; + + /** + * 页面不需要显示的列表字段 + */ + public static final String[] COLUMN_NAME_NOT_LIST = {"id", "create_by", "create_time", "del_flag", "update_by", + "update_time"}; + + /** + * 页面不需要查询字段 + */ + public static final String[] COLUMN_NAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark"}; + + /** + * Entity基类字段 + */ + public static final String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime", "remark"}; + + /** + * Tree基类字段 + */ + public static final String[] TREE_ENTITY = {"parentName", "parentId", "orderNum", "ancestors", "children"}; + + /** + * 文本框 + */ + public static final String HTML_INPUT = "input"; + + /** + * 文本域 + */ + public static final String HTML_TEXTAREA = "textarea"; + + /** + * 下拉框 + */ + public static final String HTML_SELECT = "select"; + + /** + * 单选框 + */ + public static final String HTML_RADIO = "radio"; + + /** + * 复选框 + */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** + * 日期控件 + */ + public static final String HTML_DATETIME = "datetime"; + + /** + * 图片上传控件 + */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** + * 文件上传控件 + */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** + * 富文本控件 + */ + public static final String HTML_EDITOR = "editor"; + + /** + * 字符串类型 + */ + public static final String TYPE_STRING = "String"; + + /** + * 整型 + */ + public static final String TYPE_INTEGER = "Integer"; + + /** + * 长整型 + */ + public static final String TYPE_LONG = "Long"; + + /** + * 浮点型 + */ + public static final String TYPE_DOUBLE = "Double"; + + /** + * 高精度计算类型 + */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** + * 时间类型 + */ + public static final String TYPE_DATE = "Date"; + + /** + * 模糊查询 + */ + public static final String QUERY_LIKE = "LIKE"; + + /** + * 相等查询 + */ + public static final String QUERY_EQ = "EQ"; + + /** + * 需要 + */ + public static final String REQUIRE = "1"; +} diff --git a/play-generator/src/main/java/com/starry/generator/controller/GenController.java b/play-generator/src/main/java/com/starry/generator/controller/GenController.java new file mode 100644 index 0000000..00714c3 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/controller/GenController.java @@ -0,0 +1,96 @@ +package com.starry.generator.controller; + + +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.starry.common.annotation.Log; +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; +import com.starry.generator.entity.GenTableEntity; +import com.starry.generator.entity.vo.GenTableVo; +import com.starry.generator.service.GenTableService; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.io.IOUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + + +/** + * @author admin + * @since 2022/7/14 + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController { + + @Resource + private GenTableService genTableService; + + @ApiOperation(value = "查询数据库列表") + @GetMapping("/db/list") + public R dataList(GenTableEntity genTableEntity) { + IPage list = genTableService.selectDbTableList(genTableEntity); + return R.ok(list); + } + + @ApiOperation(value = "保存表结构") + @PostMapping("/importTable") + public R importTableSave(String tables) { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList); + return R.ok(); + } + + @ApiOperation(value = "查询生成表列表") + @GetMapping("/list") + public R genList(GenTableVo vo) { + return R.ok(genTableService.selectGenTableList(vo)); + } + + @ApiOperation(value = "预览代码") + @GetMapping("/preview/{tableId}") + public R preview(@PathVariable("tableId") Long tableId) { + Map dataMap = genTableService.previewCode(tableId); + return R.ok(dataMap); + } + + @ApiOperation(value = "生成代码(下载方式)") + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @ApiOperation(value = "批量生成代码") + @GetMapping("/download/batch") + public void batchGenCode(HttpServletResponse response, String tables) throws IOException { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + @ApiOperation(value = "删除代码生成") + @DeleteMapping("/{tableIds}") + public R remove(@PathVariable Long[] tableIds) { + genTableService.deleteGenTableByIds(tableIds); + return R.ok(); + } + + private void genCode(HttpServletResponse response, byte[] data) throws IOException { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"admin.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} diff --git a/play-generator/src/main/java/com/starry/generator/entity/GenTableColumnEntity.java b/play-generator/src/main/java/com/starry/generator/entity/GenTableColumnEntity.java new file mode 100644 index 0000000..3217388 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/entity/GenTableColumnEntity.java @@ -0,0 +1,137 @@ +package com.starry.generator.entity; + + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import com.starry.common.utils.StringUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * @author admin + * 代码生成业务字段表 gen_table_column + * @since 2022/7/14 + */ +@Getter +@Setter +@TableName("gen_table_column") +@ApiModel(value = "GenTableColumn对象", description = "代码生成业务表字段") +public class GenTableColumnEntity extends BaseEntity implements Serializable { + + /** + * 编号 + */ + @ApiModelProperty("主键ID") + @TableId(value = "table_id", type = IdType.AUTO) + private Long columnId; + + /** + * 归属表编号 + */ + private Long tableId; + + /** + * 列名称 + */ + private String columnName; + + /** + * 列描述 + */ + private String columnComment; + + /** + * 列类型 + */ + private String columnType; + + /** + * JAVA类型 + */ + private String javaType; + + /** + * JAVA字段名 + */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** + * 是否主键(1是) + */ + private String isPk; + + /** + * 是否自增(1是) + */ + private String isIncrement; + + /** + * 是否必填(1是) + */ + private String isRequired; + + /** + * 是否为插入字段(1是) + */ + private String isInsert; + + /** + * 是否编辑字段(1是) + */ + private String isEdit; + + /** + * 是否列表字段(1是) + */ + private String isList; + + /** + * 是否查询字段(1是) + */ + private String isQuery; + + /** + * 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) + */ + private String queryType; + + /** + * 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) + */ + private String htmlType; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 排序 + */ + private Integer sort; + + public static boolean isSuperColumn(String javaField) { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createdBy", "createdTime", "updatedBy", "updatedTime", "deleted", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isSuperColumn() { + return isSuperColumn(this.javaField); + } + + public String getCapJavaField() { + return StringUtils.capitalize(javaField); + } + +} diff --git a/play-generator/src/main/java/com/starry/generator/entity/GenTableEntity.java b/play-generator/src/main/java/com/starry/generator/entity/GenTableEntity.java new file mode 100644 index 0000000..54390e1 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/entity/GenTableEntity.java @@ -0,0 +1,175 @@ +package com.starry.generator.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.starry.common.domain.BaseEntity; +import com.starry.common.utils.StringUtils; +import com.starry.generator.constant.GenConstants; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * @author admin + * 业务表 gen_table + * @since 2022/7/14 + */ +@Getter +@Setter +@TableName("gen_table") +@ApiModel(value = "GenTable对象", description = "代码生成业务表") +public class GenTableEntity extends BaseEntity { + + @ApiModelProperty("主键ID") + @TableId(value = "table_id", type = IdType.AUTO) + private Long tableId; + + /** + * 表名称 + */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** + * 表描述 + */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** + * 关联父表的表名 + */ + private String subTableName; + + /** + * 本表关联父表的外键名 + */ + private String subTableFkName; + + /** + * 实体类名称(首字母大写) + */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** + * 使用的模板(crud单表操作 tree树表操作 sub主子表操作) + */ + private String tplCategory; + + /** + * 生成包路径 + */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** + * 生成模块名 + */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** + * 生成业务名 + */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** + * 生成功能名 + */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** + * 生成作者 + */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** + * 生成代码方式(0zip压缩包 1自定义路径) + */ + private String genType; + + /** + * 生成路径(不填默认项目路径) + */ + private String genPath; + + /** + * 主键信息 + */ + @TableField(exist = false) + private GenTableColumnEntity pkColumn; + + /** + * 子表信息 + */ + @TableField(exist = false) + private GenTableEntity subTable; + + /** + * 表列信息 + */ + @TableField(exist = false) + private List columns; + + /** + * 其它生成选项 + */ + private String options; + + /** + * 树编码字段 + */ + @TableField(exist = false) + private String treeCode; + + /** + * 树父编码字段 + */ + @TableField(exist = false) + private String treeParentCode; + + /** + * 树名称字段 + */ + @TableField(exist = false) + private String treeName; + + /** + * 上级菜单ID字段 + */ + @TableField(exist = false) + private String parentMenuId; + + /** + * 上级菜单名称字段 + */ + @TableField(exist = false) + private String parentMenuName; + + + public static boolean isCrud(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public static boolean isTree(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() { + return isCrud(this.tplCategory); + } + + public boolean isTree() { + return isTree(this.tplCategory); + } +} diff --git a/play-generator/src/main/java/com/starry/generator/entity/vo/GenTableVo.java b/play-generator/src/main/java/com/starry/generator/entity/vo/GenTableVo.java new file mode 100644 index 0000000..fe0dcfd --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/entity/vo/GenTableVo.java @@ -0,0 +1,155 @@ +package com.starry.generator.entity.vo; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.starry.common.domain.BasePageEntity; +import com.starry.generator.entity.GenTableColumnEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * @author admin + * 业务表 gen_table + * @since 2022/7/14 + */ + +@EqualsAndHashCode(callSuper = true) +@Data +public class GenTableVo extends BasePageEntity { + + @ApiModelProperty("主键ID") + @TableId(value = "table_id", type = IdType.AUTO) + private Long tableId; + + /** + * 表名称 + */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** + * 表描述 + */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** + * 关联父表的表名 + */ + private String subTableName; + + /** + * 本表关联父表的外键名 + */ + private String subTableFkName; + + /** + * 实体类名称(首字母大写) + */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** + * 使用的模板(crud单表操作 tree树表操作 sub主子表操作) + */ + private String tplCategory; + + /** + * 生成包路径 + */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** + * 生成模块名 + */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** + * 生成业务名 + */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** + * 生成功能名 + */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** + * 生成作者 + */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** + * 生成代码方式(0zip压缩包 1自定义路径) + */ + private String genType; + + /** + * 生成路径(不填默认项目路径) + */ + private String genPath; + + /** + * 主键信息 + */ + @TableField(exist = false) + private GenTableColumnEntity pkColumn; + + /** + * 子表信息 + */ + @TableField(exist = false) + private GenTableVo subTable; + + /** + * 表列信息 + */ + @TableField(exist = false) + private List columns; + + /** + * 其它生成选项 + */ + private String options; + + /** + * 树编码字段 + */ + @TableField(exist = false) + private String treeCode; + + /** + * 树父编码字段 + */ + @TableField(exist = false) + private String treeParentCode; + + /** + * 树名称字段 + */ + @TableField(exist = false) + private String treeName; + + /** + * 上级菜单ID字段 + */ + @TableField(exist = false) + private String parentMenuId; + + /** + * 上级菜单名称字段 + */ + @TableField(exist = false) + private String parentMenuName; + +} diff --git a/play-generator/src/main/java/com/starry/generator/mapper/GenTableColumnMapper.java b/play-generator/src/main/java/com/starry/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..b34ef5b --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,28 @@ +package com.starry.generator.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.generator.entity.GenTableColumnEntity; + +import java.util.List; + +/** + *

+ * 代码生成业务表字段 Mapper 接口 + *

+ * + * @author admin + * @since 2022-07-15 + */ +public interface GenTableColumnMapper extends BaseMapper { + + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + @InterceptorIgnore(tenantLine = "1") + List selectDbTableColumnsByName(String tableName); + +} diff --git a/play-generator/src/main/java/com/starry/generator/mapper/GenTableMapper.java b/play-generator/src/main/java/com/starry/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..c3583fb --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/mapper/GenTableMapper.java @@ -0,0 +1,64 @@ +package com.starry.generator.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.generator.entity.GenTableEntity; +import com.starry.generator.entity.vo.GenTableVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author admin + * 业务 数据层 + * @since 2022/7/14 + */ +public interface GenTableMapper extends BaseMapper { + + /** + * 查询业务列表 + * + * @param genTableEntity 业务信息 + * @return 业务集合 + */ + + IPage selectGenTableList(Page page, @Param(value = "genTableEntity") GenTableVo genTableEntity); + + + @InterceptorIgnore(tenantLine = "1") + IPage selectDbTableList(Page page, @Param("genTableEntity") GenTableEntity genTableEntity); + + /** + * 查询数据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @InterceptorIgnore(tenantLine = "1") + List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + GenTableEntity selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + GenTableEntity selectGenTableByName(String tableName); +} diff --git a/play-generator/src/main/java/com/starry/generator/service/GenTableService.java b/play-generator/src/main/java/com/starry/generator/service/GenTableService.java new file mode 100644 index 0000000..b8630aa --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/service/GenTableService.java @@ -0,0 +1,80 @@ +package com.starry.generator.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.generator.entity.GenTableEntity; +import com.starry.generator.entity.vo.GenTableVo; + +import java.util.List; +import java.util.Map; + +/** + * @author admin + * @since 2022/7/14 + */ +public interface GenTableService extends IService { + + /** + * 查询数据库列表 + * + * @param genTableEntity 业务信息 + * @return 数据库表集合 + */ + /*List selectDbTableList(GenTableEntity genTableEntity);*/ + + IPage selectDbTableList(GenTableEntity genTableEntity); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + List selectDbTableListByNames(String[] tableNames); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + void importGenTable(List tableList); + + /** + * 查询业务列表 + * + * @param genTableEntity 业务信息 + * @return 业务集合 + */ + IPage selectGenTableList(GenTableVo genTableEntity); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + byte[] downloadCode(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + byte[] downloadCode(String[] tableNames); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + */ + void deleteGenTableByIds(Long[] tableIds); +} diff --git a/play-generator/src/main/java/com/starry/generator/service/GenTableServiceImpl.java b/play-generator/src/main/java/com/starry/generator/service/GenTableServiceImpl.java new file mode 100644 index 0000000..5e7db07 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/service/GenTableServiceImpl.java @@ -0,0 +1,212 @@ +package com.starry.generator.service; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.common.utils.ServletUtils; +import com.starry.common.utils.StringUtils; +import com.starry.generator.constant.GenConstants; +import com.starry.generator.entity.GenTableColumnEntity; +import com.starry.generator.entity.GenTableEntity; +import com.starry.generator.entity.vo.GenTableVo; +import com.starry.generator.mapper.GenTableColumnMapper; +import com.starry.generator.mapper.GenTableMapper; +import com.starry.generator.utils.GenUtils; +import com.starry.generator.utils.VelocityInitializer; +import com.starry.generator.utils.VelocityUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * @author admin + * @since 2022/7/14 + */ +@Slf4j +@Service +public class GenTableServiceImpl extends ServiceImpl implements GenTableService { + + @Resource + private GenTableColumnMapper genTableColumnMapper; + + /*@Override + public List selectDbTableList(GenTableEntity genTableEntity) { + return baseMapper.selectDbTableList(genTableEntity); + }*/ + @Override + public IPage selectDbTableList(GenTableEntity genTableEntity) { + Page mpPage = new Page(Convert.toLong(ServletUtils.getParameterToInt("pageNum"), 1L), Convert.toLong(ServletUtils.getParameterToInt("pageSize"), 10L)); + return baseMapper.selectDbTableList(mpPage, genTableEntity); + } + + @Override + public List selectDbTableListByNames(String[] tableNames) { + return baseMapper.selectDbTableListByNames(tableNames); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void importGenTable(List tableList) { + if (CollectionUtil.isNotEmpty(tableList)) { + for (GenTableEntity table : tableList) { + String tableName = table.getTableName(); + GenUtils.initTable(table); + // 保存生成表数据 + int row = baseMapper.insert(table); + if (row > 0) { + // 查询数据库表列信息 + List genTableColumnEntities = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (CollectionUtil.isNotEmpty(genTableColumnEntities)) { + for (GenTableColumnEntity column : genTableColumnEntities) { + GenUtils.initColumnField(column, table); + // 保存生成表字段数据 + genTableColumnMapper.insert(column); + } + } + } + } + } + } + + @Override + public IPage selectGenTableList(GenTableVo vo) { + return baseMapper.selectGenTableList(new Page<>(vo.getPageNum(), vo.getPageSize()), vo); + } + + @Override + public Map previewCode(Long tableId) { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTableEntity table = baseMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + // 初始化 + VelocityInitializer.initVelocity(); + // 设置模板变量信息 + VelocityContext context = VelocityUtils.prepareContext(table); + // 获取模版列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, "UTF-8"); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + @Override + public byte[] downloadCode(String tableName) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + @Override + public byte[] downloadCode(String[] tableNames) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteGenTableByIds(Long[] tableIds) { + baseMapper.deleteBatchIds(Arrays.asList(tableIds)); + List genTableColumnEntities = genTableColumnMapper.selectList(new LambdaQueryWrapper().in(GenTableColumnEntity::getTableId, Arrays.asList(tableIds))); + genTableColumnMapper.deleteBatchIds(genTableColumnEntities); + } + + private void generatorCode(String tableName, ZipOutputStream zip) { + // 查询表信息 + GenTableEntity table = baseMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + // 初始化 + VelocityInitializer.initVelocity(); + // 设置模板变量信息 + VelocityContext context = VelocityUtils.prepareContext(table); + // 获取模版列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, "UTF-8"); + tpl.merge(context, sw); + try { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, "UTF-8"); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } catch (IOException e) { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + public void setSubTable(GenTableEntity table) { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotBlank(subTableName)) { + + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTableEntity table) { + for (GenTableColumnEntity column : table.getColumns()) { + if ("1".equals(column.getIsPk())) { + table.setPkColumn(column); + break; + } + if (StringUtils.isNull(table.getPkColumn())) { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) { + for (GenTableColumnEntity columnSub : table.getSubTable().getColumns()) { + if ("1".equals(columnSub.getIsPk())) { + table.getSubTable().setPkColumn(columnSub); + break; + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + } + } +} diff --git a/play-generator/src/main/java/com/starry/generator/utils/GenUtils.java b/play-generator/src/main/java/com/starry/generator/utils/GenUtils.java new file mode 100644 index 0000000..75c6eb5 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/utils/GenUtils.java @@ -0,0 +1,265 @@ +package com.starry.generator.utils; + +import com.starry.generator.config.GenConfig; +import com.starry.generator.constant.GenConstants; +import com.starry.generator.entity.GenTableColumnEntity; +import com.starry.generator.entity.GenTableEntity; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +/** + * @author admin + * 代码生成器 工具类 + * @since 2022/7/15 + */ +public class GenUtils { + + /** + * 初始化表信息 + */ + public static void initTable(GenTableEntity genTableEntity) { + genTableEntity.setClassName(convertClassName(genTableEntity.getTableName())); + genTableEntity.setPackageName(GenConfig.getPackageName()); + genTableEntity.setModuleName(getModuleName(GenConfig.getPackageName())); + genTableEntity.setBusinessName(getBusinessName(genTableEntity.getTableName())); + genTableEntity.setFunctionName(genTableEntity.getTableComment()); + genTableEntity.setFunctionAuthor(GenConfig.getAuthor()); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) { + // 获取是否自动去除表前缀 + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + // 获取表前缀 + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotBlank(tablePrefix)) { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacement 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacement, String[] searchList) { + String text = replacement; + for (String searchString : searchList) { + if (replacement.startsWith(searchString)) { + text = replacement.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 + * 例如:HELLO_WORLD -> HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + StringBuilder result = new StringBuilder(); + if (StringUtils.isBlank(name)) { + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenTableColumnEntity column, GenTableEntity table) { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + // 设置java字段名 + column.setJavaField(toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMN_TYPE_STR, dataType) || arraysContains(GenConstants.COLUMN_TYPE_TEXT, dataType)) { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMN_TYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } else if (arraysContains(GenConstants.COLUMN_TYPE_TIME, dataType)) { + // 时间类型 + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) { + // 数字类型 + column.setHtmlType(GenConstants.HTML_INPUT); + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) { + // 如果是整形 + column.setJavaType(GenConstants.TYPE_INTEGER); + } else { + // 长整形 + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMN_NAME_NOT_EDIT, columnName) && !"1".equals(column.getIsPk())) { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMN_NAME_NOT_LIST, columnName) && !"1".equals(column.getIsPk())) { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMN_NAME_NOT_QUERY, columnName) && !"1".equals(column.getIsPk())) { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 获取数据库类型字段 + * 例如: bigint 、varchar(64) + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) { + if (StringUtils.indexOf(columnType, "(") > 0) { + return StringUtils.substringBefore(columnType, "("); + } else { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) { + if (StringUtils.indexOf(columnType, "(") > 0) { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } else { + return 0; + } + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) { + if (StringUtils.isNotBlank(s)) { + // 小写 + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + // 大写 + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '_') { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + return s; + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) { + return Arrays.asList(arr).contains(targetValue); + } +} diff --git a/play-generator/src/main/java/com/starry/generator/utils/VelocityInitializer.java b/play-generator/src/main/java/com/starry/generator/utils/VelocityInitializer.java new file mode 100644 index 0000000..55f9687 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/utils/VelocityInitializer.java @@ -0,0 +1,22 @@ +package com.starry.generator.utils; + +import org.apache.velocity.app.Velocity; + +import java.util.Properties; + +/** + * @author VelocityEngine工厂 + * @since 2022/7/19 + */ +public class VelocityInitializer { + + public static void initVelocity() { + Properties p = new Properties(); + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, "UTF-8"); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } +} diff --git a/play-generator/src/main/java/com/starry/generator/utils/VelocityUtils.java b/play-generator/src/main/java/com/starry/generator/utils/VelocityUtils.java new file mode 100644 index 0000000..21cfb30 --- /dev/null +++ b/play-generator/src/main/java/com/starry/generator/utils/VelocityUtils.java @@ -0,0 +1,345 @@ +package com.starry.generator.utils; + + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.starry.common.utils.StringUtils; +import com.starry.generator.constant.GenConstants; +import com.starry.generator.entity.GenTableColumnEntity; +import com.starry.generator.entity.GenTableEntity; +import org.apache.velocity.VelocityContext; + +import java.util.*; + +/** + * @author admin + * 模板处理工具类 + * @since 2022/7/19 + */ +public class VelocityUtils { + + /** + * 默认上级菜单,系统工具 + */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 项目空间路径 + */ + private static final String PROJECT_PATH = "main/java"; + /** + * mybatis空间路径 + */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTableEntity genTableEntity) { + String moduleName = genTableEntity.getModuleName(); + String businessName = genTableEntity.getBusinessName(); + String packageName = genTableEntity.getPackageName(); + String tplCategory = genTableEntity.getTplCategory(); + String functionName = genTableEntity.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTableEntity.getTplCategory()); + velocityContext.put("tableName", genTableEntity.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTableEntity.getClassName()); + // 首字母小写 + velocityContext.put("className", StringUtils.uncapitalize(genTableEntity.getClassName())); + velocityContext.put("moduleName", genTableEntity.getModuleName()); + // 首字母大写 + velocityContext.put("BusinessName", StringUtils.capitalize(genTableEntity.getBusinessName())); + velocityContext.put("businessName", genTableEntity.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTableEntity.getFunctionAuthor()); + velocityContext.put("datetime", DateUtil.format(new Date(), "yyyy-MM-dd")); + velocityContext.put("pkColumn", genTableEntity.getPkColumn()); + velocityContext.put("importList", getImportList(genTableEntity)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTableEntity.getColumns()); + velocityContext.put("table", genTableEntity); + velocityContext.put("dicts", getDicts(genTableEntity)); + setMenuVelocityContext(velocityContext, genTableEntity); + if (GenConstants.TPL_TREE.equals(tplCategory)) { + setTreeVelocityContext(velocityContext, genTableEntity); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) { + setSubVelocityContext(velocityContext, genTableEntity); + } + return velocityContext; + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTableEntity 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTableEntity genTableEntity) { + List columns = genTableEntity.getColumns(); + GenTableEntity subGenTableEntity = genTableEntity.getSubTable(); + HashSet importList = new HashSet<>(); + if (StringUtils.isNotNull(subGenTableEntity)) { + importList.add("java.util.List"); + } + for (GenTableColumnEntity column : columns) { + // 不是基础字段 且是 时间类型 + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) { + importList.add("java.util.Date"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 根据列类型获取字典组 + * + * @param genTableEntity 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTableEntity genTableEntity) { + List columns = genTableEntity.getColumns(); + Set dicts = new HashSet<>(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTableEntity.getSubTable())) { + List subColumns = genTableEntity.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) { + for (GenTableColumnEntity column : columns) { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[]{GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX})) { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + public static void setMenuVelocityContext(VelocityContext context, GenTableEntity genTableEntity) { + String options = genTableEntity.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTableEntity genTableEntity) { + String options = genTableEntity.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeCode = getTreeCode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTableEntity)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTableEntity genTableEntity) { + GenTableEntity subTable = genTableEntity.getSubTable(); + String subTableName = genTableEntity.getSubTableName(); + String subTableFkName = genTableEntity.getSubTableFkName(); + String subClassName = genTableEntity.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTableEntity.getSubTable())); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreeCode(JSONObject paramsObj) { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + public static int getExpandColumn(GenTableEntity genTableEntity) { + String options = genTableEntity.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenTableColumnEntity column : genTableEntity.getColumns()) { + if ("1".equals(column.getIsList())) { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) { + break; + } + } + } + return num; + } + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory) { + List templates = new ArrayList<>(); + templates.add("vm/java/entity.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) { + templates.add("vm/vue/index.vue.vm"); + } else if (GenConstants.TPL_TREE.equals(tplCategory)) { + templates.add("vm/vue/index-tree.vue.vm"); + } else if (GenConstants.TPL_SUB.equals(tplCategory)) { + templates.add("vm/vue/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + return templates; + } + + /** + * 获取文件名 + * + * @return 文件名称 + */ + public static String getFileName(String template, GenTableEntity genTableEntity) { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTableEntity.getPackageName(); + // 模块名 + String moduleName = genTableEntity.getModuleName(); + // 大写类名 + String className = genTableEntity.getClassName(); + // 业务名称 + String businessName = genTableEntity.getBusinessName(); + + // 相关生成文件目录 + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String vuePath = "vue"; + + if (template.contains("domain.java.vm")) { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } else if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTableEntity.getTplCategory())) { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTableEntity.getSubTable().getClassName()); + } else if (template.contains("mapper.java.vm")) { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } else if (template.contains("service.java.vm")) { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } else if (template.contains("serviceImpl.java.vm")) { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } else if (template.contains("controller.java.vm")) { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } else if (template.contains("mapper.xml.vm")) { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } else if (template.contains("sql.vm")) { + fileName = businessName + "Menu.sql"; + } else if (template.contains("api.js.vm")) { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } else if (template.contains("index.vue.vm")) { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } else if (template.contains("index-tree.vue.vm")) { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + return fileName; + } +} diff --git a/play-generator/src/main/resources/generator.properties b/play-generator/src/main/resources/generator.properties new file mode 100644 index 0000000..d8ef2f2 --- /dev/null +++ b/play-generator/src/main/resources/generator.properties @@ -0,0 +1,5 @@ +dataSource.url=jdbc:mysql://42.194.222.32:3306/admin?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai +dataSource.driverName=com.mysql.cj.jdbc.Driver +dataSource.username=root +dataSource.password=yx_admin +package.base=com.admin.modules \ No newline at end of file diff --git a/play-generator/src/main/resources/generator.yml b/play-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..4add015 --- /dev/null +++ b/play-generator/src/main/resources/generator.yml @@ -0,0 +1,10 @@ +# 代码生成 +gen: + # 作者 + author: admin + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.starry.modules.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ \ No newline at end of file diff --git a/play-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/play-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..9dc8b36 --- /dev/null +++ b/play-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/play-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..253c481 --- /dev/null +++ b/play-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, + package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, + created_by, created_time, updated_by, updated_time from gen_table + + + + + + + + + + + + \ No newline at end of file diff --git a/play-generator/src/main/resources/vm/java/controller.java.vm b/play-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..c0a5531 --- /dev/null +++ b/play-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,118 @@ +package ${packageName}.controller; + +import com.starry.common.enums.BusinessType; +import com.starry.common.result.R; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.starry.common.annotation.Log; +import org.springframework.security.access.prepost.PreAuthorize; + +import javax.annotation.Resource; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ${packageName}.entity.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +#if($table.crud || $table.sub) +import com.starry.common.page.TableDataInfo; + +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @since ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller { + @Resource + private I${ClassName}Service ${className}Service; + +/** + * 查询${functionName}列表 + */ +@PreAuthorize("@customSs.hasPermi('${permissionPrefix}:list')") +@GetMapping("/list") + #if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } + #elseif($table.tree) + public R list(${ClassName} ${className}) { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return R.ok(list); + } + #end + + /** + * 导出${functionName}列表 + */ + @PreAuthorize("@customSs.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}. class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @PreAuthorize("@customSs.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public R getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) { + return R.ok(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @PreAuthorize("@customSs.hasPermi('${permissionPrefix}:create')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping("/create") + public R create(@RequestBody ${ClassName} ${className}) { + boolean success = ${className}Service.create(${className}); + if (success) { + return R.ok(); + } + return R.error("添加失败"); + } + + /** + * 修改${functionName} + */ + @PreAuthorize("@customSs.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PostMapping(value = "/update/{${pkColumn.javaField}}") + public R update(@PathVariable ${pkColumn.javaType} ${pkColumn.javaField}, @RequestBody ${ClassName} ${className}) { + ${className}.set${pkColumn.javaField}(${pkColumn.javaField}); + boolean success = ${className}Service.update(${className}); + if (success) { + return R.ok(); + } + return R.error("修改失败"); + } + + /** + * 删除${functionName} + */ + @PreAuthorize("@customSs.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public R remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) { + return R.ok(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/play-generator/src/main/resources/vm/java/entity.java.vm b/play-generator/src/main/resources/vm/java/entity.java.vm new file mode 100644 index 0000000..a953774 --- /dev/null +++ b/play-generator/src/main/resources/vm/java/entity.java.vm @@ -0,0 +1,66 @@ +package ${packageName}.entity; + +#foreach ($import in $importList) +import ${import}; +#end +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.Date; +#if($table.crud || $table.sub) +import com.starry.common.domain.BaseEntity; +#elseif($table.tree) +import com.starry.common.domain.TreeEntity; +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @since ${datetime} + */ +#if($table.crud || $table.sub) + #set($Entity="BaseEntity") +#elseif($table.tree) + #set($Entity="TreeEntity") +#end +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("$table.tableName") +public class ${ClassName} extends ${Entity}{ + +private static final long serialVersionUID = 1L; + + #foreach ($column in $columns) + #if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ + #if($column.list) + #set($parentheseIndex=$column.columnComment.indexOf("(")) + #if($parentheseIndex != -1) + #set($comment=$column.columnComment.substring(0, $parentheseIndex)) + #else + #set($comment=$column.columnComment) + #end + #if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") + #elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") + #else + @Excel(name = "${comment}") + #end + #end + private $column.javaType $column.javaField; + + #end + #end + #if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + + #end + +} diff --git a/play-generator/src/main/resources/vm/java/mapper.java.vm b/play-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..fb5240d --- /dev/null +++ b/play-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,50 @@ +package ${packageName}.mapper; + +import java.util.List; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import ${packageName}.entity.${ClassName}; +#if($table.sub) +import ${packageName}.entity .${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @since ${datetime} + */ +public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> { + /** + * 查询${functionName} + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + #if($table.sub) + + /** + * 批量删除${subTable.functionName} + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + #end +} diff --git a/play-generator/src/main/resources/vm/java/service.java.vm b/play-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..f2e153a --- /dev/null +++ b/play-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,58 @@ +package ${packageName}.service; + +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import ${packageName}.entity .${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @since ${datetime} + */ +public interface I${ClassName}Service extends IService<${ClassName}> { + /** + * 查询${functionName} + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * @param ${className} ${functionName} + * @return 结果 + */ + boolean create(${ClassName} ${className}); + + /** + * 修改${functionName} + * @param ${className} ${functionName} + * @return 结果 + */ + boolean update(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/play-generator/src/main/resources/vm/java/serviceImpl.java.vm b/play-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..b351013 --- /dev/null +++ b/play-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,143 @@ +package ${packageName}.service.impl; + +import java.util.List; +import java.util.Arrays; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Service; + #if($table.sub) + import java.util.ArrayList; + + import com.starry.common.utils.StringUtils; + import org.springframework.transaction.annotation.Transactional; + import ${packageName}.entity.${subClassName}; + #end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.entity.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @since ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}> implements I${ClassName}Service { + @Resource + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * @param ${className} ${functionName} + * @return 结果 + */ + #if($table.sub) + @Transactional(rollbackFor = Exception.class) + #end + @Override + public boolean create(${ClassName} ${className}) { + #if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; + #else + return save(${className}); + #end + } + + /** + * 修改${functionName} + * @param ${className} ${functionName} + * @return 结果 + */ + #if($table.sub) + @Transactional(rollbackFor = Exception.class) + #end + @Override + public boolean update(${ClassName} ${className}) { + #if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()) + ; + insert${subClassName}(${className}); + #end + return updateById(${className}); + } + + /** + * 批量删除${functionName} + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ + #if($table.sub) + @Transactional(rollbackFor = Exception.class) + #end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) { + #if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); + #end + return ${className}Mapper.deleteBatchIds(Arrays.asList(${pkColumn.javaField}s)); + } + + /** + * 删除${functionName}信息 + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + #if($table.sub) + @Transactional(rollbackFor = Exception.class) + #end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { + #if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); + #end + return ${className}Mapper.deleteById(${pkColumn.javaField}); + } + #if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} :${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (!list.isEmpty()) { + ${className}Mapper.batch${subClassName}(list); + } + } + } + #end +} diff --git a/play-generator/src/main/resources/vm/js/api.js.vm b/play-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9d1b799 --- /dev/null +++ b/play-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}/create', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(${pkColumn.javaField}, data) { + return request({ + url: '/${moduleName}/${businessName}/update/' + ${pkColumn.javaField}, + method: 'post', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/play-generator/src/main/resources/vm/sql/sql.vm b/play-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..a312b63 --- /dev/null +++ b/play-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, sort, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:create', '#', 'admin', sysdate(), '', null); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null); \ No newline at end of file diff --git a/play-generator/src/main/resources/vm/vue/index-tree.vue.vm b/play-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..e69d1ba --- /dev/null +++ b/play-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,572 @@ + + + diff --git a/play-generator/src/main/resources/vm/vue/index.vue.vm b/play-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 0000000..980ddea --- /dev/null +++ b/play-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,751 @@ + + + diff --git a/play-generator/src/main/resources/vm/xml/mapper.xml.vm b/play-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..2c1164c --- /dev/null +++ b/play-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,120 @@ + + + + + + #foreach ($column in $columns) + + #end + + #if($table.sub) + + + + + + + #foreach ($column in $subTable.columns) + + #end + + #end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end + from ${tableName} + + + + + + + + #if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName} + (#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()) + ,#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField + }#if($foreach.count != $subTable.columns.size()),#end#end) + + + #end + \ No newline at end of file diff --git a/play-weichat/pom.xml b/play-weichat/pom.xml new file mode 100644 index 0000000..c6f0bc9 --- /dev/null +++ b/play-weichat/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + com.starry + play-with + 1.0 + + + play-weichat + + + 1.8 + 1.8 + UTF-8 + + + + + + + com.github.binarywang + weixin-java-common + + + + com.github.binarywang + weixin-java-mp + + + + com.github.binarywang + weixin-java-miniapp + + + + + com.github.binarywang + weixin-java-pay + + + guava + com.google.guava + + + + + + com.starry + play-common + 1.0 + + + cos_api + com.qcloud + + + + + + \ No newline at end of file diff --git a/play-weichat/src/main/java/com/starry/weichat/builder/AbstractBuilder.java b/play-weichat/src/main/java/com/starry/weichat/builder/AbstractBuilder.java new file mode 100644 index 0000000..2b38db7 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/builder/AbstractBuilder.java @@ -0,0 +1,13 @@ +package com.starry.weichat.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author admin + */ +public abstract class AbstractBuilder { + + public abstract WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, WxMpService service); +} diff --git a/play-weichat/src/main/java/com/starry/weichat/builder/ImageBuilder.java b/play-weichat/src/main/java/com/starry/weichat/builder/ImageBuilder.java new file mode 100644 index 0000000..e7c6f97 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/builder/ImageBuilder.java @@ -0,0 +1,22 @@ + +package com.starry.weichat.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author admin + */ +public class ImageBuilder extends AbstractBuilder { + + @Override + public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, + WxMpService service) { + + return WxMpXmlOutMessage.IMAGE().mediaId(content) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/builder/TextBuilder.java b/play-weichat/src/main/java/com/starry/weichat/builder/TextBuilder.java new file mode 100644 index 0000000..9a6382f --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/builder/TextBuilder.java @@ -0,0 +1,23 @@ + +package com.starry.weichat.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage; + +/** + * @author admin + */ +public class TextBuilder extends AbstractBuilder { + + @Override + public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, + WxMpService service) { + WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + return m; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/config/CommonConstants.java b/play-weichat/src/main/java/com/starry/weichat/config/CommonConstants.java new file mode 100644 index 0000000..970f76a --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/CommonConstants.java @@ -0,0 +1,25 @@ + +package com.starry.weichat.config; + +/** + * @author admin + */ +public interface CommonConstants { + /** + * 是 + */ + String YES = "1"; + /** + * 否 + */ + String NO = "0"; + + /** + * 树形父类ID + */ + String PARENT_ID = "0"; + /** + * 编码 + */ + String UTF8 = "UTF-8"; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/config/WebConfig.java b/play-weichat/src/main/java/com/starry/weichat/config/WebConfig.java new file mode 100644 index 0000000..d61c604 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/WebConfig.java @@ -0,0 +1,36 @@ +package com.starry.weichat.config; + +import com.starry.weichat.interceptor.ThirdSessionInterceptor; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * web配置 + * + * @author admin + */ +@Configuration +@AllArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + /** + * 拦截器 + * + * @param registry InterceptorRegistry + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + /* + 进入ThirdSession拦截器 + */ + registry.addInterceptor(new ThirdSessionInterceptor()) + // 拦截/api/**接口 + .addPathPatterns("/weixin/api/**") + // 放行接口 + .excludePathPatterns("/weixin/api/ma/wxuser/login", + "/weixin/api/ma/orderinfo/notify-order", "/weixin/api/ma/orderinfo/notify-logisticsr", + "/weixin/api/ma/orderinfo/notify-refunds"); + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/config/WxMaConfiguration.java b/play-weichat/src/main/java/com/starry/weichat/config/WxMaConfiguration.java new file mode 100644 index 0000000..3570f65 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/WxMaConfiguration.java @@ -0,0 +1,142 @@ +package com.starry.weichat.config; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; +import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.message.WxMaMessageHandler; +import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author admin + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(WxMaProperties.class) +public class WxMaConfiguration { + private static final Map routers = Maps.newHashMap(); + private static Map maServices; + private final WxMaProperties properties; + private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder() + .templateId("此处更换为自己的模板id") + .data(Lists.newArrayList( + new WxMaSubscribeMessage.MsgData("keyword1", "339208499"))) + .toUser(wxMessage.getFromUser()) + .build()); + return null; + }; + private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到消息:" + wxMessage.toString()); + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson()) + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息") + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> { + try { + WxMediaUploadResult uploadResult = service.getMediaService() + .uploadMedia("image", "png", + ClassLoader.getSystemResourceAsStream("tmp.png")); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + + } + + return null; + }; + private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> { + try { + final File file = service.getQrcodeService().createQrcode("123", 430); + WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + + } + + return null; + }; + + @Autowired + public WxMaConfiguration(WxMaProperties properties) { + this.properties = properties; + } + + public static WxMaService getMaService(String appId) { + WxMaService wxService = maServices.get(appId); + if (wxService == null) { + throw new IllegalArgumentException(String.format("未找到对应appId=[%s]的配置,请核实!", appId)); + } + + return wxService; + } + + public static WxMaMessageRouter getRouter(String appId) { + return routers.get(appId); + } + + @PostConstruct + public void init() { + List configs = this.properties.getConfigs(); + if (configs == null) { + throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + + maServices = configs.stream() + .map(a -> { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + // 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常 + config.setAppid(a.getAppId()); + config.setSecret(a.getSecret()); + config.setToken(a.getToken()); + config.setAesKey(a.getAesKey()); + config.setMsgDataFormat(a.getMsgDataFormat()); + + WxMaService service = new WxMaServiceImpl(); + service.setWxMaConfig(config); + routers.put(a.getAppId(), this.newRouter(service)); + return service; + }).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a)); + } + + private WxMaMessageRouter newRouter(WxMaService service) { + final WxMaMessageRouter router = new WxMaMessageRouter(service); + router + .rule().handler(logHandler).next() + .rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end() + .rule().async(false).content("文本").handler(textHandler).end() + .rule().async(false).content("图片").handler(picHandler).end() + .rule().async(false).content("二维码").handler(qrcodeHandler).end(); + return router; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/config/WxMaProperties.java b/play-weichat/src/main/java/com/starry/weichat/config/WxMaProperties.java new file mode 100644 index 0000000..b7cbaaf --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/WxMaProperties.java @@ -0,0 +1,58 @@ + +package com.starry.weichat.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * @author admin + */ +@Data +@ConfigurationProperties(prefix = "wx.ma") +public class WxMaProperties { + + private List configs; + + @Data + public static class Config { + /** + * 设置微信小程序的appid + */ + private String appId; + + /** + * 设置微信小程序的Secret + */ + private String secret; + + /** + * 设置微信小程序消息服务器配置的token + */ + private String token; + + /** + * 设置微信小程序消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat; + /** + * 微信支付商户号 + */ + private String mchId; + /** + * 微信支付商户密钥 + */ + private String mchKey; + /** + * p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头) + */ + private String keyPath; + } + +} \ No newline at end of file diff --git a/play-weichat/src/main/java/com/starry/weichat/config/WxMpConfiguration.java b/play-weichat/src/main/java/com/starry/weichat/config/WxMpConfiguration.java new file mode 100644 index 0000000..3c72a57 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/WxMpConfiguration.java @@ -0,0 +1,112 @@ + +package com.starry.weichat.config; + + +import com.starry.weichat.handler.*; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.stream.Collectors; + +import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY; + +/** + * wechat mp configuration + * + * @author admin + */ +@AllArgsConstructor +@Configuration +@EnableConfigurationProperties(WxMpProperties.class) +public class WxMpConfiguration { + private final LogHandler logHandler; + private final NullHandler nullHandler; + private final KfSessionHandler kfSessionHandler; + private final StoreCheckNotifyHandler storeCheckNotifyHandler; + private final LocationHandler locationHandler; + private final MenuHandler menuHandler; + private final MsgHandler msgHandler; + private final UnsubscribeHandler unsubscribeHandler; + private final SubscribeHandler subscribeHandler; + private final ScanHandler scanHandler; + private final WxMpProperties properties; + + @Bean + public WxMpService wxMpService() { + // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! + final List configs = this.properties.getConfigs(); + if (configs == null) { + throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + + WxMpService service = new WxMpServiceImpl(); + service.setMultiConfigStorages(configs + .stream().map(a -> { + WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl(); + configStorage.setAppId(a.getAppId()); + configStorage.setSecret(a.getSecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAesKey()); + return configStorage; + }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o))); + return service; + } + + @Bean + public WxMpMessageRouter messageRouter(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + + // 记录所有事件的日志 (异步执行) + newRouter.rule().handler(this.logHandler).next(); + + // 接收客服会话管理事件 + newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION) + .handler(this.kfSessionHandler).end(); + + // 门店审核事件 + newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end(); + + // 自定义菜单事件 + newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.CLICK).handler(this.menuHandler).end(); + + // 点击菜单连接事件 + newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.VIEW).handler(this.nullHandler).end(); + + // 关注事件 + newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); + + // 取消关注事件 + newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); + + // 上报地理位置事件 + newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end(); + + // 接收地理位置消息 + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end(); + + // 扫码事件 + newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end(); + + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); + + return newRouter; + } + +} \ No newline at end of file diff --git a/play-weichat/src/main/java/com/starry/weichat/config/WxMpProperties.java b/play-weichat/src/main/java/com/starry/weichat/config/WxMpProperties.java new file mode 100644 index 0000000..320585d --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/WxMpProperties.java @@ -0,0 +1,46 @@ +package com.starry.weichat.config; + +import com.starry.weichat.utils.JsonUtils; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * wechat mp properties + * + * @author admin + */ +@Data +@ConfigurationProperties(prefix = "wx.mp") +public class WxMpProperties { + private List configs; + + @Override + public String toString() { + return JsonUtils.toJson(this); + } + + @Data + public static class MpConfig { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + } +} \ No newline at end of file diff --git a/play-weichat/src/main/java/com/starry/weichat/config/WxPayConfiguration.java b/play-weichat/src/main/java/com/starry/weichat/config/WxPayConfiguration.java new file mode 100644 index 0000000..7a31641 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/config/WxPayConfiguration.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2018-2019 + * All rights reserved, Designed By admin + * 注意: + * + */ +package com.starry.weichat.config; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +/** + * 微信支付Configuration + * + * @author admin + */ +@Slf4j +@Configuration +public class WxPayConfiguration { + + private static WxMaProperties wxMaProperties; + + @Autowired + public WxPayConfiguration(WxMaProperties wxMaProperties) { + WxPayConfiguration.wxMaProperties = wxMaProperties; + } + + /** + * 获取WxMpService + * + * @return + */ + public static WxPayService getPayService() { + WxPayService wxPayService = null; + WxPayConfig payConfig = new WxPayConfig(); + payConfig.setAppId(wxMaProperties.getConfigs().get(0).getAppId()); + payConfig.setMchId(wxMaProperties.getConfigs().get(0).getMchId()); + payConfig.setMchKey(wxMaProperties.getConfigs().get(0).getMchKey()); + payConfig.setKeyPath(wxMaProperties.getConfigs().get(0).getKeyPath()); + // 可以指定是否使用沙箱环境 + payConfig.setUseSandboxEnv(false); + wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(payConfig); + return wxPayService; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/constant/ConfigConstant.java b/play-weichat/src/main/java/com/starry/weichat/constant/ConfigConstant.java new file mode 100644 index 0000000..3c1e202 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/constant/ConfigConstant.java @@ -0,0 +1,85 @@ + +package com.starry.weichat.constant; + +/** + * 全局常量 + * + * @author admin + * 2019年1月21日 + */ +public interface ConfigConstant { + + // 订阅状态(0:已订阅;1:未订阅;2:网页授权用户) + /** + * 0:未订阅,取消订阅 + */ + String SUBSCRIBE_TYPE_NO = "0"; + /** + * 1:已订阅 + */ + String SUBSCRIBE_TYPE_YES = "1"; + /** + * 2:网页授权用户 + */ + String SUBSCRIBE_TYPE_WEBLIEN = "2"; + + /** + * 应用类型 1:小程序 + */ + String WX_APP_TYPE_1 = "1"; + /** + * 应用类型 2:公众号 + */ + String WX_APP_TYPE_2 = "2"; + + /** + * 消息自动回复类型(1、关注时回复;2、消息回复;3、关键词回复) + */ + String WX_AUTO_REPLY_TYPE_1 = "1"; + String WX_AUTO_REPLY_TYPE_2 = "2"; + String WX_AUTO_REPLY_TYPE_3 = "3"; + + /** + * 回复类型文本匹配类型(1、全匹配,2、半匹配) + */ + String WX_REP_MATE_1 = "1"; + String WX_REP_MATE_2 = "2"; + + /** + * 消息分类(1、用户发给公众号;2、公众号发给用户;) + */ + String WX_MSG_TYPE_1 = "1"; + String WX_MSG_TYPE_2 = "2"; + + /** + * 群发消息发送类型(1、分组发;2、选择用户发) + */ + String WX_MASS_SEND_TYPE_1 = "1"; + String WX_MASS_SEND_TYPE_2 = "2"; + + /** + * 群发消息发送后的状态(SUB_SUCCESS:提交成功,SUB_FAIL:提交失败,SEND_SUCCESS:发送成功,SENDING:发送中,SEND_FAIL:发送失败,DELETE:已删除) + */ + String WX_MASS_STATUS_SUB_SUCCESS = "SUB_SUCCESS"; + String WX_MASS_STATUS_SUB_FAIL = "SUB_FAIL"; + String WX_MASS_STATUS_SEND_SUCCESS = "SEND_SUCCESS"; + String WX_MASS_STATUS_SENDING = "SENDING"; + String WX_MASS_STATUS_SEND_FAIL = "SEND_FAIL"; + String WX_MASS_STATUS_DELETE = "DELETE"; + + /** + * 菜单类型(1:普通菜单,2:个性化菜单) + */ + String WX_MENU_TYPE_1 = "1"; + String WX_MENU_TYPE_2 = "2"; + + /** + * header中的app-id + */ + String HEADER_APP_ID = "app-id"; + + /** + * header中的third-session + */ + String HEADER_THIRDSESSION = "third-session"; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/constant/MyReturnCode.java b/play-weichat/src/main/java/com/starry/weichat/constant/MyReturnCode.java new file mode 100644 index 0000000..4c27df8 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/constant/MyReturnCode.java @@ -0,0 +1,57 @@ + +package com.starry.weichat.constant; + +/** + * 全局返回码 + * 小程序用6开头,例60001 + * + * @author admin + * 2019年7月25日 + */ +public enum MyReturnCode { + + // 其它错误 + ERR_60000(60000, "系统错误,请稍候再试") {}, + ERR_60001(60001, "登录超时,请重新登录") {}, + ERR_60002(60002, "session不能为空") {}, + + ERR_70001(70001, "该状态订单不允许操作") {}, + ERR_70002(70002, "请选择付款方式") {}, + ERR_70003(70003, "没有符合下单条件的规格商品,商品已下架或库存不足") {}, + ERR_70004(70004, "只有未支付的详单能发起支付") {}, + ERR_70005(70005, "无效订单") {}, + + ERR_80004(80004, "该商品已删除") {}, + + ; + + private int code; + private String msg; + + MyReturnCode(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return "MyReturnCode{" + "code='" + code + '\'' + "msg='" + msg + '\'' + '}'; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/constant/WebSocketConstant.java b/play-weichat/src/main/java/com/starry/weichat/constant/WebSocketConstant.java new file mode 100644 index 0000000..14196e3 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/constant/WebSocketConstant.java @@ -0,0 +1,12 @@ + +package com.starry.weichat.constant; + +/** + * @author admin + */ +public interface WebSocketConstant { + + String USER_DESTINATION_PREFIX = "/weixin/"; + String WX_MSG = "wx_msg"; + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/constant/WxMaConstants.java b/play-weichat/src/main/java/com/starry/weichat/constant/WxMaConstants.java new file mode 100644 index 0000000..5ad54b3 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/constant/WxMaConstants.java @@ -0,0 +1,17 @@ + +package com.starry.weichat.constant; + +/** + * @author admin + */ +public interface WxMaConstants { + + /** + * redis中3rd_session过期时间(单位:小时) + */ + long TIME_OUT_SESSION = 6; + /** + * redis中3rd_session拼接前缀 + */ + String THIRD_SESSION_BEGIN = "wx:ma:3rd_session"; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/constant/WxReturnCode.java b/play-weichat/src/main/java/com/starry/weichat/constant/WxReturnCode.java new file mode 100644 index 0000000..f4cc73d --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/constant/WxReturnCode.java @@ -0,0 +1,57 @@ +package com.starry.weichat.constant; + +/** + * 微信接口全局返回码 + * + * @author admin + */ + +@SuppressWarnings("") +public enum WxReturnCode { + + SUC_0("0", "请求成功") {}, ERR_1("-1", "系统繁忙,此时请开发者稍候再试") {}, ERR_10001("10001", "涉嫌广告 ") {}, ERR_20001("20001", "涉嫌政治 ") {}, ERR_20002("20002", "涉嫌色情 ") {}, ERR_20004("20004", "涉嫌社会 ") {}, ERR_20006("20006", "涉嫌违法犯罪 ") {}, ERR_20008("20008", "涉嫌欺诈 ") {}, ERR_20013("20013", "涉嫌版权 ") {}, ERR_21000("21000", "涉嫌其他 ") {}, ERR_22000("22000", "涉嫌互推(互相宣传) ") {}, ERR_30001("30001", "原创校验出现系统错误且用户选择了被判为转载就不群发 ") {}, ERR_30002("30002", "原创校验被判定为不能群发 ") {}, ERR_30003("30003", "原创校验被判定为转载文且用户选择了被判为转载就不群发 ") {}, ERR_40001("40001", "获取access_token时AppSecret错误,或者access_token无效。请开发者认真比对AppSecret的正确性,或查看是否正在为恰当的公众号调用接口") {}, ERR_40002("40002", "不合法的凭证类型") {}, ERR_40003("40003", "不合法的OpenID,请开发者确认OpenID(该用户)是否已关注公众号,或是否是其他公众号的OpenID") {}, ERR_40004("40004", "不合法的媒体文件类型") {}, ERR_40005("40005", "不合法的文件类型") {}, ERR_40006("40006", "不合法的文件大小") {}, ERR_40007("40007", "不合法的媒体文件id") {}, ERR_40008("40008", "不合法的消息类型") {}, ERR_40009("40009", "不合法的图片文件大小") {}, ERR_40010("40010", "不合法的语音文件大小") {}, ERR_40011("40011", "不合法的视频文件大小") {}, ERR_40012("40012", "不合法的缩略图文件大小") {}, ERR_40013("40013", "不合法的AppID,请开发者检查AppID的正确性,避免异常字符,注意大小写") {}, ERR_40014("40014", "不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口") {}, ERR_40015("40015", "不合法的菜单类型") {}, ERR_40016("40016", "不合法的按钮个数") {}, ERR_40017("40017", "不合法的按钮个数") {}, ERR_40018("40018", "不合法的按钮名字长度") {}, ERR_40019("40019", "不合法的按钮KEY长度") {}, ERR_40020("40020", "不合法的按钮URL长度") {}, ERR_40021("40021", "不合法的菜单版本号") {}, ERR_40022("40022", "不合法的子菜单级数") {}, ERR_40023("40023", "不合法的子菜单按钮个数") {}, ERR_40024("40024", "不合法的子菜单按钮类型") {}, ERR_40025("40025", "不合法的子菜单按钮名字长度") {}, ERR_40026("40026", "不合法的子菜单按钮KEY长度") {}, ERR_40027("40027", "不合法的子菜单按钮URL长度") {}, ERR_40028("40028", "不合法的自定义菜单使用用户") {}, ERR_40029("40029", "不合法的oauth_code") {}, ERR_40030("40030", "不合法的refresh_token") {}, ERR_40031("40031", "不合法的openid列表") {}, ERR_40032("40032", "不合法的openid列表个数") {}, ERR_40033("40033", "不合法的请求字符,不能包含xxxx格式的字符") {}, ERR_40035("40035", "不合法的参数") {}, ERR_40055("40055", "不完整的url,前面要加http://") {}, ERR_40037("40037", "template_id不正确") {}, ERR_40038("40038", "不合法的请求格式") {}, ERR_40039("40039", "不合法的URL长度") {}, ERR_40050("40050", "不合法的分组id") {}, ERR_40051("40051", "分组名字不合法") {}, ERR_40062("40062", "标题长度不合法") {}, ERR_40097("40097", "参数不合法") {}, ERR_40113("40113", "文件名称不合法,需包含正确后缀") {}, ERR_40117("40117", "分组名字不合法") {}, ERR_40118("40118", "media_id大小不合法") {}, ERR_40119("40119", "button类型错误") {}, ERR_40120("40120", "button类型错误") {}, ERR_40121("40121", "不合法的media_id类型") {}, ERR_40125("40125", "不合法的AppSecret,请开发者检查AppSecret的正确性,避免异常字符,注意大小写") {}, ERR_40130("40130", "至少需要同时发送两个用户") {}, ERR_40132("40132", "微信号不合法") {}, ERR_40137("40137", "不支持的图片格式") {}, ERR_40164("40164", "调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置") {}, ERR_41001("41001", "缺少access_token参数") {}, ERR_41002("41002", "缺少appid参数") {}, ERR_41003("41003", "缺少refresh_token参数") {}, ERR_41004("41004", "缺少secret参数") {}, ERR_41005("41005", "缺少多媒体文件数据") {}, ERR_41006("41006", "缺少media_id参数") {}, ERR_41007("41007", "缺少子菜单数据") {}, ERR_41008("41008", "缺少oauth code") {}, ERR_41009("41009", "缺少openid") {}, ERR_41028("41028", "form_id不正确,或者过期") {}, ERR_41029("41029", "form_id已被使用") {}, ERR_41030("41030", "page不正确") {}, ERR_42001("42001", "access_token超时,请检查access_token的有效期,请参考基础支持-获取access_token中,对access_token的详细机制说明") {}, ERR_42002("42002", "refresh_token超时") {}, ERR_42003("42003", "oauth_code超时") {}, ERR_43001("43001", "需要GET请求") {}, ERR_43002("43002", "需要POST请求") {}, ERR_43003("43003", "需要HTTPS请求") {}, ERR_43004("43004", "需要接收者关注") {}, ERR_43005("43005", "需要好友关系") {}, ERR_44001("44001", "多媒体文件为空") {}, ERR_44002("44002", "POST的数据包为空") {}, ERR_44003("44003", "图文消息内容为空") {}, ERR_44004("44004", "文本消息内容为空") {}, ERR_45001("45001", "多媒体文件大小超过限制") {}, ERR_45002("45002", "消息内容超过限制") {}, ERR_45003("45003", "标题字段超过限制") {}, ERR_45004("45004", "描述字段超过限制") {}, ERR_45005("45005", "链接字段超过限制") {}, ERR_45006("45006", "图片链接字段超过限制") {}, ERR_45007("45007", "语音播放时间超过限制") {}, ERR_45008("45008", "图文消息超过限制") {}, ERR_45009("45009", "接口调用超过限制") {}, ERR_45010("45010", "创建菜单个数超过限制") {}, ERR_45015("45015", "回复时间超过限制") {}, ERR_45016("45016", "系统分组,不允许修改") {}, ERR_45017("45017", "分组名字过长") {}, ERR_45018("45018", "分组数量超过上限") {}, ERR_45028("45028", "没有群发的配额,配额已经用完") {}, ERR_45047("45047", "客服下行消息超过上限") {}, ERR_45157("45157", "标签名非法,请注意不能和其他标签重名") {}, ERR_45158("45158", "标签名长度超过30个字节") {}, ERR_45056("45056", "创建的标签数过多,请注意不能超过100个") {}, ERR_45058("45058", "不能修改0/1/2这三个系统默认保留的标签") {}, ERR_45057("45057", "该标签下粉丝数超过10w,不允许直接删除") {}, ERR_45059("45059", "有粉丝身上的标签数已经超过限制") {}, ERR_45159("45159", "非法的tag_id") {}, ERR_46001("46001", "不存在媒体数据") {}, ERR_46002("46002", "不存在的菜单版本") {}, ERR_46003("46003", "不存在的菜单数据") {}, ERR_46004("46004", "不存在的用户") {}, ERR_47001("47001", "解析JSON/XML内容错误") {}, ERR_48001("48001", "api功能未授权,请确认公众号已获得该接口,可以在公众平台官网-开发者中心页中查看接口权限") {}, ERR_48002("48002", "粉丝拒收消息(粉丝在公众号选项中,关闭了 “ 接收消息 ” )") {}, ERR_48004("48004", "api 接口被封禁,请登录 admin.weixin.qq.com 查看详情") {}, ERR_48005("48005", "api 禁止删除被自动回复和自定义菜单引用的素材") {}, ERR_48006("48006", "api 禁止清零调用次数,因为清零次数达到上限") {}, ERR_48008("48008", "没有该类型消息的发送权限") {}, ERR_49003("49003", "传入的openid不属于此AppID") {}, ERR_50001("50001", "用户未授权该api") {}, ERR_50002("50002", "用户受限,可能是违规后接口被封禁") {}, ERR_50005("50005", "用户未关注公众号") {}, ERR_61003("61003", "请确认是否取消授权(第三方平台授权)") {}, ERR_61004("61004", "当前ip未在白名单中,直接获取本地ip添加") {}, ERR_61005("61005", " 组件 ticket已失效,重新接受授权url反馈的ticket") {}, ERR_61006("61006", "获取componentTicket为null") {}, ERR_61007("61007", "当前公众号或者小程序已在公众平台解绑") {}, ERR_61009("61009", "授权码失效,重新授权") {}, ERR_61451("61451", "参数错误(invalid parameter)") {}, ERR_61452("61452", "无效客服账号(invalid kf_account)") {}, ERR_61453("61453", "客服帐号已存在(kf_account exsited)") {}, ERR_61454("61454", "客服帐号名长度超过限制(仅允许10个英文字符,不包括@及@后的公众号的微信号)(invalid kf_acount length)") {}, ERR_61455("61455", "客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account)") {}, ERR_61456("61456", "客服帐号个数超过限制(10个客服账号)(kf_account count exceeded)") {}, ERR_61457("61457", "无效头像文件类型(invalid file type)") {}, ERR_61450("61450", "系统错误(system error)") {}, ERR_61500("61500", "日期格式错误") {}, ERR_61501("61501", "日期范围错误") {}, ERR_65400("65400", "API不可用,即没有开通/升级到新版客服功能") {}, ERR_65401("65401", "无效客服帐号") {}, ERR_65403("65403", "客服昵称不合法") {}, ERR_65404("65404", "客服帐号不合法") {}, ERR_65405("65405", "帐号数目已达到上限,不能继续添加") {}, ERR_65406("65406", "已经存在的客服帐号") {}, ERR_65407("65407", "邀请对象已经是该公众号客服") {}, ERR_65408("65408", "本公众号已经有一个邀请给该微信") {}, ERR_65409("65409", "无效的微信号") {}, ERR_65410("65410", "邀请对象绑定公众号客服数达到上限(目前每个微信号可以绑定5个公众号客服帐号)") {}, ERR_65411("65411", "该帐号已经有一个等待确认的邀请,不能重复邀请") {}, ERR_65412("65412", "该帐号已经绑定微信号,不能进行邀请") {}, ERR_99999("99999", "无法获取到文件名") {}, ERR_9001001("9001001", "POST数据参数不合法") {}, ERR_9001002("9001002", "远端服务不可用") {}, ERR_9001003("9001003", "Ticket不合法") {}, ERR_9001004("9001004", "获取摇周边用户信息失败") {}, ERR_9001005("9001005", "获取商户信息失败") {}, ERR_9001006("9001006", "获取OpenID失败") {}, ERR_9001007("9001007", "上传文件缺失") {}, ERR_9001008("9001008", "上传素材的文件类型不合法") {}, ERR_9001009("9001009", "上传素材的文件尺寸不合法") {}, ERR_9001010("9001010", "上传失败") {}, ERR_9001020("9001020", "帐号不合法") {}, ERR_9001021("9001021", "已有设备激活率低于50%,不能新增设备") {}, ERR_9001022("9001022", "设备申请数不合法,必须为大于0的数字") {}, ERR_9001023("9001023", "已存在审核中的设备ID申请") {}, ERR_9001024("9001024", "一次查询设备ID数量不能超过50") {}, ERR_9001025("9001025", "设备ID不合法") {}, ERR_9001026("9001026", "页面ID不合法") {}, ERR_9001027("9001027", "页面参数不合法") {}, ERR_9001028("9001028", "一次删除页面ID数量不能超过10") {}, ERR_9001029("9001029", "页面已应用在设备中,请先解除应用关系再删除") {}, ERR_9001030("9001030", "一次查询页面ID数量不能超过50") {}, ERR_9001031("9001031", "时间区间不合法") {}, ERR_9001032("9001032", "保存设备与页面的绑定关系参数错误") {}, ERR_9001033("9001033", "门店ID不合法") {}, ERR_9001034("9001034", "设备备注信息过长") {}, ERR_9001035("9001035", "设备申请参数不合法") {}, ERR_9001036("9001036", "查询起始值begin不合法") {}; + + private String code; + private String msg; + + WxReturnCode(String code, String msg) { + this.code = code; + this.msg = msg; + } + + /** + * 通过code获取msg + * + * @param code + * @return + */ + public static String getMsg(String code) { + try { + return WxReturnCode.valueOf(code).getMsg(); + } catch (IllegalArgumentException e) { + return "未定义的返回码:" + code; + } + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return "WxReturnCode{" + "code='" + code + '\'' + "msg='" + msg + '\'' + '}'; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxAutoReplyController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxAutoReplyController.java new file mode 100644 index 0000000..c3623f0 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxAutoReplyController.java @@ -0,0 +1,127 @@ + +package com.starry.weichat.controller; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.common.result.R; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.entity.WxAutoReply; +import com.starry.weichat.service.WxAutoReplyService; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 消息自动回复 + * + * @author admin + * @since 2019-04-18 15:40:39 + */ +@RestController +@AllArgsConstructor +@RequestMapping("/wxautoreply") +public class WxAutoReplyController { + + @Resource + WxAutoReplyService wxAutoReplyService; + + /** + * 分页查询 + * @param page 分页对象 + * @param wxAutoReply 消息自动回复 + * @return + */ + @GetMapping("/page") + @PreAuthorize("@customSs.hasPermi('wxmp:wxautoreply:index')") + public R getWxAutoReplyPage(Page page, WxAutoReply wxAutoReply) { + return R.ok(wxAutoReplyService.page(page,Wrappers.query(wxAutoReply))); + } + + + /** + * 通过id查询消息自动回复 + * @param id id + * @return R + */ + @GetMapping("/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxautoreply:get')") + public R getById(@PathVariable("id") String id){ + return R.ok(wxAutoReplyService.getById(id)); + } + + /** + * 新增消息自动回复 + * @param wxAutoReply 消息自动回复 + * @return R + */ + @PostMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxautoreply:add')") + public R save(@RequestBody WxAutoReply wxAutoReply){ + this.jude(wxAutoReply); + return R.ok(wxAutoReplyService.save(wxAutoReply)); + } + + /** + * 修改消息自动回复 + * @param wxAutoReply 消息自动回复 + * @return R + */ + @PutMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxautoreply:edit')") + public R updateById(@RequestBody WxAutoReply wxAutoReply){ + this.jude(wxAutoReply); + return R.ok(wxAutoReplyService.updateById(wxAutoReply)); + } + + /** + * 通过id删除消息自动回复 + * @param id id + * @return R + */ + @DeleteMapping("/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxautoreply:del')") + public R removeById(@PathVariable String id){ + return R.ok(wxAutoReplyService.removeById(id)); + } + + /** + * //校验参数 + * @param wxAutoReply + */ + public void jude(WxAutoReply wxAutoReply){ + if(ConfigConstant.WX_AUTO_REPLY_TYPE_2.equals(wxAutoReply.getType())){ + Wrapper queryWrapper = Wrappers.lambdaQuery() + .eq(WxAutoReply::getReqType,wxAutoReply.getReqType()); + List list = wxAutoReplyService.list(queryWrapper); + if(StringUtils.isNotBlank(wxAutoReply.getId())){ + if(list != null && list.size() == 1){ + if(!list.get(0).getId().equals(wxAutoReply.getId())){ + throw new RuntimeException("请求消息类型重复"); + } + } + if(list != null && list.size()>1){ + throw new RuntimeException("请求消息类型重复"); + } + } + } + if(ConfigConstant.WX_AUTO_REPLY_TYPE_3.equals(wxAutoReply.getType())){ + Wrapper queryWrapper = Wrappers.lambdaQuery() + .eq(WxAutoReply::getReqKey,wxAutoReply.getReqKey()) + .eq(WxAutoReply::getRepType,wxAutoReply.getRepMate()); + List list = wxAutoReplyService.list(queryWrapper); + if(list != null && list.size() == 1){ + if(!list.get(0).getId().equals(wxAutoReply.getId())){ + throw new RuntimeException("关键词重复"); + } + } + if(list != null && list.size()>1){ + throw new RuntimeException("关键词重复"); + } + } + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxDraftController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxDraftController.java new file mode 100644 index 0000000..3ae72cc --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxDraftController.java @@ -0,0 +1,129 @@ + +package com.starry.weichat.controller; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.common.result.R; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpDraftService; +import me.chanjar.weixin.mp.api.WxMpFreePublishService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.draft.WxMpAddDraft; +import me.chanjar.weixin.mp.bean.draft.WxMpDraftArticles; +import me.chanjar.weixin.mp.bean.draft.WxMpUpdateDraft; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 微信草稿箱 + * + * @author admin + * @since 2022-03-10 21:26:35 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxdraft") +@Api(value = "wxdraft", tags = "微信草稿箱") +public class WxDraftController { + + @Resource + WxMpService wxService; + + /** + * 新增图文消息 + * + * @param data + * @return + */ + @ApiOperation(value = "新增草稿箱") + @PostMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxdraft:add')") + public R add(@RequestBody JSONObject data) throws Exception { + JSONArray jSONArray = data.getJSONArray("articles"); + List articles = jSONArray.toList(WxMpDraftArticles.class); + WxMpAddDraft wxMpAddDraft = new WxMpAddDraft(); + wxMpAddDraft.setArticles(articles); + WxMpDraftService wxMpDraftService = wxService.getDraftService(); + String rs = wxMpDraftService.addDraft(wxMpAddDraft); + return R.ok(rs); + } + + /** + * 修改微信草稿箱 + * + * @param data + * @return + */ + @ApiOperation(value = "修改微信草稿箱") + @PutMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxdraft:edit')") + public R edit(@RequestBody JSONObject data) throws Exception { + String mediaId = data.getStr("mediaId"); + JSONArray jSONArray = data.getJSONArray("articles"); + List articles = jSONArray.toList(WxMpDraftArticles.class); + WxMpDraftService wxMpDraftService = wxService.getDraftService(); + WxMpUpdateDraft wxMpUpdateDraft = new WxMpUpdateDraft(); + wxMpUpdateDraft.setMediaId(mediaId); + int index = 0; + for (WxMpDraftArticles article : articles) { + wxMpUpdateDraft.setIndex(index); + wxMpUpdateDraft.setArticles(article); + wxMpDraftService.updateDraft(wxMpUpdateDraft); + index++; + } + return R.ok(); + } + + /** + * 通过id删除微信草稿箱 + * + * @param + * @return R + */ + @ApiOperation(value = "通过id删除微信草稿箱") + @DeleteMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxdraft:del')") + public R del(String id) throws Exception { + WxMpDraftService wxMpDraftService = wxService.getDraftService(); + return R.ok(wxMpDraftService.delDraft(id)); + } + + /** + * 分页查询 + * + * @param page 分页对象 + * @param + * @return + */ + @ApiOperation(value = "分页查询") + @GetMapping("/page") + @PreAuthorize("@customSs.hasPermi('wxmp:wxdraft:index')") + public R getPage(Page page) throws Exception { + WxMpDraftService wxMpDraftService = wxService.getDraftService(); + int count = (int) page.getSize(); + int offset = (int) page.getCurrent() * count - count; + return R.ok(wxMpDraftService.listDraft(offset, count)); + } + + /** + * 发布草稿箱 + * @param id + * @return + */ + @ApiOperation(value = "发布草稿箱") + @PostMapping("/publish/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxdraft:publish')") + public R publish(@PathVariable String id) throws Exception { + WxMpFreePublishService wxMpFreePublishService = wxService.getFreePublishService(); + wxMpFreePublishService.submit(id); + return R.ok(); + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxFreePublishController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxFreePublishController.java new file mode 100644 index 0000000..a067005 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxFreePublishController.java @@ -0,0 +1,67 @@ + +package com.starry.weichat.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.common.result.R; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpFreePublishService; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 微信发布 + * + * @author admin + * @since 2022-03-10 21:26:35 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/freepublish") +@Api(value = "freepublish", tags = "微信发布") +public class WxFreePublishController { + + @Resource + WxMpService wxService; + + /** + * 删除发布 + * + * @param + * @return R + */ + @ApiOperation(value = "删除发布") + @DeleteMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxfreepublish:del')") + public R del(String id) throws Exception { + WxMpFreePublishService wxMpFreePublishService = wxService.getFreePublishService(); + return R.ok(wxMpFreePublishService.deletePushAllArticle(id)); + } + + /** + * 获取成功发布列表 + * + * @param page 获取成功发布列表 + * @param + * @return + */ + @ApiOperation(value = "获取成功发布列表") + @GetMapping("/page") + @PreAuthorize("@customSs.hasPermi('wxmp:wxfreepublish:index')") + public R getPage(Page page) throws Exception { + WxMpFreePublishService wxMpFreePublishService = wxService.getFreePublishService(); + int count = (int) page.getSize(); + int offset = (int) page.getCurrent() * count - count; + return R.ok(wxMpFreePublishService.getPublicationRecords(offset, count)); + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxMaterialController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxMaterialController.java new file mode 100644 index 0000000..a716036 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxMaterialController.java @@ -0,0 +1,239 @@ + +package com.starry.weichat.controller; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.common.result.R; +import com.starry.weichat.entity.ImageManager; +import com.starry.weichat.utils.FileUtils; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpMaterialService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult; +import me.chanjar.weixin.mp.bean.material.WxMpMaterial; +import me.chanjar.weixin.mp.bean.material.WxMpMaterialFileBatchGetResult; +import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 微信素材 + * + * @author admin + * @since 2019-03-23 21:26:35 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxmaterial") +public class WxMaterialController { + + @Resource + WxMpService wxService; + + /** + * 上传非图文微信素材 + * @param mulFile + * @param mediaType + * @return + */ + @PostMapping("/materialFileUpload") + // @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:add')") + public R materialFileUpload(@RequestParam("file") MultipartFile mulFile, + @RequestParam("title") String title, + @RequestParam("introduction") String introduction, + @RequestParam("mediaType") String mediaType) { + try { + WxMpMaterial material = new WxMpMaterial(); + material.setName(mulFile.getOriginalFilename()); + if(WxConsts.MediaFileType.VIDEO.equals(mediaType)){ + material.setVideoTitle(title); + material.setVideoIntroduction(introduction); + } + File file = FileUtils.multipartFileToFile(mulFile); + material.setFile(file); + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + WxMpMaterialUploadResult wxMpMaterialUploadResult = wxMpMaterialService.materialFileUpload(mediaType,material); + WxMpMaterialFileBatchGetResult.WxMaterialFileBatchGetNewsItem wxMpMaterialFileBatchGetResult = new WxMpMaterialFileBatchGetResult.WxMaterialFileBatchGetNewsItem(); + wxMpMaterialFileBatchGetResult.setName(file.getName()); + wxMpMaterialFileBatchGetResult.setMediaId(wxMpMaterialUploadResult.getMediaId()); + wxMpMaterialFileBatchGetResult.setUrl(wxMpMaterialUploadResult.getUrl()); + return R.ok(wxMpMaterialFileBatchGetResult); + } catch (WxErrorException e) { + log.error("上传非图文微信素材失败" + e); + return R.error(e.getMessage()); + } catch (Exception e) { + log.error("上传失败", e); + return R.error(e.getLocalizedMessage()); + } + } + + /** + * 上传图文消息内的图片获取URL + * @param mulFile + * @return + */ + @PostMapping("/newsImgUpload") + // @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:add')") + public String newsImgUpload(@RequestParam("file") MultipartFile mulFile) throws Exception { + File file = FileUtils.multipartFileToFile(mulFile); + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + WxMediaImgUploadResult wxMediaImgUploadResult = wxMpMaterialService.mediaImgUpload(file); + Map responseData = new HashMap<>(); + responseData.put("link", wxMediaImgUploadResult.getUrl()); + return JSONUtil.toJsonStr(responseData); + } + + /** + * 通过id删除微信素材 + * @param + * @return R + */ + @DeleteMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:del')") + public R materialDel(String id){ + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + try { + return R.ok(wxMpMaterialService.materialDelete(id)); + } catch (WxErrorException e) { + + log.error("删除微信素材失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 分页查询 + * @param page 分页对象 + * @param type + * @return + */ + @GetMapping("/page") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:index')") + public R getWxMaterialPage(Page page, String type) { + try { + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + int count = (int)page.getSize(); + int offset = (int)page.getCurrent()*count-count; + if(WxConsts.MaterialType.NEWS.equals(type)){ + return R.ok(wxMpMaterialService.materialNewsBatchGet(offset,count)); + }else{ + return R.ok(wxMpMaterialService.materialFileBatchGet(type,offset,count)); + } + } catch (WxErrorException e) { + + log.error("查询素材失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 分页查询2 + * @param type + * @return + */ + @GetMapping("/page-manager") +// @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:index')") + public String getWxMaterialPageManager(Integer count, Integer offset, String type) throws WxErrorException { + List listImageManager = new ArrayList<>(); + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + List list = wxMpMaterialService.materialFileBatchGet(type,offset,count).getItems(); + list.forEach(wxMaterialFileBatchGetNewsItem -> { + ImageManager imageManager = new ImageManager(); + imageManager.setName(wxMaterialFileBatchGetNewsItem.getMediaId()); + imageManager.setUrl(wxMaterialFileBatchGetNewsItem.getUrl()); + imageManager.setThumb(wxMaterialFileBatchGetNewsItem.getUrl()); + listImageManager.add(imageManager); + }); + return JSONUtil.toJsonStr(listImageManager); + } + + /** + * 获取微信视频素材 + * @param + * @return R + */ + @GetMapping("/materialVideo") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:get')") + public R getMaterialVideo(String mediaId){ + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + try { + return R.ok(wxMpMaterialService.materialVideoInfo(mediaId)); + } catch (WxErrorException e) { + + log.error("获取微信视频素材失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 获取微信素材直接文件 + * @param + * @return R + */ + @GetMapping("/materialOther") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmaterial:get')") + public ResponseEntity getMaterialOther(String mediaId, String fileName) throws Exception { + try { + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + //获取文件 + InputStream is = wxMpMaterialService.materialImageOrVoiceDownload(mediaId); + byte[] body = new byte[is.available()]; + is.read(body); + HttpHeaders headers = new HttpHeaders(); + //设置文件类型 + headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(fileName, "UTF-8")); + headers.add("Content-Type", "application/octet-stream"); + HttpStatus statusCode = HttpStatus.OK; + //返回数据 + return new ResponseEntity<>(body, headers, statusCode); + } catch (WxErrorException e) { + + log.error("获取微信素材直接文件失败", e); + return null; + } + } + + /** + * 获取微信临时素材直接文件 + * @param + * @return R + */ + @GetMapping("/tempMaterialOther") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmsg:index')") + public ResponseEntity getTempMaterialOther(String mediaId, String fileName) throws Exception { + WxMpMaterialService wxMpMaterialService = wxService.getMaterialService(); + try (InputStream is = Files.newInputStream(wxMpMaterialService.mediaDownload(mediaId).toPath())){ + byte[] body = new byte[is.available()]; + is.read(body); + HttpHeaders headers = new HttpHeaders(); + //设置文件类型 + headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(fileName, "UTF-8")); + headers.add("Content-Type", "application/octet-stream"); + HttpStatus statusCode = HttpStatus.OK; + //返回数据 + return new ResponseEntity<>(body, headers, statusCode); + } catch (WxErrorException e) { + + log.error("获取微信素材直接文件失败", e); + return null; + } + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxMenuController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxMenuController.java new file mode 100644 index 0000000..b943997 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxMenuController.java @@ -0,0 +1,64 @@ + +package com.starry.weichat.controller; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.starry.common.result.R; +import com.starry.weichat.service.WxMenuService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 自定义菜单 + * + * @author admin + * @since 2019-03-27 16:52:10 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxmenu") +public class WxMenuController { + + @Resource + WxMenuService wxMenuService; + + /** + * 通过appId查询自定义菜单 + * + * @return R + */ + @GetMapping("/list") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmenu:get')") + public R getWxMenuButton() { + return R.ok(wxMenuService.getWxMenuButton()); + } + + /** + * 保存并发布菜单 + * + * @param + * @return R + */ + @PostMapping("/release") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmenu:add')") + public R saveAndRelease(@RequestBody String data) { + JSONObject jSONObject = JSONUtil.parseObj(data); + String strWxMenu = jSONObject.getStr("strWxMenu"); + String appId = jSONObject.getStr("appId"); + try { + wxMenuService.saveAndRelease(strWxMenu); + return R.ok(); + } catch (WxErrorException e) { + + log.error("发布自定义菜单失败appID:" + appId + ":" + e.getMessage()); + return R.error(e.getMessage()); + } + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxMsgController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxMsgController.java new file mode 100644 index 0000000..9cc3057 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxMsgController.java @@ -0,0 +1,210 @@ + +package com.starry.weichat.controller; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.common.result.R; +import com.starry.weichat.config.CommonConstants; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.entity.WxMsg; +import com.starry.weichat.entity.WxMsgVO; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.service.WxMsgService; +import com.starry.weichat.service.WxUserService; +import io.swagger.annotations.Api; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpKefuService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 微信消息 + * + * @author admin + * @since 2019-05-28 16:12:10 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxmsg") +@Api(value = "wxmsg", tags = "wxmsg管理") +public class WxMsgController { + + @Resource + WxMsgService wxMsgService; + + @Resource + WxUserService wxUserService; + + @Resource + WxMpService wxService; + + /** + * 分页查询 + * + * @param page 分页对象 + * @param wxMsgVO 微信消息 + * @return + */ + @GetMapping("/page") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmsg:index')") + public R getWxMsgPage(Page page, WxMsgVO wxMsgVO) { + if (StringUtils.isNotBlank(wxMsgVO.getNotInRepType())) { + return R.ok(wxMsgService.listWxMsgMapGroup(page, wxMsgVO)); + } + //标记为已读 + if (StringUtils.isNotBlank(wxMsgVO.getWxUserId())) { + WxMsg wxMsg = new WxMsg(); + wxMsg.setReadFlag(CommonConstants.YES); + Wrapper queryWrapper = Wrappers.lambdaQuery().eq(WxMsg::getWxUserId, wxMsgVO.getWxUserId()).eq(WxMsg::getReadFlag, CommonConstants.NO); + wxMsgService.update(wxMsg, queryWrapper); + } + return R.ok(wxMsgService.page(page, Wrappers.query(wxMsgVO))); + } + + /** + * 通过id查询微信消息 + * + * @param id id + * @return R + */ + @GetMapping("/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmsg:get')") + public R getById(@PathVariable("id") String id) { + return R.ok(wxMsgService.getById(id)); + } + + /** + * 新增微信消息 + * + * @param wxMsg 微信消息 + * @return R + */ + @PostMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxmsg:add')") + public R save(@RequestBody WxMsg wxMsg) { + try { + WxUser wxUser = wxUserService.getById(wxMsg.getWxUserId()); + //入库 + wxMsg.setNickName(wxUser.getNickName()); + wxMsg.setHeadimgUrl(wxUser.getHeadimgUrl()); + wxMsg.setCreateTime(LocalDateTime.now()); + wxMsg.setType(ConfigConstant.WX_MSG_TYPE_2); + WxMpKefuMessage wxMpKefuMessage = null; + if (WxConsts.KefuMsgType.TEXT.equals(wxMsg.getRepType())) { + wxMsg.setRepContent(wxMsg.getRepContent()); + wxMpKefuMessage = WxMpKefuMessage.TEXT().build(); + wxMpKefuMessage.setContent(wxMsg.getRepContent()); + } + if (WxConsts.KefuMsgType.IMAGE.equals(wxMsg.getRepType())) {//图片 + wxMsg.setRepName(wxMsg.getRepName()); + wxMsg.setRepUrl(wxMsg.getRepUrl()); + wxMsg.setRepMediaId(wxMsg.getRepMediaId()); + wxMpKefuMessage = WxMpKefuMessage.IMAGE().build(); + wxMpKefuMessage.setMediaId(wxMsg.getRepMediaId()); + } + if (WxConsts.KefuMsgType.VOICE.equals(wxMsg.getRepType())) { + wxMsg.setRepName(wxMsg.getRepName()); + wxMsg.setRepUrl(wxMsg.getRepUrl()); + wxMsg.setRepMediaId(wxMsg.getRepMediaId()); + wxMpKefuMessage = WxMpKefuMessage.VOICE().build(); + wxMpKefuMessage.setMediaId(wxMsg.getRepMediaId()); + } + if (WxConsts.KefuMsgType.VIDEO.equals(wxMsg.getRepType())) { + wxMsg.setRepName(wxMsg.getRepName()); + wxMsg.setRepDesc(wxMsg.getRepDesc()); + wxMsg.setRepUrl(wxMsg.getRepUrl()); + wxMsg.setRepMediaId(wxMsg.getRepMediaId()); + wxMpKefuMessage = WxMpKefuMessage.VIDEO().build(); + wxMpKefuMessage.setMediaId(wxMsg.getRepMediaId()); + wxMpKefuMessage.setTitle(wxMsg.getRepName()); + wxMpKefuMessage.setDescription(wxMsg.getRepDesc()); + } + if (WxConsts.KefuMsgType.MUSIC.equals(wxMsg.getRepType())) { + wxMsg.setRepName(wxMsg.getRepName()); + wxMsg.setRepDesc(wxMsg.getRepDesc()); + wxMsg.setRepUrl(wxMsg.getRepUrl()); + wxMsg.setRepHqUrl(wxMsg.getRepHqUrl()); + wxMpKefuMessage = WxMpKefuMessage.MUSIC().build(); + wxMpKefuMessage.setTitle(wxMsg.getRepName()); + wxMpKefuMessage.setDescription(wxMsg.getRepDesc()); + wxMpKefuMessage.setMusicUrl(wxMsg.getRepUrl()); + wxMpKefuMessage.setHqMusicUrl(wxMsg.getRepHqUrl()); + wxMpKefuMessage.setThumbMediaId(wxMsg.getRepThumbMediaId()); + } + if (WxConsts.KefuMsgType.NEWS.equals(wxMsg.getRepType())) { + List list = new ArrayList<>(); + JSONArray jSONArray = wxMsg.getContent().getJSONArray("articles"); + WxMpKefuMessage.WxArticle t; + for (Object object : jSONArray) { + JSONObject jSONObject = JSONUtil.parseObj(JSONUtil.toJsonStr(object)); + t = new WxMpKefuMessage.WxArticle(); + t.setTitle(jSONObject.getStr("title")); + t.setDescription(jSONObject.getStr("digest")); + t.setPicUrl(jSONObject.getStr("thumbUrl")); + t.setUrl(jSONObject.getStr("url")); + list.add(t); + } + wxMsg.setRepName(wxMsg.getRepName()); + wxMsg.setRepDesc(wxMsg.getRepDesc()); + wxMsg.setRepUrl(wxMsg.getRepUrl()); + wxMsg.setRepMediaId(wxMsg.getRepMediaId()); + wxMsg.setContent(wxMsg.getContent()); + wxMpKefuMessage = WxMpKefuMessage.NEWS().build(); + wxMpKefuMessage.setArticles(list); + } + if (wxMpKefuMessage != null) { + WxMpKefuService wxMpKefuService = wxService.getKefuService(); + wxMpKefuMessage.setToUser(wxUser.getOpenId()); + wxMpKefuService.sendKefuMessage(wxMpKefuMessage); + wxMsgService.save(wxMsg); + return R.ok(wxMsg); + } else { + return R.error("非法消息类型"); + } + } catch (WxErrorException e) { + log.error("新增微信消息失败" + e.getMessage()); + return R.error(e.getMessage()); + } + } + + /** + * 修改微信消息 + * + * @param wxMsg 微信消息 + * @return R + */ + @PutMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxmsg:edit')") + public R updateById(@RequestBody WxMsg wxMsg) { + return R.ok(wxMsgService.updateById(wxMsg)); + } + + /** + * 通过id删除微信消息 + * + * @param id id + * @return R + */ + @DeleteMapping("/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxmsg:del')") + public R removeById(@PathVariable String id) { + return R.ok(wxMsgService.removeById(id)); + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxPortalController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxPortalController.java new file mode 100644 index 0000000..15546e0 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxPortalController.java @@ -0,0 +1,112 @@ + +package com.starry.weichat.controller; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author admin + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("/weixin/portal/{appid}") +public class WxPortalController { + + @Resource + private final WxMpService wxService; + + @Resource + private final WxMpMessageRouter messageRouter; + + @GetMapping(produces = "text/plain;charset=utf-8") + public String authGet(@PathVariable String appid, + @RequestParam(name = "signature", required = false) String signature, + @RequestParam(name = "timestamp", required = false) String timestamp, + @RequestParam(name = "nonce", required = false) String nonce, + @RequestParam(name = "echostr", required = false) String echostr) { + + log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, + timestamp, nonce, echostr); + if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { + throw new IllegalArgumentException("请求参数非法,请核实!"); + } + + if (!this.wxService.switchover(appid)) { + throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); + } + + if (wxService.checkSignature(timestamp, nonce, signature)) { + return echostr; + } + + return "非法请求"; + } + + @PostMapping(produces = "application/xml; charset=UTF-8") + public String post(@PathVariable String appid, + @RequestBody String requestBody, + @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("openid") String openid, + @RequestParam(name = "encrypt_type", required = false) String encType, + @RequestParam(name = "msg_signature", required = false) String msgSignature) { + log.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," + + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", + openid, signature, encType, msgSignature, timestamp, nonce, requestBody); + + if (!this.wxService.switchover(appid)) { + throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); + } + + if (!wxService.checkSignature(timestamp, nonce, signature)) { + throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); + } + + String out = null; + if (encType == null) { + // 明文传输的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toXml(); + } else if ("aes".equalsIgnoreCase(encType)) { + // aes加密的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), + timestamp, nonce, msgSignature); + log.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); + } + + log.debug("\n组装回复信息:{}", out); + return out; + } + + private WxMpXmlOutMessage route(WxMpXmlMessage message) { + try { + return this.messageRouter.route(message); + } catch (Exception e) { + log.error("路由消息时出现异常!", e); + } + + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxSummaryController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxSummaryController.java new file mode 100644 index 0000000..b34946a --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxSummaryController.java @@ -0,0 +1,109 @@ + +package com.starry.weichat.controller; + +import com.starry.common.result.R; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpDataCubeService; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; + +/** + * 微信账号配置 + * + * @author admin + * @since 2019-03-23 21:26:35 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxsummary") +public class WxSummaryController { + + @Resource + WxMpService wxService; + + /** + * 获取用户增减数据 + * + * @param appId + * @param startDate + * @param endDate + * @return + */ + @GetMapping("/usersummary") +// @PreAuthorize("@customSs.hasPermi('wxmp:wxsummary:index')") + public R getUsersummary(String appId, String startDate, String endDate) { + try { + WxMpDataCubeService wxMpDataCubeService = wxService.getDataCubeService(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return R.ok(wxMpDataCubeService.getUserSummary(sdf.parse(startDate), sdf.parse(endDate))); + } catch (WxErrorException e) { + + log.error("获取用户增减数据失败", e); + return R.error(e.getMessage()); + } catch (Exception e) { + + log.error("获取用户增减数据失败", e); + return R.error("获取用户增减数据失败"); + } + } + + /** + * 获取累计用户数据 + * + * @param appId + * @param startDate + * @param endDate + * @return + */ + @GetMapping("/usercumulate") +// @PreAuthorize("@customSs.hasPermi('wxmp:wxsummary:index')") + public R getUserCumulate(String appId, String startDate, String endDate) { + try { + WxMpDataCubeService wxMpDataCubeService = wxService.getDataCubeService(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return R.ok(wxMpDataCubeService.getUserCumulate(sdf.parse(startDate), sdf.parse(endDate))); + } catch (WxErrorException e) { + + log.error("获取累计用户数据失败", e); + return R.error(e.getMessage()); + } catch (Exception e) { + + log.error("获取用户增减数据失败", e); + return R.error("获取用户增减数据失败"); + } + } + + /** + * 获取接口分析数据 + * + * @param appId + * @param startDate + * @param endDate + * @return + */ + @GetMapping("/interfacesummary") +// @PreAuthorize("@customSs.hasPermi('wxmp:wxsummary:index')") + public R getInterfaceSummary(String appId, String startDate, String endDate) { + try { + WxMpDataCubeService wxMpDataCubeService = wxService.getDataCubeService(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return R.ok(wxMpDataCubeService.getInterfaceSummary(sdf.parse(startDate), sdf.parse(endDate))); + } catch (WxErrorException e) { + + log.error("获取接口分析数据失败", e); + return R.error(e.getMessage()); + } catch (Exception e) { + + log.error("获取接口分析数据失败", e); + return R.error("获取接口分析数据失败"); + } + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxUserController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxUserController.java new file mode 100644 index 0000000..ae5f447 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxUserController.java @@ -0,0 +1,175 @@ + +package com.starry.weichat.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.common.result.R; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.service.WxUserService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 微信用户 + * + * @author admin + * @since 2019-03-25 15:39:39 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxuser") +public class WxUserController { + + @Resource + WxUserService wxUserService; + + /** + * 分页查询 + * + * @param page 分页对象 + * @param wxUser 微信用户 + * @return + */ + @GetMapping("/page") + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:index')") + public R getWxUserPage(Page page, WxUser wxUser, String tagId) { + Wrapper queryWrapper; + if (StringUtils.isNotBlank(tagId)) { + queryWrapper = Wrappers.lambdaQuery(wxUser) + .and(wrapper -> wrapper + .eq(WxUser::getTagidList, "[" + tagId + "]") + .or() + .like(WxUser::getTagidList, "," + tagId + ",") + .or() + .likeRight(WxUser::getTagidList, "[" + tagId + ",") + .or() + .likeLeft(WxUser::getTagidList, "," + tagId + "]")); + } else if (StrUtil.isNotBlank(wxUser.getNickName())) { + String nickName = wxUser.getNickName(); + wxUser.setNickName(null); + queryWrapper = Wrappers.lambdaQuery(wxUser) + .like(WxUser::getNickName, nickName); + } else { + queryWrapper = Wrappers.lambdaQuery(wxUser); + } + return R.ok(wxUserService.page(page, queryWrapper)); + } + + + /** + * 通过id查询微信用户 + * + * @param id id + * @return R + */ + @GetMapping("/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:get')") + public R getById(@PathVariable("id") String id) { + return R.ok(wxUserService.getById(id)); + } + + /** + * 新增微信用户 + * + * @param wxUser 微信用户 + * @return R + */ + @PostMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:add')") + public R save(@RequestBody WxUser wxUser) { + return R.ok(wxUserService.save(wxUser)); + } + + /** + * 修改微信用户 + * + * @param wxUser 微信用户 + * @return R + */ + @PutMapping + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:edit')") + public R updateById(@RequestBody WxUser wxUser) { + return R.ok(wxUserService.updateById(wxUser)); + } + + /** + * 通过id删除微信用户 + * + * @param id id + * @return R + */ + @DeleteMapping("/{id}") + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:del')") + public R removeById(@PathVariable String id) { + return R.ok(wxUserService.removeById(id)); + } + + @PostMapping("/synchron") + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:synchro')") + public R synchron() { + try { + wxUserService.synchroWxUser(); + return R.ok(); + } catch (WxErrorException e) { + + log.error("同步微信用户失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 修改微信用户备注 + * + * @param wxUser + * @return + */ + @PutMapping("/remark") + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:edit:remark')") + public R remark(@RequestBody WxUser wxUser) { + try { + return R.ok(wxUserService.updateRemark(wxUser)); + } catch (WxErrorException e) { + + log.error("修改微信用户备注失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 打标签 + * + * @param data + * @return + */ + @PutMapping("/tagid-list") + @PreAuthorize("@customSs.hasPermi('wxmp:wxuser:tagging')") + public R tagidList(@RequestBody JSONObject data) { + try { + String appId = data.getStr("appId"); + String taggingType = data.getStr("taggingType"); + JSONArray tagIdsArray = data.getJSONArray("tagIds"); + JSONArray openIdsArray = data.getJSONArray("openIds"); + String[] openIds = openIdsArray.toArray(new String[0]); + for (Object tagId : tagIdsArray) { + wxUserService.tagging(taggingType, Long.valueOf(String.valueOf(tagId)), openIds); + } + return R.ok(); + } catch (WxErrorException e) { + + log.error("修改微信用户备注失败", e); + return R.error(e.getMessage()); + } + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/controller/WxUserTagsController.java b/play-weichat/src/main/java/com/starry/weichat/controller/WxUserTagsController.java new file mode 100644 index 0000000..86742c3 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/controller/WxUserTagsController.java @@ -0,0 +1,155 @@ + +package com.starry.weichat.controller; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.starry.common.result.R; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.entity.WxUserTagsDict; +import com.starry.weichat.service.WxUserService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.WxMpUserTagService; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * 微信用户标签 + * + * @author admin + * @since 2019-03-25 15:39:39 + */ +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/wxusertags") +public class WxUserTagsController { + + @Resource + WxMpService wxService; + + @Resource + WxUserService wxUserService; + + /** + * 获取微信用户标签 + * @return + */ + @PreAuthorize("@customSs.hasPermi('wxmp:wxusertags:list')") + @GetMapping("/list") + public R getWxUserList(String appId) { + WxMpUserTagService wxMpUserTagService = wxService.getUserTagService(); + try { + List listWxUserTag = wxMpUserTagService.tagGet(); + return R.ok(listWxUserTag); + } catch (WxErrorException e) { + + log.error("获取微信用户标签失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 获取微信用户标签字典 + * @param appId + * @return + */ + @PreAuthorize("@customSs.hasPermi('wxmp:wxusertags:list')") + @GetMapping("/dict") + public R getWxUserTagsDict(String appId) { + WxMpUserTagService wxMpUserTagService = wxService.getUserTagService(); + try { + List listWxUserTag = wxMpUserTagService.tagGet(); + List listWxUserTagsDict = new ArrayList<>(); + WxUserTagsDict wxUserTagsDict; + for(WxUserTag wxUserTag : listWxUserTag){ + wxUserTagsDict = new WxUserTagsDict(); + wxUserTagsDict.setName(wxUserTag.getName()); + wxUserTagsDict.setValue(wxUserTag.getId()); + listWxUserTagsDict.add(wxUserTagsDict); + } + return R.ok(listWxUserTagsDict); + } catch (WxErrorException e) { + + log.error("获取微信用户标签字典失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 新增微信用户标签 + * @return + */ + @PreAuthorize("@customSs.hasPermi('wxmp:wxusertags:add')") + @PostMapping + public R save(@RequestBody JSONObject data){ + String appId = data.getStr("appId"); + String name = data.getStr("name"); + WxMpUserTagService wxMpUserTagService = wxService.getUserTagService(); + try { + return R.ok(wxMpUserTagService.tagCreate(name)); + } catch (WxErrorException e) { + + log.error("新增微信用户标签失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 修改微信用户标签 + * @return + */ + @PreAuthorize("@customSs.hasPermi('wxmp:wxusertags:edit')") + @PutMapping + public R updateById(@RequestBody JSONObject data){ + String appId = data.getStr("appId"); + Long id = data.getLong("id"); + String name = data.getStr("name"); + WxMpUserTagService wxMpUserTagService = wxService.getUserTagService(); + try { + return R.ok(wxMpUserTagService.tagUpdate(id,name)); + } catch (WxErrorException e) { + + log.error("修改微信用户标签失败", e); + return R.error(e.getMessage()); + } + } + + /** + * 删除微信用户标签 + * @param id + * @param appId + * @return + */ + @PreAuthorize("@customSs.hasPermi('wxmp:wxusertags:del')") + @DeleteMapping + public R removeById(Long id,String appId){ + int count = (int) wxUserService.count(Wrappers.lambdaQuery() + .and(wrapper -> wrapper + .eq(WxUser::getTagidList,"["+id+"]") + .or() + .like(WxUser::getTagidList,","+id+",") + .or() + .likeRight(WxUser::getTagidList,"["+id+",") + .or() + .likeLeft(WxUser::getTagidList,","+id+"]"))); + if(count>0){ + return R.error("该标签下有用户存在,无法删除"); + } + WxMpUserTagService wxMpUserTagService = wxService.getUserTagService(); + try { + return R.ok(wxMpUserTagService.tagDelete(id)); + } catch (WxErrorException e) { + + log.error("删除微信用户标签失败", e); + return R.error(e.getMessage()); + } + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/ImageManager.java b/play-weichat/src/main/java/com/starry/weichat/entity/ImageManager.java new file mode 100644 index 0000000..ec4e2da --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/ImageManager.java @@ -0,0 +1,23 @@ + +package com.starry.weichat.entity; + +import lombok.Data; + +import java.io.Serializable; + +/** + * imageManager + * + * @author admin + * @since 2019-03-23 21:26:35 + */ +@Data +public class ImageManager implements Serializable { + private static final long serialVersionUID = 1L; + + private String url; + private String thumb; + private String tag; + private String name; + private Integer id; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/Menu.java b/play-weichat/src/main/java/com/starry/weichat/entity/Menu.java new file mode 100644 index 0000000..8f1179a --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/Menu.java @@ -0,0 +1,41 @@ + +package com.starry.weichat.entity; + +import cn.hutool.json.JSONUtil; +import lombok.Data; +import me.chanjar.weixin.common.bean.menu.WxMenuRule; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 自定义菜单模型 + * + * @author admin + */ +@Data +public class Menu implements Serializable { + private static final long serialVersionUID = -7083914585539687746L; + + private List button = new ArrayList<>(); + + private WxMenuRule matchrule; + + /** + * 反序列化 + */ + public static Menu fromJson(String json) { + return JSONUtil.parseObj(json).toBean(Menu.class); + } + + public String toJson() { + return JSONUtil.toJsonStr(this); + } + + @Override + public String toString() { + return this.toJson(); + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/MenuButton.java b/play-weichat/src/main/java/com/starry/weichat/entity/MenuButton.java new file mode 100644 index 0000000..303bf96 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/MenuButton.java @@ -0,0 +1,69 @@ + +package com.starry.weichat.entity; + +import cn.hutool.json.JSONObject; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 自定义菜单模型 + * + * @author admin + */ +@Data +public class MenuButton implements Serializable { + + private String type; + + private String name; + + private String key; + + private String url; + + private String media_id; + + private String appid; + + private String pagepath; + + private List sub_button = new ArrayList<>(); + /** + * content内容 + */ + private JSONObject content; + + private String repContent; + /** + * 消息类型 + */ + private String repType; + /** + * 消息名 + */ + private String repName; + /** + * 视频和音乐的描述 + */ + private String repDesc; + /** + * 视频和音乐的描述 + */ + private String repUrl; + /** + * 高质量链接 + */ + private String repHqUrl; + /** + * 缩略图的媒体id + */ + private String repThumbMediaId; + /** + * 缩略图url + */ + private String repThumbUrl; + private String article_id; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/ThirdSession.java b/play-weichat/src/main/java/com/starry/weichat/entity/ThirdSession.java new file mode 100644 index 0000000..447dbec --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/ThirdSession.java @@ -0,0 +1,29 @@ + +package com.starry.weichat.entity; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author admin + */ +@Data +public class ThirdSession implements Serializable { + /** + * 微信用户ID + */ + private String wxUserId; + /** + * 配置项ID + */ + private String appId; + /** + * 微信sessionKey + */ + private String sessionKey; + /** + * 用户标识 + */ + private String openId; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxAutoReply.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxAutoReply.java new file mode 100644 index 0000000..ebfa110 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxAutoReply.java @@ -0,0 +1,120 @@ + +package com.starry.weichat.entity; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.starry.common.config.typehandler.JsonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.ibatis.type.JdbcType; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 消息自动回复 + * + * @author admin + * @since 2019-04-18 15:40:39 + */ +@Data +@TableName("wx_auto_reply") +@EqualsAndHashCode(callSuper = true) +public class WxAutoReply extends Model { + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private String id; + /** + * 创建者 + */ + private String createId; + /** + * 创建时间 + */ + private LocalDateTime createTime; + /** + * 更新者 + */ + private String updateId; + /** + * 更新时间 + */ + private LocalDateTime updateTime; + /** + * 备注 + */ + private String remark; + /** + * 逻辑删除标记(0:显示;1:隐藏) + */ + private String delFlag; + /** + * 类型(1、关注时回复;2、消息回复;3、关键词回复) + */ + @NotNull(message = "类型不能为空") + private String type; + /** + * 关键词 + */ + private String reqKey; + /** + * 请求消息类型(text:文本;image:图片;voice:语音;video:视频;shortvideo:小视频;location:地理位置) + */ + private String reqType; + /** + * 回复消息类型(text:文本;image:图片;voice:语音;video:视频;music:音乐;news:图文) + */ + @NotNull(message = "回复消息类型不能为空") + private String repType; + /** + * 回复类型文本匹配类型(1、全匹配,2、半匹配) + */ + private String repMate; + /** + * 回复类型文本保存文字 + */ + private String repContent; + /** + * 回复的素材名、视频和音乐的标题 + */ + private String repName; + /** + * 回复类型imge、voice、news、video的mediaID或音乐缩略图的媒体id + */ + private String repMediaId; + /** + * 视频和音乐的描述 + */ + private String repDesc; + /** + * 链接 + */ + private String repUrl; + /** + * 高质量链接 + */ + private String repHqUrl; + /** + * 缩略图的媒体id + */ + private String repThumbMediaId; + /** + * 缩略图url + */ + private String repThumbUrl; + + /** + * 图文消息的内容 + */ + @TableField(typeHandler = JsonTypeHandler.class, jdbcType = JdbcType.VARCHAR) + private JSONObject content; + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxMenu.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxMenu.java new file mode 100644 index 0000000..655e817 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxMenu.java @@ -0,0 +1,118 @@ + +package com.starry.weichat.entity; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.starry.common.config.typehandler.JsonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.ibatis.type.JdbcType; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 自定义菜单 + * + * @author admin + * @since 2019-03-27 16:52:10 + */ +@Data +@TableName("wx_menu") +@EqualsAndHashCode(callSuper = true) +public class WxMenu extends Model { + private static final long serialVersionUID = 1L; + + /** + * 菜单ID(click、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、pic_weixin、location_select:保存key) + */ + @TableId(type = IdType.ASSIGN_ID) + private String id; + /** + * 父菜单ID + */ + private String parentId; + /** + * 排序值 + */ + private Integer sort; + /** + * 创建时间 + */ + private LocalDateTime createTime; + /** + * 更新时间 + */ + private LocalDateTime updateTime; + /** + * 逻辑删除标记(0:显示;1:隐藏) + */ + private String delFlag; + /** + * 菜单类型click、view、miniprogram、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、pic_weixin、location_select、media_id、view_limited等 + */ + @NotNull(message = "菜单类型不能为空") + private String type; + /** + * 菜单名 + */ + @NotNull(message = "菜单名不能为空") + private String name; + /** + * View:保存链接到url + */ + private String url; + /** + * Img、voice、News:保存mediaID + */ + private String repMediaId; + /** + * 回复消息类型(text:文本;image:图片;voice:语音;video:视频;music:音乐;news:图文) + */ + private String repType; + /** + * 素材名、视频和音乐的标题 + */ + private String repName; + /** + * Text:保存文字 + */ + private String repContent; + /** + * 小程序的appid + */ + private String maAppId; + /** + * 小程序的页面路径 + */ + private String maPagePath; + /** + * 视频和音乐的描述 + */ + private String repDesc; + /** + * 视频和音乐的描述 + */ + private String repUrl; + /** + * 高质量链接 + */ + private String repHqUrl; + /** + * 缩略图的媒体id + */ + private String repThumbMediaId; + /** + * 缩略图url + */ + private String repThumbUrl; + /** + * 图文消息的内容 + */ + @TableField(typeHandler = JsonTypeHandler.class, jdbcType = JdbcType.VARCHAR) + private JSONObject content; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxMsg.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxMsg.java new file mode 100644 index 0000000..e02800c --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxMsg.java @@ -0,0 +1,146 @@ + +package com.starry.weichat.entity; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.starry.common.config.typehandler.JsonTypeHandler; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.ibatis.type.JdbcType; + +import java.time.LocalDateTime; + +/** + * 微信消息 + * + * @author admin + * @since 2019-05-28 16:12:10 + */ +@Data +@TableName("wx_msg") +@EqualsAndHashCode(callSuper = true) +@ApiModel(description = "微信消息") +public class WxMsg extends Model { + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private String id; + /** + * 创建者 + */ + private String createId; + /** + * 创建时间 + */ + private LocalDateTime createTime; + /** + * 更新者 + */ + private String updateId; + /** + * 更新时间 + */ + private LocalDateTime updateTime; + /** + * 备注 + */ + private String remark; + /** + * 逻辑删除标记(0:显示;1:隐藏) + */ + private String delFlag; + /** + * 公众号名称 + */ + private String appName; + /** + * 公众号logo + */ + private String appLogo; + /** + * 微信用户ID + */ + private String wxUserId; + /** + * 昵称 + */ + private String nickName; + /** + * 头像 + */ + private String headimgUrl; + /** + * 消息分类(1、用户发给公众号;2、公众号发给用户;) + */ + private String type; + /** + * 消息类型(text:文本;image:图片;voice:语音;video:视频;shortvideo:小视频;location:地理位置;music:音乐;news:图文;event:推送事件) + */ + private String repType; + /** + * 事件类型(subscribe:关注;unsubscribe:取关;CLICK、VIEW:菜单事件) + */ + private String repEvent; + /** + * 回复类型文本保存文字 + */ + private String repContent; + /** + * 回复类型imge、voice、news、video的mediaID或音乐缩略图的媒体id + */ + private String repMediaId; + /** + * 回复的素材名、视频和音乐的标题 + */ + private String repName; + /** + * 视频和音乐的描述 + */ + private String repDesc; + /** + * 链接 + */ + private String repUrl; + /** + * 高质量链接 + */ + private String repHqUrl; + /** + * 图文消息的内容 + */ + @TableField(typeHandler = JsonTypeHandler.class, jdbcType = JdbcType.VARCHAR) + private JSONObject content; + /** + * 缩略图的媒体id + */ + private String repThumbMediaId; + /** + * 缩略图url + */ + private String repThumbUrl; + /** + * 地理位置维度 + */ + private Double repLocationX; + /** + * 地理位置经度 + */ + private Double repLocationY; + /** + * 地图缩放大小 + */ + private Double repScale; + /** + * 已读标记(0:是;1:否) + */ + private String readFlag; + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxMsgVO.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxMsgVO.java new file mode 100644 index 0000000..a84cbb6 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxMsgVO.java @@ -0,0 +1,28 @@ + +package com.starry.weichat.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 微信消息 + * + * @author admin + * @since 2019-05-28 16:12:10 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxMsgVO extends WxMsg { + private static final long serialVersionUID = 1L; + + /** + * 数量 + */ + private Integer countMsg; + + /** + * repType not in筛选 + */ + private String notInRepType; + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxOpenDataDTO.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxOpenDataDTO.java new file mode 100644 index 0000000..f7fab6f --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxOpenDataDTO.java @@ -0,0 +1,19 @@ +package com.starry.weichat.entity; + +import lombok.Data; + +/** + * 微信开发数据 + * @author admin + */ +@Data +public class WxOpenDataDTO { + private String appId; + private String userId; + private String encryptedData; + private String errMsg; + private String iv; + private String rawData; + private String signature; + private String sessionKey; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxUser.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxUser.java new file mode 100644 index 0000000..be93335 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxUser.java @@ -0,0 +1,154 @@ + +package com.starry.weichat.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.starry.common.config.typehandler.ArrayLongTypeHandler; +import com.starry.common.sensitive.Sensitive; +import com.starry.common.sensitive.SensitiveTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.ibatis.type.JdbcType; + +import java.time.LocalDateTime; + +/** + * 微信用户 + * + * @author admin + * @since 2019-03-25 15:39:39 + */ +@Data +@TableName("wx_user") +@EqualsAndHashCode(callSuper = true) +public class WxUser extends Model { + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private String id; + /** + * 创建者 + */ + private String createId; + /** + * 创建时间 + */ + private LocalDateTime createTime; + /** + * 更新者 + */ + private String updateId; + /** + * 更新时间 + */ + private LocalDateTime updateTime; + /** + * 备注信息 + */ + private String remark; + /** + * 逻辑删除标记(0:显示;1:隐藏) + */ + private String delFlag; + /** + * 应用类型(1:小程序,2:公众号) + */ + private String appType; + /** + * 是否订阅(0:是;1:否;2:网页授权用户) + */ + private String subscribe; + /** + * 返回用户关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索,ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移,ADD_SCENE_PROFILE_CARD 名片分享,ADD_SCENE_QR_CODE 扫描二维码,ADD_SCENEPROFILE LINK 图文页内名称点击,ADD_SCENE_PROFILE_ITEM 图文页右上角菜单,ADD_SCENE_PAID 支付后关注,ADD_SCENE_OTHERS 其他 + */ + private String subscribeScene; + /** + * 关注时间 + */ + private LocalDateTime subscribeTime; + /** + * 关注次数 + */ + private Integer subscribeNum; + /** + * 取消关注时间 + */ + private LocalDateTime cancelSubscribeTime; + /** + * 用户标识 + */ + private String openId; + /** + * 昵称 + */ + private String nickName; + /** + * 性别(1:男,2:女,0:未知) + */ + private String sex; + /** + * 所在城市 + */ + private String city; + /** + * 所在国家 + */ + private String country; + /** + * 所在省份 + */ + private String province; + /** + * 手机号码 + */ + @Sensitive(type = SensitiveTypeEnum.MOBILE_PHONE) + private String phone; + /** + * 用户语言 + */ + private String language; + /** + * 头像 + */ + private String headimgUrl; + /** + * union_id + */ + private String unionId; + /** + * 用户组 + */ + private String groupId; + /** + * 标签列表 + */ + @TableField(typeHandler = ArrayLongTypeHandler.class, jdbcType = JdbcType.VARCHAR) + private Long[] tagidList; + /** + * 二维码扫码场景 + */ + private String qrSceneStr; + /** + * 地理位置纬度 + */ + private Double latitude; + /** + * 地理位置经度 + */ + private Double longitude; + /** + * 地理位置精度 + */ + @TableField(value = "`precision`") + private Double precision; + /** + * 会话密钥 + */ + private String sessionKey; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/entity/WxUserTagsDict.java b/play-weichat/src/main/java/com/starry/weichat/entity/WxUserTagsDict.java new file mode 100644 index 0000000..433ef23 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/entity/WxUserTagsDict.java @@ -0,0 +1,13 @@ + +package com.starry.weichat.entity; + +import lombok.Data; + +/** + * @author admin + */ +@Data +public class WxUserTagsDict { + private String name; + private Long value; +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/AbstractHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/AbstractHandler.java new file mode 100644 index 0000000..a551ecb --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/AbstractHandler.java @@ -0,0 +1,13 @@ + +package com.starry.weichat.handler; + +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author admin + */ +public abstract class AbstractHandler implements WxMpMessageHandler { + protected Logger logger = LoggerFactory.getLogger(getClass()); +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/KfSessionHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/KfSessionHandler.java new file mode 100644 index 0000000..15ca8f6 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/KfSessionHandler.java @@ -0,0 +1,26 @@ + +package com.starry.weichat.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author admin + */ +@Component +public class KfSessionHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + // TODO 对会话做处理 + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/LocationHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/LocationHandler.java new file mode 100644 index 0000000..2fc3163 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/LocationHandler.java @@ -0,0 +1,45 @@ + +package com.starry.weichat.handler; + +import com.starry.weichat.builder.TextBuilder; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +/** + * @author admin + */ +@Component +public class LocationHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) { + // TODO 接收处理用户发送的地理位置消息 + try { + String content = "感谢反馈,您的的地理位置已收到!"; + return new TextBuilder().build(content, wxMessage, null); + } catch (Exception e) { + this.logger.error("位置消息接收处理失败", e); + return null; + } + } + + // 上报地理位置事件 + this.logger.info("上报地理位置,纬度 : {},经度 : {},精度 : {}", + wxMessage.getLatitude(), wxMessage.getLongitude(), wxMessage.getPrecision()); + + // TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 + + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/LogHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/LogHandler.java new file mode 100644 index 0000000..f2e34f7 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/LogHandler.java @@ -0,0 +1,40 @@ + +package com.starry.weichat.handler; + +import cn.hutool.core.util.StrUtil; +import com.starry.common.utils.HttpUtils; +import com.starry.weichat.utils.JsonUtils; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author admin + */ +@Component +public class LogHandler extends AbstractHandler { + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + this.logger.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage)); + // 以下为测试代码,自行删除不影响系统功能 + if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) { + if (wxMessage.getEvent().equals(WxConsts.EventType.SUBSCRIBE) || + wxMessage.getEvent().equals(WxConsts.EventType.SCAN)) { + if (wxMessage.getEventKey().contains("jl-wiki")) { + String openId = wxMessage.getFromUser(); + String sceneStr = StrUtil.split(wxMessage.getEventKey(), ":").get(1); + String rs = HttpUtils.sendPost("http://127.0.0.1:8083/joolun-open/user", StrUtil.format("openId={}&sceneStr={}", openId, sceneStr)); + } + } + } + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/MenuHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/MenuHandler.java new file mode 100644 index 0000000..cdc937d --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/MenuHandler.java @@ -0,0 +1,173 @@ + +package com.starry.weichat.handler; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.starry.weichat.config.CommonConstants; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.entity.WxMenu; +import com.starry.weichat.entity.WxMsg; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.mapper.WxMenuMapper; +import com.starry.weichat.mapper.WxMsgMapper; +import com.starry.weichat.mapper.WxUserMapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import me.chanjar.weixin.mp.builder.outxml.*; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author admin + */ +@Slf4j +@Component +@AllArgsConstructor +public class MenuHandler extends AbstractHandler { + + private final WxMenuMapper wxMenuMapper; + private final WxUserMapper wxUserMapper; + private final WxMsgMapper wxMsgMapper; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) throws WxErrorException { + // 消息记录 + WxMenu wxMenu; + if (WxConsts.EventType.CLICK.equals(wxMessage.getEvent()) + || WxConsts.EventType.SCANCODE_WAITMSG.equals(wxMessage.getEvent())) { + wxMenu = wxMenuMapper.selectById(wxMessage.getEventKey()); + if (wxMenu == null) {// 菜单过期 + return new TextBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).content("非常抱歉,该菜单已删除!").build(); + } + } else { + wxMenu = new WxMenu(); + } + WxUser wxUser = wxUserMapper.selectOne(Wrappers.lambdaQuery() + .eq(WxUser::getOpenId, wxMessage.getFromUser())); + if (wxUser == null) {// 库中无此用户 + WxMpUser userWxInfo = weixinService.getUserService() + .userInfo(wxMessage.getFromUser(), null); + wxUser = new WxUser(); + wxUser.setSubscribeNum(1); + SubscribeHandler.setWxUserValue(wxUser, userWxInfo); + wxUserMapper.insert(wxUser); + } + // 组装菜单回复消息 + return getWxMpXmlOutMessage(wxMessage, wxMenu, wxUser); + } + + /** + * 组装菜单回复消息 + * + * @param wxMessage + * @param wxMenu + * @return + */ + public WxMpXmlOutMessage getWxMpXmlOutMessage(WxMpXmlMessage wxMessage, WxMenu wxMenu, WxUser wxUser) { + WxMpXmlOutMessage wxMpXmlOutMessage = null; + // 记录接收消息 + WxMsg wxMsg = new WxMsg(); +// wxMsg.setTenantId(wxApp.getTenantId()); + wxMsg.setWxUserId(wxUser.getId()); + wxMsg.setNickName(wxUser.getNickName()); + wxMsg.setHeadimgUrl(wxUser.getHeadimgUrl()); + wxMsg.setType(ConfigConstant.WX_MSG_TYPE_1); + wxMsg.setRepEvent(wxMessage.getEvent()); + wxMsg.setRepType(wxMessage.getMsgType()); + wxMsg.setRepName(wxMenu.getName()); + if (WxConsts.EventType.VIEW.equals(wxMessage.getEvent())) { + wxMsg.setRepUrl(wxMessage.getEventKey()); + } + if (WxConsts.EventType.SCANCODE_WAITMSG.equals(wxMessage.getEvent())) { + wxMsg.setRepContent(wxMessage.getScanCodeInfo().getScanResult()); + } + wxMsg.setReadFlag(CommonConstants.NO); + LocalDateTime now = LocalDateTime.now(); + wxMsg.setCreateTime(now); + wxMsgMapper.insert(wxMsg); + if (WxConsts.MenuButtonType.CLICK.equals(wxMenu.getType()) + || WxConsts.MenuButtonType.SCANCODE_WAITMSG.equals(wxMenu.getType())) { + // 记录回复消息 + wxMsg = new WxMsg(); + wxMsg.setWxUserId(wxUser.getId()); + wxMsg.setNickName(wxUser.getNickName()); + wxMsg.setHeadimgUrl(wxUser.getHeadimgUrl()); + wxMsg.setCreateTime(now.plusSeconds(1)); + wxMsg.setType(ConfigConstant.WX_MSG_TYPE_2); + wxMsg.setRepType(wxMenu.getRepType()); + if (WxConsts.KefuMsgType.TEXT.equals(wxMenu.getRepType())) { + wxMsg.setRepContent(wxMenu.getRepContent()); + wxMpXmlOutMessage = new TextBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).content(wxMenu.getRepContent()).build(); + } + if (WxConsts.KefuMsgType.IMAGE.equals(wxMenu.getRepType())) { + wxMsg.setRepName(wxMenu.getRepName()); + wxMsg.setRepUrl(wxMenu.getRepUrl()); + wxMsg.setRepMediaId(wxMenu.getRepMediaId()); + wxMpXmlOutMessage = new ImageBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).mediaId(wxMenu.getRepMediaId()).build(); + } + if (WxConsts.KefuMsgType.VOICE.equals(wxMenu.getRepType())) { + wxMsg.setRepName(wxMenu.getRepName()); + wxMsg.setRepUrl(wxMenu.getRepUrl()); + wxMsg.setRepMediaId(wxMenu.getRepMediaId()); + wxMpXmlOutMessage = new VoiceBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).mediaId(wxMenu.getRepMediaId()).build(); + } + if (WxConsts.KefuMsgType.VIDEO.equals(wxMenu.getRepType())) { + wxMsg.setRepName(wxMenu.getRepName()); + wxMsg.setRepDesc(wxMenu.getRepDesc()); + wxMsg.setRepUrl(wxMenu.getRepUrl()); + wxMsg.setRepMediaId(wxMenu.getRepMediaId()); + wxMpXmlOutMessage = new VideoBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).mediaId(wxMenu.getRepMediaId()) + .title(wxMenu.getRepName()).description(wxMenu.getRepDesc()).build(); + } + if (WxConsts.KefuMsgType.MUSIC.equals(wxMenu.getRepType())) { + wxMsg.setRepName(wxMenu.getRepName()); + wxMsg.setRepDesc(wxMenu.getRepDesc()); + wxMsg.setRepUrl(wxMenu.getRepUrl()); + wxMsg.setRepHqUrl(wxMenu.getRepHqUrl()); + wxMsg.setRepThumbMediaId(wxMenu.getRepThumbMediaId()); + wxMsg.setRepThumbUrl(wxMenu.getRepThumbUrl()); + wxMpXmlOutMessage = new MusicBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .thumbMediaId(wxMenu.getRepThumbMediaId()) + .title(wxMenu.getRepName()).description(wxMenu.getRepDesc()) + .musicUrl(wxMenu.getRepUrl()).hqMusicUrl(wxMenu.getRepHqUrl()).build(); + } + if (WxConsts.KefuMsgType.NEWS.equals(wxMenu.getRepType())) { + List list = new ArrayList<>(); + List listJSONObject = JSONUtil.toList(wxMenu.getContent().getJSONArray("articles"), JSONObject.class); + WxMpXmlOutNewsMessage.Item t; + for (JSONObject jSONObject : listJSONObject) { + t = new WxMpXmlOutNewsMessage.Item(); + t.setTitle(jSONObject.getStr("title")); + t.setDescription(jSONObject.getStr("digest")); + t.setPicUrl(jSONObject.getStr("thumbUrl")); + t.setUrl(jSONObject.getStr("url")); + list.add(t); + } + wxMsg.setRepName(wxMenu.getRepName()); + wxMsg.setRepDesc(wxMenu.getRepDesc()); + wxMsg.setRepUrl(wxMenu.getRepUrl()); + wxMsg.setRepMediaId(wxMenu.getRepMediaId()); + wxMsg.setContent(wxMenu.getContent()); + wxMpXmlOutMessage = new NewsBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).articles(list).build(); + } + wxMsgMapper.insert(wxMsg); + } + return wxMpXmlOutMessage; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/MsgHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/MsgHandler.java new file mode 100644 index 0000000..eb3fe00 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/MsgHandler.java @@ -0,0 +1,195 @@ + +package com.starry.weichat.handler; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.starry.weichat.config.CommonConstants; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.constant.WebSocketConstant; +import com.starry.weichat.entity.WxAutoReply; +import com.starry.weichat.entity.WxMsg; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.mapper.WxUserMapper; +import com.starry.weichat.service.WxAutoReplyService; +import com.starry.weichat.service.WxMsgService; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; +import me.chanjar.weixin.mp.builder.outxml.*; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +/** + * @author admin + */ +@Component +@AllArgsConstructor +public class MsgHandler extends AbstractHandler { + + private final WxAutoReplyService wxAutoReplyService; + private final WxUserMapper wxUserMapper; + private final WxMsgService wxMsgService; + + /** + * 组装回复消息,并记录消息 + * + * @param wxMessage + * @param listWxAutoReply + * @return + */ + public static WxMpXmlOutMessage getWxMpXmlOutMessage(WxMpXmlMessage wxMessage, List listWxAutoReply, WxUser wxUser, WxMsgService wxMsgService) { + WxMpXmlOutMessage wxMpXmlOutMessage = null; + // 记录接收消息 + WxMsg wxMsg = new WxMsg(); + wxMsg.setWxUserId(wxUser.getId()); + wxMsg.setNickName(wxUser.getNickName()); + wxMsg.setHeadimgUrl(wxUser.getHeadimgUrl()); + wxMsg.setType(ConfigConstant.WX_MSG_TYPE_1); + wxMsg.setRepEvent(wxMessage.getEvent()); + wxMsg.setRepType(wxMessage.getMsgType()); + wxMsg.setRepMediaId(wxMessage.getMediaId()); + if (WxConsts.XmlMsgType.TEXT.equals(wxMessage.getMsgType())) { + wxMsg.setRepContent(wxMessage.getContent()); + } + if (WxConsts.XmlMsgType.VOICE.equals(wxMessage.getMsgType())) { + wxMsg.setRepName(wxMessage.getMediaId() + "." + wxMessage.getFormat()); + wxMsg.setRepContent(wxMessage.getRecognition()); + } + if (WxConsts.XmlMsgType.IMAGE.equals(wxMessage.getMsgType())) { + wxMsg.setRepUrl(wxMessage.getPicUrl()); + } + if (WxConsts.XmlMsgType.LINK.equals(wxMessage.getMsgType())) { + wxMsg.setRepName(wxMessage.getTitle()); + wxMsg.setRepDesc(wxMessage.getDescription()); + wxMsg.setRepUrl(wxMessage.getUrl()); + } + if (WxConsts.MediaFileType.FILE.equals(wxMessage.getMsgType())) { + wxMsg.setRepName(wxMessage.getTitle()); + wxMsg.setRepDesc(wxMessage.getDescription()); + } + if (WxConsts.XmlMsgType.VIDEO.equals(wxMessage.getMsgType())) { + wxMsg.setRepThumbMediaId(wxMessage.getThumbMediaId()); + } + if (WxConsts.XmlMsgType.LOCATION.equals(wxMessage.getMsgType())) { + wxMsg.setRepLocationX(wxMessage.getLocationX()); + wxMsg.setRepLocationY(wxMessage.getLocationY()); + wxMsg.setRepScale(wxMessage.getScale()); + wxMsg.setRepContent(wxMessage.getLabel()); + } + wxMsg.setReadFlag(CommonConstants.NO); + LocalDateTime now = LocalDateTime.now(); + wxMsg.setCreateTime(now); + wxMsgService.save(wxMsg); + // 推送websocket + String destination = WebSocketConstant.USER_DESTINATION_PREFIX + WebSocketConstant.WX_MSG + wxMsg.getWxUserId(); + if (listWxAutoReply != null && !listWxAutoReply.isEmpty()) { + WxAutoReply wxAutoReply = listWxAutoReply.get(0); + // 记录回复消息 + wxMsg = new WxMsg(); + wxMsg.setWxUserId(wxUser.getId()); + wxMsg.setNickName(wxUser.getNickName()); + wxMsg.setHeadimgUrl(wxUser.getHeadimgUrl()); + wxMsg.setCreateTime(now.plusSeconds(1)); + wxMsg.setType(ConfigConstant.WX_MSG_TYPE_2); + wxMsg.setRepType(wxAutoReply.getRepType()); + + if (WxConsts.KefuMsgType.TEXT.equals(wxAutoReply.getRepType())) {// 文本 + wxMsg.setRepContent(wxAutoReply.getRepContent()); + wxMpXmlOutMessage = new TextBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).content(wxAutoReply.getRepContent()).build(); + } + if (WxConsts.KefuMsgType.IMAGE.equals(wxAutoReply.getRepType())) {// 图片 + wxMsg.setRepName(wxAutoReply.getRepName()); + wxMsg.setRepUrl(wxAutoReply.getRepUrl()); + wxMsg.setRepMediaId(wxAutoReply.getRepMediaId()); + wxMpXmlOutMessage = new ImageBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).mediaId(wxAutoReply.getRepMediaId()).build(); + } + if (WxConsts.KefuMsgType.VOICE.equals(wxAutoReply.getRepType())) { + wxMsg.setRepName(wxAutoReply.getRepName()); + wxMsg.setRepUrl(wxAutoReply.getRepUrl()); + wxMsg.setRepMediaId(wxAutoReply.getRepMediaId()); + wxMpXmlOutMessage = new VoiceBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).mediaId(wxAutoReply.getRepMediaId()).build(); + } + if (WxConsts.KefuMsgType.VIDEO.equals(wxAutoReply.getRepType())) { + wxMsg.setRepName(wxAutoReply.getRepName()); + wxMsg.setRepDesc(wxAutoReply.getRepDesc()); + wxMsg.setRepUrl(wxAutoReply.getRepUrl()); + wxMsg.setRepMediaId(wxAutoReply.getRepMediaId()); + wxMpXmlOutMessage = new VideoBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).mediaId(wxAutoReply.getRepMediaId()).title(wxAutoReply.getRepName()).description(wxAutoReply.getRepDesc()).build(); + } + if (WxConsts.KefuMsgType.MUSIC.equals(wxAutoReply.getRepType())) { + wxMsg.setRepName(wxAutoReply.getRepName()); + wxMsg.setRepDesc(wxAutoReply.getRepDesc()); + wxMsg.setRepUrl(wxAutoReply.getRepUrl()); + wxMsg.setRepHqUrl(wxAutoReply.getRepHqUrl()); + wxMsg.setRepThumbMediaId(wxAutoReply.getRepThumbMediaId()); + wxMsg.setRepThumbUrl(wxAutoReply.getRepThumbUrl()); + wxMpXmlOutMessage = new MusicBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).thumbMediaId(wxAutoReply.getRepThumbMediaId()).title(wxAutoReply.getRepName()).description(wxAutoReply.getRepDesc()).musicUrl(wxAutoReply.getRepUrl()).hqMusicUrl(wxAutoReply.getRepHqUrl()).build(); + } + if (WxConsts.KefuMsgType.NEWS.equals(wxAutoReply.getRepType())) { + List list = new ArrayList<>(); + List listJSONObject = wxAutoReply.getContent().getJSONArray("articles").toList(JSONObject.class); + WxMpXmlOutNewsMessage.Item t; + for (JSONObject jSONObject : listJSONObject) { + t = new WxMpXmlOutNewsMessage.Item(); + t.setTitle(jSONObject.getStr("title")); + t.setDescription(jSONObject.getStr("digest")); + t.setPicUrl(jSONObject.getStr("thumbUrl")); + t.setUrl(jSONObject.getStr("url")); + list.add(t); + } + wxMsg.setRepName(wxAutoReply.getRepName()); + wxMsg.setRepDesc(wxAutoReply.getRepDesc()); + wxMsg.setRepUrl(wxAutoReply.getRepUrl()); + wxMsg.setRepMediaId(wxAutoReply.getRepMediaId()); + wxMsg.setContent(wxAutoReply.getContent()); + wxMpXmlOutMessage = new NewsBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).articles(list).build(); + } + wxMsgService.save(wxMsg); + } + return wxMpXmlOutMessage; + } + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService, WxSessionManager sessionManager) { + // 组装回复消息 + if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) { + WxMpXmlOutMessage rs; + // TODO 可以选择将消息保存到本地 + WxUser wxUser = wxUserMapper.selectOne(Wrappers.lambdaQuery().eq(WxUser::getOpenId, wxMessage.getFromUser())); + if (WxConsts.KefuMsgType.TEXT.equals(wxMessage.getMsgType())) {// 1、先处理是否有文本关键字回复 + // 先全匹配 + List listWxAutoReply = wxAutoReplyService.list(Wrappers.query().lambda().eq(WxAutoReply::getType, ConfigConstant.WX_AUTO_REPLY_TYPE_3).eq(WxAutoReply::getRepMate, ConfigConstant.WX_REP_MATE_1).eq(WxAutoReply::getReqKey, wxMessage.getContent())); + if (listWxAutoReply != null && !listWxAutoReply.isEmpty()) { + rs = this.getWxMpXmlOutMessage(wxMessage, listWxAutoReply, wxUser, wxMsgService); + if (rs != null) { + return rs; + } + } + // 再半匹配 + listWxAutoReply = wxAutoReplyService.list(Wrappers.query().lambda().eq(WxAutoReply::getType, ConfigConstant.WX_AUTO_REPLY_TYPE_3).eq(WxAutoReply::getRepMate, ConfigConstant.WX_REP_MATE_2).like(WxAutoReply::getReqKey, wxMessage.getContent())); + if (listWxAutoReply != null && !listWxAutoReply.isEmpty()) { + rs = this.getWxMpXmlOutMessage(wxMessage, listWxAutoReply, wxUser, wxMsgService); + if (rs != null) { + return rs; + } + } + } + // 2、再处理消息回复 + List listWxAutoReply = wxAutoReplyService.list(Wrappers.query().lambda().eq(WxAutoReply::getType, ConfigConstant.WX_AUTO_REPLY_TYPE_2).eq(WxAutoReply::getReqType, wxMessage.getMsgType())); + rs = this.getWxMpXmlOutMessage(wxMessage, listWxAutoReply, wxUser, wxMsgService); + return rs; + } + return null; + + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/NullHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/NullHandler.java new file mode 100644 index 0000000..91fdc1c --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/NullHandler.java @@ -0,0 +1,25 @@ + +package com.starry.weichat.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author admin + */ +@Component +public class NullHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/ScanHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/ScanHandler.java new file mode 100644 index 0000000..6f3d229 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/ScanHandler.java @@ -0,0 +1,25 @@ + +package com.starry.weichat.handler; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author admin + */ +@Component +public class ScanHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + // 扫码事件处理 + return null; + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/StoreCheckNotifyHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/StoreCheckNotifyHandler.java new file mode 100644 index 0000000..ea29ef6 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/StoreCheckNotifyHandler.java @@ -0,0 +1,28 @@ + +package com.starry.weichat.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 门店审核事件处理 + * + * @author admin + */ +@Component +public class StoreCheckNotifyHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + // TODO 处理门店审核事件 + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/SubscribeHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/SubscribeHandler.java new file mode 100644 index 0000000..9c6361c --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/SubscribeHandler.java @@ -0,0 +1,85 @@ + +package com.starry.weichat.handler; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.entity.WxAutoReply; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.mapper.WxUserMapper; +import com.starry.weichat.service.WxAutoReplyService; +import com.starry.weichat.service.WxMsgService; +import com.starry.weichat.utils.LocalDateTimeUtils; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * @author admin + */ +@Slf4j +@Component +@AllArgsConstructor +public class SubscribeHandler extends AbstractHandler { + private final WxAutoReplyService wxAutoReplyService; + private final WxUserMapper wxUserMapper; + private final WxMsgService wxMsgService; + + public static void setWxUserValue(WxUser wxUser, WxMpUser userWxInfo) { + wxUser.setAppType(ConfigConstant.WX_APP_TYPE_2); + wxUser.setSubscribe(ConfigConstant.SUBSCRIBE_TYPE_YES); + wxUser.setSubscribeScene(userWxInfo.getSubscribeScene()); + if (null != userWxInfo.getSubscribeTime()) { + wxUser.setSubscribeTime(LocalDateTimeUtils.timestamToDatetime(userWxInfo.getSubscribeTime() * 1000)); + } + wxUser.setOpenId(userWxInfo.getOpenId()); + wxUser.setLanguage(userWxInfo.getLanguage()); + wxUser.setRemark(userWxInfo.getRemark()); + wxUser.setUnionId(userWxInfo.getUnionId()); + wxUser.setGroupId(JSONUtil.toJsonStr(userWxInfo.getGroupId())); + wxUser.setTagidList(userWxInfo.getTagIds()); + wxUser.setQrSceneStr(userWxInfo.getQrSceneStr()); + } + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService weixinService, WxSessionManager sessionManager) { + log.info("新关注用户 OPENID: " + wxMessage.getFromUser()); + // 获取微信用户基本信息 + try { + WxMpUser userWxInfo = weixinService.getUserService().userInfo(wxMessage.getFromUser(), null); + if (userWxInfo != null) { + // TODO 添加关注用户到本地数据库 + WxUser wxUser = wxUserMapper.selectOne(Wrappers.lambdaQuery().eq(WxUser::getOpenId, userWxInfo.getOpenId())); + if (wxUser == null) { + // 第一次关注 + wxUser = new WxUser(); + wxUser.setSubscribeNum(1); + setWxUserValue(wxUser, userWxInfo); + // wxUser.setTenantId(wxApp.getTenantId()); + wxUserMapper.insert(wxUser); + } else { + // 曾经关注过 + wxUser.setSubscribeNum(wxUser.getSubscribeNum() + 1); + setWxUserValue(wxUser, userWxInfo); + // wxUser.setTenantId(wxApp.getTenantId()); + wxUserMapper.updateById(wxUser); + } + // 发送关注消息 + List listWxAutoReply = wxAutoReplyService.list(Wrappers.query().lambda().eq(WxAutoReply::getType, ConfigConstant.WX_AUTO_REPLY_TYPE_1)); + return MsgHandler.getWxMpXmlOutMessage(wxMessage, listWxAutoReply, wxUser, wxMsgService); + } + } catch (Exception e) { + log.error("用户关注出错:" + e.getMessage()); + } + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/handler/UnsubscribeHandler.java b/play-weichat/src/main/java/com/starry/weichat/handler/UnsubscribeHandler.java new file mode 100644 index 0000000..54e8c77 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/handler/UnsubscribeHandler.java @@ -0,0 +1,49 @@ + +package com.starry.weichat.handler; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.mapper.WxUserMapper; +import com.starry.weichat.service.WxMsgService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * @author admin + */ +@Slf4j +@Component +@AllArgsConstructor +public class UnsubscribeHandler extends AbstractHandler { + + private final WxMsgService wxMsgService; + private final WxUserMapper wxUserMapper; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + String openId = wxMessage.getFromUser(); + log.info("取消关注用户 OPENID: " + openId); + WxUser wxUser = wxUserMapper.selectOne(Wrappers.lambdaQuery() + .eq(WxUser::getOpenId, openId)); + if (wxUser != null) { + wxUser.setSubscribe(ConfigConstant.SUBSCRIBE_TYPE_NO); + wxUser.setCancelSubscribeTime(LocalDateTime.now()); + wxUserMapper.updateById(wxUser); + // 消息记录 + MsgHandler.getWxMpXmlOutMessage(wxMessage, null, wxUser, wxMsgService); + } + return null; + } + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/interceptor/ThirdSessionInterceptor.java b/play-weichat/src/main/java/com/starry/weichat/interceptor/ThirdSessionInterceptor.java new file mode 100644 index 0000000..18686e8 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/interceptor/ThirdSessionInterceptor.java @@ -0,0 +1,72 @@ +package com.starry.weichat.interceptor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.starry.common.result.R; +import com.starry.weichat.config.CommonConstants; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.constant.MyReturnCode; +import com.starry.weichat.constant.WxMaConstants; +import com.starry.weichat.entity.ThirdSession; +import com.starry.weichat.utils.ThirdSessionHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * ThirdSession拦截器,校验每个请求的ThirdSession + * + * @author admin + */ +@Slf4j +@Component +public class ThirdSessionInterceptor implements HandlerInterceptor { + + @Resource + RedisTemplate redisTemplate; + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 获取header中的thirdSession + String thirdSessionHeader = request.getHeader(ConfigConstant.HEADER_THIRDSESSION); + if (StrUtil.isNotBlank(thirdSessionHeader)) { + // 获取缓存中的ThirdSession + String key = WxMaConstants.THIRD_SESSION_BEGIN + ":" + thirdSessionHeader; + Object thirdSessionObj = redisTemplate.opsForValue().get(key); + if (thirdSessionObj == null) { + // session过期 + R r = R.error(MyReturnCode.ERR_60001.getCode(), MyReturnCode.ERR_60001.getMsg()); + this.writerPrint(response, r); + return Boolean.FALSE; + } else { + String thirdSessionStr = String.valueOf(thirdSessionObj); + ThirdSession thirdSession = JSONUtil.toBean(thirdSessionStr, ThirdSession.class); + // 设置thirdSession + ThirdSessionHolder.setThirdSession(thirdSession); + } + } else { + R r = R.error(MyReturnCode.ERR_60002.getCode(), MyReturnCode.ERR_60002.getMsg()); + this.writerPrint(response, r); + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + private void writerPrint(HttpServletResponse response, R r) throws IOException { + // 返回超时错误码,触发小程序重新登录 + response.setCharacterEncoding(CommonConstants.UTF8); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + PrintWriter writer = response.getWriter(); + writer.print(JSONUtil.parseObj(r)); + writer.close(); + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/mapper/WxAutoReplyMapper.java b/play-weichat/src/main/java/com/starry/weichat/mapper/WxAutoReplyMapper.java new file mode 100644 index 0000000..4859378 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/mapper/WxAutoReplyMapper.java @@ -0,0 +1,16 @@ + +package com.starry.weichat.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.weichat.entity.WxAutoReply; + +/** + * 消息自动回复 + * + * @author admin + * @since 2019-04-18 15:40:39 + */ +public interface WxAutoReplyMapper extends BaseMapper { + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/mapper/WxMenuMapper.java b/play-weichat/src/main/java/com/starry/weichat/mapper/WxMenuMapper.java new file mode 100644 index 0000000..3acd7b7 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/mapper/WxMenuMapper.java @@ -0,0 +1,15 @@ + +package com.starry.weichat.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.weichat.entity.WxMenu; + +/** + * 自定义菜单 + * + * @author admin + * @since 2019-03-27 16:52:10 + */ +public interface WxMenuMapper extends BaseMapper { + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/mapper/WxMsgMapper.java b/play-weichat/src/main/java/com/starry/weichat/mapper/WxMsgMapper.java new file mode 100644 index 0000000..f867b7d --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/mapper/WxMsgMapper.java @@ -0,0 +1,28 @@ + +package com.starry.weichat.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.starry.weichat.entity.WxMsg; +import com.starry.weichat.entity.WxMsgVO; +import org.apache.ibatis.annotations.Param; + +/** + * 微信消息 + * + * @author admin + * @since 2019-05-28 16:12:10 + */ +public interface WxMsgMapper extends BaseMapper { + + /** + * 获取分组后的消息列表 + * + * @param page + * @param wxMsgVO + * @return + */ + IPage listWxMsgMapGroup(Page page, @Param("query") WxMsgVO wxMsgVO); +} diff --git a/play-weichat/src/main/java/com/starry/weichat/mapper/WxUserMapper.java b/play-weichat/src/main/java/com/starry/weichat/mapper/WxUserMapper.java new file mode 100644 index 0000000..934c87c --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/mapper/WxUserMapper.java @@ -0,0 +1,15 @@ + +package com.starry.weichat.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.starry.weichat.entity.WxUser; + +/** + * 微信用户 + * + * @author admin + * @since 2019-03-25 15:39:39 + */ +public interface WxUserMapper extends BaseMapper { + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/WxAutoReplyService.java b/play-weichat/src/main/java/com/starry/weichat/service/WxAutoReplyService.java new file mode 100644 index 0000000..acb2414 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/WxAutoReplyService.java @@ -0,0 +1,15 @@ + +package com.starry.weichat.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.weichat.entity.WxAutoReply; + +/** + * 消息自动回复 + * + * @author admin + * @since 2019-04-18 15:40:39 + */ +public interface WxAutoReplyService extends IService { + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/WxMenuService.java b/play-weichat/src/main/java/com/starry/weichat/service/WxMenuService.java new file mode 100644 index 0000000..501d25b --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/WxMenuService.java @@ -0,0 +1,29 @@ + +package com.starry.weichat.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.weichat.entity.WxMenu; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 自定义菜单 + * + * @author admin + * @since 2019-03-27 16:52:10 + */ +public interface WxMenuService extends IService { + + /*** + * 获取WxApp下的菜单 + * @return + */ + String getWxMenuButton(); + + /** + * 保存并发布菜单 + * + * @param + */ + void saveAndRelease(String strWxMenu) throws WxErrorException; + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/WxMsgService.java b/play-weichat/src/main/java/com/starry/weichat/service/WxMsgService.java new file mode 100644 index 0000000..d31b3e3 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/WxMsgService.java @@ -0,0 +1,26 @@ + +package com.starry.weichat.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.weichat.entity.WxMsg; +import com.starry.weichat.entity.WxMsgVO; + +/** + * 微信消息 + * + * @author admin + * @since 2019-05-28 16:12:10 + */ +public interface WxMsgService extends IService { + + /** + * 获取分组后的消息列表 + * + * @param page + * @param wxMsgVO + * @return + */ + IPage listWxMsgMapGroup(Page page, WxMsgVO wxMsgVO); +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/WxUserService.java b/play-weichat/src/main/java/com/starry/weichat/service/WxUserService.java new file mode 100644 index 0000000..ceaa6ee --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/WxUserService.java @@ -0,0 +1,64 @@ + +package com.starry.weichat.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.starry.weichat.entity.WxOpenDataDTO; +import com.starry.weichat.entity.WxUser; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信用户 + * + * @author admin + * @since 2019-03-25 15:39:39 + */ +public interface WxUserService extends IService { + + /** + * 同步微信用户 + */ + void synchroWxUser() throws WxErrorException; + + /** + * 修改用户备注 + * + * @param entity + * @return + */ + boolean updateRemark(WxUser entity) throws WxErrorException; + + /** + * 认识标签 + * + * @param taggingType + * @param tagId + * @param openIds + * @throws WxErrorException + */ + void tagging(String taggingType, Long tagId, String[] openIds) throws WxErrorException; + + /** + * 根据openId获取用户 + * + * @param openId + * @return + */ + WxUser getByOpenId(String openId); + + /** + * 小程序登录 + * + * @param appId + * @param jsCode + * @return + */ + WxUser loginMa(String appId, String jsCode) throws WxErrorException; + + /** + * 新增、更新微信用户 + * + * @param wxOpenDataDTO + * @return + */ + WxUser saveOrUptateWxUser(WxOpenDataDTO wxOpenDataDTO); +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/impl/WxAutoReplyServiceImpl.java b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxAutoReplyServiceImpl.java new file mode 100644 index 0000000..1583d4f --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxAutoReplyServiceImpl.java @@ -0,0 +1,19 @@ + +package com.starry.weichat.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.weichat.entity.WxAutoReply; +import com.starry.weichat.mapper.WxAutoReplyMapper; +import com.starry.weichat.service.WxAutoReplyService; +import org.springframework.stereotype.Service; + +/** + * 消息自动回复 + * + * @author admin + * @since 2019-04-18 15:40:39 + */ +@Service +public class WxAutoReplyServiceImpl extends ServiceImpl implements WxAutoReplyService { + +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/impl/WxMenuServiceImpl.java b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxMenuServiceImpl.java new file mode 100644 index 0000000..053c147 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxMenuServiceImpl.java @@ -0,0 +1,164 @@ + +package com.starry.weichat.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.weichat.config.CommonConstants; +import com.starry.weichat.entity.Menu; +import com.starry.weichat.entity.MenuButton; +import com.starry.weichat.entity.WxMenu; +import com.starry.weichat.mapper.WxMenuMapper; +import com.starry.weichat.service.WxMenuService; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * 自定义菜单 + * + * @author admin + * @since 2019-03-27 16:52:10 + */ +@Service +@AllArgsConstructor +public class WxMenuServiceImpl extends ServiceImpl implements WxMenuService { + private final WxMpService wxService; + + /*** + * 获取WxApp下的菜单树结构 + * @return + */ + @Override + public String getWxMenuButton() { + // 查出一级菜单 + List listWxMenu = baseMapper.selectList(Wrappers + .query().lambda() + .eq(WxMenu::getParentId, CommonConstants.PARENT_ID).orderByAsc(WxMenu::getSort)); + Menu menu = new Menu(); + List listMenuButton = new ArrayList<>(); + MenuButton menuButton; + List subButtons; + MenuButton subButton; + if (listWxMenu != null && !listWxMenu.isEmpty()) { + for (WxMenu wxMenu : listWxMenu) { + menuButton = new MenuButton(); + menuButton.setName(wxMenu.getName()); + String type = wxMenu.getType(); + if (StringUtils.isNotBlank(type)) {// 无二级菜单 + menuButton.setType(type); + setButtonValue(menuButton, wxMenu); + } else {// 有二级菜单 + // 查出二级菜单 + List listWxMenu1 = baseMapper.selectList(Wrappers + .query().lambda() + .eq(WxMenu::getParentId, wxMenu.getId()).orderByAsc(WxMenu::getSort)); + subButtons = new ArrayList<>(); + for (WxMenu wxMenu1 : listWxMenu1) { + subButton = new MenuButton(); + String type1 = wxMenu1.getType(); + subButton.setName(wxMenu1.getName()); + subButton.setType(type1); + setButtonValue(subButton, wxMenu1); + subButtons.add(subButton); + } + menuButton.setSub_button(subButtons); + } + listMenuButton.add(menuButton); + } + } + menu.setButton(listMenuButton); + return menu.toString(); + } + + void setButtonValue(MenuButton menuButton, WxMenu wxMenu) { + menuButton.setKey(wxMenu.getId()); + menuButton.setUrl(wxMenu.getUrl()); + menuButton.setContent(wxMenu.getContent()); + menuButton.setRepContent(wxMenu.getRepContent()); + menuButton.setMedia_id(wxMenu.getRepMediaId()); + menuButton.setRepType(wxMenu.getRepType()); + menuButton.setRepName(wxMenu.getRepName()); + menuButton.setAppid(wxMenu.getMaAppId()); + menuButton.setPagepath(wxMenu.getMaPagePath()); + menuButton.setUrl(wxMenu.getUrl()); + menuButton.setRepUrl(wxMenu.getRepUrl()); + menuButton.setRepHqUrl(wxMenu.getRepHqUrl()); + menuButton.setRepDesc(wxMenu.getRepDesc()); + menuButton.setRepThumbMediaId(wxMenu.getRepThumbMediaId()); + menuButton.setRepThumbUrl(wxMenu.getRepThumbUrl()); + } + + /** + * 保存并发布菜单 + * + * @param + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void saveAndRelease(String strWxMenu) throws WxErrorException { + Menu menu = Menu.fromJson(strWxMenu); + List buttons = menu.getButton(); + // 先删除 + baseMapper.delete(Wrappers + .query().lambda()); + WxMenu wxMenu = null; + WxMenu wxMenu1 = null; + int sort1 = 1; + int sort2 = 1; + // 入库 + for (MenuButton menuButton : buttons) { + wxMenu = new WxMenu(); + setWxMenuValue(wxMenu, menuButton); + wxMenu.setSort(sort1); + wxMenu.setParentId(CommonConstants.PARENT_ID); + baseMapper.insert(wxMenu); + menuButton.setKey(wxMenu.getId()); + sort1++; + for (MenuButton menuButton1 : menuButton.getSub_button()) { + wxMenu1 = new WxMenu(); + setWxMenuValue(wxMenu1, menuButton1); + wxMenu1.setSort(sort2); + wxMenu1.setParentId(wxMenu.getId()); + baseMapper.insert(wxMenu1); + menuButton1.setKey(wxMenu1.getId()); + sort2++; + } + } + // 创建菜单 + wxService.getMenuService().menuCreate(menu.toString()); + } + + void setWxMenuValue(WxMenu wxMenu, MenuButton menuButton) { + wxMenu.setId(menuButton.getKey()); + wxMenu.setType(menuButton.getType()); + wxMenu.setName(menuButton.getName()); + wxMenu.setUrl(menuButton.getUrl()); + wxMenu.setRepMediaId(menuButton.getMedia_id()); + wxMenu.setRepType(menuButton.getRepType()); + wxMenu.setRepName(menuButton.getRepName()); + wxMenu.setMaAppId(menuButton.getAppid()); + wxMenu.setMaPagePath(menuButton.getPagepath()); + wxMenu.setRepContent(menuButton.getRepContent()); + wxMenu.setContent(menuButton.getContent()); + wxMenu.setRepUrl(menuButton.getRepUrl()); + wxMenu.setRepHqUrl(menuButton.getRepHqUrl()); + wxMenu.setRepDesc(menuButton.getRepDesc()); + wxMenu.setRepThumbMediaId(menuButton.getRepThumbMediaId()); + wxMenu.setRepThumbUrl(menuButton.getRepThumbUrl()); + menuButton.setRepUrl(null); + menuButton.setRepDesc(null); + menuButton.setRepHqUrl(null); + menuButton.setContent(null); + menuButton.setRepContent(null); + menuButton.setRepType(null); + menuButton.setRepName(null); + menuButton.setRepThumbMediaId(null); + menuButton.setRepThumbUrl(null); + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/impl/WxMsgServiceImpl.java b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxMsgServiceImpl.java new file mode 100644 index 0000000..3cccd1e --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxMsgServiceImpl.java @@ -0,0 +1,38 @@ + +package com.starry.weichat.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.starry.weichat.entity.WxMsg; +import com.starry.weichat.entity.WxMsgVO; +import com.starry.weichat.mapper.WxMsgMapper; +import com.starry.weichat.service.WxMsgService; +import org.springframework.stereotype.Service; + +/** + * 微信消息 + * + * @author admin + * @since 2019-05-28 16:12:10 + */ +@Service +public class WxMsgServiceImpl extends ServiceImpl implements WxMsgService { + + @Override + public IPage listWxMsgMapGroup(Page page, WxMsgVO vo) { + +// LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); +// if (StrUtil.isBlankIfStr(vo.getType())) { +// queryWrapper.eq(WxMsg::getType, vo.getType()); +// } +// if (StrUtil.isBlankIfStr(vo.getReadFlag())) { +// queryWrapper.eq(WxMsg::getReadFlag, vo.getReadFlag()); +// } +// if (StrUtil.isBlankIfStr(vo.getRepType())) { +// queryWrapper.eq(WxMsg::getRepType, vo.getRepType()); +// } + + return baseMapper.listWxMsgMapGroup(page, vo); + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/service/impl/WxUserServiceImpl.java b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxUserServiceImpl.java new file mode 100644 index 0000000..9f457f1 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/service/impl/WxUserServiceImpl.java @@ -0,0 +1,243 @@ + +package com.starry.weichat.service.impl; + +import cn.binarywang.wx.miniapp.api.WxMaUserService; +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.starry.weichat.config.WxMaConfiguration; +import com.starry.weichat.constant.ConfigConstant; +import com.starry.weichat.constant.WxMaConstants; +import com.starry.weichat.entity.ThirdSession; +import com.starry.weichat.entity.WxOpenDataDTO; +import com.starry.weichat.entity.WxUser; +import com.starry.weichat.handler.SubscribeHandler; +import com.starry.weichat.mapper.WxUserMapper; +import com.starry.weichat.service.WxUserService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.WxMpUserService; +import me.chanjar.weixin.mp.api.WxMpUserTagService; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import me.chanjar.weixin.mp.bean.result.WxMpUserList; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 微信用户 + * + * @author admin + * @since 2019-03-25 15:39:39 + */ +@Slf4j +@Service +@AllArgsConstructor +public class WxUserServiceImpl extends ServiceImpl implements WxUserService { + + private final WxMpService wxService; + private final RedisTemplate redisTemplate; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateRemark(WxUser entity) throws WxErrorException { + String id = entity.getId(); + String remark = entity.getRemark(); + String openId = entity.getOpenId(); + entity = new WxUser(); + entity.setId(id); + entity.setRemark(remark); + super.updateById(entity); + WxMpUserService wxMpUserService = wxService.getUserService(); + wxMpUserService.userUpdateRemark(openId, remark); + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void tagging(String taggingType, Long tagId, String[] openIds) throws WxErrorException { + WxMpUserTagService wxMpUserTagService = wxService.getUserTagService(); + WxUser wxUser; + if ("tagging".equals(taggingType)) { + for (String openId : openIds) { + wxUser = baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(WxUser::getOpenId, openId)); + Long[] tagidList = wxUser.getTagidList(); + List list = Arrays.asList(tagidList); + list = new ArrayList<>(list); + if (!list.contains(tagId)) { + list.add(tagId); + tagidList = list.toArray(new Long[list.size()]); + wxUser.setTagidList(tagidList); + this.updateById(wxUser); + } + } + wxMpUserTagService.batchTagging(tagId, openIds); + } + if ("unTagging".equals(taggingType)) { + for (String openId : openIds) { + wxUser = baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(WxUser::getOpenId, openId)); + Long[] tagidList = wxUser.getTagidList(); + List list = Arrays.asList(tagidList); + list = new ArrayList<>(list); + if (list.contains(tagId)) { + list.remove(tagId); + tagidList = list.toArray(new Long[list.size()]); + wxUser.setTagidList(tagidList); + this.updateById(wxUser); + } + } + wxMpUserTagService.batchUntagging(tagId, openIds); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void synchroWxUser() throws WxErrorException { + // 先将已关注的用户取关 + WxUser wxUser = new WxUser(); + wxUser.setSubscribe(ConfigConstant.SUBSCRIBE_TYPE_NO); + this.baseMapper.update(wxUser, Wrappers.lambdaQuery() + .eq(WxUser::getSubscribe, ConfigConstant.SUBSCRIBE_TYPE_YES)); + WxMpUserService wxMpUserService = wxService.getUserService(); + this.recursionGet(wxMpUserService, null); + } + + /** + * 递归获取 + * + * @param nextOpenid + */ + void recursionGet(WxMpUserService wxMpUserService, String nextOpenid) throws WxErrorException { + WxMpUserList userList = wxMpUserService.userList(nextOpenid); + List listWxUser = new ArrayList<>(); + List listWxMpUser = getWxMpUserList(wxMpUserService, userList.getOpenids()); + listWxMpUser.forEach(wxMpUser -> { + WxUser wxUser = baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(WxUser::getOpenId, wxMpUser.getOpenId())); + if (wxUser == null) {// 用户未存在 + wxUser = new WxUser(); + wxUser.setSubscribeNum(1); + } + SubscribeHandler.setWxUserValue(wxUser, wxMpUser); + listWxUser.add(wxUser); + }); + this.saveOrUpdateBatch(listWxUser); + if (userList.getCount() >= 10000) { + this.recursionGet(wxMpUserService, userList.getNextOpenid()); + } + } + + /** + * 分批次获取微信粉丝信息 每批100条 + * + * @param wxMpUserService + * @param openidsList + * @return + * @throws WxErrorException + * @author + */ + private List getWxMpUserList(WxMpUserService wxMpUserService, List openidsList) throws WxErrorException { + // 粉丝openid数量 + int count = openidsList.size(); + if (count <= 0) { + return new ArrayList<>(); + } + List list = Lists.newArrayList(); + List followersInfoList; + int a = count % 100 > 0 ? count / 100 + 1 : count / 100; + for (int i = 0; i < a; i++) { + if (i + 1 < a) { + log.debug("i:{},from:{},to:{}", i, i * 100, (i + 1) * 100); + followersInfoList = wxMpUserService.userInfoList(openidsList.subList(i * 100, ((i + 1) * 100))); + if (null != followersInfoList && !followersInfoList.isEmpty()) { + list.addAll(followersInfoList); + } + } else { + log.debug("i:{},from:{},to:{}", i, i * 100, count - i * 100); + followersInfoList = wxMpUserService.userInfoList(openidsList.subList(i * 100, count)); + if (null != followersInfoList && !followersInfoList.isEmpty()) { + list.addAll(followersInfoList); + } + } + } + log.debug("本批次获取微信粉丝数:{}", list.size()); + return list; + } + + @Override + public WxUser getByOpenId(String openId) { + return this.baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(WxUser::getOpenId, openId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public WxUser loginMa(String appId, String jsCode) throws WxErrorException { + WxMaJscode2SessionResult jscode2session = WxMaConfiguration.getMaService(appId).jsCode2SessionInfo(jsCode); + WxUser wxUser = this.getByOpenId(jscode2session.getOpenid()); + if (wxUser == null) { + // 新增微信用户 + wxUser = new WxUser(); + wxUser.setAppType(ConfigConstant.WX_APP_TYPE_1); + wxUser.setOpenId(jscode2session.getOpenid()); + wxUser.setSessionKey(jscode2session.getSessionKey()); + wxUser.setUnionId(jscode2session.getUnionid()); + try { + this.save(wxUser); + } catch (DuplicateKeyException e) { + if (e.getMessage().contains("uk_appid_openid")) { + wxUser = this.getByOpenId(wxUser.getOpenId()); + } + } + } else { + // 更新SessionKey + wxUser.setAppType(ConfigConstant.WX_APP_TYPE_1); + wxUser.setOpenId(jscode2session.getOpenid()); + wxUser.setSessionKey(jscode2session.getSessionKey()); + wxUser.setUnionId(jscode2session.getUnionid()); + this.updateById(wxUser); + } + + String thirdSessionKey = UUID.randomUUID().toString(); + ThirdSession thirdSession = new ThirdSession(); + thirdSession.setAppId(appId); + thirdSession.setSessionKey(wxUser.getSessionKey()); + thirdSession.setWxUserId(wxUser.getId()); + thirdSession.setOpenId(wxUser.getOpenId()); + // 将3rd_session和用户信息存入redis,并设置过期时间 + String key = WxMaConstants.THIRD_SESSION_BEGIN + ":" + thirdSessionKey; + redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(thirdSession), WxMaConstants.TIME_OUT_SESSION, TimeUnit.HOURS); + wxUser.setSessionKey(thirdSessionKey); + return wxUser; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public WxUser saveOrUptateWxUser(WxOpenDataDTO wxOpenDataDTO) { + WxMaUserService wxMaUserService = WxMaConfiguration.getMaService(wxOpenDataDTO.getAppId()).getUserService(); + WxMaUserInfo wxMaUserInfo = wxMaUserService.getUserInfo(wxOpenDataDTO.getSessionKey(), wxOpenDataDTO.getEncryptedData(), wxOpenDataDTO.getIv()); + WxUser wxUser = new WxUser(); + BeanUtil.copyProperties(wxMaUserInfo, wxUser); + wxUser.setId(wxOpenDataDTO.getUserId()); + wxUser.setSex(wxMaUserInfo.getGender()); + wxUser.setHeadimgUrl(wxMaUserInfo.getAvatarUrl()); + baseMapper.updateById(wxUser); + wxUser = baseMapper.selectById(wxUser.getId()); + return wxUser; + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/utils/FileUtils.java b/play-weichat/src/main/java/com/starry/weichat/utils/FileUtils.java new file mode 100644 index 0000000..ee8d43a --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/utils/FileUtils.java @@ -0,0 +1,67 @@ + +package com.starry.weichat.utils; + +import cn.hutool.core.lang.UUID; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; + +/** + * file工具 + * @author admin + */ +public class FileUtils { + + /** + * 将MultipartFile转为File + * + * @param mulFile + * @return + */ + public static File multipartFileToFile(MultipartFile mulFile) throws IOException { + InputStream ins = mulFile.getInputStream(); + String fileName = mulFile.getOriginalFilename(); + String prefix = getFileNameNoEx(fileName) + UUID.fastUUID(); + String suffix = "." + getExtensionName(fileName); + File toFile = File.createTempFile(prefix, suffix); + OutputStream os = Files.newOutputStream(toFile.toPath()); + int bytesRead; + byte[] buffer = new byte[8192]; + while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + ins.close(); + return toFile; + } + + /** + * 获取文件扩展名 + */ + public static String getExtensionName(String filename) { + if ((filename != null) && (!filename.isEmpty())) { + int dot = filename.lastIndexOf('.'); + if ((dot > -1) && (dot < (filename.length() - 1))) { + return filename.substring(dot + 1); + } + } + return filename; + } + + /** + * 获取不带扩展名的文件名 + */ + public static String getFileNameNoEx(String filename) { + if ((filename != null) && (!filename.isEmpty())) { + int dot = filename.lastIndexOf('.'); + if (dot > -1) { + return filename.substring(0, dot); + } + } + return filename; + } +} \ No newline at end of file diff --git a/play-weichat/src/main/java/com/starry/weichat/utils/JsonUtils.java b/play-weichat/src/main/java/com/starry/weichat/utils/JsonUtils.java new file mode 100644 index 0000000..7e715f8 --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/utils/JsonUtils.java @@ -0,0 +1,17 @@ + +package com.starry.weichat.utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * @author admin + */ +public class JsonUtils { + public static String toJson(Object obj) { + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + return gson.toJson(obj); + } +} diff --git a/play-weichat/src/main/java/com/starry/weichat/utils/LocalDateTimeUtils.java b/play-weichat/src/main/java/com/starry/weichat/utils/LocalDateTimeUtils.java new file mode 100644 index 0000000..c73b58c --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/utils/LocalDateTimeUtils.java @@ -0,0 +1,93 @@ + +package com.starry.weichat.utils; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; + +/** + * LocalDateTime时间工具 + * @author admin + */ +public class LocalDateTimeUtils { + + public static final String YYYY = "yyyy"; + public static final String YYYYMM = "yyyyMM"; + public static final String YYYYMMDD = "yyyyMMdd"; + public static final String YYYYMMDDHH = "yyyyMMddHH"; + public static final String YYYYMMDDHHMM = "yyyyMMddHHmm"; + public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + public static final String YYYY_MM = "yyyy-MM"; + public static final String YYYY_MM_DD = "yyyy-MM-dd"; + public static final String YYYY_MM_DD_HH = "yyyy-MM-dd HH"; + public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm"; + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static final String BASE_TIME_FORMAT = "[yyyyMMddHHmmss][yyyyMMddHHmm][yyyyMMddHH][yyyyMMdd][yyyyMM][yyyy][[-][/][.]MM][[-][/][.]dd][ ][HH][[:][.]mm][[:][.]ss][[:][.]SSS]"; + + /** + * 【推荐】解析常用时间字符串,支持,并不局限于以下形式: + * [yyyy][yyyy-MM][yyyy-MM-dd][yyyy-MM-dd HH][yyyy-MM-dd HH:mm][yyyy-MM-dd HH:mm:ss][yyyy-MM-dd HH:mm:ss:SSS] + * [yyyy][yyyy/MM][yyyy/MM/dd][yyyy/MM/dd HH][yyyy/MM/dd HH:mm][yyyy/MM/dd HH:mm:ss][yyyy/MM/dd HH:mm:ss:SSS] + * [yyyy][yyyy.MM][yyyy.MM.dd][yyyy.MM.dd HH][yyyy.MM.dd HH.mm][yyyy.MM.dd HH.mm.ss][yyyy.MM.dd HH.mm.ss.SSS] + * [yyyy][yyyyMM][yyyyMMdd][yyyyMMddHH][yyyyMMddHHmm][yyyyMMddHHmmss] + * [MM-dd] + * 不支持yyyyMMddHHmmssSSS,因为本身DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")就不支持这个形式 + * + * @param timeString + * @return + */ + public static LocalDateTime parse(String timeString) { + return LocalDateTime.parse(timeString, getDateTimeFormatterByPattern(BASE_TIME_FORMAT)); + } + + /** + * 根据传进来的pattern返回LocalDateTime,自动补齐 + * + * @param timeString + * @param pattern + * @return + */ + public static LocalDateTime parseByPattern(String timeString, String pattern) { + return LocalDateTime.parse(timeString, getDateTimeFormatterByPattern(pattern)); + } + + private static DateTimeFormatter getDateTimeFormatterByPattern(String pattern) { + return new DateTimeFormatterBuilder() + .appendPattern(pattern) + .parseDefaulting(ChronoField.YEAR_OF_ERA, LocalDateTime.now().getYear()) + .parseDefaulting(ChronoField.MONTH_OF_YEAR, LocalDateTime.now().getMonthValue()) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .toFormatter(); + } + + /** + * 将timestamp转为LocalDateTime + * + * @param timestamp + * @return + */ + public static LocalDateTime timestamToDatetime(long timestamp) { + Instant instant = Instant.ofEpochMilli(timestamp); + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + + /** + * 将LocalDataTime转为timestamp + * + * @param ldt + * @return + */ + public static long datatimeToTimestamp(LocalDateTime ldt) { + ZoneId zone = ZoneId.systemDefault(); + return ldt.atZone(zone).toInstant().toEpochMilli(); + } + +} \ No newline at end of file diff --git a/play-weichat/src/main/java/com/starry/weichat/utils/ThirdSessionHolder.java b/play-weichat/src/main/java/com/starry/weichat/utils/ThirdSessionHolder.java new file mode 100644 index 0000000..fe4d49f --- /dev/null +++ b/play-weichat/src/main/java/com/starry/weichat/utils/ThirdSessionHolder.java @@ -0,0 +1,46 @@ +package com.starry.weichat.utils; + +import com.alibaba.ttl.TransmittableThreadLocal; +import com.starry.weichat.entity.ThirdSession; +import lombok.experimental.UtilityClass; + +/** + * @author thirdSession工具类 + */ +@UtilityClass +public class ThirdSessionHolder { + + private final ThreadLocal THREAD_LOCAL_THIRD_SESSION = new TransmittableThreadLocal<>(); + + /** + * 获取TTL中的thirdSession + * + * @return + */ + public ThirdSession getThirdSession() { + return THREAD_LOCAL_THIRD_SESSION.get(); + } + + /** + * TTL 设置thirdSession + * + * @param thirdSession + */ + public void setThirdSession(ThirdSession thirdSession) { + THREAD_LOCAL_THIRD_SESSION.set(thirdSession); + } + + public void clear() { + THREAD_LOCAL_THIRD_SESSION.remove(); + } + + + /** + * 获取用户商城ID + * + * @return + */ + public String getWxUserId() { + return THREAD_LOCAL_THIRD_SESSION.get().getWxUserId(); + } +} diff --git a/play-weichat/src/main/resources/mapper/weixin/WxMsgMapper.xml b/play-weichat/src/main/resources/mapper/weixin/WxMsgMapper.xml new file mode 100644 index 0000000..7b5ff16 --- /dev/null +++ b/play-weichat/src/main/resources/mapper/weixin/WxMsgMapper.xml @@ -0,0 +1,35 @@ + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..add56f1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,203 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + + + com.starry + play-with + 1.0 + pom + + + play-admin + play-common + play-generator + play-weichat + + + + + + 1.8 + + 3.5.3.2 + + 2.0.7 + + 0.9.0 + + 2.14.5 + + 5.8.26 + + 1.2.21 + + 2.15.1 + + 2.0.47 + + 2.1 + + 6.5.0 + + 2.2.11 + + 4.6.0 + + 5.6.205 + + 3.1.1 + + 2.10.1 + + + + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + cn.hutool + hutool-all + ${hutool.version} + + + + com.alibaba + druid-spring-boot-starter + ${druid-version} + + + + com.github.xiaoymin + knife4j-spring-boot-starter + ${swagger.version} + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + + + + commons-io + commons-io + ${commom-io.version} + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2-version} + + + + org.apache.velocity + velocity-engine-core + ${velocity-version} + + + + com.github.oshi + oshi-core + ${oshi-version} + + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + com.qcloud + cos_api + ${cos-version} + + + com.google.code.gson + gson + ${gson-version} + + + + com.github.gavlyukovskiy + p6spy-spring-boot-starter + 1.9.1 + + + + + com.qcloud + cos-sts_api + ${cos_sts-version} + + + + + com.starry + play-common + 1.0 + + + + com.starry + play-generator + 1.0 + + + + com.starry + play-admin + 1.0 + + + + + com.github.binarywang + weixin-java-common + ${weixin-java.version} + + + + com.github.binarywang + weixin-java-mp + ${weixin-java.version} + + + + com.github.binarywang + weixin-java-miniapp + ${weixin-java.version} + + + + com.github.binarywang + weixin-java-pay + ${weixin-java.version} + + + + + + \ No newline at end of file