自 2023 年 4 月起,Windows 11 已经重新支持显示“秒”,无需第三方工具来实现,点击查看详情。
ElevenClock 下载地址:GitHub,开源软件放心使用
效果:
* 该软件不会影响右下角的显示桌面和系统通知功能。
v3.3 设置方法:
√ 在主屏幕上时钟区显示本程序的时钟样式
ElevenClock 不直接修改任务栏上的时钟区域,而是将时钟覆盖在系统时钟区域的上方。
√ 不要在辅助监视器上显示时钟
此项按实际需求勾选
√ 时间与日期设置 - Set a custom date and time format (for advanced users only)
填写以下内容并 Apply
%H:%M:%S
%Y/%#m/%#d %a
如果不想显示星期,把 %a 去掉即可。
√ 使用自定义字体大小
因分辨率缩放设置不同可能导致显示的字体大小与系统时钟不同,会导致覆盖面过大或过小,从而使系统托盘中的其它图标显示不完整。所以应选择一个与系统时间差不多的字体大小。当显示“周序号”时 ElevenClock 时钟区域可能会远宽于系统时钟区域,可以设置系统时钟显示“星期”(方法见文末)。
v3.2 设置方法:
√ 在主屏幕上时钟区显示本程序的时钟样式
ElevenClock 不直接修改任务栏上的时钟区域,而是将时钟覆盖在系统时钟区域的上方。
√ 不要在辅助监视器上显示时钟
此项按实际需求勾选
√ 显示秒数
这是我们的最终目的。
√ 使用自定义字体大小
因分辨率缩放设置不同可能导致显示的字体大小与系统时钟不同,会导致覆盖面过大或过小,从而使系统托盘中的其它图标显示不完整。所以应选择一个与系统时间差不多的字体大小。当显示“周序号”时 ElevenClock 时钟区域可能会远宽于系统时钟区域,可以设置系统时钟显示“星期”(方法见文末)。
如何显示系统时间“星期”:
打开“更改日期和时间”,在“日期”选项卡的“短日期”中添加“ddd”。
将 Windows 更新代理更新到最新版本:从 Microsoft 下载中心手动下载 Windows 更新代理
再次尝试。
System.NotSupportedException: Serialization and deserialization of 'System.DateOnly' instances are not supported.
The unsupported member type is located on type 'System.Nullable`1[System.DateOnly]'.
解决方法:
https://www.nuget.org/packages/DateOnlyTimeOnly.AspNet/
builder.Services
.AddControllers(options => options.UseDateOnlyTimeOnlyStringConverters())
.AddJsonOptions(options => options.UseDateOnlyTimeOnlyStringConverters());
今天发现在开发新项目时,微信分享失效了,开启 debug = true 后,在微信开发工具中提示:
{"errMsg":"updateTimelineShareData:fail, the permission value is offline verifying"}
在真机上提示:
{"errMsg":"config:ok"}
{"errMsg":"updateAppMessageShareData:ok"}
{"errMsg":"updateTimelineShareData"}
仍然找不出原因。
尝试打开已上线的项目,分享功能均正常。
偶然发现,当打开使用 JS-SDK 分享后的图文框的页面,能够正常分享,而直接点开链接的页面,没有分享按钮。
这难免不让人联想到最近GJ出的新政,新版微信已经开放淘宝、抖音等平台的链接打开权限,只是当点击链接的时候会出现一个风险提示页面:
而一旦由这个页面进入的网页,分享功能均不能正常。
另外还测试了以下几种情况的分享功能也能正常:
公众号推送的图文或链接、公众号菜单、个人发送到公众号的链接、通过扫一扫打开的页面等。
-- 2021.9
本文记录于 2021 年 9 月。
升级前 | 期望(最新正式版) | 最终选择 | |
操作系统 | CentOS 6.5 | Alibaba Cloud Linux 3 | Alibaba Cloud Linux 3 |
管理面板 | lnmp | 宝塔面板 Linux 版 7.7.0 | 宝塔面板 Linux 版 7.7.0 |
Web 服务 | nginx 1.6 | nginx 1.21 | nginx 1.21 |
脚本语言 | PHP 5.6 | PHP 8.0 | PHP 7.4 |
数据库 | RDS MySQL 5.6 | RDS MySQL 8.0 | RDS MySQL 5.6 |
论坛程序 | Discuz! X3.2 GBK | Discuz! X3.5 UTF-8(即将发布) | Discuz! X3.4 GBK |
版本选择原因:
Alibaba Cloud Linux 完全兼容 CentOS,相比于 CentOS 较短的生命周期,Alibaba Cloud Linux 3 将于 2029 年 4 月 30 日结束生命周期。
Discuz! X3.4 不支持 PHP 8.0,安装时即报错,打开页面时一片空白。
MySQL 8.0 和阿里云 RDS 的 MySQL 7.5 不支持 MyISAM,而数据表 pre_common_member_grouppm 和 pre_forum_post 使用联合主键且自动递增字段不是第一主键,使用 InnoDB 引擎创建表时会报“1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key”错误,而擅自更改主键次序会影响业务逻辑。因此,在必须选择阿里云 RDS 的情况下,只能选择 MySQL 5.6。(2023年8月注:查看如何更改为 InnoDB)
Discuz! X3.5 正式版尚未发布(截止发稿),即便发布,插件也可能不能得到及时更新。相比之下,X3.4 首个版本发布距今已有 4 年,相关第三方插件已经非常成熟。
完整升级步骤:
备份原网站程序、RDS 数据库;
购买新的 ECS、RDS,挂载磁盘,安装云监控;
迁移(或还原)数据库到新的 RDS;
安装宝塔面板并配置;
安装 nginx 及 PHP;
创建网站、配置 SSL、伪静态、防盗链、可写目录禁执行等(.conf);
配置 hosts;
上传原网站程序到新的站点目录下;
按 Discuz! X 升级文档升级 X3.2 至 X3.4;详情见下文 ↓;
配置 OSS、Redis、更新缓存等;
测试论坛基本功能是否正常;检查附件是否正常显示;全面检查控制台配置;
逐个开启插件并检查兼容性;
按二开备忘录逐个按需进行二开;
逐个修改调用论坛接口的项目及直接调用论坛数据库的项目;
调试 MAGAPP 接口;
尝试强制 https 访问;
将以上所有修改后的程序保留备份;发布升级公告并关闭论坛;重复以上步骤;修改域名解析;开启论坛;
配置 IP 封禁、定时器、日志、自动备份、配置其它 ECS 的 hosts 等;
查看搜索引擎中收录的地址,是否有无法访问的情况;
尝试将历史遗留的本地附件全部转移到 OSS;
参这篇文章,可能有其它需要配置的地方。
Discuz! X 升级步骤及注意点:
升级前务必先修改 ./config/ 目录下的数据库/缓存连接信息,以防出现新站连接老库的情况;
按官方文档进行升级;
【问题】运行到 ./install/update.php?step=data&op=notification 时白屏。
【排查】尝试切换到 PHP 5.6 后成功(但该版本过于陈旧不能使用);尝试升级 CPU 和内存 PHP 7.4 上升级仍不成功。
【原因】DB::result_first() 方法不对 SQL 语句追加“limit 1”,而是 SELECT 所有记录后在 PHP 端取第一条数据;
【解决】打开文件 update.php,查找 elseif($_GET['op'] == 'notification'),该节点的功能是在表 home_notification 中查找 category <= 0 的数据并修复它,如果数据库中所有 category 都大于 0,直接注释其内部 if 代码段继续升级即可(或改为 if(false && ...))。
【问题】发布主题遇到错误:(1062) Duplicate entry '*' for key 'pid'
【原因】forum_post 中的 pid 不是自动增长的,而是由表 forum_post_tableid 中自动增长的 pid 生成的。如果生成的 pid 值已在 forum_post 表中存在,则会出现此错误。
【解决】迁移数据库时应关闭论坛,以防止 forum_post 表有新数据插入。
【问题】打开帖子页面 ./thread-***-1-1.html 显示 404 Not Found,而 ./forum.php?mod=viewthread&tid=*** 可以正常打开
【原因】未配置伪静态(可在宝塔面板中选择)
【问题】打开 UCenter 时报错:UCenter info: MySQL Query Error SQL:SELECT value FROM [Table]vars WHERE name='noteexists'
【解决】打开文件 ./uc_server/data/config.inc.php 配置数据库连接
【问题】打开登录 UCenter 后一片空白
【解决】将目录 ./uc_server/data/ 设为可写
需要将原来安装的插件文件移回 ./source/plugin/ 目录,并设置可写;
界面-表情管理,界面-编辑器设置-Discuz!代码
后续 Discuz! X3.4 R 小版本升级注意事项:
确认插件是否支持新版本(如短信通)
先创建一个新网站测试二开代码
保留 /config/、/data/、/uc_client/data/、/uc_server/data/、/source/plugin/,其它移入 old
上传文件
移回其它需要的文件,如:
-- 勋章/loading/logo/nv 等:/static/image/common/
-- 表情:/static/image/smiley/
-- 水印:/static/image/common/watermark.*
-- 风格:/template/default/style/t2/nv.png 等
-- 默认头像:/uc_server/images/noavatar_***.gif
-- 根目录 favicon.ico 等
-- 及其它非 DZ 文件
再次检查可写目录的写入权限和禁止运行 PHP 效果。
HttpHelper类:
using System; using System.Collections.Specialized; using System.IO; using System.Net; using System.Text; namespace ConsoleApplication1 { public static class HttpHelper { private static readonly Encoding DEFAULTENCODE = Encoding.UTF8; /// <summary> /// HttpUploadFile /// </summary> /// <param name="url"></param> /// <param name="file"></param> /// <param name="data"></param> /// <returns></returns> public static string HttpUploadFile(string url, string file, NameValueCollection data) { return HttpUploadFile(url, file, data, DEFAULTENCODE); } /// <summary> /// HttpUploadFile /// </summary> /// <param name="url"></param> /// <param name="file"></param> /// <param name="data"></param> /// <param name="encoding"></param> /// <returns></returns> public static string HttpUploadFile(string url, string file, NameValueCollection data, Encoding encoding) { return HttpUploadFile(url, new string[] { file }, data, encoding); } /// <summary> /// HttpUploadFile /// </summary> /// <param name="url"></param> /// <param name="files"></param> /// <param name="data"></param> /// <returns></returns> public static string HttpUploadFile(string url, string[] files, NameValueCollection data) { return HttpUploadFile(url, files, data, DEFAULTENCODE); } /// <summary> /// HttpUploadFile /// </summary> /// <param name="url"></param> /// <param name="files"></param> /// <param name="data"></param> /// <param name="encoding"></param> /// <returns></returns> public static string HttpUploadFile(string url, string[] files, NameValueCollection data, Encoding encoding) { string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); byte[] boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n"); byte[] endbytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n"); //1.HttpWebRequest HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.ContentType = "multipart/form-data; boundary=" + boundary; request.Method = "POST"; request.KeepAlive = true; request.Credentials = CredentialCache.DefaultCredentials; using (Stream stream = request.GetRequestStream()) { //1.1 key/value string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; if (data != null) { foreach (string key in data.Keys) { stream.Write(boundarybytes, 0, boundarybytes.Length); string formitem = string.Format(formdataTemplate, key, data[key]); byte[] formitembytes = encoding.GetBytes(formitem); stream.Write(formitembytes, 0, formitembytes.Length); } } //1.2 file string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n"; byte[] buffer = new byte[4096]; int bytesRead = 0; for (int i = 0; i < files.Length; i++) { stream.Write(boundarybytes, 0, boundarybytes.Length); string header = string.Format(headerTemplate, "file" + i, Path.GetFileName(files[i])); byte[] headerbytes = encoding.GetBytes(header); stream.Write(headerbytes, 0, headerbytes.Length); using (FileStream fileStream = new FileStream(files[i], FileMode.Open, FileAccess.Read)) { while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) { stream.Write(buffer, 0, bytesRead); } } } //1.3 form end stream.Write(endbytes, 0, endbytes.Length); } //2.WebResponse HttpWebResponse response = (HttpWebResponse)request.GetResponse(); using (StreamReader stream = new StreamReader(response.GetResponseStream())) { return stream.ReadToEnd(); } } } }
调用示例:
using System; using System.Collections.Specialized; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { NameValueCollection data = new NameValueCollection(); data.Add("name", "木子屋"); data.Add("url", "http://www.mzwu.com/"); Console.WriteLine(HttpHelper.HttpUploadFile("http://localhost/Test", new string[] { @"E:\Index.htm", @"E:\test.rar" }, data)); Console.ReadKey(); } } }
Nuget 包:System.Runtime.Caching
依赖注入:
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
定义键:
public static class CacheKeys
{
public static string Entry { get { return "_Entry"; } }
}
赋值与取值:
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// 尝试从缓存获取,若获取失败则重新赋值
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// 新的内容
cacheEntry = DateTime.Now;
// 缓存选项
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
// 保存到缓存中
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
注意:
.SetAbsoluteExpiration() 用于设置绝对过期时间,它表示只要时间一到就过期
.SetSlidingExpiration() 用于设置可调过期时间,它表示当离最后访问超过某个时间段后就过期
// 4月 2021
d1 = DateTime.Now.ToString("MMM yyyy"),
// 四月 2021
d2 = DateTime.Now.ToString("MMMM yyyy"),
// Apr 2021
d3 = DateTime.Now.ToString("MMM yyyy", System.Globalization.CultureInfo.CreateSpecificCulture("en-US")),
// April 2021
d4 = DateTime.Now.ToString("MMMM yyyy", System.Globalization.CultureInfo.CreateSpecificCulture("en-US")),
该问题将在 MySQL Connector/NET 8.0.24 中修复,参考:https://bugs.mysql.com/bug.php?id=102381
“DatabaseFacade”未包含“ExecuteSqlCommand”的定义,并且找不到可接受第一个“DatabaseFacade”类型参数的可访问扩展方法“ExecuteSqlCommand”(是否缺少 using 指令或程序集引用?)
思路:(以 Entity Framework Core 5.0 为例)
先借用 SELETE 的 IQueryable 语句,将 SELETE 关键字替换成 DELETE,再使用 FromSqlRaw 执行。这样可准确使用真实的数据库表名和字段名。
如:
using Microsoft.EntityFrameworkCore;
string sql = db.表.Where(c => c.Date < minDate).ToQueryString();
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "DELETE FROM", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `Date` < @__minDate_0
db.Database.ExecuteSqlRaw(sql);
以上例子针对 MySQL,且未考虑字符串内容中包含符号 ` 的情况。实例操作中应严格防范 SQL 注入。
封装成方法:
using Microsoft.EntityFrameworkCore;
const RegexOptions ro = RegexOptions.Singleline;
/// <summary>
/// 组装一个 DELETE SQL 语句
/// </summary>
/// <typeparam name="T">数据库表实体类</typeparam>
/// <param name="queryableWithConditions">IQueryable,可带筛选条件</param>
/// <returns></returns>
public static string AssembleDeleteSql<T>(IQueryable<T> queryableWithConditions) where T : class
{
// MySQL 版
string sql = queryableWithConditions.ToQueryString(); // SET @__minDate_0 = '2020-01-14'; SELECT `d`.`Date`, `d`.`Code6` FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "DELETE FROM", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `Date` < @__minDate_0
return sql;
}
/// <summary>
/// 组装一个 UPDATE SQL 语句
/// </summary>
/// <typeparam name="T">数据库表实体类</typeparam>
/// <param name="queryableWithConditions">IQueryable,可带筛选条件</param>
/// <param name="columnsAndValues">需要更改的字段和值</param>
/// <returns></returns>
public static string AssembleUpdateSql<T>(IQueryable<T> queryableWithConditions, Dictionary<string, object> columnsAndValues) where T : class
{
// MySQL 版
string sql = queryableWithConditions.ToQueryString(); // SET @__minDate_0 = '2020-01-14'; SELECT `d`.`Date`, `d`.`Code6` FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "UPDATE", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` WHERE `Date` < @__minDate_0
string sets = string.Join(",", columnsAndValues.Select(c => $"`{c.Key.Replace("`", @"\`")}` = '{c.Value.ToString().Replace("'", @"\'")}'"));
sql = Regex.Replace(sql, @"`\s*WHERE", $"` SET {sets} WHERE", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` SET `field1` = 'value1', `field2` = 'value2' WHERE `Date` < @__minDate_0
return sql;
}
调用示例:
using Microsoft.EntityFrameworkCore;
// 删除记录
DateTime minDate = DateTime.Today.AddYears(-1).AddMonths(-1);
string sql = AssembleDeleteSql(db.表.Where(c => c.Date < minDate));
db.Database.ExecuteSqlRaw(sql);
// 更新记录
var sets = new Dictionary<string, object>();
foreach (var p in db.Model.FindEntityType(typeof(表)).GetProperties())
{
switch (p.Name)
{
case nameof(表.字段一): sets.Add(p.GetColumnBaseName(), 123); break;
case nameof(表.字段二): sets.Add(p.GetColumnBaseName(), 456); break;
}
}
DateTime minDate = DateTime.Today.AddYears(-1).AddMonths(-1);
string sql = AssembleUpdateSql(db.表.Where(c => c.Date < minDate), sets);
db.Database.ExecuteSqlRaw(sql);