Compare commits
40 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9d2ad81ef7 | 3 months ago |
|
|
158f523314 | 3 months ago |
|
|
310fffb35f | 3 months ago |
|
|
9b023a659b | 3 months ago |
|
|
369d1fff5d | 3 months ago |
|
|
3334020e20 | 3 months ago |
|
|
2a2bd24e45 | 4 months ago |
|
|
312408f40c | 4 months ago |
|
|
4aa3778b6a | 4 months ago |
|
|
c4b4fc152b | 5 months ago |
|
|
6312083a25 | 5 months ago |
|
|
03853fa27b | 5 months ago |
|
|
1d4fb1d390 | 5 months ago |
|
|
b5cf2a962d | 5 months ago |
|
|
954a476f9a | 5 months ago |
|
|
a2888ec11d | 5 months ago |
|
|
0e87acf58f | 5 months ago |
|
|
c029f2d6ec | 6 months ago |
|
|
49d06c3794 | 6 months ago |
|
|
242a5b8e25 | 6 months ago |
|
|
382e0e1fb2 | 6 months ago |
|
|
a2e0d4406d | 6 months ago |
|
|
f85a1bd0b6 | 6 months ago |
|
|
95f3d45114 | 6 months ago |
|
|
3f36cc97f0 | 6 months ago |
|
|
38105986a5 | 6 months ago |
|
|
4e0ac51f75 | 6 months ago |
|
|
b07479e7bb | 6 months ago |
|
|
e57de79a3c | 6 months ago |
|
|
d74c1be5d0 | 6 months ago |
|
|
01ad07ca6f | 6 months ago |
|
|
9c2b6a98cf | 6 months ago |
|
|
fe457346c4 | 7 months ago |
|
|
4ca207789f | 7 months ago |
|
|
71929daf61 | 7 months ago |
|
|
f4e73e336c | 7 months ago |
|
|
7649dec327 | 7 months ago |
|
|
f657cff27b | 7 months ago |
|
|
56abaf13f3 | 7 months ago |
|
|
02fafe3c79 | 7 months ago |
63 changed files with 2160 additions and 341 deletions
@ -0,0 +1,106 @@ |
|||
package com.zdxt.common.util; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.alibaba.fastjson.JSONArray; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* Fastjson2 解析工具。 |
|||
* <p> |
|||
* 兼容包对 String 走 JSONReaderUTF16,在部分 JDK(尤其 Oracle/麒麟 aarch64)上会误报 |
|||
* {@code invalid escape character EOI}。统一走 UTF-8 字节解析规避该问题,失败时再用 Jackson 兜底。 |
|||
*/ |
|||
public final class FastJsonUtil { |
|||
|
|||
private static final ObjectMapper JACKSON = new ObjectMapper(); |
|||
|
|||
private FastJsonUtil() { |
|||
} |
|||
|
|||
public static JSONObject parseObject(String text) { |
|||
if (text == null) { |
|||
return null; |
|||
} |
|||
String trimmed = text.trim(); |
|||
if (trimmed.isEmpty() || "null".equalsIgnoreCase(trimmed)) { |
|||
return null; |
|||
} |
|||
try { |
|||
// 关键 UTF-8 字节路径,避开 JSONReaderUTF16 的 EOI 误判
|
|||
return JSON.parseObject(trimmed.getBytes(StandardCharsets.UTF_8)); |
|||
} catch (Exception fastjsonEx) { |
|||
try { |
|||
@SuppressWarnings("unchecked") |
|||
Map<String, Object> map = JACKSON.readValue(trimmed, Map.class); |
|||
return new JSONObject(map); |
|||
} catch (Exception jacksonEx) { |
|||
fastjsonEx.addSuppressed(jacksonEx); |
|||
if (fastjsonEx instanceof RuntimeException) { |
|||
throw (RuntimeException) fastjsonEx; |
|||
} |
|||
throw new RuntimeException(fastjsonEx); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static JSONArray parseArray(String text) { |
|||
if (text == null) { |
|||
return null; |
|||
} |
|||
String trimmed = text.trim(); |
|||
if (trimmed.isEmpty() || "null".equalsIgnoreCase(trimmed)) { |
|||
return null; |
|||
} |
|||
try { |
|||
Object parsed = JSON.parse(trimmed.getBytes(StandardCharsets.UTF_8)); |
|||
if (parsed instanceof JSONArray) { |
|||
return (JSONArray) parsed; |
|||
} |
|||
if (parsed == null) { |
|||
return null; |
|||
} |
|||
JSONArray arr = new JSONArray(); |
|||
arr.add(parsed); |
|||
return arr; |
|||
} catch (Exception fastjsonEx) { |
|||
try { |
|||
List<?> list = JACKSON.readValue(trimmed, List.class); |
|||
return new JSONArray(list); |
|||
} catch (Exception jacksonEx) { |
|||
fastjsonEx.addSuppressed(jacksonEx); |
|||
if (fastjsonEx instanceof RuntimeException) { |
|||
throw (RuntimeException) fastjsonEx; |
|||
} |
|||
throw new RuntimeException(fastjsonEx); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static <T> T parseObject(String text, Class<T> clazz) { |
|||
if (text == null) { |
|||
return null; |
|||
} |
|||
String trimmed = text.trim(); |
|||
if (trimmed.isEmpty() || "null".equalsIgnoreCase(trimmed)) { |
|||
return null; |
|||
} |
|||
try { |
|||
return JSON.parseObject(trimmed.getBytes(StandardCharsets.UTF_8), clazz); |
|||
} catch (Exception fastjsonEx) { |
|||
try { |
|||
return JACKSON.readValue(trimmed, clazz); |
|||
} catch (Exception jacksonEx) { |
|||
fastjsonEx.addSuppressed(jacksonEx); |
|||
if (fastjsonEx instanceof RuntimeException) { |
|||
throw (RuntimeException) fastjsonEx; |
|||
} |
|||
throw new RuntimeException(fastjsonEx); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
package com.zdxt.common.util; |
|||
|
|||
import cn.hutool.core.codec.Base64; |
|||
import cn.hutool.core.util.IdUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import cn.hutool.crypto.SecureUtil; |
|||
import cn.hutool.crypto.asymmetric.KeyType; |
|||
import cn.hutool.crypto.asymmetric.RSA; |
|||
import cn.hutool.http.HttpRequest; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.nio.charset.StandardCharsets; |
|||
|
|||
/** |
|||
* 证件号加密工具 |
|||
* |
|||
* @author joelzzhang |
|||
*/ |
|||
public class PhoneCardCryptoUtil { |
|||
|
|||
/** |
|||
* 以下字符串为密钥,可以通过配置来传递 |
|||
*/ |
|||
public static final String OUTER_AES_KEY = "u3BPiZWALItH27e34oO6bw=="; |
|||
public static final String INNER_AES_KEY = "ol1hV81iCruo+thxtZGnCg=="; |
|||
public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDADIVdRXNyT5pmGxSUfwEup2Y1YCz7VfibMyyQhkpVP3ByKsoFDjZ30yW5gv70sQbqn/MCthTFqwh3/I/YLfthM3vU7TdlmNfnlKcQ6prxYofLkX8akrhgQVFerb/4DhcbAk5xvx12Dkeh/nzDf5Qw2K3CymRPnr4zN6ZjAL79HwIDAQAB"; |
|||
public static final String PRIVATE_KEY = "MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBAMAMhV1Fc3JPmmYbFJR/AS6nZjVgLPtV+JszLJCGSlU/cHIqygUONnfTJbmC/vSxBuqf8wK2FMWrCHf8j9gt+2Eze9TtN2WY1+eUpxDqmvFih8uRfxqSuGBBUV6tv/gOFxsCTnG/HXYOR6H+fMN/lDDYrcLKZE+evjM3pmMAvv0fAgMBAAECf2rTWPVVrHuC/ywzFz+CmSXAxLNSZbMCH0doYvs0hIrmvtjCjgN1F6P1z2yUpHBEbsetZEwdyQnEf74+WCuBaFIYjmQm/05grIxam/QrGibnV5qNiA0VeCkj6bUy+z5mt3/ithgzjmOiYIgMenjf6ut6RmgRDodgJiFbvSvfskECQQDoqGMNaUV+Y/O9h+l9eOCkSUOjOsL9WDRL34ei/jf7VFFYBZLs2GMzLunDkDz1dHvufDiMPeHUTTN8zIEEpNMVAkEA01Ehf9Np2zUqFQp3SW/fHVsaGM97JsbsTQrsDdUWwZLJTSQ7Yf+yg21GkNa8Mmlsp1OLyS607HV7lPlKO0LsYwJBAN1LTOv0taUCbVRZcj1mPEYHac5Ylh9jRlrkwp7GL3lfOf//TUD6kWCdvrvE9jaCFV9ByIecVLEavV53JkDEhgECQHgR40z0XXOWs8Cc38a0cDwH5b4vNjSEVwQ3roT1jSaUNxlD+xHl7hLLZosp2Gl0ia5RxF2d0mOBJaUyOMGPdy0CQCLBU3995JMoUyLgt2+Riw0PFbdxFEnKGyshAt5Q957/hC6L/V5tQNnh/AM9q23DIUQGb1whhxdIzV12qFSSS34="; |
|||
|
|||
/** |
|||
* 新版 AES/ECB/PKCS5Padding 对称密钥(与 userCrypto.js 保持一致) |
|||
* js 中: str1="qwepoiad", str2="sf12vfgi", key = str1+str2 |
|||
*/ |
|||
private static final String AES_ECB_KEY = "qwepoiadsf12vfgi"; |
|||
|
|||
public static void main(String[] args) { |
|||
System.out.println("=============== 新版 AES/ECB 加解密示例 ==============="); |
|||
// 加密
|
|||
String source = "19925235717"; |
|||
String encrypted = aesEcbEncrypt(source); |
|||
System.out.println(source + " 加密后: " + encrypted); |
|||
|
|||
// 解密
|
|||
String decrypted = aesEcbDecrypt("pm1VDl8q3dfp7irhyhrtiw3amYU1jKdOhpjTqMWv0w="); |
|||
System.out.println(encrypted + " 解密后: " + decrypted); |
|||
|
|||
System.out.println("=============== 批量测试 ==============="); |
|||
String[] phones = {"13631554532", "13692276207"}; |
|||
for (String phone : phones) { |
|||
String enc = aesEcbEncrypt(phone); |
|||
System.out.println(phone + " 加密后: " + enc); |
|||
System.out.println(enc + " 解密后: " + aesEcbDecrypt(enc)); |
|||
} |
|||
} |
|||
|
|||
// ======================== 新版 AES/ECB 加解密(与 userCrypto.js 一致) ========================
|
|||
|
|||
/** |
|||
* AES/ECB/PKCS5Padding 加密(Java中PKCS5Padding等价于JS的PKCS7Padding,块大小16时行为一致) |
|||
* |
|||
* @param content 明文字符串 |
|||
* @return Base64 编码的密文 |
|||
*/ |
|||
public static String aesEcbEncrypt(String content) { |
|||
try { |
|||
byte[] keyBytes = AES_ECB_KEY.getBytes(StandardCharsets.UTF_8); |
|||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); |
|||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); |
|||
cipher.init(Cipher.ENCRYPT_MODE, keySpec); |
|||
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); |
|||
return Base64.encode(encrypted); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("AES ECB 加密失败", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* AES/ECB/PKCS5Padding 解密(与 userCrypto.js 的 aesDecrypt 对应) |
|||
* |
|||
* @param encryptedText Base64 编码的密文 |
|||
* @return 解密后的明文字符串 |
|||
*/ |
|||
public static String aesEcbDecrypt(String encryptedText) { |
|||
try { |
|||
byte[] keyBytes = AES_ECB_KEY.getBytes(StandardCharsets.UTF_8); |
|||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); |
|||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); |
|||
cipher.init(Cipher.DECRYPT_MODE, keySpec); |
|||
byte[] decrypted = cipher.doFinal(Base64.decode(encryptedText)); |
|||
return new String(decrypted, StandardCharsets.UTF_8); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("AES ECB 解密失败", e); |
|||
} |
|||
} |
|||
|
|||
// ======================== 旧版方法(保留兼容) ========================
|
|||
|
|||
/** |
|||
* 证件号通过RSA的公钥加密 |
|||
*/ |
|||
public static String asymmetricEncryptIdCard(String publicKey, String privateKey, String content) { |
|||
RSA rsa = SecureUtil.rsa(privateKey, publicKey); |
|||
byte[] encrypt = rsa.encrypt(content, StandardCharsets.UTF_8, KeyType.PublicKey); |
|||
return Base64.encode(encrypt); |
|||
} |
|||
|
|||
/** |
|||
* 证件号通过RSA的私钥解密 |
|||
*/ |
|||
public static String asymmetricDecryptIdCard(String publicKey, String privateKey, String content) { |
|||
RSA rsa = SecureUtil.rsa(privateKey, publicKey); |
|||
byte[] decrypt = rsa.decrypt(content, KeyType.PrivateKey); |
|||
return StrUtil.str(decrypt, StandardCharsets.UTF_8); |
|||
} |
|||
|
|||
/** |
|||
* 证件号对称加密后入库(旧版) |
|||
*/ |
|||
public static String symmetricEncryptIdCard(String key, String content) { |
|||
return SecureUtil.aes(SecureUtil.decode(key)).encryptBase64(content); |
|||
} |
|||
|
|||
/** |
|||
* 证件号对称解密(旧版) |
|||
*/ |
|||
public static String symmetricDecryptIdCard(String key, String content) { |
|||
return SecureUtil.aes(SecureUtil.decode(key)).decryptStr(content); |
|||
} |
|||
|
|||
public static String httpSendPost(String url, String userId, String userName, String appCode) { |
|||
HttpRequest request = HttpRequest.post(url) |
|||
.header("x-auth-channel", IdUtil.simpleUUID()) |
|||
.header("x-auth-app-code", appCode) |
|||
.form("userId", userId) |
|||
.form("userName", userName); |
|||
request.contentType("application/x-www-form-urlencoded"); |
|||
JSONObject jsonObject = JSONObject.parseObject(request.execute().body()); |
|||
return jsonObject.getString("data"); |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
package com.zdxt.app.controller.external.company; |
|||
|
|||
import cn.hutool.core.io.FileTypeUtil; |
|||
import cn.hutool.core.io.FileUtil; |
|||
import com.google.common.collect.Lists; |
|||
import com.zdxt.auth.common.constant.Constants; |
|||
import com.zdxt.auth.common.utils.StringUtils; |
|||
import com.zdxt.auth.common.utils.file.FileUploadUtils; |
|||
import com.zdxt.auth.common.utils.file.FileUtils; |
|||
import com.zdxt.auth.dto.UserBean; |
|||
import com.zdxt.auth.feign.UserApi; |
|||
import com.zdxt.auth.framework.config.ServerConfig; |
|||
import com.zdxt.auth.framework.web.domain.AjaxResult; |
|||
import com.zdxt.common.ZDResponse; |
|||
import com.zdxt.common.config.RuoYiConfig; |
|||
import org.apache.commons.collections4.CollectionUtils; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.io.InputStream; |
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
|
|||
/** |
|||
* 通用请求处理 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
@Controller |
|||
public class OutCommonController |
|||
{ |
|||
private static final Logger log = LoggerFactory.getLogger(OutCommonController.class); |
|||
|
|||
@Autowired |
|||
private ServerConfig serverConfig; |
|||
|
|||
@Autowired |
|||
private UserApi userApi; |
|||
/** |
|||
* 默认允许的文件类型 |
|||
* |
|||
* @see FileTypeUtil |
|||
*/ |
|||
private List<String> defaultAllowTypes = Lists.newArrayList( |
|||
"xlsx", "xls", "docx", "doc", "pdf", "txt", |
|||
"jpg", "jpeg", "png", "bmp", "mp4", |
|||
"zip"); |
|||
|
|||
/** |
|||
* 通用下载请求 |
|||
* |
|||
* @param fileName 文件名称 |
|||
* @param delete 是否删除 |
|||
*/ |
|||
@GetMapping("/external/common/download") |
|||
public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) |
|||
{ |
|||
try |
|||
{ |
|||
if (!FileUtils.isValidFilename(fileName)) |
|||
{ |
|||
throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); |
|||
} |
|||
String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); |
|||
String filePath = RuoYiConfig.getDownloadPath() + fileName; |
|||
|
|||
response.setCharacterEncoding("utf-8"); |
|||
response.setContentType("multipart/form-data"); |
|||
response.setHeader("Content-Disposition", |
|||
"attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName)); |
|||
FileUtils.writeBytes(filePath, response.getOutputStream()); |
|||
if (delete) |
|||
{ |
|||
FileUtils.deleteFile(filePath); |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
log.error("下载文件失败", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 通用上传请求 |
|||
*/ |
|||
@PostMapping("/external/common/upload") |
|||
@ResponseBody |
|||
public AjaxResult uploadFile(MultipartFile file) throws Exception |
|||
{ |
|||
try |
|||
{ |
|||
String fileNameTmp = file.getOriginalFilename(); |
|||
InputStream in = file.getInputStream(); |
|||
String type = FileTypeUtil.getType(in); |
|||
if (StringUtils.isBlank(type)) { |
|||
// 无法通过文件头或者扩展名识别文件类型。
|
|||
return AjaxResult.error("上传失败格式不符合要求"); |
|||
} |
|||
|
|||
String extType = FileUtil.extName(fileNameTmp); |
|||
if (StringUtils.isBlank(extType)) { |
|||
// 后缀读不到类型,直接拒绝
|
|||
return AjaxResult.error("无法读取文件类型"); |
|||
} |
|||
|
|||
// 文件头类型和后缀类型都要满足白名单
|
|||
if (!CollectionUtils.containsAll(defaultAllowTypes, |
|||
Lists.newArrayList(type.toLowerCase(Locale.ROOT), extType.toLowerCase(Locale.ROOT)))) { |
|||
|
|||
return AjaxResult.error("文件上传失败"); |
|||
} |
|||
|
|||
// 上传文件路径
|
|||
String filePath = RuoYiConfig.getUploadPath(); |
|||
// 上传并返回新文件名称
|
|||
String fileName = FileUploadUtils.upload(filePath, file); |
|||
log.info("文件上传"+fileName); |
|||
String url = fileName; |
|||
AjaxResult ajax = AjaxResult.success(); |
|||
ajax.put("fileName", fileName); |
|||
ajax.put("url", url); |
|||
return ajax; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return AjaxResult.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 本地资源通用下载 |
|||
*/ |
|||
@GetMapping("/external/common/download/resource") |
|||
public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) |
|||
throws Exception |
|||
{ |
|||
// 本地资源路径
|
|||
String localPath = RuoYiConfig.getProfile(); |
|||
// 数据库资源地址
|
|||
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); |
|||
// 下载名称
|
|||
String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); |
|||
response.setCharacterEncoding("utf-8"); |
|||
response.setContentType("multipart/form-data"); |
|||
response.setHeader("Content-Disposition", |
|||
"attachment;fileName=" + FileUtils.setFileDownloadHeader(request, downloadName)); |
|||
FileUtils.writeBytes(downloadPath, response.getOutputStream()); |
|||
} |
|||
|
|||
@GetMapping("/external/getUserInfo") |
|||
@ResponseBody |
|||
public ZDResponse getUserInfo(){ |
|||
UserBean user = userApi.getCurrentUser(); |
|||
return ZDResponse.success("查询成功",user); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
package com.zdxt.app.controller.external.company; |
|||
|
|||
import cn.hutool.core.bean.BeanUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.zdxt.code.domain.*; |
|||
import com.zdxt.code.service.*; |
|||
import com.zdxt.common.ZDResponse; |
|||
import com.zdxt.common.controller.BaseController; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
/** |
|||
* 外部接口-企业执法-在线投诉Controller |
|||
* |
|||
* @author kylin |
|||
* @date 2026-04-15 |
|||
*/ |
|||
//@Tag(name = "外部接口", description = "企业信息")
|
|||
@Controller |
|||
@RequestMapping("/external/enterprise/info") |
|||
public class OutEnterpriseInfoController extends BaseController { |
|||
|
|||
@Autowired private IEnterpriseInformationService enterpriseInformationService; |
|||
@Autowired private IQrCodeGenerateHistoryService iQrCodeGenerateHistoryService; |
|||
@Autowired private ThirdLoginService thirdLoginService; |
|||
|
|||
@GetMapping("/getQrCode") |
|||
@ResponseBody |
|||
public ZDResponse getQrCode(String enterpriseNumber) |
|||
{ |
|||
//查询企业二维码信息
|
|||
QrCodeGenerateHistory qrCodeGenerateHistory = iQrCodeGenerateHistoryService.selectQrCodeGenerateHistoryByNewest(enterpriseNumber); |
|||
//查询企业信息
|
|||
EnterpriseInformation enterpriseInformation = |
|||
enterpriseInformationService.selectEnterpriseInformationByNum(enterpriseNumber); |
|||
if(BeanUtil.isNotEmpty(enterpriseInformation) && |
|||
StrUtil.isNotEmpty(enterpriseInformation.getEnterpriseNumber())){ |
|||
qrCodeGenerateHistory.setEnterpriseName(enterpriseInformation.getEnterpriseName()); |
|||
} |
|||
return ZDResponse.success("查询成功",qrCodeGenerateHistory); |
|||
} |
|||
|
|||
/** |
|||
* 新增保存企业信息登记 |
|||
*/ |
|||
@PostMapping("/addEnterprise") |
|||
@ResponseBody |
|||
public ZDResponse addEnterprise(@RequestBody EnterpriseInformation enterpriseInformation) |
|||
{ |
|||
return enterpriseInformationService.insertEnterpriseInformation(enterpriseInformation) > 0 |
|||
? ZDResponse.success("登记成功"): ZDResponse.error("登记失败"); |
|||
} |
|||
|
|||
/** |
|||
* 添加深i企 |
|||
* @param jsonObject |
|||
* @return |
|||
*/ |
|||
@PostMapping("/addLiqUserCompany") |
|||
@ResponseBody |
|||
public ZDResponse addLiqUserCompany(@RequestBody JSONObject jsonObject) { |
|||
return thirdLoginService.addLiqUserCompany(jsonObject) ? ZDResponse.success("登记成功") : ZDResponse.error("登记失败"); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
package com.zdxt.app.controller.external.company; |
|||
|
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.zdxt.auth.dto.DeptBean; |
|||
import com.zdxt.auth.feign.DeptApi; |
|||
import com.zdxt.code.domain.EnterpriseInformation; |
|||
import com.zdxt.code.service.ThirdLoginService; |
|||
import com.zdxt.common.ZDResponse; |
|||
import com.zdxt.common.controller.BaseController; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @Author: kylin |
|||
* @Date: 2024/5/1 3:00 下午 |
|||
* @Version: 1.0 |
|||
* @Desc: TODO |
|||
*/ |
|||
@Controller |
|||
@RequestMapping("/external/user") |
|||
public class OutUserController extends BaseController { |
|||
|
|||
@Autowired private ThirdLoginService thirdLoginService; |
|||
|
|||
/** |
|||
* 新增保存企业信息登记 |
|||
*/ |
|||
@PostMapping("/addLiqUser") |
|||
@ResponseBody |
|||
public ZDResponse addLiqUser(@RequestBody JSONObject jsonObject) |
|||
{ |
|||
Long userId = thirdLoginService.addLiqUser(jsonObject); |
|||
return ZDResponse.success("登记成功", userId); |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
package com.zdxt.auth.project.system.user.controller; |
|||
|
|||
import com.google.gson.Gson; |
|||
import com.google.gson.JsonObject; |
|||
import com.zdxt.auth.framework.shiro.nopaddword.LoginType; |
|||
import com.zdxt.auth.framework.shiro.nopaddword.UserToken; |
|||
import com.zdxt.auth.framework.shiro.nopaddword.UserType; |
|||
import com.zdxt.auth.framework.web.controller.BaseController; |
|||
import com.zdxt.auth.project.system.user.service.IUserService; |
|||
import com.zdxt.domain.auth.User; |
|||
import org.apache.shiro.SecurityUtils; |
|||
import org.apache.shiro.authc.AuthenticationException; |
|||
import org.apache.shiro.subject.Subject; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
|
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.web.server.ResponseStatusException; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.Base64; |
|||
|
|||
/** |
|||
* 通过用户ID登录 |
|||
*/ |
|||
@Controller |
|||
public class LoginByUserIdController extends BaseController { |
|||
|
|||
private static final Logger log = LoggerFactory.getLogger(LoginByUserIdController.class); |
|||
|
|||
/** |
|||
* AES解密密钥(与新系统 EncryptUtils.encryptByAes 加密密钥一致) |
|||
*/ |
|||
private static final String AES_KEY = "zdxt2026PlatForm"; |
|||
|
|||
/** |
|||
* 时间戳允许的最大偏移量(秒) |
|||
*/ |
|||
private static final long ALLOWED_TIME_OFFSET_SECONDS = 20; |
|||
|
|||
/** |
|||
* 允许登录的开始日期时间(格式 yyyy-MM-dd HH:mm) |
|||
*/ |
|||
private static final String ALLOWED_START_TIME = "2026-06-18 08:00"; |
|||
|
|||
/** |
|||
* 允许登录的结束日期时间(格式 yyyy-MM-dd HH:mm) |
|||
*/ |
|||
private static final String ALLOWED_END_TIME = "2026-09-01 22:00"; |
|||
|
|||
@Autowired |
|||
private IUserService userService; |
|||
|
|||
/** |
|||
* 通过v1_system_credential免密登录,解密凭据后验证时间戳偏移并使用userId登录 |
|||
* |
|||
* @param v1SystemCredential AES加密的凭据(包含userid和ts) |
|||
* @return 跳转页面 |
|||
*/ |
|||
@GetMapping("/loginByUserId") |
|||
public String loginByUserId(@RequestParam("v1_system_credential") String v1SystemCredential) { |
|||
// 0. 验证当前时间是否在允许的日期时间范围内
|
|||
if (!isWithinAllowedTime()) { |
|||
log.warn("通过userId登录失败, 当前时间不在允许的时间范围内[{} - {}]", |
|||
ALLOWED_START_TIME, ALLOWED_END_TIME); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 1. AES解密凭据
|
|||
String decryptedJson = decryptByAes(v1SystemCredential); |
|||
if (decryptedJson == null) { |
|||
log.warn("通过userId登录失败, v1_system_credential解密失败"); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 2. 解析JSON获取userid和ts
|
|||
Long userId; |
|||
long ts; |
|||
try { |
|||
Gson gson = new Gson(); |
|||
JsonObject jsonObject = gson.fromJson(decryptedJson, JsonObject.class); |
|||
userId = Long.parseLong(jsonObject.get("userid").getAsString()); |
|||
ts = Long.parseLong(jsonObject.get("ts").getAsString()); |
|||
} catch (Exception e) { |
|||
log.warn("通过userId登录失败, 凭据JSON解析异常: {}", e.getMessage()); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 3. 验证时间戳是否与当前系统时间偏移在±20秒内
|
|||
// long currentTimeMillis = System.currentTimeMillis();
|
|||
// long diffSeconds = Math.abs(currentTimeMillis - ts) / 1000;
|
|||
// if (diffSeconds > ALLOWED_TIME_OFFSET_SECONDS) {
|
|||
// log.warn("通过userId登录失败, 时间戳偏移超过{}秒, ts: {}, 当前时间: {}, 偏移: {}秒",
|
|||
// ALLOWED_TIME_OFFSET_SECONDS, ts, currentTimeMillis, diffSeconds);
|
|||
// throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
|||
// }
|
|||
|
|||
// 4. 根据userId查询用户
|
|||
User sysUser = userService.selectUserById(userId); |
|||
if (sysUser == null) { |
|||
log.warn("通过userId登录失败, 用户不存在, userId: {}", userId); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 5. 使用免密登录方式进行Shiro认证
|
|||
UserToken token = new UserToken(sysUser.getLoginName(), LoginType.NOPASSWD, UserType.SYS.getType()); |
|||
Subject subject = SecurityUtils.getSubject(); |
|||
try { |
|||
subject.login(token); |
|||
log.info("通过v1_system_credential登录成功, userId: {}", userId); |
|||
return "redirect:/code/statisticalPanel/home-analysis"; |
|||
} catch (AuthenticationException e) { |
|||
log.error("通过userId登录认证失败, userId: {}, 原因: {}", userId, e.getMessage()); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断当前时间是否在允许的日期时间范围内 |
|||
* |
|||
* @return true-在允许时间范围内, false-不在允许时间范围内 |
|||
*/ |
|||
private boolean isWithinAllowedTime() { |
|||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); |
|||
LocalDateTime startTime = LocalDateTime.parse(ALLOWED_START_TIME, formatter); |
|||
LocalDateTime endTime = LocalDateTime.parse(ALLOWED_END_TIME, formatter); |
|||
LocalDateTime now = LocalDateTime.now(); |
|||
return !now.isBefore(startTime) && !now.isAfter(endTime); |
|||
} |
|||
|
|||
/** |
|||
* AES/ECB/PKCS5Padding 解密(对应新系统 EncryptUtils.encryptByAes 的加密方式) |
|||
* |
|||
* @param encryptedText Base64编码的AES密文 |
|||
* @return 解密后的明文字符串,解密失败返回null |
|||
*/ |
|||
private String decryptByAes(String encryptedText) { |
|||
try { |
|||
byte[] keyBytes = AES_KEY.getBytes(StandardCharsets.UTF_8); |
|||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); |
|||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); |
|||
cipher.init(Cipher.DECRYPT_MODE, keySpec); |
|||
byte[] decodeBytes = Base64.getDecoder().decode(encryptedText); |
|||
byte[] decrypted = cipher.doFinal(decodeBytes); |
|||
return new String(decrypted, StandardCharsets.UTF_8); |
|||
} catch (Exception e) { |
|||
log.error("AES解密失败: {}", e.getMessage()); |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,99 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<configuration debug="false" scan="true" scanPeriod="1 seconds"> |
|||
<contextName>logback</contextName> |
|||
|
|||
<property name="zdxtEmergencyBootStart.path" value="/usr/local/zdxtEmergencyBootStart/logs/zdxtEmergencyBootStart.log"/> |
|||
<property name="zdxtEmergencyBootStart.error.path" value="/usr/local/zdxtEmergencyBootStart/logs/zdxtEmergencyBootStart.error.log"/> |
|||
<!--有线报警数据日志--> |
|||
<!-- <property name="zdxtEmergencyBootStart.realtimeWarnHandler.path" value="/usr/local/zdxtEmergencyBootStart/logs/realtimeWarnHandler.log"/>--> |
|||
<!--mqtt数据日志--> |
|||
<!-- <property name="zdxtEmergencyBootStart.warningMessageCallback.path" value="/usr/local/zdxtEmergencyBootStart/logs/warningMessageCallback.log"/>--> |
|||
<!--mqtt缓存数据日志--> |
|||
<!-- <property name="zdxtEmergencyBootStart.iotMessageTask.path" value="/usr/local/zdxtEmergencyBootStart/logs/iotMessageTask.log"/>--> |
|||
|
|||
<property name="historyLogPath" value="/usr/local/zdxtEmergencyBootStart/logs/history"/> |
|||
|
|||
<!-- 异步任务日志 --> |
|||
<!-- <property name="zdxtEmergencyBootStart.asyncTask.path" value="/usr/local/qn-worksite/logs/zdxtEmergencyBootStart.asyncTask.log"/>--> |
|||
|
|||
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender"> |
|||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
|||
<level>INFO</level> |
|||
</filter> |
|||
<encoder> |
|||
<pattern> |
|||
%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
<charset>UTF-8</charset> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<appender name="zdxtEmergencyBootStart" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>${zdxtEmergencyBootStart.path}</file> |
|||
<!-- 过滤日志 --> |
|||
<filter class="ch.qos.logback.classic.filter.LevelFilter"> |
|||
<level>ERROR</level> |
|||
<onMatch>DENY</onMatch> <!-- 如果命中就禁止这条日志 --> |
|||
<onMismatch>ACCEPT</onMismatch> <!-- 如果没有命中就使用这条规则 --> |
|||
</filter> |
|||
|
|||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
|||
<fileNamePattern>${historyLogPath}/%d{yyyy-MM-dd}/zdxtEmergencyBootStart.zip</fileNamePattern> |
|||
<maxHistory>180</maxHistory> |
|||
<!-- <maxFileSize>500MB</maxFileSize>--> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n</pattern> |
|||
<charset>UTF-8</charset> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<appender name="zdxtEmergencyBootStart-error" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>${zdxtEmergencyBootStart.error.path}</file> |
|||
<!-- 过滤日志 --> |
|||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
|||
<level>ERROR</level> |
|||
</filter> |
|||
|
|||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
|||
<fileNamePattern>${historyLogPath}/%d{yyyy-MM-dd}/zdxtEmergencyBootStart-error.zip</fileNamePattern> |
|||
<maxHistory>180</maxHistory> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n</pattern> |
|||
<charset>UTF-8</charset> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<logger name="com.zdxt" level="INFO" additivity="false"> |
|||
<appender-ref ref="zdxtEmergencyBootStart" /> |
|||
<appender-ref ref="zdxtEmergencyBootStart-error"/> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-realtimeWarnHandler"/>--> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-warningMessageCallback"/>--> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-iotMessageTask"/>--> |
|||
</logger> |
|||
|
|||
<logger name="org.springframework" level="INFO" additivity="false"> |
|||
<appender-ref ref="zdxtEmergencyBootStart" /> |
|||
<appender-ref ref="zdxtEmergencyBootStart-error"/> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-realtimeWarnHandler"/>--> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-warningMessageCallback"/>--> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-iotMessageTask"/>--> |
|||
</logger> |
|||
|
|||
|
|||
<root level="INFO"> |
|||
<appender-ref ref="zdxtEmergencyBootStart"/> |
|||
<appender-ref ref="zdxtEmergencyBootStart-error"/> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-realtimeWarnHandler"/>--> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-warningMessageCallback"/>--> |
|||
<!-- <appender-ref ref="zdxtEmergencyBootStart-iotMessageTask"/>--> |
|||
<appender-ref ref="consoleAppender"/> |
|||
</root> |
|||
<!-- <root level="ERROR"> |
|||
<appender-ref ref="zdxtEmergencyBootStart"/> |
|||
<appender-ref ref="zdxtEmergencyBootStart-error"/> |
|||
<appender-ref ref="zdxtEmergencyBootStart-realtimeWarnHandler"/> |
|||
<appender-ref ref="zdxtEmergencyBootStart-warningMessageCallback"/> |
|||
<appender-ref ref="consoleAppender"/> |
|||
</root>--> |
|||
</configuration> |
|||
@ -0,0 +1,248 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> |
|||
<head> |
|||
<th:block th:include="zdxtInclude :: header('送码入企信息查询')" /> |
|||
</head> |
|||
<body class="gray-bg"> |
|||
<div class="container-div"> |
|||
<div class="row"> |
|||
<div class="col-sm-12 search-collapse"> |
|||
<form id="formId"> |
|||
<div class="select-list"> |
|||
<ul> |
|||
<li> |
|||
<p>企业名称:</p> |
|||
<input type="text" name="enterpriseName"/> |
|||
</li> |
|||
<li> |
|||
<p>统一社会信用代码:</p> |
|||
<input type="text" name="enterpriseNumber"/> |
|||
</li> |
|||
<li> |
|||
<p>所属区域:</p> |
|||
<select name="areaId" id="areaSelect"> |
|||
<option value="">全部</option> |
|||
</select> |
|||
</li> |
|||
<li> |
|||
<p>所属街道:</p> |
|||
<select name="streetId" id="streetSelect"> |
|||
<option value="">全部</option> |
|||
</select> |
|||
</li> |
|||
<li> |
|||
<p>匹配状态:</p> |
|||
<select name="matchingStatus"> |
|||
<option value="">全部</option> |
|||
<option value="未匹配">未匹配</option> |
|||
<option value="已匹配">已匹配</option> |
|||
<option value="强制匹配">强制匹配</option> |
|||
<option value="匹配失败">匹配失败</option> |
|||
</select> |
|||
</li> |
|||
<li> |
|||
<p>下载情况:</p> |
|||
<select name="downloadStatus"> |
|||
<option value="">全部</option> |
|||
<option value="未下载">未下载</option> |
|||
<option value="已下载">已下载</option> |
|||
</select> |
|||
</li> |
|||
<li> |
|||
<p>上传人:</p> |
|||
<input type="text" name="userName"/> |
|||
</li> |
|||
<li> |
|||
<p>上传单位:</p> |
|||
<input type="text" name="deptName"/> |
|||
</li> |
|||
<li> |
|||
<a class="btn btn-success btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a> |
|||
<a class="btn btn-reset btn-sm" onclick="resetQueryForm()"><i class="fa fa-refresh"></i> 重置</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
|
|||
<div class="btn-group-sm" id="toolbar" role="group"> |
|||
<a class="btn btn-primary" onclick="downloadData()"> |
|||
<i class="fa fa-download"></i> 下载 |
|||
</a> |
|||
</div> |
|||
<div class="col-sm-12 select-table table-bordered"> |
|||
<table id="bootstrap-table"></table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<th:block th:include="zdxtInclude :: footer" /> |
|||
<script th:inline="javascript"> |
|||
var prefix = ctx + "code/enterprisePubQrcode"; |
|||
|
|||
$(function() { |
|||
loadAreaDict(); |
|||
|
|||
var options = { |
|||
url: prefix + "/queryPageList", |
|||
modalName: "送码入企信息查询", |
|||
showHeader: false, |
|||
showFooter: false, |
|||
showColumns: false, |
|||
showRefresh: false, |
|||
showToggle: false, |
|||
pageSize: 20, |
|||
columns: [ |
|||
{ |
|||
checkbox: true |
|||
}, |
|||
{ |
|||
field : 'id', |
|||
title : '主键ID', |
|||
visible: false |
|||
}, |
|||
{ |
|||
field : 'importRowNum', |
|||
title : '导入序号' |
|||
}, |
|||
{ |
|||
field : 'enterpriseName', |
|||
title : '企业名称' |
|||
}, |
|||
{ |
|||
field : 'enterpriseNumber', |
|||
title : '统一社会信用代码' |
|||
}, |
|||
{ |
|||
field : 'areaName', |
|||
title : '所属区域' |
|||
}, |
|||
{ |
|||
field : 'streetName', |
|||
title : '所属街道' |
|||
}, |
|||
{ |
|||
field : 'matchingStatus', |
|||
title : '匹配状态', |
|||
formatter: function(value, row, index) { |
|||
if (value == '匹配失败') { |
|||
return '<span style="color:red;text-decoration:underline;cursor:pointer;" title="' + (row.matchingMessage || '') + '">' + value + '</span>'; |
|||
} |
|||
if ((value == '已匹配' || value == '强制匹配') && row.matchingMessage) { |
|||
return '<span style="color:blue;text-decoration:underline;cursor:pointer;" title="' + row.matchingMessage + '">' + value + '</span>'; |
|||
} |
|||
return value; |
|||
} |
|||
}, |
|||
{ |
|||
field : 'downloadStatus', |
|||
title : '下载情况' |
|||
}, |
|||
{ |
|||
field : 'userName', |
|||
title : '上传人' |
|||
}, |
|||
{ |
|||
field : 'deptName', |
|||
title : '上传单位' |
|||
}, |
|||
{ |
|||
field : 'remark', |
|||
title : '备注' |
|||
}] |
|||
}; |
|||
$.table.init(options); |
|||
}); |
|||
|
|||
/** 加载区域字典数据 */ |
|||
function loadAreaDict() { |
|||
$.ajax({ |
|||
url: prefix + "/dictAreaList", |
|||
type: "get", |
|||
dataType: "json", |
|||
success: function(result) { |
|||
if (result.success && result.data) { |
|||
var $areaSelect = $('#areaSelect'); |
|||
$.each(result.data, function(i, item) { |
|||
$areaSelect.append('<option value="' + item.dictValue + '">' + item.dictLabel + '</option>'); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** 区域变更时加载街道字典数据 */ |
|||
$('#areaSelect').change(function() { |
|||
var areaId = $(this).val(); |
|||
var $streetSelect = $('#streetSelect'); |
|||
$streetSelect.html('<option value="">全部</option>'); |
|||
if (areaId) { |
|||
$.ajax({ |
|||
url: prefix + "/dictStreetList", |
|||
type: "get", |
|||
data: { areaId: areaId }, |
|||
dataType: "json", |
|||
success: function(result) { |
|||
if (result.success && result.data) { |
|||
$.each(result.data, function(i, item) { |
|||
$streetSelect.append('<option value="' + item.dictValue + '">' + item.dictLabel + '</option>'); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
/** 重置查询表单 */ |
|||
function resetQueryForm() { |
|||
$.form.reset(); |
|||
$('#streetSelect').html('<option value="">全部</option>'); |
|||
} |
|||
|
|||
/** 批量下载 */ |
|||
function downloadData() { |
|||
var rows = $.table.selectColumns("id"); |
|||
if (rows.length == 0) { |
|||
$.modal.alertWarning("请至少选择一条记录"); |
|||
return; |
|||
} |
|||
var loadIndex = layer.msg('PDF生成中,请稍候...', {icon: 16, shade: [0.3, '#000'], time: 0}); |
|||
$.ajax({ |
|||
url: prefix + "/generatePdf", |
|||
type: "post", |
|||
data: { "ids": rows.join(",") }, |
|||
dataType: "json", |
|||
success: function(result) { |
|||
layer.close(loadIndex); |
|||
if (result.success) { |
|||
layer.open({ |
|||
type: 1, |
|||
title: '生成完成', |
|||
area: ['380px', '200px'], |
|||
shade: 0.3, |
|||
shadeClose: true, |
|||
content: '<div style="padding:30px;text-align:center;">' + |
|||
'<p style="margin-bottom:20px;font-size:14px;"><i class="fa fa-check-circle" style="color:#5cb85c;margin-right:6px;"></i>PDF文件已生成完成</p>' + |
|||
'<a href="' + result.data + '" class="btn btn-primary" target="_blank">' + |
|||
'<i class="fa fa-download"></i> 点击下载</a></div>', |
|||
btn: ['关闭'], |
|||
btn1: function(index) { |
|||
layer.close(index); |
|||
$.table.refresh(); |
|||
}, |
|||
cancel: function() { |
|||
$.table.refresh(); |
|||
} |
|||
}); |
|||
} else { |
|||
$.modal.alertError(result.message); |
|||
} |
|||
}, |
|||
error: function() { |
|||
layer.close(loadIndex); |
|||
$.modal.alertError("生成失败,请联系管理员!"); |
|||
} |
|||
}); |
|||
} |
|||
</script> |
|||
</body> |
|||
</html> |
|||
Binary file not shown.
@ -0,0 +1,308 @@ |
|||
package org.crazycake.shiro; |
|||
|
|||
import org.apache.shiro.cache.Cache; |
|||
import org.apache.shiro.cache.CacheException; |
|||
import org.apache.shiro.subject.PrincipalCollection; |
|||
import org.apache.shiro.util.CollectionUtils; |
|||
import org.crazycake.shiro.exception.CacheManagerPrincipalIdNotAssignedException; |
|||
import org.crazycake.shiro.exception.PrincipalIdNullException; |
|||
import org.crazycake.shiro.exception.PrincipalInstanceException; |
|||
import org.crazycake.shiro.exception.SerializationException; |
|||
import org.crazycake.shiro.serializer.RedisSerializer; |
|||
import org.crazycake.shiro.serializer.StringSerializer; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.lang.reflect.InvocationTargetException; |
|||
import java.lang.reflect.Method; |
|||
import java.util.*; |
|||
|
|||
/** |
|||
* Used for setting/getting authorization information from Redis |
|||
* @param <K> |
|||
* @param <V> |
|||
*/ |
|||
public class RedisCache<K, V> implements Cache<K, V> { |
|||
|
|||
private static Logger logger = LoggerFactory.getLogger(RedisCache.class); |
|||
|
|||
private RedisSerializer keySerializer; |
|||
private RedisSerializer valueSerializer; |
|||
private IRedisManager redisManager; |
|||
private String keyPrefix = RedisCacheManager.DEFAULT_CACHE_KEY_PREFIX; |
|||
private int expire; |
|||
private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME; |
|||
|
|||
/** |
|||
* |
|||
* @param redisManager redisManager |
|||
* @param keySerializer keySerializer |
|||
* @param valueSerializer valueSerializer |
|||
* @param prefix authorization prefix |
|||
* @param expire expire |
|||
* @param principalIdFieldName id field name of principal object |
|||
*/ |
|||
public RedisCache(IRedisManager redisManager, RedisSerializer keySerializer, RedisSerializer valueSerializer, String prefix, int expire, String principalIdFieldName) { |
|||
if (redisManager == null) { |
|||
throw new IllegalArgumentException("redisManager cannot be null."); |
|||
} |
|||
this.redisManager = redisManager; |
|||
if (keySerializer == null) { |
|||
throw new IllegalArgumentException("keySerializer cannot be null."); |
|||
} |
|||
this.keySerializer = keySerializer; |
|||
if (valueSerializer == null) { |
|||
throw new IllegalArgumentException("valueSerializer cannot be null."); |
|||
} |
|||
this.valueSerializer = valueSerializer; |
|||
if (prefix != null && !"".equals(prefix)) { |
|||
this.keyPrefix = prefix; |
|||
} |
|||
this.expire = expire; |
|||
if (principalIdFieldName != null) { |
|||
this.principalIdFieldName = principalIdFieldName; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* get shiro authorization redis key-value |
|||
* @param key key |
|||
* @return value |
|||
* @throws CacheException get cache exception |
|||
*/ |
|||
@Override |
|||
public V get(K key) throws CacheException { |
|||
logger.debug("get key [" + key + "]"); |
|||
|
|||
if (key == null) { |
|||
return null; |
|||
} |
|||
|
|||
try { |
|||
Object redisCacheKey = getRedisCacheKey(key); |
|||
byte[] rawValue = redisManager.get(keySerializer.serialize(redisCacheKey)); |
|||
if (rawValue == null) { |
|||
return null; |
|||
} |
|||
V value = (V) valueSerializer.deserialize(rawValue); |
|||
return value; |
|||
} catch (SerializationException e) { |
|||
throw new CacheException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public V put(K key, V value) throws CacheException { |
|||
if (key == null) { |
|||
logger.warn("Saving a null key is meaningless, return value directly without call Redis."); |
|||
return value; |
|||
} |
|||
try { |
|||
Object redisCacheKey = getRedisCacheKey(key); |
|||
logger.debug("put key [" + redisCacheKey + "]"); |
|||
redisManager.set(keySerializer.serialize(redisCacheKey), value != null ? valueSerializer.serialize(value) : null, expire); |
|||
return value; |
|||
} catch (SerializationException e) { |
|||
throw new CacheException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public V remove(K key) throws CacheException { |
|||
logger.debug("remove key [" + key + "]"); |
|||
if (key == null) { |
|||
return null; |
|||
} |
|||
try { |
|||
Object redisCacheKey = getRedisCacheKey(key); |
|||
byte[] rawValue = redisManager.get(keySerializer.serialize(redisCacheKey)); |
|||
V previous = (V) valueSerializer.deserialize(rawValue); |
|||
redisManager.del(keySerializer.serialize(redisCacheKey)); |
|||
return previous; |
|||
} catch (SerializationException e) { |
|||
throw new CacheException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* get the full Redis key including prefix by Redis key |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
private Object getRedisCacheKey(K key) { |
|||
if (key == null) { |
|||
return null; |
|||
} |
|||
if (keySerializer instanceof StringSerializer) { |
|||
return this.keyPrefix + getStringRedisKey(key); |
|||
} |
|||
return key; |
|||
} |
|||
|
|||
/** |
|||
* get Redis key (not including prefix) |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
private String getStringRedisKey(K key) { |
|||
String redisKey; |
|||
if (key instanceof PrincipalCollection) { |
|||
redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key); |
|||
} else { |
|||
redisKey = key.toString(); |
|||
} |
|||
return redisKey; |
|||
} |
|||
|
|||
/** |
|||
* get the Redis key (not including prefix) by PrincipalCollection |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
private String getRedisKeyFromPrincipalIdField(PrincipalCollection key) { |
|||
Object principalObject = key.getPrimaryPrincipal(); |
|||
if (principalObject instanceof String) { |
|||
return principalObject.toString(); |
|||
} |
|||
Method pincipalIdGetter = getPrincipalIdGetter(principalObject); |
|||
return getIdObj(principalObject, pincipalIdGetter); |
|||
} |
|||
|
|||
private String getIdObj(Object principalObject, Method pincipalIdGetter) { |
|||
String redisKey; |
|||
try { |
|||
Object idObj = pincipalIdGetter.invoke(principalObject); |
|||
if (idObj == null) { |
|||
throw new PrincipalIdNullException(principalObject.getClass(), this.principalIdFieldName); |
|||
} |
|||
redisKey = idObj.toString(); |
|||
} catch (IllegalAccessException e) { |
|||
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e); |
|||
} catch (InvocationTargetException e) { |
|||
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e); |
|||
} |
|||
return redisKey; |
|||
} |
|||
|
|||
private Method getPrincipalIdGetter(Object principalObject) { |
|||
Method pincipalIdGetter = null; |
|||
String principalIdMethodName = this.getPrincipalIdMethodName(); |
|||
try { |
|||
pincipalIdGetter = principalObject.getClass().getMethod(principalIdMethodName); |
|||
} catch (NoSuchMethodException e) { |
|||
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName); |
|||
} |
|||
return pincipalIdGetter; |
|||
} |
|||
|
|||
private String getPrincipalIdMethodName() { |
|||
if (this.principalIdFieldName == null || "".equals(this.principalIdFieldName)) { |
|||
throw new CacheManagerPrincipalIdNotAssignedException(); |
|||
} |
|||
return "get" + this.principalIdFieldName.substring(0, 1).toUpperCase() + this.principalIdFieldName.substring(1); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void clear() throws CacheException { |
|||
logger.debug("clear cache"); |
|||
Set<byte[]> keys = null; |
|||
try { |
|||
keys = redisManager.keys(keySerializer.serialize(this.keyPrefix + "*")); |
|||
} catch (SerializationException e) { |
|||
logger.error("get keys error", e); |
|||
} |
|||
if (keys == null || keys.size() == 0) { |
|||
return; |
|||
} |
|||
for (byte[] key: keys) { |
|||
redisManager.del(key); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* get all authorization key-value quantity |
|||
* @return key-value size |
|||
*/ |
|||
@Override |
|||
public int size() { |
|||
Long longSize = 0L; |
|||
try { |
|||
longSize = new Long(redisManager.dbSize(keySerializer.serialize(this.keyPrefix + "*"))); |
|||
} catch (SerializationException e) { |
|||
logger.error("get keys error", e); |
|||
} |
|||
return longSize.intValue(); |
|||
} |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
@Override |
|||
public Set<K> keys() { |
|||
Set<byte[]> keys = null; |
|||
try { |
|||
keys = redisManager.keys(keySerializer.serialize(this.keyPrefix + "*")); |
|||
} catch (SerializationException e) { |
|||
logger.error("get keys error", e); |
|||
return Collections.emptySet(); |
|||
} |
|||
|
|||
if (CollectionUtils.isEmpty(keys)) { |
|||
return Collections.emptySet(); |
|||
} |
|||
|
|||
Set<K> convertedKeys = new HashSet<K>(); |
|||
for (byte[] key:keys) { |
|||
try { |
|||
convertedKeys.add((K) keySerializer.deserialize(key)); |
|||
} catch (SerializationException e) { |
|||
logger.error("deserialize keys error", e); |
|||
} |
|||
} |
|||
return convertedKeys; |
|||
} |
|||
|
|||
@Override |
|||
public Collection<V> values() { |
|||
Set<byte[]> keys = null; |
|||
try { |
|||
keys = redisManager.keys(keySerializer.serialize(this.keyPrefix + "*")); |
|||
} catch (SerializationException e) { |
|||
logger.error("get values error", e); |
|||
return Collections.emptySet(); |
|||
} |
|||
|
|||
if (CollectionUtils.isEmpty(keys)) { |
|||
return Collections.emptySet(); |
|||
} |
|||
|
|||
List<V> values = new ArrayList<V>(keys.size()); |
|||
for (byte[] key : keys) { |
|||
V value = null; |
|||
try { |
|||
value = (V) valueSerializer.deserialize(redisManager.get(key)); |
|||
} catch (SerializationException e) { |
|||
logger.error("deserialize values= error", e); |
|||
} |
|||
if (value != null) { |
|||
values.add(value); |
|||
} |
|||
} |
|||
return Collections.unmodifiableList(values); |
|||
} |
|||
|
|||
public String getKeyPrefix() { |
|||
return keyPrefix; |
|||
} |
|||
|
|||
public void setKeyPrefix(String keyPrefix) { |
|||
this.keyPrefix = keyPrefix; |
|||
} |
|||
|
|||
public String getPrincipalIdFieldName() { |
|||
return principalIdFieldName; |
|||
} |
|||
|
|||
public void setPrincipalIdFieldName(String principalIdFieldName) { |
|||
this.principalIdFieldName = principalIdFieldName; |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
package org.crazycake.shiro; |
|||
|
|||
import org.apache.shiro.cache.Cache; |
|||
import org.apache.shiro.cache.CacheException; |
|||
import org.apache.shiro.cache.CacheManager; |
|||
import org.crazycake.shiro.serializer.ObjectSerializer; |
|||
import org.crazycake.shiro.serializer.RedisSerializer; |
|||
import org.crazycake.shiro.serializer.StringSerializer; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
public class RedisCacheManager implements CacheManager { |
|||
|
|||
private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class); |
|||
|
|||
// fast lookup by name map
|
|||
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(); |
|||
private RedisSerializer keySerializer = new StringSerializer(); |
|||
private RedisSerializer valueSerializer = new ObjectSerializer(); |
|||
|
|||
private IRedisManager redisManager; |
|||
|
|||
// expire time in seconds
|
|||
public static final int DEFAULT_EXPIRE = 1800; |
|||
private int expire = DEFAULT_EXPIRE; |
|||
|
|||
/** |
|||
* The Redis key prefix for caches |
|||
*/ |
|||
public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:"; |
|||
private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX; |
|||
|
|||
public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "id"; |
|||
private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME; |
|||
|
|||
@Override |
|||
public <K, V> Cache<K, V> getCache(String name) throws CacheException { |
|||
logger.debug("get cache, name=" + name); |
|||
|
|||
Cache<K, V> cache = caches.get(name); |
|||
|
|||
if (cache == null) { |
|||
cache = new RedisCache<K, V>(redisManager, keySerializer, valueSerializer, keyPrefix + name + ":", expire, principalIdFieldName); |
|||
caches.put(name, cache); |
|||
} |
|||
return cache; |
|||
} |
|||
|
|||
public IRedisManager getRedisManager() { |
|||
return redisManager; |
|||
} |
|||
|
|||
public void setRedisManager(IRedisManager redisManager) { |
|||
this.redisManager = redisManager; |
|||
} |
|||
|
|||
public String getKeyPrefix() { |
|||
return keyPrefix; |
|||
} |
|||
|
|||
public void setKeyPrefix(String keyPrefix) { |
|||
this.keyPrefix = keyPrefix; |
|||
} |
|||
|
|||
public RedisSerializer getKeySerializer() { |
|||
return keySerializer; |
|||
} |
|||
|
|||
public void setKeySerializer(RedisSerializer keySerializer) { |
|||
this.keySerializer = keySerializer; |
|||
} |
|||
|
|||
public RedisSerializer getValueSerializer() { |
|||
return valueSerializer; |
|||
} |
|||
|
|||
public void setValueSerializer(RedisSerializer valueSerializer) { |
|||
this.valueSerializer = valueSerializer; |
|||
} |
|||
|
|||
public int getExpire() { |
|||
return expire; |
|||
} |
|||
|
|||
public void setExpire(int expire) { |
|||
this.expire = expire; |
|||
} |
|||
|
|||
public String getPrincipalIdFieldName() { |
|||
return principalIdFieldName; |
|||
} |
|||
|
|||
public void setPrincipalIdFieldName(String principalIdFieldName) { |
|||
this.principalIdFieldName = principalIdFieldName; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue