时间:2017年07月21日星期五
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/s...
第一章:课程简介
1-1 引言
通过一个项目案例的讲解,如何在JavaWeb应用中实现图片水印的添加。
1-2 课程内容
课程内容
1.Java图片水印实现思路
2.课程项目案例介绍
框架
实现方式
结果演示
3.课程案例详解
实现图片添加单个文字水印
实现图片添加单个图片水印
实现图片添加多个文字水印
实现图片添加多个图片水印
实现多图片批量添加水印
4.课程总结
1-3 课程目标
目标
1.了解Java图片水印实现思路
2.掌握文字水印和图片水印的思路
3.掌握多图片批量水印的实现
第二章:实现原理
2-1 实现思路
实现思路
1.创建缓存图片对象
2.创建Java绘图工具对象
3.使用绘图工具对象将原图绘制到缓存图片对象
4.使用绘图工具将水印(文字/图片)绘制到缓存图片对象
5.创建图像编码工具类
6.使用图片编码工具类,输出缓存图像到目标图片文件
2-2 实用工具类
实用工具类
1.BufferedImage:图片缓存类
2.Graphics2D:对平面2D图片进行操作
3.JPEGImageEncoder:对图片文件进行编码处理并输出到磁盘文件中
第三章:案例介绍
3-1 案例演示
项目案例
基于Strutus2框架的JavaWeb应用程序,允许一次上传多个图片,应用默认为上传图片添加水印,并将原图与添加水印图片对比展示。
说明
个人学习时,均使用SpringBoot框架相关技术
结果演示
默认首页
点击浏览并选中图片
点击上传,结果如下
第四章:添加单个文字水印
4-1 搭建项目
创建名为watermark的maven项目POM文件如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myimooc</groupId>
<artifactId>watermark</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>watermark</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
编写配置类和启动类,完成后项目结构如下
4-2 编写页面
代码演示:
<html>
<head>
<title>上传文件</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<div align="center">
<h2>图片上传</h2>
<form action="watermark" enctype="multipart/form-data" method="post">
<h2>请上传图片</h2>
<input type="file" name="image"/>
<input type="submit" value="上传图片" />
</form>
</div>
</body>
</html>
4-3 编写控制器类
代码演示:
package com.myimooc.watermark.controller;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
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.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.myimooc.watermark.domain.PicInfo;
import com.myimooc.watermark.service.MarkService;
import com.myimooc.watermark.service.UploadService;
/**
* WatermarkController 控制类
*
* @author ZhangCheng on 2017-07-21
*
*/
@Controller
public class WatermarkController {
private static Logger logger = LoggerFactory.getLogger(WatermarkController.class);
@Autowired
private UploadService uploadService;
@Autowired
private MarkService markService;
/***
* 单图片上传
*
* @param image
* @param request
* @return
*/
@PostMapping("/watermark")
public ModelAndView watermark(MultipartFile image, HttpServletRequest request) {
ModelAndView mav = new ModelAndView("/watermark");
PicInfo picInfo = new PicInfo();
String uploadPath = "static/images/";
String realUploadPath = getClass().getClassLoader().getResource(uploadPath).getPath();
logger.info("上传相对目录:{}", uploadPath);
logger.info("上传绝对目录:{}", uploadPath);
String imageURL = uploadService.uploadImage(image, uploadPath, realUploadPath);
File imageFile = new File(realUploadPath + image.getOriginalFilename());
String logoImageURL = markService.watermake(imageFile, image.getOriginalFilename(), uploadPath, realUploadPath);
picInfo.setImageURL(imageURL);
picInfo.setLogoImageURL(logoImageURL);
mav.addObject("picInfo", picInfo);
return mav;
}
/**
* 图片批量上传
*
* @param image
* @param request
* @return
*/
@PostMapping("/morewatermark")
public ModelAndView morewatermark(List<MultipartFile> image, HttpServletRequest request) {
ModelAndView mav = new ModelAndView("/morewatermark");
String uploadPath = "static/images/";
String realUploadPath = getClass().getClassLoader().getResource(uploadPath).getPath();
logger.info("上传相对目录:{}", uploadPath);
logger.info("上传绝对目录:{}", realUploadPath);
if (image != null && image.size() > 0) {
List<PicInfo> picInfoList = new ArrayList<PicInfo>();
for (MultipartFile imageFileTemp : image) {
if(imageFileTemp == null || imageFileTemp.getSize() < 1){
continue;
}
PicInfo picInfo = new PicInfo();
String imageURL = uploadService.uploadImage(imageFileTemp, uploadPath, realUploadPath);
File imageFile = new File(realUploadPath + imageFileTemp.getOriginalFilename());
String logoImageURL = markService.watermake(imageFile, imageFileTemp.getOriginalFilename(), uploadPath,
realUploadPath);
picInfo.setImageURL(imageURL);
picInfo.setLogoImageURL(logoImageURL);
picInfoList.add(picInfo);
}
mav.addObject("picInfoList", picInfoList);
}
return mav;
}
}
4-4 编写水印接口
代码演示:
package com.myimooc.watermark.service;
import java.awt.Color;
import java.awt.Font;
import java.io.File;
/**
* 图片水印服务类
* @author ZhangCheng on 2017-07-21
*
*/
public interface MarkService {
/** 水印文字内容 */
public static final String MARK_TEXT = "妙手空空";
/** 水印文字类型 */
public static final String FONT_NAME = "微软雅黑";
/** 水印文字样式 */
public static final int FONT_STYLE = Font.BOLD;
/** 水印文字大小 */
public static final int FONT_SIZE= 120;// 单位:像素
/** 水印文字颜色 */
public static final Color FONT_COLOR= Color.BLACK;
/** 水印文字位置X轴 */
public static final int X = 10;
/** 水印文字位置Y轴 */
public static final int Y = 10;
/** 水印文字透明度*/
public static final float ALPHA = 0.3F;
/** 水印图片*/
public static final String LOGO = "logo.png";
/**
* 功能:将传入的图片添加水印并保存到服务器中
* @param file
* @param uploadPath
* @param realUploadPath
* @return 添加水印后图片的URL相对地址
*/
String watermake(File imageFile,String imageFileName,String uploadPath,String realUploadPath);
}
4-5 添加单个文字水印
代码演示
package com.myimooc.watermark.service;
import java.awt.AlphaComposite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import org.springframework.stereotype.Service;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
* 图片水印服务类,添加文字水印
* @author ZhangCheng on 2017-07-21
*
*/
//@Service
@SuppressWarnings("unused")
public class TextMarkServiceImpl implements MarkService {
@Override
public String watermake(File imageFile,String imageFileName, String uploadPath, String realUploadPath) {
String logoFileName = "logo_" + imageFileName;
OutputStream os = null;
try {
Image image = ImageIO.read(imageFile);
int width = image.getWidth(null);// 原图宽度
int height = image.getHeight(null);// 原图高度
// 创建图片缓存对象
BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
// 创建绘绘图工具对象
Graphics2D g = bufferedImage.createGraphics();
// 使用绘图工具将原图绘制到缓存图片对象
g.drawImage(image, 0, 0, width,height,null);
// 设置水印文字字体信息
g.setFont(new Font(FONT_NAME,FONT_STYLE,FONT_SIZE));
// 设置水印文字颜色
g.setColor(FONT_COLOR);
int markWidth = FONT_SIZE * getTextLength(MARK_TEXT);
int markHeight = FONT_SIZE;
// 水印的高度和宽度之差
int widthDiff = width - markWidth;
int heightDiff = height - markHeight;
int x = X;
int y = Y;
// 判断设置的值是否大于图片大小
if(x > widthDiff){
x = widthDiff;
}
if(y > heightDiff){
y =heightDiff;
}
// 设置水印文字透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));
// 添加水印
g.drawString(MARK_TEXT, x, y + FONT_SIZE);
g.dispose();
os = new FileOutputStream(realUploadPath + "/" + logoFileName);
JPEGImageEncoder en = JPEGCodec.createJPEGEncoder(os);
en.encode(bufferedImage);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return uploadPath + "/" + logoFileName;
}
/**
* 功能:获取文本长度。汉字为1:1,英文和数字为2:1
*/
private int getTextLength(String text){
int length = text.length();
for(int i = 0 ; i < text.length(); i++){
String s = String.valueOf(text.charAt(i));
if(s.getBytes().length > 1){
length++;
}
}
length = length % 2 == 0 ? length / 2 : length / 2 + 1;
return length;
}
}
第五章:添加单个图片水印
5-1 图片水印添加
代码演示:
package com.myimooc.watermark.service;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import org.springframework.stereotype.Service;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
* 图片水印服务类,添加图片水印
* @author ZhangCheng on 2017-07-22
*
*/
//@Service
@SuppressWarnings("unused")
public class ImageMarkServiceImpl implements MarkService {
@Override
public String watermake(File imageFile, String imageFileName, String uploadPath, String realUploadPath) {
String logoFileName = "logo_" + imageFileName;
OutputStream os = null;
// 图片地址
String logoPath = realUploadPath + "/" + LOGO;
try {
Image image = ImageIO.read(imageFile);
int width = image.getWidth(null);// 原图宽度
int height = image.getHeight(null);// 原图高度
// 创建图片缓存对象
BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
// 创建绘绘图工具对象
Graphics2D g = bufferedImage.createGraphics();
// 使用绘图工具将原图绘制到缓存图片对象
g.drawImage(image, 0, 0, width,height,null);
// 读取Logo图片
File logo = new File(logoPath);
Image imageLogo = ImageIO.read(logo);
// 获取Logo图片的宽度和高度
int markWidth = imageLogo.getWidth(null);
int markHeight = imageLogo.getHeight(null);
// 原图和Logo图片的高度和宽度之差
int widthDiff = width - markWidth;
int heightDiff = height - markHeight;
int x = X;
int y = Y;
// 判断设置的值是否大于图片大小
if(x > widthDiff){
x = widthDiff;
}
if(y > heightDiff){
y =heightDiff;
}
// 设置水印透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));
// 添加水印
g.drawImage(imageLogo, x, y, null);
g.dispose();
os = new FileOutputStream(realUploadPath + "/" + logoFileName);
JPEGImageEncoder en = JPEGCodec.createJPEGEncoder(os);
en.encode(bufferedImage);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return uploadPath + "/" + logoFileName;
}
}
第六章:添加多个文字水印
6-1 添加多个文字水印
代码演示:
package com.myimooc.watermark.service;
import java.awt.AlphaComposite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import org.springframework.stereotype.Service;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
* 图片水印服务类,添加多个文字水印
* @author ZhangCheng on 2017-07-22
*
*/
//@Service
@SuppressWarnings("unused")
public class MoreTextMarkServiceImpl implements MarkService {
@Override
public String watermake(File imageFile, String imageFileName, String uploadPath, String realUploadPath) {
String logoFileName = "logo_" + imageFileName;
OutputStream os = null;
try {
Image image = ImageIO.read(imageFile);
int width = image.getWidth(null);// 原图宽度
int height = image.getHeight(null);// 原图高度
// 创建图片缓存对象
BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
// 创建绘绘图工具对象
Graphics2D g = bufferedImage.createGraphics();
// 使用绘图工具将原图绘制到缓存图片对象
g.drawImage(image, 0, 0, width,height,null);
// 设置水印文字字体信息
g.setFont(new Font(FONT_NAME,FONT_STYLE,FONT_SIZE));
// 设置水印文字颜色
g.setColor(FONT_COLOR);
int markWidth = FONT_SIZE * getTextLength(MARK_TEXT);
int markHeight = FONT_SIZE;
// 设置水印透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));
// 旋转图片
g.rotate(Math.toRadians(30), bufferedImage.getWidth()/2, bufferedImage.getHeight()/2);
int x = -width / 2;
int y = -height / 2;
int xmove = 200;// 水印之间的间隔
int ymove = 200;// 水印之间的间隔
// 循环添加
while (x < width * 1.5){
y = -height / 2;
while(y < height * 1.5){
g.drawString(MARK_TEXT, x, y);
y += markHeight + ymove;
}
x += markWidth + xmove;
}
g.dispose();
os = new FileOutputStream(realUploadPath + "/" + logoFileName);
JPEGImageEncoder en = JPEGCodec.createJPEGEncoder(os);
en.encode(bufferedImage);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return uploadPath + "/" + logoFileName;
}
/**
* 功能:获取文本长度。汉字为1:1,英文和数字为2:1
*/
private int getTextLength(String text){
int length = text.length();
for(int i = 0 ; i < text.length(); i++){
String s = String.valueOf(text.charAt(i));
if(s.getBytes().length > 1){
length++;
}
}
length = length % 2 == 0 ? length / 2 : length / 2 + 1;
return length;
}
}
第七章:添加多个图片水印
7-1 添加多个图片水印
代码演示:
package com.myimooc.watermark.service;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import org.springframework.stereotype.Service;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
* 图片水印服务类,添加多个图片水印
* @author ZhangCheng on 2017-07-22
*
*/
@Service
public class MoreImageMarkServiceImpl implements MarkService {
@Override
public String watermake(File imageFile, String imageFileName, String uploadPath, String realUploadPath) {
String logoFileName = "logo_" + imageFileName;
OutputStream os = null;
try {
Image image = ImageIO.read(imageFile);
int width = image.getWidth(null);// 原图宽度
int height = image.getHeight(null);// 原图高度
// 创建图片缓存对象
BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
// 创建绘绘图工具对象
Graphics2D g = bufferedImage.createGraphics();
// 使用绘图工具将原图绘制到缓存图片对象
g.drawImage(image, 0, 0, width,height,null);
// 图片地址
String logoPath = realUploadPath + "/" + LOGO;
// 读取Logo图片
File logo = new File(logoPath);
Image imageLogo = ImageIO.read(logo);
// Logo图片的宽度和高度
int markWidth = imageLogo.getWidth(null);
int markHeight = imageLogo.getHeight(null);
// 设置水印透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));
// 旋转图片
g.rotate(Math.toRadians(30), bufferedImage.getWidth()/2, bufferedImage.getHeight()/2);
int x = -width / 2;
int y = -height / 2;
int xmove = 200;// 水印之间的间隔
int ymove = 200;// 水印之间的间隔
// 循环添加
while (x < width * 1.5){
y = -height / 2;
while(y < height * 1.5){
// 添加水印
g.drawImage(imageLogo, x, y, null);
y += markHeight + ymove;
}
x += markWidth + xmove;
}
g.dispose();
os = new FileOutputStream(realUploadPath + "/" + logoFileName);
JPEGImageEncoder en = JPEGCodec.createJPEGEncoder(os);
en.encode(bufferedImage);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return uploadPath + "/" + logoFileName;
}
}
第八章:批量添加图片水印
8-1 批量添加图片水印
代码演示
1.编写moreupload.html
<html>
<head>
<title>上传文件</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<div align="center">
<h2>图片批量上传</h2>
<form action="morewatermark" enctype="multipart/form-data" method="post">
<h2>请上传图片</h2>
<input type="file" name="image"/>
<br />
<input type="file" name="image"/>
<br />
<input type="file" name="image"/>
<br />
<input type="file" name="image"/>
<br />
<input type="file" name="image"/>
<br />
<input type="submit" value="上传图片" />
</form>
</div>
</body>
</html>
2.修改WatermarkController类
package com.myimooc.watermark.controller;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
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.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.myimooc.watermark.domain.PicInfo;
import com.myimooc.watermark.service.MarkService;
import com.myimooc.watermark.service.UploadService;
/**
* WatermarkController 控制类
*
* @author ZhangCheng on 2017-07-21
*
*/
@Controller
public class WatermarkController {
private static Logger logger = LoggerFactory.getLogger(WatermarkController.class);
@Autowired
private UploadService uploadService;
@Autowired
private MarkService markService;
/***
* 单图片上传
*
* @param image
* @param request
* @return
*/
@PostMapping("/watermark")
public ModelAndView watermark(MultipartFile image, HttpServletRequest request) {
ModelAndView mav = new ModelAndView("/watermark");
PicInfo picInfo = new PicInfo();
String uploadPath = "static/images/";
String realUploadPath = getClass().getClassLoader().getResource(uploadPath).getPath();
logger.info("上传相对目录:{}", uploadPath);
logger.info("上传绝对目录:{}", uploadPath);
String imageURL = uploadService.uploadImage(image, uploadPath, realUploadPath);
File imageFile = new File(realUploadPath + image.getOriginalFilename());
String logoImageURL = markService.watermake(imageFile, image.getOriginalFilename(), uploadPath, realUploadPath);
picInfo.setImageURL(imageURL);
picInfo.setLogoImageURL(logoImageURL);
mav.addObject("picInfo", picInfo);
return mav;
}
/**
* 图片批量上传
*
* @param image
* @param request
* @return
*/
@PostMapping("/morewatermark")
public ModelAndView morewatermark(List<MultipartFile> image, HttpServletRequest request) {
ModelAndView mav = new ModelAndView("/morewatermark");
String uploadPath = "static/images/";
String realUploadPath = getClass().getClassLoader().getResource(uploadPath).getPath();
logger.info("上传相对目录:{}", uploadPath);
logger.info("上传绝对目录:{}", realUploadPath);
if (image != null && image.size() > 0) {
List<PicInfo> picInfoList = new ArrayList<PicInfo>();
for (MultipartFile imageFileTemp : image) {
if(imageFileTemp == null || imageFileTemp.getSize() < 1){
continue;
}
PicInfo picInfo = new PicInfo();
String imageURL = uploadService.uploadImage(imageFileTemp, uploadPath, realUploadPath);
File imageFile = new File(realUploadPath + imageFileTemp.getOriginalFilename());
String logoImageURL = markService.watermake(imageFile, imageFileTemp.getOriginalFilename(), uploadPath,
realUploadPath);
picInfo.setImageURL(imageURL);
picInfo.setLogoImageURL(logoImageURL);
picInfoList.add(picInfo);
}
mav.addObject("picInfoList", picInfoList);
}
return mav;
}
}
3.编写morewatermark.html
<html>
<head>
<title>处理结果</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<h2 align="center">操作结果</h2>
<table width="100%" align="center">
[#list picInfoList as item]
<tr>
<td width="50%" align="center">
<img src='[@common.ctx /]${item.imageURL}' width="500" />
</td>
<td width="50%" align="center">
<img src='[@common.ctx /]${item.logoImageURL}' width="500" />
</td>
</tr>
[/#list]
</table>
<hr />
<div align="center">
<a href='[@common.ctx /]/index'>返回</a>
</div>
</body>
</html>
4.效果展示
访问图片批量上传页
选择文件
点击上传图片,结果如下
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。