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,164 @@
package com.budwk.app;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import com.budwk.app.base.utils.AppIocUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.sys.services.SysTaskService;
import com.budwk.app.task.services.TaskPlatformService;
import com.budwk.app.web.commons.auth.satoken.SaTokenContextImpl;
import com.budwk.app.web.commons.auth.satoken.SaTokenDaoRedisImpl;
import com.budwk.app.web.commons.auth.satoken.StpInterfaceImpl;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.web.commons.ext.pubsub.WebPubSub;
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
import lombok.extern.slf4j.Slf4j;
import org.beetl.core.GroupTemplate;
import org.nutz.boot.NbApp;
import org.nutz.dao.Dao;
import org.nutz.dao.impl.FileSqlManager;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.integration.jedis.JedisAgent;
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.lang.Mirror;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.*;
import org.quartz.Scheduler;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.ServletContext;
import java.lang.management.ManagementFactory;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;
import java.util.List;
@IocBean(create = "init", depose = "depose")
@Localization(value = "locales/", defaultLocalizationKey = "zh_CN")
@Encoding(input = "UTF-8", output = "UTF-8")
@ChainBy(args = "chain/mvc-chain.json")
@Slf4j
public class MainLauncher {
protected static final String PRE = "security.";
@Inject("refer:$ioc")
private Ioc ioc;
@Inject
private PropertiesProxy conf;
@Inject
private JedisAgent jedisAgent;
@Inject
private WebPubSub webPubSub;//注入一下为了初始化
@Inject
private GroupTemplate groupTemplate;
@Inject
private TaskPlatformService taskPlatformService;
@Inject
private SysTaskService sysTaskService;
@Inject
private Dao dao;
public static void main(String[] args) throws Exception {
NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true);
nb.getAppContext().setMainPackage("com.budwk");
nb.run();
}
public static NbApp warMain(ServletContext sc) {
NbApp nb = new NbApp().setPrintProcDoc(true);
nb.getAppContext().setMainPackage("com.budwk");
return nb;
}
public void init() {
Mvcs.DISABLE_X_POWERED_BY = true;
Globals.AppBase = Mvcs.getServletContext().getContextPath();
Globals.AppRoot = Mvcs.getServletContext().getRealPath("/");
init_sys();
init_task();
init_auth();
ioc.get(Globals.class);
ioc.get(FlowEngine.class);
ioc.get(GlobalMessageSendService.class);
AppIocUtil.setIoc(ioc);
}
public void init_auth() {
SaTokenConfig saTokenConfig = conf.makeDeep(SaTokenConfig.class, PRE);
String tokenName = conf.get(PRE + "tokenName", "token");
// 注意这里的token默认超时时间与控制中心 websocket 超时时间保持一致
saTokenConfig.setTimeout(conf.getLong(PRE + "timeout", 86400));
saTokenConfig.setTokenName(tokenName);
saTokenConfig.setIsV(false);
SaManager.setConfig(saTokenConfig);
SaManager.setSaTokenContext(ioc.get(SaTokenContextImpl.class));
SaManager.setSaTokenDao(ioc.get(SaTokenDaoRedisImpl.class));
SaManager.setStpInterface(ioc.get(StpInterfaceImpl.class));
}
private void init_task() {
if (log.isDebugEnabled() && !dao.exists("sys_qrtz_triggers") && !dao.exists("sys_qrtz_triggers".toUpperCase())) {
//执行Quartz SQL脚本
String dbType = dao.getJdbcExpert().getDatabaseType();
log.debug("dbType:::" + dbType);
FileSqlManager fmq = new FileSqlManager("db/quartz/" + dbType.toLowerCase() + ".sql");
List<Sql> sqlListq = fmq.createCombo(fmq.keys());
Sql[] sqlsq = sqlListq.toArray(new Sql[sqlListq.size()]);
for (Sql sql : sqlsq) {
dao.execute(sql);
}
}
}
private void init_sys() {
if (log.isDebugEnabled()) {
//通过POJO类创建表结构
try {
Daos.createTablesInPackage(dao, "com.budwk", false);
//通过POJO类修改表结构
Daos.migration(dao, "com.budwk", true, false);
// Daos.migration(dao, "com.budwk", true, false, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void depose() {
// 非mysql数据库,或多webapp共享mysql驱动的话,以下语句删掉
try {
Mirror.me(Class.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread")).invoke(null, "shutdown");
} catch (Throwable e) {
}
// 解决quartz有时候无法停止的问题
try {
ioc.get(Scheduler.class).shutdown(true);
} catch (Exception e) {
}
// 解决com.alibaba.druid.proxy.DruidDriver和com.mysql.jdbc.Driver在reload时报warning的问题
// 多webapp共享mysql驱动的话,以下语句删掉
Enumeration<Driver> en = DriverManager.getDrivers();
while (en.hasMoreElements()) {
try {
Driver driver = en.nextElement();
String className = driver.getClass().getName();
log.debug("deregisterDriver: " + className);
DriverManager.deregisterDriver(driver);
} catch (Exception e) {
}
}
try {
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("com.alibaba.druid:type=MockDriver");
if (mbeanServer.isRegistered(objectName)) mbeanServer.unregisterMBean(objectName);
objectName = new ObjectName("com.alibaba.druid:type=DruidDriver");
if (mbeanServer.isRegistered(objectName)) mbeanServer.unregisterMBean(objectName);
} catch (Exception ex) {
}
}
}
@@ -0,0 +1,22 @@
package com.budwk.app.base.annotation;
import java.lang.annotation.*;
/**
* 字典枚举类注解,方便收集做为字典使用的
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DictEnum{
/**
* 名称
*/
String name();
/**
* 唯一标识
*/
String key();
}
@@ -0,0 +1,23 @@
package com.budwk.app.base.annotation;
import java.lang.annotation.*;
/**
* 自定义注解防止表单重复提交
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 5000;
/**
* 提示消息
*/
public String message() default "请求过于频繁,请稍后再试";
}
@@ -0,0 +1,41 @@
package com.budwk.app.base.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SLog {
String type() default "platform";
/**
* 标签
*
* @return
*/
String tag();
String msg() default "";
/**
* 是否记录传递参数
*
* @return 消息模板
*/
boolean param() default true;
/**
* 记录执行结果
*
* @return 消息模板
*/
boolean result() default true;
/**
* 是否异步执行,默认为true
*
* @return true, 如果需要异步执行
*/
boolean async() default true;
}
@@ -0,0 +1,119 @@
package com.budwk.app.base.config;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.exception.BaseException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.HashMap;
import java.util.Map;
/**
* @version 1.0
* @Author zzr
* @nameDataCenterProperties
* @Date 2025/9/15 15:34
* @注释 数据中心配置文件,存放接口地址和参数配置
*/
@Slf4j
@Data
@IocBean(create = "init")
public class DataCenterProperties {
@Inject
private PropertiesProxy conf;
@Inject
private RedisService redisService;
private String appId;
private String tokenUrl;
private Map<String, String> codes = new HashMap<>();
private Map<String, String> urls = new HashMap<>();
public void init() {
Map<String, String> all = conf.toMap();
appId = all.get("data-center.app-id");
tokenUrl = all.get("data-center.token-url");
extract(all, "data-center.codes.", codes);
extract(all, "data-center.urls.", urls);
}
/**
* 扫描配置 把 prefix 开头的 key 变成 Map
* @param prefix 配置前缀
* @param target 目标值
*/
private void extract(Map<String, String> all, String prefix, Map<String, String> target) {
all.forEach((k, v) -> {
if (k.startsWith(prefix)) {
target.put(k.substring(prefix.length()), v);
}
});
}
/**
* 读取配置
* @param key 接口类型枚举
* @return 相关配置
*/
public Credential credential(String key) {
String code = codes.get(key);
String url = urls.get(key);
if (code == null || url == null) {
throw new BaseException("未知业务: " + key);
}
String token = getToken(Integer.parseInt(code), key);
return new Credential(url, token);
}
@Getter
@AllArgsConstructor
public static class Credential {
private String url;
private String token;
}
/**
* 获取token
* @param code
* @param keyPrefix
* @return token
*/
private String getToken(Integer code, String keyPrefix) {
String dateCenterPrefix = RedisConstant.DATE_CENTER_PREFIX + keyPrefix;
String token = redisService.get(dateCenterPrefix);
if (StrUtil.isNotBlank(token)) {
return token;
} else {
HttpRequest httpRequest = HttpUtil.createPost(tokenUrl);
Map<String, Object> reqBody = Map.of(
"appId", appId,
"code", code
);
httpRequest.body(JSONUtil.toJsonStr(reqBody));
JSONObject resp = JSONUtil.parseObj(httpRequest.execute().body());
if (resp.getInt("code") != 200) {
throw new BaseException("获取token失败: " + resp.getStr("msg"));
} else {
JSONObject data = resp.getJSONObject("data");
String tokenStr = data.getStr("token");
Integer expire = data.getInt("expire");
redisService.setex(dateCenterPrefix, expire, tokenStr);
return tokenStr;
}
}
}
}
@@ -0,0 +1,125 @@
package com.budwk.app.base.config;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @version 1.0
* @Author zzr
* @nameThreadPoolConfig
* @Date 2024/9/2 10:18
* @注释
*/
@Slf4j
@IocBean
public class ThreadPoolConfig {
@Inject
private Dao dao;
/**
* 核心线程数(默认线程数)
*/
private static final int CORE_POOL_SIZE = 2 * Runtime.getRuntime().availableProcessors() + 1;
/**
* 最大线程数
*/
private static final int MAX_POOL_SIZE = 128;
/**
* 允许线程空闲时间(单位:默认为秒)
*/
private static final int KEEP_ALIVE_TIME = 5;
/**
* 任务的等待时间
*/
private static final int AWAIT_TERMINATION_TIME = 30;
/**
* 缓冲队列数
*/
private static final int QUEUE_CAPACITY = 1200;
/**
* 线程池名前缀
*/
private static final String THREAD_NAME_PREFIX = "dd3s-thread-pool";
/**
* bean的名称,默认为首字母小写的方法名
* spring管理的线程池,顶级父类也是Executor
*/
@IocBean(name = "executorService")
public ThreadPoolTaskExecutor executorService() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.setAwaitTerminationSeconds(AWAIT_TERMINATION_TIME);
executor.setWaitForTasksToCompleteOnShutdown(true);
// 线程池对拒绝任务的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/**
* 异步执行
*
* @param list 泛型list,任意实体类集合
* @param batchSize 期望单次操作的数量,默认200
* @param mode insert插入 update更新 insertOrUpdate插入或更新
* @param isFastInsertOrUpdateIgnoreNull mode为新增此值为true代表fastInsertmode为修改时此值为true代表updateIgnoreNull
* @param <T>
*/
public <T> void asyncExecute(List<T> list, Integer batchSize, String mode, boolean isFastInsertOrUpdateIgnoreNull) {
if (Lang.isEmpty(list)) {
return;
}
if (batchSize == null || batchSize <= 0) {
batchSize = 200;
}
for (int i = 0; i < list.size(); i += batchSize) {
final int end = Math.min(i + batchSize, list.size());
List<T> batch = list.subList(i, end);
executorService().execute(() -> {
try {
switch(mode) {
case "insert" -> {
if (isFastInsertOrUpdateIgnoreNull) {
dao.fastInsert(batch);
} else {
dao.insert(batch);
}
}
case "update" -> {
if (isFastInsertOrUpdateIgnoreNull) {
dao.updateIgnoreNull(batch);
} else {
dao.update(batch);
}
}
case "insertOrUpdate" -> dao.insertOrUpdate(batch);
}
} catch (Exception e) {
// 日志记录异常信息
System.err.println("Error occurred while inserting batch: " + e.getMessage());
e.printStackTrace();
}
});
}
}
}
@@ -0,0 +1,49 @@
package com.budwk.app.base.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程常量
*/
@Getter
@AllArgsConstructor
public enum BpmProcessConstant {
WARMTH_CONDOLENCE("送温暖慰问"),
LOVE_ASSIST("爱心扶助基金"),
PROPOSAL("提案"),
SUGGESTION_TC("教代会意见建议"),
SUGGESTION_WC("工代会意见建议"),
GRASSROOTS_CONGRESS("基层双代会"),
GRASSROOTS_CONGRESS_MATERIALS("基层双代会会议资料"),
MEMBER_SINGLE_APPLY("会员入会申请"),
MEMBER_CHANGE("会员入会申请"),
MEMBER_BRANCH_UNION_CHANGE("会员分工会变更"),
CLUB_JOIN("协会入会申请"),
CLUB_EXAMINE("协会考核"),
CLUB_EVALUATE("协会评优"),
CLUB_REGISTER("协会注册"),
PERSON_EVALUATE("评优评先申请"),
ARTICLE("新闻投稿"),
BRANCH_UNION_WEIYUAN_AUTHORIZATION("分工会委员授权")
;
public final String description;
}
@@ -0,0 +1,17 @@
package com.budwk.app.base.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ProcessBusinessConstant {
CONDOLENCE_APPLY("慰问");
/**
* 避免和枚举类的name冲突
*/
public final String roleName;
}
@@ -0,0 +1,75 @@
package com.budwk.app.base.constant;
/**
* @author wizzer@qq.com
*/
public class RedisConstant {
//项目统一前缀
public final static String PLATFORM_REDIS_PREFIX = "budwk5mini:";
public final static String PLATFORM_REDIS_WKCACHE_PREFIX = PLATFORM_REDIS_PREFIX + "wkcache:";
//聊天室
public final static String REDIS_KEY_WSROOM = PLATFORM_REDIS_PREFIX + "wsroom:";
public final static String REDIS_KEY_LOGIN_ADMIN_CAPTCHA = PLATFORM_REDIS_PREFIX + "admin:login:captcha:";
public final static String REDIS_KEY_ADMIN_PUBSUB = PLATFORM_REDIS_PREFIX + "admin:pubsub:";
//微信token
public final static String REDIS_KEY_WX_TOKEN = PLATFORM_REDIS_PREFIX + "wx:token:";
//企业微信token
public final static String REDIS_KEY_WX_WORK_TOKEN = PLATFORM_REDIS_PREFIX + "wxwork:token:";
//健步走小程序TOKEN
public final static String REDIS_KEY_WE_APP_ACCESS_TOKEN = PLATFORM_REDIS_PREFIX + "weapp:token:";
//图形验证码
public final static String REDIS_CAPTCHA_KEY = PLATFORM_REDIS_PREFIX + "platfrom:captcha:";
//短信验证码
public final static String REDIS_SMSCODE_KEY = PLATFORM_REDIS_PREFIX + "platfrom:smscode:";
public final static String REDIS_KEY_API_SIGN_DEPLOY_NONCE = PLATFORM_REDIS_PREFIX + "api:sign:deploy:nonce:";
public final static String REDIS_KEY_API_SIGN_OPEN_NONCE = PLATFORM_REDIS_PREFIX + "api:sign:open:nonce:";
/**
* Token 缓存前缀
*/
public static final String TOKEN = PLATFORM_REDIS_PREFIX + "token:";
/**
* 验证码前缀
*/
public static final String UCENTER_CAPTCHA = PLATFORM_REDIS_PREFIX + "ucenter:captcha:";
/**
* 短信验证码前缀
*/
public static final String UCENTER_SMSCODE = PLATFORM_REDIS_PREFIX + "ucenter:smscode:";
/**
* 接口重复提交前缀
*/
public static final String REPEAT_SUBMIT_PREFIX = PLATFORM_REDIS_PREFIX + "repeat_submit:";
/**
* 签名前缀
*/
public static final String SIGNATURE_PREFIX = PLATFORM_REDIS_PREFIX + "sys:signature:";
/**
* 用户登录锁前缀
*/
public static final String USER_LOGIN_LOCK_PREFIX = PLATFORM_REDIS_PREFIX + "user:login:lock:";
/**
* 数据接口锁前缀
*/
public static final String DATE_CENTER_PREFIX = PLATFORM_REDIS_PREFIX + "datacenter:token:";
/**
* 数据接口锁前缀
*/
public static final String RSA_KEY_PREFIX = PLATFORM_REDIS_PREFIX + "rsa:private:";
}
@@ -0,0 +1,111 @@
package com.budwk.app.base.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 角色常量
*/
@Getter
@AllArgsConstructor
public enum
RoleConstant {
PUBLIC("公共角色"),
MEMBER("会员角色"),
SYSADMIN("系统管理员"),
UNION_COMMITTEE_MEMBER("工代会委员"),
SCHOOL_UNION_ADMIN("校工会管理员"),
SCHOOL_UNION_CHAIRMAN("校工会主席"),
SCHOOL_UNION_VICE_CHAIRMAN("校工会副主席"),
SCHOOL_UNION_WELFARE_ADMIN("校工会福利管理员"),
SCHOOL_UNION_MEMBER_ADMIN("校工会会员管理员"),
SCHOOL_UNION_TC_ADMIN("校工会教代会管理员"),
SCHOOL_UNION_WC_ADMIN("校工会工代会管理员"),
SCHOOL_UNION_DC_ADMIN("校工会民主管理员"),
SCHOOL_UNION_PROPOSAL_ADMIN("校工会提案管理员"),
SCHOOL_UNION_CLUB_ADMIN("校工会协会管理员"),
SCHOOL_UNION_ACTIVITY_ADMIN("校工会活动管理员"),
SCHOOL_UNION_ARTICLE_CLUB_ADMIN("校工会协会新闻审批管理员"),
SCHOOL_UNION_ARTICLE_FGH_ADMIN("校工会分工会新闻审批管理员"),
SCHOOL_UNION_ARTICLE_ADMIN("校工会新闻审批管理员"),
SCHOOL_OUTLAY_ADMIN("校工会经费管理员"),
BRANCH_UNION_ADMIN("分工会管理员"),
BRANCH_UNION_CHAIRMAN("分工会主席"),
BRANCH_UNION_VICE_CHAIRMAN("分工会副主席"),
BRANCH_UNION_OPERATOR("分工会操作员"),
BRANCH_UNION_GROUP_LEADER("工会小组组长"),
BRANCH_UNION_ARTICLE_WRITER("分工会新闻投稿员"),
BRANCH_UNION_WY("分工会委员"),
BRANCH_UNION_ZUZHI_WY("分工会组织委员"),
BRANCH_UNION_XUANCHUAN_WY("分工会宣传委员"),
BRANCH_UNION_NVGONG_WY("分工会女工委员"),
BRANCH_UNION_QINGNIAN_WY("分工会青年委员"),
BRANCH_UNION_WENTI_WY("分工会文体委员"),
BRANCH_UNION_SHENGGHUO_WY("分工会生活委员"),
BRANCH_UNION_TIAOJIE_WY("分工会调解委员"),
BRANCH_UNION_WENTI_SPORTS("分工会文体福利委员"),
UNIT_PARTY_SECRETARY("单位党委书记"),
TEACHER_CONGRESS_DELEGATE_FORMAL("教代会正式代表"),
TEACHER_CONGRESS_DELEGATE_ATTENDANCE("教代会列席代表"),
TEACHER_CONGRESS_DELEGATE_SPECIALLY_INVITE("教代会特邀代表"),
TEACHER_CONGRESS_DELEGATION_HEAD("教代会代表团团长"),
TEACHER_CONGRESS_VICE_DELEGATION_HEAD("教代会代表团副团长"),
TEACHER_CONGRESS_DELEGATION_CONTACT("教代会代表团联络人"),
TEACHER_CONGRESS_DELEGATION_QUALIFICATION("教代会代表资格审查员"),
TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_DIRECTOR("筹备领导小组主任"),
TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_MEMBER("筹备领导小组成员"),
PROPOSAL_COMMITTEE_DIRECTOR("提案委员会主任"),
PROPOSAL_COMMITTEE_DEPUTY_DIRECTOR("提案委员会副主任"),
PROPOSAL_BRANCH_SCHOOL_LEADER("提案分管校领导"),
PROPOSAL_UNIT_LEADER("提案承办单位领导"),
PROPOSAL_UNIT_PROXY("提案承办单位代理答复人"),
WORKER_CONGRESS_DELEGATE_FORMAL("工代会正式代表"),
WORKER_CONGRESS_DELEGATE_ATTENDANCE("工代会列席代表"),
WORKER_CONGRESS_DELEGATE_SPECIALLY_INVITE("工代会特邀代表"),
WORKER_CONGRESS_DELEGATION_HEAD("工代会代表团团长"),
SCHOOL_UNION_WC_AUDIT_ADMIN("基层双代会校工会批复人"),
SCHOOL_HOSPITAL_LEADER("校医院负责人"),
/*协会模块*/
CLUB_MEMBER("协会会员"),
CLUB_MANAGER("协会负责人"),
CLUB_PRESIDENT("协会会长"),
CLUB_VICE_PRESIDENT("协会副会长"),
CLUB_SECRETARY("协会秘书长"),
CLUB_VICE_SECRETARY("协会副秘书长"),
// SCHOOL_UNION_CLUB_ADMIN("校工会协会管理员"),
CLUB_AUDIT_LEADER("协会分管领导"),
CLUB_OPERATOR("协会操作员"),
/*大病基金会员*/
AID_FUND_MEMBER("大病基金会员"),
/*爱心扶助基金*/
LOVE_ASSIST_WORK_GROUP_LEADER("爱心扶助小组组长"),
LOVE_ASSIST_WORK_GROUP_MEMBER("爱心扶助小组审批员"),
PSYCHOLOGY_DOCTOR("心理咨询师"),
LEGAL_DOCTOR("法律顾问"),
UNION_REIMBURSEMENT_MANAGER("分工会报销负责人"),
CLUB_REIMBURSEMENT_MANAGER("协会报销负责人"),
SCHOOL_REIMBURSEMENT_MANAGER("校工会报销负责人"),
;
/**
* 避免和枚举类的name冲突
*/
public final String roleName;
}
@@ -0,0 +1,15 @@
package com.budwk.app.base.enums;
/**
* 浏览器平台
*/
public enum BrowserPlatform {
PC_WEB,
H5_WEB,
WEI_XIN,
QI_YE_WEI_XIN;
}
@@ -0,0 +1,35 @@
package com.budwk.app.base.enums;
import cn.hutool.core.convert.Convert;
import java.util.Arrays;
import java.util.Optional;
public interface CodedEnum {
Integer getCode();
String getMessage();
public static <E extends Enum<?> & CodedEnum> Optional<E> codeOf(Class<E> enumClass, Object code) {
try {
return Arrays.stream(enumClass.getEnumConstants()).filter(e ->
e.getCode().equals(Convert.toInt(code))
|| e.name().equalsIgnoreCase(Convert.toStr(code))
|| e.getMessage().equalsIgnoreCase(Convert.toStr(code))
).findAny();
} catch (Exception e) {
return Optional.empty();
}
}
/**
* 获取数据类型,用于转换String或Integer
*
* @return
*/
default Class<?> getDataType() {
return Integer.class;
}
}
@@ -0,0 +1,21 @@
package com.budwk.app.base.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 登录类型
*/
@Getter
@AllArgsConstructor
public enum LoginType {
QI_YE_WECHAT("企业微信"),
WECHAT("微信"),
PC_LOCAL("PC本地"),
MOBILE_LOCAL("手机本地"),
CAS("CAS");
private final String value;
}
@@ -0,0 +1,21 @@
package com.budwk.app.base.enums;
/**
* 文件存储桶的权限策略枚举
*/
public enum MinIoFileBucketAuthEnum {
/**
* 私有的(仅有 owner 可以读写)
*/
PRIVATE,
/**
* 公有读,私有写( owner 可以读写, 其他客户可以读)
*/
PUBLIC_READ,
/**
* 公共读写(即所有人都可以读写,慎用)
*/
PUBLIC_READ_WRITE
}
@@ -0,0 +1,18 @@
package com.budwk.app.base.enums;
import lombok.Data;
import java.util.List;
/**
* @author mldong
* @date 2023/9/27
*/
@Data
//@ApiModel(value = "UpAndDownParam对象", description = "启用/禁用实体")
public class UpAndDownParam {
// @ApiModelProperty(value = "id集合")
private List<Object> ids;
// @ApiModelProperty(value = "操作类型")
private YesNoEnum opType;
}
@@ -0,0 +1,52 @@
package com.budwk.app.base.enums;
import com.budwk.app.base.annotation.DictEnum;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* yes_no
*
* @author mldong
*/
@DictEnum(key = "yes_no", name = "是否")
public enum YesNoEnum implements CodedEnum {
/**
* 是
*/
YES(1, ""),
/**
* 否
*/
NO(0, "");
private Integer code;
private String message;
/**
* 未删除
*/
public final static int Y = 1;
/**
* 已删除
*/
public final static int N = 0;
@JsonCreator
public static YesNoEnum forValue(Object value) {
return CodedEnum.codeOf(YesNoEnum.class, value).get();
}
YesNoEnum(int value, String name) {
this.code = value;
this.message = name;
}
@JsonValue
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
@@ -0,0 +1,14 @@
package com.budwk.app.base.event.flow.jumptofirsttask;
/**
* @version 1.0
* @Author FKY
* @nameFlowJumpToFirstTaskNodeListener
* @Date 2025/12/20 14:10
* @注释
*/
public interface FlowJumpToFirstTaskNodeListener {
void receive(Long processTaskId);
}
@@ -0,0 +1,25 @@
package com.budwk.app.base.event.flow.jumptofirsttask;
import com.budwk.app.base.event.user.UserChangeEventListener;
import com.budwk.app.base.utils.AppIocUtil;
import org.nutz.ioc.Ioc;
import org.nutz.mvc.Mvcs;
/**
* @version 1.0
* @Author FKY
* @nameFlowJumpToFirstTaskNodeEvent
* @Date 2025/12/20 14:09
* @注释
*/
public class FlowJumpToFirstTaskNodePublisher {
public static void broadcast(Long processTaskId) {
String[] names = Mvcs.getIoc().getNamesByType(FlowJumpToFirstTaskNodeListener.class);
for (String listener : names) {
FlowJumpToFirstTaskNodeListener listenerBean = Mvcs.getIoc().get(FlowJumpToFirstTaskNodeListener.class, listener);
listenerBean.receive(processTaskId);
}
}
}
@@ -0,0 +1,13 @@
package com.budwk.app.base.event.role;
/**
* @version 1.0
* @Author zzr
* @nameRoleEventListener
* @Date 2025/9/3 17:13
* @注释
*/
public interface RoleEventListener {
void receive(RoleEventMsg message);
}
@@ -0,0 +1,68 @@
package com.budwk.app.base.event.role;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @version 1.0
* @Author zzr
* @nameRoleEventMsg
* @Date 2025/9/3 17:14
* @注释
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RoleEventMsg {
/**
* 用户id
*/
private List<String> userIds;
/**
* 角色code
*/
private String roleCode;
/**
* 单位id
*/
private String unitId;
/**
* 操作类型
*/
private Integer operationType;
/**
* 添加角色
*/
public static final int ADD_ROLE = 1;
/**
* 删除角色
*/
public static final int REMOVE_ROLE = 2;
/**
* 更新角色
*/
public static final int RENEW_ROLE = 3;
public RoleEventMsg(String unitId, String roleCode, Integer operationType){
this.unitId = unitId;
this.roleCode = roleCode;
this.operationType = operationType;
}
public RoleEventMsg(List<String> userIds, String roleCode, Integer operationType){
this.userIds = userIds;
this.roleCode = roleCode;
this.operationType = operationType;
}
}
@@ -0,0 +1,21 @@
package com.budwk.app.base.event.role;
import org.nutz.mvc.Mvcs;
/**
* @version 1.0
* @Author zzr
* @nameRoleEventPublisher
* @Date 2025/9/3 17:14
* @注释
*/
public class RoleEventPublisher {
public static void broadcast(RoleEventMsg event){
String[] names = Mvcs.getIoc().getNamesByType(RoleEventListener.class);
for (String name : names) {
RoleEventListener listener = Mvcs.getIoc().get(RoleEventListener.class, name);
listener.receive(event);
}
}
}
@@ -0,0 +1,17 @@
package com.budwk.app.base.event.user;
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class SysUserEvent {
//变更类型
private MemberChangeType memberChangeType;
//userId
private String userId;
}
@@ -0,0 +1,7 @@
package com.budwk.app.base.event.user;
public interface SysUserEventListener {
void onEvent(SysUserEvent event);
}
@@ -0,0 +1,14 @@
package com.budwk.app.base.event.user;
import org.nutz.mvc.Mvcs;
public class SysUserPublisher {
public static void notify(SysUserEvent event){
String[] names = Mvcs.getIoc().getNamesByType(SysUserEventListener.class);
for (String name : names) {
SysUserEventListener listener = Mvcs.getIoc().get(SysUserEventListener.class, name);
listener.onEvent(event);
}
}
}
@@ -0,0 +1,13 @@
package com.budwk.app.base.event.user;
/**
* @version 1.0
* @Author zzr
* @nameUserChangeEventListener
* @Date 2025/8/13 14:18
* @注释
*/
public interface UserChangeEventListener {
void receive(UserChangeMsg message);
}
@@ -0,0 +1,70 @@
package com.budwk.app.base.event.user;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @version 1.0
* @Author zzr
* @nameUserChangeMsg
* @Date 2025/8/13 11:51
* @注释
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserChangeMsg {
/**
* 变更资源,用户Id
*/
private List<String> userIds;
/**
* 操作类型
*/
private Integer operationType;
/**
* 变更前的单位Id,当operationType值为1时,该字段有效
*/
private String sourceUnitId;
/**
* 变更后的单位Id,当operationType值为1时,该字段有效
*/
private String targetUnitId;
/**
* 单位变动
*/
public final static int UNIT_CHANGE_OPERATION = 1;
/**
* 退休
*/
public final static int RETIRE_OPERATION = 2;
/**
* 入会
*/
public final static int RESTORE_OPERATION = 3;
public UserChangeMsg(List<String> userIds, Integer operationType) {
super();
this.userIds = userIds;
this.operationType = operationType;
}
public UserChangeMsg(List<String> userIds, Integer operationType, String sourceUnitId) {
super();
this.userIds = userIds;
this.operationType = operationType;
this.sourceUnitId = sourceUnitId;
}
}
@@ -0,0 +1,25 @@
package com.budwk.app.base.event.user;
import com.budwk.app.base.utils.AppIocUtil;
import org.nutz.ioc.Ioc;
/**
* @version 1.0
* @Author zzr
* @nameUserChangePublisher
* @Date 2025/8/13 14:23
* @注释
*/
public class UserChangePublisher {
// 发送消息给所有订阅者
public static void broadcast(UserChangeMsg msg) {
Ioc ioc = AppIocUtil.get();
String[] names = ioc.getNamesByType(UserChangeEventListener.class);
for (String listener : names) {
UserChangeEventListener listenerBean = ioc.get(UserChangeEventListener.class, listener);
listenerBean.receive(msg);
}
}
}
@@ -0,0 +1,19 @@
package com.budwk.app.base.exception;
import cn.hutool.core.util.StrUtil;
/**
* @author wizzer@qq.com
*/
public class BaseException extends RuntimeException{
private static final long serialVersionUID = 7192152812384031563L;
public BaseException(String message) {
super(message);
}
public BaseException(String message, Object... arguments) {
super(StrUtil.format(message, arguments));
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.base.exception;
import cn.hutool.core.util.StrUtil;
/**
* 方法参数校验异常
*/
public class MethodArgumentNotValidException extends RuntimeException{
public MethodArgumentNotValidException(String message) {
super(message);
}
public MethodArgumentNotValidException(String message, Object... arguments) {
super(StrUtil.format(message, arguments));
}
}
@@ -0,0 +1,8 @@
package com.budwk.app.base.exception;
public class UnknownAccountException extends BaseException{
public UnknownAccountException(String message) {
super(message);
}
}
@@ -0,0 +1,19 @@
package com.budwk.app.base.interceptor.repeatSubmit;
import com.budwk.app.base.annotation.RepeatSubmit;
import org.nutz.aop.MethodInterceptor;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.aop.SimpleAopMaker;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.Method;
import java.util.List;
@IocBean(name = "$aop_repeatSubmit")
public class RepeatSubmitAopConfiguration extends SimpleAopMaker<RepeatSubmit> {
@Override
public List<? extends MethodInterceptor> makeIt(RepeatSubmit repeatSubmit, Method method, Ioc ioc) {
return List.of(new RepeatSubmitAopInterceptor(ioc, repeatSubmit, method));
}
}
@@ -0,0 +1,51 @@
package com.budwk.app.base.interceptor.repeatSubmit;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.RepeatSubmit;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.InterceptorChain;
import org.nutz.aop.MethodInterceptor;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.Ioc;
import org.nutz.mvc.Mvcs;
import java.lang.reflect.Method;
public class RepeatSubmitAopInterceptor implements MethodInterceptor {
protected Ioc ioc;
public RepeatSubmitAopInterceptor(Ioc ioc, RepeatSubmit repeatSubmit, Method method) {
RedisService redisService = ioc.get(RedisService.class);
}
@Override
public void filter(InterceptorChain chain) throws Throwable {
Method method = chain.getCallingMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
int interval = annotation.interval();
RedisService redisService = Mvcs.getIoc().get(RedisService.class);
String source = method.getDeclaringClass().getName() + "#" + method.getName();
Object[] args = chain.getArgs();
String parameterJson = "";
if (ObjectUtil.isNotEmpty(args)) {
parameterJson = JSONUtil.toJsonStr(args); // 序列化实际参数
}
String key = RedisConstant.REPEAT_SUBMIT_PREFIX + SecurityUtil.getUserId() + ":" + source;
Boolean exists = redisService.exists(key);
if (exists) {
// 判断
String oldParameterJson = redisService.get(key);
if (oldParameterJson != null && oldParameterJson.equalsIgnoreCase(parameterJson)) {
throw new BaseException(annotation.message());
}
} else {
redisService.setex(key, interval / 1000, parameterJson);
}
chain.doChain();
}
}
@@ -0,0 +1,34 @@
package com.budwk.app.base.interceptor.sLog;
import com.budwk.app.base.annotation.SLog;
import org.nutz.aop.MethodInterceptor;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.aop.SimpleAopMaker;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
* Created by wizzer on 2016/6/22.
*/
@IocBean(name = "$aop_syslog")
public class SLogAopConfigration extends SimpleAopMaker<SLog> {
@Inject("refer:$ioc")
protected Ioc ioc;
public List<? extends MethodInterceptor> makeIt(SLog slog, Method method, Ioc ioc) {
return Arrays.asList(new SLogAopInterceptor(ioc, slog, method));
}
public String[] getName() {
return new String[0];
}
public boolean has(String name) {
return false;
}
}
@@ -0,0 +1,88 @@
package com.budwk.app.base.interceptor.sLog;
import com.budwk.app.base.annotation.SLog;
import org.nutz.aop.InterceptorChain;
import org.nutz.aop.MethodInterceptor;
import org.nutz.el.El;
import org.nutz.ioc.Ioc;
import org.nutz.lang.segment.CharSegment;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Created by wizzer on 2016/6/22.
*/
public class SLogAopInterceptor implements MethodInterceptor {
private static final Log log = Logs.get();
protected SLogService sLogService;
protected String source;
protected String type;
protected String tag;
protected CharSegment seg;
protected boolean param;
protected boolean result;
protected boolean async;
protected Map<String, El> els;
protected Ioc ioc;
public SLogAopInterceptor(Ioc ioc, SLog slog, Method method) {
this.seg = new CharSegment(slog.msg());
if (seg.hasKey()) {
els = new HashMap<String, El>();
for (String key : seg.keys()) {
els.put(key, new El(key));
}
}
this.param = slog.param();
this.result = slog.result();
this.ioc = ioc;
this.source = method.getDeclaringClass().getName() + "#" + method.getName();
this.tag = slog.tag();
SLog _s = method.getDeclaringClass().getAnnotation(SLog.class);
if (_s != null) {
this.tag = _s.tag() + "," + this.tag;
}
this.type = slog.type();
this.async = slog.async();
}
public void filter(InterceptorChain chain) throws Throwable {
try {
chain.doChain();
doLog("aop.after", seg, chain, null);
} catch (Throwable e) {
doLog("aop.error", seg, chain, e);
throw e;
}
}
protected void doLog(String t, CharSegment seg, InterceptorChain chain, Throwable e) {
if (sLogService == null)
sLogService = ioc.get(SLogService.class);
try {
sLogService.log(t,
type,
tag,
source,
seg,
els,
param,
result,
async,
chain.getArgs(),
chain.getReturn(),
chain.getCallingMethod(),
chain.getCallingObj(),
e);
} catch (Exception e1) {
log.debug("slog fail", e1);
}
}
}
@@ -0,0 +1,230 @@
package com.budwk.app.base.interceptor.sLog;
import com.budwk.app.sys.models.Sys_log;
import com.budwk.app.sys.services.SysLogService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.Nutz;
import org.nutz.el.El;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.segment.CharSegment;
import org.nutz.lang.util.ClassMetaReader;
import org.nutz.lang.util.Context;
import org.nutz.lang.util.MethodParamNamesScaner;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.Mvcs;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Created by wizzer on 2016/6/22.
*/
@IocBean(create = "init", depose = "close")
public class SLogService implements Runnable {
private static final Log log = Logs.get();
ExecutorService es;
LinkedBlockingQueue<Sys_log> queue;
@Inject
protected SysLogService sysLogService;
/**
* 异步插入日志
*
* @param syslog 日志对象
*/
public void async(Sys_log syslog) {
LinkedBlockingQueue<Sys_log> queue = this.queue;
if (queue != null)
try {
boolean re = queue.offer(syslog, 50, TimeUnit.MILLISECONDS);
if (!re) {
log.info("syslog queue is full, drop it ...");
}
} catch (InterruptedException e) {
}
}
/**
* 同步插入日志
*
* @param syslog 日志对象
*/
public void sync(Sys_log syslog) {
try {
sysLogService.fastInsertSysLog(syslog);
} catch (Throwable e) {
log.info("insert syslog sync fail", e);
}
}
public void run() {
while (true) {
LinkedBlockingQueue<Sys_log> queue = this.queue;
if (queue == null)
break;
try {
Sys_log sysLog = queue.poll(1, TimeUnit.SECONDS);
if (sysLog != null) {
sync(sysLog);
}
} catch (InterruptedException e) {
break;
}
}
}
/**
* 本方法通常由aop拦截器调用.
*
* @param t 日志类型
* @param tag 标签
* @param source 源码位置
* @param els 消息模板的EL表达式预处理表
* @param param 是否异步插入
* @param result 是否异步插入
* @param async 是否异步插入
* @param args 方法参数
* @param re 方法返回值
* @param method 方法实例
* @param obj 被拦截的对象
* @param e 异常对象
*/
public void log(String t, String type, String tag, String source, CharSegment seg,
Map<String, El> els, boolean param, boolean result,
boolean async,
Object[] args, Object re, Method method, Object obj,
Throwable e) {
String _msg = null;
if (seg.hasKey()) {
Context ctx = Lang.context();
List<String> names = null;
if (Nutz.majorVersion() == 1 && Nutz.minorVersion() < 60) {
Class<?> klass = obj.getClass();
if (klass.getName().endsWith("$$NUTZAOP"))
klass = klass.getSuperclass();
String key = klass.getName();
if (caches.containsKey(key))
names = caches.get(key).get(ClassMetaReader.getKey(method));
else {
try {
Map<String, List<String>> tmp = MethodParamNamesScaner.getParamNames(klass);
names = tmp.get(ClassMetaReader.getKey(method));
caches.put(key, tmp);
} catch (IOException e1) {
log.debug("error when reading param name");
}
}
} else {
names = MethodParamNamesScaner.getParamNames(method);
}
if (names != null) {
for (int i = 0; i < names.size() && i < args.length; i++) {
ctx.set(names.get(i), args[i]);
}
}
ctx.set("obj", obj);
ctx.set("args", args);
ctx.set("re", re);
ctx.set("return", re);
ctx.set("req", Mvcs.getReq());
ctx.set("resp", Mvcs.getResp());
Context _ctx = Lang.context();
for (String key : seg.keys()) {
_ctx.set(key, els.get(key).eval(ctx));
}
_msg = seg.render(_ctx).toString();
} else {
_msg = seg.getOrginalString();
}
String _param = "";
String _result = "";
if (param && args != null) {
try {
_param = Json.toJson(args);
} catch (Exception e1) {
_param = "传参不能转换为JSON格式";
}
}
if (result && re != null) {
try {
_result = Json.toJson(re);
} catch (Exception e1) {
_param = "返回对象不能转换为JSON格式";
}
}
log(type, tag, source, _msg, async, _param, _result);
}
public void log(String type, String tag, String source, String msg, boolean async, String param, String result) {
Sys_log slog = makeLog(type, tag, source, msg, param, result);
if (async)
async(slog);
else
sync(slog);
}
protected static Map<String, Map<String, List<String>>> caches = new HashMap<String, Map<String, List<String>>>();
public void init() {
queue = new LinkedBlockingQueue<Sys_log>();
int c = Runtime.getRuntime().availableProcessors();
es = Executors.newFixedThreadPool(c);
for (int i = 0; i < c; i++) {
es.submit(this);
}
}
public void close() throws InterruptedException {
queue = null; // 触发关闭
if (es != null && !es.isShutdown()) {
es.shutdown();
es.awaitTermination(5, TimeUnit.SECONDS);
}
}
public static Sys_log makeLog(String type, String tag, String source, String msg, String param, String result) {
Sys_log sysLog = new Sys_log();
if (type == null || tag == null) {
throw new RuntimeException("type/tag can't null");
}
if (source == null) {
StackTraceElement[] tmp = Thread.currentThread().getStackTrace();
if (tmp.length > 2) {
source = tmp[2].getClassName() + "#" + tmp[2].getMethodName();
} else {
source = "main";
}
}
sysLog.setType(type);
sysLog.setTag(tag);
sysLog.setSrc(source);
sysLog.setMsg(msg);
sysLog.setParam(param);
sysLog.setResult(result);
if (Mvcs.getReq() != null) {
sysLog.setIp(Lang.getIP(Mvcs.getReq()));
}
sysLog.setCreatedBy(SecurityUtil.getUserId());
sysLog.setDelFlag(false);
sysLog.setUsername(SecurityUtil.getUserUsername());
sysLog.setLoginname(SecurityUtil.getUserLoginname());
return sysLog;
}
}
@@ -0,0 +1,94 @@
package com.budwk.app.base.model;
import cn.hutool.json.JSONObject;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Table("audit")
@Comment("审核记录表")
@Data
public class Audit extends BaseModel {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("审核人")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(els = {@EL("$me.uid()")})
private String auditor;
@Column
@Comment("是否通过")
@ColDefine(type = ColType.BOOLEAN)
private Boolean auditPass;
@Column
@Comment("审核时间")
@Prev(els = {@EL("$me.nowDate()")})
private Date auditTime;
@Column
@Comment("审核意见")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String auditOpinion;
@Column
@Comment("签字")
@ColDefine(type = ColType.TEXT)
private String auditSign;
@Column
@Comment("审核人姓名")
@ColDefine(type = ColType.VARCHAR, width = 50)
@PrevInsert(els = {@EL("$me.userName()")})
private String username;
@Column
@Comment("审核人工号")
@ColDefine(type = ColType.VARCHAR, width = 50)
@PrevInsert(els = {@EL("$me.loginName()")})
private String loginname;
@Column
@Comment("审核类型(1.通过 2.拒绝 3.退回)")
@ColDefine(type = ColType.INT)
private Integer auditType;
@Column
@Comment("扩展字段")
@ColDefine(type = ColType.MYSQL_JSON)
private JSONObject ext;
@Column
@Comment("父级ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String parentId;
public Date nowDate() {
return new Date();
}
public String uid() {
return SecurityUtil.getUserId();
}
public String userName() {
return SecurityUtil.getUserUsername();
}
public String loginName() {
return SecurityUtil.getUserLoginname();
}
}
@@ -0,0 +1,92 @@
package com.budwk.app.base.model;
import cn.dev33.satoken.stp.StpUtil;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.dao.interceptor.annotation.PrevUpdate;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Strings;
import java.io.Serializable;
/**
* Created by wizzer on 2016/6/21.
* Update by wizzer on 2021/8/30.
*/
@Data
public abstract class BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
@Column
@Comment("创建人")
@PrevInsert(els = @EL("$me.createdByUid()"))
@ColDefine(type = ColType.VARCHAR, width = 32)
private String createdBy;
/**
* Long不要用ColDefine定义,兼容oracle/mysql,支持2038年以后的时间戳
* 13位时间戳哦,不再是11位
*/
@Column
@Comment("创建时间")
@PrevInsert(now = true)
private Long createdAt;
@Column
@Comment("修改人")
@PrevInsert(els = @EL("$me.updatedByUid()"))
@PrevUpdate(els = @EL("$me.updatedByUid()"))
@ColDefine(type = ColType.VARCHAR, width = 32)
private String updatedBy;
/**
* Long不要用ColDefine定义,兼容oracle/mysql,支持2038年以后的时间戳
* 13位时间戳哦,不再是11位
*/
@Column
@Comment("修改时间")
@PrevInsert(now = true)
@PrevUpdate(now = true)
private Long updatedAt;
@Column
@Comment("删除标记")
@PrevInsert(els = @EL("$me.flag()"))
@ColDefine(type = ColType.BOOLEAN)
private Boolean delFlag;
public String toJsonString() {
return Json.toJson(this, JsonFormat.compact());
}
public Boolean flag() {
return false;
}
public String createdByUid() {
String uid = getCreatedBy();
if (Strings.isNotBlank(uid)) {
return uid;
}
try {
return StpUtil.getLoginIdAsString();
} catch (Exception e) {
return "";
}
}
public String updatedByUid() {
String uid = getUpdatedBy();
if (Strings.isNotBlank(uid)) {
return uid;
}
try {
return StpUtil.getLoginIdAsString();
} catch (Exception e) {
return "";
}
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.base.model;
import lombok.Data;
import java.util.List;
@Data
public class CustomFormField {
private String fieldName; // 字段名称
private String fieldType; // 字段类型(如 "text", "date", "file" 等)
private Boolean required; // 是否必填
private Integer maxLength; // 最大字符数(仅适用于文本类型)
private String dateRange; // 日期范围(仅适用于日期类型,格式为 "YYYY-MM-DD,YYYY-MM-DD"
private List<String> allowedFileTypes; // 允许的文件类型(仅适用于文件类型,如 ["pdf", "jpg"]
private Integer maxFiles; // 最大文件数量(仅适用于文件类型)
}
@@ -0,0 +1,25 @@
package com.budwk.app.base.model;
import lombok.Getter;
/**
* Excel导入错误信息
*/
public class ExcelImportError {
/**
* 行号
*/
private int row;
/**
* 错误信息
*/
@Getter
private String errMsg;
public void setErrInfo(String errMsg, int row) {
this.errMsg = errMsg;
this.row = row;
}
}
@@ -0,0 +1,29 @@
package com.budwk.app.base.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* Excel导入结果
* @param <T>
*/
@Data
public class ExcelImportRes<T extends ExcelImportError> {
private int totalRecords = 0;
private int successCount = 0;
private int failedCount = 0;
private List<T> errorDetails = new ArrayList<>();
// 失败数+1
public void addFailedCount() {
failedCount++;
System.out.println(failedCount);
}
}
@@ -0,0 +1,46 @@
package com.budwk.app.base.page;
import org.nutz.dao.pager.Pager;
import java.io.Serializable;
/**
* 指定偏移量及大小的Pager, 这样就不会受限于原生Pager的offset=(pageNumber-1)*pageSize
*
* @author wendal
*/
public class OffsetPager extends Pager implements Serializable {
private static final long serialVersionUID = -1385308131663113162L;
protected int offset = -1;
protected OffsetPager() {
}
/**
* 构建一个指定偏移量及大小的Pager
*
* @param offset 偏移量
* @param size 数据大小
*/
public OffsetPager(int offset, int size) {
super();
this.offset = offset;
setPageSize(size);
}
/**
* 覆盖超类的计算得到的offset
*/
public int getOffset() {
if (offset > -1)
return offset;
return super.getOffset();
}
public void setOffset(int offset) {
this.offset = offset;
}
}
@@ -0,0 +1,57 @@
package com.budwk.app.base.page;
public interface Paginable {
/**
* 总记录数
*
* @return
*/
public int getTotalCount();
/**
* 总页数
*
* @return
*/
public int getTotalPage();
/**
* 每页记录数
*
* @return
*/
public int getPageSize();
/**
* 当前页号
*
* @return
*/
public int getPageNo();
/**
* 是否第一页
*
* @return
*/
public boolean isFirstPage();
/**
* 是否最后一页
*
* @return
*/
public boolean isLastPage();
/**
* 返回下页的页号
*/
public int getNextPage();
/**
* 返回上页的页号
*/
public int getPrePage();
}
@@ -0,0 +1,77 @@
package com.budwk.app.base.page;
import org.nutz.lang.Lang;
import java.util.List;
public class Pagination<T> extends SimplePage implements java.io.Serializable {
private static final long serialVersionUID = 1L;
public Pagination() {
}
/**
* 构造器
*
* @param pageNo 页码
* @param pageSize 每页几条数据
* @param totalCount 总共几条数据
*/
public Pagination(int pageNo, int pageSize, int totalCount) {
super(pageNo, pageSize, totalCount);
}
/**
* 构造器
*
* @param pageNo 页码
* @param pageSize 每页几条数据
* @param totalCount 总共几条数据
* @param list 分页内容
*/
public Pagination(int pageNo, int pageSize, int totalCount, List<T> list) {
super(pageNo, pageSize, totalCount);
this.list = list;
}
/**
* 第一条数据位置
*
* @return
*/
public int getFirstResult() {
return (pageNo - 1) * pageSize;
}
/**
* 当前页的数据
*/
private List<T> list;
/**
* 获得分页内容
*
* @return
*/
public List<T> getList() {
return list;
}
/**
* @param classOfT 列表容器內的元素类型
* @return
*/
public List<T> getList(Class<T> classOfT) {
return Lang.collection2list(list, classOfT);
}
/**
* 设置分页内容
*
* @param list
*/
public void setList(List<T> list) {
this.list = list;
}
}
@@ -0,0 +1,175 @@
package com.budwk.app.base.page;
import java.util.ArrayList;
import java.util.List;
public class SimplePage implements java.io.Serializable, Paginable {
private static final long serialVersionUID = 1L;
public static final int DEF_COUNT = 10;
private List<Integer> localArrayList = new ArrayList<Integer>();
public List<Integer> getSegment() {
return localArrayList;
}
/**
* 检查页码 checkPageNo
*
* @param pageNo
* @return if pageNo==null or pageNo 小于 1 then return 1 else return pageNo
*/
public static int cpn(Integer pageNo) {
return (pageNo == null || pageNo < 1) ? 1 : pageNo;
}
public SimplePage() {
}
/**
* 构造器
*
* @param pageNo 页码
* @param pageSize 每页几条数据
* @param totalCount 总共几条数据
*/
public SimplePage(int pageNo, int pageSize, int totalCount) {
setTotalCount(totalCount);
setPageSize(pageSize);
setPageNo(pageNo);
adjustPageNo();
int totalPages = getTotalPage();
minPage = minPage < 1 ? 1 : minPage;
maxPage = maxPage > totalPages ? totalPages : maxPage;
for (int i = minPage; i <= maxPage; i++) {
localArrayList.add(i);
}
}
/**
* 调整页码,使不超过最大页数
*/
public void adjustPageNo() {
if (pageNo == 1) {
return;
}
int tp = getTotalPage();
if (pageNo > tp) {
pageNo = tp;
}
}
/**
* 获得页码
*/
public int getPageNo() {
return pageNo;
}
/**
* 每页几条数据
*/
public int getPageSize() {
return pageSize;
}
/**
* 总共几条数据
*/
public int getTotalCount() {
return totalCount;
}
/**
* 总共几页
*/
public int getTotalPage() {
int totalPage = totalCount / pageSize;
if (totalPage == 0 || totalCount % pageSize != 0) {
totalPage++;
}
return totalPage;
}
/**
* 是否第一页
*/
public boolean isFirstPage() {
return pageNo <= 1;
}
/**
* 是否最后一页
*/
public boolean isLastPage() {
return pageNo >= getTotalPage();
}
/**
* 下一页页码
*/
public int getNextPage() {
if (isLastPage()) {
return pageNo;
} else {
return pageNo + 1;
}
}
/**
* 上一页页码
*/
public int getPrePage() {
if (isFirstPage()) {
return pageNo;
} else {
return pageNo - 1;
}
}
protected int totalCount = 0;
protected int pageSize = 20;
protected int pageNo = 1;
/**
* if totalCount 小于 0 then totalCount=0
*
* @param totalCount
*/
public void setTotalCount(int totalCount) {
if (totalCount < 0) {
this.totalCount = 0;
} else {
this.totalCount = totalCount;
}
}
/**
* if pageSize 小于 1 then pageSize=DEF_COUNT
*
* @param pageSize
*/
public void setPageSize(int pageSize) {
if (pageSize < 1) {
this.pageSize = DEF_COUNT;
} else {
this.pageSize = pageSize;
}
}
/**
* if pageNo 小于 1 then pageNo=1
*
* @param pageNo
*/
public void setPageNo(int pageNo) {
if (pageNo < 1) {
this.pageNo = 1;
} else {
this.pageNo = pageNo;
}
}
int minPage = pageNo - (int) Math.floor((pageSize - 1) / 2.0D);
int maxPage = pageNo + (int) Math.ceil((pageSize - 1) / 2.0D);
int totalPage = getTotalPage();
}
@@ -0,0 +1,47 @@
package com.budwk.app.base.page.datatable;
import java.io.Serializable;
/**
* Created by wizzer on 2016/6/27.
*/
public class DataTableColumn implements Serializable {
private static final long serialVersionUID = 1L;
protected String data;
protected String name;
protected boolean searchable;
protected boolean orderable;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSearchable() {
return searchable;
}
public void setSearchable(boolean searchable) {
this.searchable = searchable;
}
public boolean isOrderable() {
return orderable;
}
public void setOrderable(boolean orderable) {
this.orderable = orderable;
}
}
@@ -0,0 +1,29 @@
package com.budwk.app.base.page.datatable;
import java.io.Serializable;
/**
* Created by wizzer on 2016/6/27.
*/
public class DataTableOrder implements Serializable {
private static final long serialVersionUID = 1L;
protected int column;
protected String dir;
public int getColumn() {
return column;
}
public void setColumn(int column) {
this.column = column;
}
public String getDir() {
return dir;
}
public void setDir(String dir) {
this.dir = dir;
}
}
@@ -0,0 +1,21 @@
package com.budwk.app.base.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 单个条件
*/
@Data
public class Condition {
@ApiModelProperty("字段名")
private String field;
@ApiModelProperty("操作符: =, !=, >, <, >=, <=, LIKE, IN, NOT IN, IS NULL, IS NOT NULL")
private String operator;
@ApiModelProperty("字段值")
private Object value;
}
@@ -0,0 +1,24 @@
package com.budwk.app.base.param;
import com.budwk.app.sys.param.SysDataUserUpdateParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* 条件组,支持嵌套的与或非逻辑
*/
@Data
public class ConditionGroup {
@ApiModelProperty("逻辑类型:AND, OR")
private String logic = "AND";
@ApiModelProperty("条件列表")
private List<Condition> conditions;
@ApiModelProperty("嵌套条件组")
private List<ConditionGroup> groups;
}
@@ -0,0 +1,14 @@
package com.budwk.app.base.param;
import lombok.Data;
/**
* 自定义导出表格列
*/
@Data
public class ExportTableColumns {
private String label;
private String prop;
}
@@ -0,0 +1,60 @@
package com.budwk.app.base.param;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
/**
* @Digits注解 验证注解的元素值的整数位数和小数位数上限 ,并且类型为floatdoubleBigDecimal。
*/
@Data
public class PageForm {
@NotNull(message = "pageNumber不能为空")
@Min(value = 1, message = "pageNumber最小为1")
@Positive(message = "pageNumber必须是正数")
private Integer pageNumber;
@NotNull(message = "pageSize不能为空")
@Max(value = 200, message = "pageSize最大为50")
private Integer pageSize;
private String pageOrderName;
private String pageOrderBy;
private String searchName;
private String searchKeyword;
/*======================流程参数==============================*/
/**
* 是否审核
*/
private Boolean audit;
/**
* 业务ID
*/
private String bizId;
public PageForm defaultSort(String column, String order) {
this.setPageOrderName(column);
this.setPageOrderBy(order);
return this;
}
public PageForm defaultSortAsc(String column) {
return this.defaultSort(column, "ascending");
}
public PageForm defaultSortDesc(String column) {
return this.defaultSort(column, "descending");
}
}
@@ -0,0 +1,68 @@
package com.budwk.app.base.payment.alipay.controller;
import com.budwk.app.base.payment.alipay.service.AlipayService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @ClassName AlipayClientController
* @Description TODO
* @Author zhf
* @Date 2024/1/25 15:36
*/
@IocBean
@Slf4j
@Ok("json:full")
@At("/mobile/memberCost/AlipayClient")
public class AlipayClientController {
@Inject
private AlipayService alipayService;
@At("/tradePagePay")
public String tradePagePay(String money, String paymentProjectId) throws Exception {
log.info("统一收单下单并支付接口调用");
String formStr = alipayService.tradeCreate(money, paymentProjectId);
return formStr;
}
@At("/tradeNotify")
@Ok("re")
public String tradeNotify(HttpServletRequest request) {
try {
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
//乱码解决,这段代码在出现乱码时使用
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
log.info("支付通知,正在执行,通知参数:{}");
return alipayService.tradeNotify(params);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
@@ -0,0 +1,22 @@
package com.budwk.app.base.payment.alipay.service;
/**
* @ClassName AlipayClientConfig
* @Description TODO
* @Author zhf
* @Date 2024/1/25 15:33
*/
public interface AlipayClientConfig {
String serverUrl = "https://openapi.alipay.com/gateway.do";
String appId = "2021004133670087";
String sellerId = "2088002638762620";
String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCEJBWNlUIY/2Tave1OtjTgvA21GZ5tdvZZD3jFmixIFTkEdZaRUpXCvlKuZ9heoL1IQwpbuOErJ4dM2f2Ju/Oc7QtUOjEOX6CnElR73KK5OSY8d8f5822LUOgp2zg5FHI6C7olYmjN4Nw26G6BGyqQBmWs4/sKzghKelEaH9AqkR8DZ+yOvuYtIg5KEvVixsM27TMhd6/3BuNXDIiQDVH9ZeQVb9IKuynZ6hnUJS0T4MkS41qehS1u8LgplGU5yvrNuUNeQtybADsiGHDNmNkl5lVt0QNh+N/lo56//duf5PJwh8ApDB22eXptiS6p321PSrYowy5VeFpHC07Tref9AgMBAAECggEAPQddXPKegGUT82LcgwnsCg9QVV/rp6rVw/Q9VyYJZZFHsCOO0rdNq4+zKBGWAL5ELWepFQuiEwhCmN/lFQPVU5i3LsNhfL31uLPiiQIxtVILssmvdmVTJpDLaOlyYgprZxubcUCuHsfHbSSURNSDJ9mMg/kXLLpTYfJ0Gpiuz7lkrMuAxncdz88Sp2r0O9k2T+TcnHFpRXHs+C/QAaa15EF18ieDSBLPkY0ti1R8J91LGAzDrjwcP7pNlq4u7TasZ7djv47ZFwQ3yjWoo13d9ab0MBYeQx9Grf5N+JGNf33YLyulQ0IshcuwNyB+RYhyWGjNmjnJdytzKRMOYSpPgQKBgQC6iqMPX+LGunyI+3T9l/AwAVWRJXzEHPflNxDYfQbicB5awlRixxH5T1qyZ46KZb/pBt01qDULXy0bEObVFSjAn1wXPzC9Fwb+BtVZUroYBTKV3tOAMpVCz2AAC34OZwG/3X//sRtW9Sr0Dr+yCcx8ULMiFS1BuCZfhSNXA516NQKBgQC1V+rge1pYKGnjtWqML4qJy4FEIDI4fSpGIizxumYDMxdXoUdNKoG2NHQkznHU4fIHJPVvIN/Tm82mfj2rhH9eFuu1FB8TL2soe5nPgzSJ9AmP7rZ7KBo3/SczvZqUEpii8+PjHu+Gx95BmgTzOFMnYz8xzitb4IBGTEGxVzavqQKBgCL28XFkS6FsOwOB/n+6Bu84kM1P9X7c3yo7lBC//pZa5ZWlbyPEpH3SffGKr5EJWVtYSgZQrQCgqle0Z0/xU14ngJ9q3LvUoR7kMQE5HGB8OOsWhRY2ZPvM2vWnMc2JSS6pWeCXVcSK164/RXcdlxKGHc15oq360lZ9Ig6hQtcxAoGAIYijd2V4HGtEwYXvsQKtlLOcMNPfVZDXu2Zgd0bnzFmrTFoXjE569A2ZGKFQbXRmxClydV1X5pKeIdagYtI6l1SXu8F/yZVwNGk+1R/HHGeTSRl0hyuwRYA7upaqOg2CSkfdcUdsNblKAUmQ9sUIo3T336bnX7MRJW6+iyO8ihkCgYBG8vG1fVUwPmqlRK+QanHxicR1+UyjRAN+aW6dgJUNMlC+tmPeHj21+WGkDdKPdhinHziLihLmzbmpwWSDwuGMMQPNQm8gSjiaqy1tWcP6XKDxjpG5biTzwEyHNuCIY6odS4HTeDOrrU4LvN6PxirN8xKBIpq+A2cgpGo+Mn4x+g==";
String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjO/QUy+ujNBlToL6Kt8YjvTRGpEwmXjqc9pFe7GIORVkzBXP3F/UC/w5mPiwXhhyymXAe7bUwLANL/QMUZhvpjjCdLENtFWC9bd6hq3m2oHiTu0oicbzqCuaoQkzik2NybrNjuH+aYVi4RU0eNYpdewQoHHW+hbfxkSYUWmx7NF4SuG93edYlpc/G/ImdrweATLE0b6W9il4H/BxVUweCraPrispiOM8r6TN3fiIKUQRP15HKQo8Xsr4pMJzfi37I9aN8AThN/xBf6sual8IZZsH5hC3PS37LrYkXbUjt0dK5tqKl3SsZPjvvHGyz9b9JC225+0q09Y6B8/axOlihwIDAQAB";
/* String serverUrl = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
String appId = "9021000134641646";
String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBFTF8qFbK+NiqUbKf1/jWHL/s0hfUXK6cyKwZCEXkEMZH1Lu6kZQl3kQye9Fsasawxn1vLmKz+OKjMiE+TdTytHDu5ULdLlSHN3bY1qf91i8L+RJfmZpb1p+NQOk9zImhhSauuk+itMkTC3bTiwwAkg/utOxkNB7YaynpAb3evhXnlX3LUVTHaMnCQFeV7ithh7CDKx0LPlxOpOvxnkWDBR3nsdcfjDqYNDVHXnUxEk0KQV4Ik6fA40+Gq/HXTByJolCSAaTk0OaH2ULp1RZAklWLqhiNEhytTZAlJUx8cSm36mm0c4jhgSXNdRz0Yi1BlPY9yxQGStpH5ue3+EfvAgMBAAECggEAda/D4Zp9HysIJyi7SZ6XwWaO2TSnAzfI/yq6NUxkpE1i6cSbr7rgrjHaTYiz5DvpkOw3GnHZDarhvtaKlopRl0JKIJSnhI6kBG/eDE6Q9Q4c8LaSONULdaXU6+z9ehmMddW28eDtkDNxi8qM+MuyEqcYnZMYXzdh1ADIQicTXArxo+9fpvlKUMNxOTO5LoOf0DtJg/xE1CD9PtWVc+l2G8S3+C52+RhfZ214YQdxwayMlvJQlWzLTbfzJCp8w4F2up1nJdwp7EY8k8xDUngEnG6BWm8ztWUVTTg7qp/+SC9yMTD2dl86wDkJt1lTtwcKaWxHYh2ZIn8a8+icHFykaQKBgQDgBEWAmuj0pgpny5Q0GRsUYPq42SwpN70u4rmD5UYFCeDHaKb4U3kyvA1V+1Tp0XWzFi7GU855h1n8dnQX64J3Vm4GfhftWwTJgB5ibbY53cZVEoV5nZe0bOO4hxxMdNy3BKjPovkT0REPpCkVas/RRLAFCdnvi6vmDkFxvv9JewKBgQCTgx9NZZCqSzzCJYpLTDOCitooOQmR8kAlgBiDCcHeH2wNVMh5O2Dl/OyTGauKUJFWIOSD8FXVTrNUbOEmx58msnS/+Mc5SnbEka5ptVEDp73gf6ctB+eZAMdO1kLsBT3iKnuyNa7pRyR/D2A692RHOTQZIvRHE1N+kM1r+iBPHQKBgCZRVLocxUAjrp/2eJE4wiMIO5qmuyEJwbhlW6ot634tJF+3jOfpD1AYToeoh+u+wk0xvCp8Z1LuTfwbHtyAkj5rcEJ4Cwu0h+sXmRro/USEpMokcuRMioh4H7OOmM6TJrW/fEiX7dbv6eUtBUH+lTGMeDdEahzJXUvqoFMegnhFAoGAa2eflRUMflIFzT7UMPsszjIXog3VoASJHETmlBK4q+ipHhPLXCoV5Z+BCaI5Op5rFBEjEcYeWQ/Slvgn/7p9cQmi5FxZgqdrTrjcC2/Pqx+pqxqHOMx2nvroI8jyNTOj2VchdaKM1qHMOw+Q+55Bz3IhMuQ2hMHXm2IHkEUw3SkCgYBjcqcWbJcq26L8yYVpU2JFzOvu7iHuV99TpEvF9Ym3cAcSNuoR5kGXzGS0MadYY87cd4HK9HvmXQmthLY14buKYxq27ZFfNbK/Auz8vk0Wbrm1Ym4Z1yMqClQqxKdr4hV29ijn1nPProRqgnVsGH3EtFmNxDfilZoVzh9ERaZe7w==";
String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArhElUiFMpxqFCs+8zDHa4OVuhdWgKUUGVKgWnfLktPgy9mDZkuGPKk295k8ktWQSUZsnFvT8ikJIKdGYfbSWzlXvCAn6lDDDlsjfOJWMQ6zLmb9xygCih2OjwN5xt9QFIIthnWq8sOXpWvqmoRpIFQHolxxmvvCmUZ/VwK6mwgwU/yVvVypruQ/FGrJA70LAcJ764spWAMy/pWIoIs5UewOlJa3TAcZQIqRnjupL2/sI/TpIWFWF90a/rahwLfBgZ83VH7MA4Tl9uFLc3aeNFLllDF4X7lRdzaHIMYgUUKte9yvlDy5Ihrt+ssjsgdRhqLHvFn00u/g7BCvO5ZmIUwIDAQAB";
*/
}
@@ -0,0 +1,15 @@
package com.budwk.app.base.payment.alipay.service;
import com.budwk.app.base.service.BaseService;
import java.util.Map;
public interface AlipayService extends BaseService {
/**
* 支付宝开放平台接收 request请求对象后
* @return
*/
String tradeCreate( String money, String paymentProjectId) throws Exception;
String tradeNotify(Map<String, String> params) ;
}
@@ -0,0 +1,140 @@
package com.budwk.app.base.payment.alipay.service.impl;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConstants;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.budwk.app.base.payment.alipay.service.AlipayClientConfig;
import com.budwk.app.base.payment.alipay.service.AlipayService;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffmanage.memberCost.model.MemberPaymentInfo;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Date;
import java.util.Map;
/**
* @ClassName AlipayNewServiceImpl
* @Description TODO
* @Author zhf
* @Date 2024/1/25 15:38
*/
@Slf4j
@IocBean(args = {"refer:dao"})
public class AlipayNewServiceImpl extends BaseServiceImpl implements AlipayService {
public AlipayNewServiceImpl(Dao dao) {
super(dao);
}
DefaultAlipayClient alipayClient = new DefaultAlipayClient(AlipayClientConfig.serverUrl,
AlipayClientConfig.appId,
AlipayClientConfig.privateKey,
AlipayConstants.FORMAT_JSON,
AlipayConstants.CHARSET_UTF8,
AlipayClientConfig.alipayPublicKey,
AlipayConstants.SIGN_TYPE_RSA2);
@Override
public String tradeCreate(String money, String paymentProjectId) throws Exception {
String outTradeNo = String.valueOf(new Date().getTime());
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
//支付宝公共参数
request.setReturnUrl("http://sansi.ltd/mobile/memberCost/AlipayClient/tradeNotify");
//面向对象封装业务参数
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount(money);
model.setSubject("会员入会会费");
model.setProductCode("QUICK_WAP_WAY");
model.setSellerId(AlipayClientConfig.sellerId);
model.setTimeoutExpress("30m");
model.setQuitUrl("http://sansi.ltd/mobile/memberCost/list");
request.setBizModel(model);
//执行请求,调用支付宝
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request, "POST");
if (response.isSuccess()) {
dao().update(MemberPaymentInfo.class, Chain.make("payId", outTradeNo), Cnd.where("paymentProjectId", "=", paymentProjectId)
.and("userId", "=", SecurityUtil.getUserId()));
log.info("调用成功,返回结果:[{}]", response.getBody());
return response.getBody();
} else {
log.info("调用失败!!");
}
return null;
}
@Override
public String tradeNotify(Map<String, String> params) {
String total_amount = params.get("total_amount");
String out_trade_no = params.get("out_trade_no");
String trade_no = params.get("trade_no");
//
String result = "failure";
try {
//异步通知验签
boolean signVerified = AlipaySignature.rsaCheckV1(params,
AlipayClientConfig.alipayPublicKey,
AlipayConstants.CHARSET_UTF8,
AlipayConstants.SIGN_TYPE_RSA2);
if (!signVerified) {
// TODO 验签失败则记录异常日志,并在response中返回failure.
log.error("支付成功,异步通知验签失败!");
return result;
}
log.info("支付成功,异步通知验签成功!");
//TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验
//1.验证out_trade_no 是否为商家系统中创建的订单号
String outTradeNo = params.get("out_trade_no");
if (!outTradeNo.equals(out_trade_no)) {
log.error("订单号不一致");
return result;
}
//2.判断 total_amount 是否确实为该订单的实际金额
String totalAmount = params.get("total_amount");
if (!totalAmount.equals(total_amount)) {
log.error("订单的实际金额不一致");
return result;
}
//3.校验通知中的 seller_id是否为 out_trade_no 这笔单据的对应的操作方
String sellerId = params.get("seller_id");
if (!sellerId.equals(AlipayClientConfig.sellerId)) {
log.error("商家PID校验失败");
return result;
}
//4.验证 app_id 是否为该商家本身
String appId = params.get("app_id");
if (!appId.equals(AlipayClientConfig.appId)) {
log.error("app_id校验失败");
return result;
}
dao().update(MemberPaymentInfo.class,
Chain.make("isPayment", true)
.add("paymentMoney", total_amount)
.add("paymentTime", new Date())
.add("tradeNo", trade_no),
Cnd.where("userId", "=", SecurityUtil.getUserId()).and("payId", "=", out_trade_no));
//TODO 处理自身业务
result = "->:/mobile/memberCost/list/paySuccess";
} catch (AlipayApiException e) {
e.printStackTrace();
}
return result;
}
}
@@ -0,0 +1,24 @@
package com.budwk.app.base.payment.enums;
import com.budwk.app.base.annotation.DictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 退款状态枚举
*/
@Getter
@AllArgsConstructor
@DictEnum(key = "RefundStateEnum", name = "退款状态枚举")
public enum RefundStateEnum {
INITIATE_REFUND("发起退款"),
REFUNDING("退款中"),
REFUND_SUCCESS("退款成功"),
REFUND_FAILED("退款失败");
/**
* 避免和枚举类的name冲突
*/
public final String refundStateName;
}
@@ -0,0 +1,25 @@
package com.budwk.app.base.payment.enums;
import com.budwk.app.base.annotation.DictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 交易渠道枚举
*/
@Getter
@AllArgsConstructor
@DictEnum(key = "TradingChannelEnum", name = "交易渠道枚举")
public enum TradingChannelEnum {
QQ_PAY("QQ"),
WX_PAY("微信"),
JD_PAY("京东"),
ALI_PAY("支付宝"),
UNION_PAY("云闪付");
/**
* 避免和枚举类的name冲突
*/
public final String tradingChannelName;
}
@@ -0,0 +1,24 @@
package com.budwk.app.base.payment.enums;
import com.budwk.app.base.annotation.DictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付状态枚举
*/
@Getter
@AllArgsConstructor
@DictEnum(key = "TradingStateEnum", name = "支付状态枚举")
public enum TradingStateEnum {
WAITING_FOR_PAYMENT("等待支付"),
PAY_SUCCESS("支付成功"),
CANCEL_PAYMENT("取消支付");
/**
* 避免和枚举类的name冲突
*/
public final String tradingStateName;
}
@@ -0,0 +1,22 @@
package com.budwk.app.base.payment.enums;
import com.budwk.app.base.annotation.DictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 交易类型枚举
*/
@Getter
@AllArgsConstructor
@DictEnum(key = "TradingTypeEnum", name = "交易类型枚举")
public enum TradingTypeEnum {
PAYMENT("付款"),
REFUND("退款");
/**
* 避免和枚举类的name冲突
*/
public final String tradingTypeName;
}
@@ -0,0 +1,30 @@
package com.budwk.app.base.payment.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @version 1.0
* @Author zzr
* @nameWxPayResultCode
* @Date 2025/3/10 10:27
* @注释 微信支付返回码
*/
@Getter
@AllArgsConstructor
public enum WxPayResultCode {
SUCCESS(200, "操作成功"),
APPID_MCHID_NOT_MATCH(400, "商户ID和服务号或企业微信号不匹配"),
INVALID_REQUEST(400, "无效请求"),
MCH_NOT_EXISTS(400, "商户号不存在"),
PARAM_ERROR(400, "参数错误"),
SIGN_ERROR(401, "签名错误"),
NO_AUTH(403, "商户无权限"),
OUT_TRADE_NO_USED(403, "商户订单号重复"),
FREQUENCY_LIMITED(429, "频率超限"),
SYSTEM_ERROR(500, "系统错误");
final int code;
final String msg;
}
@@ -0,0 +1,61 @@
package com.budwk.app.base.payment.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.math.BigDecimal;
/**
* 退款记录表
*/
@Data
@Table
@Comment("退款记录表")
@EqualsAndHashCode(callSuper = true)
public class PaymentRefundRecord extends BaseModel {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("关联系统交易订单表Id")
@ColDefine(type = ColType.VARCHAR,width = 32)
private String paymentTransactionId;
@Column
@Comment("本次退款订单号")
@ColDefine(type = ColType.VARCHAR,width = 128)
private String outRefundTradeNo;
@Column
@Comment("第三方支付的退款单号")
@ColDefine(type = ColType.VARCHAR,width = 128)
private String refundNo;
@Column
@Comment("退款状态")
@ColDefine(type = ColType.VARCHAR,width = 30)
private String refundState;
@Column
@Comment("本次退款金额")
@ColDefine(type = ColType.INT)
private Integer refundAmount;
@Column
@Comment("原订单金额")
@ColDefine(type = ColType.INT)
private Integer total;
@Column
@Comment("交易返回数据")
@ColDefine(type = ColType.MYSQL_JSON)
private String resultJson;
}
@@ -0,0 +1,131 @@
package com.budwk.app.base.payment.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.math.BigDecimal;
/**
* 交易单表
*/
@Data
@Table
@Comment("交易订单表")
@EqualsAndHashCode(callSuper = true)
public class PaymentTransaction extends BaseModel {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("商户号")
@ColDefine(type = ColType.VARCHAR,width = 128)
private String mchId;
@Column
@Comment("订单号,传递给交易系统,交易系统订单号")
@ColDefine(type = ColType.VARCHAR,width = 128)
private String outTradeNo;
@Column
@Comment("交易系统交易号")
@ColDefine(type = ColType.VARCHAR,width = 128)
private String transactionId;
@Column
@Comment("交易渠道")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String tradingChannel;
@Column
@Comment("交易类型")
@ColDefine(type = ColType.VARCHAR,width = 30)
private String tradingType;
@Column
@Comment("交易状态")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String tradingState;
@Column
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR,width = 32)
private String userId;
@Column
@Comment("用户工号")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String loginName;
@Column
@Comment("用户姓名")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String userName;
@Column
@Comment("工会id")
@ColDefine(type = ColType.VARCHAR,width = 32)
private String unionId;
@Column
@Comment("用户工会")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String unionName;
@Column
@Comment("单位id")
@ColDefine(type = ColType.VARCHAR,width = 32)
private String unitId;
@Column
@Comment("用户单位")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String unitName;
@Column
@Comment("交易金额")
@ColDefine(type = ColType.INT)
private Integer tradingAmount;
@Column
@Comment("是否退款")
@ColDefine(type = ColType.BOOLEAN)
private Boolean isRefund;
@Column
@Comment("退款金额")
@ColDefine(type = ColType.INT)
private Integer refundAmount;
@Column
@Comment("订单描述")
@ColDefine(type = ColType.VARCHAR,width = 128)
private String description;
@Column
@Comment("附加详细描述")
@ColDefine(type = ColType.TEXT)
private String attach;
@Column
@Comment("微信支付用户openId")
@ColDefine(type = ColType.VARCHAR,width = 50)
private String openId;
@Column
@Comment("交易返回数据")
@ColDefine(type = ColType.MYSQL_JSON)
private String resultJson;
@Column
@Comment("交易名单的主键id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String memberPaymentInfoId;
}
@@ -0,0 +1,53 @@
package com.budwk.app.base.payment.wxpay.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @version 1.0
* @Author zzr
* @nameWxPayV3Bean
* @Date 2025/3/6 9:54
* @注释
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WxPayV3Bean {
/**
* 服务号
* appid: wxf12fa6175ec7c75f
* app_secret: f65bbb41d3de4a93c8c93d8e2fd50369
*/
private String appId;
private String keyPath;
private String publicKeyPath;
private String certPath;
private String certP12Path;
private String platformCertPath;
private String mchId;
private String apiKey;
private String apiKey3;
private String domain;
private String publicKeyId;
@Override
public String toString() {
return "WxPayV3Bean{" +
"appId='" + appId + '\'' +
", keyPath='" + keyPath + '\'' +
", publicKeyPath='" + publicKeyPath + '\'' +
", certPath='" + certPath + '\'' +
", certP12Path='" + certP12Path + '\'' +
", platformCertPath='" + platformCertPath + '\'' +
", mchId='" + mchId + '\'' +
", apiKey='" + apiKey + '\'' +
", apiKey3='" + apiKey3 + '\'' +
", domain='" + domain + '\'' +
", publicKeyId='" + publicKeyId + '\'' +
'}';
}
}
@@ -0,0 +1,44 @@
package com.budwk.app.base.payment.wxpay.controller;
import com.budwk.app.base.payment.wxpay.service.WxPayCommonService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @version 1.0
* @Author zzr
* @nameWxPayCommonController
* @Date 2025/3/10 19:28
* @注释 微信支付回调
*/
@Slf4j
@IocBean
@Ok("json:full")
@At("/platform/wxPay")
public class WxPayCommonController {
@Inject
private WxPayCommonService wxPayCommonService;
@At
@Ok("void")
@ApiOperation("微信支付-付款回调")
public void payNotify(HttpServletRequest request, HttpServletResponse response){
wxPayCommonService.payNotify(request, response);
}
@At
@Ok("void")
public void refundNotify(HttpServletRequest request, HttpServletResponse response){
wxPayCommonService.refundNotify(request, response);
}
}
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEKDCCAxCgAwIBAgIUONYpzvxVfV/RrWnLGPlL6/nb/AcwDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjUwMzA2MTQwNDAzWhcNMzAwMzA1MTQwNDAzWjCBgTETMBEGA1UEAwwK
MTY2NjkzMDk4MDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
DCTmrabmsYnlnLDlpKfkuInmgJ3np5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT
AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALlDML+FJEW+m2hXp/qUH1cU6xlwFllLU4LjFusJkxwyzzcZO4nY0mOi
3woz8xIJipvUWj2Y413WYwMkfcJcRgghupZkfYwdQtSyPfUu7xQBUm8TLWGH88Re
kyyVABEB5T+ne6r3LesXS4IuZknm5N/iaa4z9mbbscGqF0GLvMUqZCRNyTkyfK9r
Bkvs/vspi+Jcjx9I7aU++h/vwzsGB6Bf/uB3kLMb+lj/i3xY20sdrrW8JD2sqONQ
NcRIKmNXSp4zsNy+d6kIKWE3P5609ZTta1ogkeGBNdGXMRNNy6vLIudQc7zny/9H
S4LxGsIrsYmZNhGljRng56X+PG3w1CUCAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG
A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu
aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC
MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB
OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ5e8U
1pQ40gfRoZyO0s+QF16L8pAcVLVwHffo0mmJpIRB4bu8W5IJ9kKhksJ0CzZ0YEwB
Yv7/LTC1qpi7PE73nZ4Ytm9oZeH1Kk5KIlQS+2H3IwOpoV4wucMc34y6MVQJjC7E
u5OnglBPSAopwNP4p+Us9A3ah7Ks48XCw+LCEWpA/setJwo9C13Qq2AQm8qLpogK
pNBwm/oylxb8/kZK1lLPgcXd7geLDhl/Str+aOzBXyd7YSEMxS8zWiZhWozu6rCq
dg10QFKicPGDkDzNQwBz7cDXvlJXSWRDBK5r1li1YgX+QD5WJYPchATo5SlvF4Lr
5/V/dZzI7BJTinr1
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5QzC/hSRFvpto
V6f6lB9XFOsZcBZZS1OC4xbrCZMcMs83GTuJ2NJjot8KM/MSCYqb1Fo9mONd1mMD
JH3CXEYIIbqWZH2MHULUsj31Lu8UAVJvEy1hh/PEXpMslQARAeU/p3uq9y3rF0uC
LmZJ5uTf4mmuM/Zm27HBqhdBi7zFKmQkTck5MnyvawZL7P77KYviXI8fSO2lPvof
78M7BgegX/7gd5CzG/pY/4t8WNtLHa61vCQ9rKjjUDXESCpjV0qeM7DcvnepCClh
Nz+etPWU7WtaIJHhgTXRlzETTcuryyLnUHO858v/R0uC8RrCK7GJmTYRpY0Z4Oel
/jxt8NQlAgMBAAECggEBAKGQqTi9Ii741Ysftsze1J4bDhS3HaZt/LyO0vPS+8qB
vRiuFrBWuNRPu/en6nl2fLz9al2Bsfep+dtJvar80c5Rq+lwZdUFx7y6XTIv5iwq
IvvfW5pM+Bfu6qbcopwybP0gUMd+nTsCq8UyNd6JUFy3lbZodqCpQ9JDRdjRxqSi
oMonve36Q9kJyDs0mS2F0w+Y9oYFtpE9WvGMBthwT7VfzWuDa+YC/Gh0RhT2V9OG
uDC8kxmfaIbk0IITDVFWVMMhScXiqmJL1II9yQWsCHH73dZS+dDxwwH0ObRD3kOx
hHptxpRzhtuNxLjCOZDGhneyvUHIftpqf4wdOgQ8iUECgYEA6TDo36s3XDFIs8SH
lpVYrhie0nmaNET50CtOc5C2M1OotrkMUrQTMoa4F263UloJfqweW9AKZX11wPp2
8h7af2sZsGawjuwv7mMthZJoi0fc5J2+z6BnVGgFo7uZbcs1Y/+B0tNrDKIHVrqb
Lywmh18iKDXrMiMDMRp8mtiNyU0CgYEAy2Im0oGb1rmAh9gPvQIENmNEgRsyIU4x
WPAFb5KO7jnseO9KrC9uiRSO5JuwZu/QNGlga2w77Gu7n+Q4HfSU2KFlNhR6pKjb
mqXzPq1quryzbtWay520VsRKz3F/Tfp1FUpIuZvj8x7FrL8z8vjbLkbW4p+Z7HE4
heYdmk2KCjkCgYBC6xU48pJtc3swk2pxjSQoucoCDoFxFIY39eWKhnGy4wQcg5gE
t8Z2jnCP0ObszAfDgTMpF9eT7l/3ZCj0LGR5gkuZ8q4iJadPmsh6d1xm89VxCc+z
F43vi/ZqWTCbcGA+9RjVXnRu6KSAgAZ+kAXyfRt5PJflCGUnFznA2fo+VQKBgG6s
n7Gno/UKBv5lPdM9mKs8A5g6A2KKWWIrYtnfJ/ZNAPiiRsFCImVoEYYnjqvFTkhn
G3f5cvZpfbU5jKwIX0MvOJNgO4kE9lPGgIKX/d9WNlJW3KB21h7tPER8X6lAZJxM
SxztzvKKyUkC1zMd5q/dDqxN64X16qs6mNtR3I9JAoGBAJbqQtg59Szi2m4jZ9Xo
0cKvP7+PSKgysdWsemTkU0ji1rcMpGRTtcUGRWlawLjRSInQS+xNdL6GVU5mHZWe
gNwXIXEpape4IJta0B1PYAhNcIvOSh5CzcUnVTeo6q0CTSVQPdsivlrObLKZ4uGQ
eewDw82Ej6YoSgczjf99ug0x
-----END PRIVATE KEY-----
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEFDCCAvygAwIBAgIUKN05Gm0Q9n9mLPPltUh8qjKKegQwDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjUwMzA2MTQwNDAzWhcNMzAwMzA1MTQwNDAzWjBuMRgwFgYDVQQDDA9U
ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGEwJDTjERMA8GA1UEBwwIU2hlblpo
ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbMenF0GNlcUYErPpj
Aj+WOuzRFz+PJcrtgkI81S0WDh8flMtq1A2nwKARNyPXx+UPd9it657nH5G8E9kL
Ea5u5rENpn+JZc4cAIvZqazteSAUg2pfQH9ewW+/CxNOWarjXPNRYa0OjZWf+5F8
ZBOT/x8sJ2+jou9lxKNe1skha+jzmiQ8m3B2vPXqR87qgL/fyQjjpLvBGpvearh/
hxTMBEfGeB4F6Fu52Fz3/VCWe38ubxwj8EMcRSPp4xsl2BTaRVMMT525Hul2N49C
vXCSVsCNt3ND8UDffXnb19Zgpir4KqpECTbfqcT7YX+U34P1Av8gcDl6rRm+7Wpj
PY4RAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAUAvXCx55ZGmd3ATi28W4mirnUHQb2RAw
X5/0OC2SkuBdzxI5RbY4YAuugP6nQIAU8TXCANzno2oqZ6vmFZ4dBnJmLewVFMoM
qp18A4RWmnsGnmEIgMm6N1kB+RbM11oLCLdy2KK0SvmqDmTBFJyjyUGqEmRshnV/
QfKn4UUzJ4suDFAolGX4DyDIOtM0D+SCHwwxGI4yf2+nj4IB8XgmmOeschxoSvu9
T3E6BgQ271YVDwg+3r9CiFVI5JdyftvQagOEQmZJMPINEKgFn5LzrZJW7mH42+N+
i4RFz04wpGUsrSFDESh3JxYSNhq/HulpMLinntWxEBHFhpkjyyGDGg==
-----END CERTIFICATE-----
@@ -0,0 +1,71 @@
package com.budwk.app.base.payment.wxpay.service;
import com.budwk.app.base.payment.wxpay.vo.WxPayNeedParamVo;
import com.budwk.app.base.result.Result;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @version 1.0
* @Author zzr
* @nameWxPayCommonService
* @Date 2025/3/6 11:40
* @注释
*/
public interface WxPayCommonService {
/**
* 二维码统一下单,返回支付二维码链接
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
* @return
*/
Result nativePay(WxPayNeedParamVo paramVo);
/**
* JSAPI支付,返回支付参数
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
* JsAPI支付,需要传递openId,请自行获取
* @return
*/
Result jsApiPay(WxPayNeedParamVo paramVo);
/**
* WAP支付,返回支付参数
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
* @return
*/
Result wapPay(WxPayNeedParamVo paramVo);
/**
* 支付回调,验证是否支付成功
* @param request 请求
* @param response 响应
*/
void payNotify(HttpServletRequest request, HttpServletResponse response);
/**
* 退款
* @param transactionId 微信订单号
* @param outTradeNo 商户订单号
* @return
*/
Result refund(String transactionId, String outTradeNo);
/**
* 退款回调
* @param request 请求
* @param response 响应
*/
void refundNotify(HttpServletRequest request, HttpServletResponse response);
}
@@ -0,0 +1,612 @@
package com.budwk.app.base.payment.wxpay.service.impl;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.payment.enums.*;
import com.budwk.app.base.payment.model.PaymentRefundRecord;
import com.budwk.app.base.payment.model.PaymentTransaction;
import com.budwk.app.base.payment.wxpay.bean.WxPayV3Bean;
import com.budwk.app.base.payment.wxpay.service.WxPayCommonService;
import com.budwk.app.base.payment.wxpay.vo.WxPayNeedParamVo;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffmanage.memberCost.model.MemberPaymentInfo;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.AuthTypeEnum;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.model.v3.*;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @version 1.0
* @Author zzr
* @nameWxPayCommonServiceImpl
* @Date 2025/3/6 11:41
* @注释
*/
@Slf4j
@IocBean
public class WxPayCommonServiceImpl implements WxPayCommonService {
// 判断下单接口成功返回码
private final static int OK = 200;
// 1666930980 商户号
// ww3eda050a1b5ad96e 企业微信
// wxf12fa6175ec7c75f 微信服务号
// 微信V3必要的支付参数,后期优化
private final static WxPayV3Bean WX_PAY_V3_BEAN = new WxPayV3Bean(
"wxf12fa6175ec7c75f",
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem",
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem",
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem",
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem",
"src/main/java/com/budwk/app/base/payment/wxpay/pem/wechatpay.pem",
"1666930980",
"",
"4ebd020883285d698c44ec50939c0967",
"https://schoolhd.top/platform",
"");
// 自定义参数,内容是统一的,但会被多次调用,直接声明
private String serialNo;
private String platSerialNo;
private final String payNotifyUrl = WX_PAY_V3_BEAN.getDomain().concat("/wxPay/payNotify");
private final String refundNotifyUrl = WX_PAY_V3_BEAN.getDomain().concat("/wxPay/refundNotify");
@Inject
private Dao dao;
@Inject
private SysUserService sysUserService;
/**
* 微信扫描二维码支付
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
* @return
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public Result nativePay(WxPayNeedParamVo paramVo) {
try {
// 商户订单号,存储当前用户的订单
String outTradeNo = PayKit.generateStr();
String timeExpire = getNowTimeExpire();
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(WX_PAY_V3_BEAN.getAppId())
.setMchid(WX_PAY_V3_BEAN.getMchId())
// 描述信息
.setDescription(paramVo.getDescription())
.setOut_trade_no(outTradeNo)
.setTime_expire(timeExpire)
.setAttach(paramVo.getAttach())
.setNotify_url(payNotifyUrl)
// 订单金额
.setAmount(paramVo.getAmount());
log.info("二维码支付统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.NATIVE_PAY.toString(),
WX_PAY_V3_BEAN.getMchId(),
getSerialNumber(),
null,
WX_PAY_V3_BEAN.getKeyPath(),
JSONUtil.toJsonStr(unifiedOrderModel),
AuthTypeEnum.RSA.getCode()
);
log.info("统一下单响应 {}", response);
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
// 下单返回数据
String result = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(result);
if (response.getStatus() == OK) {
// 统一下单成功,向数据库插入订单记录
insertTradingOrder(paramVo, outTradeNo, result);
return Result.success("请在三分钟内完成支付,超时将自动取消订单").addData(result);
} else {
WxPayResultCode code = WxPayResultCode.valueOf(WxPayResultCode.class, jsonObject.getStr("code"));
return Result.error(code.getMsg());
}
} catch (Exception e) {
log.error("系统异常", e);
return Result.error(e.getMessage());
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Result jsApiPay(WxPayNeedParamVo paramVo) {
try {
Sys_user user = dao.fetch(Sys_user.class, SecurityUtil.getUserId());
if (StrUtil.isBlank(user.getOpenId())) {
return Result.error("未获取到当前用户的openid,请重新登录");
}
// 商户订单号,存储当前用户的订单
String outTradeNo = PayKit.generateStr();
// 订单关闭时间
String timeExpire = getNowTimeExpire();
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(WX_PAY_V3_BEAN.getAppId())
.setMchid(WX_PAY_V3_BEAN.getMchId())
.setDescription(paramVo.getDescription())
.setOut_trade_no(outTradeNo)
.setTime_expire(timeExpire)
.setAttach(paramVo.getAttach())
.setNotify_url(payNotifyUrl)
.setAmount(paramVo.getAmount())
.setPayer(new Payer().setOpenid(user.getOpenId()));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.JS_API_PAY.toString(),
WX_PAY_V3_BEAN.getMchId(),
getSerialNumber(),
null,
WX_PAY_V3_BEAN.getKeyPath(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
log.info("统一下单响应 {}", response);
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
if (response.getStatus() == OK && verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = WxPayKit.jsApiCreateSign(WX_PAY_V3_BEAN.getAppId(), prepayId, WX_PAY_V3_BEAN.getKeyPath());
log.info("唤起支付参数:{}", map);
// 唤起支付成功,向数据库插入订单参数
insertTradingOrder(paramVo, outTradeNo, body);
return Result.success().addData(JSONUtil.toJsonStr(map));
}
// return JSONUtil.toJsonStr(response);
return Result.error("唤起支付失败");
} catch (Exception e) {
log.error("系统异常", e);
return Result.error(e.getMessage());
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Result wapPay(WxPayNeedParamVo paramVo) {
try {
// 商户订单号,存储当前用户的订单
String outTradeNo = PayKit.generateStr();
// 订单关闭时间
String timeExpire = getNowTimeExpire();
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(WX_PAY_V3_BEAN.getAppId())
.setMchid(WX_PAY_V3_BEAN.getMchId())
.setDescription(paramVo.getDescription())
.setOut_trade_no(outTradeNo)
.setTime_expire(timeExpire)
.setAttach(paramVo.getAttach())
.setNotify_url(payNotifyUrl)
.setAmount(paramVo.getAmount())
.setScene_info(
new SceneInfo()
.setPayer_client_ip("").
setH5_info(new H5Info()
.setType("Wap")));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.H5_PAY.toString(),
WX_PAY_V3_BEAN.getMchId(),
getSerialNumber(),
null,
WX_PAY_V3_BEAN.getKeyPath(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
log.info("统一下单响应 {}", response);
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
if (response.getStatus() == OK && verifySignature) {
String body = response.getBody();
insertTradingOrder(paramVo, outTradeNo, body);
return Result.success(JSONUtil.toJsonStr(body));
}
// return JSONUtil.toJsonStr(response);
return Result.error();
} catch (Exception e) {
log.error("系统异常", e);
return Result.error(e.getMessage());
}
}
/**
* 支付回调
* @param request
* @param response
*/
@Override
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("支付通知密文 {}", result);
// String plainText = null;
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
// 微信公钥验证签名并解密
// if (ObjectUtil.equals(serialNo, WX_PAY_V3_BEAN.getPublicKeyId())) {
// plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
// WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
// }
log.info("支付通知明文 {}", plainText);
if (StrUtil.isNotEmpty(plainText)) {
// 支付结果
JSONObject jsonResult = JSONUtil.parseObj(plainText);
// 获取缴费记录,并修改缴费结果
PaymentTransaction transaction = sysUserService.dao().fetch(PaymentTransaction.class, Cnd.where("outTradeNo", "=", jsonResult.getStr("out_trade_no")));
transaction.setTradingState(TradingStateEnum.PAY_SUCCESS.name());
transaction.setTransactionId(jsonResult.getStr("transaction_id"));
sysUserService.dao().update(transaction);
sysUserService.dao().update(MemberPaymentInfo.class,
Chain.make("isPayment", true).add("paymentTime", DateUtil.date())
.add("paymentMoney", transaction.getTradingAmount()),
Cnd.where("id", "=", transaction.getMemberPaymentInfoId())
);
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
log.error("签名错误");
}
// 支付回调,返回给微信,自己也接收不到结果,这里的返回没用
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
log.error("系统异常", e);
}
}
/**
* 退款
* @param transactionId 微信订单号
* @param outTradeNo 商户订单号
* @return
*/
@Override
public Result refund(String transactionId, String outTradeNo) {
try {
// 查询商户订单
PaymentTransaction transaction = sysUserService.dao().fetch(PaymentTransaction.class, Cnd.where("outTradeNo", "=", outTradeNo));
Integer tradingAmount = transaction.getTradingAmount();
String outRefundNo = PayKit.generateStr();
log.info("商户退款单号: {}", outRefundNo);
// 没用
// List<RefundGoodsDetail> list = new ArrayList<>();
// RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail()
// .setMerchant_goods_id("123")
// .setGoods_name("测试")
// .setUnit_price(1)
// .setRefund_amount(1)
// .setRefund_quantity(1);
// list.add(refundGoodsDetail);
RefundModel refundModel = new RefundModel()
.setOut_refund_no(outRefundNo)
.setReason(transaction.getDescription() + "退款")
.setNotify_url(refundNotifyUrl)
.setAmount(new RefundAmount().setRefund(tradingAmount).setTotal(tradingAmount).setCurrency("CNY"));
// .setGoods_detail(list);
if (StrUtil.isNotEmpty(transactionId)) {
refundModel.setTransaction_id(transactionId);
}
if (StrUtil.isNotEmpty(outTradeNo)) {
refundModel.setOut_trade_no(outTradeNo);
}
log.info("退款参数 {}", JSONUtil.toJsonStr(refundModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.REFUND.toString(),
WX_PAY_V3_BEAN.getMchId(),
getSerialNumber(),
null,
WX_PAY_V3_BEAN.getKeyPath(),
JSONUtil.toJsonStr(refundModel)
);
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
// 微信支付公钥验证签名
// boolean verifySignature = WxPayKit.verifyPublicKeySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
log.info("退款响应 {}", response);
if (verifySignature) {
// 用户发起退款,存一个退款记录
PaymentRefundRecord record = new PaymentRefundRecord();
record.setId(R.UU32());
record.setPaymentTransactionId(transaction.getId());
record.setOutRefundTradeNo(outRefundNo);
record.setRefundNo(null);
record.setRefundState(RefundStateEnum.REFUNDING.name());
record.setRefundAmount(tradingAmount);
record.setTotal(tradingAmount);
record.setResultJson(response.getBody());
dao.insert(record);
dao.update(MemberPaymentInfo.class, Chain.make("isRefund", true)
.add("refundState", RefundStateEnum.REFUNDING.name()),
Cnd.where("id", "=", transaction.getMemberPaymentInfoId()));
// 返回退款下单结果
return Result.success();
}
return Result.error("退款失败");
} catch (Exception e) {
log.error("系统异常", e);
return Result.error(e.getMessage());
}
}
/**
* 退款回调
* @param request
* @param response
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void refundNotify(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("退款通知 timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("退款通知密文 {}", result);
// String plainText = null;
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
// 微信公钥验证签名并解密
// if (ObjectUtil.equals(serialNo, WX_PAY_V3_BEAN.getPublicKeyId())) {
// plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
// WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
// }
log.info("退款通知明文 {}", plainText);
if (StrUtil.isNotEmpty(plainText)) {
// 取出明文,修改退款的结果
JSONObject jsonObject = JSONUtil.parseObj(plainText);
// 查询出退款记录,存储关键信息并修改
PaymentRefundRecord record = dao.fetch(PaymentRefundRecord.class, Cnd.where("outRefundTradeNo", "=", jsonObject.getStr("out_refund_no")));
record.setRefundNo(jsonObject.getStr("refund_id"));
record.setRefundState(RefundStateEnum.REFUND_SUCCESS.name());
// 到此是退款成功了,还要去修改订单表的数据
PaymentTransaction transaction = dao.fetch(PaymentTransaction.class, record.getPaymentTransactionId());
dao.update(PaymentTransaction.class, Chain.make("isRefund", true)
.add("refundAmount", record.getRefundAmount()),
Cnd.where("id", "=", record.getPaymentTransactionId()));
dao.update(MemberPaymentInfo.class, Chain.make("isPayment", null).add("paymentMoney", null)
.add("refundState", RefundStateEnum.REFUND_SUCCESS.name()),
Cnd.where("id", "=", transaction.getMemberPaymentInfoId()));
dao.update(record);
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
}
// 没用
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
log.error("系统异常", e);
}
}
/**
* 公共方法,获取证书序列号
* @return
*/
private String getSerialNumber() {
if (StrUtil.isEmpty(serialNo)) {
// 获取证书序列号
X509Certificate certificate = PayKit.getCertificate(WX_PAY_V3_BEAN.getCertPath());
if (null != certificate) {
serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
// 提前两天检查证书是否有效
boolean isValid = PayKit.checkCertificateIsValid(certificate, WX_PAY_V3_BEAN.getMchId(), -2);
log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
}
// System.out.println("输出证书信息:\n" + certificate.toString());
// // 输出关键信息,截取部分并进行标记
// System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
// System.out.println("版本号:" + certificate.getVersion());
// System.out.println("签发者:" + certificate.getIssuerDN());
// System.out.println("有效起始日期:" + certificate.getNotBefore());
// System.out.println("有效终止日期:" + certificate.getNotAfter());
// System.out.println("主体名:" + certificate.getSubjectDN());
// System.out.println("签名算法:" + certificate.getSigAlgName());
// System.out.println("签名:" + certificate.getSignature().toString());
}
System.out.println("serialNo:" + serialNo);
return serialNo;
}
/**
* 公共方法,获取平台证书序列号
* @return
*/
private String getPlatSerialNumber() {
if (StrUtil.isEmpty(platSerialNo)) {
// 获取平台证书序列号
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(WX_PAY_V3_BEAN.getPlatformCertPath()));
platSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
System.out.println("platSerialNo:" + platSerialNo);
return platSerialNo;
}
/**
* 公共方法,保存平台证书
* @param associatedData
* @param nonce
* @param cipherText
* @param algorithm
* @param certPath
* @return
*/
private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
try {
String key3 = WX_PAY_V3_BEAN.getApiKey3();
String publicKey;
if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
} else {
AesUtil aesUtil = new AesUtil(WX_PAY_V3_BEAN.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 平台证书密文解密
// encrypt_certificate 中的 associated_data nonce ciphertext
publicKey = aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
);
}
if (StrUtil.isNotEmpty(publicKey)) {
// 保存证书
FileWriter writer = new FileWriter(certPath);
writer.write(publicKey);
// 获取平台证书序列号
X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
return certificate.getSerialNumber().toString(16).toUpperCase();
}
return "";
} catch (Exception e) {
log.error("保存平台证书异常", e);
return e.getMessage();
}
}
/**
* 公共方法,构建订单关闭时间,默认3分钟
* @return
*/
private String getNowTimeExpire() {
long currentTimeMillis = System.currentTimeMillis() + 1000 * 60 * 3;
// 将时间戳转换为 DateTime 对象
DateTime dateTime = DateUtil.date(currentTimeMillis);
return DateUtil.format(dateTime, "yyyy-MM-dd'T'HH:mm:ssXXX");
}
/**
* 公共方法,插入交易订单
* @param paramVo 交易必填参数Vo
* @param outTradeNo 商户交易号
* @param result 下单返回结构
*/
private void insertTradingOrder(WxPayNeedParamVo paramVo, String outTradeNo, String result){
View_user user = sysUserService.dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
PaymentTransaction transaction = new PaymentTransaction();
transaction.setId(R.UU32());
transaction.setMchId(WX_PAY_V3_BEAN.getMchId());
transaction.setOutTradeNo(outTradeNo);
transaction.setTransactionId(null);
transaction.setTradingChannel(TradingChannelEnum.WX_PAY.name());
transaction.setTradingType(TradingTypeEnum.PAYMENT.name());
transaction.setUserId(user.getId());
transaction.setLoginName(user.getLoginname());
transaction.setUserName(user.getUsername());
transaction.setUnitId(user.getUnitId());
transaction.setUnitName(user.getUnitName());
transaction.setUnionId(user.getUnionId());
transaction.setUnionName(user.getUnionName());
transaction.setTradingState(TradingStateEnum.WAITING_FOR_PAYMENT.name());
// 获取订单金额
transaction.setTradingAmount(paramVo.getAmount().getTotal());
transaction.setIsRefund(false);
transaction.setRefundAmount(null);
transaction.setDescription(paramVo.getDescription());
transaction.setAttach(paramVo.getAttach());
transaction.setOpenId(null);
transaction.setResultJson(result);
transaction.setMemberPaymentInfoId(paramVo.getMemberPaymentInfoId());
sysUserService.dao().insert(transaction);
}
}
@@ -0,0 +1,132 @@
package com.budwk.app.base.payment.wxpay.util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.constant.RedisConstant;
import lombok.extern.slf4j.Slf4j;
import org.nutz.http.Http;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.util.NutMap;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.util.Base64;
/**
* @version 1.0
* @Author zzr
* @nameWeChatUtils
* @Date 2025/3/10 14:13
* @注释
*/
@Slf4j
@IocBean
public class WeChatUtils {
private static final String ALGORITHM = "AES";
private static final String KEY = "4eXvXq1ZJmZj8z5z6yXvXq1ZJmZj8z5z";
@Inject
private RedisService redisService;
public static final String APPID = "wxf12fa6175ec7c75f";
public static final String SECRET = "f65bbb41d3de4a93c8c93d8e2fd50369";
// 获取code
public final static String CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
//获取网页access_token的接口地址
public final static String WEB_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";
//获取用户信息地址
public final static String USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
// 获取access_token的接口地址(GET 限2000(次/天)
public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
// 发送模板消息
public static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
/**
* 生成获取code的url
*
* @param redirectUrl
* @return
*/
public String getCodeUrl(String redirectUrl, String state) {
String requestUrl = null;
state = state == null ? "" : state;
try {
requestUrl = CODE_URL.replace("APPID", APPID).replace("REDIRECT_URI", URLEncoder.encode(redirectUrl, "UTF-8")).replace("STATE", state);
} catch (Exception e) {
e.printStackTrace();
}
return requestUrl;
}
/**
* 获取微信网页access_token 和 openid
*
* @param code
* @return
*/
public JSONObject getWebAccessTokenAndOpenid(String code) {
String requestUrl = WEB_ACCESS_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", SECRET).replace("CODE", code);
return JSONUtil.parseObj(HttpUtil.post(requestUrl, new JSONObject()));
}
/**
* 网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了
* <a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html">...</a>
*
* @param webAccessToken
* @param openid
* @return
*/
public JSONObject getUserInfo(String webAccessToken, String openid) {
String requestUrl = USERINFO_URL.replace("ACCESS_TOKEN", webAccessToken).replace("OPENID", openid);
return JSONUtil.parseObj(HttpUtil.post(requestUrl, new JSONObject()));
}
/**
* 获取access_token 先从Redis获取,如果没有再调微信获取并存Redis
*
* @return
*/
public String getAccessToken() {
String token = redisService.get(RedisConstant.REDIS_KEY_WX_TOKEN);
if (StrUtil.isNotBlank(token)) {
return token;
}
String accessTokenUrl = ACCESS_TOKEN_URL.formatted(APPID, SECRET);
String accessToken = Json.fromJson(NutMap.class, Http.get(accessTokenUrl).getContent()).getString("access_token");
redisService.setex(RedisConstant.REDIS_KEY_WX_TOKEN, 60 * 60 * 2, accessToken);
return accessToken;
}
public String encrypt(String data) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt(String encryptedData) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes);
}
}
@@ -0,0 +1,37 @@
package com.budwk.app.base.payment.wxpay.vo;
import com.ijpay.wxpay.model.v3.Amount;
import com.ijpay.wxpay.model.v3.Payer;
import lombok.Data;
/**
* @version 1.0
* @Author zzr
* @nameWxPayNeedParamVo
* @Date 2025/3/7 18:43
* @注释
*/
@Data
public class WxPayNeedParamVo {
/**
* 订单描述,会显示在支付界面的标题,最大长度127,必填
*/
private String description;
/**
* 附加数据,订单的详细描述信息,不必填
*/
private String attach;
/**
* 金额对象,对象中的 total 是金额,必填;
* 其余参数可不传递也可不填
*/
private Amount amount;
/**
* 会员支付信息ID,必填
*/
private String memberPaymentInfoId;
}
@@ -0,0 +1,10 @@
package com.budwk.app.base.result;
/**
* @author wizzer@qq.com
*/
public interface IResultCode {
int getCode();
String getMsg();
}
@@ -0,0 +1,117 @@
package com.budwk.app.base.result;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Strings;
import org.nutz.mvc.Mvcs;
import java.io.Serializable;
/**
* Created by wizzer on 2016/12/21.
*/
public class Result implements Serializable {
private static final long serialVersionUID = 1L;
private int code;
private String msg;
private Object data;
private long time;
public Result() {
this.time = System.currentTimeMillis();
}
public static Result NEW() {
return new Result();
}
public Result addCode(int code) {
this.code = code;
return this;
}
public Result addMsg(String msg) {
if (Strings.isBlank(msg) || Mvcs.getActionContext() == null || Mvcs.getActionContext().getRequest() == null || Mvcs.getMessage(Mvcs.getActionContext().getRequest(), msg) == null) {
this.msg = Strings.sNull(msg);
} else {
this.msg = Mvcs.getMessage(Mvcs.getActionContext().getRequest(), msg);
}
return this;
}
public Result addData(Object data) {
this.data = data;
return this;
}
public Result(int code, String msg, Object data) {
this.code = code;
if (Strings.isBlank(msg) || Mvcs.getActionContext() == null || Mvcs.getActionContext().getRequest() == null || Mvcs.getMessage(Mvcs.getActionContext().getRequest(), msg) == null) {
this.msg = Strings.sNull(msg);
} else {
this.msg = Mvcs.getMessage(Mvcs.getActionContext().getRequest(), msg);
}
this.data = data;
this.time = System.currentTimeMillis();
}
public static Result success(String content) {
return new Result(0, content, null);
}
public static Result success(String content, Object data) {
return new Result(0, content, data);
}
public static Result success(Object data) {
return new Result(0, "system.success", data);
}
public boolean isSuccess() {
return this.code == ResultCode.SUCCESS.code;
}
public static Result error(IResultCode resultCode) {
return new Result(resultCode.getCode(), resultCode.getMsg(), null);
}
public static Result error(int code, String content) {
return new Result(code, content, null);
}
public static Result error(String content) {
return new Result(1, content, null);
}
public static Result success() {
return new Result(0, "system.success", null);
}
public static Result error() {
return new Result(1, "system.error", null);
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public Object getData() {
return data;
}
public static Result condition(boolean flag) {
return flag ? success("system.error") : error("system.error");
}
public String toJsonString() {
return Json.toJson(this, JsonFormat.compact());
}
}
@@ -0,0 +1,42 @@
package com.budwk.app.base.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author wizzer@qq.com
*/
@Getter
@AllArgsConstructor
public enum ResultCode implements IResultCode {
SUCCESS(0, "操作成功"),
FAILURE(200400, "业务异常"),
NOT_FOUND(200404, "服务未找到"),
TOO_MANY_REQUESTS(200429, "请求次数过多"),
SERVER_ERROR(200500, "服务异常"),
IOC_ERROR(500000, "IOC对象加载异常"),
NULL_DATA_ERROR(500100, "数据不存在"),
HAVE_DATA_ERROR(500110, "数据已存在"),
PARAM_ERROR(500200, "参数错误"),
XSS_SQL_ERROR(500300, "传参被拦截"),
BLACKLIST_ERROR(500400, "IP黑名单"),
DAO_ERROR(500600, "DAO数据库查询错误"),
DEMO_ERROR(500700, "演示环境,限制操作"),
PARAM_VALID_ERROR(500800, "请求参数校验异常"),
USER_NOT_LOGIN(600098, "用户未登录"),
USER_NOT_ROLE(600099, "无此角色"),
USER_NOT_PERMISSION(600100, "无此权限"),
USER_NOT_FOUND(600101, "用户不存在"),
USER_DISABLED(600102, "用户被禁用"),
USER_LOCKED(600103, "用户已锁定"),
USER_NAME_ERROR(600104, "用户名错误"),
USER_PWD_ERROR(600105, "用户密码错误"),
USER_PWD_EXPIRED(600106, "用户密码过期"),
USER_VERIFY_ERROR(600107, "验证码错误"),
USER_LOGIN_FAIL(600108, "用户登录失败");
final int code;
final String msg;
}
@@ -0,0 +1,960 @@
package com.budwk.app.base.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.page.datatable.DataTableColumn;
import com.budwk.app.base.page.datatable.DataTableOrder;
import com.budwk.app.base.param.PageForm;
import org.nutz.dao.*;
import org.nutz.dao.entity.Entity;
import org.nutz.dao.entity.Record;
import org.nutz.dao.pager.Pager;
import org.nutz.dao.sql.Sql;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Map;
/**
* Created by wizzer on 2016/12/22.
*/
public interface BaseService<T> {
Dao dao();
/**
* 获取实体的Entity
*
* @return 实体的Entity
*/
Entity<T> getEntity();
/**
* 获取实体类型
*
* @return 实体类型
*/
Class<T> getEntityClass();
/**
* 统计符合条件的对象表条数
*
* @param cnd
* @return
*/
int count(Condition cnd);
/**
* 统计对象表条数
*
* @return
*/
int count();
/**
* 统计符合条件的记录条数
*
* @param tableName
* @param cnd
* @return
*/
int count(String tableName, Condition cnd);
/**
* 统计表记录条数
*
* @param tableName
* @return
*/
int count(String tableName);
/**
* 自定义SQL统计
*
* @param sql
* @return
*/
int count(Sql sql);
/**
* 通过数字型主键查询对象
*
* @param id
* @return
*/
T fetch(long id);
/**
* 自定义SQL查询VO对象
*
* @param sql
* @param clazz
* @param <C>
* @return 味大无需多盐
*/
<C> C fetchVO(Sql sql, Class<C> clazz);
/**
* 通过字符型主键查询对象
*
* @param id
* @return
*/
T fetch(String id);
/**
* 查询关联表
*
* @param obj 数据对象,可以是普通对象或集合,但不是类
* @param regex 为null查询全部,支持通配符 ^(a|b)$
* @return
*/
<T> T fetchLinks(T obj, String regex);
/**
* 查询关联表
*
* @param obj 数据对象,可以是普通对象或集合,但不是类
* @param regex 为null查询全部,支持通配符 ^(a|b)$
* @param cnd 关联字段的过滤(排序,条件语句,分页等)
* @return
*/
<T> T fetchLinks(T obj, String regex, Condition cnd);
/**
* 查出符合条件的第一条记录
*
* @param cnd 查询条件
* @return 实体, 如不存在则为null
*/
T fetch(Condition cnd);
/**
* 复合主键专用
*
* @param pks 键值
* @return 对象 T
*/
T fetchx(Object... pks);
/**
* 复合主键专用
*
* @param pks 键值
* @return 对象 T
*/
boolean exists(Object... pks);
/**
* 将一个对象插入到一个数据库
*
* @param obj 要被插入的对象
* 它可以是:
* 普通 POJO
* 集合
* 数组
* Map
* 注意:如果是集合,数组或者 Map,所有的对象必须类型相同,否则可能会出错
* @return 插入后的对象
*/
<T> T insert(T obj);
/**
* 将一个对象按FieldFilter过滤后,插入到一个数据源。
* <p>
* <code>dao.insert(pet, FieldFilter.create(Pet.class, FieldMatcher.create(false)));</code>
*
* @param obj 要被插入的对象
* @param filter 字段过滤器, 其中FieldMatcher.isIgnoreId生效
* @return 插入后的对象
* @see Dao#insert(Object)
*/
<T> T insert(T obj, FieldFilter filter);
/**
* 根据对象的主键(@Id/@Name/@Pk)先查询, 如果存在就更新, 不存在就插入
*
* @param obj 对象
* @return 原对象
*/
<T> T insertOrUpdate(T obj);
/**
* 根据对象的主键(@Id/@Name/@Pk)先查询, 如果存在就更新, 不存在就插入
*
* @param obj 对象
* @param insertFieldFilter 插入时的字段过滤, 可以是null
* @param updateFieldFilter 更新时的字段过滤,可以是null
* @return 原对象
*/
<T> T insertOrUpdate(T obj, FieldFilter insertFieldFilter, FieldFilter updateFieldFilter);
/**
* 自由的向一个数据表插入一条数据
*
* @param tableName 表名
* @param chain 数据名值链
*/
void insert(String tableName, Chain chain);
/**
* 快速插入一个对象,对象的 '@Prev' 以及 '@Next' 在这个函数里不起作用
*
* @param obj
* @return
*/
<T> T fastInsert(T obj);
/**
* 将对象插入数据库同时,也将符合一个正则表达式的所有关联字段关联的对象统统插入相应的数据库
* <p>
* 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述
*
* @param obj 数据对象
* @param regex 正则表达式,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被插入
* @return 数据对象本身
* @see org.nutz.dao.entity.annotation.One
* @see org.nutz.dao.entity.annotation.Many
* @see org.nutz.dao.entity.annotation.ManyMany
*/
<T> T insertWith(T obj, String regex);
/**
* 根据一个正则表达式,仅将对象所有的关联字段插入到数据库中,并不包括对象本身
*
* @param obj 数据对象
* @param regex 正则表达式,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被插入
* @return 数据对象本身
* @see org.nutz.dao.entity.annotation.One
* @see org.nutz.dao.entity.annotation.Many
* @see org.nutz.dao.entity.annotation.ManyMany
*/
<T> T insertLinks(T obj, String regex);
/**
* 将对象的一个或者多个,多对多的关联信息,插入数据表
*
* @param obj 对象
* @param regex 正则表达式,描述了那种多对多关联字段将被执行该操作
* @return 对象自身
* @see org.nutz.dao.entity.annotation.ManyMany
*/
<T> T insertRelation(T obj, String regex);
/**
* 更新数据
*
* @param obj
* @return
*/
int update(Object obj);
/**
* 更新数据忽略值为null的字段
*
* @param obj
* @return
*/
int updateIgnoreNull(Object obj);
/**
* 部分更新实体表
*
* @param chain
* @param cnd
* @return
*/
int update(Chain chain, Condition cnd);
/**
* 部分更新表
*
* @param tableName
* @param chain
* @param cnd
* @return
*/
int update(String tableName, Chain chain, Condition cnd);
/**
* 将对象更新的同时,也将符合一个正则表达式的所有关联字段关联的对象统统更新
* <p>
* 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述
*
* @param obj 数据对象
* @param regex 正则表达式,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被更新
* @return 数据对象本身
* @see org.nutz.dao.entity.annotation.One
* @see org.nutz.dao.entity.annotation.Many
* @see org.nutz.dao.entity.annotation.ManyMany
*/
<T> T updateWith(T obj, String regex);
/**
* 根据一个正则表达式,仅更新对象所有的关联字段,并不包括对象本身
*
* @param obj 数据对象
* @param regex 正则表达式,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被更新
* @return 数据对象本身
* @see org.nutz.dao.entity.annotation.One
* @see org.nutz.dao.entity.annotation.Many
* @see org.nutz.dao.entity.annotation.ManyMany
*/
<T> T updateLinks(T obj, String regex);
/**
* 多对多关联是通过一个中间表将两条数据表记录关联起来。
* <p>
* 而这个中间表可能还有其他的字段,比如描述关联的权重等
* <p>
* 这个操作可以让你一次更新某一个对象中多个多对多关联的数据
*
* @param classOfT 对象类型
* @param regex 正则表达式,描述了那种多对多关联字段将被执行该操作
* @param chain 针对中间关联表的名值链。
* @param cnd 针对中间关联表的 WHERE 条件
* @return 共有多少条数据被更新
* @see org.nutz.dao.entity.annotation.ManyMany
*/
int updateRelation(Class<?> classOfT, String regex, Chain chain, Condition cnd);
/**
* 基于版本的更新,版本不一样无法更新到数据
*
* @param obj 需要更新的对象, 必须有version属性
* @return 若更新成功, 大于0, 否则小于0
*/
int updateWithVersion(Object obj);
/**
* 基于版本的更新,版本不一样无法更新到数据
*
* @param obj 需要更新的对象, 必须有version属性
* @param filter 需要过滤的字段设置
* @return 若更新成功, 大于0, 否则小于0
*/
int updateWithVersion(Object obj, FieldFilter filter);
/**
* 乐观锁, 以特定字段的值作为限制条件,更新对象,并自增该字段.
* <p>
* 执行的sql如下:
* <p>
* <code>update t_user set age=30, city="广州", version=version+1 where name="wendal" and version=124;</code>
*
* @param obj 需要更新的对象, 必须带@Id/@Name/@Pk中的其中一种.
* @param fieldFilter 需要过滤的属性. 若设置了哪些字段不更新,那务必确保过滤掉fieldName的字段
* @param fieldName 参考字段的Java属性名.默认是"version",可以是任意数值字段
* @return 若更新成功, 返回值大于0, 否则小于等于0
*/
int updateAndIncrIfMatch(Object obj, FieldFilter fieldFilter, String fieldName);
/**
* 获取某个对象,最大的 ID 值,这个对象必须声明了 '@Id'
*
* @return
*/
int getMaxId();
/**
* 通过long主键删除数据
*
* @param id
* @return
*/
int delete(long id);
/**
* 通过int主键删除数据
*
* @param id
* @return
*/
int delete(int id);
/**
* 通过string主键删除数据
*
* @param id
* @return
*/
int delete(String id);
/**
* 批量删除
*
* @param ids
*/
void delete(Integer[] ids);
/**
* 批量删除
*
* @param ids
*/
void delete(Long[] ids);
/**
* 批量删除
*
* @param ids
*/
void delete(String[] ids);
/**
* 批量删除
*
* @param ids
*/
void delete(List<String> ids);
/**
* 清空表
*
* @return
*/
int clear();
/**
* 清空表
*
* @return
*/
int clear(String tableName);
/**
* 按条件清除一组数据
*
* @return
*/
int clear(Condition cnd);
/**
* 按条件清除一组数据
*
* @return
*/
int clear(String tableName, Condition cnd);
/**
* 伪删除
*
* @param id
* @return
*/
int vDelete(String id);
/**
* 批量伪删除
*
* @param ids
* @return
*/
int vDelete(String[] ids);
/**
* 批量伪删除
*
* @param ids
* @return
*/
int vDelete(List<String> ids);
/**
* 根据条件进行伪删除
*
* @param cnd
* @return
*/
int vDelete(Condition cnd);
/**
* 根据条件进行伪删除
*
* @param cnd
* @return
*/
int vDelete(String tableName, Condition cnd);
/**
* 通过LONG主键获取部分字段值
*
* @param fieldName
* @param id
* @return
*/
T getField(String fieldName, long id);
/**
* 通过INT主键获取部分字段值
*
* @param fieldName
* @param id
* @return
*/
T getField(String fieldName, int id);
/**
* 通过NAME主键获取部分字段值
*
* @param fieldName 支持通配符 ^(a|b)$
* @param name
* @return
*/
T getField(String fieldName, String name);
/**
* 通过条件获取部分字段值
*
* @param fieldName 支持通配符 ^(a|b)$
* @param cnd
* @return
*/
T getField(String fieldName, Condition cnd);
/**
* 查询获取部分字段
*
* @param fieldName 支持通配符 ^(a|b)$
* @param cnd
* @return
*/
List<T> query(String fieldName, Condition cnd);
/**
* 查询一组对象。你可以为这次查询设定条件
*
* @param cnd WHERE 条件。如果为 null,将获取全部数据,顺序为数据库原生顺序<br>
* 只有在调用这个函数的时候, cnd.limit 才会生效
* @return 对象列表
*/
List<T> query(Condition cnd);
/**
* 获取全部数据
*
* @return
*/
List<T> query();
/**
* @param cnd 查询条件
* @param linkName 关联字段,支持正则 ^(a|b)$
* @return
*/
List<T> query(Condition cnd, String linkName);
/**
* 获取表及关联表全部数据(支持子查询)
*
* @param cnd 查询条件
* @param linkName 关联字段,支持正则 ^(a|b)$
* @param linkCnd 关联条件
* @return
*/
List<T> query(Condition cnd, String linkName, Condition linkCnd);
/**
* 获取表及关联表全部数据
*
* @param linkName 关联字段,支持正则 ^(a|b)$
* @return
*/
List<T> query(String linkName);
/**
* 分页关联字段查询
*
* @param cnd 查询条件
* @param linkName 关联字段,支持正则 ^(a|b)$
* @param pager 分页对象
* @return
*/
List<T> query(Condition cnd, String linkName, Pager pager);
/**
* 分页关联字段查询(支持关联条件)
*
* @param cnd 查询条件
* @param linkName 关联字段,支持正则 ^(a|b)$
* @param linkCnd 关联条件
* @param pager 分页对象
* @return
*/
List<T> query(Condition cnd, String linkName, Condition linkCnd, Pager pager);
/**
* 分页查询
*
* @param cnd 查询条件
* @param pager 分页对象
* @return
*/
List<T> query(Condition cnd, Pager pager);
/**
* 查询获取NutMap对象
*
* @param keyColumnName 作为key的字段名
* @param valueColumnName 作为value的字段名
* @param cnd 查询条件
* @return
*/
NutMap query(String keyColumnName, String valueColumnName, Condition cnd);
/**
* 查询获取NutMap对象
*
* @param tableName 表名
* @param keyColumnName 作为key的字段名
* @param valueColumnName 作为value的字段名
* @param cnd 查询条件
* @return
*/
NutMap query(String tableName, String keyColumnName, String valueColumnName, Condition cnd);
/**
* 计算子节点TREEID
*
* @param tableName
* @param colName
* @param value
* @return
*/
String getSubPath(String tableName, String colName, String value);
/**
* 获取TREEID父级
*
* @param path
* @return
*/
String getParentPath(String path);
/**
* 执行一条自定义SQL
*
* @param sql
* @return
*/
Sql execute(Sql sql);
/**
* 自定义SQL返回Record记录集,Record是个MAP但不区分大小写
* 别返回Map对象,因为MySql和Oracle中字段名有大小写之分
*
* @param sql
* @return
*/
List<Record> list(Sql sql);
/**
* 自定义SQL返回NutMap记录集,区分大小写
*
* @param sql
* @return
*/
List<NutMap> listMap(Sql sql);
/**
* 自定义sql 返回自定义类型
*
* @param sql
* @return
*/
<C> List<C> listVO(Sql sql, Class<C> clazz);
/**
* 自定义分页sql 返回自定义类型
*
* @param sql
* @return
*/
<C> Pagination<C> listPageVO(PageForm pageForm, Sql sql, Class<C> clazz);
/**
* 自定义查询,并返回当前实体类对象
*
* @param sql
* @return
*/
List<T> listEntity(Sql sql);
/**
* 自定义sql获取map key-value
*
* @param sql
* @return
*/
Map getMap(Sql sql);
/**
* 自定义sql获取NutMap key-value
*
* @param sql
* @return
*/
NutMap getNutMap(Sql sql);
/**
* 分页查询
*
* @param pageNumber
* @param cnd
* @return
*/
Pagination listPage(Integer pageNumber, Condition cnd);
/**
* 分页查询
*
* @param pageNumber
* @param sql
* @return
*/
Pagination listPage(Integer pageNumber, Sql sql);
/**
* 分页查询(sql)
*
* @param pageNumber
* @param pageSize
* @param sql
* @return
*/
Pagination listPage(Integer pageNumber, int pageSize, Sql sql);
/**
* 分页查询
*
* @param pageNumber
* @param sql 查询语句
* @param countSql 统计语句
* @return
*/
Pagination listPage(Integer pageNumber, Sql sql, Sql countSql);
/**
* 分页查询
*
* @param pageNumber
* @param pageSize
* @param sql 查询语句
* @param countSql 统计语句
* @return
*/
Pagination listPage(Integer pageNumber, int pageSize, Sql sql, Sql countSql);
/**
* 分页查询
*
* @param pageNumber
* @param sql
* @return
*/
Pagination listPageMap(Integer pageNumber, Sql sql);
/**
* 分页查询(sql)
*
* @param pageNumber
* @param pageSize
* @param sql
* @return
*/
Pagination listPageMap(Integer pageNumber, int pageSize, Sql sql);
/**
* 分页查询
*
* @param pageNumber
* @param sql 查询语句
* @param countSql 统计语句
* @return
*/
Pagination listPageMap(Integer pageNumber, Sql sql, Sql countSql);
/**
* 分页查询
*
* @param pageNumber
* @param pageSize
* @param sql 查询语句
* @param countSql 统计语句
* @return
*/
Pagination listPageMap(Integer pageNumber, int pageSize, Sql sql, Sql countSql);
/**
* 分页查询
*
* @param pageNumber
* @param tableName
* @param cnd
* @return
*/
Pagination listPage(Integer pageNumber, String tableName, Condition cnd);
/**
* 分页查询(cnd)
*
* @param pageNumber
* @param pageSize
* @param cnd
* @return
*/
Pagination listPage(Integer pageNumber, int pageSize, Condition cnd);
/**
* 分页 只要你的class是实体类 就是标注了@Table 就可以不写service 直接用这个
*
* @param pageNumber
* @param pageSize
* @param clazz
* @param cnd
* @param <C>
* @return
*/
<C> Pagination<C> listPage(Integer pageNumber, int pageSize, Class<C> clazz, Condition cnd);
/**
* 分页查询,获取部分字段(cnd)
*
* @param pageNumber
* @param pageSize
* @param cnd
* @param fieldName 支持通配符 ^(a|b)$
* @return
*/
Pagination listPage(Integer pageNumber, int pageSize, Condition cnd, String fieldName);
/**
* 关联查询
*
* @param pageNumber
* @param pageSize
* @param cnd
* @param linkName 支持通配符 ^(a|b)$
* @return
*/
Pagination listPageLinks(Integer pageNumber, int pageSize, Condition cnd, String linkName);
/**
* 关联查询,带子查询条件
*
* @param pageNumber
* @param pageSize
* @param cnd
* @param linkName 支持通配符 ^(a|b)$
* @param subCnd 子查询条件
* @return
*/
Pagination listPageLinks(Integer pageNumber, int pageSize, Condition cnd, String linkName, Condition subCnd);
/**
* 分页查询(tabelName)
*
* @param pageNumber
* @param pageSize
* @param tableName
* @param cnd
* @return
*/
Pagination listPage(Integer pageNumber, int pageSize, String tableName, Condition cnd);
/**
* 分页查询并返回包含实体类内容的NutMap对象
*
* @param pageNumber
* @param cnd
* @return
*/
Pagination listPageMap(Integer pageNumber, Condition cnd);
/**
* 分页查询并返回包含实体类内容的NutMap对象
*
* @param pageNumber
* @param pageSize
* @param cnd
* @return
*/
Pagination listPageMap(Integer pageNumber, int pageSize, Condition cnd);
/**
* DataTable Page
*
* @param length 页大小
* @param start start
* @param draw draw
* @param orders 排序
* @param columns 字段
* @param cnd 查询条件
* @param linkName 关联查询 支持通配符 ^(a|b)$
* @return
*/
NutMap data(int length, int start, int draw, List<DataTableOrder> orders, List<DataTableColumn> columns, Cnd cnd, String linkName);
/**
* DataTable Page
*
* @param length 页大小
* @param start start
* @param draw draw
* @param orders 排序
* @param columns 字段
* @param cnd 查询条件
* @param linkName 关联查询 支持通配符 ^(a|b)$
* @param subCnd 关联查询条件
* @return
*/
NutMap data(int length, int start, int draw, List<DataTableOrder> orders, List<DataTableColumn> columns, Cnd cnd, String linkName, Cnd subCnd);
/**
* DataTable Page 自定义SQL
*
* @param length 页大小
* @param start start
* @param draw draw
* @param countSql 统计查询语句
* @param orderSql 结果查询语句
* @return
*/
NutMap data(int length, int start, int draw, Sql countSql, Sql orderSql);
/**
* DataTable Page 自定义SQL
*
* @param length 页大小
* @param start start
* @param draw draw
* @param countSql 统计查询语句
* @param orderSql 结果查询语句
* @param countOnly 统计查询语句是否只有count()
* @return
*/
NutMap data(int length, int start, int draw, Sql countSql, Sql orderSql, boolean countOnly);
/**
* DataTable Page
*
* @param length 页大小
* @param start start
* @param draw draw
* @param cnd 查询条件
* @param linkName 关联查询 支持通配符 ^(a|b)$
* @return
*/
NutMap data(int length, int start, int draw, Cnd cnd, String linkName);
/**
* 获取Map
* @param sql
* @return
*/
NutMap fetchMap(Sql sql);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
package com.budwk.app.base.sms;
import java.util.List;
public interface SmsService {
/**
* 单发
* @param loginName 工号
* @param content 内容
*/
void send(String loginName, String content);
/**
* 单发
* @param loginName 工号
* @param title 标题
* @param content 内容
*/
void send(String loginName, String title, String content);
/**
* 单发
* @param loginName 工号
* @param title 标题
* @param content 内容
* @param link 链接
*/
void send(String loginName, String title, String content, String link);
/**
* 群发 多人接收内容相同时使用该方法
* @param loginNames 工号
* @param title 标题
* @param content 内容
* @param link 链接
*/
void massSend(List<String> loginNames, String title, String content, String link);
}
@@ -0,0 +1,150 @@
package com.budwk.app.base.sms.impl.jshvc;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.budwk.app.base.sms.SmsService;
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.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName SmsJshvcServiceImpl
* @Author JyuHsin
* @Date 2025/11/26 19:32
* @Version 1.0
* @Description TODO
*/
@Slf4j
@IocBean
public class SmsJshvcServiceImpl implements SmsService {
private static final String APPID = "1430576717768122368";
private static final String APP_SECRET = "19A0ACB03FBQHL4R4XQD";
private static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken";
private static final String MSG_URL = "https://gateway.jshvc.edu.cn/mp/restful/v2/message/send";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:";
@Inject
private RedisService redisService;
@Override
public void send(String loginName, String content) {
// send(loginName, "智慧工会", content);
}
@Override
public void send(String loginName, String title, String content) {
send(loginName, "智慧工会", content, null);
}
@Override
public void send(String loginName, String title, String content, String link) {
// Map<String, String> paramMap = Map.of("userId", loginName);
// doSend(title, content, List.of(paramMap), link);
}
@Override
public void massSend(List<String> loginNames, String title, String content, String link) {
// List<Map<String, String>> receivers = loginNames.stream()
// .map(name -> Map.of("userId", name))
// .toList();
// doSend(title, content, receivers, link);
}
/**
* sign: 请求签名:accessToken + 第一个receivers 的userID )的32位小写的MD5加密值,其中如果相应部分没有则忽略
* msgType: 0: 普通消息(默认) 1: 必读消息 2: 验证码(为验证码时消息一定不入收件箱)
* expiredTime: 当msgType 为1必读消息 ,该字段为必填字段 格式:yyyy-MM-dd HH:mm:ss
* sendType: 1.只发送PC门户 2.只发送移动校园 3.邮件 4.短信 5.微信企业号 6.钉钉企业内部应用工作通知 7.微信服务号 8.welink
* wxSendType: 当发送类型为5时此字段才会生效:text(文本消息)、textcard(文本卡片)、nes(图文卡片,如果图文,则qyWeChatImgUrl必填)、button(按钮卡片详见示例),不传默认为text
* receiverType: 1:用户 2:用户组 3:部门,默认为1
*
* @param title
* @param content
* @param receivers
*/
private void doSend(String title, String content, List<Map<String, String>> receivers, String link) {
if (Lang.isEmpty(receivers)) {
log.error("send fail by receivers is null");
return;
}
// 获取token
String accessToken = buildAccessToken();
// 获取第一个接收人的userID
String firstUserId = receivers.get(0).get("userId");
// md5小写加密生成签名
String sign = DigestUtil.md5Hex(accessToken + firstUserId);
NutMap paramsMap = new NutMap();
paramsMap.put("sign", sign);
paramsMap.put("msgType", "0");
paramsMap.put("subject", title);
paramsMap.put("content", content);
paramsMap.put("sendType", "5");
paramsMap.put("receivers", receivers);
if (StrUtil.isBlank(link)) {
paramsMap.put("wxSendType", "text");
} else {
paramsMap.put("wxSendType", "textcard");
paramsMap.put("mobileUrl", link);
// 这是图文卡片,后面用到再对接吧
// paramsMap.put("qyWeChatImgUrl", "");
}
log.debug("send params: {}", Json.toJson(paramsMap));
HttpRequest request = HttpRequest.post(MSG_URL)
.header("appId", APPID)
.header("accessToken", accessToken)
.body(Json.toJson(paramsMap));
try {
HttpResponse response = request.execute();
log.info("send response body: {}", response.body());
NutMap bodyMap = Json.fromJson(NutMap.class, response.body());
if (bodyMap.getInt("status") == 200) {
log.info("send success code: {}, msg: {}", bodyMap.getInt("status"), bodyMap.getString("msg"));
} else {
log.error("send error code: {}, msg: {}", bodyMap.getInt("status"), bodyMap.getString("msg"));
}
} catch (Exception e) {
log.error("Failed to send: {}", e.getMessage(), e);
}
}
private String buildAccessToken() {
String token = redisService.get(REDIS_KEY_MSG_ACCESS_TOKEN);
if (StrUtil.isNotBlank(token)) {
return token;
}
HttpRequest request = HttpRequest.get(TOKEN_URL);
request.header("appId", APPID);
request.header("appSecret", APP_SECRET);
HttpResponse response = request.execute();
log.info("accessToken response: {}", Json.toJson(response.body()));
NutMap bodyMap = Json.fromJson(NutMap.class, response.body());
if (bodyMap.getInt("errcode") == 0) {
log.info("accessToken status: {}", bodyMap.getInt("errorcode"));
redisService.setex(REDIS_KEY_MSG_ACCESS_TOKEN, 60 * 120, bodyMap.getString("data"));
return bodyMap.getString("data");
} else {
log.error("accessToken status: {}", bodyMap.getInt("errorcode"));
}
return "";
}
}
@@ -0,0 +1,117 @@
package com.budwk.app.base.sqlCallback;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONObject;
import com.mysql.cj.MysqlType;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.nutz.dao.impl.jdbc.BlobValueAdaptor;
import org.nutz.dao.jdbc.Jdbcs;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.sql.SqlCallback;
import org.nutz.json.Json;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List;
public class FetchVoCallback implements SqlCallback {
protected Class<?> clazz;
public FetchVoCallback(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public Object invoke(Connection connection, ResultSet rs, Sql sql) throws SQLException {
if (null != rs && rs.next()) {
try {
Object obj = clazz.getDeclaredConstructor().newInstance();
String name = null;
ResultSetMetaData meta = rs.getMetaData();
int count = meta.getColumnCount();
for (int i = 1; i <= count; ++i) {
name = meta.getColumnLabel(i);
int columnType = meta.getColumnType(i);
String columnTypeName = meta.getColumnTypeName(i);
Field field = ReflectUtil.getField(clazz, name);
if(ObjectUtil.isNull(field)){
continue;
}
Object object = null;
if (columnType == 93 || columnType == 91) {
object = rs.getTimestamp(i);
} else if (columnType == 2004) {
object = (new BlobValueAdaptor(Jdbcs.getFilePool())).get(rs, name);
} else if (columnType == 2005) {
object = rs.getString(i);
} else if (columnType == -1) {
if (columnTypeName.equals(MysqlType.JSON.getName())) {
Class<?> type = field.getType();
if (type.isAssignableFrom(List.class)) {
System.out.println(rs.getString(i));
//List的泛型怎么获取
if (field.getGenericType() instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Type[] actualTypeArguments = pt.getActualTypeArguments();
for (Type typeArg : actualTypeArguments) {
System.out.println("List Field Generic Type: " + TypeUtils.toString(typeArg));
/*if(typeArg.getTypeName().equals(String.class.getName())){
if(ObjectUtil.isNotNull(rs.getString(i))){
object = Json.fromJsonAsList(String.class, rs.getString(i));
break;
}
} else if(typeArg.getTypeName().equals(JSONObject.class.getName())) {
if(ObjectUtil.isNotNull(rs.getObject(i))){
object = Json.fromJsonAsList(JSONObject.class, rs.getString(i));
break;
}
}*/
if(ObjectUtil.isNotNull(rs.getObject(i))){
object = Json.fromJsonAsList(Class.forName(typeArg.getTypeName()), rs.getString(i));
break;
}
//todo Map暂未实现
}
}else{
object = rs.getString(i);
}
}else if(type.isArray()){
object = Json.fromJsonAsArray(type, rs.getString(i));
}else{
object = Json.fromJson(type, rs.getString(i));
}
} else {
object = rs.getObject(i);
}
} else {
object = rs.getObject(i);
}
//生成clazz类型的实例
if(ObjectUtil.isNotNull(field)){
ReflectUtil.setFieldValue(obj, field, object);
}
}
return obj;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
}
@@ -0,0 +1,458 @@
//package com.budwk.app.base.sqlCallback;
//
//import cn.hutool.core.util.ObjectUtil;
//import cn.hutool.core.util.ReflectUtil;
//import cn.hutool.core.util.TypeUtil;
//import com.mysql.cj.MysqlType;
//import org.nutz.dao.DaoException;
//import org.nutz.dao.impl.jdbc.BlobValueAdaptor;
//import org.nutz.dao.jdbc.Jdbcs;
//import org.nutz.dao.pager.ResultSetLooping;
//import org.nutz.dao.sql.Sql;
//import org.nutz.dao.sql.SqlCallback;
//import org.nutz.dao.sql.SqlContext;
//import org.nutz.json.Json;
//
//import java.lang.reflect.Field;
//import java.lang.reflect.Type;
//import java.sql.*;
//import java.util.ArrayList;
//import java.util.Arrays;
//import java.util.List;
//
///**
// * VO 查询回调
// * 如果你的字段是json类型 那么也可以根据VO的类型来做映射
// * todo 暂时只支持List<String>
// * @author zhaoxinyu
// */
//public class QueryVoCallback implements SqlCallback {
//
// protected Class<?> clazz;
//
// public QueryVoCallback(Class<?> clazz) {
// this.clazz = clazz;
// }
//
// @Override
// public Object invoke(Connection connection, ResultSet rs, Sql sql) throws SQLException {
// final ResultSetMetaData meta = rs.getMetaData();
//
//
// ResultSetLooping ing = new ResultSetLooping() {
// protected boolean createObject(int index, ResultSet rs, SqlContext context, int rowCount) {
// try {
// Object obj = clazz.getDeclaredConstructor().newInstance();
// int count = meta.getColumnCount();
//
// for (int i = 1; i <= count; ++i) {
// String columnName = meta.getColumnLabel(i);
// int columnType = meta.getColumnType(i);
// String columnTypeName = meta.getColumnTypeName(i);
//
// // 根据列名找到对应的字段
// Field field = getField(clazz, columnName);
//
// if(ObjectUtil.isNull(field)){
// continue;
// }
//
// Object object = null;
//
// try {
// // 特殊类型处理
// if (columnType == Types.TIMESTAMP || columnType == Types.DATE) {
// object = rs.getTimestamp(i);
// // 如果是Date类型且值为Timestamp转换为Date
// if (object instanceof Timestamp && field.getType() == Date.class) {
// object = new Date(((Timestamp) object).getTime());
// }
// } else if (columnType == Types.BLOB) {
// object = (new BlobValueAdaptor(Jdbcs.getFilePool())).get(rs, columnName);
// } else if (columnType == Types.CLOB) {
// object = rs.getString(i);
// } else if (columnType == Types.LONGVARCHAR || columnType == Types.LONGNVARCHAR) {
// if ("JSON".equalsIgnoreCase(columnTypeName)) {
// object = parseJsonField(rs, i, field);
// } else {
// object = rs.getObject(i);
// }
// } else if (columnType == Types.ARRAY) {
// object = rs.getArray(i);
// } else {
// object = rs.getObject(i);
// }
//
// // 设置字段值
// if (ObjectUtil.isNotNull(object)) {
// ReflectUtil.setFieldValue(obj, field, object);
// }
//
// } catch (Exception e) {
// // 记录警告日志但不中断处理
// System.err.println("设置字段值失败: 字段=" + field.getName() + ", 列=" + columnName + ", 错误=" + e.getMessage());
// continue;
// }
//
//// if (columnType == 93 || columnType == 91) {
//// object = rs.getTimestamp(i);
//// } else if (columnType == 2004) {
//// object = (new BlobValueAdaptor(Jdbcs.getFilePool())).get(rs, name);
//// } else if (columnType == 2005) {
//// object = rs.getString(i);
//// } else if (columnType == -1) {
//// if (columnTypeName.equals(MysqlType.JSON.getName())) {
//// try{
//// Class<?> type = field.getType();
////
//// if (type.isAssignableFrom(List.class)) {
//// Type typeArgument = TypeUtil.getTypeArgument(field.getGenericType());
//// if(ObjectUtil.isNotNull(rs.getString(i))){
//// object = Json.fromJsonAsList(Class.forName(typeArgument.getTypeName()), rs.getString(i));
//// }
//// }else if(type.isArray()){
//// if(ObjectUtil.isNotNull(rs.getString(i))){
//// object = Json.fromJsonAsArray(type.getComponentType(), rs.getString(i));
//// }
//// }else{
//// object = Json.fromJson(type, rs.getString(i));
//// }
//// }catch (Exception e){
//// e.printStackTrace();
//// }
//// } else {
//// object = rs.getObject(i);
//// }
//// } else {
//// object = rs.getObject(i);
//// }
//
// //生成clazz类型的实例
// if(ObjectUtil.isNotNull(field)){
// ReflectUtil.setFieldValue(obj, field, object);
// }
// }
// list.add(obj);
// } catch (SQLException e) {
// throw new DaoException(e);
// } catch (Exception e){
// throw new RuntimeException(e);
// }
// return true;
// }
// };
// ing.doLoop(rs, sql.getContext());
// return ing.getList();
// }
//
// /**
// * 解析JSON字段
// */
// private Object parseJsonField(ResultSet rs, int columnIndex, Field field) {
// try {
// String jsonString = rs.getString(columnIndex);
// if (ObjectUtil.isNull(jsonString)) {
// return null;
// }
//
// Class<?> fieldType = field.getType();
//
// // List类型处理
// if (fieldType.isAssignableFrom(List.class)) {
// Type genericType = field.getGenericType();
// Type[] typeArguments = ((java.lang.reflect.ParameterizedType) genericType).getActualTypeArguments();
// if (typeArguments.length > 0) {
// Class<?> elementType = (Class<?>) typeArguments[0];
// return Json.fromJsonAsList(elementType, jsonString);
// }
// }
// // 数组类型处理
// else if (fieldType.isArray()) {
// Class<?> componentType = fieldType.getComponentType();
// return Json.fromJsonAsArray(componentType, jsonString);
// }
// // 普通对象处理
// else {
// return Json.fromJson(fieldType, jsonString);
// }
// } catch (Exception e) {
// System.err.println("JSON解析失败: 字段=" + field.getName() + ", 错误=" + e.getMessage());
// }
// return null;
// }
//
// /**
// * 多种方式获取字段支持不同命名规范
// */
// public static Field getField(Class<?> clazz, String columnName) {
// if (clazz == null || columnName == null || columnName.isEmpty()) {
// return null;
// }
//
// // 1. 直接匹配完全相同
// Field field = getFieldDirectly(clazz, columnName);
// if (field != null) {
// return field;
// }
//
// // 2. 驼峰命名匹配
// String camelName = underlineToCamel(columnName);
// field = getFieldDirectly(clazz, camelName);
// if (field != null) {
// return field;
// }
//
// // 3. 下划线命名匹配
// String underlineName = camelToUnderline(columnName);
// field = getFieldDirectly(clazz, underlineName);
// if (field != null) {
// return field;
// }
//
// // 4. 忽略大小写匹配
// field = getFieldIgnoreCase(clazz, columnName);
// if (field != null) {
// return field;
// }
//
// // 5. 驼峰转下划线后忽略大小写匹配
// field = getFieldIgnoreCase(clazz, underlineName);
// if (field != null) {
// return field;
// }
//
// // 6. 下划线转驼峰后忽略大小写匹配
// field = getFieldIgnoreCase(clazz, camelName);
// if (field != null) {
// return field;
// }
//
// return null;
// }
//
// /**
// * 直接获取字段完全匹配
// */
// private static Field getFieldDirectly(Class<?> clazz, String fieldName) {
// try {
// return clazz.getDeclaredField(fieldName);
// } catch (NoSuchFieldException e) {
// // 继续向上查找父类
// Class<?> superclass = clazz.getSuperclass();
// if (superclass != null && superclass != Object.class) {
// return getFieldDirectly(superclass, fieldName);
// }
// return null;
// }
// }
//
// /**
// * 忽略大小写获取字段
// */
// private static Field getFieldIgnoreCase(Class<?> clazz, String fieldName) {
// Field[] fields = getAllFields(clazz);
// for (Field field : fields) {
// if (field.getName().equalsIgnoreCase(fieldName)) {
// return field;
// }
// }
//
// // 再尝试驼峰和下划线转换后忽略大小写
// String camelName = underlineToCamel(fieldName);
// String underlineName = camelToUnderline(fieldName);
//
// for (Field field : fields) {
// if (field.getName().equalsIgnoreCase(camelName) ||
// field.getName().equalsIgnoreCase(underlineName)) {
// return field;
// }
// }
//
// return null;
// }
//
// /**
// * 获取类及其父类的所有字段
// */
// private static Field[] getAllFields(Class<?> clazz) {
// List<Field> fieldList = new ArrayList<>();
// Class<?> currentClass = clazz;
//
// while (currentClass != null && currentClass != Object.class) {
// fieldList.addAll(Arrays.asList(currentClass.getDeclaredFields()));
// currentClass = currentClass.getSuperclass();
// }
//
// return fieldList.toArray(new Field[0]);
// }
//
// /**
// * 下划线转驼峰
// */
// public static String underlineToCamel(String underlineStr) {
// if (underlineStr == null || !underlineStr.contains("_")) {
// return underlineStr;
// }
//
// StringBuilder result = new StringBuilder();
// String[] parts = underlineStr.toLowerCase().split("_");
//
// for (int i = 0; i < parts.length; i++) {
// if (i == 0) {
// result.append(parts[i]);
// } else {
// result.append(capitalize(parts[i]));
// }
// }
//
// return result.toString();
// }
//
// /**
// * 驼峰转下划线
// */
// public static String camelToUnderline(String camelStr) {
// if (camelStr == null || camelStr.isEmpty()) {
// return camelStr;
// }
//
// StringBuilder result = new StringBuilder();
// for (int i = 0; i < camelStr.length(); i++) {
// char c = camelStr.charAt(i);
// if (Character.isUpperCase(c)) {
// if (i > 0) {
// result.append("_");
// }
// result.append(Character.toLowerCase(c));
// } else {
// result.append(c);
// }
// }
// return result.toString();
// }
//
// /**
// * 首字母大写
// */
// private static String capitalize(String str) {
// if (str == null || str.isEmpty()) {
// return str;
// }
// return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
// }
//
//}
package com.budwk.app.base.sqlCallback;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import com.mysql.cj.MysqlType;
import org.nutz.dao.DaoException;
import org.nutz.dao.impl.jdbc.BlobValueAdaptor;
import org.nutz.dao.jdbc.Jdbcs;
import org.nutz.dao.pager.ResultSetLooping;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.sql.SqlCallback;
import org.nutz.dao.sql.SqlContext;
import org.nutz.json.Json;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List;
/**
* VO 查询回调
* 如果你的字段是json类型 那么也可以根据VO的类型来做映射
* todo 暂时只支持List<String>
* @author zhaoxinyu
*/
public class QueryVoCallback implements SqlCallback {
protected Class<?> clazz;
public QueryVoCallback(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public Object invoke(Connection connection, ResultSet rs, Sql sql) throws SQLException {
final ResultSetMetaData meta = rs.getMetaData();
ResultSetLooping ing = new ResultSetLooping() {
protected boolean createObject(int index, ResultSet rs, SqlContext context, int rowCount) {
try {
Object obj = clazz.getDeclaredConstructor().newInstance();
String name = null;
int count = meta.getColumnCount();
for (int i = 1; i <= count; ++i) {
name = meta.getColumnLabel(i);
int columnType = meta.getColumnType(i);
String columnTypeName = meta.getColumnTypeName(i);
Field field = ReflectUtil.getField(clazz, name);
if(ObjectUtil.isNull(field)){
continue;
}
Object object = null;
if (columnType == 93 || columnType == 91) {
object = rs.getTimestamp(i);
} else if (columnType == 2004) {
object = (new BlobValueAdaptor(Jdbcs.getFilePool())).get(rs, name);
} else if (columnType == 2005) {
object = rs.getString(i);
} else if (columnType == -1) {
if (columnTypeName.equals(MysqlType.JSON.getName())) {
try{
Class<?> type = field.getType();
if (type.isAssignableFrom(List.class)) {
Type typeArgument = TypeUtil.getTypeArgument(field.getGenericType());
if(ObjectUtil.isNotNull(rs.getString(i))){
object = Json.fromJsonAsList(Class.forName(typeArgument.getTypeName()), rs.getString(i));
}
}else if(type.isArray()){
if(ObjectUtil.isNotNull(rs.getString(i))){
object = Json.fromJsonAsArray(type.getComponentType(), rs.getString(i));
}
}else{
object = Json.fromJson(type, rs.getString(i));
}
}catch (Exception e){
e.printStackTrace();
}
} else {
object = rs.getObject(i);
}
} else {
object = rs.getObject(i);
}
//生成clazz类型的实例
if(ObjectUtil.isNotNull(field)){
ReflectUtil.setFieldValue(obj, field, object);
}
}
list.add(obj);
} catch (SQLException e) {
throw new DaoException(e);
} catch (Exception e){
throw new RuntimeException(e);
}
return true;
}
};
ing.doLoop(rs, sql.getContext());
return ing.getList();
}
}
@@ -0,0 +1,27 @@
package com.budwk.app.base.utils;
import org.nutz.ioc.Ioc;
/**
* @version 1.0
* @Author zzr
* @nameAppIocUtil
* @Date 2025/8/26 17:40
* @注释
*/
public class AppIocUtil {
private static Ioc ioc;
public static void setIoc(Ioc ioc) {
AppIocUtil.ioc = ioc;
}
public static Ioc get() {
if (ioc == null) {
throw new IllegalStateException("Ioc not initialized yet!");
}
return ioc;
}
}
@@ -0,0 +1,122 @@
package com.budwk.app.base.utils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class ClassScannerUtil implements ResourceLoaderAware {
private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
public static Set<Class> scan(String[] basePackages,
Class<? extends Annotation>... annotations) {
ClassScannerUtil cs = new ClassScannerUtil();
if (ArrayUtils.isNotEmpty(annotations)) {
for (Class anno : annotations) {
cs.addIncludeFilter(new AnnotationTypeFilter(anno));
}
}
Set<Class> classes = new HashSet<Class>();
for (String s : basePackages) {
classes.addAll(cs.doScan(s));
}
return classes;
}
public static Set<Class> scan(String basePackages, Class<? extends Annotation>... annotations) {
return ClassScannerUtil.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
}
public final ResourceLoader getResourceLoader() {
return this.resourcePatternResolver;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils
.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(
resourceLoader);
}
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(0, excludeFilter);
}
public void resetFilters(boolean useDefaultFilters) {
this.includeFilters.clear();
this.excludeFilters.clear();
}
public Set<Class> doScan(String basePackage) {
Set<Class> classes = new HashSet<Class>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ org.springframework.util.ClassUtils
.convertClassNameToResourcePath(SystemPropertyUtils
.resolvePlaceholders(basePackage))
+ "/**/*.class";
Resource[] resources = this.resourcePatternResolver
.getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0)
|| matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader
.getClassMetadata().getClassName()));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"I/O failure during classpath scanning", ex);
}
return classes;
}
protected boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,93 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议您在使用过程中需要注意以下几点
*
* 1.请不要删除和修改根目录下的LICENSE文件
* 2.请不要删除和修改Snowy源码头部的版权声明
* 3.本项目代码可免费商业使用商业使用请保留源码和相关描述文件的项目出处作者声明等
* 4.分发源码时候请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package com.budwk.app.base.utils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.URLUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
/**
* 文件下载工具类使用本类前对参数校验的异常使用CommonResponseUtil.renderError()方法进行渲染
*
* @author xuyuxiang
* @date 2020/8/5 21:45
*/
@Slf4j
public class CommonDownloadUtil {
/**
* 下载文件
*
* @param file 要下载的文件
* @param response 响应
* @author xuyuxiang
* @date 2020/8/5 21:46
*/
public static void download(File file, HttpServletResponse response) {
download(file.getName(), FileUtil.readBytes(file), response);
}
/**
* 下载文件
*
* @param fileName
* @param workbook
* @param response
*/
public static void download(String fileName, Workbook workbook, HttpServletResponse response) {
ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
try {
workbook.write(byteOs);
} catch (IOException e) {
e.printStackTrace();
log.error(">>> 文件下载异常:", e);
} finally {
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
IoUtil.close(byteOs);
}
download(fileName, byteOs.toByteArray(), response);
}
/**
* 下载文件
*
* @author xuyuxiang
* @date 2022/7/31 10:57
*/
public static void download(String fileName, byte[] fileBytes, HttpServletResponse response) {
try {
response.setHeader("Content-Disposition", "attachment;filename=" + URLUtil.encode(fileName));
response.addHeader("Content-Length", "" + fileBytes.length);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setContentType("application/octet-stream;charset=UTF-8");
IoUtil.write(response.getOutputStream(), true, fileBytes);
} catch (IOException e) {
log.error(">>> 文件下载异常:", e);
}
}
}
@@ -0,0 +1,159 @@
package com.budwk.app.base.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.param.Condition;
import com.budwk.app.base.param.ConditionGroup;
import com.budwk.app.sys.param.SysDataUserUpdateParam;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.util.cri.SqlExpressionGroup;
/**
* 条件组处理工具类
* 用于处理复杂的条件组和条件生成对应的SQL条件
*/
@Slf4j
public class ConditionGroupUtil {
/**
* 处理条件组将条件组转换为Cnd条件
*
* @param cnd 原始条件
* @param group 条件组
* @return 处理后的条件
*/
public static Cnd applyConditionGroup(Cnd cnd, ConditionGroup group) {
if (group == null) {
return cnd;
}
SqlExpressionGroup expGroup = new SqlExpressionGroup();
// 处理条件列表
if (group.getConditions() != null && !group.getConditions().isEmpty()) {
for (Condition condition : group.getConditions()) {
expGroup = applyCondition(expGroup, condition, group.getLogic());
}
}
// 处理嵌套条件组
if (group.getGroups() != null && !group.getGroups().isEmpty()) {
for (ConditionGroup nestedGroup : group.getGroups()) {
// 创建子条件
Cnd subCnd = Cnd.NEW();
subCnd = applyConditionGroup(subCnd, nestedGroup);
// 将子条件的表达式组添加到当前表达式组
if ("OR".equalsIgnoreCase(group.getLogic())) {
expGroup.or(subCnd.where());
} else {
expGroup.and(subCnd.where());
}
}
}
// 将表达式组添加到主条件
cnd.and(expGroup);
return cnd;
}
/**
* 处理单个条件
*
* @param expGroup 表达式组
* @param condition 条件
* @param logic 逻辑类型 (AND/OR)
* @return 处理后的表达式组
*/
public static SqlExpressionGroup applyCondition(SqlExpressionGroup expGroup, Condition condition, String logic) {
String field = condition.getField();
String operator = condition.getOperator();
Object value = condition.getValue();
// 根据操作符处理条件
switch (operator.toUpperCase()) {
case "=":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "=", value);
} else {
expGroup.and(field, "=", value);
}
break;
case "!=":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "!=", value);
} else {
expGroup.and(field, "!=", value);
}
break;
case ">":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, ">", value);
} else {
expGroup.and(field, ">", value);
}
break;
case "<":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "<", value);
} else {
expGroup.and(field, "<", value);
}
break;
case ">=":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, ">=", value);
} else {
expGroup.and(field, ">=", value);
}
break;
case "<=":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "<=", value);
} else {
expGroup.and(field, "<=", value);
}
break;
case "LIKE":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "LIKE", "%" + value + "%");
} else {
expGroup.and(field, "LIKE", "%" + value + "%");
}
break;
case "IN":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "IN", ObjectUtil.isNotNull(value) ? value.toString().split(",") : new String[0]);
} else {
expGroup.and(field, "IN", ObjectUtil.isNotNull(value) ? value.toString().split(",") : new String[0]);
}
break;
case "NOT IN":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "NOT IN", ObjectUtil.isNotNull(value) ? value.toString().split(",") : new String[0]);
} else {
expGroup.and(field, "NOT IN", ObjectUtil.isNotNull(value) ? value.toString().split(",") : new String[0]);
}
break;
case "IS NULL":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "IS", null);
} else {
expGroup.and(field, "IS", null);
}
break;
case "IS NOT NULL":
if ("OR".equalsIgnoreCase(logic)) {
expGroup.or(field, "IS NOT", null);
} else {
expGroup.and(field, "IS NOT", null);
}
break;
default:
log.warn("不支持的操作符: {}", operator);
}
return expGroup;
}
}
@@ -0,0 +1,147 @@
package com.budwk.app.base.utils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.nutz.lang.Times;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Created by wizzer on 2016/6/24.
*/
public class DateUtil {
private static final Locale DEFAULT_LOCALE = Locale.CHINA;
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 获取当前时间(HH:mm:ss)
*
* @return
*/
public static String getDate() {
return DateFormatUtils.format(new Date(), "yyyy-MM-dd", DEFAULT_LOCALE);
}
/**
* 获取当前时间(HH:mm:ss)
*
* @return
*/
public static String getTime() {
return DateFormatUtils.format(new Date(), "HH:mm:ss", DEFAULT_LOCALE);
}
/**
* 获取当前时间(yyyy-MM-dd HH:mm:ss)
*
* @return
*/
public static String getDateTime() {
return DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss", DEFAULT_LOCALE);
}
/**
* 转换日期格式(yyyy-MM-dd HH:mm:ss)
*
* @param date
* @return
*/
public static String formatDateTime(Date date) {
if (date == null) return "";
return DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss", DEFAULT_LOCALE);
}
/**
* 转换日期格式(yyyy-MM-dd HH:mm:ss)
*
* @param date
* @param f
* @return
*/
public static String format(Date date, String f) {
if (date == null) return "";
return DateFormatUtils.format(date, f, DEFAULT_LOCALE);
}
/**
* 时间戳日期
*
* @param time
* @return
*/
public static String getDate(long time) {
return DateFormatUtils.format(new Date(time * 1000), "yyyy-MM-dd HH:mm:ss", DEFAULT_LOCALE);
}
/**
* 时间戳日期
*
* @param time
* @param f
* @return
*/
public static String getDate(long time, String f) {
return DateFormatUtils.format(new Date(time * 1000), f, DEFAULT_LOCALE);
}
/**
* 通过字符串时间获取时间戳 nutzwk5.0改为long
*
* @param date
* @return
*/
public static long getTime(String date) {
try {
return Times.parse(sdf, date).getTime() / 1000;
} catch (ParseException e) {
return 0;
}
}
/**
* 通过字符串时间获取时间戳 nutzwk5.0改为long
*
* @param date
* @return
*/
public static long getTime(SimpleDateFormat sdf, String date) {
try {
return Times.parse(sdf, date).getTime() / 1000;
} catch (ParseException e) {
return 0;
}
}
/**
* 计算两个时间差
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
}
public static long formatDate(String data) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(data).getTime();
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
}
@@ -0,0 +1,41 @@
package com.budwk.app.base.utils;
/**
* @ClassName DistanceUtils
* @Author JyuHsin
* @Date 2025/10/30 16:22
* @Version 1.0
* @Description TODO
*/
public class DistanceUtil {
private static final double EARTH_RADIUS = 6371000; // 地球半径单位
/**
* 计算两个经纬度之间的距离单位
* @param lat1 纬度1单位
* @param lng1 经度1单位
* @param lat2 纬度2单位
* @param lng2 经度2单位
* @return 距离
*/
public static double getDistance(double lat1, double lng1, double lat2, double lng2) {
// 将角度转为弧度
double lat1Rad = Math.toRadians(lat1);
double lng1Rad = Math.toRadians(lng1);
double lat2Rad = Math.toRadians(lat2);
double lng2Rad = Math.toRadians(lng2);
// 纬度和经度的差值弧度
double deltaLat = lat2Rad - lat1Rad;
double deltaLng = lng2Rad - lng1Rad;
// Haversine 公式
double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)
+ Math.cos(lat1Rad) * Math.cos(lat2Rad)
* Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c; // 返回米
}
}
@@ -0,0 +1,449 @@
package com.budwk.app.base.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.HashBasedTable;
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;
public class DynamicFormFieldParserUtil {
@Data
public static class FieldInfo {
private String key; // key
private String title; // 标题
private String type; // 字段类型 text,number,date,select,checkbox,radio,textarea,file,tableForm
private Object value; // 字段值 原始值处理selectcheckboxradiotableForm 等负责情况
private String info; // 字段说明
private Boolean required; // 是否必填
private String displayValue; // 用于显示的值label 基本类型使用该字段
private JSONObject props; //属性
private String parentId = ""; // 父级字段id
private Map<String, Object> tableData; // 存储tableForm类型的columns和rows
}
/**
* 解析表单规则和数据
*
* @param config 表单规则JSON对象
* @param formDataJson 表单数据JSON字符串
* @return 字段信息Map
*/
public static Map<String, FieldInfo> parseFormFields(JSONObject config, String formDataJson) {
Map<String, FieldInfo> fieldInfoMap = new HashMap<>();
// 解析表单数据
// 用户数据
JSONObject formData = JSONUtil.parseObj(formDataJson);
//表单配置
JSONArray rules = JSONUtil.parseArray(config.getStr("rule"));
Map<String, Map<String, String>> optionsMap = new HashMap<>();
Map<String, List<TreeNode>> treeDataMap = new HashMap<>();
extractFieldInfoAndOptions(rules, fieldInfoMap, optionsMap, treeDataMap, null);
// 解析表单值
for (Map.Entry<String, Object> entry : formData.entrySet()) {
String field = entry.getKey();
Object value = entry.getValue();
if (fieldInfoMap.containsKey(field)) {
FieldInfo fieldInfo = fieldInfoMap.get(field);
fieldInfo.setKey(field);
fieldInfo.setValue(value);
// 处理值及显示值
processFieldValue(fieldInfo, value, optionsMap, treeDataMap, fieldInfoMap);
}
}
return fieldInfoMap;
}
private static void processFieldValue(FieldInfo fieldInfo, Object value,
Map<String, Map<String, String>> optionsMap,
Map<String, List<TreeNode>> treeDataMap,
Map<String, FieldInfo> fieldInfoMap) { // 添加 fieldInfoMap 参数
// 处理 tableForm 类型
if (fieldInfo.getType().equals("tableForm") && value instanceof List) {
List<Map<String, Object>> rows = (List<Map<String, Object>>) value;
// 初始化 tableData
Map<String, Object> tableData = new HashMap<>();
List<FieldInfo> columns = new ArrayList<>();
// 获取并存储列信息
if (fieldInfoMap.containsKey(fieldInfo.getKey())) {
// 假设 columns 是一个包含 FieldInfo 的结构
for (Map.Entry<String, FieldInfo> entry : fieldInfoMap.entrySet()) {
if (!entry.getKey().equals(fieldInfo.getKey()) && StrUtil.isNotBlank(entry.getValue().getParentId()) && entry.getValue().getParentId().equals(fieldInfo.getKey())) {
FieldInfo columnFieldInfo = entry.getValue();
columnFieldInfo.setKey(entry.getKey());
//找老爹要table的label属性
FieldInfo parentFieldInfo = fieldInfoMap.get(columnFieldInfo.getParentId());
if(null != parentFieldInfo){
columnFieldInfo.setTitle(parentFieldInfo.getTitle());
}
columns.add(columnFieldInfo);
}
}
}
tableData.put("columns", columns); // 存储列信息
tableData.put("rows", rows); // 存储行数据
fieldInfo.setTableData(tableData); // 设置 tableData
StringBuilder displayValueBuilder = new StringBuilder();
for (Map<String, Object> row : rows) {
StringBuilder rowDisplay = new StringBuilder();
for (Map.Entry<String, Object> entry : row.entrySet()) {
String columnField = entry.getKey();
Object columnValue = entry.getValue();
// 获取字段信息
FieldInfo columnFieldInfo = fieldInfoMap.get(columnField);
if (columnFieldInfo != null) {
// 设置原始值
columnFieldInfo.setValue(columnValue);
// 处理显示值
processColumnValue(columnFieldInfo, columnValue, optionsMap, treeDataMap);
// 追加到行显示值
rowDisplay.append(columnFieldInfo.getTitle())
.append(": ")
.append(columnFieldInfo.getDisplayValue())
.append(", ");
}
}
// 去除末尾的逗号和空格
if (rowDisplay.length() > 0) {
rowDisplay.setLength(rowDisplay.length() - 2);
}
displayValueBuilder.append("行: [").append(rowDisplay.toString()).append("]").append("\n");
}
fieldInfo.setDisplayValue(displayValueBuilder.toString());
}
// 处理复选框和选择器
else if (value instanceof List && optionsMap.containsKey(fieldInfo.getKey())) {
List<String> values = (List<String>) value;
Map<String, String> options = optionsMap.get(fieldInfo.getKey());
List<String> labels = values.stream()
.map(v -> options.getOrDefault(v, v))
.collect(Collectors.toList());
fieldInfo.setDisplayValue(String.join(", ", labels));
}
// 处理单选框
else if (value instanceof String && optionsMap.containsKey(fieldInfo.getTitle())) {
Map<String, String> options = optionsMap.get(fieldInfo.getTitle());
String valueStr = (String) value;
fieldInfo.setDisplayValue(options.getOrDefault(valueStr, valueStr));
}
// 处理树形控件
else if (value instanceof List && treeDataMap.containsKey(fieldInfo.getTitle())) {
List<String> selectedIds = (List<String>) value;
List<TreeNode> treeNodes = treeDataMap.get(fieldInfo.getTitle());
Map<String, String> idToLabelMap = new HashMap<>();
buildIdToLabelMap(treeNodes, idToLabelMap);
List<String> labels = selectedIds.stream()
.map(id -> idToLabelMap.getOrDefault(id, id))
.collect(Collectors.toList());
fieldInfo.setDisplayValue(String.join(", ", labels));
}
// 处理普通文本字段
else {
fieldInfo.setDisplayValue(value != null ? value.toString() : "");
}
}
/**
* 树节点结构
*/
@Data
private static class TreeNode {
private String id;
private String label;
private List<TreeNode> children;
}
/**
* 递归构建ID到Label的映射
*/
private static void buildIdToLabelMap(List<TreeNode> nodes, Map<String, String> idToLabelMap) {
if (nodes == null) return;
for (TreeNode node : nodes) {
if (node.getId() != null && node.getLabel() != null) {
idToLabelMap.put(node.getId(), node.getLabel());
}
buildIdToLabelMap(node.getChildren(), idToLabelMap);
}
}
/**
* 递归解析树节点
*/
private static List<TreeNode> parseTreeData(JSONArray treeData) {
List<TreeNode> result = new ArrayList<>();
for (int i = 0; i < treeData.size(); i++) {
JSONObject item = treeData.getJSONObject(i);
TreeNode node = new TreeNode();
node.setId(item.getStr("id"));
node.setLabel(item.getStr("label"));
if (item.containsKey("children") && item.get("children") instanceof JSONArray) {
node.setChildren(parseTreeData(item.getJSONArray("children")));
}
result.add(node);
}
return result;
}
/**
* 递归提取字段信息和选项映射
*/
private static void extractFieldInfoAndOptions(JSONArray items, Map<String, FieldInfo> fieldInfoMap,
Map<String, Map<String, String>> optionsMap,
Map<String, List<TreeNode>> treeDataMap, String parentId) {
for (int i = 0; i < items.size(); i++) {
JSONObject item = items.getJSONObject(i);
// 如果有子元素递归处理
if (item.containsKey("children")) {
extractFieldInfoAndOptions(item.getJSONArray("children"), fieldInfoMap, optionsMap, treeDataMap, item.getStr("field"));
}
// 如果是表单递归处理
if(item.containsKey("props") && item.getJSONObject("props").containsKey("columns") && !item.getJSONObject("props").getJSONArray("columns").isEmpty()){
extractFieldInfoAndOptions(item.getJSONObject("props").getJSONArray("columns"), fieldInfoMap, optionsMap, treeDataMap, item.getStr("field"));
}
// 提取字段信息
if (item.containsKey("field") && item.containsKey("title")) {
String field = item.getStr("field");
String type = item.getStr("type");
FieldInfo fieldInfo = new FieldInfo();
fieldInfo.setTitle(item.getStr("title"));
fieldInfo.setType(type);
fieldInfo.setInfo(item.getStr("info"));
fieldInfo.setParentId(parentId);
fieldInfo.setProps(item.getJSONObject("props"));
// 处理必填字段
Object required = item.get("$required");
if (required != null) {
fieldInfo.setRequired(required instanceof Boolean ? (Boolean) required : true);
}
fieldInfoMap.put(field, fieldInfo);
// 处理表格类型
// if ("tableForm".equals(type) && item.containsKey("props")) {
// JSONObject props = item.getJSONObject("props");
// if (props.containsKey("columns") && props.get("columns") instanceof JSONArray) {
// JSONArray columns = props.getJSONArray("columns");
// for (int j = 0; j < columns.size(); j++) {
// JSONObject column = columns.getJSONObject(j);
// if (column.containsKey("rule") && column.get("rule") instanceof JSONArray) {
// extractFieldInfoAndOptions(column.getJSONArray("rule"), fieldInfoMap, optionsMap, treeDataMap, field);
// }
// }
// }
// }
// 处理带选项的字段如checkboxradioselect等
if (item.containsKey("options") && item.get("options") instanceof JSONArray) {
JSONArray options = item.getJSONArray("options");
Map<String, String> valueToLabelMap = new HashMap<>();
for (int j = 0; j < options.size(); j++) {
JSONObject option = options.getJSONObject(j);
if (option.containsKey("value") && option.containsKey("label")) {
String value = option.getStr("value");
String label = option.getStr("label");
valueToLabelMap.put(value, label);
}
}
if (!valueToLabelMap.isEmpty()) {
optionsMap.put(field, valueToLabelMap);
}
}
// 处理树形控件
if ("tree".equals(type) && item.containsKey("props")) {
JSONObject props = item.getJSONObject("props");
if (props.containsKey("data") && props.get("data") instanceof JSONArray) {
List<TreeNode> treeNodes = parseTreeData(props.getJSONArray("data"));
treeDataMap.put(field, treeNodes);
}
}
}
}
}
/**
* 从formConfig JSONObject中提取所有表单字段的详细信息
*
* @param formConfigJson formConfig JSONObject对象
* @return 字段信息列表每个字段包含其所有属性
* @throws Exception 解析异常
*/
public static List<Map<String, Object>> extractFormFields(JSONObject formConfigJson) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// 从JSONObject获取rule字符串
String ruleStr = formConfigJson.getStr("rule");
// 解析rule
JsonNode ruleNode = mapper.readTree(ruleStr);
// 提取字段信息
List<Map<String, Object>> fieldsList = new ArrayList<>();
traverseAndCollectFields(ruleNode, fieldsList);
return fieldsList;
}
/**
* 递归遍历表单规则结构收集所有的字段信息
*
* @param node 当前节点
* @param fieldsList 收集字段的列表
*/
private static void traverseAndCollectFields(JsonNode node, List<Map<String, Object>> fieldsList) {
if (node.isArray()) {
// 处理数组节点
for (JsonNode item : node) {
traverseAndCollectFields(item, fieldsList);
}
} else if (node.isObject()) {
// 检查是否是表单字段具有field和title属性的节点
if (node.has("field") && node.has("title")) {
Map<String, Object> fieldInfo = new HashMap<>();
// 遍历当前节点的所有属性并保存
node.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode value = entry.getValue();
if (value.isTextual()) {
fieldInfo.put(key, value.asText());
} else if (value.isBoolean()) {
fieldInfo.put(key, value.asBoolean());
} else if (value.isInt()) {
fieldInfo.put(key, value.asInt());
} else if (value.isObject() || value.isArray()) {
// 对于复杂对象转换为字符串
fieldInfo.put(key, value.toString());
}
});
fieldsList.add(fieldInfo);
}
// 递归处理子节点
if (node.has("children")) {
traverseAndCollectFields(node.get("children"), fieldsList);
}
}
}
private static void processColumnValue(FieldInfo columnFieldInfo, Object columnValue,
Map<String, Map<String, String>> optionsMap,
Map<String, List<TreeNode>> treeDataMap) {
// 处理复选框和选择器
if (columnValue instanceof List && optionsMap.containsKey(columnFieldInfo.getTitle())) {
List<String> values = (List<String>) columnValue;
Map<String, String> options = optionsMap.get(columnFieldInfo.getTitle());
List<String> labels = values.stream()
.map(v -> options.getOrDefault(v, v))
.collect(Collectors.toList());
columnFieldInfo.setDisplayValue(String.join(", ", labels));
}
// 处理单选框
else if (columnValue instanceof String && optionsMap.containsKey(columnFieldInfo.getKey())) {
Map<String, String> options = optionsMap.get(columnFieldInfo.getTitle());
String valueStr = (String) columnValue;
columnFieldInfo.setDisplayValue(options.getOrDefault(valueStr, valueStr));
}
// 处理树形控件
else if (columnValue instanceof List && treeDataMap.containsKey(columnFieldInfo.getTitle())) {
List<String> selectedIds = (List<String>) columnValue;
List<TreeNode> treeNodes = treeDataMap.get(columnFieldInfo.getTitle());
Map<String, String> idToLabelMap = new HashMap<>();
buildIdToLabelMap(treeNodes, idToLabelMap);
List<String> labels = selectedIds.stream()
.map(id -> idToLabelMap.getOrDefault(id, id))
.collect(Collectors.toList());
columnFieldInfo.setDisplayValue(String.join(", ", labels));
}
// 处理普通文本字段
else {
columnFieldInfo.setDisplayValue(columnValue != null ? columnValue.toString() : "");
}
}
/**
* 使用示例
*/
public static void main(String[] args) {
// 表单数据
String formDataJson = """
{"F7m3m8744w1nb7c": [{"F93zm8744xc3bcc": "1", "Fifam87455auboc": "2", "Fp5mm8745476blc": "1"}, {"F93zm8744xc3bcc": "2", "Fifam87455auboc": "3", "Fp5mm8745476blc": "2"}], "Fc4lm87457knbuc": "2", "Fkdtm87456owbrc": "1"}
""";
// 规则数据 (这里应该是完整的规则JSON字符串)
String ruleJson = """
{"rule": "[{\\"type\\":\\"tableForm\\",\\"field\\":\\"F7m3m8744w1nb7c\\",\\"title\\":\\"表格表单\\",\\"info\\":\\"\\",\\"props\\":{\\"columns\\":[{\\"label\\":\\"自定义名称\\",\\"required\\":false,\\"style\\":{\\"width\\":\\"auto\\"},\\"rule\\":[{\\"type\\":\\"input\\",\\"field\\":\\"F93zm8744xc3bcc\\",\\"title\\":\\"输入框\\",\\"info\\":\\"\\",\\"$required\\":false,\\"_fc_id\\":\\"id_Fklbm8744xc3bdc\\",\\"name\\":\\"ref_F2g2m8744xc3bec\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"}]},{\\"label\\":\\"自定义名称\\",\\"required\\":false,\\"style\\":{\\"width\\":\\"auto\\"},\\"rule\\":[{\\"type\\":\\"input\\",\\"field\\":\\"Fp5mm8745476blc\\",\\"title\\":\\"输入框\\",\\"info\\":\\"\\",\\"$required\\":false,\\"_fc_id\\":\\"id_Fkb2m8745476bmc\\",\\"name\\":\\"ref_F0ntm8745476bnc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"}]},{\\"label\\":\\"自定义名称\\",\\"required\\":false,\\"style\\":{\\"width\\":\\"auto\\"},\\"rule\\":[{\\"type\\":\\"radio\\",\\"field\\":\\"Fifam87455auboc\\",\\"title\\":\\"单选框\\",\\"info\\":\\"\\",\\"effect\\":{\\"fetch\\":\\"\\"},\\"$required\\":false,\\"options\\":[{\\"label\\":\\"选项01\\",\\"value\\":\\"1\\"},{\\"label\\":\\"选项02\\",\\"value\\":\\"2\\"},{\\"label\\":\\"选项03\\",\\"value\\":\\"3\\"}],\\"_fc_id\\":\\"id_Fl2im87455aubpc\\",\\"name\\":\\"ref_Fz4em87455aubqc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"radio\\"}]}]},\\"_fc_id\\":\\"id_F7eym8744w1nb8c\\",\\"name\\":\\"ref_Fj8jm8744w1nb9c\\",\\"$required\\":false,\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"tableForm\\"},{\\"type\\":\\"input\\",\\"field\\":\\"Fkdtm87456owbrc\\",\\"title\\":\\"输入框\\",\\"info\\":\\"\\",\\"$required\\":true,\\"_fc_id\\":\\"id_F1hjm87456owbsc\\",\\"name\\":\\"ref_Fsj4m87456owbtc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"},{\\"type\\":\\"input\\",\\"field\\":\\"Fc4lm87457knbuc\\",\\"title\\":\\"输入框\\",\\"info\\":\\"\\",\\"$required\\":true,\\"_fc_id\\":\\"id_F592m87457knbvc\\",\\"name\\":\\"ref_Fxu3m87457knbwc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"}]", "options": "{\\"form\\":{\\"inline\\":false,\\"hideRequiredAsterisk\\":false,\\"labelPosition\\":\\"right\\",\\"size\\":\\"default\\",\\"labelWidth\\":\\"125px\\"},\\"language\\":{},\\"resetBtn\\":{\\"show\\":false,\\"innerText\\":\\"重置\\"},\\"submitBtn\\":{\\"show\\":false,\\"innerText\\":\\"提交\\"}}"}
""";
JSONObject config = JSONUtil.parseObj(ruleJson);
// 解析字段信息
Map<String, FieldInfo> fieldInfoMap = parseFormFields(config, formDataJson);
// 打印结果
for (Map.Entry<String, FieldInfo> entry : fieldInfoMap.entrySet()) {
System.out.println("字段: " + entry.getKey());
System.out.println("标题: " + entry.getValue().getTitle());
System.out.println("类型: " + entry.getValue().getType());
System.out.println("原始值: " + entry.getValue().getValue());
System.out.println("显示值: " + entry.getValue().getDisplayValue());
System.out.println("表格数据: " + entry.getValue().getTableData());
System.out.println("---");
}
}
}
@@ -0,0 +1,321 @@
package com.budwk.app.base.utils;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class DynamicFormFieldParserUtil2 {
@Data
public static class FieldInfo {
private String title; // 标题
private String type; // 字段类型
private Object value; // 字段值
private String info; // 字段说明
private Boolean required; // 是否必填
private String displayValue; // 用于显示的值label
}
/**
* 解析表单规则和数据
* @param config 表单规则JSON对象
* @param formDataJson 表单数据JSON字符串
* @return 字段信息Map
*/
public static Map<String, FieldInfo> parseFormFields(JSONObject config, String formDataJson) {
Map<String, FieldInfo> fieldInfoMap = new HashMap<>();
// 解析表单数据
JSONObject formData = JSONUtil.parseObj(formDataJson);
// 解析规则
JSONArray rules = JSONUtil.parseArray(config.getStr("rule"));
// 保存字段选项映射value -> label
Map<String, Map<String, String>> optionsMap = new HashMap<>();
// 保存树形数据
Map<String, List<TreeNode>> treeDataMap = new HashMap<>();
// 先提取字段信息和选项映射
extractFieldInfoAndOptions(rules, fieldInfoMap, optionsMap, treeDataMap);
// 添加表单值并处理显示值
for (Map.Entry<String, Object> entry : formData.entrySet()) {
String field = entry.getKey();
Object value = entry.getValue();
if (fieldInfoMap.containsKey(field)) {
FieldInfo fieldInfo = fieldInfoMap.get(field);
fieldInfo.setValue(value);
// 处理checkboxselect等选项类控件
if (value instanceof List && optionsMap.containsKey(field)) {
List<String> values = (List<String>) value;
Map<String, String> options = optionsMap.get(field);
List<String> labels = values.stream()
.map(v -> options.getOrDefault(v, v))
.collect(Collectors.toList());
fieldInfo.setDisplayValue(String.join(", ", labels));
}
// 处理单选
else if (value instanceof String && optionsMap.containsKey(field)) {
Map<String, String> options = optionsMap.get(field);
String valueStr = (String) value;
fieldInfo.setDisplayValue(options.getOrDefault(valueStr, valueStr));
}
// 处理树形控件
else if (value instanceof List && treeDataMap.containsKey(field)) {
List<String> selectedIds = (List<String>) value;
List<TreeNode> treeNodes = treeDataMap.get(field);
// 构建ID到Label的映射
Map<String, String> idToLabelMap = new HashMap<>();
buildIdToLabelMap(treeNodes, idToLabelMap);
// 将ID转换为对应的Label
List<String> labels = selectedIds.stream()
.map(id -> idToLabelMap.getOrDefault(id, id))
.collect(Collectors.toList());
fieldInfo.setDisplayValue(String.join(", ", labels));
} else {
// 对于普通文本字段显示值与值相同
fieldInfo.setDisplayValue(value != null ? value.toString() : "");
}
}
}
return fieldInfoMap;
}
/**
* 树节点结构
*/
@Data
private static class TreeNode {
private String id;
private String label;
private List<TreeNode> children;
}
/**
* 递归构建ID到Label的映射
*/
private static void buildIdToLabelMap(List<TreeNode> nodes, Map<String, String> idToLabelMap) {
if (nodes == null) return;
for (TreeNode node : nodes) {
if (node.getId() != null && node.getLabel() != null) {
idToLabelMap.put(node.getId(), node.getLabel());
}
buildIdToLabelMap(node.getChildren(), idToLabelMap);
}
}
/**
* 递归解析树节点
*/
private static List<TreeNode> parseTreeData(JSONArray treeData) {
List<TreeNode> result = new ArrayList<>();
for (int i = 0; i < treeData.size(); i++) {
JSONObject item = treeData.getJSONObject(i);
TreeNode node = new TreeNode();
node.setId(item.getStr("id"));
node.setLabel(item.getStr("label"));
if (item.containsKey("children") && item.get("children") instanceof JSONArray) {
node.setChildren(parseTreeData(item.getJSONArray("children")));
}
result.add(node);
}
return result;
}
/**
* 递归提取字段信息和选项映射
*/
private static void extractFieldInfoAndOptions(JSONArray items, Map<String, FieldInfo> fieldInfoMap,
Map<String, Map<String, String>> optionsMap,
Map<String, List<TreeNode>> treeDataMap) {
for (int i = 0; i < items.size(); i++) {
JSONObject item = items.getJSONObject(i);
// 如果有子元素递归处理
if (item.containsKey("children")) {
extractFieldInfoAndOptions(item.getJSONArray("children"), fieldInfoMap, optionsMap, treeDataMap);
}
// 提取字段信息
if (item.containsKey("field") && item.containsKey("title")) {
String field = item.getStr("field");
String type = item.getStr("type");
FieldInfo fieldInfo = new FieldInfo();
fieldInfo.setTitle(item.getStr("title"));
fieldInfo.setType(type);
fieldInfo.setInfo(item.getStr("info"));
// 处理必填字段
Object required = item.get("$required");
if (required != null) {
if (required instanceof Boolean) {
fieldInfo.setRequired((Boolean) required);
} else {
fieldInfo.setRequired(true); // 如果$required存在但不是布尔值视为必填
}
}
fieldInfoMap.put(field, fieldInfo);
// 处理带选项的字段如checkboxradioselect等
if (item.containsKey("options") && item.get("options") instanceof JSONArray) {
JSONArray options = item.getJSONArray("options");
Map<String, String> valueToLabelMap = new HashMap<>();
for (int j = 0; j < options.size(); j++) {
JSONObject option = options.getJSONObject(j);
if (option.containsKey("value") && option.containsKey("label")) {
String value = option.getStr("value");
String label = option.getStr("label");
valueToLabelMap.put(value, label);
}
}
if (!valueToLabelMap.isEmpty()) {
optionsMap.put(field, valueToLabelMap);
}
}
// 处理树形控件
if ("tree".equals(type) && item.containsKey("props")) {
JSONObject props = item.getJSONObject("props");
if (props.containsKey("data") && props.get("data") instanceof JSONArray) {
List<TreeNode> treeNodes = parseTreeData(props.getJSONArray("data"));
treeDataMap.put(field, treeNodes);
}
}
}
}
}
/**
* 从formConfig JSONObject中提取所有表单字段的详细信息
*
* @param formConfigJson formConfig JSONObject对象
* @return 字段信息列表每个字段包含其所有属性
* @throws Exception 解析异常
*/
public static List<Map<String, Object>> extractFormFields(JSONObject formConfigJson) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// 从JSONObject获取rule字符串
String ruleStr = formConfigJson.getStr("rule");
// 解析rule
JsonNode ruleNode = mapper.readTree(ruleStr);
// 提取字段信息
List<Map<String, Object>> fieldsList = new ArrayList<>();
traverseAndCollectFields(ruleNode, fieldsList);
return fieldsList;
}
/**
* 递归遍历表单规则结构收集所有的字段信息
*
* @param node 当前节点
* @param fieldsList 收集字段的列表
*/
private static void traverseAndCollectFields(JsonNode node, List<Map<String, Object>> fieldsList) {
if (node.isArray()) {
// 处理数组节点
for (JsonNode item : node) {
traverseAndCollectFields(item, fieldsList);
}
} else if (node.isObject()) {
// 检查是否是表单字段具有field和title属性的节点
if (node.has("field") && node.has("title")) {
Map<String, Object> fieldInfo = new HashMap<>();
// 遍历当前节点的所有属性并保存
node.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode value = entry.getValue();
if (value.isTextual()) {
fieldInfo.put(key, value.asText());
} else if (value.isBoolean()) {
fieldInfo.put(key, value.asBoolean());
} else if (value.isInt()) {
fieldInfo.put(key, value.asInt());
} else if (value.isObject() || value.isArray()) {
// 对于复杂对象转换为字符串
fieldInfo.put(key, value.toString());
}
});
fieldsList.add(fieldInfo);
}
// 递归处理子节点
if (node.has("children")) {
traverseAndCollectFields(node.get("children"), fieldsList);
}
}
}
/**
* 使用示例
*/
public static void main(String[] args) {
// 表单数据
String formDataJson = """
{
"F0qpm83seg72c8c": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"],
"F9epm83rypzsazc": ["1", "2"],
"Fctxm83ryd5catc": "1",
"Fdqgm83scr4ebgc": "11",
"Frhum83scsvabjc": "1"
}
""";
// 规则数据 (这里应该是完整的规则JSON字符串)
String ruleJson = """
{
"rule": "[{\\"type\\":\\"input\\",\\"field\\":\\"Fctxm83ryd5catc\\",\\"title\\":\\"孩子身份证\\",\\"info\\":\\"\\",\\"$required\\":false,\\"_fc_id\\":\\"id_F6d7m83ryd5cauc\\",\\"name\\":\\"ref_Filom83ryd5davc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"},{\\"type\\":\\"checkbox\\",\\"field\\":\\"F9epm83rypzsazc\\",\\"title\\":\\"衣服尺码\\",\\"info\\":\\"\\",\\"effect\\":{\\"fetch\\":\\"\\"},\\"$required\\":true,\\"options\\":[{\\"label\\":\\"M\\",\\"value\\":\\"1\\"},{\\"label\\":\\"L\\",\\"value\\":\\"2\\"},{\\"label\\":\\"XL\\",\\"value\\":\\"3\\"}],\\"_fc_id\\":\\"id_F6zwm83rypzsb0c\\",\\"name\\":\\"ref_F0x3m83rypzsb1c\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"checkbox\\"},{\\"type\\":\\"fcRow\\",\\"children\\":[{\\"type\\":\\"col\\",\\"props\\":{\\"span\\":12},\\"children\\":[{\\"type\\":\\"input\\",\\"field\\":\\"Fdqgm83scr4ebgc\\",\\"title\\":\\"输入框\\",\\"info\\":\\"\\",\\"$required\\":false,\\"_fc_id\\":\\"id_F97bm83scr4ebhc\\",\\"name\\":\\"ref_Fbljm83scr4ebic\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"}],\\"_fc_id\\":\\"id_Fip5m83sclr8bcc\\",\\"name\\":\\"ref_Fhxvm83sclr8bdc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"col\\"},{\\"type\\":\\"col\\",\\"props\\":{\\"span\\":12},\\"children\\":[{\\"type\\":\\"input\\",\\"field\\":\\"Frhum83scsvabjc\\",\\"title\\":\\"输入框\\",\\"info\\":\\"\\",\\"$required\\":false,\\"_fc_id\\":\\"id_Fdaqm83scsvabkc\\",\\"name\\":\\"ref_Fagdm83scsvablc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"input\\"}],\\"_fc_id\\":\\"id_Flw4m83sclr8bec\\",\\"name\\":\\"ref_F894m83sclr8bfc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"col\\"}],\\"_fc_id\\":\\"id_Fpqmm83sclr7bac\\",\\"name\\":\\"ref_Fo5ym83sclr7bbc\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"fcRow\\"},{\\"type\\":\\"tree\\",\\"field\\":\\"F0qpm83seg72c8c\\",\\"title\\":\\"树形控件\\",\\"info\\":\\"\\",\\"effect\\":{\\"fetch\\":\\"\\"},\\"$required\\":false,\\"props\\":{\\"props\\":{\\"label\\":\\"label\\"},\\"showCheckbox\\":true,\\"nodeKey\\":\\"id\\",\\"data\\":[{\\"label\\":\\"选项201\\",\\"id\\":\\"1\\",\\"children\\":[{\\"label\\":\\"选项101\\",\\"id\\":\\"2\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"3\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"4\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"5\\"}]},{\\"label\\":\\"选项102\\",\\"id\\":\\"6\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"7\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"8\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"9\\"}]},{\\"label\\":\\"选项103\\",\\"id\\":\\"10\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"11\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"12\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"13\\"}]}]},{\\"label\\":\\"选项202\\",\\"id\\":\\"14\\",\\"children\\":[{\\"label\\":\\"选项101\\",\\"id\\":\\"15\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"16\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"17\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"18\\"}]},{\\"label\\":\\"选项102\\",\\"id\\":\\"19\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"20\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"21\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"22\\"}]},{\\"label\\":\\"选项103\\",\\"id\\":\\"23\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"24\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"25\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"26\\"}]}]},{\\"label\\":\\"选项203\\",\\"id\\":\\"27\\",\\"children\\":[{\\"label\\":\\"选项101\\",\\"id\\":\\"28\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"29\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"30\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"31\\"}]},{\\"label\\":\\"选项102\\",\\"id\\":\\"32\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"33\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"34\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"35\\"}]},{\\"label\\":\\"选项103\\",\\"id\\":\\"36\\",\\"children\\":[{\\"label\\":\\"选项01\\",\\"id\\":\\"37\\"},{\\"label\\":\\"选项02\\",\\"id\\":\\"38\\"},{\\"label\\":\\"选项03\\",\\"id\\":\\"39\\"}]}]}]},\\"_fc_id\\":\\"id_Fbq9m83seg72c9c\\",\\"name\\":\\"ref_Fkgqm83seg72cac\\",\\"display\\":true,\\"hidden\\":false,\\"_fc_drag_tag\\":\\"tree\\"}]",
"options": "{\\"form\\":{\\"inline\\":false,\\"hideRequiredAsterisk\\":false,\\"labelPosition\\":\\"right\\",\\"size\\":\\"default\\",\\"labelWidth\\":\\"125px\\"},\\"language\\":{},\\"resetBtn\\":{\\"show\\":false,\\"innerText\\":\\"重置\\"},\\"submitBtn\\":{\\"show\\":false,\\"innerText\\":\\"提交\\"}}"
}
""";
JSONObject config = JSONUtil.parseObj(ruleJson);
// 解析字段信息
Map<String, FieldInfo> fieldInfoMap = parseFormFields(config, formDataJson);
// 打印结果
for (Map.Entry<String, FieldInfo> entry : fieldInfoMap.entrySet()) {
System.out.println("字段: " + entry.getKey());
System.out.println("标题: " + entry.getValue().getTitle());
System.out.println("类型: " + entry.getValue().getType());
System.out.println("原始值: " + entry.getValue().getValue());
System.out.println("显示值: " + entry.getValue().getDisplayValue());
System.out.println("---");
}
}
}
@@ -0,0 +1,46 @@
package com.budwk.app.base.utils;
import cn.hutool.core.util.ReflectUtil;
import org.nutz.lang.util.NutMap;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 枚举工具类
*/
public class EnumUtil {
/**
* 将枚举转换为列表
* @param enumClass
* @return
* @param <E>
*/
public static <E> List<Map<String, Object>> transToList(Class<E> enumClass) {
Assert.isTrue(enumClass.isEnum(), String.format("%s not a enum", enumClass.getSimpleName()));
List<Map<String, Object>> result = new ArrayList<>();
E[] enumConstants = enumClass.getEnumConstants();
for (E enumConstant : enumConstants) {
Map<String, Object> map = NutMap.NEW();
// 获取枚举实例的所有字段
// Field[] fields = enumClass.getDeclaredFields();
Field[] fields = ReflectUtil.getFields(enumClass);
for (Field field : fields) {
try {
field.setAccessible(true); // 使私有字段可访问
map.put(field.getName(), field.get(enumConstant)); // 获取字段值并添加到map中
} catch (Exception e) {
// 如果无法访问字段可以选择忽略或处理异常
e.printStackTrace();
}
}
result.add(map);
}
return result;
}
}
@@ -0,0 +1,184 @@
package com.budwk.app.base.utils;
import java.lang.reflect.Field;
import java.util.*;
/**
* 字段映射工具类
* 支持多种命名规范的字段匹配和转换
*/
public class FieldMappingUtil {
/**
* 多种方式获取字段支持不同命名规范
* @param clazz
* @param columnName 列名或字段名
* @return Field对象找不到返回null
*/
public static Field getField(Class<?> clazz, String columnName) {
if (clazz == null || columnName == null || columnName.isEmpty()) {
return null;
}
// 1. 直接匹配完全相同
Field field = getFieldDirectly(clazz, columnName);
if (field != null) {
return field;
}
// 2. 驼峰命名匹配
String camelName = underlineToCamel(columnName);
if (!camelName.equals(columnName)) {
field = getFieldDirectly(clazz, camelName);
if (field != null) {
return field;
}
}
// 3. 下划线命名匹配
String underlineName = camelToUnderline(columnName);
if (!underlineName.equals(columnName)) {
field = getFieldDirectly(clazz, underlineName);
if (field != null) {
return field;
}
}
// 4. 忽略大小写匹配
field = getFieldIgnoreCase(clazz, columnName);
if (field != null) {
return field;
}
// 5. 驼峰转下划线后忽略大小写匹配
if (!underlineName.equals(columnName)) {
field = getFieldIgnoreCase(clazz, underlineName);
if (field != null) {
return field;
}
}
// 6. 下划线转驼峰后忽略大小写匹配
if (!camelName.equals(columnName)) {
field = getFieldIgnoreCase(clazz, camelName);
if (field != null) {
return field;
}
}
return null;
}
/**
* 直接获取字段完全匹配
*/
private static Field getFieldDirectly(Class<?> clazz, String fieldName) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// 继续向上查找父类
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
return getFieldDirectly(superclass, fieldName);
}
return null;
}
}
/**
* 忽略大小写获取字段
*/
private static Field getFieldIgnoreCase(Class<?> clazz, String fieldName) {
Field[] fields = getAllFields(clazz);
for (Field field : fields) {
if (field.getName().equalsIgnoreCase(fieldName)) {
return field;
}
}
// 再尝试驼峰和下划线转换后忽略大小写
String camelName = underlineToCamel(fieldName);
String underlineName = camelToUnderline(fieldName);
for (Field field : fields) {
String fieldNameLower = field.getName().toLowerCase();
if (fieldNameLower.equals(fieldName.toLowerCase()) ||
fieldNameLower.equals(camelName.toLowerCase()) ||
fieldNameLower.equals(underlineName.toLowerCase())) {
return field;
}
}
return null;
}
/**
* 获取类及其父类的所有字段
*/
private static Field[] getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
Class<?> currentClass = clazz;
while (currentClass != null && currentClass != Object.class) {
fieldList.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}
return fieldList.toArray(new Field[0]);
}
/**
* 下划线转驼峰
*/
public static String underlineToCamel(String underlineStr) {
if (underlineStr == null || !underlineStr.contains("_")) {
return underlineStr;
}
StringBuilder result = new StringBuilder();
String[] parts = underlineStr.toLowerCase().split("_");
for (int i = 0; i < parts.length; i++) {
if (i == 0) {
result.append(parts[i]);
} else {
result.append(capitalize(parts[i]));
}
}
return result.toString();
}
/**
* 驼峰转下划线
*/
public static String camelToUnderline(String camelStr) {
if (camelStr == null || camelStr.isEmpty()) {
return camelStr;
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < camelStr.length(); i++) {
char c = camelStr.charAt(i);
if (Character.isUpperCase(c)) {
if (i > 0) {
result.append("_");
}
result.append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
/**
* 首字母大写
*/
private static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
}
@@ -0,0 +1,61 @@
package com.budwk.app.base.utils;
import org.nutz.lang.util.NutMap;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
/**
* @author wizzer@qq.com
*/
public class FileUtil {
/**
* 分页获取文件列表
*
* @param basePath 目录
* @param pageNumber 页码
* @param pageSize 页大小
* @param sort 按文件名排序
* @return 列表
* @throws Exception
*/
public static NutMap readListPage(String basePath, Integer pageNumber, Integer pageSize, String sort)
throws Exception {
int offset = (pageNumber - 1) * pageSize;
int limit = pageNumber * pageSize;
long total = 0;
List<NutMap> list = new ArrayList<>();
Comparator<Path> comparator = Comparator.naturalOrder();
if ("desc".equals(sort)) {
comparator = Comparator.reverseOrder();
}
try (Stream<Path> fileList = Files.list(Paths.get(basePath))) {
total = fileList.count();
}
try (Stream<Path> fileList = Files.list(Paths.get(basePath)).sorted(comparator).skip(offset)
.limit(limit)) {
fileList.forEach(file -> {
NutMap nutMap = NutMap.NEW();
String fileName = file.getFileName().toString();
nutMap.addv("fileName", fileName);
if (Files.isDirectory(file.toAbsolutePath())) {
nutMap.addv("folder", true);
nutMap.addv("suffix", "folder");
} else {
String suffix = fileName.substring(fileName.indexOf(".") + 1).toLowerCase();
nutMap.addv("folder", false);
nutMap.addv("suffix", suffix);
}
list.add(nutMap);
});
return NutMap.NEW().addv("total", total).addv("list", list);
}
}
}
@@ -0,0 +1,85 @@
package com.budwk.app.base.utils;
import com.budwk.app.base.config.ThreadPoolConfig;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @version 1.0
* @Author zzr
* @nameManyAddOrRenewUtil
* @Date 2024/7/24 16:16
* @注释 多线程批量新增或更新
*/
@IocBean
public class ManyAddOrRenewUtil {
@Inject
private ThreadPoolConfig threadPoolConfig;
/**
* 批量快速插入异步执行
*
* @param list 泛型list,任意实体类集合
* @param batchSize 期望单次操作的数量默认200
* @param <T>
*/
public <T> void asyncExecuteFastInsert(List<T> list, Integer batchSize) {
threadPoolConfig.asyncExecute(list, batchSize, "insert", true);
}
/**
* 批量插入(非快速)异步执行
*
* @param list 泛型list,任意实体类集合
* @param batchSize 期望单次操作的数量默认200
* @param <T>
*/
public <T> void asyncExecuteInsert(List<T> list, Integer batchSize) {
threadPoolConfig.asyncExecute(list, batchSize, "insert", true);
}
/**
* 批量修改忽略空值异步执行
*
* @param list 泛型list,任意实体类集合
* @param batchSize 期望单次操作的数量默认200
* @param <T>
*/
public <T> void asyncExecuteUpdateIgnoreNull(List<T> list, Integer batchSize) {
threadPoolConfig.asyncExecute(list, batchSize, "update", true);
}
/**
* 批量修改(不忽略空值)异步执行
*
* @param list 泛型list,任意实体类集合
* @param batchSize 期望单次操作的数量默认200
* @param <T>
*/
public <T> void asyncExecuteUpdate(List<T> list, Integer batchSize) {
threadPoolConfig.asyncExecute(list, batchSize, "update", false);
}
/**
* 新增或修改异步执行
*
* @param list 泛型list,任意实体类集合
* @param batchSize 期望单次操作的数量默认200
* @param <T>
*/
public <T> void asyncExecuteInsertOrUpdate(List<T> list, Integer batchSize) {
threadPoolConfig.asyncExecute(list, batchSize, "insertOrUpdate", false);
}
}
@@ -0,0 +1,33 @@
package com.budwk.app.base.utils;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
/**
* Created by wizzer on 2017/7/21.
*/
public class MapUtil {
/**
* 使用 Map按key进行排序
*
* @param map
* @return
*/
public static Map<String, Object> sortMapByKey(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> sortMap = new TreeMap<>(
new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
}
class MapKeyComparator implements Comparator<String> {
@Override
public int compare(String str1, String str2) {
return str1.compareTo(str2);
}
}
@@ -0,0 +1,99 @@
package com.budwk.app.base.utils;
import cn.hutool.core.io.FileUtil;
import com.aspose.cells.PdfSaveOptions;
import com.aspose.cells.Workbook;
import com.aspose.words.Document;
import com.aspose.words.SaveFormat;
import com.budwk.app.base.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 文档转换
*/
@Slf4j
public class OfficePlusUtil {
/**
* 转换
*
* @param sourcePath 源文件路径
* @param targetPath 目标文件路径
*/
public static void convert(String sourcePath, String targetPath) {
String suffix = FileUtil.getSuffix(sourcePath);
if (suffix.equals("doc") || suffix.equals("docx")) {
wordConvertPdf(sourcePath, targetPath);
} else if (suffix.equals("xls") || suffix.equals("xlsx")) {
excelConvertPdf(sourcePath, targetPath);
} else {
throw new BaseException("不支持的文档格式");
}
}
/**
* excel转pdf
*
* @param sourcePath 源文件路径
* @param targetPath 目标文件路径
*/
public static void excelConvertPdf(String sourcePath, String targetPath) {
FileOutputStream fileOS = null;
try {
Workbook wb = new Workbook(sourcePath);
fileOS = new FileOutputStream(targetPath);
PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
pdfSaveOptions.setOnePagePerSheet(true);
// for (int i = 0; i < 3; i++) {
// wb.getWorksheets().get(i).getHorizontalPageBreaks().clear();
// wb.getWorksheets().get(i).getVerticalPageBreaks().clear();
// }
// for (int i = 1; i < wb.getWorksheets().getCount(); i++) {
// wb.getWorksheets().get(i).setVisible(false);
// }
wb.getWorksheets().get(0).setVisible(true);
wb.save(fileOS, pdfSaveOptions);
fileOS.flush();
} catch (Exception e) {
log.info("转换pdf报错", e);
} finally {
if (fileOS != null) {
try {
fileOS.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* word转pdf
*
* @param sourcePath 源文件路径
* @param targetPath 目标文件路径
*/
public static void wordConvertPdf(String sourcePath, String targetPath) {
FileOutputStream os = null;
try {
File file = new File(targetPath);
os = new FileOutputStream(file);
Document doc = new Document(sourcePath);
doc.save(os, SaveFormat.PDF);
} catch (Exception e) {
log.info("转换pdf报错", e);
} finally {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.base.utils;
import org.nutz.lang.util.NutMap;
/**
* Created by wizzer on 2018.09
*/
public class PageUtil {
static NutMap map = NutMap.NEW().addv("ascending", "asc").addv("descending", "desc");
public static String getOrder(String key) {
return map.getString(key);
}
}
@@ -0,0 +1,68 @@
package com.budwk.app.base.utils;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.nutz.ioc.loader.annotation.IocBean;
/**
* Created by wizzer on 2017/5/24.
*/
@IocBean
public class PinyinUtil {
/**
* 将汉字转换为全拼
*/
public static String getPingYin(String name) {
char[] charArray = name.toCharArray();
StringBuilder pinyin = new StringBuilder();
for (int i = 0; i < charArray.length; i++) {
if (Character.toString(charArray[i]).matches("[\\u4E00-\\u9FA5]+")) {
pinyin.append(PinyinHelper.toHanyuPinyinStringArray(charArray[i])[0]);
} else {
pinyin.append(charArray[i]);
}
}
return pinyin.toString();
}
/**
* 返回中文的首字母
*
* @param str
* @return
*/
public static String getPinYinHeadChar(String str) {
String convert = "";
for (int j = 0; j < str.length(); j++) {
char word = str.charAt(j);
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(word);
if (pinyinArray != null) {
convert += pinyinArray[0].charAt(0);
} else {
convert += word;
}
}
return convert;
}
/**
* 将字符串转移为ASCII码
*
* @param cnStr
* @return
*/
public static String getCnASCII(String cnStr) {
StringBuffer strBuf = new StringBuffer();
byte[] bGBK = cnStr.getBytes();
for (int i = 0; i < bGBK.length; i++) {
strBuf.append(Integer.toHexString(bGBK[i] & 0xff));
}
return strBuf.toString();
}
// public static void main(String[] args) {
// System.out.println(getPingYin("綦江qq县"));
// System.out.println(getPinYinHeadChar("綦江县"));
// System.out.println(getCnASCII("綦江县"));
// }
}
@@ -0,0 +1,97 @@
package com.budwk.app.base.utils;
import lombok.extern.slf4j.Slf4j;
import org.nutz.lang.Lang;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 为老兼容老的密码加密方式
*
* @author wizzer@qq.com
*/
@Slf4j
public class PwdUtil {
private static final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
private static final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String DIGITS = "0123456789";
private static final String SPECIAL = "!@#$%^&*()-_=+{};:,<.>";
private static final String ALL = LOWERCASE + UPPERCASE + DIGITS + SPECIAL;
private static final SecureRandom random = new SecureRandom();
public static String getPassword(String passowrd, String salt) {
byte[] bytes = hash(passowrd.getBytes(), salt.getBytes(), 1024);
if (bytes != null) {
return Lang.fixedHexString(bytes);
}
return "";
}
public static byte[] hash(byte[] bytes, byte[] salt, int hashIterations) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
if (salt != null) {
digest.reset();
digest.update(salt);
}
byte[] hashed = digest.digest(bytes);
int iterations = hashIterations - 1;
for (int i = 0; i < iterations; ++i) {
digest.reset();
hashed = digest.digest(hashed);
}
return hashed;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String generate(int length) {
if (length < 8 || length > 20) {
throw new IllegalArgumentException("密码长度必须在8到20之间");
}
List<Character> passwordChars = new ArrayList<>();
// 保证每类字符至少一个
passwordChars.add(randomCharFrom(LOWERCASE));
passwordChars.add(randomCharFrom(UPPERCASE));
passwordChars.add(randomCharFrom(DIGITS));
passwordChars.add(randomCharFrom(SPECIAL));
// 剩余位置随机填充
for (int i = passwordChars.size(); i < length; i++) {
passwordChars.add(randomCharFrom(ALL));
}
// 打乱顺序以避免固定模式
Collections.shuffle(passwordChars);
// 构建字符串
StringBuilder password = new StringBuilder();
for (char ch : passwordChars) {
password.append(ch);
}
return password.toString();
}
private static char randomCharFrom(String chars) {
return chars.charAt(random.nextInt(chars.length()));
}
// 示例主方法
public static void main(String[] args) {
System.out.println("生成的密码: " + generate(12));
}
}
@@ -0,0 +1,42 @@
package com.budwk.app.base.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
/**
* @ClassName RsaUtils
* @Author JyuHsin
* @Date 2025/10/29 9:56
* @Version 1.0
* @Description TODO
*/
public class RsaUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
keyGen.initialize(2048);
return keyGen.generateKeyPair();
}
public static String getPublicKeyBase64(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
public static String getPrivateKeyBase64(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}
public static String decrypt(String encryptedBase64, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedBase64));
return new String(decrypted, StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,42 @@
package com.budwk.app.base.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
/**
* @ClassName RsaUtils
* @Author JyuHsin
* @Date 2025/10/29 9:56
* @Version 1.0
* @Description TODO
*/
public class RsaUtils {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
keyGen.initialize(2048);
return keyGen.generateKeyPair();
}
public static String getPublicKeyBase64(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
public static String getPrivateKeyBase64(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}
public static String decrypt(String encryptedBase64, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedBase64));
return new String(decrypted, StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,32 @@
package com.budwk.app.base.utils;
import org.nutz.lang.Lang;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Created by wizzer on 2018/6/28.
*/
public class SignUtil {
public static String createSign(String appkey, Map<String, Object> params) {
Map<String, Object> map = MapUtil.sortMapByKey(params);
StringBuffer sb = new StringBuffer();
Set<String> keySet = map.keySet();
Iterator<String> it = keySet.iterator();
while (it.hasNext()) {
String k = it.next();
String v = (String) map.get(k);
if (null != v && !"".equals(v)
&& !"sign".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("appkey=" + appkey);
String sign = Lang.md5(sb.toString());
return sign;
}
}
@@ -0,0 +1,86 @@
package com.budwk.app.base.utils;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Strings;
import java.util.Random;
/**
* Created by wizzer on 2018/3/17.
*/
public class StringUtil {
/**
* 去掉URL中?后的路径
*
* @param p
* @return
*/
public static String getPath(String p) {
if (Strings.sNull(p).contains("?")) {
return p.substring(0, p.indexOf("?"));
}
return Strings.sNull(p);
}
/**
* 获得父节点ID
*
* @param s
* @return
*/
public static String getParentId(String s) {
if (!Strings.isEmpty(s) && s.length() > 4) {
return s.substring(0, s.length() - 4);
}
return "";
}
/**
* 得到n位随机数
*
* @param s
* @return
*/
public static String getRndNumber(int s) {
Random ra = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s; i++) {
sb.append(String.valueOf(ra.nextInt(8)));
}
return sb.toString();
}
/**
* 判断是否以字符串开头
*
* @param str
* @param s
* @return
*/
public boolean startWith(String str, String s) {
return Strings.sNull(str).startsWith(Strings.sNull(s));
}
/**
* 判断是否包含字符串
*
* @param str
* @param s
* @return
*/
public boolean contains(String str, String s) {
return Strings.sNull(str).contains(Strings.sNull(s));
}
/**
* 将对象转为JSON字符串页面上使用
*
* @param obj
* @return
*/
public String toJson(Object obj) {
return Json.toJson(obj, JsonFormat.compact());
}
}

Some files were not shown because too many files have changed in this diff Show More