设计模式二
备注
时间:2020年07月13日
架构课程第三次笔记
一:代理模式
1.1:实现的业务
将图片上传到本地的FastDFS,将视频等大文件上传到阿里OSS。
1.2:业务实现模型图
讲解:
1、FileUpload抽象接口,定义了文件上传方法,分别给它写了2种实现。
2、AliyunOSSFileUpload是将文件上传到aliyunOSS,主要上传mp4和avi的视频大文件。
3、FastdfsFileUpoad是将文件上传到FastDFS,主要上传png/jpg等图片小文件。
4、FileUploadProxy是代理对象,供用户访问,调用了FileUpload的文件上传方法,为用户提供不同文件上传调 用。
5、FileController是控制器,用于接收用户提交的文件,并调用代理FileUploadProxy实现文件上传。
1.3:代码实现
- 配置文件:
server:
port: 18081
#FastDFS配置
fastdfs:
url: http://192.168.211.137:28181/
fdfsPrefix: png,jpg #上传前缀配置,默认上传到FastDFS
#aliyun
aliyun:
oss:
endpoint: oss-cn-beijing.aliyuncs.com
accessKey: a7i6rVEjbtaJdYX2
accessKeySecret: MeSZPybPHfJtsYCRlEaUbfRtdH8gl4
bucketName: sklll
key: video/
ossPrefix: avi,mp4 #上传文件前缀配置
backurl: https://sklll.oss-cn-beijing.aliyuncs.com/video/ #访问地址配置
spring:
servlet:
multipart: max-file-size: 100MB #上传文件大小配置
- FileUpload接口定义:
public interface FileUpload {
/***
* 文件上传 *
* @param buffers:文件字节数组
* @param extName:后缀名
* @return
*/
String upload(byte[] buffers,String extName);
}
- AliyunOSSFileUpload实现:
@Component(value = "aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKey}")
private String accessKey;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.key}")
private String key;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.backurl}")
private String backurl;
/****
* 文件上传
* 文件类型如果是图片,则上传到本地FastDFS
* 文件类型如果是视频,则上传到aliyun OSS
*/
@Override
public String upload(byte[] buffers, String extName) {
String realName = UUID.randomUUID().toString() + "." + extName;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret);
// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如 abc/efg/123.jpg。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key + realName, new ByteArrayInputStream(buffers));
// 上传字符串。
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(FileUtil.getContentType("." + extName));
putObjectRequest.setMetadata(objectMetadata);
ossClient.putObject(putObjectRequest);
// 关闭OSSClient。
ossClient.shutdown();
return backurl + realName;
}
}
- FastdfsFileUpoad实现:
@Component(value = "fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload {
@Value("${fastdfs.url}")
private String url;
/***
* 文件上传
* @param buffers:文件字节数组
* @param extName:后缀名
* @return
*/
@Override
public String upload(byte[] buffers, String extName) { /*** * 文件上传后的返回值 * uploadResults[0]:文件上传所存储的组名,例如:group1 * uploadResults[1]:文件存储路径,例如: M00/00/00/wKjThF0DBzaAP23MAAXz2mMp9oM26.jpeg */
String[] uploadResults = null;
try {//获取StorageClient对象 StorageClient storageClient = getStorageClient(); //执行文件上传
uploadResults = storageClient.upload_file(buffers, extName, null);
return url + uploadResults[0] + "/" + uploadResults[1];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/***
* 初始化tracker信息
*/
static {
try {
//获取tracker的配置文件fdfs_client.conf的位置
String filePath = new ClassPathResource("fdfs_client.conf").getPath();
//加载tracker配置信息
ClientGlobal.init(filePath);
} catch (Exception e) {
e.printStackTrace();
}
}
/***
* 获取StorageClient
* @return
* @throws Exception
*/
public static StorageClient getStorageClient() throws Exception {
//创建TrackerClient对象
TrackerClient trackerClient = new TrackerClient();
//通过TrackerClient获取TrackerServer对象
TrackerServer trackerServer = trackerClient.getConnection();
//通过TrackerServer创建StorageClient
StorageClient storageClient = new StorageClient(trackerServer, null);
return storageClient;
}
}
- FileUploadProxy代理实现:
@Component
public class FileUploadProxy {
@Autowired
private FileUpload aliyunOSSFileUpload;
@Autowired
private FileUpload fastdfsFileUpoad;
@Value("${aliyun.oss.ossPrefix}")
private List<String> ossPrefix;
@Value("${fastdfs.fdfsPrefix}")
private List<String> fdfsPrefix;
/***
* 文件上传代理方法
* @return
*/
public String upload(byte[] buffers, String extName) {
//上传至云OSS
for (String prefix : ossPrefix) {
if (extName.equals(prefix)) {
return aliyunOSSFileUpload.upload(buffers, extName);
}
}
//FastDFS
for (String prefix : fdfsPrefix) {
if (extName.equals(prefix)) {
return fastdfsFileUpoad.upload(buffers, extName);
}
}
//默认传 FastDFS
return fastdfsFileUpoad.upload(buffers, extName);
}
}
- FileController控制器实现:
@RestController
@RequestMapping(value = "/file")
public class FileController {
@Autowired
private FileUploadProxy fileUploadProxy;
/***
* 文件上传
* @param file
* @return
* @throws IOException
*/
@PostMapping(value = "/upload")
public String upload(MultipartFile file) throws IOException {
return fileUploadProxy.upload(file.getBytes(), StringUtils.getFilenameExtension(file.getOriginalFilename()));
}
}
二:享元模式
2.1:概述
- 概念
有効地支持大量细粒度对象的复用.
- 优点和缺点
1:优点
相同对象只要保存一份,这降低了系统中对象的数量。
2:缺点
为了使对象可以共享,需要将一些不能共享的属性外部化,增加程序复杂性。
2.2:要实现业务
用户信息共享。
2.3:代码实现
- 定义共享组件: LogComponent
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public abstract class LogComponent {
private String sex;
private String role;
private String methodName;
private String message;
abstract void supplementLogContent(String... args);
public LogComponent(String username, String sex, String role) {
this.username = username;
this.sex = sex;
this.role = role;
}
}
- 享元组件逻辑操作对象: SupplementSource
public class SupplementSource extends LogComponent {
/****
* 填充参数
* @param username
*/
public SupplementSource(String username, String sex, String role) {
super(username, sex, role);
}
/****
* 业务逻辑,完善不同方法的日志记录
* @param args 长度为2,第1个是方法名字,第2个是方日志信息
*/
@Override
void supplementLogContent(String... args) {
super.setMethodName(args[0]);
super.setMessage(args[1]);
}
}
- 多线程安全控制: ThreadUserLog
@Component
public class ThreadUserLog {
//存储线程对应的用户名日志信息
private static ThreadLocal<LogComponent> userRecode = new ThreadLocal<LogComponent>();
/****添加用户信息记录 */
public void add(LogComponent logComponent) {
userRecode.set(logComponent);
}
/*** * 记录方法名和参数*/
public String reload(String... args) {
//获取对象
LogComponent logComponent = userRecode.get();
//设置数据
logComponent.supplementLogContent(args);
return logComponent.toString();
}
/****获取LogComponent */
public LogComponent get() {
return userRecode.get();
}
/**** * 移除 */
public void remove() {
userRecode.remove();
}
}
- 线程会话初始化: AuthorizationInterceptor
@Component
public class AuthorizationInterceptor implements HandlerInterceptor {
@Autowired
private ThreadUserLog threadUserLog;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
//获取令牌
String authorization = request.getHeader("Authorization");
// 解析令牌
if (!StringUtils.isEmpty(authorization)) {
Map<String, Object> tokenMap = JwtTokenUtil.parseToken(authorization);
//将用户数据存入到ThreadLocal中
LogComponent logComponent = new SupplementSource(tokenMap.get("username").toString(),
tokenMap.get("sex").toString(),
tokenMap.get("role").toString());
//添加当前线程用户信息记录
threadUserLog.add(logComponent);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
//输出令牌校验失败
response.setContentType("application/json;charset=utf-8");
response.getWriter().
print("身份校验失败!");
response.getWriter().
close();
return false;
}
/*** 移除会话信息 * @throws Exception */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
threadUserLog.remove();
}
}
- 配置拦截器
@Component
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private AuthorizationInterceptor authorizationInterceptor;
/*** * 拦截器配置 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login");
}
}
三:装饰者模式
3.1:概述
- 定义
不改变类结构的前提下,动态向一个对象中添加新的功能。
- 优缺点
1:优点
装饰类和被装饰类可以独立发展不耦合。
2:缺点
多层装饰比较复杂。
3.2:业务场景
- 场景
价格计算(订单价格,结算价格)
- 思路分析
1、创建接口(MoneyOperation),定义订单价格计算,因为所有价格波动,都是基于订单价格来波动的。
2、创建订单价格计算类(OrderPayMoneyOperation),实现MoneyOperation接口,实现订单价格计算。
3、创建装饰者对象(Decorator),以供功能扩展。
4、实现优惠券优惠金额计算功能扩展,创建Decorator的扩展类CouponsMoneyOperation,先计算订单金额,再 计算优惠券使用之后的优惠金额。
5、实现金币抵现功能扩展,创建Decorator的扩展类GoldMoneyOperation,先计算订单金额,再实现金币优惠之 后的金额。
3.3:代码实现
- 创建接口 MoneyOperation
public interface MoneyOperation {
/*** * 订单金额计算 * @return */
Integer operation(Order order);
}
- :创建类 OrderPayMoneyOperation 实现订单金额的计算
@Component
public class OrderPayMoneyOperation implements MoneyOperation {
@Autowired
private ItemDao itemDao;
/*** * 订单金额计算 */
@Override
public Integer operation(Order order) {
//根据ID查询商品
Item item = itemDao.findById(order.getItemId());
//计算订单金额
return item.getPrice() * order.getNum();
}
}
- 创建装饰者类 Decorator 供其他类扩展。
public abstract class Decorator implements MoneyOperation {
private MoneyOperation moneyOperation;
public void setMoneyOperation(MoneyOperation moneyOperation) {
this.moneyOperation = moneyOperation;
}
/*** * 订单金额计算 */
@Override
public Integer operation(Order order) {
return moneyOperation.operation(order);
}
}
- 创建类 CouponsMoneyOperation 扩展装饰者类,实现优惠券优惠价格计算
@Component
public class CouponsMoneyOperation extends Decorator {
@Autowired
private ThreadUserLog threadUserLog;
@Autowired
private CouponsDao couponsDao;
/*** * 功能扩展,增加优惠券折扣计算 */
@Override
public Integer operation(Order order) {
//订单金额计算
Integer payMoney = super.operation(order);
//优惠券金额优惠后金额计算
payMoney = couponsMoney(payMoney, order.getCouponsId());
return payMoney > 0 ? payMoney : 0;
}
/*** * 优惠券折扣计算 */
public Integer couponsMoney(Integer payMoney, String couponsId) {
if (payMoney <= 0) {
return 0;
}
//获取用户username->结合了享元模式
LogComponent logComponent = threadUserLog.get();
//查询优惠券
Coupons coupons = couponsDao.findByIdAndUserName(couponsId, logComponent.getUsername());
if (coupons == null) {
return payMoney;
}
//价格计算
payMoney = payMoney - coupons.getMoney();
//标记优惠券
couponsDao.modifyCouponsStatus(couponsId);
return payMoney > 0 ? payMoney : 0;
}
}
- 创建类 GoldMoneyOperation ,实现金币抵现优惠计算。
@Component
public class GoldMoneyOperation extends Decorator {
@Autowired
private ThreadUserLog threadUserLog;
@Autowired
private UserDao userDao;
/*** * 扩展功能:金币优惠后计算 */
@Override
public Integer operation(Order order) {
//订单基本金额计算
Integer payMoney = super.operation(order);
//金币优惠
payMoney = gold(payMoney);
return payMoney;
}
/*** * 金币优惠计算 */
public Integer gold(Integer payMoney) {
if (payMoney <= 0) {
return 0;
}
//获取当前用户的会话->结合了享元模式
LogComponent logComponent = threadUserLog.get();
User user = userDao.findByUserName(logComponent.getUsername());
if (user.getGold() <= 0) {
return payMoney;
}
//剩余金币
int remaining = 0;
//金币比payMoney小
if (payMoney >= user.getGold()) {
payMoney = payMoney - user.getGold();
} else {
remaining = user.getGold() - payMoney;
payMoney = 0;
}
//更新用户剩余金币
userDao.modifyGold(logComponent.getUsername(), remaining);
return payMoney;
}
}
四:工厂模式
4.1:概述
- 定义
提供一个工厂类获取对象。
- 优缺点
1:优点
解耦,降低对象之间的依赖。
2:缺点
每次增加一个新的产品,都需要重新创建一个工厂。
4.2:业务场景
支付渠道选择。
4.3:代码实现
- PayChannel 定义支付行为。
public interface PayChannel {
/*** * 支付 */
void pay();
}
- WeixinPay 实现微信支付操作
@Component("weixinPay")
public class WeixinPay implements PayChannel {
@Override
public void pay() {
System.out.println("微信支付成功!");
}
}
- AliPay 实现支付宝支付
@Component("aliPay")
public class AliPay implements PayChannel {
@Override
public void pay() {
System.out.println("支付宝支付成功!");
}
}
- 支付渠道映射配置
#支付通道列表,application.yml
pay:
paymap: {"1":"weixinPay","2":"aliPay"}
- 创建 PayFactory 用于获取支付渠道的实例
@Data
@Component
@ConfigurationProperties(prefix = "pay")
public class PayFactory implements ApplicationContextAware {
//Spring容器
private static ApplicationContext applicationContext;
//支付键值对信息
private Map<String, String> paymap;
/*** * 创建支付通道,从paymap中获取对应通道的实例名字,从applicationContext获取通道实例 */
public PayChannel createChannel(String key) {
return applicationContext.getBean(paymap.get(key), PayChannel.class);
}
/*** * 获取容器 * @param applicationContext * @throws BeansException */
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
PayFactory.applicationContext = applicationContext;
}
}
五:状态模式
5.1:概述
5.2:业务场景
5.3:代码实现
- State 接口,用于定义更新状态对象,同时执行相关的行为。
public interface State {
/*** * 变更状态 * @param order */
void doAction(Order order);
/*** * 执行行为 */
void execute();
}
- SendMsgBehavior(成功)
@Component("sendMsgBehavior")
public class SendMsgBehavior implements State {
@Override
public void doAction(Order order) {
System.out.println("订单支付");
order.setState(this);
}
@Override
public void execute() {
System.out.println("订单变更为已支付,需要通知商家发货!");
}
}
- ResetStoreBehavior(失败)
@Component("resetStoreBehavior")
public class ResetStoreBehavior implements State {
@Override
public void doAction(Order order) {
System.out.println("订单取消");
order.setState(this);
}
@Override
public void execute() {
System.out.println("订单取消,执行库存回滚!");
System.out.println("订单取消,执行退款!");
}
}