package jnpf.base.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import com.alibaba.fastjson.JSONObject; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import jnpf.base.ActionResult; import jnpf.base.UserInfo; import jnpf.base.entity.VisualdevEntity; import jnpf.base.entity.VisualdevReleaseEntity; import jnpf.base.entity.VisualdevShortLinkEntity; import jnpf.base.model.ColumnDataModel; import jnpf.base.model.VisualDevJsonModel; import jnpf.base.model.VisualWebTypeEnum; import jnpf.base.model.online.VisualdevModelDataInfoVO; import jnpf.base.model.shortlink.*; import jnpf.base.service.VisualdevReleaseService; import jnpf.base.service.VisualdevService; import jnpf.base.service.VisualdevShortLinkService; import jnpf.base.util.VisualUtil; import jnpf.base.vo.PageListVO; import jnpf.base.vo.PaginationVO; import jnpf.config.ConfigValueUtil; import jnpf.constant.MsgCode; import jnpf.consts.DeviceType; import jnpf.database.util.TenantDataSourceUtil; import jnpf.exception.DataException; import jnpf.exception.LoginException; import jnpf.exception.WorkFlowException; import jnpf.model.OnlineDevData; import jnpf.onlinedev.model.DataInfoVO; import jnpf.onlinedev.model.PaginationModel; import jnpf.onlinedev.model.VisualParamModel; import jnpf.onlinedev.model.VisualdevModelDataCrForm; import jnpf.onlinedev.service.VisualDevInfoService; import jnpf.onlinedev.service.VisualDevListService; import jnpf.onlinedev.service.VisualdevModelDataService; import jnpf.onlinedev.util.OnlinePublicUtils; import jnpf.onlinedev.util.OnlineSwapDataUtils; import jnpf.util.*; import jnpf.util.context.RequestContext; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Map; /** * 在线开发表单外链Controller * * @author JNPF开发平台组 * @version V3.4.2 * @copyright 引迈信息技术有限公司 * @date 2022/12/30 11:33:17 */ @Tag(name = "表单外链", description = "BaseShortLink") @RestController @RequiredArgsConstructor // 🔴 2026-08-11 平台聚合改名:原为 "/ShortLink",与 jnpf-message 的 ShortLinkController // (短链跳转)在同一 JVM 内相撞——本类的 GET /{id} 与它的 GET /{shortLink} pattern 等价, // Spring **启动时不报错**,运行时才抛 IllegalStateException: Ambiguous handler methods, // 且被全局异常处理器包装成 {"code":400,"msg":"系统异常"},调用方看不到真实原因。 // 改内部路径而非 message 侧:message 的 /api/message/ShortLink/ 被写死在 // SendFlowMsgUtil / SentMessageUtil 里用于生成发给用户的短信/邮件短链,是对外硬契约。 // ⚠️ 对外路径 /api/visualdev/ShortLink/** 不变——网关 router.yaml 的 // jnpf-visualdev-shortlink 路由用 RewritePath 原样重写到这里,已生成的表单外链继续有效。 // 改这里必须同步改那条网关路由,两者是一对。 @RequestMapping("/VisualdevShortLink") public class VisualdevShortLinkController extends SuperController { private final VisualdevShortLinkService visualdevShortLinkService; private final ConfigValueUtil configValueUtil; private final VisualdevService visualdevService; private final VisualdevReleaseService visualdevReleaseService; private final VisualdevModelDataService visualdevModelDataService; private final OnlineSwapDataUtils onlineSwapDataUtils; private final VisualDevListService visualDevListService; private final VisualDevInfoService visualDevInfoService; @Operation(summary = "获取外链信息") @Parameter(name = "id", description = "主键") @GetMapping("/{id}") @SaCheckPermission("onlineDev.formDesign") public ActionResult getInfo(@PathVariable("id") String id) { VisualdevShortLinkEntity info = visualdevShortLinkService.getById(id); VisualdevShortLinkVo vo; if (info != null) { vo = JsonUtil.getJsonToBean(info, VisualdevShortLinkVo.class); vo.setAlreadySave(true); } else { vo = new VisualdevShortLinkVo(); vo.setId(id); } vo.setFormLink(geturl(id, "form")); vo.setColumnLink(geturl(id, "list")); return ActionResult.success(vo); } /** * 获取url * * @param * @return * @copyright 引迈信息技术有限公司 * @date 2023/3/9 */ private String geturl(String id, String type) { String url = configValueUtil.getApiDomain() + "/api/visualdev/ShortLink/trigger/" + id + "?encryption="; UserInfo userInfo = UserProvider.getUser(); JSONObject obj = new JSONObject(); obj.put("type", type); if (configValueUtil.isMultiTenancy()) { obj.put("tenantId", userInfo.getTenantId()); } //参数加密 String encryption = DesUtil.aesOrDecode(obj.toJSONString(), true, true); url += encryption; return url; } @Operation(summary = "修改外链信息") @PutMapping("") @SaCheckPermission("onlineDev.formDesign") public ActionResult saveOrupdate(@RequestBody VisualdevShortLinkForm data) { VisualdevShortLinkEntity entity = JsonUtil.getJsonToBean(data, VisualdevShortLinkEntity.class); if (entity.getFormLink().contains(configValueUtil.getApiDomain())) { entity.setFormLink(entity.getFormLink().replace(configValueUtil.getApiDomain(), "")); } if (entity.getColumnLink().contains(configValueUtil.getApiDomain())) { entity.setColumnLink(entity.getColumnLink().replace(configValueUtil.getApiDomain(), "")); } VisualdevShortLinkEntity info = visualdevShortLinkService.getById(data.getId()); UserInfo userInfo = UserProvider.getUser(); if (info != null) { entity.setLastModifyTime(new Date()); entity.setLastModifyUserId(userInfo.getUserId()); } else { entity.setCreatorTime(new Date()); entity.setCreatorUserId(userInfo.getUserId()); } String pcLink = "/formShortLink"; String appLink = "/pages/formShortLink/index"; entity.setRealPcLink(pcLink); entity.setRealAppLink(appLink); entity.setUserId(userInfo.getUserId()); visualdevShortLinkService.saveOrUpdate(entity); return ActionResult.success(MsgCode.SU002.get()); } /** * 参数解密切换数据源 * * @param * @return * @copyright 引迈信息技术有限公司 * @date 2023/3/9 */ private VisualdevShortLinkModel aesDecodeMatchDatabase(String encryption) throws LoginException { //参数解密 String str = DesUtil.aesOrDecode(encryption, false, true); if (StringUtil.isEmpty(str)) { throw new LoginException(MsgCode.VS009.get()); } VisualdevShortLinkModel model = JsonUtil.getJsonToBean(str, VisualdevShortLinkModel.class); if (configValueUtil.isMultiTenancy()) { if (StringUtil.isNotEmpty(model.getTenantId())) { //切换成租户库 TenantDataSourceUtil.switchTenant(model.getTenantId()); } else { throw new LoginException(MsgCode.LOG115.get()); } } return model; } @NoDataSourceBind @Operation(summary = "外链请求入口") @Parameter(name = "id", description = "主键") @GetMapping("/trigger/{id}") public ActionResult getlink(@PathVariable("id") String id, @RequestParam(value = "encryption") String encryption, HttpServletResponse response) throws LoginException, IOException { VisualdevShortLinkModel model = aesDecodeMatchDatabase(encryption); String link = ""; VisualdevShortLinkEntity entity = visualdevShortLinkService.getById(id); DeviceType deviceType = UserProvider.getDeviceForAgent(); if (entity != null) { if (DeviceType.PC.equals(deviceType)) { link = configValueUtil.getFrontDomain() + entity.getRealPcLink(); } else { link = configValueUtil.getAppDomain() + entity.getRealAppLink(); } } else { return ActionResult.fail(MsgCode.VS010.get()); } JSONObject obj = new JSONObject(); obj.put("modelId", id); obj.put("type", model.getType()); if (configValueUtil.isMultiTenancy()) { obj.put("tenantId", model.getTenantId()); } //新链接参数加密 String encryptionNew = DesUtil.aesOrDecode(obj.toJSONString(), true, true); link += "?encryption=" + encryptionNew; response.sendRedirect(link); return ActionResult.success(MsgCode.SU000.get()); } @NoDataSourceBind @Operation(summary = "获取外链配置") @Parameter(name = "id", description = "主键") @GetMapping("/getConfig/{id}") public ActionResult getConfig(@PathVariable("id") String id, @RequestParam("encryption") String encryption) throws LoginException { aesDecodeMatchDatabase(encryption); VisualdevShortLinkEntity info = visualdevShortLinkService.getById(id); VisualdevShortLinkConfigVo vo = JsonUtil.getJsonToBean(info, VisualdevShortLinkConfigVo.class); vo.setFormLink(geturl(id, "form")); vo.setColumnLink(geturl(id, "list")); return ActionResult.success(vo); } @NoDataSourceBind @Operation(summary = "密码验证") @PostMapping("/checkPwd") public ActionResult checkPwd(@RequestBody VisualdevShortLinkPwd form) throws LoginException { //参数解密 aesDecodeMatchDatabase(form.getEncryption()); VisualdevShortLinkEntity info = visualdevShortLinkService.getById(form.getId()); boolean flag = false; if ((OnlineDevData.STATE_ENABLE.equals(info.getFormPassUse()) && 0 == form.getType() && Md5Util.getStringMd5(info.getFormPassword()).equals(form.getPassword())) || (OnlineDevData.STATE_ENABLE.equals(info.getColumnPassUse()) && 1 == form.getType() && Md5Util.getStringMd5(info.getColumnPassword()).equals(form.getPassword()))) { flag = true; } if (flag) { return ActionResult.success(); } return ActionResult.fail(MsgCode.VS011.get()); } @NoDataSourceBind @Operation(summary = "获取列表表单配置JSON") @GetMapping("/{modelId}/Config") public ActionResult getData(@PathVariable("modelId") String modelId, @RequestParam(value = "type", required = false) String type, @RequestParam("encryption") String encryption) throws LoginException { aesDecodeMatchDatabase(encryption); VisualdevEntity entity; //线上版本 if ("0".equals(type)) { entity = visualdevService.getInfo(modelId); } else { VisualdevReleaseEntity releaseEntity = visualdevReleaseService.getById(modelId); entity = JsonUtil.getJsonToBean(releaseEntity, VisualdevEntity.class); } if (entity == null) { return ActionResult.fail(MsgCode.VS012.get()); } String s = VisualUtil.checkPublishVisualModel(entity, MsgCode.VS005.get()); if (s != null) { return ActionResult.fail(s); } DataInfoVO vo = JsonUtil.getJsonToBean(entity, DataInfoVO.class); return ActionResult.success(vo); } @NoDataSourceBind @Operation(summary = "外链数据列表") @Parameter(name = "modelId", description = "模板id") @PostMapping("/{modelId}/ListLink") public ActionResult>> listLink(@PathVariable("modelId") String modelId, @RequestParam("encryption") String encryption, @RequestBody PaginationModel paginationModel) throws WorkFlowException, LoginException { aesDecodeMatchDatabase(encryption); VisualdevReleaseEntity visualdevEntity = visualdevReleaseService.getById(modelId); VisualDevJsonModel visualJsonModel = OnlinePublicUtils.getVisualJsonModel(visualdevEntity); //判断请求客户端来源 if (!RequestContext.isOrignPc()) { visualJsonModel.setColumnData(visualJsonModel.getAppColumnData()); } List> realList; if (VisualWebTypeEnum.DATA_VIEW.getType().equals(visualdevEntity.getWebType())) {// //数据视图的接口数据获取、 ColumnDataModel columnDataModel = JsonUtil.getJsonToBean(visualdevEntity.getColumnData(), ColumnDataModel.class); realList = onlineSwapDataUtils.getInterfaceData(visualdevEntity, paginationModel, columnDataModel); } else { realList = visualDevListService.getDataListLink(visualJsonModel, paginationModel); } PaginationVO paginationVO = JsonUtil.getJsonToBean(paginationModel, PaginationVO.class); return ActionResult.page(realList, paginationVO); } @NoDataSourceBind @Operation(summary = "获取数据信息(带转换数据)") @Parameter(name = "modelId", description = "模板id") @Parameter(name = "id", description = "数据id") @GetMapping("/{modelId}/{id}/DataChange") public ActionResult infoWithDataChange(@PathVariable("modelId") String modelId, @PathVariable("id") String id, @RequestParam("encryption") String encryption) throws DataException, LoginException { aesDecodeMatchDatabase(encryption); modelId = XSSEscape.escape(modelId); id = XSSEscape.escape(id); VisualdevEntity visualdevEntity = visualdevService.getReleaseInfo(modelId); VisualdevModelDataInfoVO vo = visualDevInfoService.getDetailsDataInfo(id, visualdevEntity); return ActionResult.success(vo); } //**********以下微服务和单体不同 @NoDataSourceBind @Operation(summary = "添加数据") @Parameter(name = "modelId", description = "模板id") @Parameter(name = "visualdevModelDataCrForm", description = "功能数据创建表单") @PostMapping("/{modelId}") public ActionResult create(@PathVariable("modelId") String modelId, @RequestParam("encryption") String encryption, @RequestBody VisualdevModelDataCrForm visualdevModelDataCrForm) throws WorkFlowException, LoginException { VisualdevShortLinkModel visualdevShortLinkModel = aesDecodeMatchDatabase(encryption); VisualdevShortLinkEntity info = visualdevShortLinkService.getById(modelId); if (1 != info.getFormUse()) { return ActionResult.fail(MsgCode.VS013.get()); } String tenantId = visualdevShortLinkModel.getTenantId(); try { if (configValueUtil.isMultiTenancy()) { if (StringUtil.isNotEmpty(tenantId)) { //切换成租户库 TenantDataSourceUtil.switchTenant(tenantId); } else { return ActionResult.fail(MsgCode.LOG115.get()); } } VisualdevEntity visualdevEntity = visualdevService.getReleaseInfo(modelId); Map map = JsonUtil.stringToMap(visualdevModelDataCrForm.getData()); visualdevModelDataService.visualCreate(VisualParamModel.builder().visualdevEntity(visualdevEntity).data(map).isLink(true).build()); } catch (Exception e) { throw new WorkFlowException(e.getMessage(), e); } return ActionResult.success(MsgCode.SU001.get()); } }