医院网站建设预算网页制作与网站发布
医院网站建设预算,网页制作与网站发布,120亿营收超120亿,可以分为( )DeOldify赋能微信小程序#xff1a;老照片上色功能的快速实现
家里的老相册#xff0c;总藏着几页泛黄的黑白照片。那是爷爷奶奶的结婚照#xff0c;是父母年轻时的模样#xff0c;是我们回不去的童年。这些照片承载着记忆#xff0c;却因为褪色而显得有些遥远。现在 // 核心服务类后面会写 PostMapping(/upload) public ApiResponse uploadPhoto(RequestParam(file) MultipartFile file, RequestParam(userId) String userId) { if (file.isEmpty()) { return ApiResponse.error(请选择要上传的照片); } try { // 1. 保存上传的原始图片到服务器临时目录 String originalFileName file.getOriginalFilename(); String fileExtension originalFileName.substring(originalFileName.lastIndexOf(.)); String savedFileName UUID.randomUUID().toString() fileExtension; Path savePath Paths.get(/tmp/uploads, savedFileName); Files.createDirectories(savePath.getParent()); Files.write(savePath, file.getBytes()); // 2. 调用服务提交照片到DeOldify进行处理并获取任务ID String taskId deOldifyService.submitColorizationTask(savePath.toString(), userId); // 3. 返回任务ID给小程序小程序凭此ID查询结果 MapString, String data new HashMap(); data.put(taskId, taskId); data.put(message, 照片已接收正在上色中请稍后查询结果); return ApiResponse.success(data); } catch (IOException e) { e.printStackTrace(); return ApiResponse.error(文件保存失败); } catch (Exception e) { e.printStackTrace(); return ApiResponse.error(提交上色任务失败); } } }2. 结果查询控制器 (TaskQueryController)小程序拿到taskId后会定期比如每5秒调用这个接口询问“我的照片上色好了吗”RestController RequestMapping(/api/task) public class TaskQueryController { Autowired private DeOldifyService deOldifyService; GetMapping(/status) public ApiResponse getTaskStatus(RequestParam String taskId) { TaskResult result deOldifyService.getTaskResult(taskId); if (result null) { return ApiResponse.error(未找到该任务); } MapString, Object data new HashMap(); data.put(taskId, taskId); data.put(status, result.getStatus()); // 状态 processing, success, failed data.put(message, result.getMessage()); // 如果任务成功返回彩色图片的访问地址 if (success.equals(result.getStatus())) { data.put(coloredImageUrl, result.getColoredImageUrl()); } return ApiResponse.success(data); } }2.3 核心服务与星图DeOldify API对话DeOldifyService是后端最核心的部分它封装了与星图平台API的所有交互逻辑。这里假设星图平台提供了标准的RESTful API包含“提交任务”和“查询任务结果”两个端点。Service public class DeOldifyService { // 这些配置应该放在application.yml中 Value(${starchart.api.base-url}) private String apiBaseUrl; Value(${starchart.api.key}) private String apiKey; // 用一个Map在内存中模拟任务状态存储生产环境应该用Redis或数据库 private ConcurrentHashMapString, TaskResult taskStore new ConcurrentHashMap(); /** * 提交上色任务到星图平台 */ public String submitColorizationTask(String imagePath, String userId) throws Exception { String taskId TASK_ System.currentTimeMillis() _ userId; // 1. 构建提交任务的请求体根据星图平台API文档调整 MapString, Object requestBody new HashMap(); requestBody.put(image_url, uploadToTempUrl(imagePath)); // 需要先将图片上传到一个可公网访问的临时地址或使用Base64 requestBody.put(task_id, taskId); // 2. 调用星图API的提交接口 String submitUrl apiBaseUrl /v1/colorize/submit; HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer apiKey); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object requestEntity new HttpEntity(requestBody, headers); RestTemplate restTemplate new RestTemplate(); ResponseEntityMap response restTemplate.postForEntity(submitUrl, requestEntity, Map.class); // 3. 初始化任务状态 TaskResult taskResult new TaskResult(); taskResult.setTaskId(taskId); taskResult.setStatus(processing); taskResult.setMessage(任务已提交等待处理); taskStore.put(taskId, taskResult); // 4. 启动一个异步线程去轮询任务结果生产环境建议用消息队列或定时任务 new Thread(() - pollTaskResult(taskId)).start(); return taskId; } /** * 轮询查询任务结果 */ private void pollTaskResult(String taskId) { String queryUrl apiBaseUrl /v1/colorize/result?task_id taskId; HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer apiKey); boolean isFinished false; int maxRetries 60; // 最多轮询60次 int interval 3000; // 每次间隔3秒 for (int i 0; i maxRetries !isFinished; i) { try { Thread.sleep(interval); RestTemplate restTemplate new RestTemplate(); HttpEntityString entity new HttpEntity(headers); ResponseEntityMap response restTemplate.exchange(queryUrl, HttpMethod.GET, entity, Map.class); MapString, Object resultBody response.getBody(); String status (String) resultBody.get(status); TaskResult taskResult taskStore.get(taskId); if (completed.equals(status)) { // 任务完成下载彩色图片并保存到本地或云存储 String coloredImageUrl (String) resultBody.get(result_url); String savedUrl downloadAndSaveImage(coloredImageUrl, taskId); taskResult.setStatus(success); taskResult.setColoredImageUrl(savedUrl); // 返回给小程序访问的地址 taskResult.setMessage(照片上色完成); isFinished true; } else if (failed.equals(status)) { taskResult.setStatus(failed); taskResult.setMessage(上色处理失败: resultBody.get(error)); isFinished true; } // 如果状态是processing继续轮询 } catch (Exception e) { e.printStackTrace(); TaskResult taskResult taskStore.get(taskId); taskResult.setStatus(failed); taskResult.setMessage(查询任务结果时发生异常); isFinished true; } } // 超时处理 if (!isFinished) { TaskResult taskResult taskStore.get(taskId); if (processing.equals(taskResult.getStatus())) { taskResult.setStatus(failed); taskResult.setMessage(任务处理超时); } } } public TaskResult getTaskResult(String taskId) { return taskStore.get(taskId); } // 辅助方法下载图片并保存 private String downloadAndSaveImage(String imageUrl, String taskId) throws IOException { // 实现从imageUrl下载图片保存到服务器或云存储如OSS // 返回一个能通过公网访问的URL例如http://your-domain.com/results/colored_taskId.jpg // 这里简化返回一个示例URL return /results/colored_ taskId .jpg; } }这个服务类做了几件关键事封装API调用、管理任务状态、异步轮询结果。在生产环境中轮询部分最好改用更可靠的消息队列或事件驱动机制。3. 前端核心微信小程序页面开发小程序端主要负责友好的用户交互上传界面、等待提示、结果展示。3.1 页面布局与样式 (index.wxml)我们设计一个简单的页面包含上传按钮、预览区域和状态提示。!-- index.wxml -- view classcontainer view classheader text classtitle老照片上色助手/text text classsubtitle上传黑白照片AI为您还原色彩/text /view view classupload-area bindtapchooseImage wx:if{{!originalImage}} image src/images/upload-icon.png classupload-icon/image text classupload-text点击上传黑白/老照片/text text classupload-hint支持JPG、PNG格式建议尺寸小于5MB/text /view view classpreview-section wx:if{{originalImage}} view classimage-comparison view classimage-box text classimage-label原图/text image src{{originalImage}} modewidthFix classpreview-image/image /view view classimage-box wx:if{{coloredImage}} text classimage-label上色后/text image src{{coloredImage}} modewidthFix classpreview-image/image /view /view button typeprimary bindtapstartColorization loading{{isProcessing}} disabled{{isProcessing}} {{isProcessing ? 上色中... : 开始AI上色}} /button button typedefault bindtapreset wx:if{{!isProcessing}}重新选择照片/button /view view classstatus-message wx:if{{statusMessage}} text{{statusMessage}}/text /view view classtips text使用提示\n1. 照片人物面部清晰效果更佳\n2. 处理通常需要30-60秒\n3. 结果图片可长按保存/text /view /view3.2 页面逻辑与交互 (index.js)小程序的JavaScript逻辑处理图片选择、上传、轮询查询等。// index.js Page({ data: { originalImage: , // 上传的原图临时路径 coloredImage: , // 上色后的图片URL isProcessing: false, // 是否正在处理中 statusMessage: , // 状态提示信息 currentTaskId: null, // 当前任务ID pollTimer: null // 轮询定时器 }, // 选择图片 chooseImage: function() { const that this; wx.chooseImage({ count: 1, sizeType: [compressed], // 压缩图 sourceType: [album, camera], success(res) { const tempFilePath res.tempFilePaths[0]; that.setData({ originalImage: tempFilePath, coloredImage: , statusMessage: 照片已选择点击下方按钮开始上色 }); } }) }, // 开始上色处理 startColorization: function() { const that this; const filePath this.data.originalImage; if (!filePath) { wx.showToast({ title: 请先选择照片, icon: none }); return; } this.setData({ isProcessing: true, statusMessage: 正在上传并处理照片请稍候... }); // 1. 上传图片到后端服务器 wx.uploadFile({ url: https://your-backend-domain.com/api/photo/upload, // 替换为你的后端地址 filePath: filePath, name: file, formData: { userId: wx.getStorageSync(userId) || anonymous_ Date.now() }, success(res) { const data JSON.parse(res.data); if (data.code 200) { const taskId data.data.taskId; that.setData({ currentTaskId: taskId, statusMessage: 照片上传成功AI正在努力上色中... }); // 2. 开始轮询查询任务结果 that.startPollingTaskResult(taskId); } else { wx.showToast({ title: 上传失败 data.message, icon: none }); that.setData({ isProcessing: false, statusMessage: }); } }, fail() { wx.showToast({ title: 网络请求失败, icon: none }); that.setData({ isProcessing: false, statusMessage: }); } }); }, // 轮询查询任务状态 startPollingTaskResult: function(taskId) { const that this; let pollCount 0; const maxPollCount 60; // 最多轮询60次 // 清除之前的定时器 if (this.data.pollTimer) { clearInterval(this.data.pollTimer); } const timer setInterval(() { pollCount; if (pollCount maxPollCount) { clearInterval(timer); that.setData({ isProcessing: false, statusMessage: 处理超时请稍后重试, pollTimer: null }); return; } wx.request({ url: https://your-backend-domain.com/api/task/status, // 替换为你的后端地址 method: GET, data: { taskId: taskId }, success(res) { const data res.data; if (data.code 200) { const status data.data.status; if (status success) { // 处理成功显示彩色图片 clearInterval(timer); that.setData({ isProcessing: false, coloredImage: data.data.coloredImageUrl, statusMessage: 上色完成快看看效果吧~, pollTimer: null }); wx.showToast({ title: 上色成功, icon: success }); } else if (status failed) { // 处理失败 clearInterval(timer); that.setData({ isProcessing: false, statusMessage: 上色失败 data.data.message, pollTimer: null }); wx.showToast({ title: 处理失败, icon: none }); } else { // 仍在处理中更新等待信息 that.setData({ statusMessage: AI正在处理中... (${pollCount * 3}秒) }); } } }, fail() { // 网络错误继续轮询 console.log(轮询请求失败继续尝试); } }); }, 3000); // 每3秒轮询一次 this.setData({ pollTimer: timer }); }, // 重置重新选择照片 reset: function() { if (this.data.pollTimer) { clearInterval(this.data.pollTimer); } this.setData({ originalImage: , coloredImage: , isProcessing: false, statusMessage: , currentTaskId: null, pollTimer: null }); }, onUnload: function() { // 页面卸载时清除定时器 if (this.data.pollTimer) { clearInterval(this.data.pollTimer); } } })3.3 页面样式 (index.wxss)/* index.wxss */ .container { padding: 30rpx; display: flex; flex-direction: column; align-items: center; min-height: 100vh; background-color: #f8f9fa; } .header { text-align: center; margin-bottom: 60rpx; } .title { font-size: 48rpx; font-weight: bold; color: #333; display: block; margin-bottom: 16rpx; } .subtitle { font-size: 28rpx; color: #666; } .upload-area { width: 600rpx; height: 400rpx; border: 4rpx dashed #ccc; border-radius: 24rpx; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #fff; margin-bottom: 40rpx; } .upload-area:active { background-color: #f5f5f5; } .upload-icon { width: 120rpx; height: 120rpx; margin-bottom: 30rpx; opacity: 0.6; } .upload-text { font-size: 32rpx; color: #555; margin-bottom: 16rpx; } .upload-hint { font-size: 24rpx; color: #999; } .preview-section { width: 100%; } .image-comparison { display: flex; justify-content: space-around; margin-bottom: 50rpx; } .image-box { display: flex; flex-direction: column; align-items: center; } .image-label { font-size: 28rpx; color: #666; margin-bottom: 20rpx; } .preview-image { width: 320rpx; border-radius: 16rpx; box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.1); } button { width: 80%; margin-bottom: 30rpx; border-radius: 50rpx; font-size: 32rpx; } button[typeprimary] { background-color: #07c160; } .status-message { padding: 20rpx 40rpx; background-color: #e6f3ff; border-radius: 12rpx; margin: 30rpx 0; width: 80%; text-align: center; font-size: 28rpx; color: #0066cc; } .tips { margin-top: 60rpx; padding: 30rpx; background-color: #fff8e6; border-radius: 16rpx; width: 90%; font-size: 26rpx; color: #8a6d3b; line-height: 1.6; white-space: pre-line; }4. 实际应用与效果把前后端代码都部署好后这个小程序就可以跑起来了。实际用起来流程非常顺畅用户打开小程序看到一个简洁的上传页面。从手机相册选一张黑白的老照片比如一张几十年前的家庭合影。点击“开始AI上色”按钮照片开始上传界面显示“正在处理中...”。等待大约30-60秒具体时间取决于照片大小和服务器负载期间小程序会安静地在后台轮询结果。处理完成后页面会自动刷新原图旁边会出现一张色彩还原后的新图。人物的肤色、衣物的颜色、背景的景物都被AI智能地填充上了合理的色彩。用户可以左右对比如果满意可以长按彩色图片保存到手机。对于用户来说他们无需了解背后的技术细节只需要几次点击就能完成一次老照片的修复。这种“即拍即得”或“即传即得”的体验对于传播和分享非常有吸引力。你可以想象用户将修复后的彩色照片分享到朋友圈本身就是对小程序最好的推广。5. 总结通过这个项目我们完成了一个从想法到产品的完整闭环。前端微信小程序负责收集用户输入和展示结果提供了便捷的移动端入口后端SpringBoot服务作为中坚调度者处理文件、调用AI、管理任务状态而星图平台上的DeOldify模型则提供了核心的AI上色能力我们无需关心复杂的模型训练和部署通过API即可调用。整个过程涉及了小程序开发、文件上传、后端API编写、异步任务处理和第三方服务集成是一个很好的全栈实践。在实际部署时你还需要考虑一些工程化问题比如将图片存储到对象存储服务如OSS以节省服务器磁盘空间使用Redis来存储任务状态以保证服务重启后不丢失以及为API调用增加限流和降级策略以保证服务稳定性。这个案例的框架可以很容易地扩展到其他AI能力上比如人像卡通化、风格迁移、图像超分辨率等。只需要把后端的API调用换成其他模型的接口前端稍作调整一个新的小程序就又诞生了。希望这个实践能给你带来启发动手试试把你家的老照片也变成彩色的吧。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。