微信小程序 rich-text 超过 2 行显示省略号

rich-text(富文本),如果想实现文本超过两行变成省略号,常规的 div 可以实现,但因为是在微信小程序中,同时使用的是 rich-text 返回的是富文本,所以不能简单的使用以下代码实现:

word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;

因为富文本使用的 rich-text 回显的,想着直接对这个标签写上这个 CSS,发现也无法达到想要的效果。Android 真机可以正常显示,在模拟器上也能正常变成省略号,但 iOS 真机不兼容。

解决办法

在回显的 rich-text 中包裹一层 div,在这个包裹层中写上样式,就可达到超过两行隐藏的效果。

<rich-text style="word-wrap: break-word;word-break: break-all;" nodes="<div style='text-overflow:ellipsis; -webkit-box-orient:vertical;-webkit-line-clamp:2; overflow: hidden; display: -webkit-box;'>{{assetInfo.description}}</div>"></rich-text>

如上演示加粗部分就是需要手工增加的内容,手工在数据外加一层 div 包裹在外即可解决问题。

如何使用 NPM 将 package.json 内的依赖一键升级到最新版本

随着项目的创建、维护及迭代会使 package.json 内部引入很多的依赖,但依赖也是会随着时间进行更新,当后期使用其他依赖时,现有的部份依赖因为版本老旧问题导致无法安装,这时就需要更新依赖,那么如何使用 NPMpackage.json 一键升级到最新版本?

全局安装 npm-check-updates

npm i -g npm-check-updates

检查并更新依赖版本

package.json 所在目录(根目录)执行如下命令,可以查看当前的以来版本和最新的依赖版本

ncu -u

执行完毕之后,可以看到所有依赖的当前的版本和最新版本号

重新安装依赖

npm install

重新安装依赖后 package.json 内的依赖版本就会更新到最新版本

Vue 项目配置 @ 别名

在实际项目中,我们通常可以将 src 目录通过设置别名为 @ 目录,这样引入文件时候可以一目了然而且使用起来非常方便,可以提高我们的开发效率。

@ 代表的是 src 文件夹,这样将来文件过多,找的时候也方便,而且也还有提示。

Webpack + JavaScript 项目配置 @ 别名

在项目新建 vue.config.js,编辑 vue.config.js 内容如下:

const path = require('path')
 
function resolve(dir) {
  return path.join(__dirname, dir)
}
 
module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        '@': resolve('src')
      }
    }
  }
}

新建 jsconfig.json,内容如下:
@node_moulesdist 文件中不能使用)

# 方法一

{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "baseUrl": "./",
    "moduleResolution": "node",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  }
}
# 方法二

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "dist"
  ]
}

Vite + TypeScript 项目配置 @ 别名

编辑 vite.config.ts 内容如下:

import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import { resolve } from 'path'
 
export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src') // 路径别名
    },
    extensions: ['.js', '.json', '.ts', '.vue'] // 使用路径别名时想要省略的后缀名,可以自己 增减
  }
})

编辑 tsconfig.json,内容如下:

{
  "compilerOptions": {
    "baseUrl": ".",
    // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
    "paths": {
      // 用于设置模块名到基于baseUrl的路径映射
      "@/*": [
        "src/*"
      ]
    }
  }
}

使用方法

重新运行一遍项目即可

import Home from '@/pages/Layout/index.vue'

可能出现的问题

使用 WebStorm + Vue 3 + TypeScript 开发项目时使用 @ 别名可能会存在以下报错:

Cannot find module ‘@/views/xxx.vue‘ or its corresponding type declarations

意思是说找不到对应的模块“@/views/xxx.vue”或其相应的类型声明,因为 TypeScript 只能解析 .ts 文件,无法解析 .vue 文件

解决方法

查找项目内的 vite-env.d.ts 文件,一开始的时候 vite-env.d.ts 是空文件,我们可以在其中引入如下代码:

declare module '*.vue' {
  import { DefineComponent } from "vue"
  const component: DefineComponent<{}, {}, any>
  export default component
}

加入上面的代码后重新运行项目就不再报错了。

参考文章

Vue项目中怎么配置在src文件下@别名
vue3/vue2 项目配置 别名 @
Cannot find module ‘../views/HomeView.vue‘ or its corresponding type declarations.ts
webstorm vue3+ts报错:Cannot find module ‘@/views/xxx.vue‘ or its corresponding type declarations

旅行 · 记录 · 2023

从学校毕业答辩完成离校以后,外出旅游了一次,想把这次的旅行记录一下,不过介于时间有点长,可能有些详细内容已经记不清楚了,这次旅行前往了北京和天津这两座城市。

当然,不确定这个文章会有多少人能够看到,主要也算是自己记录一下吧,想起来什么就说什么,所以可能会很乱,如果你看到这篇文章你也可以在评论区留下自己想说的话,与我一起分享。

北京 · 旅行

5月29日下午,坐动车到达了北京,到达前在网上看了很多旅行路线推荐,但是真到了北京之后倒也是没有跟推荐的路线走,而是想到了哪就去哪。

5月29日下午到达北京后先坐地铁不断换乘前往自己住的酒店,北京的酒店相较其他大部分城市来说属于价格比较贵的了(可能只是相较于本人来说),我住的酒店在朝阳公园旁边,在美团上订的一晚200+,而且也不是连锁酒店,如果想去北京手头富裕的话还是建议住连锁品牌的酒店。有一说一,任何平台上的房间图建议不要太相信,同时也多看看酒店评论和其中的图片,给自己打个预防针,而且这个地方寸土寸金,每个房间不可能都太大,当然如果钱多的话那就另说了。同时,尽可能住的房间窗户不建议选走廊的,晚上睡觉的时候会很吵。

其次,北京的交通特别发达,但是不同线的地铁之间换乘在有些站很麻烦,有时候需要在通道里走很远,但是在哪里都能很方便的找到地铁口。同时,北京人也很多基本每个地铁都是人从众叕,如果是第一次来的话需要做好心理准备。

当天晚上坐公交车先去了一趟三里屯,三里屯分北区、南区、西区还有三里屯SOHO,第一天晚上可以来这里看看,能够看到很多街拍。一些大牌也都能在这里找到,如果时间允许可以都逛一逛。美食也有很多,可以在平台上看看有没有自己喜欢美食。我当天晚上吃的麻辣香锅,吃饱喝足以后就回酒店了,在北京基本交通出行如果远的话基本都一小时打底。

第二天早晨,早早就起来了,洗漱完以后就去了护国寺小吃,点了份豆汁、焦圈、麻酱烧饼。豆汁酸溜的,大部分人可能都不太能喝的下,我也就喝了半碗。但是去都去了,点一碗可以尝尝。

吃过早餐以后就坐地铁到天安门广场参观,现在如果想要近距离参观天安门和天安门广场,需要至少提前一天在“天安门广场预约参观”小程序上进行预约,里面分为了升旗、上午、下午、降旗,这四个时间段每天都可分别预约一次,到了预约的当天就可以去了,坐地铁出来时会有一次刷身份证过安检的环节,有点费时间,所以建议早点去,从地铁站出来后到天安门前还会有一次安检,这次的安检会比地铁站内的严格,所以更费时间,我从地铁下车到进入天安门耗时将近一个小时。过完安检之后就可以近距离参观天安门雄伟壮观的景象啦。

推荐的游览路线:人民英雄纪念碑 – 毛主席纪念堂(需要提前预约)- 天安门(需要提前预约)-故宫(需要提前预约)

我来北京前预约天安门广场预约的日期毛主席纪念堂和故宫当天的票已经都预约满了,所以我预约的6月2日参观毛主席纪念堂和故宫。这些地方通过刷身份证能够知道你当天是否预约了,所以在北京建议时刻带着身份证。

未完待续

Nest.js 配置文件上传及下载

最近使用 Nest.js 做项目,需要用到文件上传及下载功能,关于 Nest.js 的上传、下载文件一直没有仔细研究过,经过将近两天的查阅资料和动手实践,整合相关 Nest.js 上传、下载文件的相关方法。

全局配置

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 配置 public 文件夹为静态目录,以达到可直接访问下面文件的目的
  const rootDir = join(__dirname, '..');
  app.use('/public', express.static(join(rootDir, 'public')));
  
  app.useGlobalPipes(new ValidationPipe());
  app.useGlobalFilters(new HttpExceptionFilter());
  app.useGlobalInterceptors(new TransformInterceptor());
  await app.listen(3000);
}

创建 upload 模块

nest g resource upload --no-spec

模块配置

upload.module.ts

在 Module 中添加全局配置,这种方式会将内容存储在 public/upload/ 目录下,并根据代码中配置进行判断,如果文件夹中不包含分类文件夹则自动创建

import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { checkDirAndCreate } from '../../utils/checkDirAndCreate';
const image = ['gif', 'png', 'jpg', 'jpeg', 'bmp', 'webp'];
const video = ['mp4', 'webm'];
const audio = ['mp3', 'wav', 'ogg'];
const work = ['txt', 'rtf', 'pdf', 'xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx'];

// 配置文件上传
const multerOptions: MulterOptions = {
  // 配置文件的存储
  storage: diskStorage({
    // 存储地址
    // 配置文件上传后的文件夹路径
    destination: (req, file, cb) => {
      // 根据上传的文件类型将图片视频音频和其他类型文件分别存到对应英文文件夹
      const mimeType = file.mimetype.split('/')[1];
      let temp = 'other';
      image.filter(item => item === mimeType).length > 0
        ? (temp = 'image')
        : '';
      video.filter(item => item === mimeType).length > 0
        ? (temp = 'video')
        : '';
      audio.filter(item => item === mimeType).length > 0
        ? (temp = 'audio')
        : '';
      work.filter(item => item === mimeType).length > 0
        ? (temp = 'file')
        : '';

      const filePath = `./public/upload/${temp}/`;
      checkDirAndCreate(filePath); // 判断文件夹是否存在,不存在则自动生成
      return cb(null, `./${filePath}`);
    },
    // 存储名称
    filename: (req, file, callback) => {
      const suffix = extname(file.originalname); // 获取文件后缀
      const docName = new Date().getTime(); // 自定义文件名
      return callback(null, `${docName}${suffix}`);
    }
  }),
  // 过滤存储的文件
  fileFilter: (_req, file, callback) => {
    // multer 默认使用 latin1 编码来解析文件名, 而 latin1 编码不支持中文字符, 所以会出现中文名乱码的现象
    // 这里将文件名从 latin1 编码转换为 Buffer 对象, 再用 toString('utf8') 将 Buffer 对象转换为 utf8 编码的字符串
    // utf8 是一种支持多国语言的编码方式, 这样就可以保证文件名的中文字符不会被错误解析
    file.originalname = Buffer.from(file.originalname, 'latin1').toString(
      'utf8',
    );
    callback(null, true);
  },
  // 限制文件大小
  limits: {
    // 限制文件大小为 10 MB
    fileSize: 10 * 1024 * 1024, // 默认无限制
    // 限制文件名长度为 50 bytes
    fieldNameSize: 50, // 默认 100 bytes
  }
};

@Module({
  imports: [MulterModule.register(multerOptions),],
  controllers: [UploadController],
  providers: [UploadService]
})
export class UploadModule { }

checkDirAndCreate.ts

// src/utils/checkDirAndCreate.ts

import * as fs from 'fs';

export const checkDirAndCreate = (filePath: string) => {
    const pathArr = filePath.split('/');
    let checkPath = '.';
    let item: string;
    for (item of pathArr) {
        checkPath += `/${item}`;
        if (!fs.existsSync(checkPath)) {
            fs.mkdirSync(checkPath);
        }
    }
};

上传单个文件

import { Controller, Post, UseInterceptors ,UploadedFile} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file) {
  console.log(file);
}

现在让我们测试下,看下收到的 file 是什么样的。可以使用 postman,或者其他工具,这里不介绍如何使用工具,如果需要可在评论区评论。如下:

{
  fieldname: 'file',
  originalname: 'ceshi.png',
  encoding: '7bit',
  mimetype: 'image/png',
  buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 80 00 00 04 38 08 06 00 00 00 e8 d3 c1 43 00 00 20 00 49 44 41 54 78 9c ec dd 77 9c 94 f5 bd ... 1874657 more bytes>,
  size: 1874707
}

可以看到我们获得的一个文件的详细信息,以及一个 buffer,我们可以通过收到的 buffer 进行存储,如下。

import * as fs from 'fs';

fs.writeFileSync('./hah.jpg', file.buffer);

目前为止我们已经学会如何上传一个文件以及存储。

上面这种方式,每次都需要自已写保存文件的方式,可以通过配置,来定义上传。

上传多个文件

如下,与上面的不同之处有两个 FilesInterceptorUploadedFiles

import { Controller, Post, UseInterceptors, UploadedFile, UploadedFiles } from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';

@Post('upload')
@UseInterceptors(FilesInterceptor('file'))
uploadFile(@UploadedFiles() files) {
  console.log(files);
}

日志如下:

[
  {
    fieldname: 'file',
    originalname: '1.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 80 00 00 04 38 08 06 00 00 00 e8 d3 c1 43 00 00 20 00 49 44 41 54 78 9c ec dd 7b ac 6d d9 55 ... 1770419 more bytes>,
    size: 1770469
  },
  {
    fieldname: 'file',
    originalname: '2.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 80 00 00 04 38 08 06 00 00 00 e8 d3 c1 43 00 00 20 00 49 44 41 54 78 9c ec dd 77 9c 94 f5 bd ... 1874657 more bytes>,
    size: 1874707
  }
]

自定义不同的字段名称键

@Post('upload')
@UseInterceptors(FileFieldsInterceptor([
  { name: 'avatar', maxCount: 1 },
  { name: 'background', maxCount: 1 },
]))
uploadFile(@UploadedFiles() files) {
  console.log(files);
}

也可携带其他参数

 @Post('uploads')
    @UseInterceptors(FileFieldsInterceptor([
        { name: 'avatar', maxCount: 1 },
        { name: 'background', maxCount: 1 },
        { name: 'avatar_name'},
        { name: 'background_name'}
    ]))
    async uploads(@UploadedFiles() files,@Body() body) {
        console.log(files,body)
    }

日志如下:

[Object: null prototype] {
  avatar: [
    {
      fieldname: 'avatar',
      originalname: '应用预览图-1.png',
      encoding: '7bit',
      mimetype: 'image/png',
      buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 80 00 00 04 38 08 06 00 00 00 e8 d3 c1 43 00 00 20 00 49 44 41 54 78 9c ec dd 7b ac 6d d9 55 ... 1770419 more bytes>,
      size: 1770469
    }
  ],
  background: [
    {
      fieldname: 'background',
      originalname: '应用预览图-3.png',
      encoding: '7bit',
      mimetype: 'image/png',
      buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 80 00 00 04 38 08 06 00 00 00 e8 d3 c1 43 00 00 20 00 49 44 41 54 78 9c ec dd 77 9c 94 f5 bd ... 1838100 more bytes>,
      size: 1838150
    }
  ]
} [Object: null prototype] {
  avatar_name: 'ceshi',
  background_name: 'ceshi'
}

上传文件使用任意的字段名称

  @Post('upload5')
  @UseInterceptors(AnyFilesInterceptor())
  uploadFileAny(@UploadedFiles() files) {
    console.log(files);
  }

参照文章

WordPress 文章转为 Markdown 格式

因为 WordPress 导出的文章是 xml 格式的文件,如果想转到其他博客平台的话非常不方便,所以想将 WordPress 的文章转换成 Markdown 格式的文件。

上网搜了一下有很多这种功能的工具,我用的是 Blogger to Markdown 这个工具。

这个工具使用很简单,如同自述文件里写的一样:

  • 下载这个项目压缩包并解压缩
  • cd 到该目录下
  • 运行 npm install 安装依赖
  • 运行 node index.js <arg>

因为我是要从 WordPress 导出为 Markdown 格式的文件,所以运行:

node index.js w your-wordpress-backup-export.xml out

稍等一会你的所有文章就都统一在 out 文件夹中生成了。

替换 Gravatar 头像

之前重新配置 WordPress 后曾经替换过 Gravatar 头像源,但是没有想到自己用的这个 WordPress 官方主题年份已经这么旧了,竟然还有更新,导致我写入 functions.php 内的代码被覆盖掉了,我之前的头像替换就丢失了。

因为之前有搜索过关于头像的插件但都没有找到简单、易使用的,有些插件不是太重、就是太旧,夹杂的东西很多都是用不上的,有的也跟我想要的纯粹的替换头像不太一样,所以就从网上搜索,自己再次替换 functions.php 这次并作一次记录,以防后续文件覆盖后丢失。

打开 functions.php 文件,并在文件内加入如下代码:

/**
 * WordPress 头像
 */
function theme_get_avatar( $avatar ) {
	$avatar = preg_replace("/\/\/(www|\d|secure|cn).gravatar.com\/avatar\//", "//cdn.v2ex.com/gravatar/", $avatar);
	return $avatar;
}

add_filter('get_avatar', 'theme_get_avatar');

这次使用的是 V2EX 的 CDN 服务,针对国内和国外线路都有优化,而且还支持ssl访问。

同时从网上搜索了几个支持 Gravatar CDN 的网址:

你还想知道……

WordPress 如何显示“链接”功能?点击查看解决方案

Docker Mysql 8 自动定时备份

自服务器重新维护以后,上一次的 MySQL 数据库定时备份由于直接写在 crontab 中,导致这次再次测试却一直也不好使,反而导致我测试浪费了好几天。

这回从网上找到了其他方法,我今天测试了一下已经能够正常自动定时备份了。

创建备份文件

mkdir /data/backup
cd /data/backup

编写备份脚本代码

vim backup.sh
#!/bin/sh
#-h 后面改为自己的ip
#-u 后面改为自己的数据库账号
#-p 后面改为自己的数据库密码,有字符需要加""
#demand_database改为你想要备份的数据库名称
echo "开始备份数据库";
#导出所有数据库 username 替换为自己mysql登陆名,password123登陆密码
## mysqldump -h106.14.XX.XXX -uusername -p"password123" --all-databases > /data/mysqlbackup/databaseName`date +%Y-%m-%d_%H%M%S`.sql;

#导出指定数据库并压缩
## mysqldump -h106.14.XX.XXX -uusername -p"password123"  demand_database| gzip > /data/mysqlbackup/databaseName`date +%Y-%m-%d_%H%M%S`.sql.gz;

#最近转投docker怀抱,本地不安装mysql时,采用docker的mysql备份,备份语句修改为
docker exec mysql sh -c 'exec mysqldump --all-databases -uUSERNAME -pPASSWORD --all-databases' > /data/backup/database_`date +%Y-%m-%d_%H%M%S`.sql;

# mysql 替换为对应的容器名
#删除 3 天前的备份文件

backupdir=/data/backup
db_name=databaseName_

find $backupdir -name $db_name"*.sql.gz" -type f -mtime +3 -exec rm -rf {} \;


echo "备份完成";

测试以上代码需要根据自己的需求进行相应修改。

更改备份脚本权限

chmod +x dbbackup.sh

使用 crontab 定时执行备份脚本

crontab -e

输入上面的命令后会进入 vim 编辑的一个文件,在上面写 cron 表达式 + 脚本地址就行了

测试 每 1 分钟执行一次:

*/1 * * * *  /data/backup/backup.sh

我设定每 12 个小时更新一次,添加如下代码:

0 0 */12 * * /data/backup/backup.sh

参照文章

MySQL8 定时备份数据库

docker MySQL数据库的备份与还原,以及每天定时自动备份

开发一个仿朋友圈主题练手博客-Moment

很久没有发过博客了,最近在开发一个仿朋友圈主题的简陋版博客,但也没有完全完成开发,可能算是开发到了一半中的一半吧,期间捡起、放下、再捡起,需要重新看文档进行回忆,所以导致耽误了很长一段时间,即使这样,开发也不是一帆风顺,甚至说是磕磕绊绊,一个小问题可能导致耽误一天的时间。

这个项目我一般还是喜欢称它为:朋友圈,因为对于它的定位我其实也是捉摸不定的,目前权当是练手了。你可以在这里查看目前的内容:点击跳转到 Moment

目前完成了如下内容:

前端
– 昵称、头像、背景图、个性签名
– 动态显示头像、昵称(点击跳转网址链接)、内容、内容标签
– 发布天数
– 点赞
管理后台
仪表盘
– 显示发布动态数量
– 发布动态
设置
– 修改网址名称
– 修改网址头像
– 修改网址背景

这些看着很多,但其实功能都很简单,但作为刚开始学习的我来说,确实很麻烦,以至于有些地方写的很冗余、杂乱,欠缺考虑。但是也不能一直止步不前,只能在往后的时间里不断优化和完善内容了。

如果,你觉得这个项目对你来说有用处或者也想练练手,你可以选择帮忙一同开发维护,如果不介意,也可以帮忙点个 ⭐ Star。

前端:Moment

服务端:Moment Server

网站回归

嗨,欢迎回来。

可能你第一次访问,也可能你是老访客了,总之看到这里的时候说明网站的服务器前一阵子崩掉了。

说来也奇怪,凌晨两点之后服务器的监控指标突然就全部消失了,这也是服务器在崩溃后的第二天我登录服务器管理后台才发现。服务器的磁盘存储空间被塞满了,一点空间都没剩下。

记忆里,服务器磁盘空间被塞满的前一天晚我刚添加了一个青龙的 js 文件,之后就没再管理。所以我顺着这条思路排查,发现果然是青龙的日志文件把服务器空间塞满了。遂开始清理文件,恢复服务器访问。等处理好之后已经花了大半天时间,看着服务器里的文件也都存在着各种各样的错乱,内心解决了许久决定重置一下服务器,最终终究了好几天(对的,这件事是一周前发生的)终于在前天重置了服务器,直到昨天才将大部分内容重新处理好。

虽然将博客之前的文章备份了,但是恢复过来后有些设置没有变更,所以有些文章目前在搜索引擎上的路径和现在恢复后的路径有所不同,同时有些文章自己看了后觉得属于是水文,所以也稍微删除了点。