2024年2月


0 abstract

Recent advance in deep offline reinforcement learning (RL) has made it possible to train strong robotic agents from offline datasets. However, depending on the quality of the trained agents and the application being considered, it is often desirable to fine-tune such agents via further online interactions. In this paper, we observe that state-action distribution shift may lead to severe bootstrap error during fine-tuning, which destroys the good initial policy obtained via offline RL. To address this issue, we first propose a balanced replay scheme that prioritizes samples encountered online while also encouraging the use of near-on-policy samples from the offline dataset. Furthermore, we leverage multiple Q-functions trained pessimistically offline, thereby preventing overoptimism concerning unfamiliar actions at novel states during the initial training phase. We show that the proposed method improves sample-efficiency and final performance of the fine-tuned robotic agents on various locomotion and manipulation tasks.

  • background:
    • 我们希望做 offline 2 online: offline RL 训练 robotic agent 已经很强大了。然而,1. agent 质量不好,2. task 希望定制,通常希望通过进一步的 online 交互,来 fine-tune 这些 agent。
    • gap:我们发现,state-action distribution shift 可能导致 fine-tune 过程中的 bootstrapping error,从而破坏 offline RL 的良好初始策略。
  • method:
    • ① 提出了一种平衡的(balanced)重放方案(replay scheme),优先考虑 online sample,同时也鼓励使用 offline dataset 中的 near-on-policy samples。
    • ② 利用了离线悲观训练的(trained pessimistically offline)多个 Q function,从而防止在初始训练阶段,对新状态下的不熟悉 action 过度乐观。
    • near on-policy experience replay(?) + pessimistic Q-ensemble。
  • results:
    • proposed method 提高了 fine-tune robotic agent 在各种 locomotion 和 manipulation task 上的样本效率和最终性能。

主要思想

  • motivation:
    • 我们希望选取更加 on-policy 的 transition,更新 sac 的 actor-critic。
    • 这样可以避免 state-action distribution shift,即,使用当前 value function 的 unseen action 更新 value function,会导致 bootstrapping error,毁掉整个 Q function。
  • 观察:
    • 发现在 online fine-tune 刚开始时,使用 offline samples 学习缓慢,使用 online samples 有 bootstrapping error 的风险;
    • 因此在 offline online 之间权衡,用 density function 的估计度量 on-policy-ness,选取 offline + online 里最 on-policy 的 samples,做 sac fine-tune。
    • (story 是这样讲的,但感觉 importance sampling 的故事更好)。
  • offline online 权衡:
    • 在 offline + online buffer 的采样概率,应当与
      \(d^{on}(s,a) / d^{off}(s,a)\)
      成正比(importance sampling)。
  • (原来 near-on-policy 的最初的 motivation,是避免 distribution shift 嘛…… 有端联想 QPA)

1 intro

  • off-policy RL 不能直接做 offline 2 online:
    • off-policy RL 的应用场景,貌似适合 offline 2 online,因为 off-policy RL 可以同时利用 offline 和 online 样本。
    • 然而,由于 distribution shift,传统 off-policy RL 进行 online 微调的效果不好,因为 agent 可能会遇到 offline dataset 中 unseen 的 state-aciton。
    • 对于这种 OOD 的 online sample,Q function 无法提供准确的值估计,使用这种 sample 进行 Q update 会导致严重的 bootstrapping error,最终导致 policy update 在任意方向(in an arbitrary direction),破坏 offline RL 的良好初始策略。
  • 解决 state-action distribution shift,引入一种 balanced replay scheme:
    • 除了 online 样本外,还为 agent 的 Q update 提供 offline dataset 中的 near-on-policy 样本。具体的,我们训了一个 NN,来测量 offline samples 的在线性(online-ness),然后根据该 measure,对样本进行优先级排序。
  • 解决对 unseen action 的 over-estimation,使用 pessimistic Q-ensemble:
    • 发现一类特定的 offline RL 算法,它们训练的悲观 Q function 是 offline 2 online 的绝佳起点。使用 Q 函数隐式约束策略,使其在初始微调阶段保持在行为策略附近(?)。

2 background

  • 回顾 Conservative Q-learning(保守的 Q-learning):
    • (policy evaluation)CQL 的 critic 更新的 loss function:
      \(\frac12E_{(s,a,s')\sim B}[Q-B^{\pi_\phi}Q]^2+\alpha_0E_{s\sim B}[\log\sum_a\exp Q(s,a)-E_{a\sim \pi_\beta}[Q(s,a)]]\)
      ,第二项拉低了 unseen action 的 Q value,拉高的 seen action 的 Q value。
    • CQL 的 policy update 与 SAC 相同。

3 offline RL + fine-tuning 的关键挑战: state-action distribution shift

主要在讨论 state-action distribution shift。

  • policy evaluation 使用样本(offline / online)的 trade-off:
    • online sample 的 fine-tune 效率高,但因为 unseen / OOD state-action 所以危险;offline sample 效率低但安全。
    • 若全部使用 online sample 来 fine-tune,会导致性能一开始大幅度下降。若随机采样 offline + online,则因为使用的 online 样本不够多,导致 value 更新缓慢,学习缓慢。
  • 发现 pessimistic Q function 更适合作为 offline 起点。为了更悲观一点,我们采用 pessimistic Q-ensemble。

4 method

整点知乎博客:

4.1 balanced experience replay

https://blog.csdn.net/sinat_37422398/article/details/127292692

直接看这一篇的 4.1 即可,对如何估计 density ratio
\(w(s,a)=d^{on}(s,a)/d^{off}(s,a)\)
讲得比较清楚。可以与原文交替看。感觉是神秘的 trick。

在这里插入图片描述

4.2 pessimistic Q-ensemble

https://blog.csdn.net/sinat_37422398/article/details/127292692

直接看这一篇的 4.2 即可。在 MuJoco task 里的 ensemble size N = 5。

4.3 算法概括

  • offline RL: CQL ensemble,得到 offline 的 policy / value 训练起点。
  • online fine-tune:
    • 初始化 density ratio estimation network ψ;初始化 online buffer
      \(B^{on}\)
      为空集、prioritized buffer
      \(B^{priority}\)
      为空集、default priority value p0 ← 1.0。
    • 对 offline buffer B^{off} 中的每个 transition τi,将 {(τi, p0)} 加入
      \(B^{priority}\)
    • 赋值 p0 ← P0。
    • for each iteration do:
      • 收集 online training samples:使用
        \(\pi_\phi\)
        收集一个 transition τ,将其加入
        \(B^{on}\)
        ,将 {(τ, p0)} 加入
        \(B^{priority}\)
      • 更新 density ratio network:从
        \(B^{on},B^{off}\)
        里采样 B 个 transition,计算 loss function
        \(L^{DR}(\tau)\)
        ,更新 ψ。
      • 更新 policy 和 value network:计算
        \(L^{SAC}_{critic}(\theta), L^{SAC}_{actor}(\phi)\)
        ,更新 θ φ。
      • 更新 priority values: for j = 1, ..., B,将 online 采样得到的 transition τj 的 priority 更新为
        \(\frac{w(s_i,a_j)}{E_{\tau^{off}\sim B^{off}}[w(s,a)]}\)
        ,更新 p0 ←
        \(\max(p_0,\frac{w(s_i,a_j)}{E_{\tau^{off}\sim B^{off}}[w(s,a)]})\)

大致思想:

  • offline RL 采用 CQL ensemble 作为训练起点,提供 actor 和 critic。
  • 对于 online fine-tune,维护一个 priority buffer(offline + online),其中采样概率 正比于 priority,priority 为
    \(d_{on}(s,a)/d_{off}(s,a)\)
    。(简单的 importance sampling)
  • 从 priority buffer 里生成 samples,直接继续训 sac。
  • “policy 生成 (s,a) 的概率”,使用神秘计算方式;在每一轮 online 交互后,还会更新 density ratio estimation network。

6 experiment

  • 实验环境:
    • D4RL 的 MuJoco locomotion tasks(halfcheetah, hopper, walker2d)。
    • 三个 sparse-reward pixel-based manipulation tasks,貌似是在 simulator 上的,没有真机。这个实验篇幅较短,D4RL 实验的分析篇幅长。
  • baselines:
    • AWAC: Advantage Weighted Actor Critic,offline 2 online RL 方法,用于训练策略以模仿具有高优势估计的行动。
    • BCQ-ft: Batch-Constrained deep Q-learning(BCQ),offline RL 算法,通过使用一个 conditional VAE,对 behavior policy 进行建模,来更新策略。通过应用与 offline training 相同的更新规则,将 BCQ 扩展到 online fine-tune setting。
    • SAC-ft: SAC 的 fine-tune 版本。
    • SAC:从头开始训练 SAC,无法访问 offline dataset;相当于,训练的初始策略是 random policy。
  • results:
    • proposed method 在所有任务上表现良好,而 AWAC 和 BCQ-ft 的性能高度依赖于 offline dataset 的质量,在 random dataset 的 performance 甚至不如 SAC。
    • 这是因为,AWAC 和 BCQ-ft 对 offline 和 online setting 都采用相同的正则化、悲观更新规则,无论是显式(BCQ-ft) 还是隐式 (AWAC),这会导致微调速度缓慢。相反,我们的方法依赖于悲观的初始化,因此可以享受更快的微调,同时不牺牲初始训练的稳定性。



调整心态

自从去年疫情放开后,大家都本想着今年大干一场,做好的经济腾飞的准备,出乎意料的是,没想到今年行情这么的差,尤其对于未来的市场经济大家也是没有什么信心,目前很多企业发展不如预期,许多人感到沮丧,我认为与其有情绪倒不如面对现实,接受环境的变化,调整好心态适应变化。不要再用过去经济增长时期的思维套用在当下的情景里面。要认识到在过去几十年里,国内的 GPD 总值在 10% 以上的快速增长,这在历史的长河里上不是常态,是属于小概率事件,经济增速放缓才是目前全世界发展的常态。在当下的环境中,很多企业因为本身发展迟滞,让原来很多短期实现财务自由的造富神话变的几乎不可能。相反的是让裁员,降本增效的事情成为常态,很多时候不是你不够优秀和努力的问题,而是环境导致土壤可能没有机会。有时候人会被环境限制,再努力也无用。当然,也不要只看到眼前的危机就放弃,危机危机,有时候危里有机。越是在不确定的时期,越是要静下心适应调整和做好充足的准备等待下一次的潮水的到来。

最近在技术社区看到很多人在讨论
防御式编程
,认为只要把代码写的不可读且不可维护就能防止被公司裁员,这种做法还赢得很多人的支持,我认为这是一种非常短视的行为,姑且不说这种行为会给企业造成伤害,而且一个成年人应该从重视自己的职场口碑开始做起,要爱惜自己的羽毛。不要做这种双输的事情。纳瓦尔曾经说过,“
个人声誉是有复利效应的
。”,你维护好自己的职场口碑,在你以后在工作变更或者自己创业的时候,良好的口碑都会为你的信用背书。 还有很多人在焦虑 35 岁的职场危机,但是中国现代的平均寿命在 85 岁,就已经算到了 35 岁,人生还有 50 年的时间,未来还有什么职业会出现也无法预知,反正未来有无限的可能性,只要在未来的时间里只要保持学习和成长,那么我们未来将见证怎样的一次又一次的变革和机遇呢?谁也无法预测,所以要保持长期主义思维,不要过早的把自己和思维埋进土了,就像荣格说过,“真正的人生从四十岁才刚刚开始,在那之前你只是做调研而已。”所以勇敢地去调研你的人生,人生每一步都不会白走。

自我定位

去年 Open AI 的 ChatGPT 横空出世后,大模型成了行业风口,有很多人开始大量投入 AI,提出 AI 原生概念 ,我个人认为不要盲目跟风口,AI 确实是未来的方向,但有些风口不一定跟上,AI 目前不是大部分普通人的赛道,但是我认为普通人可以思考如何利用 AI 的能力到我们的熟悉的领域,会是这几年的主旋律。也是大部分人可以抓住的 AI 红利。普通人要在内卷时代找到出路,首先要找到自己的方向,那什么是自己的方向呢 ?每个人情况不同,因人而异,很多技术小伙伴把技术专家作为自己的方向,但是根据我了解很多小伙伴的性格并不适合走技术专家的路线,导致这条路走的很艰辛和痛苦。选择大于努力,条条大路通罗马,世界那么大,未必只有技术专家这一条出路可以走出去。想要走出自己的路,首先要了解自己的优势,可以使用
盖洛普优势
等权威的测评工具来了解自己的偏好。在充分了解自己的优势,结合自己的优势,在自己的优势领域保持精进和深耕,在具体的 “场景 - 角色 - 目标” 中应用,这才是当下局势的解题的关键所在。就像我的性格内向,不善言辞和交际,但是我除了喜欢好技术外,我还喜好广泛的阅读和写作,喜欢思考和理解那些复杂和抽象概念,然后总结归纳和输出。每当我掌握一个知识点或者完成阶段性工作后,我就会把他们总结写下来沉淀在自己的知识库里面,有时会拿出来分享给团队和技术社区里面去帮助别人,这也帮我收获了一些影响力,结实了很多朋友和机遇。当工作中有类似的任务我也会主动去承担,这样不仅也发挥自己的优势也能帮助业务产生价值。在我的理想工作模型中,就是自己热爱还能给组织创建价值,并且获取源源不断的收入的工作。在职场中随着年龄的增长,我们要把重心和精力放到自己内心专注且热爱的事情上,因为热爱会给你带来不可替代的竞争优势。慢慢形成你的个人品牌,就是别人遇到这个问题首先就会想到要找你解决。要主动自己贴上品牌标签,例如 “安全领域专家”,“云计算专家” 或者 “XX 行业专家” 等等,要能够在领域中沉淀下来深度思考,持续的输出和表达,建立个人的影响力。才能走的更远更长久。

创造价值

很多技术小伙伴的理想是用技术创造价值,但是用技术创造价值,首先要理解价值是什么。例如我个人的技术价值成就感是来自用技术解决现实生活中的实际问题。例如我曾经构建的外卖配送系统,每天解决几十万人的外卖订单配送问题,也曾经组织带领团队在一个月内完成某大型国企的信息检索平台的交付等。但是如今互联网发展下行,互联网的机会可能不像原来那么多,从互联网走出来的同学可以把经验带入一些传统行业或者 To B 行业,现在产业数字化升级机会还是很多,行业里面既懂技术又懂业务的人不多,产业的数字化也主要依靠这群人来实现。在这些场景下技术专业的同学有不可替代的作用。技术只要能解决实际的问题,能够提供生产效率,就有它的价值。技术的价值来源市场中供需关系,技术人的务实是了解供需关系,解决需求创造社会价值。市场需求所在就是程序员用技术创造的价值所在。有需求才有价值。不要颠倒顺序。市场对人才的需求是复合型的,不是说单维度的能力不足以跨越时间周期(年龄),而且大部分人的单维度能力无法跨越时间周期,需要不断学习,成为综合和复合型人才,提升不可替代的能力,在深耕的领域创造更大的价值,才能很好的应对当下的危机。想要突破技术的局限,不要只局限在手中的代码,跳出去,用更广的视角去看待产品,用户,甚至是商业和市场。这很难,也很难得。用更广的视野看待事情,不仅有更多的机会,看待事情的格局也会更大。

前言

大家好,这里是白泽。
《Go语言的100个错误以及如何避免》
是最近朋友推荐我阅读的书籍,我初步浏览之后,大为惊喜。就像这书中第一章的标题说到的:“
Go: Simple to learn but hard to master
”,整本书通过分析100个错误使用 Go 语言的场景,带你深入理解 Go 语言。

我的愿景是以这套文章,在保持权威性的基础上,脱离对原文的依赖,对这100个场景进行篇幅合适的中文讲解。所涉内容较多,总计约 8w 字,这是该系列的第四篇文章,对应书中第30-39个错误场景。


折腾Docker 有几天了,整别的都没这个糟心。目前已经顺利部署运行起来了。顺便给大家分享下处理在Windows上使用.NET部署到Docker的相关问题解决方法。

1.  Docker无法安装问题

(下图是网上找了个类似的安装失败截图,页面大致一样,就是提示内容是 Docker Desktop 只能运行在win10 企业版/教育版 并且版本号1904以上)

首先呢我是Win10 教育版 并且版本长期不更新。

所以只能打开Windows更新 更新到1904版本吧,发现还是不行。于是升级到企业版好了。

以下是教育版升级企业版的方法

cmd命令提示符下依次操作如下命令即可:

lmgr.vbs /upk

slmgr /ipk NPPR9-FWDCX-D2C8J-H872K-2YT43

slmgr /skms zh.us.to

slmgr /ato

然后网上下一个免费的Win10激活工具 DragonKMS

然后就可以在Win10 上安装  Docker Desktop啦

至于Win7/Win8 可以安装 Docker Toolbox

2. starting the Docker Engine  /  Docker 命令失败,退出代码 125

在这里要注意了,安装好后会默认启动Docker。

starting the Docker Engine  是Docker 客户端一直在start 状态(就是起不来,这个状态是启动中,正常状态是is runing,如下所示)

Docker 命令失败,退出代码 125    这是从VS 客户端启动时报的错 (其实百度搜一下Docker错误码,这话意思还是Docker起不起来)

主要的问题在2点

1.打开下面这五个功能再重启Docker试试

依然还是起不来的话就恭喜你往下看

2  适用于linnux的windows 子系统是打开了,但是wsl/wsl2 发行版没装

通过适用于 Linux 的 Windows 子系统 (WSL),开发人员可以安装 Linux 发行版(例如 Ubuntu、OpenSUSE、Kali、Debian、Arch Linux 等),所以需要选择一个具体的版本并安装,推荐安装WSL2具体的版本

以下是微软安装WSL具体版本的方法。

https://learn.microsoft.com/zh-cn/windows/wsl/install

安装完记得检查WSL/WSL 是否安装成功

以下是检查命令:

检查 Windows 11 上的 WSL 版本

要检查 Windows 11 上安装的 WSL 版本,请使用以下步骤:

1. 在 Windows 11 上打开开始。

2. 搜索Command Prompt,右键单击顶部结果,然后选择以管理员身份运行选项。

3. 键入以下命令以检查设备上是否安装了 WSL 2 或 WSL 1,然后按Enter 键:

wsl --list --verbose

这应该是正常的返回值列表。

注意还有一步要做!!!

这时候大家的docker 应该都属于Runing 状态了,但是docker 默认是按照linux 环境配置的,可以切换到windows 环境。鼠标右键桌面右下角托盘内的docker图标, 中间的选项有一个
switch to windows containers..

docker在linux环境的配置选项 应该是  switch to windows containers..(目前在linux环境,点击后切换到windows环境)

docker在windows环境的配置选项 应该是  switch to linux containers..(目前在windows环境,点击后切换到linux环境)

3.分析引用时出错:“
mcr.microsoft.com/dotnet/aspnet:5.0 AS base
”不是有效的存储

标注的位置不一定要是aspnet:5.0,可能也是
aspnet:8.0nanoserver-1809,net core3.1,xxxx
等。

当然这一步是概率问题。主要是docker拉取镜像源的网络此时与你不通罢了。

这一刻聪明的同学们就会去搜索 (中科大,163,微软中国,docker中国等等)国内镜像源地址,然后开心的发现没一个通的。所以要继续往后看:

以下提供本人阿里云杭州docker镜像源地址

针对安装了Docker for Windows的用户,您可以参考以下配置步骤:

在系统右下角托盘图标内右键菜单选择 Settings,打开配置窗口后左侧导航菜单选择 Docker Daemon。编辑窗口内的JSON串,填写下方加速器地址:

{
  "registry-mirrors": ["https://ku9r06hg.mirror.aliyuncs.com"]
}

编辑完成后点击 Apply 保存按钮,等待Docker重启并应用配置的镜像加速器。

注意

Docker for Windows 和 Docker Toolbox互不兼容,如果同时安装两者的话,需要使用hyperv的参数启动。

docker-machine create --engine-registry-mirror=https://ku9r06hg.mirror.aliyuncs.com -d hyperv defaul
其实,如果是企业或者个人想要尝试,也可以自己注册阿里云docker镜像源地址,在阿里云搜索框输入:镜像加速。
注册好就能用啦。

4.支持docker的.Net版本有哪些

在Microsoft 搜索有关于Docker的文档有如下注解:

看了官网这么多文档,大家应该知道了,微软非常建议大家使用.NET7/8 部署到docker;尤其推荐.NET8
(但是并不代表不支持低版本)

以下放出支持的所有.NET版本:

总结起来就是:NET 5,6,7,8    NET CORE 2,3         部分framework            都能在docker中运行

只要能拉到镜像,那么此.NET框架就是在docker中允许运行的

可以用以下命令尝试拉取镜像:

docker pull 镜像名:标签

(其实命令中不带标签也行)

以下是关于.NET 8 的镜像源

图像 注释
mcr.microsoft.com/dotnet/runtime:8.0 .NET 8 多体系结构:支持 Linux 和 Windows Nano Server,具体取决于 Docker 主机。
mcr.microsoft.com/dotnet/aspnet:8.0 ASP.NET Core 8.0 多体系结构:支持 Linux 和 Windows Nano Server,具体取决于 Docker 主机。
ASP.NET Core 的 aspnetcore 映像具有多个优化。
mcr.microsoft.com/dotnet/aspnet:8.0-bullseye-slim .NET 8 运行时-仅在 Linux Debian 发行版
mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809 .NET 8 运行时-仅在 Windows Nano Server 上(Windows Server 版本 1809)

目前镜像支持的.NET版本还挺丰富,我最低拉了个core 3.1,中间那些net 5/6/7 的镜像都删了
(主要是docker强制装C盘,我这C盘强迫症犯了可就要把所有不用的文件全部移出C盘)

如果走到这里,基本所有的大坑都踩完了。再就是常规开发代码+顺顺利利部署了
(手动播放奥特曼BGM:奇迹再现)

打开VS 新建 core/framework 项目

  • 选择镜像中支持的,.NET框架版本
  • 启用Docker(其实这里勾不勾选都无所谓)

因为所谓的Docker支持在微软官网的解释中就是一个
Dockerfile
文件(没有后缀的文本文档)

大家有兴趣的话,可以在微软官网/文档=》搜索框输入:docker,出来的教学文档还挺多。

咋还可以在发布前添加docker支持

以下是糙米式讲解:

  • 用什么镜像
  • 构建誰
  • 启动谁

以下是细糠式讲解:

1 # This my first nginx Dockerfile2 # Version 1.0
3 
4 # Base images 基础镜像5 FROM centos6 
7 #MAINTAINER 维护者信息8 MAINTAINER tianfeiyu9 
10 #ENV 设置环境变量11 ENV PATH /usr/local/nginx/sbin:$PATH12 
13 #ADD  文件放在当前目录下,拷过去会自动解压14 ADD nginx-1.8.0.tar.gz /usr/local/  
15 ADD epel-release-latest-7.noarch.rpm /usr/local/  
16 
17 #RUN 执行以下命令18 RUN rpm -ivh /usr/local/epel-release-latest-7.noarch.rpm19 RUN yum install -y wget lftp gcc gcc-c++ make openssl-devel pcre-devel pcre &&yum clean all20 RUN useradd -s /sbin/nologin -M www21 
22 #WORKDIR 相当于cd23 WORKDIR /usr/local/nginx-1.8.0 
24 
25 RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make &&make install26 
27 RUN echo "daemon off;" >> /etc/nginx.conf28 
29 #EXPOSE 映射端口30 EXPOSE 80
31 
32 #CMD 运行以下命令33 CMD ["nginx"]
指令 描述
FROM # 基础镜像,一切从这里开始构建
MAINTAINER 镜像是谁写的,姓名+邮箱
RUN 镜像构建的时候需要运行的命令
ADD 添加内容: 比如加一个tomcat压缩包
WORKDIR 镜像的工作目录
VOLUME 镜像挂载的目录
EXPOSE 保留暴露的端口
CMD 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可以被代替
ENTRYPOINT 指定这个容器启动的时候需要运行的命令,可以追加命令
ONBUILD 当构建一个被继承DockerFile 的时候就会运行 ONBUILD 的指令。触发指令
COPY 类似ADD ,将我们的文件拷贝到镜像中
ENV 构建的时候设置环境变量

因为在docker容器里端口和https证书都需要配置,所以本地注释https证书。仅配置容器端口和主机端口相通

以下注释了https证书相关配置

以下dock容器端口绑定主机端口

紧接着一路开发下去,然后按照常规发布项目的方式发布到文件系统
(此处注意以下操作:)

项目内的dockfile 右键属性 ,复制到输出目录,设置始终复制

其实这一步你不设置,直接发布也行,将dockfile文件拷到发布目录一样行。(PS:手动笑脸)

接下来全部交给dows窗体和docker命令啦

  • 管理员启动cmd
  • cd  到发布目录
  • docker build -t  你想要设置的镜像名称:1.0 .(它根据当前目录的dockerfile选择镜像源)
  • docker run -d -p 你的docker容器端口如:56666666:绑定的主机端口(在appsettings.json,url节点绑定的端口要一致)--name 你想要的容器名称--restart=always 你上条命令的镜像名称:1.0
  • 最后在浏览器中访问docker部署的NET web应用程序

打开docker desctop,在containers,images选项中都能看到相关运行的容器和镜像源

 

也可以在cmd中使用

docker ps -a             查看正在运行的容器(不带-a 就是所有的容器,不区分是否正在运行)

docker images           查看所有的镜像源


以下附上 菜鸟教程 docker 命令大全
https://www.runoob.com/docker/docker-command-manual.html
全剧终。。。。。。

背景

项目当中需要进行业务时间的校验,如上午 9:00-下午 17:00,在 9:00 前或 17:00 后是不能处理相关业务的。因此在业务校验的
Service
中定义了一个
checkBizTime()
方法。原本代码如下:

public void checkBizTime() {
    Date currentTime = new Date();
    // DateUtil.parse的作用是将配置文件中读取的时间字符串转换为Date对象,
    // bizStartTimeStr、bizEndTimeStr 是从配置文件中读取的变量,用 @Value 注解注入
    Date bizStartTime = DateUtil.parse(bizStartTimeStr, "HH:mm:ss");
    Date bizEndTime = DateUtil.parse(bizEndTimeStr, "HH:mm:ss");
    if (currentTime.before(bizStartTime) || currentTime.after(bizEndTime)) {
        throw new BizException("不在业务时间范围内,无法处理业务");
    }
}

但是如何对这个方法进行单元测试,成了一个很头疼的问题。我们知道,单元测试具有独立性和可重复性,但如果要测试上面这段方法,就会发现当系统时间在 9:00 ~ 17:00 内时,这个方法可以通过测试,而不在这个时间范围内,这个方法就会抛出异常,也就是说,这个测试方法依赖于当前系统时间,且不同时间运行测试,得到的测试结果是不同的!这违反了单元测试的独立性和可重复性。因此我们必须让时间固定在某个特定的时间。

解决方法

解决方法:在
DateUtil
类中建立一个
getCurrentDate()
方法,这个方法返回
new Date()
对象。(如果
DateUtil
是第三方库的,或是其他人开发的,那么就在项目中自己定义一个,当然名字需要和
DateUtil
区分开)

public static Date getCurrentDate() {
    return new Date();
}

然后把上述业务代码中的
new Date()
部分替换成
DateUtil.getCurrentDate()

public void checkBizTime() {
    Date currentTime = DateUtil.getCurrentDate();
    // DateUtil.parse的作用是将配置文件中读取的时间字符串转换为Date对象,
    // bizStartTimeStr、bizEndTimeStr 是从配置文件中读取的变量,用 @Value 注解注入
    Date bizStartTime = DateUtil.parse(bizStartTimeStr, "HH:mm:ss");
    Date bizEndTime = DateUtil.parse(bizEndTimeStr, "HH:mm:ss");
    if (currentTime.before(bizStartTime) || currentTime.after(bizEndTime)) {
        throw new BizException("不在业务时间范围内,无法处理业务");
    }
}

然后编写单元测试,注意要先引入
mockito-inline
这个包,才可以对静态方法进行 Mock。

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <scope>test</scope>
</dependency>

单元测试代码如下:

class BizCheckServiceTest {
    @InjectMocks
    private BizCheckServiceImpl bizCheckServiceUnderTest;

    @Mock
    private MockedStatic<DateUtil> mockedDateUtil;

    @BeforeEach
    void setup() {
        openMocks(this);
        mockedDateUtil
            .when(DateUtil::getCurrentDate)
            .thenReturn(new Date(2024, 2, 3, 10, 0, 0));
        // 假设固定返回 2024年2月3日 10:00:00。但此构造函数已弃用,可以使用其他方式返回Date对象
        // 对 DateUtil 类中的其他方法,可以让他执行真实方法
        mockedDateUtil
            .when(() -> DateUtil.parse(anyString(), anyString()))
            .thenCallRealMethod();
    }

    @Test
    void testCheckBizTime() {
        bizCheckServiceUnderTest.checkBizTime();
        // 验证 getCurrentTime() 方法被执行1次,
        // parse() 方法被执行2次
        verify(mockedDateUtil, times(1)).getCurrentTime();
        verify(mockedDateUtil, times(2)).parse(anyString(), anyString());
    }

    @AfterEach
    void tearDown() {
        // 每次使用完 MockedStatic 接口需要关闭,不然会导致测试方法报错
        mockedDateUtil.close();
    }
}

这样就可以重复执行该单元测试,每次执行的结果应该都是一样的。保持了单元测试的独立性和可重复性。