first commit

This commit is contained in:
@jyuhsin
2026-01-14 14:49:41 +08:00
commit 105c955abd
4752 changed files with 911871 additions and 0 deletions
@@ -0,0 +1,24 @@
package com.budwk.app.web.commons.auth.cas;
import lombok.extern.slf4j.Slf4j;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.ActionFilter;
import org.nutz.mvc.View;
import org.nutz.mvc.impl.processor.AbstractProcessor;
@Slf4j
public class CasAuthFilter extends AbstractProcessor implements ActionFilter {
@Override
public View match(ActionContext actionContext) {
log.debug("match? maybe.");
return null;
}
@Override
public void process(ActionContext actionContext) throws Throwable {
log.debug("before doNext");
doNext(actionContext);
log.debug("after doNext");
}
}
@@ -0,0 +1,9 @@
package com.budwk.app.web.commons.auth.cas;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean
public class CasConfig{
}
@@ -0,0 +1,37 @@
package com.budwk.app.web.commons.auth.satoken;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.Mvcs;
/**
* @author wizzer@qq.com
*/
@IocBean
public class SaTokenContextImpl implements SaTokenContext {
@Override
public SaRequest getRequest() {
return new SaRequestForServlet(Mvcs.getReq());
}
@Override
public SaResponse getResponse() {
return new SaResponseForServlet(Mvcs.getResp());
}
@Override
public SaStorage getStorage() {
return new SaStorageForServlet(Mvcs.getReq());
}
@Override
public boolean matchPath(String pattern, String path) {
return false;
}
}
@@ -0,0 +1,137 @@
package com.budwk.app.web.commons.auth.satoken;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import com.budwk.app.base.constant.RedisConstant;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author wizzer@qq.com
*/
@IocBean
public class SaTokenDaoRedisImpl implements SaTokenDao {
@Inject
private RedisService redisService;
@Override
public String get(String key) {
return redisService.get(RedisConstant.TOKEN + key);
}
@Override
public void set(String key, String value, long timeout) {
if (timeout == SaTokenDao.NEVER_EXPIRE) {
redisService.set(RedisConstant.TOKEN + key, value);
} else {
redisService.setex(RedisConstant.TOKEN + key, (int) timeout, value);
}
}
@Override
public void update(String key, String value) {
long expire = this.getTimeout(key);
// -2 = 无此键
if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.set(key, value, expire);
}
@Override
public void delete(String key) {
redisService.del(RedisConstant.TOKEN + key);
}
@Override
public long getTimeout(String key) {
return redisService.ttl(RedisConstant.TOKEN + key);
}
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if (timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = this.getTimeout(key);
if (expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.set(key, this.get(key), timeout);
}
return;
}
redisService.expire(RedisConstant.TOKEN + key, (int) timeout);
}
@Override
public Object getObject(String key) {
byte[] bytes = redisService.get((RedisConstant.TOKEN + key).getBytes(StandardCharsets.UTF_8));
if (bytes != null) {
return Lang.fromBytes(bytes, Object.class);
}
return null;
}
@Override
public void setObject(String key, Object value, long timeout) {
// 判断是否为永不过期
if (timeout == SaTokenDao.NEVER_EXPIRE) {
redisService.set((RedisConstant.TOKEN + key).getBytes(StandardCharsets.UTF_8), Lang.toBytes(value));
} else {
redisService.setex((RedisConstant.TOKEN + key).getBytes(StandardCharsets.UTF_8), (int) timeout, Lang.toBytes(value));
}
}
@Override
public void updateObject(String key, Object object) {
long expire = this.getObjectTimeout(key);
// -2 = 无此键
if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setObject(key, object, expire);
}
@Override
public void deleteObject(String key) {
redisService.del((RedisConstant.TOKEN + key).getBytes(StandardCharsets.UTF_8));
}
@Override
public long getObjectTimeout(String key) {
return redisService.ttl((RedisConstant.TOKEN + key).getBytes(StandardCharsets.UTF_8));
}
@Override
public void updateObjectTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if (timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getObjectTimeout(key);
if (expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.setObject(key, this.getObject(key), timeout);
}
return;
}
redisService.expire((RedisConstant.TOKEN + key).getBytes(StandardCharsets.UTF_8), (int) timeout);
}
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
Set<String> keys = redisService.keys(RedisConstant.TOKEN + prefix + "*" + keyword + "*");
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size);
}
}
@@ -0,0 +1,33 @@
package com.budwk.app.web.commons.auth.satoken;
import cn.dev33.satoken.stp.StpInterface;
import com.budwk.app.sys.services.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import java.util.List;
/**
* @author wizzer@qq.com
*/
@IocBean
@Slf4j
public class StpInterfaceImpl implements StpInterface {
@Inject
private SysUserService sysUserService;
@Inject("refer:$ioc")
protected Ioc ioc;
@Override
public List<String> getPermissionList(Object loginId, String loginKey) {
return sysUserService.getPermissionList(Strings.sNull(loginId));
}
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
return sysUserService.getRoleCodeList(sysUserService.fetch(Strings.sNull(loginId)));
}
}
@@ -0,0 +1,23 @@
package com.budwk.app.web.commons.auth.satoken.aop;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import org.nutz.aop.InterceptorChain;
import org.nutz.aop.MethodInterceptor;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.Method;
/**
* @author wizzer@qq.com
*/
@IocBean(singleton = false)
public class SaCheckLoginInterceptor implements MethodInterceptor {
@Override
public void filter(InterceptorChain chain) throws Throwable {
Method method = chain.getCallingMethod();
SaCheckLogin at = method.getAnnotation(SaCheckLogin.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
chain.doChain();
}
}
@@ -0,0 +1,23 @@
package com.budwk.app.web.commons.auth.satoken.aop;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.nutz.aop.InterceptorChain;
import org.nutz.aop.MethodInterceptor;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.Method;
/**
* @author wizzer@qq.com
*/
@IocBean(singleton = false)
public class SaCheckPermissionInterceptor implements MethodInterceptor {
@Override
public void filter(InterceptorChain chain) throws Throwable {
Method method = chain.getCallingMethod();
SaCheckPermission at = method.getAnnotation(SaCheckPermission.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
chain.doChain();
}
}
@@ -0,0 +1,23 @@
package com.budwk.app.web.commons.auth.satoken.aop;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckRole;
import org.nutz.aop.InterceptorChain;
import org.nutz.aop.MethodInterceptor;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.Method;
/**
* @author wizzer@qq.com
*/
@IocBean(singleton = false)
public class SaCheckRoleInterceptor implements MethodInterceptor {
@Override
public void filter(InterceptorChain chain) throws Throwable {
Method method = chain.getCallingMethod();
SaCheckRole at = method.getAnnotation(SaCheckRole.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
chain.doChain();
}
}
@@ -0,0 +1,43 @@
package com.budwk.app.web.commons.auth.satoken.aop;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.aop.config.AopConfigration;
import org.nutz.ioc.aop.config.InterceptorPair;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author wizzer@qq.com
*/
@IocBean(name = "$aop_satoken")
public class SaTokenAopConfigration implements AopConfigration {
@Override
public List<InterceptorPair> getInterceptorPairList(Ioc ioc, Class<?> clazz) {
List<InterceptorPair> list = new ArrayList<InterceptorPair>();
boolean flag = true;
for (Method method : clazz.getMethods()) {
if (method.getAnnotation(SaCheckLogin.class) != null
|| method.getAnnotation(SaCheckRole.class) != null
|| method.getAnnotation(SaCheckPermission.class) != null) {
flag = false;
break;
}
}
if (flag)
return list;
list.add(new InterceptorPair(ioc.get(SaCheckLoginInterceptor.class),
new SaTokenMethodMatcher(SaCheckLogin.class)));
list.add(new InterceptorPair(ioc.get(SaCheckRoleInterceptor.class),
new SaTokenMethodMatcher(SaCheckRole.class)));
list.add(new InterceptorPair(ioc.get(SaCheckPermissionInterceptor.class),
new SaTokenMethodMatcher(SaCheckPermission.class)));
return list;
}
}
@@ -0,0 +1,23 @@
package com.budwk.app.web.commons.auth.satoken.aop;
import org.nutz.aop.MethodMatcher;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author wizzer@qq.com
*/
public class SaTokenMethodMatcher implements MethodMatcher {
protected Class<? extends Annotation> klass;
public SaTokenMethodMatcher(Class<? extends Annotation> klass) {
this.klass = klass;
}
public boolean match(Method method) {
return method.getAnnotation(klass) != null;
}
}
@@ -0,0 +1,71 @@
package com.budwk.app.web.commons.auth.service;
import cn.dev33.satoken.stp.StpUtil;
import com.budwk.app.base.utils.SysMenuUtil;
import com.budwk.app.sys.models.Sys_menu;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.List;
/**
* @author wizzer@qq.com
*/
@IocBean
@Slf4j
public class AuthService {
@Inject
private SysUserService sysUserService;
public Sys_user getLogonUser() {
return sysUserService.getUserAndMenuById(SecurityUtil.getUserId());
}
public Object getPrincipalProperty(String property) {
Sys_user user = getLogonUser();
if (user != null) {
try {
BeanInfo bi = Introspector.getBeanInfo(user.getClass());
for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
if (pd.getName().equals(property)) {
return pd.getReadMethod().invoke(user, (Object[]) null);
}
}
log.trace("Property [{}] not found in principal of type [{}]", property,
user.getClass().getName());
} catch (Exception e) {
log.trace("Error reading property [{}] from principal of type [{}]", property,
user.getClass().getName());
}
}
return null;
}
public String getLogonUserToJson() {
Sys_user sys_user = sysUserService.getUserAndMenuById(SecurityUtil.getUserId());
return Json.toJson(sys_user);
}
public String getPrincipalPropertyToJson(String property) {
Object principalProperty = getPrincipalProperty(property);
return Json.toJson(principalProperty);
}
public String getSessionId() {
return StpUtil.getTokenValue();
}
public Object getSubAppMenus(String appId) {
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
List<Sys_menu> pcTreeMenus = SysMenuUtil.createTreeMenus(menus, appId);
return pcTreeMenus;
}
}
@@ -0,0 +1,121 @@
package com.budwk.app.web.commons.auth.service;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.utils.RsaUtil;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
/**
* @author wizzer@qq.com
*/
@IocBean
@Slf4j
public class ValidateService {
@Inject
private RedisService redisService;
/**
* 获取验证码
*
* @return
*/
public NutMap getCaptcha() {
String uuid = R.UU32();
ArithmeticCaptcha captcha = new ArithmeticCaptcha(120, 40);
captcha.getArithmeticString(); // 获取运算的公式:3+2=?
String text = captcha.text();
redisService.setex(RedisConstant.UCENTER_CAPTCHA + uuid, 180, text);
return NutMap.NEW().addv("key", uuid).addv("codeUrl", captcha.toBase64());
}
/**
* 发送短信验证码
*
* @param mobile 手机号码
* @throws BaseException
*/
public void getSmsCode(String mobile) throws BaseException {
String text = R.captchaNumber(4);
String codeFromRedis = redisService.get(RedisConstant.UCENTER_SMSCODE + mobile + ":LOCK");
if (Strings.isNotBlank(codeFromRedis)) {
throw new BaseException("请1分钟之后再试");
}
// smsSendServer.sendCode(mobile, text);
log.debug("sms code:::" + text);
redisService.setex(RedisConstant.UCENTER_SMSCODE + mobile, 300, text);
redisService.setex(RedisConstant.UCENTER_SMSCODE + mobile + ":LOCK", 60, text);
}
/**
* 核验验证码
*
* @param key 验证码的key
* @param code 验证码的值
* @throws BaseException
*/
public void checkCode(String key, String code) throws BaseException {
String codeFromRedis = redisService.get(RedisConstant.UCENTER_CAPTCHA + key);
if (Strings.isBlank(code)) {
throw new BaseException("请输入验证码");
}
if (Strings.isEmpty(codeFromRedis)) {
throw new BaseException("验证码已过期");
}
if (!Strings.equalsIgnoreCase(code, codeFromRedis)) {
throw new BaseException("验证码不正确");
}
redisService.del(RedisConstant.UCENTER_CAPTCHA + key);
}
/**
* 核验短信验证码
*
* @param mobile 手机号码
* @param code 验证码值
* @throws BaseException
*/
public void checkSMSCode(String mobile, String code) throws BaseException {
String codeFromRedis = redisService.get(RedisConstant.UCENTER_SMSCODE + mobile);
if (Strings.isBlank(code)) {
throw new BaseException("请输入短信验证码");
}
if (Strings.isEmpty(codeFromRedis)) {
throw new BaseException("短信验证码已过期");
}
if (!Strings.equalsIgnoreCase(code, codeFromRedis)) {
throw new BaseException("短信验证码不正确");
}
redisService.del(RedisConstant.UCENTER_SMSCODE + mobile);
}
public String decryptPwd(String keyId, String password) throws Exception {
// 从 Redis 获取私钥
String rsaKey = RedisConstant.RSA_KEY_PREFIX + keyId;
String privateKeyStr = redisService.get(rsaKey);
if (privateKeyStr == null) {
throw new BaseException("公钥已过期,请刷新登录页面");
}
// 删除私钥(一次性使用,防重放)
redisService.del(rsaKey);
// 解码私钥
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
// 解密密码
return RsaUtil.decrypt(password, privateKey);
}
}
@@ -0,0 +1,46 @@
package com.budwk.app.web.commons.auth.utils;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
/**
* @version 1.0
* @Author zzr
* @nameAuthUtil
* @Date 2024/8/6 14:48
* @注释
*/
@Slf4j
public class AuthUtil extends StpUtil {
/**
* 判断是否包含角色
* @param roles
* @return
*/
public static boolean hasRoleOr(String... roles) {
try {
stpLogic.checkRoleOr(roles);
return true;
} catch (NotRoleException e) {
return false;
}
}
/**
* 判断是否角色是否都拥有
* @param roles
* @return
*/
public static boolean hasRoleAnd(String... roles) {
try {
stpLogic.checkRoleAnd(roles);
return true;
} catch (NotRoleException e) {
return false;
}
}
}
@@ -0,0 +1,72 @@
package com.budwk.app.web.commons.auth.utils;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import org.nutz.lang.Strings;
import java.util.Arrays;
import java.util.List;
/**
* @author wizzer@qq.com
*/
public class SecurityUtil {
/**
* 获取当前用户ID
*/
public static String getUserId() {
return Strings.sNull(StpUtil.getLoginId(""));
}
/**
* 获取当前用户登录名
*/
public static String getUserLoginname() {
return Strings.sNull(getSession().get("loginname"));
}
/**
* 获取当前用户姓名
*/
public static String getUserUsername() {
return Strings.sNull(getSession().get("username"));
}
/**
* 获取当前用户单位ID
*/
public static String getUnitId() {
return Strings.sNull(getSession().get("unitId"));
}
/**
* 获取当前用户工会ID
*/
public static String getUnionId() {
return Strings.sNull(getSession().get("unionId"));
}
/**
* 获取当前用户工会小组ID
*/
public static String getUnionGroupId() {
return Strings.sNull(getSession().get("unionGroupId"));
}
/**
* 获取当前用户协会ID
*/
public static List<String> getClubIds() {
String clubIds = getSession().get("clubIds").toString();
return Arrays.stream(clubIds.split(",")).toList();
}
/**
* 获取当前用户Session
*/
public static SaSession getSession() {
return StpUtil.getSession(true);
}
}
@@ -0,0 +1,120 @@
package com.budwk.app.web.commons.base;
import cn.hutool.core.lang.ClassScanner;
import com.budwk.app.base.annotation.DictEnum;
import com.budwk.app.base.utils.EnumUtil;
import com.budwk.app.sys.models.Sys_config;
import com.budwk.app.sys.models.Sys_route;
import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.sys.services.SysConfigService;
import com.budwk.app.sys.services.SysRouteService;
import com.budwk.app.sys.services.SysTaskService;
import com.budwk.app.task.services.TaskPlatformService;
import org.nutz.dao.Cnd;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@IocBean(create = "init")
public class Globals {
//项目路径
public static String AppRoot = "";
//项目目录
public static String AppBase = "";
//项目名称
public static String AppName = "智慧工会";
//项目LOGO
public static String AppLogo = "";
//项目域名
public static String AppDomain = "http://127.0.0.1";
//文件访问域名
public static String AppFileDomain = "";
//学校代码
public static String SchoolCode = "OTHER";
//系统自定义参数
public static NutMap MyConfig = NutMap.NEW();
//自定义路由
public static NutMap RouteMap = NutMap.NEW();
//微信map
public static NutMap WxMap = NutMap.NEW();
//枚举map
public static Map<String, List<Map<String, Object>>> EnumMap = new HashMap<>();
//是否开启单点登录
public static boolean sso = false;
@Inject
private SysConfigService sysConfigService;
@Inject
private SysRouteService sysRouteService;
@Inject
private TaskPlatformService taskPlatformService;
@Inject
private SysTaskService sysTaskService;
@Inject
private PropertiesProxy conf;
public void init() {
initSysConfig(sysConfigService);
initRoute(sysRouteService);
initTask(sysTaskService);
initEnum();
Globals.sso = conf.getBoolean("cas.enable", false);
}
public void initTask(SysTaskService sysTaskService) {
taskPlatformService.clear();
List<Sys_task> taskList = sysTaskService.query();
for (Sys_task sysTask : taskList) {
try {
if (!sysTask.isDisabled())//不存在则新建
taskPlatformService.add(sysTask.getId(), sysTask.getId(), sysTask.getJobClass(), sysTask.getCron()
, sysTask.getNote(), sysTask.getData());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void initSysConfig(SysConfigService sysConfigService) {
Globals.MyConfig.clear();
List<Sys_config> configList = sysConfigService.query();
for (Sys_config sysConfig : configList) {
switch (Strings.sNull(sysConfig.getConfigKey())) {
case "AppName" -> Globals.AppName = sysConfig.getConfigValue();
case "AppLogo" -> Globals.AppLogo = sysConfig.getConfigValue();
case "AppDomain" -> Globals.AppDomain = sysConfig.getConfigValue();
case "AppFileDomain" -> Globals.AppFileDomain = sysConfig.getConfigValue();
default -> Globals.MyConfig.put(sysConfig.getConfigKey(), sysConfig.getConfigValue());
}
}
}
public static void initRoute(SysRouteService sysRouteService) {
Globals.RouteMap.clear();
List<Sys_route> routeList = sysRouteService.query(Cnd.where("disabled", "=", false));
for (Sys_route route : routeList) {
Globals.RouteMap.put(route.getUrl(), route);
}
}
public static void initWx() {
Globals.WxMap.clear();
}
public static void initEnum() {
Globals.EnumMap.clear();
Set<Class<?>> classes = ClassScanner.scanPackageByAnnotation("com.budwk.app", DictEnum.class);
for (Class<?> aClass : classes) {
List<Map<String, Object>> list = EnumUtil.transToList(aClass);
Globals.EnumMap.put(aClass.getSimpleName(), list);
}
}
}
@@ -0,0 +1,78 @@
package com.budwk.app.web.commons.ext.beetl;
import org.beetl.core.Resource;
import org.beetl.core.misc.BeetlUtil;
import org.beetl.core.resource.ClasspathResource;
import org.beetl.core.resource.ClasspathResourceLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BeetlCustomResourceLoader extends ClasspathResourceLoader {
public Resource getResource(String key) {
Resource resource = new ClasspathResource(key, this.getChildPath(super.getRoot(), key), this);
return resource;
}
public String getResourceId(Resource resource, String id) {
// return resource == null ? id : BeetlUtil.getRelPath(resource.getId(), id);
return getRelPath(resource.getId(), id);
}
/**
* 增加判断相对路径
* 光见鬼
*/
private String getRelPath(String siblings, String resourceId) {
// 参数校验
if (resourceId == null || resourceId.isEmpty()) {
throw new RuntimeException("资源ID为空,参数错");
}
// 如果是绝对路径,直接返回(以 / 或 \ 开头)
if (resourceId.charAt(0) == '\\' || resourceId.charAt(0) == '/') {
if (BeetlUtil.isOutsideOfRoot(resourceId)) {
throw new RuntimeException("不能访问外部文件或者模板");
}
return resourceId;
}
// 处理相对路径
String baseDir = siblings;
// 如果siblings是文件路径,获取其所在目录
int lastSeparatorIndex = Math.max(siblings.lastIndexOf('/'), siblings.lastIndexOf('\\'));
if (lastSeparatorIndex > 0) {
baseDir = siblings.substring(0, lastSeparatorIndex + 1);
}
// 分割路径
String[] parts = resourceId.replace('\\', '/').split("/");
List<String> baseParts = new ArrayList<>(
Arrays.asList(baseDir.replace('\\', '/').split("/"))
);
baseParts.removeIf(String::isEmpty);
// 处理 ../ 和 ./
for (String part : parts) {
if ("..".equals(part)) {
if (!baseParts.isEmpty()) {
baseParts.remove(baseParts.size() - 1);
}
} else if (!".".equals(part) && !part.isEmpty()) {
baseParts.add(part);
}
}
// 组合最终路径
String result = "/" + String.join("/", baseParts);
// 检查是否访问外部文件
if (BeetlUtil.isOutsideOfRoot(result)) {
throw new RuntimeException("不能访问外部文件或者模板");
}
return result;
}
}
@@ -0,0 +1,13 @@
package com.budwk.app.web.commons.ext.beetl;
import org.beetl.core.Format;
import org.nutz.lang.Strings;
/**
* Created by wizzer on 2018/1/23.
*/
public class FileSizeFormat implements Format {
public Object format(Object data, String pattern) {
return Strings.formatSizeForReadBy1024(Long.valueOf(Strings.sNull(data)));
}
}
@@ -0,0 +1,25 @@
package com.budwk.app.web.commons.ext.beetl;
import org.beetl.core.Format;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.nutz.lang.Strings;
/**
* Created by wizzer on 2018/2/6.
*/
public class Html2TxtFormat implements Format {
public Object format(Object data, String pattern) {
if (data == null) {
return "";
}
Document document= Jsoup.parse(Strings.sNull(data));
String s = document.text();
if (pattern != null && s.length() > Integer.valueOf(pattern)) {
return s.substring(0, Integer.valueOf(pattern));
}
return s;
}
}
@@ -0,0 +1,16 @@
package com.budwk.app.web.commons.ext.beetl;
/**
* Created by wizzer on 2017/2/8.
*/
import org.beetl.core.Format;
import org.nutz.lang.Strings;
public class HtmlEscapeFormat implements Format {
public Object format(Object data, String pattern) {
return Strings.escapeHtml(String.valueOf(data == null ? "" : data));
}
}
@@ -0,0 +1,240 @@
package com.budwk.app.web.commons.ext.beetl;
import lombok.extern.slf4j.Slf4j;
import org.beetl.core.Resource;
import org.beetl.core.Template;
import org.beetl.core.exception.BeetlException;
import org.beetl.core.tag.Tag;
import org.nutz.lang.random.R;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* include Vue组件标签
* 慎用。。。。
*/
@Slf4j
public class IncludeVueTag extends Tag {
private static final String SCRIPT_START = "<script>";
private static final String SCRIPT_END = "</script>";
private static final String TEMPLATE_START = "<template>";
private static final String TEMPLATE_END = "</template>";
private static final String STYLE_START = "<style>";
private static final String STYLE_END = "</style>";
private static final Pattern NAME_PATTERN = Pattern.compile("name:\\s*['\"]([^'\"]+)['\"]");
private static final Pattern EXPORT_PATTERN = Pattern.compile("export\\s+default\\s*[{]");
//缓存一下 避免每次都转换 虽然没什么耗时
private static final ConcurrentHashMap<String, CacheEntry> COMPONENT_CACHE = new ConcurrentHashMap<>();
static class CacheEntry {
final String content; // 编译后的内容
final String fileHash; // 文件内容的哈希值
CacheEntry(String content, String fileHash) {
this.content = content;
this.fileHash = fileHash;
}
}
@Override
public void render() {
try {
String resourceId = getRelResourceId();
String result = getCompiledComponent(resourceId);
ctx.byteWriter.writeString(result);
} catch (Exception e) {
handleError("Failed to process Vue component", e);
}
}
private String getCompiledComponent(String resourceId) {
// 读取文件内容
String fileContent = readVueFile(resourceId);
String currentHash = md5Hex(fileContent);
// 检查缓存
// CacheEntry cached = COMPONENT_CACHE.get(resourceId);
// if (cached != null && cached.fileHash.equals(currentHash)) {
// return cached.content;
// }
// 如果缓存不存在或文件已变化,重新编译
String compiledContent = processVueContent(fileContent);
COMPONENT_CACHE.put(resourceId, new CacheEntry(compiledContent, currentHash));
return compiledContent;
}
private String readVueFile(String resourceId) {
Template t = gt.getTemplate(resourceId, ctx);
t.binding(ctx.globalVar);
return t.render();
}
private String md5Hex(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
log.error("Failed to generate MD5", e);
return String.valueOf(input.hashCode());
}
}
// 获取路径
protected String getRelResourceId() {
if (args == null || args.length == 0 || args[0] == null) {
throw new RuntimeException("Vue component path is required");
}
Resource sibling = ctx.getResource();
return gt.getResourceLoader().getResourceId(sibling, args[0].toString());
}
private String processVueContent(String content) {
try {
String script = extractScript(content);
String componentName = extractComponentName(script);
return generateJsComponent(
extractTemplate(content),
processScript(script, componentName),
extractStyle(content),
componentName
);
} catch (Exception e) {
throw new RuntimeException("Failed to process Vue content", e);
}
}
private String extractTemplate(String content) {
String template = extractBetween(content, "<template>", "</template>");
if (template.isEmpty()) {
throw new RuntimeException("Template section is required in Vue component");
}
return template
.replace("`", "\\`")
.replace("${", "\\${");
}
private String extractComponentName(String script) {
Matcher matcher = NAME_PATTERN.matcher(script);
if (matcher.find()) {
return matcher.group(1);
}
// 如果没有找到名称 直接抛异常 为什么不写name呢
String relResourceId = getRelResourceId();
throw new BeetlException(BeetlException.ERROR, relResourceId + "没有name属性-----------");
}
private String extractScript(String content) {
String script = extractBetween(content, SCRIPT_START, SCRIPT_END);
if (script.isEmpty()) {
throw new RuntimeException("Script section is required in Vue component");
}
return script;
}
private String processScript(String script, String componentName) {
// 使用正则表达式替换 export default
return EXPORT_PATTERN.matcher(script)
.replaceFirst("const " + componentName + " = {");
}
private String extractStyle(String content) {
String style = extractBetween(content, STYLE_START, STYLE_END);
return style.replace("`", "\\`");
}
private String extractBetween(String content, String start, String end) {
if (content == null || content.isEmpty()) {
return "";
}
int startIndex = content.indexOf(start);
if (startIndex == -1) {
return "";
}
int endIndex = content.indexOf(end, startIndex + start.length());
if (endIndex == -1) {
return "";
}
return content.substring(startIndex + start.length(), endIndex).trim();
}
private String generateJsComponent(String template, String script, String style, String name) {
return """
<script>
%s
%s.template=`%s`;
(function(){
const styleId='vue-style-%s-%s';
function addStyle() {
if(!document.getElementById(styleId)) {
const style=document.createElement('style');
style.id=styleId;
style.textContent=`%s`;
document.head.appendChild(style);
}
}
// 立即添加样式
addStyle();
// 保存原始的生命周期方法
const originalMounted = %s.mounted;
const originalDestroyed = %s.destroyed;
// 扩展 mounted 钩子,确保样式存在
%s.mounted = function() {
addStyle();
if(originalMounted) originalMounted.call(this);
};
// 扩展 destroyed 钩子,检查是否还有其他实例在使用样式
%s.destroyed = function() {
if(originalDestroyed) originalDestroyed.call(this);
// 检查是否还有其他实例在使用该组件
setTimeout(() => {
const instances = document.querySelectorAll('[data-component="%s"]');
if(instances.length === 0) {
const styleEl = document.getElementById(styleId);
if(styleEl) document.head.removeChild(styleEl);
}
}, 0);
};
})();</script>
""".formatted(
script.trim(),
name,
template.trim().replaceAll("template-slot", "template"), // 将临时标签改回来
name,
R.UU32(),
style.trim(),
name, name, name, name, name
);
}
private void handleError(String message, Exception e) {
log.error(message, e);
try {
ctx.byteWriter.writeString(
String.format(
"<script>console.error('%s: %s');</script>",
message.replace("'", "\\'"),
e.getMessage().replace("'", "\\'")
)
);
} catch (IOException ioe) {
throw new RuntimeException("Failed to write error message", ioe);
}
}
}
@@ -0,0 +1,22 @@
package com.budwk.app.web.commons.ext.beetl;
import org.beetl.core.Format;
import org.nutz.lang.Strings;
/**
* Created by wizzer on 2018/2/6.
*/
public class StrlenFormat implements Format {
public Object format(Object data, String pattern) {
if (data == null) {
return "";
}
String s = Strings.sNull(data);
if (pattern != null && s.length() > Integer.valueOf(pattern)) {
return s.substring(0, Integer.valueOf(pattern));
}
return s;
}
}
@@ -0,0 +1,49 @@
package com.budwk.app.web.commons.ext.handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 错误页拦截器,登陆后台显示友好提示
*/
@IocBean
public class WkErrorPageHandler extends ErrorPageErrorHandler {
private static final Log log = Logs.get();
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (response.getStatus() == 403 || response.getStatus() == 404 || response.getStatus() == 500) {
try {
if (isAjax(request)) {
response.getWriter().write(Json.toJson(new NutMap("code", "-1").setv("msg", response.getStatus() + " error")));
return;
} else {
request.setAttribute("original_request_uri", request.getRequestURI());
RequestDispatcher rd = request.getRequestDispatcher("/platform/home/" + response.getStatus());
rd.forward(request, response);
return;
}
} catch (ServletException e) {
log.error(e);
}
}
super.handle(target, baseRequest, request, response);
}
private boolean isAjax(ServletRequest req) {
String value = ((HttpServletRequest) req).getHeader("X-Requested-With");
return value != null && "XMLHttpRequest".equalsIgnoreCase(value.trim());
}
}
@@ -0,0 +1,54 @@
package com.budwk.app.web.commons.ext.pubsub;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.sys.services.SysConfigService;
import com.budwk.app.sys.services.SysRouteService;
import com.budwk.app.web.commons.base.Globals;
import org.nutz.integration.jedis.pubsub.PubSub;
import org.nutz.integration.jedis.pubsub.PubSubService;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.log.Log;
import org.nutz.log.Logs;
/**
* 订阅发布用于更新所有实例的 Globals变量
*/
@IocBean(create = "init")
public class WebPubSub implements PubSub {
private static final Log log = Logs.get();
@Inject
protected PubSubService pubSubService;
@Inject
protected SysConfigService sysConfigService;
@Inject
protected SysRouteService sysRouteService;
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected PropertiesProxy conf;
public void init() {
pubSubService.reg(RedisConstant.PLATFORM_REDIS_PREFIX+"web:platform", this);
}
@Override
public void onMessage(String channel, String message) {
log.debug("WebPubSub onMessage::" + message);
switch (message) {
case "sys_config":
Globals.initSysConfig(sysConfigService);
break;
case "sys_route":
Globals.initRoute(sysRouteService);
break;
case "sys_wx":
Globals.initWx();
break;
}
}
}
@@ -0,0 +1,103 @@
//package com.budwk.app.web.commons.ext.sms;
//
//import com.budwk.app.base.exception.BaseException;
//import com.tencentcloudapi.common.Credential;
//import com.tencentcloudapi.common.exception.TencentCloudSDKException;
//import com.tencentcloudapi.common.profile.ClientProfile;
//import com.tencentcloudapi.common.profile.HttpProfile;
//import com.tencentcloudapi.sms.v20190711.SmsClient;
//import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
//import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;
//import org.nutz.ioc.impl.PropertiesProxy;
//import org.nutz.ioc.loader.annotation.Inject;
//import org.nutz.ioc.loader.annotation.IocBean;
//import org.nutz.log.Log;
//import org.nutz.log.Logs;
//
///**
// * 短信服务
// *
// * @author wizzer@qq.com
// */
//@IocBean
//public class SmsService {
// private static final Log log = Logs.get();
// @Inject
// private PropertiesProxy conf;
//
// /**
// * 发送短信验证码
// *
// * @param mobile 手机号
// * @param text 验证码
// * @return true发送成功
// * @throws BaseException
// */
// public boolean sendCode(String mobile, String text) throws BaseException {
// try {
// if (!conf.getBoolean("sms.enabled")) {
// return true;
// }
// Credential cred = new Credential(conf.get("sms.tencent.secret-id"), conf.get("sms.tencent.secret-key"));
//
// HttpProfile httpProfile = new HttpProfile();
// httpProfile.setEndpoint("sms.tencentcloudapi.com");
//
// ClientProfile clientProfile = new ClientProfile();
// clientProfile.setHttpProfile(httpProfile);
//
// SmsClient client = new SmsClient(cred, "", clientProfile);
//
// SendSmsRequest req = new SendSmsRequest();
// String[] phoneNumberSet1 = {"+86"+mobile};
// req.setPhoneNumberSet(phoneNumberSet1);
// String[] templateParamSet1 = {text};
// req.setTemplateParamSet(templateParamSet1);
// req.setTemplateID(conf.get("sms.tencent.tpl.code"));
// req.setSmsSdkAppid(conf.get("sms.tencent.appid"));
// req.setSign(conf.get("sms.tencent.sign"));
// SendSmsResponse resp = client.SendSms(req);
// log.debug(SendSmsResponse.toJsonString(resp));
// return true;
// } catch (TencentCloudSDKException tencentCloudSDKException) {
// throw new BaseException(tencentCloudSDKException.getMessage());
// }
// }
//
// /**
// * 发送短信通知
// *
// * @param mobile 手机号码(最多200)
// * @param param 模板参数值
// * @return
// * @throws BaseException
// */
// public boolean sendMsg(String[] mobile, String[] param) throws BaseException {
// try {
// if (!conf.getBoolean("sms.enabled")) {
// return true;
// }
// Credential cred = new Credential(conf.get("sms.tencent.secret-id"), conf.get("sms.tencent.secret-key"));
//
// HttpProfile httpProfile = new HttpProfile();
// httpProfile.setEndpoint("sms.tencentcloudapi.com");
//
// ClientProfile clientProfile = new ClientProfile();
// clientProfile.setHttpProfile(httpProfile);
//
// SmsClient client = new SmsClient(cred, "", clientProfile);
//
// SendSmsRequest req = new SendSmsRequest();
// req.setPhoneNumberSet(mobile);
// req.setTemplateParamSet(param);
// req.setTemplateID(conf.get("sms.tencent.tpl.msg"));
// req.setSmsSdkAppid(conf.get("sms.tencent.appid"));
// req.setSign(conf.get("sms.tencent.sign"));
// SendSmsResponse resp = client.SendSms(req);
// log.debug(SendSmsResponse.toJsonString(resp));
// return true;
// } catch (TencentCloudSDKException tencentCloudSDKException) {
// throw new BaseException(tencentCloudSDKException.getMessage());
// }
// }
//}
@@ -0,0 +1,52 @@
package com.budwk.app.web.commons.ext.websocket;
import org.nutz.integration.jedis.JedisAgent;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.plugins.mvc.websocket.WsRoomProvider;
import redis.clients.jedis.Jedis;
import java.util.Set;
/**
* Redis聊天室实现
*/
public class WkJedisRoomProvider implements WsRoomProvider {
private static final Log log = Logs.get();
protected JedisAgent jedisAgent;
protected int RedisKeySessionTTL;
public WkJedisRoomProvider(JedisAgent jedisAgent, int RedisKeySessionTTL) {
this.jedisAgent = jedisAgent;
this.RedisKeySessionTTL = RedisKeySessionTTL;
}
public Set<String> wsids(String room) {
try (Jedis jedis = jedisAgent.getResource()) {
return jedis.smembers(room);
}
}
/**
* 加入房间
* @param room
* @param wsid
*/
public void join(String room, String wsid) {
try (Jedis jedis = jedisAgent.getResource()) {
jedis.sadd(room, wsid);
jedis.expire(room,RedisKeySessionTTL);//每次加入的时候时间有效期重置?
}
}
/**
* 离开房间
* @param room
* @param wsid
*/
public void left(String room, String wsid) {
try (Jedis jedis = jedisAgent.getResource()) {
jedis.srem(room, wsid);
}
}
}
@@ -0,0 +1,97 @@
package com.budwk.app.web.commons.ext.websocket;
import com.budwk.app.base.constant.RedisConstant;
import org.nutz.integration.jedis.JedisAgent;
import org.nutz.integration.jedis.pubsub.PubSub;
import org.nutz.integration.jedis.pubsub.PubSubService;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.plugins.mvc.websocket.AbstractWsEndpoint;
import org.nutz.plugins.mvc.websocket.NutWsConfigurator;
import org.nutz.plugins.mvc.websocket.WsHandler;
import redis.clients.jedis.*;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
@ServerEndpoint(value = "/websocket", configurator = NutWsConfigurator.class)
@IocBean(create = "init") // 使用NutWsConfigurator的必备条件
public class WkWebSocket extends AbstractWsEndpoint implements PubSub {
protected static final Log log = Logs.get();
@Inject
protected PubSubService pubSubService;
@Inject
protected JedisAgent jedisAgent;
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject("java:$conf.getInt('security.timeout')")
private int REDIS_KEY_SESSION_TTL;
public WsHandler createHandler(Session session, EndpointConfig config) {
return ioc.get(WkWsHandler.class);
}
public void init() {
roomProvider = new WkJedisRoomProvider(jedisAgent, REDIS_KEY_SESSION_TTL);
if (jedisAgent.isClusterMode()) {
JedisCluster jedisCluster = jedisAgent.getJedisClusterWrapper().getJedisCluster();
List<String> keys=new ArrayList<>();
for (JedisPool pool : jedisCluster.getClusterNodes().values()) {
try (Jedis jedis = pool.getResource()) {
ScanParams match = new ScanParams().match(RedisConstant.REDIS_KEY_WSROOM + "*");
ScanResult<String> scan = null;
do {
scan = jedis.scan(scan == null ? ScanParams.SCAN_POINTER_START : scan.getStringCursor(), match);
keys.addAll(scan.getResult());
} while (!scan.isCompleteIteration());
}
}
try (Jedis jedis = jedisAgent.getResource()) {
for (String key : keys) {
switch (jedis.type(key)) {
case "none":
break;
case "set":
break;
default:
jedis.del(key);
}
}
}
} else {
try (Jedis jedis = jedisAgent.getResource()) {
ScanParams match = new ScanParams().match(RedisConstant.REDIS_KEY_WSROOM + "*");
ScanResult<String> scan = null;
do {
scan = jedis.scan(scan == null ? ScanParams.SCAN_POINTER_START : scan.getStringCursor(), match);
for (String key : scan.getResult()) {
switch (jedis.type(key)) {
case "none":
break;
case "set":
break;
default:
jedis.del(key);
}
}
} while (!scan.isCompleteIteration());
}
}
pubSubService.reg(RedisConstant.REDIS_KEY_WSROOM + "*", this);
}
public void onMessage(String channel, String message) {
if (log.isDebugEnabled())
log.debugf("GET PubSub channel=%s msg=%s", channel, message);
each(channel, (index, session, length) -> session.getAsyncRemote().sendText(message));
}
}
@@ -0,0 +1,32 @@
package com.budwk.app.web.commons.ext.websocket;
import com.budwk.app.base.constant.RedisConstant;
import org.nutz.integration.jedis.RedisService;
import org.nutz.integration.jedis.pubsub.PubSubService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
@IocBean
public class WkWebSocketUtil {
@Inject
private PubSubService pubSubService;
@Inject
private RedisService redisService;
public void fire(String loginName, String message) {
ScanParams match = new ScanParams().match(RedisConstant.REDIS_KEY_WSROOM + loginName + ":*");
ScanResult<String> scan = null;
do {
scan = redisService.scan(scan == null ? ScanParams.SCAN_POINTER_START : scan.getStringCursor(), match);
for (String key : scan.getResult()) {
// NutMap data = NutMap.NEW().addv("action", "h5-scan-code-signature").addv("value", url).addv("id", id);
pubSubService.fire(key, message);
}
} while (!scan.isCompleteIteration());
}
}
@@ -0,0 +1,67 @@
package com.budwk.app.web.commons.ext.websocket;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.sys.services.SysMsgService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.plugins.mvc.websocket.handler.SimpleWsHandler;
@IocBean
public class WkWsHandler extends SimpleWsHandler {
private static final Log log = Logs.get();
@Inject
private SysMsgService sysMsgService;
@Override
public void join(NutMap req) {
join(req.getString("room"));
}
@Override
public void left(NutMap req) {
left(req.getString("room"));
}
@Override
public void join(String room) {
if (!Strings.isBlank(room)) {
room = RedisConstant.REDIS_KEY_WSROOM + room;
log.debugf("session(id=%s) join room(name=%s)", session.getId(), room);
roomProvider.join(room, session.getId());
//获取用户的未读消息数量及列表
sysMsgService.getMsg(room.split(":")[2]);
}
}
@Override
public void left(String room) {
if (!Strings.isBlank(room)) {
room = RedisConstant.REDIS_KEY_WSROOM + room;
log.debugf("session(id=%s) left room(name=%s)", session.getId(), room);
roomProvider.left(room, session.getId());
}
}
/**
* 获取消息
* @param req
*/
public void getMessage(NutMap req) {
String room = req.getString("room");
if (!Strings.isBlank(room)) {
room = RedisConstant.REDIS_KEY_WSROOM + room;
log.debugf("session(id=%s) getMessage room(name=%s)", session.getId(), room);
//获取用户的未读消息数量及列表
sysMsgService.getMsg(room.split(":")[2]);
}
}
@Override
public void depose() {
//覆盖原生写法,因为room= loginname + httpSessionId 和聊天室的机制不一样,不覆盖的话功能会异常
}
}
@@ -0,0 +1,41 @@
package com.budwk.app.web.commons.filter;
import com.budwk.app.sys.models.Sys_route;
import com.budwk.app.web.commons.base.Globals;
import org.nutz.lang.Strings;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by Wizzer on 2016/7/31.
*/
public class RouteFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req2 = (HttpServletRequest) req;
HttpServletResponse res2 = (HttpServletResponse) res;
res2.setCharacterEncoding("utf-8");
req2.setCharacterEncoding("utf-8");
Sys_route route = Globals.RouteMap.getAs(Strings.sNull(req2.getRequestURI()).replace(Globals.AppBase, ""), Sys_route.class);
if (route != null) {
if ("show".equals(route.getType())) {
res2.sendRedirect(route.getToUrl());
} else {
req2.getRequestDispatcher(route.getToUrl()).forward(req2, res2);
}
} else chain.doFilter(req2, res2);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
}
@@ -0,0 +1,57 @@
package com.budwk.app.web.commons.filter;
import org.nutz.boot.AppContext;
import org.nutz.boot.starter.WebFilterFace;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@IocBean
public class RouteFilterStarter implements WebFilterFace {
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected PropertiesProxy conf;
@Inject
protected AppContext appContext;
public String getName() {
return "routeFilterStarter";
}
public String getPathSpec() {
return "/*";
}
public EnumSet<DispatcherType> getDispatches() {
return EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
}
@IocBean(name="routeFilter")
public RouteFilter createRouteFilter() {
return new RouteFilter();
}
public Filter getFilter() {
return ioc.get(RouteFilter.class, "routeFilter");
}
public Map<String, String> getInitParameters() {
return new HashMap<>();
}
public int getOrder() {
return 11;
}
}
@@ -0,0 +1,262 @@
//package com.budwk.app.web.commons.filter.cas;
//
//import cn.hutool.core.util.ArrayUtil;
//import cn.hutool.core.util.StrUtil;
//import com.budwk.app.web.commons.base.Globals;
//import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl;
//import org.jasig.cas.client.authentication.GatewayResolver;
//import org.jasig.cas.client.util.AbstractCasFilter;
//import org.jasig.cas.client.util.CommonUtils;
//import org.jasig.cas.client.util.ReflectUtils;
//import org.jasig.cas.client.validation.Assertion;
//
//import javax.servlet.*;
//import javax.servlet.http.Cookie;
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import javax.servlet.http.HttpSession;
//import java.io.IOException;
//import java.net.URLEncoder;
//import java.nio.charset.StandardCharsets;
//import java.util.HashMap;
//import java.util.Map;
//
//public class CasAuthenticationFilter extends AbstractCasFilter {
//
// /**
// * The URL to the CAS Server login.
// */
// private String casServerLoginUrl;
//
// /**
// * Whether to send the renew request or not.
// */
// private boolean renew = false;
//
// /**
// * Whether to send the gateway request or not.
// */
// private boolean gateway = false;
//
// /**
// * The method used by the CAS server to send the user back to the application.
// */
// private String method;
//
// private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
//
// private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
//
// private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;
//
// private static final Map<String, Class<? extends UrlPatternMatcherStrategy>> PATTERN_MATCHER_TYPES =
// new HashMap<String, Class<? extends UrlPatternMatcherStrategy>>();
//
// static {
// PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);
// PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);
// PATTERN_MATCHER_TYPES.put("FULL_REGEX", EntireRegionRegexUrlPatternMatcherStrategy.class);
// PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);
// }
//
// public CasAuthenticationFilter() {
// this(Protocol.CAS2);
// }
//
// protected CasAuthenticationFilter(final Protocol protocol) {
// super(protocol);
// }
//
// @Override
// protected void initInternal(final FilterConfig filterConfig) throws ServletException {
// if (!isIgnoreInitConfiguration()) {
// super.initInternal(filterConfig);
//
// final String loginUrl = getString(ConfigurationKeys.CAS_SERVER_LOGIN_URL);
// if (loginUrl != null) {
// setCasServerLoginUrl(loginUrl);
// } else {
// setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));
// }
//
// setRenew(getBoolean(ConfigurationKeys.RENEW));
// setGateway(getBoolean(ConfigurationKeys.GATEWAY));
// setMethod(getString(ConfigurationKeys.METHOD));
//
// final String ignorePattern = getString(ConfigurationKeys.IGNORE_PATTERN);
// final String ignoreUrlPatternType = getString(ConfigurationKeys.IGNORE_URL_PATTERN_TYPE);
//
// if (ignorePattern != null) {
// final Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);
// if (ignoreUrlMatcherClass != null) {
// this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName());
// } else {
// try {
// logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);
// this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType);
// } catch (final IllegalArgumentException e) {
// logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e);
// }
// }
// if (this.ignoreUrlPatternMatcherStrategyClass != null) {
// this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);
// }
// }
//
// final Class<? extends GatewayResolver> gatewayStorageClass = getClass(ConfigurationKeys.GATEWAY_STORAGE_CLASS);
//
// if (gatewayStorageClass != null) {
// setGatewayStorage(ReflectUtils.newInstance(gatewayStorageClass));
// }
//
// final Class<? extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
//
// if (authenticationRedirectStrategyClass != null) {
// this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass);
// }
// }
// }
//
// @Override
// public void init() {
// super.init();
//
// final String message = String.format(
// "one of %s and %s must not be null.",
// ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(),
// ConfigurationKeys.CAS_SERVER_URL_PREFIX.getName());
//
// CommonUtils.assertNotNull(this.casServerLoginUrl, message);
// }
//
// @Override
// public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
// final FilterChain filterChain) throws IOException, ServletException {
//
// final HttpServletRequest request = (HttpServletRequest) servletRequest;
// final HttpServletResponse response = (HttpServletResponse) servletResponse;
//
// boolean isLocalLogin = false;
//
// Cookie[] cookies = request.getCookies();
// if(ArrayUtil.isNotEmpty(cookies)){
// for (Cookie cookie : cookies) {
// String name = cookie.getName();
// if("saToken".equals(name) && StrUtil.isNotBlank(cookie.getValue())){
// isLocalLogin = true;
// break;
// }
// }
// }
//
//// logger.debug("本地登录状态:" + isLogin);
// if (isLocalLogin) {
// logger.debug("本地已登录登录,无需cas认证");
// filterChain.doFilter(request, response);
// return;
// }
//
//
// if (isRequestUrlExcluded(request)) {
// logger.debug("Request is ignored.");
// filterChain.doFilter(request, response);
// return;
// }
//
// final HttpSession session = request.getSession(false);
// final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
//
// if (assertion != null) {
// filterChain.doFilter(request, response);
// return;
// }
//
// final String serviceUrl = constructServiceUrl(request, response);
// final String ticket = retrieveTicketFromRequest(request);
// final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
//
// if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
// filterChain.doFilter(request, response);
// return;
// }
//
// final String modifiedServiceUrl;
//
// logger.debug("no ticket and no assertion found");
// if (this.gateway) {
// logger.debug("setting gateway attribute in session");
// modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
// } else {
// modifiedServiceUrl = serviceUrl;
// }
//
// logger.debug("Constructed service url: {}", modifiedServiceUrl);
//
// //回调到统一入口
// String cas_callback = Globals.AppDomain + "/platform/login/doCasLogin";
//
// String finalModifiedServiceUrl = cas_callback + "?redirect=" + URLEncoder.encode(modifiedServiceUrl, StandardCharsets.UTF_8);
//// String finalModifiedServiceUrl = cas_callback;
//
// final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
// getProtocol().getServiceParameterName(), finalModifiedServiceUrl, this.renew, this.gateway, this.method);
//
// logger.debug("redirecting to \"{}\"", urlToRedirectTo);
// this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
//
//
// }
//
// public final void setRenew(final boolean renew) {
// this.renew = renew;
// }
//
// public final void setGateway(final boolean gateway) {
// this.gateway = gateway;
// }
//
// public void setMethod(final String method) {
// this.method = method;
// }
//
// public final void setCasServerUrlPrefix(final String casServerUrlPrefix) {
// setCasServerLoginUrl(CommonUtils.addTrailingSlash(casServerUrlPrefix) + "login");
// }
//
// public final void setCasServerLoginUrl(final String casServerLoginUrl) {
// this.casServerLoginUrl = casServerLoginUrl;
// }
//
// public final void setGatewayStorage(final GatewayResolver gatewayStorage) {
// this.gatewayStorage = gatewayStorage;
// }
//
// private boolean isRequestUrlExcluded(final HttpServletRequest request) {
// if (this.ignoreUrlPatternMatcherStrategyClass == null) {
// return false;
// }
//
// String requestURI = request.getRequestURI();
// String queryString = request.getQueryString();
//
// if (queryString != null) {
// return this.ignoreUrlPatternMatcherStrategyClass.matches(requestURI + "?" + queryString);
// } else {
// return this.ignoreUrlPatternMatcherStrategyClass.matches(requestURI);
// }
//
//
//// final StringBuffer urlBuffer = request.getRequestURL();
//// if (request.getQueryString() != null) {
//// urlBuffer.append("?").append(request.getQueryString());
//// }
//// final String requestUri = urlBuffer.toString();
//// return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);
// }
//
// public final void setIgnoreUrlPatternMatcherStrategyClass(
// final UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass) {
// this.ignoreUrlPatternMatcherStrategyClass = ignoreUrlPatternMatcherStrategyClass;
// }
//
//}
@@ -0,0 +1,88 @@
//package com.budwk.app.web.commons.filter.cas;
//
//import org.jasig.cas.client.authentication.AuthenticationFilter;
//import org.jasig.cas.client.authentication.UrlPatternMatcherStrategy;
//import org.nutz.boot.AppContext;
//import org.nutz.boot.starter.WebFilterFace;
//import org.nutz.ioc.Ioc;
//import org.nutz.ioc.impl.PropertiesProxy;
//import org.nutz.ioc.loader.annotation.Inject;
//import org.nutz.ioc.loader.annotation.IocBean;
//
//import javax.servlet.DispatcherType;
//import javax.servlet.Filter;
//import java.util.ArrayList;
//import java.util.EnumSet;
//import java.util.Map;
//
//@IocBean
//public class CasAuthenticationFilterStarter implements WebFilterFace {
//
// @Inject("refer:$ioc")
// protected Ioc ioc;
//
// @Inject
// protected PropertiesProxy conf;
//
// @Inject
// protected AppContext appContext;
//
// @Override
// public String getName() {
// return "CasAuthenticationFilterStarter";
// }
//
// @Override
// public String getPathSpec() {
// return "/*";
// }
//
// @Override
// public EnumSet<DispatcherType> getDispatches() {
// return EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
// }
//
// @IocBean(name = "casFilter")
// public CasAuthenticationFilter createCasFilter() {
// CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
// authenticationFilter.setCasServerUrlPrefix(conf.get("cas.server-url-prefix"));
// authenticationFilter.setCasServerLoginUrl(conf.get("cas.server-login-url"));
// authenticationFilter.setServerName(conf.get("cas.client-host-url"));
// authenticationFilter.setIgnoreInitConfiguration(true);
//
// ArrayList<String> urls = new ArrayList<>() {{
// add("/platform/home/404");
// add("/platform/home/403");
// add("/platform/home/500");
// add("/platform/home/unknownAccountError");
// add("/platform/login");
// add("/platform/login/doLogin");
// add("/platform/login/doCasLogin");
// add("/platform/login/logout");
// add("/error");
// add("/assets");
// }};
// UrlPatternMatcherStrategy urlPatternMatcherStrategy = new SimpleUrlPatternMatcherStrategy(urls);
// authenticationFilter.setIgnoreUrlPatternMatcherStrategyClass(urlPatternMatcherStrategy);
// return authenticationFilter;
// }
//
// @Override
// public Filter getFilter() {
// boolean enable = conf.getBoolean("cas.enable", false);
// if (!enable) {
// return null;
// }
// return ioc.get(AuthenticationFilter.class, "casFilter");
// }
//
// @Override
// public Map<String, String> getInitParameters() {
// return Map.of();
// }
//
// @Override
// public int getOrder() {
// return 14;
// }
//}
@@ -0,0 +1,83 @@
//package com.budwk.app.web.commons.filter.cas;
//
//import org.jasig.cas.client.session.SingleSignOutFilter;
//import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
//import org.nutz.boot.starter.WebFilterFace;
//import org.nutz.boot.starter.impl.WebFilterReg;
//import org.nutz.ioc.impl.PropertiesProxy;
//import org.nutz.ioc.loader.annotation.Inject;
//import org.nutz.ioc.loader.annotation.IocBean;
//
//import javax.servlet.DispatcherType;
//import java.util.HashMap;
//import java.util.HashSet;
//
//@IocBean(create = "init")
//public class CasFilterConfig {
//
// @Inject
// protected PropertiesProxy conf;
//
// public void init(){
// createCasAuthenticationFilter();
// createCasTicketValidationFilter();
// createCasSingleSignOutFilter();
// }
//
// /**
// * 单点退出过滤器
// */
// public WebFilterFace createCasSingleSignOutFilter() {
// SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
//
// WebFilterReg reg = new WebFilterReg();
// reg.setFilter(singleSignOutFilter);
// reg.setName("casSingleSignOutFilter");
// reg.setPathSpecs(new String[]{"/*"});
// reg.setOrder(12);
// return reg;
// }
//
// /**
// * 单点认证过滤器
// */
// public WebFilterFace createCasAuthenticationFilter() {
// CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
// authenticationFilter.setCasServerUrlPrefix(conf.get("cas.server-url-prefix"));
// authenticationFilter.setCasServerLoginUrl(conf.get("cas.server-login-url"));
// authenticationFilter.setServerName(conf.get("cas.client-host-url"));
// authenticationFilter.setIgnoreInitConfiguration(true);
//
// WebFilterReg reg = new WebFilterReg();
// reg.setFilter(authenticationFilter);
// reg.setName("casAuthenticationFilter");
// reg.setPathSpecs(new String[]{"/*"});
// reg.setInitParameters(null);
// reg.setDispatcheTypes(new HashSet<>(){{
// add(DispatcherType.REQUEST);
// add(DispatcherType.FORWARD);
// }});
// reg.setOrder(14);
// return reg;
// }
//
// /**
// * 票据认证过滤器
// */
// public WebFilterFace createCasTicketValidationFilter() {
// Cas20ProxyReceivingTicketValidationFilter ticketValidationFilter = new Cas20ProxyReceivingTicketValidationFilter();
//
// HashMap<String, String> initParams = new HashMap<>();
// initParams.put("casServerUrlPrefix", conf.get("cas.server-url-prefix"));
// initParams.put("serverName", conf.get("cas.client-host-url"));
//
// WebFilterReg reg = new WebFilterReg();
// reg.setFilter(ticketValidationFilter);
// reg.setName("casTicketValidationFilter");
// reg.setPathSpecs(new String[]{"/*"});
// reg.setInitParameters(initParams);
// reg.setOrder(13);
// return reg;
// }
//
//}
@@ -0,0 +1,57 @@
package com.budwk.app.web.commons.filter.cas;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.nutz.boot.starter.WebFilterFace;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@IocBean
public class CasSingleSignOutFilterStarter implements WebFilterFace {
@Inject
protected PropertiesProxy conf;
@Override
public String getName() {
return "casSingleSignOutFilterStarter";
}
@Override
public String getPathSpec() {
return "/*";
}
@Override
public EnumSet<DispatcherType> getDispatches() {
return EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
}
@Override
public Filter getFilter() {
boolean enable = conf.getBoolean("cas.enable", false);
if (!enable) {
return null;
}
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
return singleSignOutFilter;
}
@Override
public Map<String, String> getInitParameters() {
HashMap<String, Object> map = new HashMap<>();
map.put("casServerUrlPrefix",conf.get("cas.server-url-prefix"));
return Map.of();
}
@Override
public int getOrder() {
return 12;
}
}
@@ -0,0 +1,72 @@
package com.budwk.app.web.commons.filter.cas;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.nutz.boot.AppContext;
import org.nutz.boot.starter.WebFilterFace;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@IocBean
public class CasTicketFilterStarter implements WebFilterFace {
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected PropertiesProxy conf;
@Inject
protected AppContext appContext;
@Override
public String getName() {
return "casTicketFilterStarter";
}
@Override
public String getPathSpec() {
return "/*";
}
@Override
public EnumSet<DispatcherType> getDispatches() {
return EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
}
@IocBean(name="casTicketFilter")
public Cas20ProxyReceivingTicketValidationFilter ticketValidationFilter() {
Cas20ProxyReceivingTicketValidationFilter filter = new Cas20ProxyReceivingTicketValidationFilter();
return filter;
}
@Override
public Filter getFilter() {
boolean enable = conf.getBoolean("cas.enable", false);
if (!enable) {
return null;
}
return ioc.get(Cas20ProxyReceivingTicketValidationFilter.class, "casTicketFilter");
}
@Override
public Map<String, String> getInitParameters() {
HashMap<String, String> initParams = new HashMap<>();
initParams.put("casServerUrlPrefix", conf.get("cas.server-url-prefix"));
initParams.put("serverName", conf.get("cas.client-host-url"));
//initParams.put("service", conf.get("cas.client-host-url")+conf.get("cas.client-call-back-url"));
return initParams;
}
@Override
public int getOrder() {
return 13;
}
}
@@ -0,0 +1,24 @@
package com.budwk.app.web.commons.filter.cas;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.nutz.boot.starter.WebEventListenerFace;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.EventListener;
@IocBean
public class CasWebEventListenerFace implements WebEventListenerFace {
@Inject
protected PropertiesProxy conf;
@Override
public EventListener getEventListener() {
if (conf.getBoolean("cas.enable", false)){
return null;
}
return new SingleSignOutHttpSessionListener();
}
}
@@ -0,0 +1,27 @@
//package com.budwk.app.web.commons.filter.cas;
//
//import org.jasig.cas.client.authentication.UrlPatternMatcherStrategy;
//
//import java.util.ArrayList;
//import java.util.List;
//
//
//public class SimpleUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy {
//
// private List<String> urls = new ArrayList<>();
//
// public SimpleUrlPatternMatcherStrategy(List<String> urls){
// this.urls = urls;
// }
//
// @Override
// public boolean matches(String url) {
// boolean b = urls.stream().anyMatch(url::contains);
// return b;
// }
//
// @Override
// public void setPattern(String pattern) {
//
// }
//}
@@ -0,0 +1,41 @@
package com.budwk.app.web.commons.proc;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.impl.processor.AbstractProcessor;
import java.util.Base64;
/**
* @ClassName CspProcessor
* @Author JyuHsin
* @Date 2025/12/19 9:58
* @Version 1.0
* @Description TODO
*/
public class CspProcessor extends AbstractProcessor {
public void process(ActionContext ac) throws Throwable {
// 1. 生成 nonce
String nonce = Base64.getEncoder().encodeToString(
java.security.SecureRandom.getInstanceStrong().generateSeed(16)
);
// 2. 存入 request,供 Beetl 使用
ac.getRequest().setAttribute("cspNonce", nonce);
// 3. 设置 CSP 头
String csp = "default-src 'self'; " +
"script-src 'self' 'unsafe-eval' 'nonce-" + nonce + "' https:; " +
"worker-src 'self' blob:;" +
"style-src * 'unsafe-inline' data:; " +
"img-src 'self' data: http: https:; " +
"font-src * data: https:; " +
"connect-src 'self' https: webpack:; " +
"frame-ancestors 'self'; " +
"object-src 'self';";
ac.getResponse().setHeader("Content-Security-Policy", csp);
doNext(ac);
}
}
@@ -0,0 +1,58 @@
package com.budwk.app.web.commons.proc;
import com.budwk.app.base.utils.DateUtil;
import com.budwk.app.base.utils.StringUtil;
import com.budwk.app.web.commons.auth.service.AuthService;
import com.budwk.app.web.commons.base.Globals;
import org.nutz.lang.Strings;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.ActionInfo;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.NutConfig;
import org.nutz.mvc.impl.processor.AbstractProcessor;
/**
* Created by wizzer on 2016/6/22.
*/
public class GlobalsSettingProcessor extends AbstractProcessor {
private static DateUtil dateUtil;
private static StringUtil stringUtil;
private static AuthService authService;
public void init(NutConfig config, ActionInfo ai) throws Throwable {
if (dateUtil == null) {
dateUtil = new DateUtil();
}
if (stringUtil == null) {
stringUtil = new StringUtil();
}
if (authService == null) {
authService = config.getIoc().get(AuthService.class);
}
}
@SuppressWarnings("rawtypes")
public void process(ActionContext ac) throws Throwable {
ac.getRequest().setAttribute("AppRoot", Globals.AppRoot);
ac.getRequest().setAttribute("AppBase", Globals.AppBase);
ac.getRequest().setAttribute("AppName", Globals.AppName);
ac.getRequest().setAttribute("AppLogo", Globals.AppLogo);
ac.getRequest().setAttribute("AppDomain", Globals.AppDomain);
ac.getRequest().setAttribute("AppFileDomain", Globals.AppFileDomain);
ac.getRequest().setAttribute("config", Globals.MyConfig);
ac.getRequest().setAttribute("auth", authService);
ac.getRequest().setAttribute("date", dateUtil);
ac.getRequest().setAttribute("string", stringUtil);
// 如果url中有语言属性则设置
String lang = ac.getRequest().getParameter("lang");
if (!Strings.isEmpty(lang)) {
Mvcs.setLocalizationKey(lang);
} else {
// Mvcs.getLocalizationKey() 1.r.56 版本是null,所以要做两次判断, 1.r.57已修复为默认值 Nutz:Fix issue 1072
lang = Strings.isBlank(Mvcs.getLocalizationKey()) ? Mvcs.getDefaultLocalizationKey() : Mvcs.getLocalizationKey();
}
ac.getRequest().setAttribute("lang", lang);
doNext(ac);
}
}
@@ -0,0 +1,32 @@
package com.budwk.app.web.commons.proc;
import org.nutz.lang.Stopwatch;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.impl.processor.AbstractProcessor;
import javax.servlet.http.HttpServletRequest;
/**
* Created by Wizzer.cn on 2015/7/2.
*/
public class LogTimeProcessor extends AbstractProcessor {
private static final Log log = Logs.get();
public void process(ActionContext ac) throws Throwable {
if (log.isDebugEnabled()) {
Stopwatch sw = Stopwatch.begin();
try {
doNext(ac);
} finally {
sw.stop();
HttpServletRequest req = ac.getRequest();
log.debugf("[%-4s]URI=%s %sms", req.getMethod(), req.getRequestURI(), sw.getDuration());
}
} else {
doNext(ac);
}
}
}
@@ -0,0 +1,51 @@
package com.budwk.app.web.commons.proc;
import com.budwk.app.base.exception.MethodArgumentNotValidException;
import lombok.extern.slf4j.Slf4j;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.impl.processor.AbstractProcessor;
import javax.validation.*;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Set;
import java.util.stream.Collectors;
/**
* jug 自定义接口参数校验
* 官方的很鸡肋 手搓
* 使用方法:@Valid 注解 + validation-api包下面的注解即可
* <p>
* 重点!!!
* 只能校验对象 基本数据类型不可以 也不要在基本数据类型上加这个注解 会报错的。。
*/
@Slf4j
public class ParamValidationProcessor extends AbstractProcessor {
// protected ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
@Override
public void process(ActionContext ac) throws Throwable {
// Validator validator = factory.getValidator();
// Method method = ac.getMethod();
// Object[] methodArgs = ac.getMethodArgs();
// Parameter[] parameters = method.getParameters();
// for (int i = 0; i < parameters.length; i++) {
// Parameter parameter = parameters[i];
// boolean hasValidAnnotation = parameter.isAnnotationPresent(Valid.class);
// if (hasValidAnnotation) {
// Object argValue = methodArgs[i];
// if (argValue == null) {
// throw new MethodArgumentNotValidException(parameter.getName() + "不能为null");
// }
//
// Set<ConstraintViolation<Object>> violations = validator.validate(argValue);
// if (!violations.isEmpty()) {
// String errMsg = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(""));
// throw new MethodArgumentNotValidException(errMsg);
// }
// }
// }
doNext(ac);
}
}
@@ -0,0 +1,328 @@
package com.budwk.app.web.commons.proc;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.enums.BrowserPlatform;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.exception.MethodArgumentNotValidException;
import com.budwk.app.base.exception.UnknownAccountException;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.result.ResultCode;
import com.budwk.app.base.utils.WebUtil;
import com.budwk.app.web.commons.base.Globals;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.nutz.castor.FailToCastObjectException;
import org.nutz.dao.DaoException;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.IocException;
import org.nutz.ioc.ObjectLoadException;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.ActionInfo;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.NutConfig;
import org.nutz.mvc.impl.processor.ViewProcessor;
import org.nutz.mvc.upload.UploadUnsupportedFileNameException;
import org.nutz.mvc.view.ForwardView;
import org.nutz.mvc.view.ServerRedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class WkFailProcessor extends ViewProcessor {
private static final Log log = Logs.get();
private String errorUri = "/platform/home/500";
private String localLoginUri = "/platform/login";
private String error403Uri = "/platform/home/403";
private String errorUnknownAccountUri = "/platform/home/unknownAccountError";
private static PropertiesProxy propertiesProxy;
private static RedisService redisService;
@Override
public void init(NutConfig config, ActionInfo ai) throws Throwable {
view = evalView(config, ai, ai.getFailView());
propertiesProxy = config.getIoc().get(PropertiesProxy.class, "conf");
redisService = config.getIoc().get(RedisService.class);
}
public void process(ActionContext ac) throws Throwable {
if (log.isWarnEnabled()) {
String uri = Mvcs.getRequestPath(ac.getRequest());
log.warn(String.format("Error@%s :", uri), ac.getError());
}
Throwable e = ac.getError();
// 捕获Ioc异常
if (e instanceof IocException || e instanceof ObjectLoadException) {
if (WebUtil.isAjax(ac.getRequest())) {
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.IOC_ERROR.getCode(), !log.isDebugEnabled() ? ResultCode.IOC_ERROR.getMsg() : e.getMessage()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.IOC_ERROR.getMsg()));
new ForwardView(errorUri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
} else if (e instanceof DaoException) {
if (WebUtil.isAjax(ac.getRequest())) {
// WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.DAO_ERROR.getCode(), !log.isDebugEnabled() ? ResultCode.DAO_ERROR.getMsg() : e.getMessage()));
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.DAO_ERROR.getCode(), ResultCode.DAO_ERROR.getMsg()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.DAO_ERROR.getMsg()));
new ForwardView(errorUri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
} else if (e instanceof FailToCastObjectException) { // 捕获类型转换异常
if (WebUtil.isAjax(ac.getRequest())) {
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.PARAM_ERROR.getCode(), !log.isDebugEnabled() ? ResultCode.PARAM_ERROR.getMsg() : e.getMessage()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.PARAM_ERROR.getMsg()));
new ForwardView(errorUri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
} else if (e instanceof UnknownAccountException) {
new ServerRedirectView(Globals.AppDomain + errorUnknownAccountUri).render(ac.getRequest(), ac.getResponse(), null);
} else if (e instanceof NotLoginException) { // 如果是未登录异常
if (WebUtil.isAjax(ac.getRequest())) {
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(e.getMessage()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), e.getMessage()));
notLoginRedirect(ac.getRequest(), ac.getResponse());
}
return;
} else if (e instanceof NotRoleException) { // 如果是角色异常
if (WebUtil.isAjax(ac.getRequest())) {
NotRoleException ee = (NotRoleException) e;
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.USER_NOT_ROLE.getCode(), ResultCode.USER_NOT_ROLE.getMsg() + ": " + ee.getRole()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.USER_NOT_ROLE.getMsg()));
new ForwardView(error403Uri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
} else if (e instanceof NotPermissionException) { // 如果是权限异常
if (WebUtil.isAjax(ac.getRequest())) {
NotPermissionException ee = (NotPermissionException) e;
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.USER_NOT_PERMISSION.getCode(), ResultCode.USER_NOT_PERMISSION.getMsg() + ": " + ee.getCode()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.USER_NOT_PERMISSION.getMsg()));
new ForwardView(error403Uri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
} else if (e instanceof MethodArgumentNotValidException) { //参数校验异常
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.PARAM_VALID_ERROR.getCode(), e.getMessage()));
return;
} else if (e instanceof UploadUnsupportedFileNameException) {
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(-1, "不支持该类型文件上传"));
return;
} else if (e instanceof BaseException) {
if (WebUtil.isAjax(ac.getRequest())) {
// WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.FAILURE.getCode(), !log.isDebugEnabled() ? ResultCode.FAILURE.getMsg() : e.getMessage()));
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.FAILURE.getCode(), e.getMessage()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.FAILURE.getMsg()));
// new ForwardView(errorUri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
} else if (e instanceof RuntimeException) {
if (WebUtil.isAjax(ac.getRequest())) {
WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(ResultCode.SERVER_ERROR.getCode(), !log.isDebugEnabled() ? ResultCode.SERVER_ERROR.getMsg() : e.getMessage()));
} else {
ac.getRequest().setAttribute("original_request_uri", ac.getRequest().getRequestURI());
ac.getRequest().setAttribute("error_message", Mvcs.getMessage(ac.getRequest(), ResultCode.SERVER_ERROR.getMsg()));
// new ForwardView(errorUri).render(ac.getRequest(), ac.getResponse(), null);
}
return;
}
super.process(ac);
}
/**
* 未登录重定向
*
* @param request
* @param response
* @throws Throwable
*/
private void notLoginRedirect(HttpServletRequest request, HttpServletResponse response) throws Throwable {
Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
if (assertion != null && assertion.getPrincipal() != null) {
// //判断cas里面是否有用户名 那么造成的原因就是系统中没他的信息
// //就是cas已经认证了 但是本地系统没有认证
// String name = assertion.getPrincipal().getName();
// log.error("未登录用户名:" + name);
// new ServerRedirectView(Globals.AppDomain + errorUnknownAccountUri).render(request, response, null);
// return;
}
String requestURI = request.getRequestURI();
String query = request.getQueryString();
String redirect;
if (StrUtil.isNotBlank(query)) {
redirect = URLEncoder.encode(requestURI + "?" + query, StandardCharsets.UTF_8);
} else {
redirect = URLEncoder.encode(requestURI, StandardCharsets.UTF_8);
}
//获取浏览器平台
BrowserPlatform browserPlatform = getBrowserPlatform(request);
if (Globals.sso) {
//1.判断是否为企业微信浏览器并且对接了企业微信
if (browserPlatform == BrowserPlatform.QI_YE_WEI_XIN && propertiesProxy.getBoolean("wxwork.enable", false)) {
//进行企业微信授权
// String token = getWxWorkToken();
// if (StrUtil.isNotBlank(token)) {
// //跳转到企业微信授权地址
// new ServerRedirectView(buildWxWorkOauth2Url()).render(request, response, null);
// } else {
// //跳转到错误页 别问我为啥错了 我也不知道 首先去检查配置文件里的参数有没有写对
//// new ServerRedirectView(Globals.AppDomain + errorUri).render(request, response, null);
// //跳转到cas登录页
// String callBackUrl = propertiesProxy.get("cas.client-host-url") + propertiesProxy.get("cas.client-call-back-url") + "?redirect=" + redirect;
// String authUrl = propertiesProxy.get("cas.qi-wx-server-login-url") + "?service=" + URLEncoder.encode(callBackUrl, StandardCharsets.UTF_8);
// new ServerRedirectView(authUrl).render(request, response, null);
// }
// 武汉城市职业学院 跳转到企业微信cas登录页
String callBackUrl = propertiesProxy.get("wecom.wecom-client-host-url") + propertiesProxy.get("wecom.wecom-client-call-back-url") + "?redirect=" + redirect;
String authUrl = propertiesProxy.get("wecom.wecom-server-login-url") + "?service=" + URLEncoder.encode(callBackUrl, StandardCharsets.UTF_8);
log.info("auth:"+authUrl);
new ServerRedirectView(authUrl).render(request, response, null);
} else if (browserPlatform == BrowserPlatform.WEI_XIN && propertiesProxy.getBoolean("wx.enable", false)) {
//进行微信公众号授权
//跳转到微信授权页
new ServerRedirectView(buildWxOauth2Url(redirect)).render(request, response, null);
} else {
//跳转到cas登录页
String callBackUrl = propertiesProxy.get("cas.client-host-url") + propertiesProxy.get("cas.client-call-back-url") + "?redirect=" + redirect;
String authUrl = propertiesProxy.get("cas.server-login-url") + "?service=" + URLEncoder.encode(callBackUrl, StandardCharsets.UTF_8);
log.info("auth:"+authUrl);
new ServerRedirectView(authUrl).render(request, response, null);
}
} else {
//跳转到本地登录页
new ServerRedirectView(Globals.AppDomain + localLoginUri + "?redirect=" + redirect).render(request, response, null);
}
}
/**
* 判断浏览器平台
*
* @param request
* @return
*/
private BrowserPlatform getBrowserPlatform(HttpServletRequest request) {
String uaStr = request.getHeader("User-Agent");
UserAgent ua = UserAgentUtil.parse(uaStr);
if (ua.isMobile()) {
if (uaStr.contains("MicroMessenger") && uaStr.contains("wxwork")) {
return BrowserPlatform.QI_YE_WEI_XIN;
}
if (uaStr.contains("MicroMessenger")) {
return BrowserPlatform.WEI_XIN;
}
return BrowserPlatform.H5_WEB;
} else {
return BrowserPlatform.PC_WEB;
}
}
/**
* 获取企业微信token
*/
private String getWxWorkToken() {
String token = redisService.get(RedisConstant.REDIS_KEY_WX_WORK_TOKEN);
if (StrUtil.isNotBlank(token)) {
return token;
}
String appId = propertiesProxy.get("wxwork.corpId");
String secret = propertiesProxy.get("wxwork.corpSecret");
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET".replace("ID", appId)
.replace("SECRET", secret);
String response = HttpUtil.get(url);
JSONObject jsonObject = JSONUtil.parseObj(response);
if (jsonObject.getInt("errcode") == 0) {
//官方提示:企业微信可能会出于运营需要,提前使access_token失效,开发者应实现access_token失效时重新获取的逻辑。
redisService.setex(RedisConstant.REDIS_KEY_WX_WORK_TOKEN, 7000, jsonObject.getStr("access_token"));
return jsonObject.getStr("access_token");
} else {
return null;
}
}
/**
* 构建企业微信oauth2授权url
*
* @return
*/
private String buildWxWorkOauth2Url() {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE&agentid=AGENTID#wechat_redirect";
url = url.replace("CORPID", propertiesProxy.get("wxwork.corpId"));
url = url.replace("REDIRECT_URI", URLEncoder.encode(Globals.AppDomain + propertiesProxy.get("wxwork.redirectUri"), StandardCharsets.UTF_8));
url = url.replace("AGENTID", propertiesProxy.get("wxwork.agentId"));
return url;
}
/**
* 获取微信token
*
* @return
*/
private String getWxToken() {
String token = redisService.get(RedisConstant.REDIS_KEY_WX_TOKEN);
if (StrUtil.isNotBlank(token)) {
return token;
}
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
String appID = propertiesProxy.get("wx.appID");
String appSecret = propertiesProxy.get("wx.appSecret");
assert appID != null;
assert appSecret != null;
url = url.replace("APPID", appID);
url = url.replace("APPSECRET", appSecret);
String response = HttpUtil.get(url);
JSONObject jsonObject = JSONUtil.parseObj(response);
if (jsonObject.getInt("errcode") == 0) {
redisService.setex(RedisConstant.REDIS_KEY_WX_TOKEN, 7000, jsonObject.getStr("access_token"));
return jsonObject.getStr("access_token");
} else {
return null;
}
}
/**
* 构建微信oauth2授权url
*
* @return
*/
private String buildWxOauth2Url(String redirect) {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
url = url.replace("APPID", propertiesProxy.get("wx.appID"));
url = url.replace("SCOPE", "snsapi_base");
String wxRedirect = Globals.AppDomain + propertiesProxy.get("wx.redirectUri") + "?redirect=" + redirect;
url = url.replace("REDIRECT_URI", URLEncoder.encode(wxRedirect, StandardCharsets.UTF_8));
return url;
}
}
@@ -0,0 +1,312 @@
package com.budwk.app.web.commons.proc;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.WebUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.ActionContext;
import org.nutz.mvc.ActionInfo;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.NutConfig;
import org.nutz.mvc.impl.processor.AbstractProcessor;
import org.nutz.mvc.view.ForwardView;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Pattern;
/**
* SQL XSS拦截
*/
public class XssSqlFilterProcessor extends AbstractProcessor {
private static final Log log = Logs.get();
protected String lerrorUri = "/error/403.html";
private PropertiesProxy conf;
private List<String> ignoreList;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final Set<String> SQL_KEYWORDS = new HashSet<>(Arrays.asList(
"select", "update", "delete", "insert", "truncate",
"drop", "execute", "exec", "declare", "union",
"create", "alter", "database", "table", "varchar",
"extractvalue", "concat", "benchmark", "sleep", "delay",
"waitfor", "pg_sleep", "information_schema", "sysobjects",
"load_file", "outfile", "dumpfile"
));
//合法字段
private static final Set<String> ALLOWED_ORDER_FIELDS = new HashSet<>(Arrays.asList(
"createdat", "updatedat"
));
@Override
public void init(NutConfig config, ActionInfo ai) throws Throwable {
try {
conf = config.getIoc().get(org.nutz.ioc.impl.PropertiesProxy.class, "conf");
ignoreList = Arrays.asList(Strings.splitIgnoreBlank(conf.get("xsssql.ignore.urls", "")));
} catch (Exception e) {
}
}
public void process(ActionContext ac) throws Throwable {
// if (checkUrl(ac) && checkParams(ac)) {
// if (WebUtil.isAjax(ac.getRequest())) {
// ac.getResponse().addHeader("loginStatus", "paramsDenied");
// WebUtil.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(Mvcs.getMessage(ac.getRequest(), "system.paramserror")));
// } else {
// new ForwardView(lerrorUri).render(ac.getRequest(), ac.getResponse(), Mvcs.getMessage(ac.getRequest(), "system.paramserror"));
// }
// return;
// }
doNext(ac);
}
private boolean checkUrl(ActionContext ac) {
String path = ac.getPath();
return !ignoreList.contains(path);
}
protected boolean checkParams(ActionContext ac) {
HttpServletRequest req = ac.getRequest();
String pageOrderName = req.getParameter("pageOrderName");
if (pageOrderName != null && ALLOWED_ORDER_FIELDS.contains(pageOrderName.toLowerCase())) {
// 如果是预定义的排序字段,直接放行
return false;
}
Iterator<String[]> values = req.getParameterMap().values().iterator();// 获取所有的表单参数
Iterator<String[]> values2 = req.getParameterMap().values().iterator();// 因为是游标所以要重新获取
boolean isError = false;
// XSS过滤
String regEx_xss = "script|iframe";
//SQL过滤
while (values.hasNext()) {
String[] valueArray = (String[]) values.next();
for (int i = 0; i < valueArray.length; i++) {
// 不转换为小写,保持原始值进行检查
String value = valueArray[i];
// 检查是否是JSON格式
if (isValidJson(value)) {
// 对JSON内容进行安全检查
if (checkJsonContent(value)) {
// isError = true;
log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "JSON内容包含危险字符:" + value);
break;
}
// JSON格式验证通过,继续处理下一个参数
continue;
}
// 非JSON格式,进行普通的SQL注入检查
if (containsSqlInjection(value, false)) {
isError = true;
log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "SQL注入风险:" + value);
break;
}
}
if (isError) break;
}
if (!isError) {
// XSS漏洞过滤
while (values2.hasNext()) {
String[] valueArray = (String[]) values2.next();
for (int i = 0; i < valueArray.length; i++) {
String value = valueArray[i].toLowerCase();
// 分拆关键字
String[] inj_stra = StringUtils.split(regEx_xss, "|");
for (int j = 0; j < inj_stra.length; j++) {
// 判断如果路径参数值中含有关键字则返回true,并且结束循环
if (value.contains("<" + inj_stra[j] + ">")
|| value.contains("<" + inj_stra[j])
|| value.contains(inj_stra[j] + ">")) {
log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "XSS关键字过滤:" + value);
isError = true;
break;
}
}
if (isError) {
break;
}
}
if (isError) {
break;
}
}
}
return isError;
}
/**
* 验证是否为有效的JSON
*/
private boolean isValidJson(String value) {
try {
objectMapper.readTree(value);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 检查JSON内容是否包含危险字符
*/
private boolean checkJsonContent(String jsonStr) {
try {
JsonNode rootNode = objectMapper.readTree(jsonStr);
return checkJsonNode(rootNode, true);
} catch (Exception e) {
log.error("JSON内容检查失败", e);
return true; // 解析失败当作危险处理
}
}
/**
* 递归检查JSON节点
*/
private boolean checkJsonNode(JsonNode node, boolean isJsonContext) {
if (node.isObject()) {
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
// JSON对象的键名不需要进行特殊字符检查,因为它们是受控的标识符
// 递归检查值
if (checkJsonNode(entry.getValue(), true)) {
return true;
}
}
} else if (node.isArray()) {
for (JsonNode element : node) {
if (checkJsonNode(element, true)) {
return true;
}
}
} else if (node.isTextual()) {
String value = node.asText();
// 在JSON上下文中使用更宽松的规则
return containsSqlInjection(value, true);
}
return false;
}
/**
* 检查是否包含SQL注入风险
*
* @param value 要检查的值
* @param isJsonContext 是否在JSON上下文中
*/
private boolean containsSqlInjection(String value, boolean isJsonContext) {
if (StringUtils.isBlank(value)) {
return false;
}
// 如果是UUID格式,直接放行
if (value.matches("[a-fA-F0-9]{32}") || value.matches("[a-fA-F0-9-]{36}")) {
return false;
}
// 如果包含HTML标签,使用更宽松的检查规则
if (value.contains("<") && value.contains(">")) {
return checkHtmlContent(value);
}
// 添加对常见标识符的检查
if (isCommonIdentifier(value) || ALLOWED_ORDER_FIELDS.contains(value.toLowerCase())) {
return false;
}
// 检查注释符号
if (value.contains("--") || value.contains("/*") || value.contains("*/")) {
return true;
}
// 检查危险的特殊字符(针对非JSON数据)
if (!isValidJson(value) && containsDangerousChars(value)) {
return true;
}
// 检查SQL关键字,但要避免误判
String valueLower = value.toLowerCase();
for (String keyword : SQL_KEYWORDS) {
// 在JSON上下文中,只有当关键字作为独立单词出现时才判定为危险
if (isJsonContext) {
if (isStandaloneWord(valueLower, keyword)) {
return true;
}
} else if (valueLower.contains(keyword)) {
return true;
}
}
return false;
}
/**
* 检查HTML内容是否包含危险脚本
*/
private boolean checkHtmlContent(String html) {
String lowerHtml = html.toLowerCase();
// 只检查真正危险的脚本标签
return lowerHtml.contains("<script") ||
lowerHtml.contains("javascript:") ||
lowerHtml.contains("onerror=") ||
lowerHtml.contains("onclick=");
}
/**
* 检查关键字是否作为独立单词出现
*/
private boolean isStandaloneWord(String text, String keyword) {
String wordBoundary = "\\b" + keyword + "\\b";
return Pattern.compile(wordBoundary).matcher(text).find();
}
/**
* 检查是否包含危险的特殊字符
*/
private boolean containsDangerousChars(String value) {
// 定义真正危险的字符(减少误判)
String[] dangerousChars = {
// "'", // 单引号
// ";", // 分号
"\\", // 反斜杠
"@@", // 系统变量前缀
"0x", // 十六进制前缀
"/*", // 注释开始
"*/" // 注释结束
};
for (String dangerous : dangerousChars) {
if (value.contains(dangerous)) {
return true;
}
}
return false;
}
/**
* 判断是否为常见的UUID或标识符格式
*/
private boolean isCommonIdentifier(String value) {
// UUID格式
if (value.matches("[a-fA-F0-9]{32}") || value.matches("[a-fA-F0-9-]{36}")) {
return true;
}
// 常见的标识符格式(字母、数字、下划线、中划线)
if (value.matches("^[a-zA-Z0-9_-]+$")) {
return true;
}
return false;
}
}
@@ -0,0 +1,161 @@
package com.budwk.app.web.controllers.open.commons;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_config;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.service.SysClubService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.validation.Valid;
import java.util.Collections;
import java.util.List;
/**
* 系统公共接口
*/
@IocBean
@At("/open/common")
@Ok("json:full")
@Api(tags = "系统公共接口")
public class CommonController {
@Inject
private Dao dao;
@Inject
private SysDictService sysDictService;
@Inject
private SysUserService sysUserService;
@Inject
private SysClubService sysClubService;
@Inject
private RedisService redisService;
/**
* 根据code获取字典选项,不包含禁用
*/
@At
@SaCheckLogin
@ApiOperation("获取字典选项,不包含禁用")
public Result dictOptions(String code) {
return Result.success(sysDictService.getSubListByCode(code));
}
/**
* 根据code获取字典选项,包含禁用
*
* @param code
* @return
*/
@At
@SaCheckLogin
@ApiOperation("获取字典选项,包含禁用")
public Result dictAllOptions(String code) {
Sys_dict dict = sysDictService.fetch(Cnd.where("code", "=", code));
if (dict == null) {
return Result.success(Collections.EMPTY_LIST);
} else {
List<Sys_dict> query = sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(dict.getId())).asc("location"));
return Result.success(query);
}
}
@At
@SaCheckLogin
@ApiOperation("获取枚举字典列表")
public Result dictEnumOptions(@Valid String name) {
if (Globals.EnumMap.containsKey(name)) {
return Result.success().addData(Globals.EnumMap.get(name));
}
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("分页查询用户,默认10条")
public Result userOptions(String query) {
Cnd cnd = Cnd.NEW();
cnd.where().orLike("username", query);
cnd.where().orLike("loginname", query);
Pagination pagination = sysUserService.listPage(1, 10, cnd);
return Result.success(pagination.getList());
}
@At
@SaCheckLogin
@ApiOperation("工会列表")
public Result unionOptions(String unionId) {
List<Sys_union> sys_unions = sysUserService.dao().query(Sys_union.class,
Cnd.NEW().andEX("id", "=", unionId).asc("unionCode"));
return Result.success(sys_unions);
}
@At
@SaCheckLogin
@ApiOperation("协会列表")
public Result clubOptions(String clubId) {
List<SysClub> sysClubs = sysClubService.dao().query(SysClub.class,
Cnd.NEW().andEX("id", "=", clubId));
return Result.success(sysClubs);
}
@At
@SaCheckLogin
@ApiOperation("单位列表")
public Result unitOptions(String unionId) {
Sql sql = Sqls.create("""
SELECT
unit.*,
su.`name` AS unionName
FROM
`sys_unit` unit
LEFT JOIN sys_union su ON su.id = unit.unionId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("unit.unionId", "=", unionId);
cnd.and("unit.unitTypeCode", "=", "1");
cnd.asc("unit.unitcode");
sql.setCondition(cnd);
List<NutMap> unitList = sysUserService.listMap(sql);
return Result.success(unitList);
}
@At
@SaCheckLogin
@ApiOperation("系统配置")
public Result getConfigKey(String key) {
Sys_config configKey = sysDictService.dao().fetch(Sys_config.class, Cnd.where("configKey", "=", key));
return Result.success().addData(configKey.getConfigValue());
}
@At
@SaCheckLogin
@ApiOperation("检查用户组权限")
public Result checkGroupPermission(String groupId) {
int count = dao.count(ActivityUserScope.class, Cnd.where(ActivityUserScope::getGroupId, "=", groupId).and(ActivityUserScope::getUserId, "=", SecurityUtil.getUserId()));
return Result.success(count > 0);
}
}
@@ -0,0 +1,106 @@
package com.budwk.app.web.controllers.open.commons.service;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_role;
import org.nutz.dao.Cnd;
import org.nutz.dao.util.lambda.PFun;
import javax.validation.Valid;
import java.util.List;
import java.util.function.Function;
/**
* @Author: JyuHsin
* @Date: 2024/8/9 15:41
* @Version: v1.0.0
* @Description: TODO
**/
public interface CommonService extends BaseService {
/**
* 根据角色code获取用户信息
*
* @param column sys_user_role表中的列,比如要查分工会管理员,那么column=unionId
* @param columnValue sys_user_role表中列的值,比如要查分工会管理员,columnValue=分工会的id
* @param roleCode 角色code,主要需要.name()后传过来
* @return 用户实体类集合
*/
List<Sys_user> findUserInfoByRoleCode(String column, String columnValue, @Valid String roleCode);
/**
* 根据多个角色code获取用户信息
*
* @param column 见上
* @param columnValue 见上
* @param roleCode 见上
* @return 见上
*/
List<Sys_user> findUserInfoByRoleCode(String column, String columnValue, @Valid List<String> roleCode);
/**
* 根据角色code获取用户信息,这个主要是来获取校级层面的
*
* @param roleCode 见上
* @return 见上
*/
List<Sys_user> findUserInfoByRoleCode(@Valid String roleCode);
/**
* 根据角色code获取角色id
*
* @param roleCode 角色code,主要需要.name()后传过来
* @return roleId的集合
*/
List<String> findUserRoleByRoleCode(@Valid String roleCode);
/**
* 根据多个角色code获取角色id
*
* @param roleCode 见上
* @return roleId的集合
*/
List<String> findUserRoleByRoleCode(@Valid List<String> roleCode);
<T> List<Sys_user> findUserInfoByRoleCode(PFun<T, ?> name, String columnValue, RoleConstant... roleConstants);
/**
* 下述为修正版
*/
/**
* 根据角色code获取多个用户工号
*
* @param roleConstant
* @return
*/
List<String> findUsersLoginNameByRoleCode(RoleConstant roleConstant);
/**
* 根据角色code获取多个用户工号
*
* @param roleConstant
* @return
*/
List<String> findUsersLoginNameByRoleCode(RoleConstant roleConstant, Cnd cnd);
/**
* 根据角色code获取一个用户工号
*
* @param roleConstant
* @return
*/
String findUserLoginNameByRoleCode(RoleConstant roleConstant);
/**
* 根据角色code获取一个用户工号
*
* @param roleConstant
* @return
*/
String findUserLoginNameByRoleCode(RoleConstant roleConstant, Cnd cnd);
}
@@ -0,0 +1,133 @@
package com.budwk.app.web.controllers.open.commons.service.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.web.controllers.open.commons.service.CommonService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.lambda.LambdaQuery;
import org.nutz.dao.util.lambda.PFun;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
/**
* @Author: JyuHsin
* @Date: 2024/8/9 15:41
* @Version: v1.0.0
* @Description: TODO
**/
@IocBean(args = {"refer:dao"})
public class CommonServiceImpl extends BaseServiceImpl implements CommonService {
@Inject
private SysRoleService sysRoleService;
public CommonServiceImpl(Dao dao) {
super(dao);
}
@Override
public List<Sys_user> findUserInfoByRoleCode(String roleCode) {
return findUserInfoByRoleCode(null, null, List.of(roleCode));
}
@Override
public List<Sys_user> findUserInfoByRoleCode(String column, String columnValue, String roleCode) {
return findUserInfoByRoleCode(column, columnValue, List.of(roleCode));
}
@Override
public List<Sys_user> findUserInfoByRoleCode(String column, String columnValue, List<String> roleCode) {
Assert.notEmpty(roleCode);
List<String> roleIdList = new ArrayList<>();
roleCode.forEach(role -> {
Sys_role sysRole = sysRoleService.getByCode(role);
roleIdList.add(sysRole.getId());
});
Cnd cnd = Cnd.where("roleId", "in", roleIdList);
if (StrUtil.isNotBlank(column)) {
cnd.andEX(column, "=", columnValue);
}
List<Sys_user_role> roleList = dao().query(Sys_user_role.class, cnd);
List<String> userIdList = roleList.stream().map(Sys_user_role::getUserId).distinct().toList();
return dao().query(Sys_user.class, Cnd.where("id", "in", userIdList));
}
@Override
public List<String> findUserRoleByRoleCode(String roleCode) {
return findUserRoleByRoleCode(List.of(roleCode));
}
@Override
public List<String> findUserRoleByRoleCode(List<String> roleCode) {
List<Sys_role> roleList = new ArrayList<>();
roleCode.forEach(role -> {
roleList.add(sysRoleService.getByCode(role));
});
return roleList.stream().map(Sys_role::getId).toList();
}
@Override
public <T> List<Sys_user> findUserInfoByRoleCode(PFun<T, ?> name, String columnValue, RoleConstant... roleConstant) {
String column = LambdaQuery.resolve(name);
List<String> list = Arrays.stream(roleConstant).map(Enum::name).toList();
return findUserInfoByRoleCode(column, columnValue, list);
}
@Override
public List<String> findUsersLoginNameByRoleCode(RoleConstant roleConstant) {
return findUsersLoginNameByRoleCode(roleConstant,Cnd.NEW());
}
@Override
public List<String> findUsersLoginNameByRoleCode(RoleConstant roleConstant, Cnd cnd) {
Sys_role sysRole = sysRoleService.getByCode(roleConstant);
cnd.and(Sys_user_role::getRoleId, "=", sysRole.getId());
List<Sys_user_role> sysUserRoles = dao().query(Sys_user_role.class, cnd);
List<String> userIds = sysUserRoles.stream().map(Sys_user_role::getUserId).toList();
if(ObjectUtil.isEmpty(userIds)){
return Collections.emptyList();
}
Sql sql = Sqls.create("select loginname from sys_user where id in (@userIds)").setParam("userIds", userIds);
sql.setCallback(Sqls.callback.strList());
dao().execute(sql);
return sql.getList(String.class);
}
@Override
public String findUserLoginNameByRoleCode(RoleConstant roleConstant) {
return findUserLoginNameByRoleCode(roleConstant, Cnd.NEW());
}
@Override
public String findUserLoginNameByRoleCode(RoleConstant roleConstant, Cnd cnd) {
Sys_role sysRole = sysRoleService.getByCode(roleConstant);
cnd.and(Sys_user_role::getRoleId, "=", sysRole.getId());
Sys_user_role sysUserRole = dao().fetch(Sys_user_role.class, cnd);
if (ObjectUtil.isNotEmpty(sysUserRole)) {
return dao().fetch(Sys_user.class, sysUserRole.getUserId()).getLoginname();
}
return "";
}
}