java实现图片水印添加并自动上传七牛云

图片左下角水印添加

满足需求:可以对不同类型尺寸的照片、图片进行水印的添加,实现尺寸自适应添加水印。

水印效果

在这里插入图片描述

代码实现

Controller

package com.wlh.zetc.restore.controller;

import cn.hutool.core.date.DateUtil;
import com.alibaba.nacos.client.utils.TenantUtil;
import com.wlh.zetc.admin.api.feign.RemoteTenantService;
import com.wlh.zetc.common.core.util.R;
import com.wlh.zetc.common.data.tenant.TenantBroker;
import com.wlh.zetc.common.data.tenant.TenantContextHolder;
import com.wlh.zetc.restore.service.QiniuService;
import com.wlh.zetc.restore.utils.ComplexWatermarkUtils;
import com.wlh.zetc.restore.utils.TextUtils;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * 七牛云存储
 *
 * @author wanghailin
 * @date 2024-03-12 10:19:56
 */
@Controller
@RequestMapping("/qiniu" )
@Tag(description = "file" , name = "七牛云文件存储" )
@AllArgsConstructor
//@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class QiniuController
{
	private final QiniuService qiniuService;
	private final ComplexWatermarkUtils complexWatermarkUtils;
	/**
	 *
	 * @param file
	 * @param location
	 * @param latitudeLongitude
	 * @param remarks
	 * @param weather
	 * @param watermark 0:不添加水印 1:添加水印 2:添加水印但是不添加天气、位置、经纬度
	 * @return
	 * @throws IOException
	 */
	@PostMapping("/image/upload")
	@ResponseBody
	public R uploadImage(@RequestParam(value = "file", required = false) MultipartFile file,
						 @RequestParam(value = "location", required = false) String location,
						 @RequestParam(value = "latitudeLongitude", required = false) String latitudeLongitude,
						 @RequestParam(value = "remarks", required = false) String remarks,
						 @RequestParam(value = "weather", required = false) String weather,
						 @RequestParam(value = "watermark", required = false,defaultValue = "1") Integer watermark) throws IOException {
		MultipartFile multipartFile = null;
		if (watermark != null && watermark.equals(0)){
			multipartFile = file;
		}else {
			multipartFile = complexWatermarkUtils.addComplexWatermark(file, DateUtil.now(), location, weather,latitudeLongitude,remarks,watermark);
		}
		System.out.println(multipartFile.getSize());
		if(!multipartFile.isEmpty())
		{
			InputStream in = multipartFile.getInputStream();
			String filename = multipartFile.getOriginalFilename();
			String project = "project"+ TenantContextHolder.getTenantId()+"/";
			try
			{
				String key = project+ TextUtils.generateFileName(filename);
				String imageUrl = qiniuService.uploadImage2qiniu(in,key);
				if(imageUrl!=null)
				{
					HashMap<String, String> map = new HashMap<>();
					String privateDownloadUrl = qiniuService.getPrivateDownloadUrl(imageUrl);
					map.put("url",imageUrl);
					map.put("accessibleUrl",privateDownloadUrl);
					return R.ok(map).setMsg("上传成功");
				}
			}
			catch(IllegalArgumentException e)
			{
				e.printStackTrace();
			}
		}
		return R.failed("上传失败");
	}

	@DeleteMapping("/image/delete")
	@ResponseBody
	public R deleteImage(@RequestParam("image")String imageUrl)
	{
		boolean deleted = qiniuService.deleteImageFromQiniu(imageUrl);
		if(deleted)
		{
			return R.ok(null,"删除成功");
		}
		return R.failed("删除失败");
	}


	@GetMapping("/image/download")
	@ResponseBody
	public R downloadImage(@RequestParam("image")String imageUrl)
	{
		try {
			String privateDownloadUrl = qiniuService.getPrivateDownloadUrl(imageUrl);
			Map<String, String> map = new HashMap<>();
			map.put("url",privateDownloadUrl);
			return R.ok().setData(map);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return R.ok();
	}
}

Utils

package com.wlh.zetc.restore.utils;

import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.wlh.zetc.admin.api.entity.SysTenant;
import com.wlh.zetc.admin.api.feign.RemoteTenantService;
import com.wlh.zetc.admin.api.feign.RemoteUserService;
import com.wlh.zetc.common.core.util.R;
import com.wlh.zetc.common.data.tenant.TenantContextHolder;
import com.wlh.zetc.common.security.util.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * 至农水印处理工具类
 *
 * @author wanghailin
 * @date 2024-03-18 10:49:33
 */
@Service
@AllArgsConstructor
public class ComplexWatermarkUtils {

	private final RemoteTenantService tenantService;

	//水印图片地址
	private static String watermarkImageURL = "https://qiniu.zjshjkj.com/logo/zhinong12.png";

	//水印添加
	public MultipartFile addComplexWatermark(MultipartFile file, String time, String location, String weather, String latitudeLongitude, String remarks,Integer watermark) {
		try {

			//MultipartFile multipartFile = correctImg(file);
			BufferedImage srcImg = null;
			srcImg = ImgUtil.read(file.getInputStream());
			Graphics2D g2d = srcImg.createGraphics();
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			int watermarkAreaHeight = (int) (srcImg.getHeight() * 0.10);
			int watermarkAreaWide = (int) (srcImg.getWidth() * 0.01);
			BufferedImage watermarkImage = ImgUtil.read(getWatermarkImage(watermarkImageURL).getInputStream());
			int scaledWatermarkWidth = watermarkImage.getWidth() * watermarkAreaHeight / watermarkImage.getHeight();
			Image scale = ImgUtil.scale(watermarkImage, scaledWatermarkWidth, watermarkAreaHeight);

			int x = watermarkAreaWide;
			int y = watermarkAreaHeight * 7 - watermarkAreaHeight / 2;
			g2d.drawImage(scale, x, y, null);
			//获取当前项目名称
			R tenant = tenantService.getByTenantId(TenantContextHolder.getTenantId());
			SysTenant project = (SysTenant) tenant.getData();
			// 设置较大字体属性,仅用于 projectName
			int projectNameFontSize = watermarkAreaHeight / 3;
			Font projectNameFont = new Font("宋体", Font.BOLD, projectNameFontSize);
			g2d.setFont(projectNameFont);
			g2d.setColor(Color.WHITE);
			// 将 projectName 分割为多行文本
			if(StringUtils.isNotEmpty(project.getName())){
				List<String> projectNameLines =splitTextToLines(project.getName(), 20);
				int projectNameY = y + watermarkAreaHeight +projectNameFontSize;
				for (String line : projectNameLines) {
					g2d.drawString(line, x, projectNameY);
					projectNameY += g2d.getFontMetrics().getHeight();
				}
				// 设置文本属性,根据水印区域大小调整字体大小
				int fontSize = watermarkAreaHeight / 5;
				Font font = new Font("宋体", Font.BOLD, fontSize);
				g2d.setFont(font);
				FontMetrics metrics = g2d.getFontMetrics(font);
				int textHeight = metrics.getHeight();
				// 绘制黄色竖杠
				g2d.setColor(Color.YELLOW);
				int barX = x;
				int barY = projectNameY - watermarkAreaHeight / 3; // 根据 projectName 的最终位置调整
				if(watermark.equals(2)){
					g2d.fillRect(barX, barY, textHeight /4, textHeight * 2 + 5 * 3);
				}else {
					g2d.fillRect(barX, barY, textHeight /4, textHeight * 5 + 5 * 3);
				}
				g2d.setColor(Color.WHITE);
				int textX = barX + watermarkAreaWide ;
				int textY = barY + textHeight;

				if(!StringUtils.isNotEmpty(remarks)){
					remarks = "";
				}
				if(watermark.equals(2)){
					String[] texts = new String[]{"时 间:"+time, "备 注:"+remarks};
					for (String text : texts) {
						g2d.drawString(text, textX, textY);
						textY += textHeight + 5;
					}
				}else {
					String[] texts = new String[]{"时 间:"+time, "地 点:"+location, "天 气:"+weather, "经纬度:"+latitudeLongitude, "备 注:"+remarks};
					for (String text : texts) {
						g2d.drawString(text, textX, textY);
						textY += textHeight + 5;
					}
				}
			}

			g2d.dispose();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			try {
				ImageIO.write(srcImg, "png", baos);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
			MultipartFile resultFile = new MockMultipartFile(file.getName(), file.getOriginalFilename(), file.getContentType(), baos.toByteArray());
			IoUtil.close(baos);
			return resultFile;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	//文件下载
	public static MultipartFile getWatermarkImage(String fileUrl) {
		try {
			URL url = new URL(fileUrl);
			HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
			httpURLConnection.setRequestMethod("GET");
			httpURLConnection.connect();
			String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
			String contentType = httpURLConnection.getContentType();
			InputStream inputStream = httpURLConnection.getInputStream();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len;
			while ((len = inputStream.read(buffer)) > -1) {
				baos.write(buffer, 0, len);
			}
			baos.flush();
			MultipartFile multipartFile = new MockMultipartFile(fileName, fileName, contentType, baos.toByteArray());
			inputStream.close();
			baos.close();
			return multipartFile;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	//图片矫正
	public static MultipartFile correctImg(MultipartFile srcFile) {
		File outFile = new File(srcFile.getName());
		try (FileOutputStream fos = new FileOutputStream(outFile)) {
			int angle = getAngle(srcFile);
			if (angle != 90 && angle != 270) {
				// todo 不需要旋转,直接返回
				return srcFile;
			} else {
				BufferedImage srcImg = ImageIO.read(srcFile.getInputStream());
				int imgWidth = srcImg.getHeight();
				int imgHeight = srcImg.getWidth();
				double centerWidth = ((double) imgWidth) / 2;
				double centerHeight = ((double) imgHeight) / 2;
				BufferedImage targetImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
				Graphics2D g = targetImg.createGraphics();
				g.rotate(Math.toRadians(angle), centerWidth, centerHeight);
				g.drawImage(srcImg, (imgWidth - srcImg.getWidth()) / 2, (imgHeight - srcImg.getHeight()) / 2, null);
				g.rotate(Math.toRadians(-angle), centerWidth, centerHeight);
				g.dispose();
				// 输出图片
				ImageIO.write(targetImg, "jpg", fos);
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				try {
					ImgUtil.write(srcImg, srcFile.getContentType(), baos);
				} catch (Exception e) {
					e.printStackTrace();
					return null;
				}
				MultipartFile resultFile = new MockMultipartFile(srcFile.getName(), srcFile.getOriginalFilename(), srcFile.getContentType(), baos.toByteArray());
				IoUtil.close(baos);
				return resultFile;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	//角度判断
	private static int getAngle(MultipartFile file) throws Exception {
		Metadata metadata = ImageMetadataReader.readMetadata(file.getInputStream());
		for (Directory directory : metadata.getDirectories()) {
			for (Tag tag : directory.getTags()) {
				if ("Orientation".equals(tag.getTagName())) {
					String orientation = tag.getDescription();
					if (orientation.contains("90")) {
						return 90;
					} else if (orientation.contains("180")) {
						return 180;
					} else if (orientation.contains("270")) {
						return 270;
					}
				}
			}
		}
		return 0;
	}

	// 辅助方法:将文本分割为多行
	private static List<String> splitTextToLines(String text, int maxCharsPerLine) {
		List<String> lines = new ArrayList<>();
		for (int start = 0; start < text.length(); start += maxCharsPerLine) {
			int end = Math.min(start + maxCharsPerLine, text.length());
			lines.add(text.substring(start, end));
		}
		return lines;
	}
}

七牛云service

package com.wlh.zetc.restore.service;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;

/**
 * 七牛文件存储
 *
 * @author wanghailin
 * @date 2024-03-12 10:21:35
 */
public interface QiniuService {
	String uploadImage2qiniu(InputStream in, String key);
	boolean deleteImageFromQiniu(String key);
	String getPrivateDownloadUrl(String fileName) throws UnsupportedEncodingException;

}

七牛云impl

package com.wlh.zetc.restore.service.impl;

import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.*;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import com.wlh.zetc.restore.properties.QiniuProperties;
import com.wlh.zetc.restore.service.QiniuService;
import com.wlh.zetc.restore.utils.TextUtils;
import io.netty.channel.unix.Unix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * 七牛文件存储
 *
 * @author wanghailin
 * @date 2024-03-12 10:21:35
 */
@Service
public class QiniuServiceImpl implements QiniuService
{
	private final String domain;
	private final String bucketName;
	private final String ak;
	private final String sk;

	// 七牛文件上传管理器
	private final Configuration cfg;
	private final Auth auth;

	@Autowired
	public QiniuServiceImpl(QiniuProperties oss)
	{
		this.ak = oss.getAccessKey();
		this.sk = oss.getSecretKey();
		this.domain = oss.getDomain(); // CDN域名
		this.bucketName = oss.getBucketName();

		// //构造一个带指定 Region 对象的配置类
		cfg = new Configuration(Zone.zone0());
		auth = Auth.create(ak,sk);
	}

	/**
	 * 上传图片到七牛云
	 * @return 图片url
	 * */
	@Override
	public String uploadImage2qiniu(InputStream in, String key)
	{
		try {
			UploadManager uploadManager = new UploadManager(cfg);
			// 根据命名空间生成的上传token
			String upToken = auth.uploadToken(bucketName);
			Response response = uploadManager.put(in,key,upToken,null, null);
			//解析上传成功的结果
			DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
			//System.out.println(putRet.key);
			//System.out.println(putRet.hash);
            //return String.format("http://%s/%s",this.domain,putRet.key);
			return putRet.key;
		} catch (QiniuException ex) {
			Response r = ex.response;
			System.err.println(r.toString());
			try {
				System.err.println(r.bodyString());
			} catch (QiniuException ex2) {
				//ignore
			}
		}
		return null;
	}
	/**
	 * 删除图片
	 * */
	@Override
	public boolean deleteImageFromQiniu(String imageUrl)
	{
		BucketManager bucketManager = new BucketManager(auth, cfg);
		try {
			String key= TextUtils.getKey(imageUrl);
			Response response = bucketManager.delete(bucketName,key);
			return response.isOK();
		} catch (QiniuException ex) {
			//如果遇到异常,说明删除失败
			System.err.println(ex.code());
			System.err.println(ex.response.toString());
		}
		return false;
	}


	/**
	 * 获取文件下载路径
	 *
	 * @param fileName
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public String getPrivateDownloadUrl(String fileName) throws UnsupportedEncodingException {
		//文件https访问配置
		DownloadUrl url = new DownloadUrl(domain, true, fileName);
		//DownloadUrl url = new DownloadUrl(domain, false, fileName);
		long expireInSeconds = 3600;//1小时,可以自定义链接过期时间
		long deadline = System.currentTimeMillis()/1000 + expireInSeconds;
		Auth auth = Auth.create(ak, sk);
        String urlString = null;
        try {
            urlString = url.buildURL(auth, deadline);
        } catch (QiniuException e) {
            throw new RuntimeException(e);
        }
		return urlString;

	}
}


七牛云配置类

package com.wlh.zetc.restore.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * 七牛文件存储
 *
 * @author wanghailin
 * @date 2024-03-12 10:21:35
 */
@Component
@ConfigurationProperties(prefix = "oss.qiniu")
@Data
public class QiniuProperties
{
	private String domain; // CDN域名
	private String accessKey; // ACCESS_KEY
	private String secretKey; // SECRET_KEY
	private String bucketName; // 空间名称
}

七牛云配置yml

oss:
  qiniu:
    domain: qiniu.znkj0215.com # 访问域名(正式访问域名地址) 暂未配置https
#    domain: qiniu.iswhl.com # 访问域名(测试访问域名地址) 已配置https
    accessKey: APlM_0fW1A_PRS5bQ92rdGf9oSW-5q9mZK3Tv6yk # 公钥
    secretKey: Ri2eN9h4htBjZa8J8n_7QBfsAAvM_Arz5_CLqWth # 私钥
    bucketName: zhinonggengdi  #存储空间名称

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/719437.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端可观测性系统建设

一. 背景 随着前端业务的日趋庞大&#xff0c;及时发现和解决业务中的问题、优化用户体验、实时监控业务健康度变得愈发重要。在业务层面&#xff0c;我们希望能够监控每次发布版本后&#xff0c;核心功能是否有显著提升或至少没有负面影响&#xff0c;核心接口是否正常运作&a…

太速科技-基于XCVU9P+ C6678的100G光纤的加速卡

基于XCVU9P C6678的100G光纤的加速卡 一、板卡概述 二、技术指标 • 板卡为自定义结构&#xff0c;板卡大小332mmx260mm; • FPGA采用Xilinx Virtex UltralSCALE 系列芯片 XCVU9P; • FPGA挂载4组FMC HPC 连接器; • 板载4路QSPF&#xff0c;每路数据速…

【深度学习驱动流体力学】剖析流体力学可视化paraview原理

目录 1.paraview版本2.配置过程检查插件库文件配置 ParaView 环境变量启动 ParaView 并检查插件3.可视化测试插件功能 3.加载数据进行可视化第一步: 导入案例第二步:查看当前目录未更新前的内容第三步:使用 blockMesh 命令生成腔体案例的网格第四步:运行仿真icoFoam第五步:使用…

ESP32蓝牙串口通讯

文章目录 一、前言二、代码三、运行 一、前言 ESP32支持经典蓝牙和低功耗蓝牙&#xff08;BLE&#xff09;,经典蓝牙可在计算机上模拟出一个串口&#xff0c;使得ESP32可以以串口的方式和计算机通信。 二、代码 #include "BluetoothSerial.h"String device_name …

以CMDB为基础构建DevOps平台体系

在当今数字化转型的浪潮中&#xff0c;企业IT运维模式正从传统的资产管理向现代化的资源管理转变。配置管理数据库&#xff08;CMDB&#xff09;作为IT运维的核心组成部分&#xff0c;其在DevOps平台中的重要性愈加凸显。通过国信证券和招商银行的实际案例&#xff0c;我们将详…

Redis缓存与数据库双写不一致及解决方法

1.缓存与数据库双写不一致 在大并发下&#xff0c;同时操作数据库与缓存会存在数据不一致性问题 1.1 双写不一致情况 1.2 读写并发不一致 2.解决方法 对于并发几率很小的数据(如个人维度的订单数据、用户数据等)&#xff0c;这种几乎不用考虑这个问题&#xff0c;很少会发生…

Stable Diffusion 3 开源了,完全不输 Midjourney

前段时间我介绍过一款文字生视频的 AI 工具&#xff1a; SadTalker&#xff0c; 当时咱们是作为 Stable Diffusion 的插件来安装的。 那基于 Stable Diffusion 呢&#xff0c;咱们今天就来聊聊新开源的 Stable Diffusion 3。 在文字生成图片这个领域&#xff0c;一直是有三个…

springSecurity(二):实现登入获取token与解析token

登入生成token 主要思想 springSecurity使用UsernamePasswordAuthenticationToken类来封装用户名和密码的认证信息 代码实现 发起登入请求后&#xff0c;进入到login()方法 /*** 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,* 所以需要在Secur…

MySQL----表级锁行级锁排它锁和共享锁意向锁

MySQL的锁机制 锁&#xff08;Locking&#xff09;是数据库在并发访问时保证数据一致性和完整性的主要机制。在 MySQL 中&#xff0c;不同存储引擎使用不同的加锁方式&#xff1b;我们以 InnoDB 存储引擎为例介绍 MySQL 中的锁机制&#xff0c;其他存储引擎中的锁相对简单一些…

隐藏element的DateTimePicker组件自带的清空按钮

管理台页面使用到el-date-picker&#xff0c;type datetimerange 但是组件自带了清空按钮&#xff0c;实际上这个控件业务上代表开始时间和结束时间是一个必填选项&#xff0c;所有想要把清空按钮隐藏掉。 查看了文档https://element.eleme.io/#/zh-CN/component/datetime-p…

C++内联函数-auto关键字-for循环-空指针

内联函数 1.1概念&#xff1a; 以 inline 修饰 的函数叫做内联函数&#xff0c; 编译时 C 编译器会在 调用内联函数的地方展开 &#xff0c;没有函数调 用建立栈帧的开销&#xff0c;内联函数提升程序运行的效率。 #define _CRT_SECURE_NO_WARNINGSint ADD(int left, int rig…

【Pandas驯化-07】DataFrame中无所不能的pivot函数

【Pandas驯化-07】DataFrame中无所不能的pivot函数 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 相关内容文档获取 微信公众号 &#x…

前端菜鸡学习日记 -- 关于pnpm

哈咯哇大家&#xff0c;我又来了&#xff0c;最近稍微悠闲一些&#xff0c;所以就趁着这个机会学习一些新的知识&#xff0c;今天就是碰巧遇到了pnm&#xff0c;这个可以看作是npm的升级版本&#xff0c;比npm要快&#xff0c;用起来也更得劲更迅速 官网地址&#xff1a;https…

OpenGL系列(六)变换

在三角形和纹理贴图示例中&#xff0c;顶点使用的是归一化设备坐标&#xff0c;在该坐标系下&#xff0c;顶点的每个轴的取值为-1到1&#xff0c;超出范围的顶点不可见。 基于归一化设备坐标的物体的形状随着设备的大小变换而变化&#xff0c;这里产生的第一个问题是&#xff0…

数学学习与研究杂志社《数学学习与研究》编辑部2024年第6期目录

课改前沿 基于核心素养的高中数学课堂教学研究——以“直线与圆、圆与圆的位置关系”为例 张亚红; 2-4 核心素养视角下初中生数学阅读能力的培养策略探究 贾象虎; 5-7 初中数学大单元教学实践策略探索 耿忠义; 8-10《数学学习与研究》投稿&#xff1a;cn7kantougao…

微信ipad协议8049新版本

首先我们要先了解下ipad协议是什么 &#xff0c;ipad协议又叫微信协议 是基于微信IPad协议的智能控制系统帮助企业快速连接客户&#xff0c;创造营销氛围&#xff0c;实现自动获客、自动传播、自动转化、智能营销等分布式营销服务。 通过API 实现 个性化微信功能 &#xff08;例…

精度丢失引起的支付失败问题

问题描述 在提交订单时候&#xff0c;输入充值金额和优惠码&#xff0c;后台会返回具体的订单信息&#xff0c;如下图&#xff0c;支付金额应该是1 * (1 - 0.09) 0.91&#xff08;这个是理想状态&#xff09;&#xff0c;但是表单显示的是0.90999997&#xff0c; 然后点击确…

React+TS前台项目实战(十二)-- 全局常用组件Toast封装,以及rxjs和useReducer的使用

文章目录 前言Toast组件1. 功能分析2. 代码详细注释&#xff08;1&#xff09;建立一个reducer.ts文件&#xff0c;用于管理状态数据&#xff08;2&#xff09;自定义一个清除定时器的hook&#xff08;3&#xff09;使用rxjs封装全局变量管理hook&#xff08;4&#xff09;在to…

2025计算机毕业设计选题题目推荐-毕设题目汇总大全

选题在于精&#xff0c;以下是推荐的容易答辩的选题&#xff1a; SpringBoot Vue选题: 基于SpringBoot Vue家政服务系统 基于SpringBoot Vue非物质文化遗产数字化传承 基于SpringBoot Vue兽医站管理系统 基于SpringBoot Vue毕业设计选题管理系统 基于SpringBoot Vue灾害应急救援…

使用Ollama+OpenWebUI本地部署阿里通义千问Qwen2 AI大模型

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; &#x1f916;Ollama部署LLM专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月17日22点50分 &#x1f004;️文章质量&#xff…