21 changed files with 1323 additions and 24 deletions
@ -0,0 +1,219 @@ |
|||||
|
package com.hxgk.lowcode.controller; |
||||
|
|
||||
|
import com.alibaba.fastjson2.JSON; |
||||
|
|
||||
|
import com.hxgk.lowcode.model.entity.AsfTableFillResult; |
||||
|
import com.hxgk.lowcode.model.entity.CustomerFormTableSingleFieldValue; |
||||
|
import com.hxgk.lowcode.model.entity.Option; |
||||
|
import com.hxgk.lowcode.model.entity.Tree; |
||||
|
import com.hxgk.lowcode.model.entity.requestParam.AsfDataTitlesByIdsQueryParam; |
||||
|
import com.hxgk.lowcode.model.entity.response.AsfDataTitlesByIdsResponseEntity; |
||||
|
import com.hxgk.lowcode.service.CustomerFormService; |
||||
|
import com.hxgk.lowcode.service.HrService; |
||||
|
import com.hxgk.lowcode.utils.JsonData; |
||||
|
import com.hxgk.lowcode.utils.sm4.ApiResponse; |
||||
|
import com.hxgk.lowcode.utils.sm4.DecryptedBody; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/* |
||||
|
* 关联表单 |
||||
|
* */ |
||||
|
@RestController |
||||
|
@RequestMapping("/lowCode") |
||||
|
public class AssociatedFormsSm4Controller { |
||||
|
|
||||
|
@Autowired |
||||
|
private CustomerFormService customerFormService; |
||||
|
|
||||
|
@Autowired |
||||
|
private AsfDataTitlesByIdsQueryParam asfDataTitlesByIdsQueryParam; |
||||
|
|
||||
|
@Autowired |
||||
|
private HrService hrService; |
||||
|
|
||||
|
public AssociatedFormsSm4Controller() { |
||||
|
} |
||||
|
|
||||
|
/*获取用户有权限的启用状态的表单列表树形结构*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getCustomerFormList") |
||||
|
public ApiResponse<Object> getCustomerFormList(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token) { |
||||
|
Tree tree = customerFormService.getCustomerFormList(key, token); |
||||
|
if(tree == null){ |
||||
|
return ApiResponse.error(2000, "登录状态已失效,请退出重新登录"); |
||||
|
} |
||||
|
return ApiResponse.success(tree); |
||||
|
} |
||||
|
|
||||
|
/*获取组织架构人员树*/ |
||||
|
@RequestMapping(value = "transfer/getOrgAndManTree") |
||||
|
public ApiResponse<Object> getOrgAndManTree(@RequestParam(value = "transferMark", required = false) String transferMark) { |
||||
|
|
||||
|
Tree tree = hrService.getOrgAndManTree(); |
||||
|
ApiResponse<Object> success = ApiResponse.success(tree); |
||||
|
return success; |
||||
|
} |
||||
|
|
||||
|
/*根据主表id拿到表单字段信息*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getFieldTree") |
||||
|
public ApiResponse<Object> getFieldTree(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
String cfid = (String) requestBody.get("cfid"); |
||||
|
|
||||
|
Tree tree = customerFormService.getFieldTree(key, token, cfid); |
||||
|
if(tree.getId() == null){ |
||||
|
return ApiResponse.error(107, "查无此表单"); |
||||
|
} else { |
||||
|
if("cfid为空".equals(tree.getId())){ |
||||
|
return ApiResponse.error(102, "cfid为空"); |
||||
|
} else if("masterTable为null".equals(tree.getId())){ |
||||
|
return ApiResponse.error(105, "masterTable为null"); |
||||
|
} |
||||
|
return ApiResponse.success(tree); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*获取系统角色列表*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getRoleList") |
||||
|
public ApiResponse<Object> getRoleList() { |
||||
|
Tree serviceRoleList = customerFormService.getRoleList(); |
||||
|
return ApiResponse.success(serviceRoleList); |
||||
|
} |
||||
|
|
||||
|
/*下拉,多选,单选,选项数据*/ |
||||
|
@PostMapping(value = "/AssociateOptionSettings/getOptions") |
||||
|
public ApiResponse<Object> getOptions() { |
||||
|
ArrayList<Option> options = new ArrayList<>(); |
||||
|
options.add(new Option("吃饭", "0")); |
||||
|
options.add(new Option("睡觉", "1")); |
||||
|
options.add(new Option("打豆豆", "2")); |
||||
|
return ApiResponse.success(options); |
||||
|
} |
||||
|
|
||||
|
/*实现获取选项效果*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getFieldRecord") |
||||
|
public ApiResponse<Object> getFieldRecord(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
String optionsValue3Field = (String) requestBody.get("optionsValue3Field"); |
||||
|
String[] optionsValue3FieldArray = optionsValue3Field.split(":"); |
||||
|
|
||||
|
ArrayList<CustomerFormTableSingleFieldValue> fieldList = customerFormService.getFieldRecord(key, token, optionsValue3FieldArray); |
||||
|
if(null == fieldList){ |
||||
|
return ApiResponse.error(403, "非法请求"); |
||||
|
} |
||||
|
return ApiResponse.success(fieldList); |
||||
|
} |
||||
|
|
||||
|
/*获取关联表单数据标题*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getAsfDataTitles") |
||||
|
public ApiResponse<Object> getAsfDataTitles(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
if(StringUtils.isBlank(key) || StringUtils.isBlank(token)){ |
||||
|
return ApiResponse.error(403, "非法请求"); |
||||
|
} |
||||
|
|
||||
|
String glbbddbd = (String) requestBody.get("glbbddbd"); |
||||
|
String formId = (String) requestBody.get("formId"); |
||||
|
String dataTitle = (String) requestBody.get("dataTitle"); |
||||
|
String rangeFormula = (String) requestBody.get("rangeFormula"); |
||||
|
String rangeString = (String) requestBody.get("rangeString"); |
||||
|
String hideFormula = (String) requestBody.get("hideFormula"); |
||||
|
String hideString = (String) requestBody.get("hideString"); |
||||
|
String masterOnField = (String) requestBody.get("masterOnField"); |
||||
|
String fillFieldsMaster = (String) requestBody.get("fillFieldsMaster"); |
||||
|
String fillFieldsChild = (String) requestBody.get("fillFieldsChild"); |
||||
|
System.out.println(fillFieldsChild); |
||||
|
|
||||
|
com.alibaba.fastjson2.JSONArray fillFieldsChildJsonObject = JSON.parseArray(fillFieldsChild); |
||||
|
ArrayList<CustomerFormTableSingleFieldValue> dataTitles = customerFormService.getAsfDataTitles( |
||||
|
key, token, glbbddbd, formId, dataTitle, rangeFormula, rangeString, |
||||
|
hideFormula, hideString, masterOnField, fillFieldsMaster, fillFieldsChildJsonObject |
||||
|
); |
||||
|
|
||||
|
return ApiResponse.success(dataTitles); |
||||
|
} |
||||
|
|
||||
|
/*列表页获取关联表单数据标题*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getAsfDataTitlesByIds") |
||||
|
public ApiResponse<Object> getAsfDataTitlesByIds(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody List<AsfDataTitlesByIdsQueryParam> param) { |
||||
|
if(StringUtils.isBlank(key) || StringUtils.isBlank(token)){ |
||||
|
return ApiResponse.error(403, "非法请求"); |
||||
|
} |
||||
|
|
||||
|
ArrayList<AsfDataTitlesByIdsResponseEntity> list = new ArrayList<>(); |
||||
|
for(AsfDataTitlesByIdsQueryParam a: param){ |
||||
|
ArrayList<CustomerFormTableSingleFieldValue> dataTitles = customerFormService.getAsfDataTitlesByIds(a.getFormId(), a.getAsfToSelectIds()); |
||||
|
|
||||
|
AsfDataTitlesByIdsResponseEntity asfDataTitlesByIdsResponseEntity = new AsfDataTitlesByIdsResponseEntity(); |
||||
|
asfDataTitlesByIdsResponseEntity.setField(a.getField()); |
||||
|
asfDataTitlesByIdsResponseEntity.setList(dataTitles); |
||||
|
list.add(asfDataTitlesByIdsResponseEntity); |
||||
|
} |
||||
|
|
||||
|
return ApiResponse.success(list); |
||||
|
} |
||||
|
|
||||
|
/*当前用户是否在指定的机构下或机构的祖先机构下*/ |
||||
|
@PostMapping(value = "/AssociatedForms/queryIfOrgOrPerson") |
||||
|
public ApiResponse<Object> queryIfOrgOrPerson(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
if(StringUtils.isBlank(key) || StringUtils.isBlank(token)){ |
||||
|
return ApiResponse.error(403, "非法请求"); |
||||
|
} |
||||
|
|
||||
|
String targetOrgOrPerson = (String) requestBody.get("left"); |
||||
|
String condition = (String) requestBody.get("operator"); |
||||
|
String currentUser = (String) requestBody.get("right"); |
||||
|
Boolean flag = customerFormService.queryIfOrgOrPersonContainsCurrentUser(key, token, targetOrgOrPerson, condition, currentUser); |
||||
|
|
||||
|
return ApiResponse.success(flag); |
||||
|
} |
||||
|
|
||||
|
@PostMapping(value = "/AssociatedForms/queryHideRoleCondition") |
||||
|
public ApiResponse<Object> queryHideRoleCondition(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
if(StringUtils.isBlank(key) || StringUtils.isBlank(token)){ |
||||
|
return ApiResponse.error(403, "非法请求"); |
||||
|
} |
||||
|
|
||||
|
String targetOrgOrPerson = (String) requestBody.get("left"); |
||||
|
String condition = (String) requestBody.get("operator"); |
||||
|
String currentUser = (String) requestBody.get("right"); |
||||
|
Boolean flag = customerFormService.queryHideRoleCondition(key, token, targetOrgOrPerson, condition, currentUser); |
||||
|
|
||||
|
return ApiResponse.success(flag); |
||||
|
} |
||||
|
|
||||
|
/*获取关联表单当前选择值的子表填充数据*/ |
||||
|
@PostMapping(value = "/AssociatedForms/getAsfTableFill") |
||||
|
public ApiResponse<Object> getAsfTableFill(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
if(StringUtils.isBlank(key) || StringUtils.isBlank(token)){ |
||||
|
return ApiResponse.error(403, "非法请求"); |
||||
|
} |
||||
|
|
||||
|
String asfFormId = (String) requestBody.get("asfFormId"); |
||||
|
String glbbddbd = (String) requestBody.get("glbbddbd"); |
||||
|
String currentVal = (String) requestBody.get("currentVal"); |
||||
|
String fillFieldsChild = (String) requestBody.get("fillFieldsChild"); |
||||
|
|
||||
|
com.alibaba.fastjson2.JSONArray fillFieldsChildJsonObject = JSON.parseArray(fillFieldsChild); |
||||
|
ArrayList<AsfTableFillResult> results = customerFormService.getAsfTableFill(key, token, asfFormId, glbbddbd, currentVal, fillFieldsChildJsonObject); |
||||
|
|
||||
|
return ApiResponse.success(results); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,178 @@ |
|||||
|
package com.hxgk.lowcode.controller; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
|
||||
|
import com.hxgk.lowcode.model.entity.UserByPort; |
||||
|
import com.hxgk.lowcode.model.entity.qywx.config.QyWxConfig; |
||||
|
import com.hxgk.lowcode.model.entity.qywx.security.pool.AuthenticationTokenPool; |
||||
|
import com.hxgk.lowcode.model.entity.qywx.security.pool.RedisPool; |
||||
|
import com.hxgk.lowcode.model.entity.response.QrCodeDetailsResponseEntity; |
||||
|
import com.hxgk.lowcode.service.QrCodeService; |
||||
|
import com.hxgk.lowcode.utils.JsonData; |
||||
|
import com.hxgk.lowcode.utils.Md5; |
||||
|
import com.hxgk.lowcode.utils.ObjectFormatUtil; |
||||
|
import com.hxgk.lowcode.utils.sm4.ApiResponse; |
||||
|
import com.hxgk.lowcode.utils.sm4.DecryptedBody; |
||||
|
import org.apache.commons.codec.digest.DigestUtils; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.awt.image.BufferedImage; |
||||
|
import java.util.*; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("/lowCode") |
||||
|
public class QrCodeSm4Controller { |
||||
|
|
||||
|
@Autowired |
||||
|
QrCodeService qrCodeService; |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisPool redisPool; |
||||
|
|
||||
|
public QrCodeSm4Controller() { |
||||
|
} |
||||
|
|
||||
|
@PostMapping(value = "/QrCode/getQrCodeImgInside") |
||||
|
public ApiResponse<Object> getQrCodeImgInside(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
String cfid = (String) requestBody.get("cfid"); |
||||
|
|
||||
|
if (StringUtils.isBlank(key) || StringUtils.isBlank(token) || StringUtils.isBlank(cfid)) { |
||||
|
return ApiResponse.error(2000, "请重新登录"); |
||||
|
} else { |
||||
|
String bufferedImage = qrCodeService.generateQrCodeByCfid(cfid); |
||||
|
return ApiResponse.success(bufferedImage); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PostMapping(value = "/QrCode/getDetailQrCodes") |
||||
|
public ApiResponse<Object> getDetailQrCodes(@RequestHeader(value = "User-Key") String key, |
||||
|
@RequestHeader(value = "User-Token") String token, |
||||
|
@DecryptedBody Map<String, Object> requestBody) { |
||||
|
String cfid = (String) requestBody.get("cfid"); |
||||
|
|
||||
|
if (StringUtils.isBlank(key) || StringUtils.isBlank(token) || StringUtils.isBlank(cfid)) { |
||||
|
return ApiResponse.error(2000, "请重新登录"); |
||||
|
} else { |
||||
|
// 注意类型转换:List<String> 而不是 ArrayList<String>
|
||||
|
ArrayList<String> ids = (ArrayList<String>) requestBody.get("idArray"); |
||||
|
LinkedHashMap<String, Object> settings = (LinkedHashMap<String, Object>) requestBody.get("settings"); |
||||
|
|
||||
|
LinkedHashMap<String, QrCodeDetailsResponseEntity> result = qrCodeService.getDetailQrCodes(cfid, ids, settings); |
||||
|
return ApiResponse.success(result); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 如果需要添加不需要加密的接口(如健康检查、测试接口等)
|
||||
|
@GetMapping(value = "/QrCode/health") |
||||
|
public ApiResponse<Object> healthCheck() { |
||||
|
// 不需要 @DecryptedBody,因为 GET 请求通常不需要加密
|
||||
|
return ApiResponse.success("服务正常"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 用于获取用于调用企业微信接口的签名 |
||||
|
* |
||||
|
* @param request |
||||
|
* @return |
||||
|
*/ |
||||
|
@GetMapping("/QrCode/QyWxSignature") |
||||
|
@ResponseBody |
||||
|
public JsonData getQyWXSignature(@RequestParam Map<String, String> map, HttpServletRequest request) { |
||||
|
// 随机字符串
|
||||
|
|
||||
|
|
||||
|
/*String token = request.getHeader("user-token"); |
||||
|
if (token == null) { |
||||
|
token = (String) request.getSession().getAttribute("userToken"); |
||||
|
}*/ |
||||
|
//UserByPort userToken = AuthenticationTokenPool.getUserToken(token);
|
||||
|
|
||||
|
|
||||
|
Map<String, String> result = new HashMap<>(); |
||||
|
// jsapi_ticket_app加密后
|
||||
|
String s = ""; |
||||
|
// jsapi_ticket_enterprises加密后
|
||||
|
String s1 = ""; |
||||
|
|
||||
|
// 时间戳
|
||||
|
long timestamp = 0; |
||||
|
|
||||
|
// 随机字符串
|
||||
|
String noncestr = ""; |
||||
|
|
||||
|
// 访问url
|
||||
|
String url = QyWxConfig.jsApiUrl; |
||||
|
|
||||
|
s = (String) redisPool.getRedisTemplateByDb(14).opsForHash().get("JavaLowCode_QyWxScanQrCodeSignature", "jsapi_ticket_app"); |
||||
|
if (s != null) { |
||||
|
s1 = (String) redisPool.getRedisTemplateByDb(14).opsForHash().get("JavaLowCode_QyWxScanQrCodeSignature", "jsapi_ticket_enterprises"); |
||||
|
timestamp = ObjectFormatUtil.toLong((String) Objects.requireNonNull(redisPool.getRedisTemplateByDb(14).opsForHash().get("JavaLowCode_QyWxScanQrCodeSignature", "timestamp"))); |
||||
|
url = (String) redisPool.getRedisTemplateByDb(14).opsForHash().get("JavaLowCode_QyWxScanQrCodeSignature", "url"); |
||||
|
noncestr = (String) redisPool.getRedisTemplateByDb(14).opsForHash().get("JavaLowCode_QyWxScanQrCodeSignature", "noncestr"); |
||||
|
} else { |
||||
|
// 时间戳
|
||||
|
timestamp = System.currentTimeMillis() / 1000; |
||||
|
//生成从ASCII 32到126组成的随机字符串
|
||||
|
// String noncestr = RandomStringUtils.randomAscii(10);
|
||||
|
// 时间戳字符串
|
||||
|
noncestr = String.valueOf(timestamp); |
||||
|
|
||||
|
// 获取企业的jsapi_ticket
|
||||
|
|
||||
|
// user-agent加密
|
||||
|
String header = request.getHeader("user-agent"); |
||||
|
String crypt = Md5.crypt(header); |
||||
|
|
||||
|
JSONObject ticketForApp = QyWxConfig.getQyWxJsApiTicketForApp(crypt); |
||||
|
JSONObject ticketForEnterprise = QyWxConfig.getQyWxJsApiTicketForEnterprise(crypt); |
||||
|
Integer errcodeForEnterprise = ticketForEnterprise.getInteger("errcode"); |
||||
|
Integer errcodeForApp = ticketForApp.getInteger("errcode"); |
||||
|
// 获取企业的jsapi_ticket
|
||||
|
String ticketForEnterprises = ""; |
||||
|
String ticketForEnterApps = ""; |
||||
|
if (Integer.compare(0, errcodeForEnterprise) == 0) { |
||||
|
ticketForEnterprises = ticketForEnterprise.getString("ticket"); |
||||
|
} |
||||
|
if (Integer.compare(0, errcodeForApp) == 0) { |
||||
|
ticketForEnterApps = ticketForApp.getString("ticket"); |
||||
|
} |
||||
|
Integer expires_in = ticketForEnterprise.getInteger("expires_in"); |
||||
|
|
||||
|
// 步骤1. 将这些参数拼接成字符串string1:
|
||||
|
String jsapi_ticket_app = "jsapi_ticket=" + ticketForEnterApps + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url; |
||||
|
String jsapi_ticket_enterprises = "jsapi_ticket=" + ticketForEnterprises + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url; |
||||
|
// 步骤2. 对string1进行sha1签名,得到signature
|
||||
|
s = DigestUtils.sha1Hex(jsapi_ticket_app); |
||||
|
s1 = DigestUtils.sha1Hex(jsapi_ticket_enterprises); |
||||
|
|
||||
|
redisPool.getRedisTemplateByDb(14).opsForHash().put("JavaLowCode_QyWxScanQrCodeSignature", "jsapi_ticket_app", s); |
||||
|
redisPool.getRedisTemplateByDb(14).opsForHash().put("JavaLowCode_QyWxScanQrCodeSignature", "jsapi_ticket_enterprises", s1); |
||||
|
redisPool.getRedisTemplateByDb(14).opsForHash().put("JavaLowCode_QyWxScanQrCodeSignature", "timestamp", String.valueOf(timestamp)); |
||||
|
redisPool.getRedisTemplateByDb(14).opsForHash().put("JavaLowCode_QyWxScanQrCodeSignature", "url", url); |
||||
|
redisPool.getRedisTemplateByDb(14).opsForHash().put("JavaLowCode_QyWxScanQrCodeSignature", "noncestr", noncestr); |
||||
|
|
||||
|
redisPool.getRedisTemplateByDb(14).expire("JavaLowCode_QyWxScanQrCodeSignature", expires_in, TimeUnit.SECONDS); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
result.put("timestamp", String.valueOf(timestamp)); |
||||
|
result.put("noncestr", noncestr); |
||||
|
result.put("jsapi_ticket_app", s); |
||||
|
result.put("jsapi_ticket_enterprises", s1); |
||||
|
result.put("corpid", QyWxConfig.corpid); |
||||
|
result.put("agentid", String.valueOf(QyWxConfig.AgentId)); |
||||
|
result.put("url", url); |
||||
|
|
||||
|
|
||||
|
return JsonData.buildSuccess(result); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class ApiResponse<T> { |
||||
|
private int code; |
||||
|
private String msg; |
||||
|
private T data; |
||||
|
|
||||
|
public ApiResponse() {} |
||||
|
|
||||
|
public ApiResponse(int code, String msg, T data) { |
||||
|
this.code = code; |
||||
|
this.msg = msg; |
||||
|
this.data = data; |
||||
|
} |
||||
|
|
||||
|
public static <T> ApiResponse<T> success(T data) { |
||||
|
if(null==data){ |
||||
|
return new ApiResponse<T>(0, "成功", (T) "成功,没有相关数据"); |
||||
|
}else{ |
||||
|
return new ApiResponse<>(0, "成功", data); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public static <T> ApiResponse<T> error(int code, String msg) { |
||||
|
return new ApiResponse<>(code, msg, null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.core.MethodParameter; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.bind.support.WebDataBinderFactory; |
||||
|
import org.springframework.web.context.request.NativeWebRequest; |
||||
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
||||
|
import org.springframework.web.method.support.ModelAndViewContainer; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class DecryptedArgumentResolver implements HandlerMethodArgumentResolver { |
||||
|
|
||||
|
private final ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
|
||||
|
@Override |
||||
|
public boolean supportsParameter(MethodParameter parameter) { |
||||
|
return parameter.hasParameterAnnotation(DecryptedBody.class); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, |
||||
|
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { |
||||
|
|
||||
|
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); |
||||
|
String decryptedBody = (String) request.getAttribute("decryptedBody"); |
||||
|
|
||||
|
if (decryptedBody != null) { |
||||
|
log.debug("准备解析解密后的数据: {}", decryptedBody); |
||||
|
return objectMapper.readValue(decryptedBody, parameter.getParameterType()); |
||||
|
} |
||||
|
|
||||
|
// 如果没有解密数据,尝试直接解析请求体(非加密模式)
|
||||
|
try { |
||||
|
return objectMapper.readValue(request.getInputStream(), parameter.getParameterType()); |
||||
|
} catch (Exception e) { |
||||
|
log.warn("无法解析请求体为 {},返回null", parameter.getParameterType().getSimpleName()); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
import java.lang.annotation.*; |
||||
|
|
||||
|
@Target(ElementType.PARAMETER) |
||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||
|
@Documented |
||||
|
public @interface DecryptedBody { |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class EncryptedRequest { |
||||
|
/** |
||||
|
* 加密的数据字段 |
||||
|
* 前端发送的是 "data" 字段,不是 "encryptedFile" |
||||
|
*/ |
||||
|
private String data; |
||||
|
|
||||
|
public EncryptedRequest() {} |
||||
|
|
||||
|
public EncryptedRequest(String data) { |
||||
|
this.data = data; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,113 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.core.MethodParameter; |
||||
|
import org.springframework.http.MediaType; |
||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||
|
import org.springframework.http.server.ServerHttpRequest; |
||||
|
import org.springframework.http.server.ServerHttpResponse; |
||||
|
import org.springframework.http.server.ServletServerHttpRequest; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.bind.annotation.ControllerAdvice; |
||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@ControllerAdvice |
||||
|
public class EncryptedResponseAdvice implements ResponseBodyAdvice<Object> { |
||||
|
|
||||
|
private final ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
|
||||
|
@Override |
||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, |
||||
|
Class<? extends HttpMessageConverter<?>> selectedConverterType, |
||||
|
ServerHttpRequest request, ServerHttpResponse response) { |
||||
|
|
||||
|
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); |
||||
|
|
||||
|
// 检查是否应该加密(可以根据路径或其他条件判断)
|
||||
|
boolean shouldEncrypt = shouldEncryptResponse(servletRequest); |
||||
|
if (!shouldEncrypt) { |
||||
|
return body; |
||||
|
} |
||||
|
|
||||
|
// 获取或生成 Auth-key
|
||||
|
String authKey = (String) servletRequest.getAttribute("responseAuthKey"); |
||||
|
if (authKey == null) { |
||||
|
authKey = RandomStringUtil.generateRandomString(16); |
||||
|
servletRequest.setAttribute("responseAuthKey", authKey); |
||||
|
} |
||||
|
|
||||
|
// 设置响应头
|
||||
|
response.getHeaders().set("Auth-key", authKey); |
||||
|
|
||||
|
try { |
||||
|
// 只有 ApiResponse 才需要加密
|
||||
|
if (body instanceof ApiResponse) { |
||||
|
ApiResponse<?> apiResponse = (ApiResponse<?>) body; |
||||
|
|
||||
|
// 如果数据不为空,进行加密
|
||||
|
if (apiResponse.getData() != null) { |
||||
|
// 将数据转换为 JSON 字符串
|
||||
|
String jsonData = objectMapper.writeValueAsString(apiResponse.getData()); |
||||
|
|
||||
|
log.debug("准备加密的数据: {}", jsonData); |
||||
|
log.debug("使用的 Auth-key: {}", authKey); |
||||
|
|
||||
|
// 使用 SM4 加密
|
||||
|
String encryptedData = SM4Utils.encrypt(jsonData, authKey); |
||||
|
|
||||
|
log.debug("加密结果长度: {}", encryptedData.length()); |
||||
|
|
||||
|
// 返回格式:{ code: 0, msg: "成功", data: "加密字符串" }
|
||||
|
return new ApiResponse<>(apiResponse.getCode(), apiResponse.getMsg(), encryptedData); |
||||
|
} else { |
||||
|
// 数据为空,直接返回原始响应
|
||||
|
return apiResponse; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 如果不是 ApiResponse,直接返回(如文件下载等)
|
||||
|
return body; |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("响应加密失败", e); |
||||
|
// 加密失败时返回错误响应
|
||||
|
return ApiResponse.error(500, "服务器加密错误: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断是否应该加密响应 |
||||
|
*/ |
||||
|
private boolean shouldEncryptResponse(HttpServletRequest request) { |
||||
|
String path = request.getRequestURI(); |
||||
|
|
||||
|
// 可以根据路径排除某些接口
|
||||
|
if (path.contains("/api/debug/") || |
||||
|
path.contains("/actuator/") || |
||||
|
path.contains("/swagger") || |
||||
|
path.contains("/v2/api-docs") || |
||||
|
path.endsWith(".html") || |
||||
|
path.endsWith(".js") || |
||||
|
path.endsWith(".css")) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 检查是否有特殊的请求头表示不需要加密
|
||||
|
if ("false".equalsIgnoreCase(request.getHeader("X-No-Encrypt"))) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
public enum ErrorCode { |
||||
|
SUCCESS(0, "成功"), |
||||
|
DATA_FORMAT_ERROR(100, "提交的数据格式错误!"), |
||||
|
DATA_EMPTY(101, "提交的数据不能为空!"), |
||||
|
KEY_PARAM_EMPTY(102, "关键参数不能为空!"), |
||||
|
DATA_EXISTS(103, "该数据已经存在!请不要重复添加"), |
||||
|
WRITE_FAILED(104, "数据写入失败!"), |
||||
|
QUERY_FAILED(105, "数据查询失败!"), |
||||
|
UPDATE_FAILED(106, "编辑失败!"), |
||||
|
NO_DATA(107, "未能查询到数据!"), |
||||
|
DELETE_FAILED(108, "删除失败"), |
||||
|
AUTH_ERROR(2000, "账户或密码错误!"); |
||||
|
|
||||
|
private final int code; |
||||
|
private final String message; |
||||
|
|
||||
|
ErrorCode(int code, String message) { |
||||
|
this.code = code; |
||||
|
this.message = message; |
||||
|
} |
||||
|
|
||||
|
public int getCode() { |
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
public String getMessage() { |
||||
|
return message; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import com.hxgk.lowcode.utils.sm4.ApiResponse; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice; |
||||
|
|
||||
|
@Slf4j |
||||
|
@RestControllerAdvice |
||||
|
public class GlobalExceptionHandler { |
||||
|
|
||||
|
@ExceptionHandler(Exception.class) |
||||
|
public ApiResponse<Object> handleException(Exception e) { |
||||
|
log.error("系统异常:", e); |
||||
|
return ApiResponse.error(500, "服务器错误"); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(IllegalArgumentException.class) |
||||
|
public ApiResponse<Object> handleIllegalArgumentException(IllegalArgumentException e) { |
||||
|
return ApiResponse.error(400, e.getMessage()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,162 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import com.alibaba.fastjson2.JSON; |
||||
|
import com.alibaba.fastjson2.JSONObject; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.http.HttpResponse; |
||||
|
import org.apache.http.client.config.RequestConfig; |
||||
|
import org.apache.http.client.methods.HttpPost; |
||||
|
import org.apache.http.entity.StringEntity; |
||||
|
import org.apache.http.impl.client.CloseableHttpClient; |
||||
|
import org.apache.http.impl.client.HttpClients; |
||||
|
import org.apache.http.util.EntityUtils; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class GoApiClient { |
||||
|
|
||||
|
private static final int TIMEOUT = 5000; |
||||
|
|
||||
|
/** |
||||
|
* 调用 Go 接口(使用加密通信) |
||||
|
* @param url Go接口地址 |
||||
|
* @param param 请求参数 |
||||
|
* @param userKey User-Key |
||||
|
* @param userToken User-Token |
||||
|
* @return 解密后的响应字符串 |
||||
|
* @throws IOException |
||||
|
*/ |
||||
|
public String callGoApi(String url, Object param, String userKey, String userToken) throws IOException { |
||||
|
// 1. 生成随机 Auth-key(16位)
|
||||
|
String authKey = RandomStringUtil.generateRandomString(16); |
||||
|
|
||||
|
// 2. 加密请求数据
|
||||
|
String encryptedData; |
||||
|
try { |
||||
|
String jsonParam = JSON.toJSONString(param); |
||||
|
encryptedData = SM4Utils.encrypt(jsonParam, authKey); |
||||
|
} catch (Exception e) { |
||||
|
log.error("加密请求数据失败", e); |
||||
|
throw new IOException("加密请求数据失败: " + e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
// 3. 构建加密请求体
|
||||
|
Map<String, String> encryptedBody = new HashMap<>(); |
||||
|
encryptedBody.put("data", encryptedData); // Go后端期望data字段
|
||||
|
String requestBody = JSON.toJSONString(encryptedBody); |
||||
|
|
||||
|
// 4. 发送 HTTP 请求
|
||||
|
CloseableHttpClient httpClient = null; |
||||
|
try { |
||||
|
RequestConfig requestConfig = RequestConfig.custom() |
||||
|
.setConnectTimeout(TIMEOUT) |
||||
|
.setSocketTimeout(TIMEOUT) |
||||
|
.setConnectionRequestTimeout(TIMEOUT) |
||||
|
.build(); |
||||
|
|
||||
|
httpClient = HttpClients.custom() |
||||
|
.setDefaultRequestConfig(requestConfig) |
||||
|
.build(); |
||||
|
|
||||
|
HttpPost httpPost = new HttpPost(url); |
||||
|
|
||||
|
// 设置请求头
|
||||
|
httpPost.setHeader("User-Key", userKey); |
||||
|
httpPost.setHeader("User-Token", userToken); |
||||
|
httpPost.setHeader("Auth-key", authKey); |
||||
|
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8"); |
||||
|
|
||||
|
// 设置请求体
|
||||
|
StringEntity entity = new StringEntity(requestBody, StandardCharsets.UTF_8); |
||||
|
httpPost.setEntity(entity); |
||||
|
|
||||
|
// 执行请求
|
||||
|
HttpResponse response = httpClient.execute(httpPost); |
||||
|
int statusCode = response.getStatusLine().getStatusCode(); |
||||
|
|
||||
|
if (statusCode != 200) { |
||||
|
String errorResponse = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); |
||||
|
throw new IOException("HTTP 请求失败,状态码: " + statusCode + ", 响应: " + errorResponse); |
||||
|
} |
||||
|
|
||||
|
// 获取响应体
|
||||
|
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); |
||||
|
|
||||
|
// 获取响应头中的 Auth-key
|
||||
|
org.apache.http.Header[] authKeyHeaders = response.getHeaders("Auth-key"); |
||||
|
String responseAuthKey = null; |
||||
|
if (authKeyHeaders != null && authKeyHeaders.length > 0) { |
||||
|
responseAuthKey = authKeyHeaders[0].getValue(); |
||||
|
} |
||||
|
|
||||
|
// 5. 解密响应数据
|
||||
|
if (responseAuthKey != null && !responseAuthKey.isEmpty()) { |
||||
|
return decryptGoResponse(responseBody, responseAuthKey); |
||||
|
} else { |
||||
|
log.warn("响应头中没有找到 Auth-key,返回原始响应"); |
||||
|
return responseBody; |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("HTTP 请求异常", e); |
||||
|
throw new IOException("HTTP 请求异常: " + e.getMessage(), e); |
||||
|
} finally { |
||||
|
if (httpClient != null) { |
||||
|
try { |
||||
|
httpClient.close(); |
||||
|
} catch (IOException e) { |
||||
|
log.error("关闭 HTTP 客户端失败", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解密 Go 后端的响应 |
||||
|
*/ |
||||
|
private String decryptGoResponse(String responseBody, String authKey) throws Exception { |
||||
|
try { |
||||
|
// 解析 Go 的响应
|
||||
|
JSONObject responseJson = JSON.parseObject(responseBody); |
||||
|
int code = responseJson.getIntValue("code"); |
||||
|
String msg = responseJson.getString("msg"); |
||||
|
String encryptedData = responseJson.getString("data"); |
||||
|
|
||||
|
if (encryptedData != null && !encryptedData.isEmpty()) { |
||||
|
// 解密数据
|
||||
|
String decryptedData = SM4Utils.decrypt(encryptedData, authKey); |
||||
|
|
||||
|
// 重新构建响应
|
||||
|
JSONObject result = new JSONObject(); |
||||
|
result.put("code", code); |
||||
|
result.put("msg", msg); |
||||
|
|
||||
|
try { |
||||
|
// 尝试将解密后的字符串解析为 JSON 对象
|
||||
|
Object dataObj = JSON.parse(decryptedData); |
||||
|
result.put("data", dataObj); |
||||
|
} catch (Exception e) { |
||||
|
// 如果解析失败,直接使用字符串
|
||||
|
result.put("data", decryptedData); |
||||
|
} |
||||
|
|
||||
|
return result.toJSONString(); |
||||
|
} else { |
||||
|
return responseBody; |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("解密 Go 响应数据失败", e); |
||||
|
throw new Exception("解密 Go 响应数据失败: " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
import org.apache.commons.codec.binary.Hex; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.security.SecureRandom; |
||||
|
import java.util.Random; |
||||
|
|
||||
|
@Component |
||||
|
public class RandomStringUtil { |
||||
|
|
||||
|
private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
||||
|
private static final SecureRandom SECURE_RANDOM = new SecureRandom(); |
||||
|
|
||||
|
/** |
||||
|
* 生成指定长度的随机字符串 |
||||
|
*/ |
||||
|
public static String generateRandomString(int length) { |
||||
|
StringBuilder sb = new StringBuilder(length); |
||||
|
for (int i = 0; i < length; i++) { |
||||
|
int index = SECURE_RANDOM.nextInt(CHARACTERS.length()); |
||||
|
sb.append(CHARACTERS.charAt(index)); |
||||
|
} |
||||
|
return sb.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取UUID |
||||
|
*/ |
||||
|
public static String generateUUID() { |
||||
|
byte[] randomBytes = new byte[16]; |
||||
|
SECURE_RANDOM.nextBytes(randomBytes); |
||||
|
randomBytes[6] &= 0x0f; /* clear version */ |
||||
|
randomBytes[6] |= 0x40; /* set to version 4 */ |
||||
|
randomBytes[8] &= 0x3f; /* clear variant */ |
||||
|
randomBytes[8] |= 0x80; /* set to IETF variant */ |
||||
|
|
||||
|
char[] uuidChars = Hex.encodeHex(randomBytes, false); |
||||
|
StringBuilder uuid = new StringBuilder(36); |
||||
|
for (int i = 0; i < uuidChars.length; i++) { |
||||
|
if (i == 8 || i == 12 || i == 16 || i == 20) { |
||||
|
uuid.append('-'); |
||||
|
} |
||||
|
uuid.append(uuidChars[i]); |
||||
|
} |
||||
|
return uuid.toString(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
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.BufferedReader; |
||||
|
import java.io.PrintWriter; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class SM4Interceptor implements HandlerInterceptor { |
||||
|
|
||||
|
private final ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
|
||||
|
@Override |
||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
||||
|
String authKey = request.getHeader("Auth-key"); |
||||
|
String contentType = request.getHeader("Content-Type"); |
||||
|
|
||||
|
log.debug("请求拦截 - Auth-key: {}, Content-Type: {}", authKey, contentType); |
||||
|
|
||||
|
// 如果是JSON请求且需要解密
|
||||
|
if (contentType != null && contentType.contains("application/json") && authKey != null) { |
||||
|
try { |
||||
|
// 读取请求体
|
||||
|
StringBuilder requestBody = new StringBuilder(); |
||||
|
try (BufferedReader reader = request.getReader()) { |
||||
|
String line; |
||||
|
while ((line = reader.readLine()) != null) { |
||||
|
requestBody.append(line); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (requestBody.length() > 0) { |
||||
|
String requestBodyStr = requestBody.toString(); |
||||
|
//log.debug("原始请求体: {}", requestBodyStr);
|
||||
|
|
||||
|
// 解析加密请求
|
||||
|
EncryptedRequest encryptedRequest = objectMapper.readValue(requestBodyStr, EncryptedRequest.class); |
||||
|
|
||||
|
if (encryptedRequest.getData() != null && !encryptedRequest.getData().isEmpty()) { |
||||
|
//log.debug("获取到加密数据: {}", encryptedRequest.getData().substring(0, Math.min(50, encryptedRequest.getData().length())) + "...");
|
||||
|
|
||||
|
// 解密数据
|
||||
|
String decryptedData = SM4Utils.decrypt(encryptedRequest.getData(), authKey); |
||||
|
//log.debug("解密后的数据: {}", decryptedData);
|
||||
|
|
||||
|
// 将解密后的数据重新放入请求体
|
||||
|
request.setAttribute("decryptedBody", decryptedData); |
||||
|
|
||||
|
// 记录解密成功
|
||||
|
//log.info("请求解密成功,Auth-key: {}", authKey);
|
||||
|
} else { |
||||
|
log.warn("加密数据为空,跳过解密"); |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求解密失败", e); |
||||
|
response.setStatus(HttpServletResponse.SC_BAD_REQUEST); |
||||
|
response.setContentType("application/json;charset=UTF-8"); |
||||
|
|
||||
|
ApiResponse<Object> errorResponse = ApiResponse.error(100, "提交的数据格式错误!"); |
||||
|
PrintWriter writer = response.getWriter(); |
||||
|
writer.write(objectMapper.writeValueAsString(errorResponse)); |
||||
|
writer.flush(); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, |
||||
|
ModelAndView modelAndView) throws Exception { |
||||
|
// 生成新的随机密钥
|
||||
|
String newAuthKey = RandomStringUtil.generateRandomString(16); |
||||
|
response.setHeader("Auth-key", newAuthKey); |
||||
|
request.setAttribute("responseAuthKey", newAuthKey); |
||||
|
|
||||
|
//log.debug("生成新的 Auth-key: {}", newAuthKey);
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, |
||||
|
Exception ex) throws Exception { |
||||
|
// 清理
|
||||
|
request.removeAttribute("decryptedBody"); |
||||
|
request.removeAttribute("responseAuthKey"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,218 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import org.bouncycastle.crypto.engines.SM4Engine; |
||||
|
import org.bouncycastle.crypto.modes.CBCBlockCipher; |
||||
|
import org.bouncycastle.crypto.paddings.PKCS7Padding; |
||||
|
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; |
||||
|
import org.bouncycastle.crypto.params.KeyParameter; |
||||
|
import org.bouncycastle.crypto.params.ParametersWithIV; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.Base64; |
||||
|
|
||||
|
@Component |
||||
|
public class SM4Utils { |
||||
|
|
||||
|
// Go 后端的 Sm4Key - 用作加密密钥
|
||||
|
@Value("${sm4.key:hengxingaokeApp1}") |
||||
|
private String sm4Key; |
||||
|
|
||||
|
// Go 后端的 Sm4Token - 默认用作 IV
|
||||
|
@Value("${sm4.token:04TzMuvkHm_EZnHm}") |
||||
|
private String sm4Token; |
||||
|
|
||||
|
// 前端使用的 systemKey(仅用于与前端对比验证)
|
||||
|
@Value("${sm4.system-key:appKeyQinDongV01}") |
||||
|
private String systemKey; |
||||
|
|
||||
|
private static String SM4_KEY_PROCESSED; |
||||
|
private static String SM4_TOKEN_PROCESSED; |
||||
|
private static String SYSTEM_KEY_HEX; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
// Go 的方式:处理为16字节(不是转16进制!)
|
||||
|
SM4_KEY_PROCESSED = processString(sm4Key, 16, "s"); |
||||
|
SM4_TOKEN_PROCESSED = processString(sm4Token, 16, "s"); |
||||
|
|
||||
|
// 前端的 systemKey 转16进制(仅用于验证)
|
||||
|
SYSTEM_KEY_HEX = stringToHex(systemKey); |
||||
|
|
||||
|
/*System.out.println("=== SM4Utils 初始化 ==="); |
||||
|
System.out.println("Go 的 Sm4Key: " + sm4Key); |
||||
|
System.out.println("Go 的 Sm4Key(处理后16字节): " + SM4_KEY_PROCESSED); |
||||
|
System.out.println("Go 的 Sm4Token: " + sm4Token); |
||||
|
System.out.println("Go 的 Sm4Token(处理后16字节): " + SM4_TOKEN_PROCESSED); |
||||
|
System.out.println("前端的 VITE_APP_SYSTEM_KEY: " + systemKey); |
||||
|
System.out.println("前端的 systemKey(hex): " + SYSTEM_KEY_HEX); |
||||
|
System.out.println("注意:Java 应该使用 Go 的 Sm4Key,而不是前端的 systemKey!");*/ |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理字符串为指定长度(与 Go 的 ProcessString 完全一致) |
||||
|
* 超过截断,不足用 pad 补全 |
||||
|
*/ |
||||
|
private static String processString(String s, int length, String pad) { |
||||
|
// 如果字符串长度已经符合要求,直接返回
|
||||
|
if (s.length() == length) { |
||||
|
return s; |
||||
|
} |
||||
|
// 如果字符串长度超过了要求,截取指定长度的部分
|
||||
|
if (s.length() > length) { |
||||
|
return s.substring(0, length); |
||||
|
} |
||||
|
// 如果字符串长度不足,使用pad字符串进行补充
|
||||
|
StringBuilder sb = new StringBuilder(s); |
||||
|
while (sb.length() < length) { |
||||
|
sb.append(pad); |
||||
|
} |
||||
|
return sb.substring(0, length); // 确保不会超过长度
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 字符串转16进制(仅用于与前端对比) |
||||
|
*/ |
||||
|
private static String stringToHex(String str) { |
||||
|
byte[] bytes = str.getBytes(StandardCharsets.UTF_8); |
||||
|
StringBuilder hex = new StringBuilder(); |
||||
|
for (byte b : bytes) { |
||||
|
hex.append(String.format("%02x", b)); |
||||
|
} |
||||
|
return hex.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为32位16进制字符串 |
||||
|
*/ |
||||
|
private static boolean isValidHex32(String str) { |
||||
|
if (str == null || str.length() != 32) return false; |
||||
|
return str.matches("[0-9a-fA-F]{32}"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 加密方法(与 Go 的 SM4Encrypt 完全一致) |
||||
|
* @param data 要加密的字符串数据 |
||||
|
* @param customKey 自定义密钥(Auth-key),如果提供则用作 IV |
||||
|
* @return 加密后的16进制字符串 |
||||
|
*/ |
||||
|
public static String encrypt(String data, String customKey) throws Exception { |
||||
|
// 1. 确定加密密钥(使用 Go 的 Sm4Key)
|
||||
|
String keyStr = SM4_KEY_PROCESSED; |
||||
|
byte[] keyBytes = keyStr.getBytes(StandardCharsets.UTF_8); |
||||
|
|
||||
|
// 2. 确定 IV(如果有 customKey 则用 customKey,否则用 Sm4Token)
|
||||
|
String ivStr = SM4_TOKEN_PROCESSED; |
||||
|
if (customKey != null && !customKey.isEmpty()) { |
||||
|
// Go 的逻辑:如果有 customKey,处理为16字节
|
||||
|
ivStr = processString(customKey, 16, "s"); |
||||
|
} |
||||
|
byte[] ivBytes = ivStr.getBytes(StandardCharsets.UTF_8); |
||||
|
|
||||
|
/*System.out.println("=== Java 加密参数 ==="); |
||||
|
System.out.println("加密密钥: " + keyStr + " (UTF-8字节: " + bytesToHex(keyBytes) + ")"); |
||||
|
System.out.println("IV: " + ivStr + " (UTF-8字节: " + bytesToHex(ivBytes) + ")"); |
||||
|
System.out.println("原始数据: " + data);*/ |
||||
|
|
||||
|
// 3. 使用 BouncyCastle 进行 SM4-CBC 加密
|
||||
|
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher( |
||||
|
new CBCBlockCipher(new SM4Engine()), new PKCS7Padding() |
||||
|
); |
||||
|
|
||||
|
cipher.init(true, new ParametersWithIV(new KeyParameter(keyBytes), ivBytes)); |
||||
|
|
||||
|
byte[] inputBytes = data.getBytes(StandardCharsets.UTF_8); |
||||
|
byte[] outputBytes = new byte[cipher.getOutputSize(inputBytes.length)]; |
||||
|
|
||||
|
int length1 = cipher.processBytes(inputBytes, 0, inputBytes.length, outputBytes, 0); |
||||
|
int length2 = cipher.doFinal(outputBytes, length1); |
||||
|
|
||||
|
byte[] encryptedBytes = new byte[length1 + length2]; |
||||
|
System.arraycopy(outputBytes, 0, encryptedBytes, 0, encryptedBytes.length); |
||||
|
|
||||
|
String result = bytesToHex(encryptedBytes); |
||||
|
//System.out.println("加密结果(hex): " + result);
|
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解密方法(与 Go 的 SM4Decrypt 完全一致) |
||||
|
* @param encryptedHex 加密后的16进制字符串 |
||||
|
* @param customKey 自定义密钥(Auth-key),如果提供则用作 IV |
||||
|
* @return 解密后的字符串 |
||||
|
*/ |
||||
|
public static String decrypt(String encryptedHex, String customKey) throws Exception { |
||||
|
// 1. 确定加密密钥(使用 Go 的 Sm4Key)
|
||||
|
String keyStr = SM4_KEY_PROCESSED; |
||||
|
byte[] keyBytes = keyStr.getBytes(StandardCharsets.UTF_8); |
||||
|
|
||||
|
// 2. 确定 IV(如果有 customKey 则用 customKey,否则用 Sm4Token)
|
||||
|
String ivStr = SM4_TOKEN_PROCESSED; |
||||
|
if (customKey != null && !customKey.isEmpty()) { |
||||
|
// Go 的逻辑:如果有 customKey,处理为16字节
|
||||
|
ivStr = processString(customKey, 16, "s"); |
||||
|
} |
||||
|
byte[] ivBytes = ivStr.getBytes(StandardCharsets.UTF_8); |
||||
|
|
||||
|
/*System.out.println("=== Java 解密参数 ==="); |
||||
|
System.out.println("解密密钥: " + keyStr + " (UTF-8字节: " + bytesToHex(keyBytes) + ")"); |
||||
|
System.out.println("IV: " + ivStr + " (UTF-8字节: " + bytesToHex(ivBytes) + ")"); |
||||
|
System.out.println("加密数据(hex): " + encryptedHex);*/ |
||||
|
|
||||
|
// 3. 转换加密数据
|
||||
|
byte[] encryptedBytes = hexToBytes(encryptedHex); |
||||
|
|
||||
|
// 4. 使用 BouncyCastle 进行 SM4-CBC 解密
|
||||
|
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher( |
||||
|
new CBCBlockCipher(new SM4Engine()), new PKCS7Padding() |
||||
|
); |
||||
|
|
||||
|
cipher.init(false, new ParametersWithIV(new KeyParameter(keyBytes), ivBytes)); |
||||
|
|
||||
|
byte[] outputBytes = new byte[cipher.getOutputSize(encryptedBytes.length)]; |
||||
|
|
||||
|
int length1 = cipher.processBytes(encryptedBytes, 0, encryptedBytes.length, outputBytes, 0); |
||||
|
int length2 = cipher.doFinal(outputBytes, length1); |
||||
|
|
||||
|
byte[] decryptedBytes = new byte[length1 + length2]; |
||||
|
System.arraycopy(outputBytes, 0, decryptedBytes, 0, decryptedBytes.length); |
||||
|
|
||||
|
String result = new String(decryptedBytes, StandardCharsets.UTF_8); |
||||
|
//System.out.println("解密结果: " + result);
|
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 字节数组转16进制字符串 |
||||
|
*/ |
||||
|
private static String bytesToHex(byte[] bytes) { |
||||
|
StringBuilder hex = new StringBuilder(); |
||||
|
for (byte b : bytes) { |
||||
|
hex.append(String.format("%02x", b)); |
||||
|
} |
||||
|
return hex.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 16进制字符串转字节数组 |
||||
|
*/ |
||||
|
private static byte[] hexToBytes(String hex) { |
||||
|
if (hex.length() % 2 != 0) { |
||||
|
throw new IllegalArgumentException("Hex string length must be even"); |
||||
|
} |
||||
|
byte[] bytes = new byte[hex.length() / 2]; |
||||
|
for (int i = 0; i < bytes.length; i++) { |
||||
|
int index = i * 2; |
||||
|
bytes[i] = (byte) Integer.parseInt(hex.substring(index, index + 2), 16); |
||||
|
} |
||||
|
return bytes; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
package com.hxgk.lowcode.utils.sm4; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Configuration |
||||
|
public class WebConfig implements WebMvcConfigurer { |
||||
|
|
||||
|
@Autowired |
||||
|
private SM4Interceptor sm4Interceptor; |
||||
|
|
||||
|
@Autowired |
||||
|
private DecryptedArgumentResolver decryptedArgumentResolver; |
||||
|
|
||||
|
@Override |
||||
|
public void addInterceptors(InterceptorRegistry registry) { |
||||
|
registry.addInterceptor(sm4Interceptor) |
||||
|
.addPathPatterns("/**") |
||||
|
.excludePathPatterns( |
||||
|
"/error", |
||||
|
"/api/sm4-test/**", // 排除测试接口
|
||||
|
"/actuator/**", // 排除监控接口
|
||||
|
"/swagger-ui/**", // 排除 Swagger
|
||||
|
"/v2/api-docs", // 排除 API 文档
|
||||
|
"/webjars/**" // 排除静态资源
|
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { |
||||
|
resolvers.add(decryptedArgumentResolver); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue