我是如何将同事的代码改成DDD风格的

news2024/9/20 15:05:22

eeccba9d660fda83b1c976c7363059e3.jpeg

DDD是领域驱动设计的简写。前段时间听群友说行业里少有DDD的代码案例,进而对DDD没有一个感性的认识。我想这是行业里普遍存在的现象吧。所以,就有了写此文的想法。

文章标题说的是“同事的代码”,其实只是为了让此文更具传播,没别的意思。

本文开篇介绍了行业里比较普遍的代码风格,接着,我采用DDD风格对其进行修改。

我无意说服读者要按照我认为的DDD的风格来写代码,只是想告诉大家,这个世界上,还存在另一种代码风格。

本文虽是以Java语言为案例演示,也希望对其它语言的读者朋友有帮助。

如果各位觉得这样的风格好,可以尝试一下。非常欢迎大家反馈,平时太少人和我交流这些了。

如果你觉得此文对你有帮助,麻烦转发。干货好文不易。谢谢。

行业里普遍的代码风格,简称A风格

代码结构如下:

├── domain 
    domain模块被同事认为是用于存放专门和DB打交道的类的地方
    - 包路径太长省略/account/repository/AbcLoginInfoRepository.java
    - 包路径太长省略/domain/account/AbcLoginInfo.java
├── repository-impl
    -  包路径太长省略/AbcLoginInfoRepositoryImlp.java
├── server
     - 包路径太长省略/server/login/LoginService.java
     - 包路径太长省略/server/login/LoginController.java
     - 包路径太长省略/server/login/AuthCodeVo.java
     - 包路径太长省略/server/login/UserInfoVo.java
     - 包路径太长省略/config/AbcWebMvcConfigurer.java

Server模块

A风格下,整个业务系统的业务逻辑都在此模块中。

LoginController.java 实现http服务:

@Controller  
@RequestMapping  
public class LoginController {  
@Autowired  
LoginService loginService;  
// 省略一些不重要的代码

@GetMapping(value = "/login")  
@ResponseBody  
public UserInfoVo login(String code) throws IOException {  
    UserInfoVo userInfoVo = loginService.login(code, httpServletResponse);  
    httpServletResponse.sendRedirect("/");  
    return userInfoVo;  
}  

@GetMapping(value = "/logout")  
@ResponseBody  
public boolean logout() {  
    return loginService.logout(httpServletRequest,httpServletResponse);  
}  
}

UserInfoVo.java是返回给前端的用户信息的结构体:

public class UserInfoVo {  
    private String id;  
    private String userType;  
    // 省略一些其它字段
    // 省略一些getter setter方法
}

AuthCodeVo.java是用于存储一些认证过程中的数据的结构体

public class AuthCodeVo {  
    private String token;  
    private Integer expiresIn;  
    // 省略一些getter setter方法
}

A风格的特点是:除了VO,行业里,还有各种O,如PO、DTO、DO。

刚入行的小伙伴很难分清各种O,所以,只有跟着前辈的老代码依葫芦画瓢。进而导致大家对于Java代码的印象:不就是各种O之间的转换嘛。

这里并不是说DDD风格下的代码没有O。在DDD风格下,O本身是有业务逻辑方法的,并不只是一堆字段、getter和setter方法。

AbcWebMvcConfigurer.java这个类用于实现对所有的请求的拦截,以实现统一认证:

@Configuration  
public class AbcWebMvcConfigurer implements WebMvcConfigurer {  
@Autowired  
LoginService loginService;  

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
    registry.addInterceptor(new UserAuthInterceptorRegistry())  
    // 省略代码
}  

class UserAuthInterceptorRegistry implements HandlerInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, 
    HttpServletResponse response, Object handler)  throws Exception {  
        if (loginService.isLoginSuccess(request)) {  
            return true;  
        }  
        response.sendRedirect("登录页面的url");  
        return false;  
    }  
}  
}

AbcLoginInfo.java,AbcLoginInfoRepository.java,AbcLoginInfoRepositoryImlp.java 三个文件实现了登录信息的存储。

其中AbcLoginInfo.java只是用户的信息及getter和setter方法,典型的贫血型模型。

AbcLoginInfoRepository是AbcLoginInfo对象的持久化接口,而AbcLoginInfoRepositoryImlp是该接口的实现。

登录逻辑LoginService

这个就是登录服务直接实现逻辑所在。源代码将近200行代码

@Service  
public class LoginService {  
// 省略一些不重要的代码
public UserInfoVo login(String code, HttpServletResponse response) {  
    AuthCodeVo authCodeVo = authCode(code);  
    // 省略部分代码
    UserInfoVo userInfoVo = getUserInfo(authCodeVo.getAccessToken(), authCodeVo.getExpiresIn());  
    // 省略部分代码
    LoginInfo loginInfo =loginInfoRepository.findByUid(userInfoVo.getUid());  
    if (loginInfo == null) {  
        loginInfo = new LoginInfo();  
    }  
    setLoginInfo(loginInfo, authCodeVo, userInfoVo);  
    loginInfoRepository.save(loginInfo);  
    addLoginCookie(loginInfo, response);  
    return userInfoVo;  
}  

private void addLoginCookie(LoginInfo loginInfo, HttpServletResponse response) {  
    Cookie tokenCookie = new Cookie(TOKEN_COOKIE, loginInfo.getAccessToken());  
    response.addCookie(tokenCookie);  
}  

public boolean isLoginSuccess(HttpServletRequest request) {  
    Cookie[] cookies = request.getCookies();  
    if (cookies == null) {  
        return false;  
    }  
    String token = null;  
    String uid = null;  
    // 此处省略代码,即从cookies中取出token和uid将设置到变量中。  
    LoginInfo loginInfo = loginInfoRepository.findByUid(uid);  
    // 此处对acessToken和过期时间进行校验
    if (token.equals(loginInfo.getAccessToken())  
            && new Date().compareTo(loginInfo.getExpiresDate()) < 0) {  
        return true;  
    }  
    return false;  
}  

public boolean logout(HttpServletRequest request, HttpServletResponse response) {  
    Cookie[] cookies = request.getCookies();  
    // 对cookie进行过期处理
    return true;  
}  

private LoginInfo setLoginInfo(LoginInfo loginInfo,  
                                     AuthCodeVo authCodeVo,  
                                     UserInfoVo userInfoVo) {  

    long nowTime = System.currentTimeMillis();  
    // 根据过期时长计算过期时间,并设置到LoginInfo中
    Date expiresDate = new Date(nowTime + authCodeVo.getExpiresIn() * 1000);  
    // 此处省略一些拿authCodeVo和userInfoVo中的信息set到loginInfo的代码
    return loginInfo;  
}  

public AuthCodeVo authCode(String code) {  
    Map<String, String> params = new HashMap<>();  
    // 省略params参数的组装的代码
    // 请求access token的地址,并拿到AuthCodeVo结构体的内容
    Map<String, Object> resultMap = restTemplate.postForObject(ACCESS_TOKEN_URL, null, Map.class, params);  
    AuthCodeVo authCodeVo = new AuthCodeVo();  
    // 将resultMap中的值set到authCodeVo中
    return authCodeVo;  
}  


public UserInfoVo getUserInfo(String accessToken, Integer expiresIn) {  
    Map<String, Object> params = new HashMap<>();  
    // 省略params参数的组装的代码
    // 请求用户的信息的地址,并拿到用户信息。注意这里直接使用restTemplate这个技术实现。
    Map<String, Object> resultMap = restTemplate.getForObject(PROFILE_URL, Map.class, params);  
    UserInfoVo userInfoVo = new UserInfoVo();  
    // 将resultMap中的值set到userInfoVo中
    return userInfoVo;  
}  
  
}

A风格小结

  1. 1. 登录的主逻辑放在LoginService中;

  2. 2. LoginService即处理http请求技术逻辑(cookie的操作),也处理业务逻辑(登录信息的持久化、登录判断、token过期时间设置);

  3. 3. LoginService存放在Server模块;

  4. 4. 所有的实体、各种O中,只有字段,getter和setter方法。这导致lombok这样的代码生成库大量被使用,因为A风格觉得为每个字段写getter和setter方法是必须,但是又是浪费时间的事情。

我们暂不讨论A风格的问题,接着看DDD风格的代码。

DDD风格的代码,简称D风格

代码仓库结构:

├── domain
    - domain是用于存放整个业务系统的核心逻辑
├── abc-o2-auth
    - 存放所有abc-o2的相关逻辑,下文详细介绍
├── server
     - 包路径太长省略/server/login/LoginController.java
     - 包路径太长省略/config/AbcWebMvcConfigurer.java
├── repository-impl
    -  包路径太长省略/AccountRepositoryImpl.java
├── spring-ioc-impl
    - spring的IoC的实现,D风格下,IoC的实现也应该是可以被低成本地替换的
├── tech-lib
    - 公共技术逻辑的接口
├── tech-lib-impl
    - 公共技术逻辑的接口的实现

abc-o2-auth模块

我们把o2-auth的所有的逻辑放在这个新的模块中。考虑到将来可能需要实现新的认证逻辑。以下是该模块的Java代码的结构:

./abc-o2-auth/src/main/java/com/xxx/domain/o2
├── AccessToken.java
├── AccessTokenFetcher.java
├── AccessTokenFetcherImpl.java
├── Account.java
├── AccountProfile.java
├── AccountProfileFetcher.java
├── AccountProfileFetcherImpl.java
├── AuthConfig.java
└── repository
   └── AccountRepository.java

登录逻辑Account

Account.java是整个o2-auth的认证方式的核心逻辑。源代码将近330行。

虽然它是一个实体,但它不是整个业务系统的核心逻辑,所以,没有被放到整个业务系统的domain模块中。

Account类中所有的技术逻辑都被抽象成接口。

公共的技术接口,比如Json数据的操作接口json-util,我们统一放在tech-lib模块中。公共的技术接口的实现,目前统一放在tech-lib-impl中。

当然,也可以以更小粒度的模块来实现解耦,比如创建一个json-util-jackson-impl模块来实现json-util接口。

P.S. 为什么不直接使用Jackson呢?你想想,当发生像Fastjson那样的安全事故时,你该如何快速的更换json的实现?如果按照D风格,是不是就很容易更换了。

当Account中的逻辑被真正运行时,需要用到这些技术接口的具体实现时,就从InstanceFactory实例工厂类的getInstance静态方法获取。InstanceFactory是什么,我们下面再说。

当前,你只需要知道,通过InstanceFactory的getInstance静态方法可以拿到接口的实现实例就可以了。

采用D风格,在写业务逻辑时,就不需要关心技术逻辑的实现了。这样就能很好的解决“无法写单元测试”的问题。

@Entity  
@Table(name = "abc_o2_accounts")
public class Account {
// 省略所有的字段,getter和setter代码

public static Optional<Account> login(String code) {  
    // AccessTokenFetcher是accessToken的拉取接口
    // 因为accessToken需要请求第三方系统
    AccessTokenFetcher accessTokenFetcher = InstanceFactory.getInstance(AccessTokenFetcher.class);  
    Optional<AccessToken> accessTokenOptional = accessTokenFetcher.auth(code);  
    if (accessTokenOptional.isEmpty()) {  
        throw new LoginBizException("401");  
    }  
    // AccountProfileFetcher是accountProfile的拉取接口
    AccountProfileFetcher accountProfileFetcher = InstanceFactory.getInstance(AccountProfileFetcher.class);  
    Optional<AccountProfile> accountProfileOptional = accountProfileFetcher.fetch(accessTokenOptional.get().getAccessToken(), accessTokenOptional.get().getExpiresIn());  
    if (accountProfileOptional.isEmpty()) {  
        throw new LoginBizException("401");  
    }  
    // 登录成功后,将登录信息持久化
    AccountRepository accountRepository = InstanceFactory.getInstance(AccountRepository.class);  
    Optional<Account> accountOptional = accountRepository.findByUid(accountProfileOptional.get().getUid());  
    if (accountOptional.isEmpty()) {  
        Account account = buildBy(accessTokenOptional.get(),accountProfileOptional.get());  
        account.save();  
        return Optional.of(account);  
    } else {  
        Account account = accountOptional.get();  
        account.update(accessTokenOptional.get(), accountProfileOptional.get());  
        return Optional.of(account);  
    }  
}  
// 登录的url是从配置中获取的。至于是从数据库,还是Etcd配置中心获取,登录核心逻辑并不关心,
// 而由AuthConfig的实现决定。这样,将来我们想换配置中心,成本就很低了。
public static String loginUrl(){  
    AuthConfig authConfig = InstanceFactory.getInstance(AuthConfig.class);  
    return authConfig.getLoginUrlWithRedirect();  
}  
// 再次review此代码时,发现这个方法叫isLoggedIn更能体现方法内的逻辑。
public static boolean isLoginSuccess(String token, String uid) {  
    AccountRepository accountRepository = InstanceFactory.getInstance(AccountRepository.class);  
    Optional<Account> accountOptional = accountRepository.findByUid(uid);  
    if (accountOptional.isEmpty()) {  
        return false;  
    }  
    return StringUtils.equals(token, accountOptional.get().getAccessToken()) && new Date().before(accountOptional.get().getExpiresDate());  
}  
  
public AccountProfile getProfile() {  
    AccountProfile result = new AccountProfile();  
    // 将Account中的信息设置到AccountProfile中,因为前端只需要Account中的部分信息
    return result;  
}  
  
private void update(AccessToken accessToken, AccountProfile accountProfile) {  
    Date expiresDate = calExpiredDate(accessToken.getExpiresIn());  
    // 省略部分更新Account对象的代码。
    // save方法即保存此对象
    save();  
}  

// 计算出最新的过期时间
private static Date calExpiredDate(int expiresIn) {  
    long nowTime = System.currentTimeMillis();  
    return new Date(nowTime + expiresIn * 1000L);  
}  
// D风格的代码的一大特点:行为跟着数据走。
// 因为数据结构在Account类中,所以数据的持久化方法save也应该放在Account类中。
// 虽然底层实现都是accountRepository.save(xxx)
// A风格下,持久化方法放在LoginService中,而数据结构放在另一个类中。
private void save() {  
    AccountRepository accountRepository = InstanceFactory.getInstance(AccountRepository.class);  
    accountRepository.save(this);  
}  
  
private static Account buildBy(AccessToken accessToken, AccountProfile accountProfile) {  
    Account result = new Account();  
    // calExpiredDate方法的实现放在AccessToken类中更合理,我们只需要调用accessToken。getExpiresDate()。
    // 因为根据过期时长计算过期日期的逻辑应该属于AccessToken,而不属于Account
    Date expiresDate = calExpiredDate(accessToken.getExpiresIn());  
    // 省略根据accessToken和accountProfile构建一个Account实例
    return result;  
}
}

Server模块

LoginController.java 只负责调用Account实体的的login方法和操作Cookie这类、HTTP服务相关的技术逻辑。

@Controller  
@RequestMapping  
public class LoginController {  

@GetMapping(value = "/login")  
@ResponseBody  
public AccountProfile login(String code, HttpServletResponse response) throws IOException {  
    Optional<Account> accountOptional = Account.login(code);  
    if (accountOptional.isPresent()) {
        responseLoginCookie(accountOptional.get().getAccessToken(), accountOptional.get().getUid(), response);  
        response.sendRedirect("/");  
        return accountOptional.get().getProfile();  
    } 
    // 此处省略部分代码
}  

@GetMapping(value = "/logout")  
@ResponseBody  
public boolean logout(HttpServletRequest request, HttpServletResponse response) {  
    Cookie[] cookies = request.getCookies();  
    // 遍历Cookie,并设置cookie过期
    return true;  
}  
private void responseLoginCookie(String accessToken, String uid, HttpServletResponse response) {  
    // 登录成功,设置cookie
}
}

以下是D风格AbcWebMvcConfigurer的代码:

@Configuration  
public class AbcWebMvcConfigurer implements WebMvcConfigurer {  

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
    registry.addInterceptor(new UserAuthInterceptorRegistry())  
           // 部分代码省略
}  

class UserAuthInterceptorRegistry implements HandlerInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
            throws Exception {  

        Cookie[] cookies = request.getCookies();  
        //省略代码
        String token = null;  
        String uid = null;  
        // 从Cookie中取值,并设置到token和uid变量中 
        if (Account.isLoginSuccess(token, uid)) {  
            return true;  
        }  
        response.sendRedirect(Account.loginUrl());  
        return false;  
    }  
}  
}

D风格的AbcWebMvcConfigurer.java 与A风格的区别是:

  1. 1. 关于Cookie的操作,A风格放在LoginService类中,而D风格放在AbcWebMvcConfigurer。因为D风格认为Cookie的操作属于HTTP服务行为,不属于核心业务。另,UserAuthInterceptorRegistry,可以考虑移到abc-o2-auth模块中;

  2. 2. 关于是否已经登录的判断逻辑,A风格放在LoginService类中,而D风格放在Account类中。因为是否已经登录的判断逻辑,D风格认为属于abc-o2-auth模块的核心逻辑,而不属于server模块。

  3. 3.

    InstanceFactory实例工厂的魔法

    在D风格中会大量使用InstanceFactory静态类,它使我们能做到与IoC的实现的解耦。

InstanceFactory代码来自https://github.com/dayatang/dddlib 。

DDDLib是我的恩师所创建。我在十年前跟他学习到的DDD。大家可以start并从该仓库学习到DDD的一些代码样例。

D风格小结

  1. 1. 登录的主逻辑放在abc-o2-auth中模块的Account实体中。D风格中的实体类包含各种业务方法,是充血型模型。每一类的设计都有业务含义的,不仅仅只是一个数据结构;

  2. 2. 在写代码时,时刻在思考:这是技术逻辑,还是业务逻辑?这是核心业务逻辑,还是非核心逻辑。

篇外话

5年前,我还是一名Java程序员的时候,我一直按照DDD风格要求自己。

但是,软件行业的绝大数公司才不管你写的代码好,还是坏。更不会管这代码在几年后还能否被维护,维护成本是多少。

这是DDD风格不流行的原因之一。另一个原因就是:根本没有几个人知道这样写代码。所以,本文算是一篇科普文。

(全文完)

如果对你有帮助,那么请你也帮助我转发,这是对我的支持。谢谢。

往期好文推荐:

  • 这十年,我所经历的领域驱动设计(DDD)

  • 耦合的本质

  • 你们的 save 方法是写在实体上,还是写 DAO 上?

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

组件的生命周期、vue2中使用axios、插槽

目录 一、组件的生命周期 1、什么是组件的生命周期 2、生命周期函数 3、生命周期的阶段划分 4、钩子函数 5、keep-alive组件 6、生命周期函数&#xff1a; 二、vue2中使用axios 1、axios&#xff1a;是一个基于Promise的网络请求库。既可以在node.js&#xff08;服务器…

【MySQL】1. 了解数据库以及MySQL安装

了解数据库和SQL什么是数据库为什么使用数据库MySQL下载,安装,配置客户端连接MySQL方法一方法二SQLSQL分类SQL的基本规则命名规则最后大家好, 我是路不停_。 上学期学校学习了MySQL之后,课后也没有做很多总结,加上课时紧凑,后续考试也是考前草草备考了一下. 最近读了本MySQL必知…

C 程序设计教程(03)—— C 语言的数据类型(一):基本数据类型

C 程序设计教程&#xff08;03&#xff09;—— C 语言的数据类型&#xff08;一&#xff09;&#xff1a;基本数据类型 一、数据类型简介 C 语言提供了丰富的数据类型&#xff0c;每一个常量和变量必须属于某一种数据类型。C 语言中的数据类型如下&#xff1a; &#xff08…

基础数学(八)——期末考试复习

文章目录考试要求考试内容简单复习插值拟合和回归数值积分直接法迭代法非线性方程求根微分方程数值解去年考题第一题&#xff0c;插值&#xff08;12分&#xff09;第二题&#xff0c;回归和拟合第三题&#xff0c;数值积分第四题&#xff0c;线性方程组求解第五题&#xff0c;…

MGRE实验配置(华为)

华为ENSP的MGRE实验&#xff1a; 建立好拓扑图 之后就是对各个路由器的配置&#xff1a; R2配置&#xff1a; int s3/0/0 link-protocol hdlc 更改接口为hdlc认证 ip address 12.1.1.2 24 int lo0 ip add 2.2.2.2 24 int s3/0/1 [r2-Serial3/0/1]ip address 23.1.1.2 24 [r2-Se…

【ML】 基本概念

ML 基本概念1. different types of functions2. how to find the function - training data3. unseen during training4. Back to ML Framework4.1 Step1: function with unknown4.2 Step2: define loss from training data4.3 step3: optimization4.4 其他4.4.1 Sigmoid → Re…

基础算法(五)——双指针算法

双指针算法 介绍 双指针算法分为两大类&#xff1a; 两个指针指向两个不同序列&#xff0c;用两个指针维护某一种逻辑&#xff0c;例如归并排序 两个指针指向一个序列&#xff0c;即用两个指针维护一段区间&#xff0c;例如快排 核心思想&#xff1a; 若能证明出题目存在…

Linux工具学习之【vim】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Whatever is worth doing is worth doing well. 任何值得去做的事情&#xff0…

springboot垃圾分类查询管理系统

1.介绍 垃圾分类查询管理系统&#xff0c;对不懂的垃圾进行查询进行分类并可以预约上门回收垃圾。 让用户自己分类垃圾&#xff0c; 按国家标准自己分类&#xff0c; 然后在网上提交订单&#xff0c; 专门有人负责回收&#xff0c; 统一回收到垃圾处理站&#xff0c; 然后工人…

Renesas 3 --烧写

1.烧录Boot程序 1.1&#xff0c;连接RESET_OUT, TOOL0, GND到板子。 1.2&#xff0c;上电后烧录器上指示灯亮&#xff08;假如不亮&#xff0c;重新上电烧录器&#xff09; 1.3&#xff0c;用Renesas flash programmer来烧写软件&#xff08;新建项目&#xff0c;然后保存项目…

RAW怎么转为JPG?这些转换技巧值得收藏

相信大多数的人&#xff0c;出去外面游玩时都喜欢使用相机来拍照&#xff0c;但是大多数相机拍出来的照片都是RAW格式&#xff0c;图片传到电脑时&#xff0c;会因为格式的不兼容而导致无法查看&#xff0c;这个时候我们就需要把图片的格式进行转换&#xff0c;例如我们将其转换…

Day850.GuardedSuspension模式 -Java 性能调优实战

GuardedSuspension模式 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于GuardedSuspension模式的内容。 小灰工作中遇到一个问题&#xff0c;开发了一个 Web 项目&#xff1a;Web 版的文件浏览器&#xff0c;通过它用户可以在浏览器里查看服务器上的目录和文件。 这…

(原创)Flow数据流的使用

前言 这篇文章主要介绍Flow的一些基础使用方法 同时介绍如何用Flow请求网络数据 下面开始&#xff01; 什么是Flow Flow翻译过来&#xff0c;是“流”的意思 举例说明&#xff0c;在大自然中&#xff0c;常见的如水流 是从高往低流动的 那么在计算机世界里&#xff0c;所谓的…

loss盘点: asl loss (Asymmetric Loss) 代码解析详细版

1. BCE公式部分 可以简单浏览下这篇博客的文章&#xff1a; https://blog.csdn.net/qq_14845119/article/details/114121003 这是多分类 经典 BCELossBCELossBCELoss 公式 L−yL−(1−y)L−L -y L_{} - (1-y) L_{-} L−yL​−(1−y)L−​ 其中&#xff0c;L/−L_{/-}L/−​…

Docker保姆级学习教程

文章目录1、什么是Docker1.1、容器技术1.2、容器与虚拟机比较1.3、Docker特点1、更高效的利用系统资源2、更快速的启动时间3、一致的运行环境4、持续支付和部署5、更轻松的迁移6、更轻松的维护和扩展2、Docker组件学习2.1、Docker客户端和服务器2.2、Docker镜像2.3、Registry&a…

奇怪的知识——Windows下怎么修改进程的名称?

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;无尽的折腾后&#xff0c;终于又回到…

element-plus的form表单form-item的prop怎么写才能正确校验,实现逻辑是怎么样的?

不管是element-plus还是上一个版本的element-ui&#xff0c;都是一个使用很广泛的基于csshtmljs的ui组件库&#xff0c;它的form表单自带强大的校验功能&#xff0c;form-item的prop怎么写才正确&#xff0c;实现逻辑是怎么样的&#xff1f;element-plus的form表单的model、for…

聚观早报 | 苹果市值跌破2万亿美元大关;卢伟冰晋升小米集团总裁

今日要闻&#xff1a;苹果市值跌破2万亿美元大关&#xff1b;卢伟冰晋升小米集团总裁&#xff1b;京东方拿下iPhone 15订单&#xff1b;英伟达与富士康达成合作&#xff1b;哪吒汽车旗下车型价格调整苹果市值跌破2万亿美元大关 1 月 4 日消息&#xff0c;据国外媒体报道&#x…

C51单片机连接wifi模块,发送AT指令

一、AT指令AT 指令集是从终端设备&#xff08; Terminal Equipment &#xff0c; TE) 或 数据终端设备 &#xff08; Data TerminalEquipment &#xff0c; DTE) 向终端适配器 (Terminal Adapter &#xff0c; TA) 或 数据电路终端设备 (Data CircuitTerminal Equipment &#…

CDGA|企业数字化转型进展得越快就越好吗?

数据治理并不是一件简单的事情。即使是行业知名公司&#xff0c;在高度重视和确保投入的情况下&#xff0c;完成全公司“数据底座”/“数据中台”的所耗时间也往往以年计。并且&#xff0c;还需要注意到&#xff0c;数据规范只是数字化转型的一个维度而已&#xff1a; 在国标《…