This commit is contained in:
oneao committed 2026-03-19 17:28:47 +08:00
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"
);
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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 识别失败,请联系管理员");
}
}
}
@@ -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();
}
}
@@ -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;
}
}
}
@@ -0,0 +1,5 @@
package cn.g3soft.utils.uniqueId.contract;
public interface IIdGenerator {
long newLong() throws IdGeneratorException;
}
@@ -0,0 +1,5 @@
package cn.g3soft.utils.uniqueId.contract;
public interface ISnowWorker {
long nextId() throws IdGeneratorException;
}
@@ -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));
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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();
}
}