u
This commit is contained in:
1 parent
6c20d0c80d
commit
4aaa93df7d
66 files changed
+4375
-1311
No files matched your search
@@ -0,0 +1,24 @@
|
||||
package cn.g3soft;
|
||||
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.utils.uniqueId.idgen.UniqueId;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = {
|
||||
DataSourceAutoConfiguration.class,
|
||||
DruidDataSourceAutoConfigure.class ,
|
||||
})
|
||||
public class ApiApplication {
|
||||
public static void main(String[] args){
|
||||
// 初始化雪花id
|
||||
IdGeneratorOptions options = new IdGeneratorOptions();
|
||||
// 每个项目中都需要设置这个,并且保证 <任意服务器> 下的id不重复
|
||||
options.WorkerId = 1;
|
||||
UniqueId.setIdGenerator(options);
|
||||
|
||||
SpringApplication.run(ApiApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.g3soft.config;
|
||||
|
||||
import cn.g3soft.config.datasource.DataSourceManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class JdbcConfig {
|
||||
@Bean
|
||||
public DataSourceManager dataSourceManager() {
|
||||
return new DataSourceManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcTemplate jdbcTemplate(DataSourceManager dataSourceManager) {
|
||||
return new JdbcTemplate(dataSourceManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.g3soft.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
@Configuration
|
||||
public class WebCorsConfig {
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
// 1.创建 CORS 配置对象
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
// 支持域
|
||||
config.addAllowedOriginPattern("*");
|
||||
// 是否发送 Cookie
|
||||
config.setAllowCredentials(true);
|
||||
// 支持请求方式
|
||||
config.addAllowedMethod("*");
|
||||
// 允许的原始请求头部信息
|
||||
config.addAllowedHeader("*");
|
||||
// 暴露的头部信息
|
||||
config.addExposedHeader("*");
|
||||
// 有效期 1800秒,设置预检请求(OPTIONS请求)的缓存时间,1800秒意味着客户端可以缓存这个设置1800秒
|
||||
config.setMaxAge(1800L);
|
||||
// 2.添加地址映射
|
||||
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
|
||||
corsConfigurationSource.registerCorsConfiguration("/**", config);
|
||||
// 3.返回 CorsFilter 对象
|
||||
return new CorsFilter(corsConfigurationSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.g3soft.config;
|
||||
|
||||
import cn.g3soft.interceptor.AuthInterceptor;
|
||||
import cn.g3soft.interceptor.DataSourceInterceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new AuthInterceptor())
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
"/auth/login",
|
||||
"/error"
|
||||
);
|
||||
|
||||
registry.addInterceptor(new DataSourceInterceptor())
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
"/auth/login",
|
||||
"/error"
|
||||
);
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package cn.g3soft.config.datasource;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import cn.g3soft.context.DataSourceContextHolder;
|
||||
import cn.g3soft.utils.SpringUtils;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
private static final String CONFIG_PATH = "dbconfigs/";
|
||||
private static final Map<String, DataSource> dataSources = new ConcurrentHashMap<>();
|
||||
|
||||
public DataSourceManager() {
|
||||
setTargetDataSources(new ConcurrentHashMap<>(dataSources));
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey() {
|
||||
return DataSourceContextHolder.getDataSourceKey();
|
||||
}
|
||||
|
||||
public static Connection getConnection(String orgCode) throws Exception {
|
||||
DataSource dataSource = getOrCreateDataSource(orgCode);
|
||||
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建数据源
|
||||
*/
|
||||
public static DataSource getOrCreateDataSource(String orgCode) throws Exception {
|
||||
|
||||
DataSource ds = dataSources.get(orgCode);
|
||||
if (ds != null) {
|
||||
return ds;
|
||||
}
|
||||
|
||||
synchronized (DataSourceManager.class) {
|
||||
|
||||
ds = dataSources.get(orgCode);
|
||||
if (ds != null) {
|
||||
return ds;
|
||||
}
|
||||
|
||||
ds = createDataSource(orgCode);
|
||||
dataSources.put(orgCode, ds);
|
||||
|
||||
refresh();
|
||||
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 routing datasource
|
||||
*/
|
||||
private static void refresh() {
|
||||
|
||||
DataSourceManager routing = SpringUtils.getBean(DataSourceManager.class);
|
||||
routing.setTargetDataSources(new ConcurrentHashMap<>(dataSources));
|
||||
routing.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据源
|
||||
*/
|
||||
private static DataSource createDataSource(String orgCode) throws Exception {
|
||||
|
||||
String file = CONFIG_PATH + orgCode + ".properties";
|
||||
|
||||
try (InputStream in = DataSourceManager.class
|
||||
.getClassLoader()
|
||||
.getResourceAsStream(file)) {
|
||||
|
||||
if (in == null) {
|
||||
throw new RuntimeException("未找到配置文件: " + file);
|
||||
}
|
||||
|
||||
Properties p = new Properties();
|
||||
p.load(in);
|
||||
|
||||
DruidDataSource ds = new DruidDataSource();
|
||||
|
||||
ds.setUrl(p.getProperty("url"));
|
||||
ds.setUsername(p.getProperty("username"));
|
||||
ds.setPassword(p.getProperty("password"));
|
||||
ds.setDriverClassName(p.getProperty("driver"));
|
||||
|
||||
ds.setInitialSize(2);
|
||||
ds.setMaxActive(20);
|
||||
ds.setMinIdle(2);
|
||||
ds.setValidationQuery("SELECT 1");
|
||||
|
||||
ds.init();
|
||||
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package cn.g3soft.config.datasource;
|
||||
|
||||
import com.alibaba.druid.support.http.StatViewServlet;
|
||||
import com.alibaba.druid.support.http.WebStatFilter;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class DruidConfig {
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
|
||||
// 监控信息显示页面
|
||||
StatViewServlet statViewServlet = new StatViewServlet();
|
||||
// 访问监控信息显示页面的 url 路径(建议值为 /druid/* )
|
||||
String urlPattern = "/druid/*";
|
||||
|
||||
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(statViewServlet, urlPattern);
|
||||
|
||||
Map<String, String> initParams = new HashMap<>();
|
||||
// 是否允许清空统计数据
|
||||
initParams.put("resetEnable", "false");
|
||||
// 登录监控信息显示页面的用户名
|
||||
initParams.put("loginUsername", "admin");
|
||||
// 登录监控信息显示页面的密码
|
||||
initParams.put("loginPassword", "admin");
|
||||
// 允许访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
|
||||
initParams.put("allow", "127.0.0.1");
|
||||
// 拒绝访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
|
||||
// initParams.put("deny", "");
|
||||
|
||||
bean.setInitParameters(initParams);
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
|
||||
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
|
||||
// 网络监控过滤器(用于采集 web-jdbc 关联监控的数据)
|
||||
bean.setFilter(new WebStatFilter());
|
||||
// 过滤所有的 url 路径
|
||||
Collection<String> urlPatterns = new ArrayList<>();
|
||||
urlPatterns.add("/*");
|
||||
bean.setUrlPatterns(urlPatterns);
|
||||
|
||||
Map<String, String> initParams = new HashMap<>();
|
||||
// 排除不必要采集的 url 路径,以逗号“,”分割
|
||||
initParams.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.map,/druid/*");
|
||||
// 是否使用 session 监控功能
|
||||
initParams.put("sessionStatEnable", "true");
|
||||
// 是否使用 session 监控最大数量(默认是1000)
|
||||
initParams.put("sessionStatMaxCount", "1000");
|
||||
// 使得 druid 能够知道当前的 session 的用户是谁,根据需要,把改值修改为你 user 信息保存在 session 中的 sessionName
|
||||
initParams.put("principalSessionName", "session_user_key");
|
||||
// 如果你的 user 信息保存在 cookie 中,你可以配置 principalCookieName,使得 druid 知道当前的 user 是谁,根据需要,把该值修改为你 user 信息保存在 cookie 中的 cookieName
|
||||
initParams.put("principalCookieName", "cookie_user_key");
|
||||
// 是否监控单个 url 调用的 sql 列表
|
||||
initParams.put("profileEnable", "true");
|
||||
bean.setInitParameters(initParams);
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cn.g3soft.context;
|
||||
|
||||
public class DataSourceContextHolder {
|
||||
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
|
||||
|
||||
public static void setDataSourceKey(String dataSourceKey) {
|
||||
contextHolder.set(dataSourceKey);
|
||||
}
|
||||
|
||||
public static String getDataSourceKey() {
|
||||
return contextHolder.get();
|
||||
}
|
||||
|
||||
public static void clearDataSourceKey() {
|
||||
contextHolder.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.g3soft.context;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class UserContext {
|
||||
private final String userId;
|
||||
private final String orgId;
|
||||
|
||||
// 使用 ThreadLocal 保存当前线程的用户上下文
|
||||
private static final ThreadLocal<UserContext> userContextThreadLocal = new ThreadLocal<>();
|
||||
|
||||
// 私有构造,防止外部直接 new
|
||||
private UserContext(String userId, String orgId) {
|
||||
this.userId = userId;
|
||||
this.orgId = orgId;
|
||||
}
|
||||
|
||||
public static void set(String userId, String orgId) {
|
||||
userContextThreadLocal.set(new UserContext(userId, orgId));
|
||||
}
|
||||
|
||||
public static UserContext get() {
|
||||
return userContextThreadLocal.get();
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
userContextThreadLocal.remove();
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import cn.g3soft.config.datasource.DataSourceManager;
|
||||
import cn.g3soft.context.DataSourceContextHolder;
|
||||
import cn.g3soft.utils.DbUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import cn.g3soft.utils.jwt.JwtPayload;
|
||||
import cn.g3soft.utils.jwt.JwtUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
/**
|
||||
* 用户登录接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - orgid: 机构码(必填)
|
||||
* - userid: 用户账号(必填)
|
||||
* - password: 登录密码
|
||||
* @return 登录结果(Map 类型),包含:
|
||||
* - token: 登录令牌
|
||||
* - username: 用户名称
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<Map<String,Object>> login(@RequestBody Map<String,Object> params){
|
||||
String orgid = (String) params.get("orgid");
|
||||
String userid = (String) params.get("userid");
|
||||
String password = (String) params.get("password");
|
||||
|
||||
if (StringUtils.isBlank(orgid)) {
|
||||
return Result.fail("机构码不能为空");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(userid)) {
|
||||
return Result.fail("账号不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
DataSourceManager.getOrCreateDataSource(orgid);
|
||||
} catch (Exception e) {
|
||||
return Result.fail("机构码错误");
|
||||
}
|
||||
|
||||
DataSourceContextHolder.setDataSourceKey(orgid);
|
||||
|
||||
String sql = "SELECT b_id,b_name,b_password,b_canuse FROM b_user WHERE b_id = ?";
|
||||
|
||||
List<Map<String, Object>> list = dbUtils.loadData(sql, userid);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return Result.fail("账号不存在");
|
||||
}
|
||||
|
||||
Map<String, Object> user = list.get(0);
|
||||
|
||||
String dbPassword = Objects.toString(user.get("b_password"), "");
|
||||
String inputPassword = Objects.toString(password, "");
|
||||
String canuse = Objects.toString(user.get("b_canuse"), "");
|
||||
|
||||
// 密码校验逻辑
|
||||
if (!dbPassword.equals(inputPassword)) {
|
||||
return Result.fail("密码错误");
|
||||
}
|
||||
|
||||
if (!canuse.equals("1")){
|
||||
return Result.fail("账号已被禁用");
|
||||
}
|
||||
|
||||
// 校验成功
|
||||
String token = JwtUtils.createToken(new JwtPayload(orgid, userid));
|
||||
|
||||
Map<String,Object> map = new HashMap<>();
|
||||
|
||||
map.put("token",token);
|
||||
map.put("username",Objects.toString(user.get("b_name"), ""));
|
||||
|
||||
return Result.ok(map);
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import cn.g3soft.utils.DbUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import cn.g3soft.utils.ValidationUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/column")
|
||||
public class ColumnController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
/**
|
||||
* 查询视图列配置接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* @return 数据列表(List 类型),返回视图的列显示配置
|
||||
*/
|
||||
@PostMapping("/view")
|
||||
public Result<List<Map<String,Object>>> loadViewColumns(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
|
||||
String viewName = (String) params.get("viewName");
|
||||
|
||||
List<String> columns = Arrays.asList(
|
||||
"col_field_name",
|
||||
"col_caption",
|
||||
"col_display_format",
|
||||
"col_show_summary",
|
||||
"col_group_index"
|
||||
);
|
||||
|
||||
String where = "col_table_name = '" + viewName + "' AND col_visible = '1'";
|
||||
|
||||
List<Map<String,Object>> data =
|
||||
dbUtils.loadData(
|
||||
"s_columnLib",
|
||||
where,
|
||||
"col_position desc",
|
||||
columns
|
||||
);
|
||||
|
||||
return Result.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询编辑列配置接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* @return 数据列表(List 类型),返回表单编辑列配置
|
||||
*/
|
||||
@PostMapping("/edit")
|
||||
public Result<List<Map<String,Object>>> loadEditColumns(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
|
||||
String viewName = (String) params.get("viewName");
|
||||
|
||||
List<String> columns = Arrays.asList(
|
||||
"col_field_name",
|
||||
"col_field_name_v",
|
||||
"col_caption",
|
||||
"col_type",
|
||||
"col_width",
|
||||
"col_allow_readonly",
|
||||
"col_display_format",
|
||||
|
||||
"col_edit_width",
|
||||
"col_edit_source_data",
|
||||
"col_edit_source_field",
|
||||
"col_edit_height",
|
||||
"col_edit_group_index"
|
||||
);
|
||||
|
||||
String where = "col_table_name = '" + viewName + "' AND col_edit = '1'";
|
||||
|
||||
List<Map<String,Object>> data =
|
||||
dbUtils.loadData(
|
||||
"s_columnLib",
|
||||
where,
|
||||
"col_edit_position desc",
|
||||
columns
|
||||
);
|
||||
|
||||
return Result.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询全部列配置接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* @return 数据列表(List 类型),返回视图的全部列配置
|
||||
*/
|
||||
@PostMapping("/all")
|
||||
public Result<List<Map<String,Object>>> loadAllColumns(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
|
||||
String viewName = (String) params.get("viewName");
|
||||
|
||||
String where = "col_table_name = '" + viewName + "'";
|
||||
List<Map<String,Object>> data =
|
||||
dbUtils.loadData(
|
||||
"s_columnLib",
|
||||
where,
|
||||
"col_edit_position desc",
|
||||
Collections.emptyList()
|
||||
);
|
||||
|
||||
return Result.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验列配置接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* @return 无返回数据,校验完成返回成功结果
|
||||
*/
|
||||
@PostMapping("/check")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> checkColumns(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
|
||||
String viewName = (String) params.get("viewName");
|
||||
|
||||
Map<String, Object> procParams = new HashMap<>();
|
||||
procParams.put("viewName", viewName);
|
||||
|
||||
dbUtils.executeProc("G3_CheckColumnSettings_New", procParams, false);
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import cn.g3soft.utils.DbUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import cn.g3soft.utils.ValidationUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/data")
|
||||
public class DataListController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
/**
|
||||
* 加载数据接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* - searchCondition: 查询条件
|
||||
* - searchColumns: 查询列(如果未指定,默认查询所有列)
|
||||
* - orderField: 排序字段
|
||||
* @return 数据列表(List 类型),返回查询结果的数据
|
||||
*/
|
||||
@PostMapping("/loadData")
|
||||
public Result<List<Map<String, Object>>> loadData(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
|
||||
String viewName = (String) params.get("viewName");
|
||||
String searchCondition = (String) params.get("searchCondition");
|
||||
List<String> searchColumns = (List<String>) params.get("searchColumns");
|
||||
String orderField = (String) params.get("orderField");
|
||||
|
||||
List<Map<String, Object>> resultList = dbUtils.loadData(viewName, searchCondition, orderField, searchColumns);
|
||||
|
||||
return Result.ok(resultList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载数据接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - sql: SQL 查询语句(必填)
|
||||
* @return 数据列表(List 类型),返回查询结果的数据
|
||||
*/
|
||||
@PostMapping("loadDataBySql")
|
||||
public Result<List<Map<String, Object>>> loadDataBySql(@RequestBody Map<String, Object> params) {
|
||||
// 校验必填字段
|
||||
ValidationUtils.checkRequiredFields(params, "sql");
|
||||
|
||||
String sql = (String) params.get("sql");
|
||||
|
||||
// 执行查询并获取数据
|
||||
List<Map<String, Object>> list = dbUtils.loadData(sql);
|
||||
|
||||
return Result.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* - page: 页码(默认 1)
|
||||
* - size: 每页条数(默认 10)
|
||||
* - searchCondition: 查询条件
|
||||
* - searchColumns: 查询列(未指定时默认查询所有列)
|
||||
* - orderField: 排序字段
|
||||
* @return 分页结果(Map 类型),包含:
|
||||
* - total: 总记录数
|
||||
* - rows: 数据列表
|
||||
*/
|
||||
@PostMapping("page")
|
||||
public Result<Map<String, Object>> page(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
|
||||
Integer page = (Integer) params.getOrDefault("page", 1);
|
||||
Integer size = (Integer) params.getOrDefault("size", 10);
|
||||
|
||||
String viewName = (String) params.get("viewName");
|
||||
String searchCondition = (String) params.get("searchCondition");
|
||||
List<String> searchColumns = (List<String>) params.get("searchColumns");
|
||||
String orderField = (String) params.get("orderField");
|
||||
|
||||
Map<String, Object> result = dbUtils.page(page, size, viewName, searchCondition, orderField, searchColumns);
|
||||
|
||||
return Result.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形数据查询接口
|
||||
*
|
||||
* @param params 参数集合,包含:
|
||||
* - viewName: 视图名称(必填)
|
||||
* - id: 当前节点 ID(为空时查询根节点)
|
||||
* - js: 当前层级
|
||||
* @return 数据列表(List 类型),返回当前节点的子节点数据
|
||||
*/
|
||||
@PostMapping("tree")
|
||||
public Result<List<Map<String, Object>>> loadTreeData(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "viewName");
|
||||
String viewName = (String) params.get("viewName");
|
||||
String id = (String) params.get("id");
|
||||
Integer js = (Integer) params.get("js");
|
||||
|
||||
StringBuilder sql = new StringBuilder("SELECT * FROM ").append(viewName).append(" WHERE js = ?");
|
||||
List<Object> sqlParams = new ArrayList<>();
|
||||
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
js++;
|
||||
}
|
||||
|
||||
sqlParams.add(js);
|
||||
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
sql.append(" AND b_id LIKE ?");
|
||||
sqlParams.add(id + "_%");
|
||||
}
|
||||
|
||||
List<Map<String, Object>> data = dbUtils.loadData(sql.toString(), sqlParams.toArray());
|
||||
|
||||
return Result.ok(data);
|
||||
}
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cn.g3soft.context.UserContext;
|
||||
import cn.g3soft.utils.DbUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import cn.g3soft.utils.TimeUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/data")
|
||||
public class DataSaveController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
// 单主键
|
||||
@PostMapping("/saveObject")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> saveObject(@RequestBody List<Map<String, Object>> params) throws JsonProcessingException {
|
||||
if (ObjectUtils.isEmpty(params)) {
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
String userId = UserContext.get().getUserId();
|
||||
String currentDateTime = TimeUtils.getDateTime();
|
||||
|
||||
List<Map<String, Object>> logList = new ArrayList<>();
|
||||
|
||||
for (Map<String, Object> param : params) {
|
||||
String tableName = (String) param.get("tableName");
|
||||
String keyField = (String) param.get("keyField");
|
||||
List<Map<String, Object>> insertList = (List<Map<String, Object>>) param.get("inserts");
|
||||
List<Map<String, Object>> updateList = (List<Map<String, Object>>) param.get("updates");
|
||||
List<Map<String, Object>> deleteList = (List<Map<String, Object>>) param.get("deletes");
|
||||
|
||||
// enable 数字控制:0 不生成日志,其余或未传默认开启
|
||||
boolean logEnabled = !"0".equals(param.get("log")); // 日志开关,默认开启
|
||||
boolean userEnable = !"0".equals(param.get("user")); // 操作人开关,默认开启
|
||||
|
||||
// 插入
|
||||
if (ObjectUtils.isNotEmpty(insertList)) {
|
||||
for (Map<String, Object> insertMap : insertList) {
|
||||
if (userEnable){
|
||||
if (keyField.equals("subid")) {
|
||||
insertMap.put("mx_inputuser_id", userId);
|
||||
insertMap.put("mx_inputdatetime", currentDateTime);
|
||||
} else {
|
||||
insertMap.put("b_inputuser_id", userId);
|
||||
insertMap.put("b_inputdatetime", currentDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
dbUtils.insert(tableName, keyField, insertMap);
|
||||
}
|
||||
|
||||
if (logEnabled){
|
||||
addOperationLog(logList,tableName,"INSERT",insertList);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新
|
||||
if (ObjectUtils.isNotEmpty(updateList)) {
|
||||
for (Map<String, Object> updateMap : updateList) {
|
||||
if (userEnable) {
|
||||
if (keyField.equals("subid")) {
|
||||
updateMap.put("mx_updateuser_id", userId);
|
||||
updateMap.put("mx_updatedatetime", currentDateTime);
|
||||
} else {
|
||||
updateMap.put("b_updateuser_id", userId);
|
||||
updateMap.put("b_updatedatetime", currentDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
dbUtils.update(tableName, keyField, updateMap);
|
||||
}
|
||||
|
||||
if (logEnabled) {
|
||||
addOperationLog(logList, tableName, "UPDATE", updateList);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
if (ObjectUtils.isNotEmpty(deleteList)) {
|
||||
for (Map<String, Object> deleteMap : deleteList) {
|
||||
dbUtils.deleteById(tableName, keyField, deleteMap.get(keyField));
|
||||
}
|
||||
|
||||
if (logEnabled) {
|
||||
addOperationLog(logList, tableName, "DELETE", deleteList);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!logList.isEmpty()) {
|
||||
dbUtils.batchInsert("log_datas", logList);
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
// 多主键
|
||||
@PostMapping("/saveObjectKeys")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> saveObjectKeys(@RequestBody List<Map<String, Object>> params) throws JsonProcessingException {
|
||||
if (ObjectUtils.isEmpty(params)) {
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
String userId = UserContext.get().getUserId();
|
||||
String currentDateTime = TimeUtils.getDateTime();
|
||||
|
||||
List<Map<String, Object>> logList = new ArrayList<>();
|
||||
|
||||
for (Map<String, Object> param : params) {
|
||||
String tableName = (String) param.get("tableName");
|
||||
List<String> keyFields = (List<String>) param.get("keyFields");
|
||||
List<Map<String, Object>> insertList = (List<Map<String, Object>>) param.get("inserts");
|
||||
List<Map<String, Object>> updateList = (List<Map<String, Object>>) param.get("updates");
|
||||
List<Map<String, Object>> deleteList = (List<Map<String, Object>>) param.get("deletes");
|
||||
// enable 数字控制:0 不生成日志,其余或未传默认开启
|
||||
boolean logEnabled = !"0".equals(param.get("log")); // 日志开关,默认开启
|
||||
boolean userEnable = !"0".equals(param.get("user")); // 操作人开关,默认开启
|
||||
|
||||
// 插入
|
||||
if (ObjectUtils.isNotEmpty(insertList)) {
|
||||
for (Map<String, Object> insertMap : insertList) {
|
||||
if(userEnable){
|
||||
if (keyFields.contains("subid")) {
|
||||
insertMap.put("mx_inputuser_id", userId);
|
||||
insertMap.put("mx_inputdatetime", currentDateTime);
|
||||
} else {
|
||||
insertMap.put("b_inputuser_id", userId);
|
||||
insertMap.put("b_inputdatetime", currentDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
dbUtils.insert(tableName, keyFields, insertMap);
|
||||
}
|
||||
|
||||
if (logEnabled) {
|
||||
addOperationLog(logList, tableName, "INSERT", insertList);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新
|
||||
if (ObjectUtils.isNotEmpty(updateList)) {
|
||||
for (Map<String, Object> updateMap : updateList) {
|
||||
if(userEnable) {
|
||||
if (keyFields.contains("subid")) {
|
||||
updateMap.put("mx_updateuser_id", userId);
|
||||
updateMap.put("mx_updatedatetime", currentDateTime);
|
||||
} else {
|
||||
updateMap.put("b_updateuser_id", userId);
|
||||
updateMap.put("b_updatedatetime", currentDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
dbUtils.update(tableName, keyFields, updateMap);
|
||||
}
|
||||
if (logEnabled) {
|
||||
addOperationLog(logList, tableName, "UPDATE", updateList);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
if (ObjectUtils.isNotEmpty(deleteList)) {
|
||||
for (Map<String, Object> deleteMap : deleteList) {
|
||||
dbUtils.delete(tableName,deleteMap);
|
||||
}
|
||||
if (logEnabled) {
|
||||
addOperationLog(logList, tableName, "DELETE", deleteList);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!logList.isEmpty()){
|
||||
dbUtils.batchInsert("log_datas",logList);
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
|
||||
private void addOperationLog(List<Map<String,Object>> logList,String table, String operation, List<Map<String, Object>> data) throws JsonProcessingException {
|
||||
if (ObjectUtils.isEmpty(data)){
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String dataStr = objectMapper.writeValueAsString(data);
|
||||
|
||||
Map<String,Object> mapData = new HashMap<>();
|
||||
mapData.put("b_table",table);
|
||||
mapData.put("b_option",operation);
|
||||
mapData.put("b_datas",dataStr);
|
||||
|
||||
logList.add(mapData);
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import cn.g3soft.context.UserContext;
|
||||
import cn.g3soft.utils.*;
|
||||
import cn.g3soft.utils.uniqueId.idgen.UniqueId;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class FileController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
@PostMapping("/getAliyunKey")
|
||||
public Result<Map<String,Object>> getAliyunKey(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "bucket");
|
||||
|
||||
String bucket = (String) params.get("bucket");
|
||||
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
|
||||
String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=" + orgId + "&bucket=" + bucket;
|
||||
String reqResult = HttpUtils.sendGet(requestUrl);
|
||||
|
||||
Map<String, Object> objectMap = JsonUtils.toMap(reqResult);
|
||||
|
||||
return Result.ok(objectMap);
|
||||
}
|
||||
|
||||
@PostMapping("/insert")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> insertFile(@RequestBody Map<String,Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid");
|
||||
|
||||
String userId = UserContext.get().getUserId();
|
||||
|
||||
params.put("subid", UniqueId.nextId());
|
||||
params.put("mx_inputuser_id",userId);
|
||||
params.put("mx_inputdatetime", TimeUtils.getDateTime());
|
||||
|
||||
dbUtils.insert("bf_file","subid",params);
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
@PostMapping("/delete")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteFile(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "id", "tableName", "bucketName");
|
||||
|
||||
String tableName = params.get("tableName").toString();
|
||||
String bucketName = params.get("bucketName").toString();
|
||||
String id = params.get("id").toString();
|
||||
|
||||
// 查询数据
|
||||
List<Map<String, Object>> maps = dbUtils.loadData("SELECT * FROM " + tableName + " WHERE subid = ?", id);
|
||||
|
||||
// 如果没有数据,直接返回删除成功
|
||||
if (ObjectUtils.isEmpty(maps)) {
|
||||
return Result.ok("删除成功");
|
||||
}
|
||||
|
||||
String mxMapfilename = maps.get(0).get("mx_mapfilename").toString();
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
int index = mxMapfilename.indexOf(orgId + "/");
|
||||
|
||||
// 如果没找到,就转换orgId为大写
|
||||
if (index < 0) {
|
||||
orgId = orgId.toUpperCase();
|
||||
index = mxMapfilename.indexOf(orgId + "/");
|
||||
}
|
||||
|
||||
String objectName = mxMapfilename.substring(index);
|
||||
|
||||
// 删除记录
|
||||
Map<String, Object> deleteParams = new HashMap<>();
|
||||
deleteParams.put("subid", id);
|
||||
int deleteCount = dbUtils.delete(tableName, deleteParams);
|
||||
|
||||
// 如果删除成功,发送删除请求
|
||||
if (deleteCount > 0) {
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
try {
|
||||
HttpUtils.sendGet(requestUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("删除文件失败",e);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/deleteAliyunFile")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteAliyunFile(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "bucketName", "mx_mapfilename");
|
||||
|
||||
String bucketName = params.get("bucketName").toString();
|
||||
String mxMapfilename = params.get("mx_mapfilename").toString();
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
|
||||
int index = mxMapfilename.indexOf(orgId + "/");
|
||||
// 如果没找到,就转换orgId为大写
|
||||
if (index < 0) {
|
||||
orgId = orgId.toUpperCase();
|
||||
index = mxMapfilename.indexOf(orgId + "/");
|
||||
}
|
||||
|
||||
String objectName = mxMapfilename.substring(index);
|
||||
|
||||
// 发送删除请求
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
|
||||
try {
|
||||
HttpUtils.sendGet(requestUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("删除文件失败",e);
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import cn.g3soft.utils.HttpUtils;
|
||||
import cn.g3soft.utils.JsonUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import cn.g3soft.utils.ValidationUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/ocr")
|
||||
public class OcrController {
|
||||
@PostMapping("/box")
|
||||
public Result<Map<String,Object>> boxOcr(@RequestBody Map<String,Object> params){
|
||||
ValidationUtils.checkRequiredFields(params, "fileUrl");
|
||||
|
||||
String fileUrl = (String) params.get("fileUrl");
|
||||
|
||||
String requestUrl = "http://116.204.97.131:80/ocr";
|
||||
|
||||
Map<String, Object> reqPayload = new HashMap<>();
|
||||
reqPayload.put("file", fileUrl);
|
||||
reqPayload.put("fileType", 1);
|
||||
reqPayload.put("visualize", false);
|
||||
|
||||
try {
|
||||
String response = HttpUtils.sendPostJson(requestUrl, reqPayload);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode root = mapper.readTree(response);
|
||||
|
||||
JsonNode ocrResults = root.path("result").path("ocrResults");
|
||||
if (!ocrResults.isArray() || ocrResults.isEmpty()) {
|
||||
return Result.ok(Collections.emptyMap());
|
||||
}
|
||||
|
||||
String number = "";
|
||||
double maxScore = -1.0;
|
||||
|
||||
for (JsonNode item : ocrResults) {
|
||||
JsonNode pruned = item.path("prunedResult");
|
||||
JsonNode recTexts = pruned.path("rec_texts");
|
||||
JsonNode recScores = pruned.path("rec_scores");
|
||||
|
||||
for (int i = 0; i < recTexts.size(); i++) {
|
||||
double score = recScores.get(i).asDouble();
|
||||
if (score > maxScore) {
|
||||
maxScore = score;
|
||||
number = recTexts.get(i).asText();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxScore < 0) {
|
||||
return Result.ok(Collections.emptyMap());
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("boxNumber", number);
|
||||
result.put("score", maxScore);
|
||||
|
||||
return Result.ok(result);
|
||||
} catch (Exception e){
|
||||
return Result.fail("OCR 识别失败,请联系管理员");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.g3soft.interceptor;
|
||||
|
||||
import cn.g3soft.context.UserContext;
|
||||
import cn.g3soft.utils.JsonUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import cn.g3soft.utils.jwt.JwtPayload;
|
||||
import cn.g3soft.utils.jwt.JwtUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
/***
|
||||
* 在请求处理之前进行调用(Controller方法调用之前)
|
||||
*/
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
//OPTIONS方法是HTTP协议中定义的一种请求方法。它用于在客户端和服务器之间发起一个预检请求(Preflight Request),以确定实际的请求是否被服务器接受。
|
||||
String method = request.getMethod();
|
||||
if("OPTIONS".equalsIgnoreCase(method)){
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean flag = false;
|
||||
|
||||
try {
|
||||
String token = request.getHeader("Authorization");
|
||||
if (token.startsWith("Bearer ")) {
|
||||
// 去掉 "Bearer " 前缀,获取 token 部分
|
||||
token = token.substring(7);
|
||||
// 继续处理 token,例如验证、解码等操作
|
||||
}
|
||||
|
||||
if(!StringUtils.isBlank(token)){
|
||||
// 验证token
|
||||
JwtPayload jwtPayload = JwtUtils.parseToken(token);
|
||||
|
||||
if (jwtPayload != null){
|
||||
flag = true;
|
||||
UserContext.set(jwtPayload.getUserId(), jwtPayload.getOrgId());
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
// 登录失败
|
||||
}
|
||||
|
||||
if(!flag){
|
||||
Result<Void> result = Result.fail(Result.AUTH_FAIL_CODE,"登录已过期");
|
||||
|
||||
String jsonString = JsonUtils.toJson(result);
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
PrintWriter out = response.getWriter();
|
||||
out.println(jsonString);
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/***
|
||||
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
|
||||
*/
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
// System.out.println("执行了拦截器的postHandle方法");
|
||||
}
|
||||
|
||||
/***
|
||||
* 整个请求结束之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(主要用于进行资源清理工作)
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
UserContext.remove();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package cn.g3soft.interceptor;
|
||||
|
||||
import cn.g3soft.config.datasource.DataSourceManager;
|
||||
import cn.g3soft.context.DataSourceContextHolder;
|
||||
import cn.g3soft.context.UserContext;
|
||||
import cn.g3soft.utils.JsonUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class DataSourceInterceptor implements HandlerInterceptor {
|
||||
/***
|
||||
* 在请求处理之前进行调用(Controller方法调用之前)
|
||||
*/
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
boolean flag = false;
|
||||
|
||||
try {
|
||||
UserContext userContext = UserContext.get();
|
||||
String orgId = userContext.getOrgId();
|
||||
|
||||
DataSourceManager.getOrCreateDataSource(orgId);
|
||||
DataSourceContextHolder.setDataSourceKey(orgId);
|
||||
|
||||
flag = true;
|
||||
} catch (Exception e){
|
||||
Result<Void> result = Result.fail(Result.AUTH_FAIL_CODE,"机构码错误,请重新登录");
|
||||
|
||||
String jsonString = JsonUtils.toJson(result);
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
PrintWriter out = response.getWriter();
|
||||
out.println(jsonString);
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
/***
|
||||
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
|
||||
*/
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
// System.out.println("执行了拦截器的postHandle方法");
|
||||
}
|
||||
|
||||
/***
|
||||
* 整个请求结束之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(主要用于进行资源清理工作)
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
DataSourceContextHolder.clearDataSourceKey();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class DbUtils {
|
||||
@Resource
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final Pattern ILLEGAL_SQL_PATTERN = Pattern.compile(
|
||||
"(?i)\\b(delete|update|insert|drop|truncate|alter|create|exec|execute|grant|revoke)\\b"
|
||||
);
|
||||
|
||||
public static final Pattern SAFE_IDENTIFIER =
|
||||
Pattern.compile("^[a-zA-Z0-9_]+$");
|
||||
|
||||
public void validateIdentifier(String name) {
|
||||
if (StringUtils.isBlank(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SAFE_IDENTIFIER.matcher(name).matches()) {
|
||||
throw new RuntimeException("非法标识符: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据
|
||||
*/
|
||||
public List<Map<String, Object>> loadData(
|
||||
String viewName,
|
||||
String searchCondition,
|
||||
String orderField,
|
||||
List<String> searchColumns
|
||||
) {
|
||||
validateIdentifier(viewName);
|
||||
|
||||
String columnsSql = (searchColumns == null || searchColumns.isEmpty())
|
||||
? "*"
|
||||
: searchColumns.stream()
|
||||
.peek(this::validateIdentifier)
|
||||
.map(c -> "[" + c + "]")
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
|
||||
sqlBuilder.append("SELECT ")
|
||||
.append(columnsSql)
|
||||
.append(" FROM ")
|
||||
.append(viewName);
|
||||
|
||||
if (StringUtils.isNotBlank(searchCondition)) {
|
||||
sqlBuilder.append(" WHERE ").append(searchCondition);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(orderField)) {
|
||||
sqlBuilder.append(" ORDER BY ").append(orderField);
|
||||
}
|
||||
|
||||
String sql = sqlBuilder.toString();
|
||||
|
||||
validateFinalSql(sql);
|
||||
|
||||
return jdbcTemplate.queryForList(sql);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> loadData(String sql) {
|
||||
validateFinalSql(sql);
|
||||
|
||||
return jdbcTemplate.queryForList(sql);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> loadData(String sql, Object... params) {
|
||||
// 校验 SQL 合法性
|
||||
validateFinalSql(sql);
|
||||
|
||||
if (params == null || params.length == 0) {
|
||||
// 如果没有参数,直接调用原来的 queryForList
|
||||
return jdbcTemplate.queryForList(sql);
|
||||
}
|
||||
|
||||
// 使用 JdbcTemplate 的 queryForList 支持预编译参数
|
||||
return jdbcTemplate.queryForList(sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页通用方法
|
||||
* @param page 页数
|
||||
* @param size 条数
|
||||
* @param viewName 视图名称
|
||||
* @param searchCondition 搜索条件
|
||||
* @param orderField 排序条件
|
||||
* @param searchColumns 搜索列(字符串数组,没传默认查 *)
|
||||
* @return Map {total: 总条数, rows: 数据列表}
|
||||
*/
|
||||
public Map<String, Object> page(
|
||||
int page,
|
||||
int size,
|
||||
String viewName,
|
||||
String searchCondition,
|
||||
String orderField,
|
||||
List<String> searchColumns
|
||||
) {
|
||||
validateIdentifier(viewName);
|
||||
validateIdentifier(orderField);
|
||||
|
||||
if (page < 1) page = 1;
|
||||
if (size <= 0) size = 10;
|
||||
|
||||
int start = (page - 1) * size + 1;
|
||||
int end = page * size;
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
// ======================
|
||||
// SELECT 字段
|
||||
// ======================
|
||||
String selectSql;
|
||||
String outerSelectSql;
|
||||
|
||||
if (searchColumns != null && !searchColumns.isEmpty()) {
|
||||
|
||||
searchColumns.forEach(this::validateIdentifier);
|
||||
|
||||
selectSql = searchColumns.stream()
|
||||
.map(c -> "t." + c)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
outerSelectSql = searchColumns.stream()
|
||||
.map(c -> "sub." + c)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
} else {
|
||||
|
||||
selectSql = "t.*";
|
||||
outerSelectSql = "sub.*";
|
||||
}
|
||||
|
||||
// ======================
|
||||
// WHERE 条件
|
||||
// ======================
|
||||
StringBuilder whereSql = new StringBuilder("1=1");
|
||||
|
||||
if (StringUtils.isNotBlank(searchCondition)) {
|
||||
whereSql.append(" AND ").append(searchCondition);
|
||||
}
|
||||
|
||||
// ======================
|
||||
// ORDER BY
|
||||
// ======================
|
||||
String orderSql;
|
||||
|
||||
if (StringUtils.isBlank(orderField)) {
|
||||
orderSql = "(SELECT NULL)";
|
||||
} else {
|
||||
orderSql = "t." + orderField;
|
||||
}
|
||||
|
||||
// ======================
|
||||
// SQLServer 2008 分页
|
||||
// ======================
|
||||
String sql =
|
||||
"SELECT " + outerSelectSql + " FROM (" +
|
||||
" SELECT ROW_NUMBER() OVER (ORDER BY " + orderSql + ") AS row_num, " +
|
||||
selectSql +
|
||||
" FROM " + viewName + " t " +
|
||||
" WHERE " + whereSql +
|
||||
") sub WHERE sub.row_num BETWEEN ? AND ?";
|
||||
|
||||
params.add(start);
|
||||
params.add(end);
|
||||
|
||||
validateFinalSql(sql);
|
||||
|
||||
List<Map<String, Object>> list =
|
||||
jdbcTemplate.queryForList(sql, params.toArray());
|
||||
|
||||
// ======================
|
||||
// COUNT
|
||||
// ======================
|
||||
String countSql =
|
||||
"SELECT COUNT(*) FROM " + viewName + " t WHERE " + whereSql;
|
||||
|
||||
Integer total = jdbcTemplate.queryForObject(
|
||||
countSql,
|
||||
Integer.class
|
||||
);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
result.put("total", total);
|
||||
result.put("rows", list);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void validateFinalSql(String sql) {
|
||||
if (StringUtils.isBlank(sql)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String lowerSql = sql.toLowerCase();
|
||||
|
||||
if (!lowerSql.trim().startsWith("select")) {
|
||||
throw new RuntimeException("只允许执行SELECT查询");
|
||||
}
|
||||
|
||||
Matcher matcher = ILLEGAL_SQL_PATTERN.matcher(sql);
|
||||
if (matcher.find()) {
|
||||
throw new RuntimeException("检测到非法SQL关键字: " + matcher.group());
|
||||
}
|
||||
|
||||
if (sql.contains(";")) {
|
||||
throw new RuntimeException("SQL包含非法字符 ';'");
|
||||
}
|
||||
|
||||
if (sql.contains("--") || sql.contains("/*") || sql.contains("*/")) {
|
||||
throw new RuntimeException("SQL包含非法注释");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入
|
||||
*/
|
||||
public int insert(String tableName, String pkColumn, Map<String, Object> data) {
|
||||
validateIdentifier(tableName);
|
||||
validateIdentifier(pkColumn);
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
throw new RuntimeException("插入数据不能为空");
|
||||
}
|
||||
|
||||
Object pkValue = getPkValue(data, pkColumn);
|
||||
|
||||
if(ObjectUtils.isEmpty(pkValue)){
|
||||
throw new RuntimeException("主键不能为空");
|
||||
}
|
||||
|
||||
String columns = data.keySet()
|
||||
.stream()
|
||||
.peek(this::validateIdentifier)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String placeholders = data.keySet()
|
||||
.stream()
|
||||
.map(k -> "?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String sql = "INSERT INTO " + tableName +
|
||||
" (" + columns + ") VALUES (" + placeholders + ")";
|
||||
|
||||
return jdbcTemplate.update(sql, data.values().toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入(支持多主键)
|
||||
*/
|
||||
public int insert(String tableName, Collection<String> pkColumns, Map<String, Object> data) {
|
||||
|
||||
validateIdentifier(tableName);
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
throw new RuntimeException("插入数据不能为空");
|
||||
}
|
||||
|
||||
if (pkColumns == null || pkColumns.isEmpty()) {
|
||||
throw new RuntimeException("主键不能为空");
|
||||
}
|
||||
|
||||
// 校验主键
|
||||
for (String pk : pkColumns) {
|
||||
validateIdentifier(pk);
|
||||
|
||||
Object pkValue = getPkValue(data, pk);
|
||||
if (ObjectUtils.isEmpty(pkValue)) {
|
||||
throw new RuntimeException("主键不能为空:" + pk);
|
||||
}
|
||||
}
|
||||
|
||||
String columns = data.keySet()
|
||||
.stream()
|
||||
.peek(this::validateIdentifier)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String placeholders = data.keySet()
|
||||
.stream()
|
||||
.map(k -> "?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String sql = "INSERT INTO " + tableName +
|
||||
" (" + columns + ") VALUES (" + placeholders + ")";
|
||||
|
||||
return jdbcTemplate.update(sql, data.values().toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
public int update(String tableName, String pkField, Map<String, Object> data) {
|
||||
validateIdentifier(tableName);
|
||||
validateIdentifier(pkField);
|
||||
|
||||
Object pkValue = getPkValue(data, pkField);
|
||||
|
||||
List<String> updateFields = getUpdateFields(data, pkField);
|
||||
|
||||
String sql = buildUpdateSql(tableName, pkField, updateFields);
|
||||
|
||||
Object[] params = buildParams(updateFields, data, pkValue);
|
||||
|
||||
return jdbcTemplate.update(sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新(支持多主键)
|
||||
*/
|
||||
public int update(String tableName, Collection<String> pkFields, Map<String, Object> data) {
|
||||
|
||||
validateIdentifier(tableName);
|
||||
|
||||
if (pkFields == null || pkFields.isEmpty()) {
|
||||
throw new RuntimeException("主键不能为空");
|
||||
}
|
||||
|
||||
// 校验主键
|
||||
Map<String, Object> pkValues = new LinkedHashMap<>();
|
||||
|
||||
for (String pk : pkFields) {
|
||||
validateIdentifier(pk);
|
||||
|
||||
Object value = getPkValue(data, pk);
|
||||
if (ObjectUtils.isEmpty(value)) {
|
||||
throw new RuntimeException("主键不能为空:" + pk);
|
||||
}
|
||||
|
||||
pkValues.put(pk, value);
|
||||
}
|
||||
|
||||
// 更新字段(排除主键)
|
||||
List<String> updateFields = data.keySet()
|
||||
.stream()
|
||||
.filter(k -> !pkFields.contains(k))
|
||||
.peek(this::validateIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (updateFields.isEmpty()) {
|
||||
throw new RuntimeException("没有可更新字段");
|
||||
}
|
||||
|
||||
// SET
|
||||
String setClause = updateFields.stream()
|
||||
.map(f -> f + "=?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
// WHERE
|
||||
String whereClause = pkFields.stream()
|
||||
.map(pk -> pk + "=?")
|
||||
.collect(Collectors.joining(" AND "));
|
||||
|
||||
String sql = "UPDATE " + tableName +
|
||||
" SET " + setClause +
|
||||
" WHERE " + whereClause;
|
||||
|
||||
// 参数
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
for (String field : updateFields) {
|
||||
params.add(data.get(field));
|
||||
}
|
||||
|
||||
params.addAll(pkValues.values());
|
||||
|
||||
return jdbcTemplate.update(sql, params.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
*/
|
||||
public int[] batchUpdate(String tableName, String pkField, List<Map<String, Object>> dataList) {
|
||||
if (dataList == null || dataList.isEmpty()) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
validateIdentifier(tableName);
|
||||
validateIdentifier(pkField);
|
||||
|
||||
List<String> updateFields = getUpdateFields(dataList.get(0), pkField);
|
||||
|
||||
String sql = buildUpdateSql(tableName, pkField, updateFields);
|
||||
|
||||
List<Object[]> batchArgs = new ArrayList<>();
|
||||
|
||||
for (Map<String, Object> data : dataList) {
|
||||
|
||||
Object pkValue = getPkValue(data, pkField);
|
||||
|
||||
batchArgs.add(buildParams(updateFields, data, pkValue));
|
||||
}
|
||||
|
||||
return jdbcTemplate.batchUpdate(sql, batchArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public int deleteById(String tableName, String pkField, Object pkValue) {
|
||||
validateIdentifier(tableName);
|
||||
validateIdentifier(pkField);
|
||||
|
||||
if (pkValue == null) {
|
||||
throw new IllegalArgumentException("主键不能为空: " + pkField);
|
||||
}
|
||||
|
||||
String sql = "DELETE FROM " + tableName +
|
||||
" WHERE " + pkField + "=?";
|
||||
|
||||
return jdbcTemplate.update(sql, pkValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除(支持多主键)
|
||||
*/
|
||||
public int delete(String tableName, Map<String, Object> pkValues) {
|
||||
|
||||
validateIdentifier(tableName);
|
||||
|
||||
if (pkValues == null || pkValues.isEmpty()) {
|
||||
throw new IllegalArgumentException("主键不能为空");
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : pkValues.entrySet()) {
|
||||
validateIdentifier(entry.getKey());
|
||||
|
||||
if (entry.getValue() == null) {
|
||||
throw new IllegalArgumentException("主键不能为空: " + entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
String whereClause = pkValues.keySet()
|
||||
.stream()
|
||||
.map(k -> k + "=?")
|
||||
.collect(Collectors.joining(" AND "));
|
||||
|
||||
String sql = "DELETE FROM " + tableName +
|
||||
" WHERE " + whereClause;
|
||||
|
||||
return jdbcTemplate.update(sql, pkValues.values().toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param dataList 数据列表
|
||||
* @return 每条记录影响行数
|
||||
*/
|
||||
public int[] batchInsert(String tableName, List<Map<String, Object>> dataList) {
|
||||
if (dataList == null || dataList.isEmpty()) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
validateIdentifier(tableName);
|
||||
|
||||
Map<String, Object> first = dataList.get(0);
|
||||
|
||||
List<String> fields = new ArrayList<>(first.keySet());
|
||||
|
||||
fields.forEach(this::validateIdentifier);
|
||||
|
||||
String columns = String.join(",", fields);
|
||||
|
||||
String placeholders = fields.stream()
|
||||
.map(f -> "?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String sql = "INSERT INTO " + tableName +
|
||||
" (" + columns + ") VALUES (" + placeholders + ")";
|
||||
|
||||
List<Object[]> batchArgs = new ArrayList<>();
|
||||
|
||||
for (Map<String, Object> data : dataList) {
|
||||
|
||||
Object[] params = new Object[fields.size()];
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
params[i] = data.get(fields.get(i));
|
||||
}
|
||||
|
||||
batchArgs.add(params);
|
||||
}
|
||||
|
||||
return jdbcTemplate.batchUpdate(sql, batchArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param pkField 主键字段
|
||||
* @param ids 主键集合
|
||||
* @return 删除行数
|
||||
*/
|
||||
public int batchDeleteByIds(String tableName, String pkField, List<Object> ids) {
|
||||
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
validateIdentifier(tableName);
|
||||
validateIdentifier(pkField);
|
||||
|
||||
String placeholders = ids.stream()
|
||||
.map(i -> "?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String sql = "DELETE FROM " + tableName +
|
||||
" WHERE " + pkField + " IN (" + placeholders + ")";
|
||||
|
||||
return jdbcTemplate.update(sql, ids.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行存储过程
|
||||
*
|
||||
* @param procName 存储过程名
|
||||
* @param params 存储过程参数,按顺序或按名字传入
|
||||
* @param returnsResultSet 是否返回结果集
|
||||
* @return 返回结果集或空
|
||||
*/
|
||||
public List<Map<String, Object>> executeProc(String procName, Map<String, Object> params, boolean returnsResultSet) {
|
||||
if (returnsResultSet) {
|
||||
// 如果返回结果集,使用 SimpleJdbcCall
|
||||
SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate)
|
||||
.withProcedureName(procName);
|
||||
Map<String, Object> result = call.execute(params);
|
||||
|
||||
// 获取第一个返回结果集,如果没有就返回空列表
|
||||
if (!result.isEmpty()) {
|
||||
Object first = result.values().iterator().next();
|
||||
if (first instanceof List) {
|
||||
return (List<Map<String, Object>>) first;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
// 不返回结果集,用 update 执行
|
||||
StringBuilder sqlBuilder = new StringBuilder("{call ").append(procName).append("(");
|
||||
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
sqlBuilder.append("?,");
|
||||
}
|
||||
sqlBuilder.deleteCharAt(sqlBuilder.length() - 1); // 删除最后一个逗号
|
||||
}
|
||||
|
||||
sqlBuilder.append(")}");
|
||||
|
||||
jdbcTemplate.update(sqlBuilder.toString(), params != null ? params.values().toArray() : new Object[]{});
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主键值
|
||||
*/
|
||||
private Object getPkValue(Map<String, Object> data, String pkField) {
|
||||
Object pkValue = data.get(pkField);
|
||||
|
||||
if (pkValue == null) {
|
||||
throw new RuntimeException("主键不能为空: " + pkField);
|
||||
}
|
||||
|
||||
return pkValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更新字段
|
||||
*/
|
||||
private List<String> getUpdateFields(Map<String, Object> data, String pkField) {
|
||||
|
||||
List<String> fields = data.entrySet()
|
||||
.stream()
|
||||
.filter(e -> !e.getKey().equalsIgnoreCase(pkField))
|
||||
.filter(e -> e.getValue() != null)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
throw new RuntimeException("没有可更新的字段");
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建更新SQL
|
||||
*/
|
||||
private String buildUpdateSql(String tableName, String pkField, List<String> fields) {
|
||||
|
||||
String setClause = fields.stream()
|
||||
.map(f -> f + "=?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
return "UPDATE " + tableName +
|
||||
" SET " + setClause +
|
||||
" WHERE " + pkField + "=?";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建参数
|
||||
*/
|
||||
private Object[] buildParams(List<String> fields, Map<String, Object> data, Object pkValue) {
|
||||
|
||||
Object[] params = new Object[fields.size() + 1];
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
|
||||
Object value = data.get(fields.get(i));
|
||||
|
||||
if (value == null) {
|
||||
throw new RuntimeException("字段值不能为空: " + fields.get(i));
|
||||
}
|
||||
|
||||
params[i] = value;
|
||||
}
|
||||
|
||||
params[fields.size()] = pkValue;
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class HttpUtils {
|
||||
|
||||
private static String request(String method, String url, String body, String contentType) {
|
||||
HttpURLConnection conn = null;
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
try {
|
||||
URL realUrl = new URL(url);
|
||||
conn = (HttpURLConnection) realUrl.openConnection();
|
||||
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(10000);
|
||||
|
||||
conn.setRequestProperty("accept", "*/*");
|
||||
conn.setRequestProperty("connection", "Keep-Alive");
|
||||
conn.setRequestProperty("user-agent", "Mozilla/5.0");
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
|
||||
conn.setDoInput(true);
|
||||
|
||||
// 👉 有 body 才写
|
||||
if (body != null && !body.isEmpty()) {
|
||||
conn.setDoOutput(true);
|
||||
try (OutputStreamWriter out =
|
||||
new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8)) {
|
||||
out.write(body);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
try (BufferedReader in = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("HTTP 请求失败", e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String sendGet(String url) {
|
||||
return request("GET", url, null, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
public static String sendGet(String url, String param) {
|
||||
String fullUrl = StringUtils.isNotBlank(param) ? url + "?" + param : url;
|
||||
return request("GET", fullUrl, null, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
public static String sendPost(String url, String param) {
|
||||
return request("POST", url, param, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
}
|
||||
|
||||
public static String sendPostJson(String url, Map<String, Object> params) {
|
||||
String json = JsonUtils.toJson(params);
|
||||
return request("POST", url, json, "application/json; charset=UTF-8");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class JsonUtils {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 判断字符串是否为有效的 JSON 格式
|
||||
public static boolean isTypeJSON(String msgText) {
|
||||
try {
|
||||
// 尝试将字符串解析为 JsonNode,如果能解析,说明是有效的 JSON
|
||||
JsonNode node = objectMapper.readTree(msgText);
|
||||
return node != null;
|
||||
} catch (Exception e) {
|
||||
return false; // 如果抛出异常,说明不是有效的 JSON
|
||||
}
|
||||
}
|
||||
|
||||
// 将 JSON 字符串转换为指定类型的 Java 对象
|
||||
public static <T> T toBean(String json, Class<T> clazz) {
|
||||
try {
|
||||
// 使用 ObjectMapper 将 JSON 字符串转换为指定类型的对象
|
||||
return objectMapper.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace(); // 打印异常信息
|
||||
return null; // 如果转换失败,返回 null
|
||||
}
|
||||
}
|
||||
|
||||
// 将对象转换为 JSON 字符串
|
||||
public static String toJson(Object obj) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(obj); // 将对象转换为 JSON 字符串
|
||||
} catch (JsonProcessingException e) {
|
||||
System.out.println(e.getMessage());
|
||||
return null; // 如果出现异常,返回 null
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> toMap(String json) {
|
||||
try {
|
||||
return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Data
|
||||
public class Result<T> {
|
||||
|
||||
private Integer code; // 状态码
|
||||
private String message; // 消息
|
||||
@NonNull
|
||||
private T data; // 数据,可空
|
||||
|
||||
// 成功和失败默认状态码
|
||||
public static final int SUCCESS_CODE = 200;
|
||||
|
||||
public static final int AUTH_FAIL_CODE = 401;
|
||||
|
||||
public static final int SYSTEM_ERROR_CODE = 500;
|
||||
public static final int BUSINESS_ERROR_CODE = 1000;
|
||||
|
||||
public Result() {
|
||||
}
|
||||
|
||||
public Result(Integer code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// ---------------- 成功 ----------------
|
||||
|
||||
public static <T> Result<T> ok() {
|
||||
return new Result<>(SUCCESS_CODE, "ok", null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(String message) {
|
||||
return new Result<>(SUCCESS_CODE, message, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(T data) {
|
||||
return new Result<>(SUCCESS_CODE, "ok", data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(String message, T data) {
|
||||
return new Result<>(SUCCESS_CODE, message, data);
|
||||
}
|
||||
|
||||
// ---------------- 失败 ----------------
|
||||
|
||||
public static <T> Result<T> fail() {
|
||||
return new Result<>(BUSINESS_ERROR_CODE, "fail", null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String message) {
|
||||
return new Result<>(BUSINESS_ERROR_CODE, message, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(Integer code, String message) {
|
||||
return new Result<>(code, message, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String message, T data) {
|
||||
return new Result<>(BUSINESS_ERROR_CODE, message, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import org.springframework.aop.framework.AopContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* spring工具类 方便在非spring管理环境中获取bean
|
||||
*
|
||||
* @author webserver
|
||||
*/
|
||||
@Component
|
||||
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
|
||||
{
|
||||
/** Spring应用上下文环境 */
|
||||
private static ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
|
||||
{
|
||||
SpringUtils.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
||||
{
|
||||
SpringUtils.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象
|
||||
*
|
||||
* @param name
|
||||
* @return Object 一个以所给名字注册的bean的实例
|
||||
* @throws BeansException
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) throws BeansException
|
||||
{
|
||||
return (T) beanFactory.getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为requiredType的对象
|
||||
*
|
||||
* @param clz
|
||||
* @return
|
||||
* @throws BeansException
|
||||
*
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clz) throws BeansException
|
||||
{
|
||||
T result = (T) beanFactory.getBean(clz);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
|
||||
*
|
||||
* @param name
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean containsBean(String name)
|
||||
{
|
||||
return beanFactory.containsBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
|
||||
*
|
||||
* @param name
|
||||
* @return boolean
|
||||
* @throws NoSuchBeanDefinitionException
|
||||
*
|
||||
*/
|
||||
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
|
||||
{
|
||||
return beanFactory.isSingleton(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return Class 注册对象的类型
|
||||
* @throws NoSuchBeanDefinitionException
|
||||
*
|
||||
*/
|
||||
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
|
||||
{
|
||||
return beanFactory.getType(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @throws NoSuchBeanDefinitionException
|
||||
*
|
||||
*/
|
||||
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
|
||||
{
|
||||
return beanFactory.getAliases(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取aop代理对象
|
||||
*
|
||||
* @param invoker
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getAopProxy(T invoker)
|
||||
{
|
||||
return (T) AopContext.currentProxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的环境配置,无配置返回null
|
||||
*
|
||||
* @return 当前的环境配置
|
||||
*/
|
||||
public static String[] getActiveProfiles()
|
||||
{
|
||||
return applicationContext.getEnvironment().getActiveProfiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的环境配置,当有多个环境配置时,只获取第一个
|
||||
*
|
||||
* @return 当前的环境配置
|
||||
*/
|
||||
public static String getActiveProfile()
|
||||
{
|
||||
final String[] activeProfiles = getActiveProfiles();
|
||||
return activeProfiles.length > 0 ? activeProfiles[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件中的值
|
||||
*
|
||||
* @param key 配置文件的key
|
||||
* @return 当前的配置文件的值
|
||||
*
|
||||
*/
|
||||
public static String getRequiredProperty(String key)
|
||||
{
|
||||
return applicationContext.getEnvironment().getRequiredProperty(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class TimeUtils {
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 获取当前时间(年月日时分秒)
|
||||
*
|
||||
* @return 当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
public static String getDateTime() {
|
||||
return LocalDateTime.now().format(DATE_TIME_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ValidationUtils {
|
||||
|
||||
public static boolean checkRequiredFields(Map<String, Object> body, String... requiredFields) {
|
||||
for (String field : requiredFields) {
|
||||
Object value = body.get(field);
|
||||
|
||||
if (value == null || value.toString().trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.g3soft.utils.jwt;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class JwtPayload {
|
||||
private String orgId;
|
||||
private String userId;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.g3soft.utils.jwt;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class JwtUtils {
|
||||
// 秘钥
|
||||
private static final String SECRET_KEY = "G3_SECRET_KEY";
|
||||
// 过期时间,1天。
|
||||
private static final Integer EXPIRE_TIME = 1;
|
||||
|
||||
// 创建 Token
|
||||
public static String createToken(JwtPayload jwtPayload) {
|
||||
long nowMillis = System.currentTimeMillis();
|
||||
Date now = new Date(nowMillis);
|
||||
|
||||
// 设置过期时间
|
||||
Date exp = new Date(nowMillis + EXPIRE_TIME * 24L * 60L * 60L * 1000L);
|
||||
return Jwts.builder()
|
||||
.setSubject("jwtToken")
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(exp)
|
||||
.claim("userId", jwtPayload.getUserId())
|
||||
.claim("orgId", jwtPayload.getOrgId())
|
||||
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
|
||||
.compact();
|
||||
}
|
||||
|
||||
// 解析 Token
|
||||
public static JwtPayload parseToken(String token){
|
||||
try {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(SECRET_KEY)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
JwtPayload jwtPayload = new JwtPayload();
|
||||
jwtPayload.setUserId(claims.get("userId", String.class));
|
||||
jwtPayload.setOrgId(claims.get("orgId", String.class));
|
||||
|
||||
return jwtPayload;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package cn.g3soft.utils.uniqueId.contract;
|
||||
|
||||
public interface IIdGenerator {
|
||||
long newLong() throws IdGeneratorException;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package cn.g3soft.utils.uniqueId.contract;
|
||||
|
||||
public interface ISnowWorker {
|
||||
long nextId() throws IdGeneratorException;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cn.g3soft.utils.uniqueId.contract;
|
||||
|
||||
public class IdGeneratorException extends RuntimeException {
|
||||
|
||||
public IdGeneratorException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IdGeneratorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IdGeneratorException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public IdGeneratorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IdGeneratorException(String msgFormat, Object... args) {
|
||||
super(String.format(msgFormat, args));
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package cn.g3soft.utils.uniqueId.contract;
|
||||
|
||||
public class IdGeneratorOptions {
|
||||
|
||||
/**
|
||||
* 雪花计算方法
|
||||
* (1-漂移算法|2-传统算法),默认1
|
||||
*/
|
||||
public short Method = 1;
|
||||
|
||||
/**
|
||||
* 基础时间(ms单位)
|
||||
* 不能超过当前系统时间
|
||||
*/
|
||||
public long BaseTime = 1582136402000L;
|
||||
|
||||
/**
|
||||
* 机器码
|
||||
* 必须由外部设定,最大值 2^WorkerIdBitLength-1
|
||||
*/
|
||||
public short WorkerId = 0;
|
||||
|
||||
/**
|
||||
* 机器码位长
|
||||
* 默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)
|
||||
*/
|
||||
public byte WorkerIdBitLength = 6;
|
||||
|
||||
/**
|
||||
* 序列数位长
|
||||
* 默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22)
|
||||
*/
|
||||
public byte SeqBitLength = 6;
|
||||
|
||||
/**
|
||||
* 最大序列数(含)
|
||||
* 设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1])
|
||||
*/
|
||||
public short MaxSeqNumber = 0;
|
||||
|
||||
/**
|
||||
* 最小序列数(含)
|
||||
* 默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号是0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位
|
||||
*/
|
||||
public short MinSeqNumber = 5;
|
||||
|
||||
/**
|
||||
* 最大漂移次数(含)
|
||||
* 默认2000,推荐范围500-10000(与计算能力有关)
|
||||
*/
|
||||
public short TopOverCostCount = 2000;
|
||||
|
||||
public IdGeneratorOptions() {
|
||||
|
||||
}
|
||||
|
||||
public IdGeneratorOptions(short workerId) {
|
||||
WorkerId = workerId;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.g3soft.utils.uniqueId.contract;
|
||||
/**
|
||||
* Id生成时回调参数
|
||||
*/
|
||||
public class OverCostActionArg {
|
||||
/**
|
||||
* 事件类型
|
||||
* 1-开始,2-结束,8-漂移
|
||||
*/
|
||||
public int ActionType = 0;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
public long TimeTick = 0;
|
||||
|
||||
/**
|
||||
* 机器码
|
||||
*/
|
||||
public short WorkerId = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public int OverCostCountInOneTerm = 0;
|
||||
|
||||
/**
|
||||
* 漂移期间生产ID个数
|
||||
*/
|
||||
public int GenCountInOneTerm = 0;
|
||||
|
||||
/**
|
||||
* 漂移周期
|
||||
*/
|
||||
public int TermIndex = 0;
|
||||
|
||||
public OverCostActionArg(short workerId, long timeTick, int actionType, int overCostCountInOneTerm, int genCountWhenOverCost, int index) {
|
||||
ActionType = actionType;
|
||||
TimeTick = timeTick;
|
||||
WorkerId = workerId;
|
||||
OverCostCountInOneTerm = overCostCountInOneTerm;
|
||||
GenCountInOneTerm = genCountWhenOverCost;
|
||||
TermIndex = index;
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package cn.g3soft.utils.uniqueId.core;
|
||||
|
||||
|
||||
import cn.g3soft.utils.uniqueId.contract.ISnowWorker;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorException;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.utils.uniqueId.contract.OverCostActionArg;
|
||||
|
||||
public class SnowWorkerM1 implements ISnowWorker {
|
||||
/**
|
||||
* 基础时间
|
||||
*/
|
||||
protected final long BaseTime;
|
||||
|
||||
/**
|
||||
* 机器码
|
||||
*/
|
||||
protected final short WorkerId;
|
||||
|
||||
/**
|
||||
* 机器码位长
|
||||
*/
|
||||
protected final byte WorkerIdBitLength;
|
||||
|
||||
/**
|
||||
* 自增序列数位长
|
||||
*/
|
||||
protected final byte SeqBitLength;
|
||||
|
||||
/**
|
||||
* 最大序列数(含)
|
||||
*/
|
||||
protected final int MaxSeqNumber;
|
||||
|
||||
/**
|
||||
* 最小序列数(含)
|
||||
*/
|
||||
protected final short MinSeqNumber;
|
||||
|
||||
/**
|
||||
* 最大漂移次数(含)
|
||||
*/
|
||||
protected final int TopOverCostCount;
|
||||
|
||||
protected final byte _TimestampShift;
|
||||
protected final static byte[] _SyncLock = new byte[0];
|
||||
|
||||
protected short _CurrentSeqNumber;
|
||||
protected long _LastTimeTick = 0;
|
||||
protected long _TurnBackTimeTick = 0;
|
||||
protected byte _TurnBackIndex = 0;
|
||||
|
||||
protected boolean _IsOverCost = false;
|
||||
protected int _OverCostCountInOneTerm = 0;
|
||||
protected int _GenCountInOneTerm = 0;
|
||||
protected int _TermIndex = 0;
|
||||
|
||||
public SnowWorkerM1(IdGeneratorOptions options) {
|
||||
BaseTime = options.BaseTime != 0 ? options.BaseTime : 1582136402000L;
|
||||
WorkerIdBitLength = options.WorkerIdBitLength == 0 ? 6 : options.WorkerIdBitLength;
|
||||
WorkerId = options.WorkerId;
|
||||
SeqBitLength = options.SeqBitLength == 0 ? 6 : options.SeqBitLength;
|
||||
MaxSeqNumber = options.MaxSeqNumber <= 0 ? (1 << SeqBitLength) - 1 : options.MaxSeqNumber;
|
||||
MinSeqNumber = options.MinSeqNumber;
|
||||
// TopOverCostCount = options.TopOverCostCount == 0 ? 2000 : options.TopOverCostCount;
|
||||
TopOverCostCount = options.TopOverCostCount;
|
||||
_TimestampShift = (byte) (WorkerIdBitLength + SeqBitLength);
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
}
|
||||
|
||||
private void DoGenIdAction(OverCostActionArg arg) {
|
||||
|
||||
}
|
||||
|
||||
private void BeginOverCostAction(long useTimeTick) {
|
||||
|
||||
}
|
||||
|
||||
private void EndOverCostAction(long useTimeTick) {
|
||||
// if (_TermIndex > 10000) {
|
||||
// _TermIndex = 0;
|
||||
// }
|
||||
}
|
||||
|
||||
private void BeginTurnBackAction(long useTimeTick) {
|
||||
|
||||
}
|
||||
|
||||
private void EndTurnBackAction(long useTimeTick) {
|
||||
|
||||
}
|
||||
|
||||
private long NextOverCostId() {
|
||||
long currentTimeTick = GetCurrentTimeTick();
|
||||
|
||||
if (currentTimeTick > _LastTimeTick) {
|
||||
EndOverCostAction(currentTimeTick);
|
||||
|
||||
_LastTimeTick = currentTimeTick;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = false;
|
||||
_OverCostCountInOneTerm = 0;
|
||||
_GenCountInOneTerm = 0;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
if (_OverCostCountInOneTerm >= TopOverCostCount) {
|
||||
EndOverCostAction(currentTimeTick);
|
||||
|
||||
_LastTimeTick = GetNextTimeTick();
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = false;
|
||||
_OverCostCountInOneTerm = 0;
|
||||
_GenCountInOneTerm = 0;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
if (_CurrentSeqNumber > MaxSeqNumber) {
|
||||
_LastTimeTick++;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = true;
|
||||
_OverCostCountInOneTerm++;
|
||||
_GenCountInOneTerm++;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
_GenCountInOneTerm++;
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
private long NextNormalId() throws IdGeneratorException {
|
||||
long currentTimeTick = GetCurrentTimeTick();
|
||||
|
||||
if (currentTimeTick < _LastTimeTick) {
|
||||
if (_TurnBackTimeTick < 1) {
|
||||
_TurnBackTimeTick = _LastTimeTick - 1;
|
||||
_TurnBackIndex++;
|
||||
// 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序
|
||||
// 支持4次回拨次序(避免回拨重叠导致ID重复),可无限次回拨(次序循环使用)。
|
||||
if (_TurnBackIndex > 4) {
|
||||
_TurnBackIndex = 1;
|
||||
}
|
||||
//BeginTurnBackAction(_TurnBackTimeTick);
|
||||
}
|
||||
|
||||
// try {
|
||||
// Thread.sleep(1);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
return CalcTurnBackId(_TurnBackTimeTick);
|
||||
}
|
||||
|
||||
// 时间追平时,_TurnBackTimeTick清零
|
||||
if (_TurnBackTimeTick > 0) {
|
||||
EndTurnBackAction(_TurnBackTimeTick);
|
||||
_TurnBackTimeTick = 0;
|
||||
}
|
||||
|
||||
if (currentTimeTick > _LastTimeTick) {
|
||||
_LastTimeTick = currentTimeTick;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
if (_CurrentSeqNumber > MaxSeqNumber) {
|
||||
BeginOverCostAction(currentTimeTick);
|
||||
|
||||
_TermIndex++;
|
||||
_LastTimeTick++;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = true;
|
||||
_OverCostCountInOneTerm = 1;
|
||||
_GenCountInOneTerm = 1;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
private long CalcId(long useTimeTick) {
|
||||
long result = ((useTimeTick << _TimestampShift) +
|
||||
((long) WorkerId << SeqBitLength) +
|
||||
(int) _CurrentSeqNumber);
|
||||
|
||||
_CurrentSeqNumber++;
|
||||
return result;
|
||||
}
|
||||
|
||||
private long CalcTurnBackId(long useTimeTick) {
|
||||
long result = ((useTimeTick << _TimestampShift) +
|
||||
((long) WorkerId << SeqBitLength) + _TurnBackIndex);
|
||||
|
||||
_TurnBackTimeTick--;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected long GetCurrentTimeTick() {
|
||||
long millis = System.currentTimeMillis();
|
||||
return millis - BaseTime;
|
||||
}
|
||||
|
||||
protected long GetNextTimeTick() {
|
||||
long tempTimeTicker = GetCurrentTimeTick();
|
||||
|
||||
while (tempTimeTicker <= _LastTimeTick) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
tempTimeTicker = GetCurrentTimeTick();
|
||||
}
|
||||
|
||||
return tempTimeTicker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextId() {
|
||||
synchronized (_SyncLock) {
|
||||
return _IsOverCost ? NextOverCostId() : NextNormalId();
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.g3soft.utils.uniqueId.core;
|
||||
|
||||
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorException;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions;
|
||||
|
||||
public class SnowWorkerM2 extends SnowWorkerM1 {
|
||||
public SnowWorkerM2(IdGeneratorOptions options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextId() {
|
||||
synchronized (_SyncLock) {
|
||||
long currentTimeTick = GetCurrentTimeTick();
|
||||
|
||||
if (_LastTimeTick == currentTimeTick) {
|
||||
if (_CurrentSeqNumber++ > MaxSeqNumber) {
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
currentTimeTick = GetNextTimeTick();
|
||||
}
|
||||
} else {
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
}
|
||||
|
||||
if (currentTimeTick < _LastTimeTick) {
|
||||
throw new IdGeneratorException("Time error for {0} milliseconds", _LastTimeTick - currentTimeTick);
|
||||
}
|
||||
|
||||
_LastTimeTick = currentTimeTick;
|
||||
long result = ((currentTimeTick << _TimestampShift) + ((long) WorkerId << SeqBitLength) + (int) _CurrentSeqNumber);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package cn.g3soft.utils.uniqueId.idgen;
|
||||
|
||||
|
||||
import cn.g3soft.utils.uniqueId.contract.IIdGenerator;
|
||||
import cn.g3soft.utils.uniqueId.contract.ISnowWorker;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorException;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.utils.uniqueId.core.SnowWorkerM1;
|
||||
import cn.g3soft.utils.uniqueId.core.SnowWorkerM2;
|
||||
|
||||
public class DefaultIdGenerator implements IIdGenerator {
|
||||
|
||||
private static ISnowWorker _SnowWorker = null;
|
||||
|
||||
public DefaultIdGenerator(IdGeneratorOptions options) throws IdGeneratorException {
|
||||
if (options == null) {
|
||||
throw new IdGeneratorException("options error.");
|
||||
}
|
||||
|
||||
// 1.BaseTime
|
||||
if (options.BaseTime < 315504000000L || options.BaseTime > System.currentTimeMillis()) {
|
||||
throw new IdGeneratorException("BaseTime error.");
|
||||
}
|
||||
|
||||
// 2.WorkerIdBitLength
|
||||
if (options.WorkerIdBitLength <= 0) {
|
||||
throw new IdGeneratorException("WorkerIdBitLength error.(range:[1, 21])");
|
||||
}
|
||||
if (options.WorkerIdBitLength + options.SeqBitLength > 22) {
|
||||
throw new IdGeneratorException("error:WorkerIdBitLength + SeqBitLength <= 22");
|
||||
}
|
||||
|
||||
// 3.WorkerId
|
||||
int maxWorkerIdNumber = (1 << options.WorkerIdBitLength) - 1;
|
||||
if (maxWorkerIdNumber == 0) {
|
||||
maxWorkerIdNumber = 63;
|
||||
}
|
||||
if (options.WorkerId < 0 || options.WorkerId > maxWorkerIdNumber) {
|
||||
throw new IdGeneratorException(
|
||||
"WorkerId error. (range:[0, " + (maxWorkerIdNumber > 0 ? maxWorkerIdNumber : 63) + "]");
|
||||
}
|
||||
|
||||
// 4.SeqBitLength
|
||||
if (options.SeqBitLength < 2 || options.SeqBitLength > 21) {
|
||||
throw new IdGeneratorException("SeqBitLength error. (range:[2, 21])");
|
||||
}
|
||||
|
||||
// 5.MaxSeqNumber
|
||||
int maxSeqNumber = (1 << options.SeqBitLength) - 1;
|
||||
if (maxSeqNumber == 0) {
|
||||
maxSeqNumber = 63;
|
||||
}
|
||||
if (options.MaxSeqNumber < 0 || options.MaxSeqNumber > maxSeqNumber) {
|
||||
throw new IdGeneratorException("MaxSeqNumber error. (range:[1, " + maxSeqNumber + "]");
|
||||
}
|
||||
|
||||
// 6.MinSeqNumber
|
||||
if (options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber) {
|
||||
throw new IdGeneratorException("MinSeqNumber error. (range:[5, " + maxSeqNumber + "]");
|
||||
}
|
||||
|
||||
// 7.TopOverCostCount
|
||||
if (options.TopOverCostCount < 0 || options.TopOverCostCount > 10000) {
|
||||
throw new IdGeneratorException("TopOverCostCount error. (range:[0, 10000]");
|
||||
}
|
||||
|
||||
switch (options.Method) {
|
||||
case 2:
|
||||
_SnowWorker = new SnowWorkerM2(options);
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
_SnowWorker = new SnowWorkerM1(options);
|
||||
break;
|
||||
}
|
||||
|
||||
if (options.Method == 1) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newLong() {
|
||||
return _SnowWorker.nextId();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package cn.g3soft.utils.uniqueId.idgen;
|
||||
|
||||
|
||||
import cn.g3soft.utils.uniqueId.contract.IIdGenerator;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorException;
|
||||
import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions;
|
||||
|
||||
public class UniqueId {
|
||||
private static IIdGenerator idGenInstance = null;
|
||||
|
||||
/**
|
||||
* 设置参数,建议程序初始化时执行一次
|
||||
*/
|
||||
public static void setIdGenerator(IdGeneratorOptions options) throws IdGeneratorException {
|
||||
idGenInstance = new DefaultIdGenerator(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成新的Id
|
||||
* 调用本方法前,请确保调用了 SetIdGenerator 方法做初始化。
|
||||
*
|
||||
*/
|
||||
public static long nextId() throws IdGeneratorException {
|
||||
// if (idGenInstance == null) {
|
||||
// idGenInstance = new DefaultIdGenerator(new IdGeneratorOptions((short) 1));
|
||||
// }
|
||||
|
||||
if (idGenInstance == null)
|
||||
throw new IdGeneratorException("Please initialize Yitter.IdGeneratorOptions first.");
|
||||
|
||||
return idGenInstance.newLong();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user