xyxsw
文章35
标签3
分类0
MonoRepo&Nuxt框架初始化实战 | 青训营笔记

MonoRepo&Nuxt框架初始化实战 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 8 天

前言

上一篇笔记介绍了MonoRepo的优点和基于go语言的MonoRepo框架TurboRepo,这一篇我们详细介绍一下如何配置出一个团队能用的TurboRepo库,包括一些actions和自动部署预览等。

技术栈

  • ⚡️ Nuxt3 - SSR框架
  • Vue3.2 - 最新Vue更多语法糖
  • 💨 Turbo - MonoRepo框架
  • 💎 Strapi - Headless CMS 框架
  • 😎 @Nuxt/Image - images 服务端渲染
  • 😁 Umami - Better Analytics 访问源分析
  • 🃏 Commit Lint - commit规范检查
  • 💖 Husky & Lint Staged — precommit运行库
  • 📏 Eslint - 规范化TypeScript、JavaScript代码
  • 🐶 UnoCss - 原子化 CSS 引擎、兼容 tailwindcss、windicss
  • 🤖 preset-icons - unocss 自带icons
  • ⏰ The <script setup> syntax setup语法糖 来自Vue3.2
  • 🍍 Pinia - 全局状态管理库 更好用
  • 🎨 APIs auto importing - 库自动引入
  • 🦾 TypeScript - 更规范的语法
  • 👷 Github Actions - actions自动运行、ci/cd云端检查语法
  • 👀 Automatic Branch and Issue Autolink - Issue AutoLink actions 创建issue会创建一个分支
  • 🗺 Nuxt SEO Kit - SEO优化 SSR必备
  • 📦 Vercel - 自动部署、预览的平台
  • 🔥 Netlify + Cloudflare - 部署平台

前端模板

前端模板选择了https://github.com/antfu/vitesse-nuxt3

是antfu的vitesse系列模板的nuxt3款

这套模板用了好多个 很好用的

配置很全、自带的unocss也是几乎兼容windicss和tailwind 很适应

想用icons可以去antfu的另一个项目里找

https://icones.js.org/ 这里二次封装了几万个icons用个爽

配置turbo

turbo官网有讲怎么配置到一个已有的MonoRepo里https://turbo.build/repo/docs/getting-started/existing-monorepo

这里只需要在根目录的package.json里建立workspaces字段 和packageManager字段

//package.json
{
"packageManager": "yarn@1.22.19",
  "workspaces": [
    "frontend",
    "backend"
  ],
}

安装TurboRepo

yarn global add turbo

创建任务管道

package.jsonturbo中,将想要”turbo”的命令添加到管道中 管道定义了 npm 包中 scripts 的依赖关系,并且为这些命令开启了缓存。这些命令的依赖关系和缓存设置会应用到 MonoRepo 中的各个包中

//turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      // A package's `build` script depends on that package's
      // dependencies and devDependencies
      // `build` tasks  being completed first
      // (the `^` symbol signifies `upstream`).
      "dependsOn": ["^build"],
      // note: output globs are relative to each package's `package.json`
      // (and not the monorepo root)
      "outputs": [".next/**"]
    },
    "test": {
      // A package's `test` script depends on that package's
      // own `build` script being completed first.
      "dependsOn": ["build"],
      // A package's `test` script should only be rerun when
      // either a `.tsx` or `.ts` file has changed in `src` or `test` folders.
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
    },
    // A package's `lint` script has no dependencies and
    // can be run whenever. It also has no filesystem outputs.
    "lint": {},
    "deploy": {
      // A package's `deploy` script depends on the `build`,
      // `test`, and `lint` scripts of the same package
      // being completed. It also has no filesystem outputs.
      "dependsOn": ["build", "test", "lint"]
    }
  }
}

上面的示例中,buildtest这两个任务具有依赖性,必须要等他们的依赖项对应的任务完成后才能执行,所以这里用^来表示。 对于每个包中 package.json 中的 script 命令,如果没有配置覆盖项,那么TurboRepo将缓存默认输出到 dist/** build/**文件夹中。

运行

这样只需要运行

npx turbo run build test lint deploy

就可以运行全部参数

配置.gitignore

根目录 创建.gitignore文件

+ .turbo
+ build/**
+ dist/**
+ .next/**

Remote cache

turbo login
turbo link

然后删除你的./node_modules/.cache/turbo文件夹

显示 full turbo 则证明匹配到了云端的缓存,直接拉下来不再构建一遍

配置husky

在根目录安装husky

先运行husky install

然后创建.husky文件夹

文件树如上

配置文件

//.gitignore
*
# husky.sh
#!/usr/bin/env sh
if [ -z "$husky_skip_init" ]; then
  debug () {
    if [ "$HUSKY_DEBUG" = "1" ]; then
      echo "husky (debug) - $1"
    fi
  }

  readonly hook_name="$(basename -- "$0")"
  debug "starting $hook_name..."

  if [ "$HUSKY" = "0" ]; then
    debug "HUSKY env variable is set to 0, skipping hook"
    exit 0
  fi

  if [ -f ~/.huskyrc ]; then
    debug "sourcing ~/.huskyrc"
    . ~/.huskyrc
  fi

  readonly husky_skip_init=1
  export husky_skip_init
  sh -e "$0" "$@"
  exitCode="$?"

  if [ $exitCode != 0 ]; then
    echo "husky - $hook_name hook exited with code $exitCode (error)"
  fi

  if [ $exitCode = 127 ]; then
    echo "husky - command not found in PATH=$PATH"
  fi

  exit $exitCode
fi
//commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --edit "$1"
//post-merge
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn install
//pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn lint-staged

安装commit lint

yarn install -D @commitlint/config-conventional 	@commitlint/cli

这个可以在MonoRepo内安装不在根目录装

在前端的package.json里配置

//./frontend/package.json
{
  "name": "frontend",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "nuxi build",
    "dev": "nuxi dev",
    "start": "node .output/server/index.mjs",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "generate": "nuxi generate",
    "typecheck": "tsc --noEmit --incremental false",
    "lint:strict": "eslint --max-warnings=0 ."
  },
  "lint-staged": {
    "**/*.{js,ts,vue,html}": [
      "eslint --max-warnings=0"
    ]
  }
}

最后在根目录创建commitlint.config.js

里面写上commit lint 规则

//commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'chore',
        'style',
        'refactor',
        'ci',
        'test',
        'revert',
        'perf',
        'build',
        'vercel',
      ],
    ],
  },
}

配置.gitignore

node_modules
*.log
dist
.output
.nuxt
.env
.turbo

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port
.vercel
frontend/.env

配置vercel

创建vercel.json

由于是nuxt3项目 vercel有预设 只需要配置一个字段

{
  "framework": "nuxtjs"
}

然后是登录vercel把项目绑定上去

配置好团队是这样的

配置netlify

创建netlify.toml

[build.environment]
  NODE_VERSION = "16"

[build]
  publish = "dist"
  command = "yarn build"
  functions = "netlify/functions"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

默认配置为如上

然后是登录netlify把项目绑定上去

配好了是这样的

配置workflows

配置一个actions用来在线lint 有错会xx

# lint.yaml
# https://github.com/kentcdodds/kentcdodds.com/blob/main/.github/workflows/deployment.yml
name: Code Check
on:
  push:
    branches:
      - main
  pull_request: {}

jobs:
  lint:
    name: ⬣ ESLint
    runs-on: ubuntu-latest
    steps:
      - name: 🛑 Cancel Previous Runs
        uses: styfle/cancel-workflow-action@0.9.1

      - name: ⬇️ Checkout repo
        uses: actions/checkout@v2

      - name: ⎔ Setup node
        uses: actions/setup-node@v2
        with:
          node-version: 16

      - name: 📥 Download deps
        uses: bahmutov/npm-install@v1

      - name: 🔬 Lint
        run: npx turbo lint:strict

配置一个release bot 用来生成打包文件 区分版本号

# release.yaml
name: release-please
on:
  # workflow_dispatch:
  push:
    branches:
      - main
jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/release-please-action@v3
        with:
          release-type: node
          package-name: release-please-action

配置 issue bot 用来自动用issue创建分支

# issue.yaml
name: "Issue Autolink"
on:
  pull_request:
    types: [opened]

jobs:
  issue-links:
    runs-on: ubuntu-latest
    steps:
      - uses: tkt-actions/add-issue-links@v1.6.0
        with:
          repo-token: "${{ secrets.AUTO_LINK }}"
          branch-prefix: "i"
          resolve: "true"

结尾

到此应该初始化的差不多了

这个从创建 debug 到上线的时间大概花了两天 问题出在 vercel 和 netlify 上

vercel 对 nuxt 的兼容性不好,不如他的亲儿子 next

刚去查了一下 nuxt3在1月23日变成了默认版本

nuxt3用了一个叫nitro的编译框架 https://github.com/unjs/nitro

https://nitro.unjs.io/

构建和部署通用JavaScript服务器

Nitro提供了一个强大的工具链和一个来自unjs生态系统的运行时框架,可以随时随地构建和部署任何JavaScript服务器!

unjs 我感觉是写js魔法的组织 里面全是魔法库

这位是主谋

这个框架会自动检测你当前的环境 给你分配一个preset 比如说你在当前node环境build nuxt的话会给你生成一个 .mjs文件 你运行文件就可以启动一个SSR服务器

你在vercel上运行的话就给你 preset:vercel 生成vercel认的文件格式

netlify同理

问题出在 使用MonoRepo 后 nitro不认我这是在vercel里了 他会生成默认的node的mjs文件 然后构建失败

很烦

参考

https://turbo.build/repo/docs/getting-started/existing-monorepo

https://github.com/antfu/vitesse-nuxt3

https://github.com/antfu?tab=repositories&q=vitesse&type=&language=&sort=

https://nitro.unjs.io/

MonoRepo设置与部署 | 青训营笔记

MonoRepo设置与部署 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 7 天

前言

举个例子,你现在有一个全栈的项目,团队里写了前端、后端、使用文档和一个工具库。

一般情况的话,我们会创建四个仓库,放不同的内容,其中前端如果使用工具库的话,我们会在前端里引入工具库的包(可能是直接,可能是在npm上publish过的)

你希望运行前端项目的时候,同时运行三个命令(例如使用yarn)你需要运行 yarn lintyarn buildyarn serve 你需要敲三个命令,很麻烦。

MonoRepo

什么是MonoRepo

在开发场景中,我们希望各个项目之间能够足够的独立,各自的开发和发布不会产生太多的耦合,现在很多的项目也是出于这种考虑去拆成一个一个独立的子项目,在单独的代码仓库中进行管理,这就是我们常见的单代码仓库的开发模式。

例如我们前言中举的例子,你想要前端项目中使用工具库中的包,你需要到前端,或者使用npm publish后再安装,当你工具库的版本更新的时候,你需要把前端项目里的工具库版本也更新掉。

如果把所有有依赖关系的代码都放到一个仓库中进行统一维护,当一个库变动时,其它的代码能自动的进行依赖升级,那么就能精简开发流程、提高开发效率。这种多包的代码仓库管,就是 MonoRepo。

其实TurboRepo在前端中非常常见,Babel、React、Vue等开源项目都是使用这种方式在管理代码,其中 Babel 官方开源的多包管理工具 Lerna 也被广泛的使用。

这次我介绍的是TurboRepo。

TurboRepo

TurboRepo是一个适用于 JavaScript 和 Typescript TurboRepo的高性能构建工具,使用go、rust语言编写,性能很好。

优势

  • 增量构建:缓存构建内容,并跳过已经计算过的内容,通过增量构建来提高构建速度

  • 内容hash:通过文件内容计算出来的hash来判断文件是否需要进行构建,缓存在云端,登录即可享受

  • 云缓存:可以和团队成员共享CI/CD的云构建缓存,来实现更快的构建

  • 多任务并行执行:在不浪费空闲 CPU 的情况下,以最大并行数量来进行构建

  • 任务管道:通过定义任务之间的关系,让 TurboRepo 优化构建的内容和时间

  • 约定式配置:通过约定来降低配置的复杂度,只需要几行简单的 JSON 就能完成配置(配置turbo.json)

开始使用

对于一个新的项目,可以运行下面的命令来生成全新的代码仓库

npx create-turbo@latest

对于一个已经存在的 monorepo 项目,可以通过下面的步骤来接入 turborepo

安装Turborepo

将 Turborepo 添加到项目最外层的devDependecies

npm install turbo -D
or
yarn add turbo --dev

创建任务管道

package.jsonturbo中,将想要”turbo”的命令添加到管道中 管道定义了 npm 包中 scripts 的依赖关系,并且为这些命令开启了缓存。这些命令的依赖关系和缓存设置会应用到 monorepo 中的各个包中

{
    "turbo": {
        "pipeline": {
            "build": {
                "dependsOn": ["^build"],        
                "outputs": [".next/**"]            
            },
            "test": {
                "dependsOn": ["^build"],
                "outputs": []                            
            },
            "lint": {
                "outputs": []
            },
            "dev": {
                "cache": false            
            } 
        }    
    }
}

上面的示例中,buildtest这两个任务具有依赖性,必须要等他们的依赖项对应的任务完成后才能执行,所以这里用^来表示。 对于每个包中 package.json 中的 script 命令,如果没有配置覆盖项,那么Turborepo将缓存默认输出到 dist/** build/**文件夹中。

pipeline

从上面的 turbo 的配置中可以看出来,管道(pipeline)是一个核心的概念,Turborepo也是通过管道来处理各个任务和他们的依赖关系的。

Turborepo提供了一种声明式的方法来指定各个任务之间的关系,这种方式能够更容易理解各个任务之间的关系,并且Turborepo也能通过这种显式的声明来优化任务的执行并充分调度CPU的多核心性能。

配置pipeline

pipeline中每个键名都可以通过运行turbo run来执行,并且可以使用dependsOn来执行当前管道的依赖项。

上图的执行流程,可以配置成如下的格式

{
    "turbo": {
        "pipeline": {
            "build": {
                "dependsOn": ["^build"],           
            },
            "test": {
                "dependsOn": ["build"],
                "outputs": []                            
            },
            "lint": {
                "outputs": []
            },
            "deploy": {
                "dependsOn": ["build", "test", "lint"]           
            } 
        }    
    }
}

通过dependsOn的配置,可以看出各个命令的执行顺序:

  • 因为A和C依赖于B,所以包的构建存在依赖关系,根据build的dependson配置,会先执行依赖项的build命令,依赖项执行完后才会执行自己的build命令。从上面的瀑布流中也可以看出,B的build先执行,执行完以后A和C的build会并行执行
  • 对于test,只依赖自己的build命令,只要自己的build命令完成了,就立即执行test
  • lint没有任何依赖,在任何时间都可以执行
  • 自己完成build、test、lint后,再执行deploy命令

可以通过下面的命令执行:

npx turbo run test build lint deploy

常规依赖

如果一个任务的执行,只依赖自己包其他的任务,那么可以把依赖的任务放在dependsOn数组里

{
    "turbo": {
        "pipeline": {
            "deploy": {
                "dependsOn": ["build", "test", "lint"]           
            } 
        }    
    }
}

特定依赖

在一些场景下,一个任务可能会依赖某个包的特定的任务,这时候我们需要去手动指定依赖关系。

{
    "turbo": {
        "pipeline": {
            "build": {
                "dependsOn": ["^build"],           
            },
            "test": {
                "dependsOn": ["build"],
                "outputs": []                            
            },
            "lint": {
                "outputs": []
            },
            "deploy": {
                "dependsOn": ["build", "test", "lint"]           
            },
            "frontend#deploy": {
                "dependsOn": ["ui#test", "backend#deploy"]            
            }
        }    
    }
}

Remote cache

当多人开个一个项目的时候,团队的成员可以共享构建的缓存,从而加快项目的构建速度。

当一个成员把某个分支构建的缓存文件推送到远程的git仓库是,另一个成员如果在同一个分支上进行开发,那么Turborepo 可以支持你去选择某个成员的构建缓存,并在运行相关的构建任务时,从远端拉去缓存文件到本地,加快构建的速度

运行 npx turbo link,进行登录后,就可以选择要使用的缓存

显示 full turbo 则证明匹配到了云端的缓存,直接拉下来不再构建一遍

结尾

我相信 Turborepo 的出现在不久的将来一定会成为 Monorepo 工具链中重要的一环,无论是构建缓存功能还是基于 pipeline 的智能任务调度系统,都非常优秀的解决了传统 Monorepo 存在“慢”的问题。

为了更好的性能,大部分人将不再局限于使用 JavaScript 开发 JavaScript 工具,而是更愿意选择其他高门槛语言。

我感觉使用turbo有很好的体验,但他好像对nuxt兼容性不好,只兼容自己的亲儿子next🤣。

参考

https://juejin.cn/post/7051929587852247077
https://juejin.cn/post/7048234698048274469
https://juejin.cn/post/7129267782515949575

https://github.com/vercel/turbo

https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks

HTTP协议 | 青训营笔记

HTTP协议 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 6 天

本堂课重点内容

  1. HTTP 协议的简单介绍
  2. HTTP 协议的报文结构

详细知识点介绍

HTTP 全称超文本传输协议(Hyper Text Transfer Protocol),是一个基于TCP协议的无状态应用层协议。

HTTP 发展历史

  • HTTP/0.9

    • 只有GET类型的请求

    • 只能响应HTML文档

  • HTTP/1.0

    • 增加了 Header

    • 增加了状态码

    • 支持了多种文档类型

  • HTTP/1.1(常见)

    • 连接复用

    • 提供了缓存支持

    • 支持内容协商

  • HTTP/2

    • 二进制协议

    • 支持Header压缩

    • 增加了服务器推送(Server Push)

  • HTTP/3

    • 基于QUIC(udp)协议

常见的 HTTP Methods

请求类型 说明
GET 请求一个指定的资源。使用GET的请求一般用于获取数据
POST 将实体提交到指定资源,通常导致服务器上的状态变化或副作用
PUT 用于请求有效载荷替换目标资源
DELETE 用于删除指定的资源
HEAD 请求一个与GET请求的响应相同的响应,但没有响应体
OPTIONS 预检请求,用于描述目标资源的通信选项

其中最常见的是 GET 请求和 POST 请求,PUT DELETE 常见于各类 RESTful API 中。而 OPTIONS 请求被称为预检请求,倘若我们尝试为前面的几个请求类型增加自定义Header,浏览器会默认向服务器发出一个OPTIONS请求,用于判断服务器能否接收/处理该header。

常见 HTTP 状态码

1xx - 指示信息

2xx - 请求成功

3xx - 重定向操作

4xx - 客户端错误

5xx - 服务端错误

  • 200 正常响应
  • 301 永久重定向
  • 302 临时重定向
  • 401 未授权
  • 403 请求被拒绝
  • 404 请求资源不存在
  • 500 服务器错误
  • 504 网关错误

RESTful API

他是一种API设计风格。

  1. 每一个URL代表一种资源
  2. 客户端和服务端之间,传递这种资源的某种表现层。
  3. 客户端通过HTTP method,对服务端资源进行操作,实现”表现层状态转化”

常见请求头

  • Accept:接受类型
  • Content-Type:客户端发送出去实体内容的类型
  • Cache-Control:指定请求和响应遵循的缓存机制。
  • Cookie:有cookie会自动带上

常用响应头

  • Set-Cookie:设置和页面关联的Cookie
  • Content-Type:服务端

HTTP2

帧:http2最小通信单位,每个帧都包含帧头

消息:与逻辑请求或响应消息对应的完整的一系列帧。

数据流:已建立的连接内的双向字节流,可以承载一条或多条消息。

交错发送,接收方重组织。

HTTP2连接是永久的,而且仅需要每个来源一个连接。

流控制:阻止发送方向接收方发送大量数据的机制。

服务器有主动推送能力,可以提前推送静态资源。

HTTPS

HTTPS = HTTP + SSL

对称加密:加密和解密都是用同一个密钥

非对称加密:加密和解密都需要使用两个不同的密钥:公钥和私钥。

鉴权

  • Session+Cookie
  • JWT

WebSocket

  • 浏览器与服务器进行全双工通讯的网络技术
  • 实时性高

其他

QUIC:HTTP3的新特性

实践练习例子

https://www.w3schools.cn/html/exercise.asp

课后个人总结

本次课程主要介绍了 HTTP 协议的基本知识,以及 HTTP 的发展历史,以及常见的请求方法和状态码,以及 RESTful API。另外还介绍了 HTTPS 和 WebSocket 以及 QUIC 等新特性。学完本节课,我对 HTTP 协议有了更深入的理解,掌握了其中的常见请求头和响应头,以及常用的状态码,并且了解了 HTTP2,HTTPS,WebSocket,以及 QUIC 等新特性。

引用参考

https://developer.mozilla.org/zh-CN/docs/Web/HTTP

https://jwt.io/

TypeScript | 青训营笔记

TypeScript | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 4 天

本堂课重点内容

  1. TypeScript 定义解析
  2. TypeScript 基础语法

详细知识点介绍

  1. 语言特性
  2. 基本数据类型

语言特性

JavaScript 是动态类型的语言,而TypeScript顾名思义是静态类型的语言。与js相比,静态类型的特性为ts带来了许多优势

  • 更强的可读性
  • 更强的可维护性
  • 大型项目中提升稳定性
  • 开发效率

而作为js的超集,ts兼容所有js特性,且支持与js共存,能够渐进式地引入和升级。

基础数据类型

TypeScript能够在变量名称后面添加冒号和期望的类型来为变量赋予具体的类型,例如下面的代码,常见的类型有 string number boolean null undefined

let a: string = "string"
let b: number = 12
let c: boolean = true

如果在之后尝试给变量赋值不同类型的值,将会产生报错

对象类型

ts使用interface关键字来定义新类型

interface ICustomObject {
    name: string
    age: number
    hobby?: string
    readonly id: number
}

const obj: ICustonObject {
    name: "hello world",
    age: 12,
    hobby: 'coding',
    id: 10
}

对于只读属性,可以在类型定义时增加readonly修饰符,在之后倘若修改只读属性,将会报错。在属性名后面紧跟问号表示该属性可选(可能不存在),后续倘若直接读取该属性,将导致报错(需要先判断该属性是否存在)

一般情况下,约定自定义类型以大写字母I开头,例如上面的 ICustomObject

特殊情况

在某些情况下,对象的键名(key)可能并不固定,比如我希望某个对象键名是任意string,键值是布尔型,那么可以向下面这样定义

interface IObj {
    [key: string]: boolean
}

或者使用 type 关键字

type IObj = Record<string, boolean>

函数类型

假设有一个js函数 add

function add(x, y) {
    return x + y
}

为其添加类型声明后

function add(x: number, y: number):number {
    return x + y
}

对于匿名的箭头函数,我们也可以为其添加类型

// js
const add = (x, y) => x + y

// ts
const add: (x: number, y: number) => number = (x, y) => x + y

或许在某些情况下,将类型和函数写在一起会稍显凌乱,那么我们也可以将函数类型单独定义,例如下面这样

interface IAdd {
    (x: number, y: number): number
}

const add: IAdd = (x, y) => x + y

数组类型

数组的类型定义有很多方法,最常见的是像c语言那样的类型定义

type IArray = number[]

const array: IArray = [1, 2, 3, 4, 5]

同样也可以使用ts提供的Array泛型,是一样的效果

type IArray = Array<number>

const array: IArray = [1, 2, 3, 4, 5]

因为数组实际上就是键名特殊的对象,所以也可以用表示对象的方法来表示

interface IArray {
    [key: number]: number
}

const array: IArray = [1, 2, 3, 4, 5]

TS 新增的类型

为了实现一些特殊需求,ts也新增了许多类型方便使用

空类型

function test(): void {
    alert("hello")
}

顾名思义,表示无赋值,例如函数没有返回值

任意类型

type IArray = Array<any>

const array: IArray = [1, "string", true, {a: 1}]

枚举类型

该类型能够通过枚举名查枚举值,同时能够使用枚举值查枚举名

enum EnumTest {
    man = 'male',
    woman = 'female',
}

EnumTest['man'] === 'male'
EnumTest['male'] === 'man'

在不提供枚举名的时候,将默认为由0开始的索引值

enum EnumWeekDay { Mon, Tue, Wed, Thu, Fri }

EnumTest['Mon'] === 0
EnumTest['Tue'] === 1

高级类型

联合类型与交叉类型

联合类型: IA | IB;

交叉类型: IA & IB;

类型保护

可以直接通过.type然后拿到某个变量的类型,可用于后续变量类型的判断,达到类型保护的作用。

补充类型

  • 空类型,表示无赋值
  • 任意类型,是所有类型的子类型
  • 枚举类型:支持枚举值到枚举名的正、反向映射

课后个人总结

TypeScript是一个非常有用的语言,给JavaScript开发带来了严格的检查

引用参考

www.typescriptlang.org/docs/

https://bytedance.feishu.cn/file/boxcnIjrYQpF7pL6nWGtTl5W8Jd

JavaScript | 青训营笔记

JavaScript | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 3 天

课程重点

  1. 编码原则
  2. 组件封装
  3. 过程抽象概念
  4. 高阶函数使用模式
  5. JavaScript 编程范式

详细知识点介绍

编码原则

各司其职,组件封装,过程抽象

我们在编码的时候需要注意,尽量让HTML/CSS/JS各司其职,避免不必要的由JS直接操作样式。

组件封装

组件是指一个包含模板、功能、样式的单元,好的组件具有封装性、正确性、扩展性、复用性。

过程抽象

过程抽象用来处理细节控制的一些方法。 需要函数式编程的思想。

函数式编程,简单理解就是无副作用的输入->处理->输出。

为了能够让只执行一次的需求覆盖不同的事件处理,我们可以将这个需求剥离出来,这个过程我们称为过程抽象

高阶函数(HOF)介绍

  • 以函数作为参数

  • 以函数作为返回值

  • 常用于作为函数装饰器

常见的高阶函数

Once

用于只需要执行一次的函数

function once(fn) {
    return function(...args) {
        if(fn) {
            const ret = fn.apply(this, args);
            fn = null;
            return ret;
        }
    }
}

使用方法:

const print = (content) => {
    console.log(content)
}

const printOnce = once(print)

printOnce("1") // 输出 1
printOnce("2") // 不输出

常用于只需要执行一次的的事件侦听器(也可以使用事件侦听器自带的once option,或者手动在执行后移除侦听器)

节流(Throttle)

如果快速调用函数,那么只有第一次能成功调用函数,之后时间间隔之内的调用会被忽略。常用于滚动条事件/瀑布流无限滚动的场景(限制函数调用频率,保证一段时间内函数只调用一次)

function throttle(fn, time = 500) {
    let timer = null;
    return function (...args) {
        if (timer) return;
        fn.apply(this, args);
        timer = setTimeout(() => {
            timer = null;
        }, time);
    }
}

防抖

当在时间间隔之内快速调用函数时,函数将始终没法执行,直到停止后,函数才会被执行。常用于一些文本输入的场景(比如搜索框停止输入后,展示联想词;文章编辑器中,输入停止后,进行草稿的保存)。

function debounce(fn, time = 500) {
    let timer = null;
    return function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, time);
    }
}

Consumer

从名字也很好理解,当快速点击时,函数并不会立刻被调用,而是先将任务推入列表,然后在一定时间后一个一个消费掉。

function consumer(fn, time = 500) {
    let tasks = [];
    let timer = null;
    return function (...args) {
        tasks.push(args);
        if (timer) return;
        timer = setInterval(() => {
            fn.apply(this, tasks.shift());
            if (tasks.length === 0) {
                clearInterval(timer);
                timer = null;
            }
        }, time)
    }
}

Lterative 可迭代函数

编程范式

命令式与声明式。

命令式趋向于怎么做。声明式趋向于做什么。

JS是既有命令式又有声明式。

命令式

let list =[1,2,3]
let map = []

for(let i =0;i<list.length;i++){
    map.push(list[i]*2);
}

声明式

let list = [1,2,3,4];
const double = x => x*2;
list.map(double);

实践练习例子

实现一个只能执行一次的函数:

// 只执行一次函数
const once = (fn) => {
  let done = false;
  return function (...args) {
    if (!done) {
      done = true;
      return fn.apply(this, args);
    }
  }
}

// 测试
const sayHello = (name) => {
  console.log(`Hello ${name}`);
};

const sayHelloOnce = once(sayHello);

sayHelloOnce('xiaoming'); // Hello xiaoming
sayHelloOnce('xiaohong'); // undefined

课后个人总结

​ 本次课程让我学到了很多有关编程原则、组件封装、过程抽象概念、高阶函数使用模式以及JavaScript编程范式的知识,收获很大。

​ 对于高阶函数,过去只是略有了解,现在能够真正去理解它们是什么,以及它们的用处如何。

引用参考

https://bytedance.feishu.cn/file/boxcnxKucsHPvnJ7PfXyCQF5WCd

深入CSS | 青训营笔记

深入CSS | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 2 天

课程重点

  1. 选择器的特异度
  2. 属性的继承
  3. Layout 方式
  4. 盒子模型
  5. 块级元素和行级元素
  6. flex
  7. grid
  8. float
  9. position

详细知识点介绍:

1. 选择器的特异度(Specificity)

即 CSS 选择器的优先级,当一个元素能够匹配多个样式时,浏览器会根据优先级为元素赋予正确的样式。

一般情况下,ID选择器优先级最高,其次是类选择器/属性选择器/伪类,优先级最低的是类型选择器和伪元素选择器。不同选择器的叠加也会改变优先级。!important 的样式会覆盖其他样式。

VSCode 中,鼠标悬浮在选择器上是,能实时看到选择器的优先级数值

id选择器 大于 类选择器 大于 标签选择器

2. CSS 属性的继承

某些属性会自动继承其父元素的计算值除非显式指定一个值

不同的 CSS 属性有着不同的继承规则。对于一个能够被继承的样式属性,子元素能够从父元素继承相同的属性值,而不需要额外的设置。当然,对于一个默认不继承的样式,也可以通过将其值设置为inherit来强制从父元素继承。

初始值:CSS 中,每个属性都有一个初始值 background-color 的初始值为 transparent
margin-left 的初始值为 0
可以使用 initial 关键字显式重置为初始值
background-color: initial

3. CSS 布局方式

CSS 存在许多种布局方式。总体上分为三类:常规流/文档流、浮动、绝对定位。其中,在正常的文档流中,又可以细分出很多布局方式,例如行级、块级、表格布局、弹性布局(flex)、网格布局(grid)

盒子模型:margin border padding

4. CSS 盒子模型

width: content box 的宽度

height: content box 的高度

padding: 元素内边距,百分比相对于元素宽度

border: 元素的边框样式、粗细和颜色

margin: 元素外边距,百分比相对于元素宽度

当 box-sizing 取值为 border-box 时,模型会发生相应变化。

5. 块级元素

display 属性值为 block 的元素。例如 bodyarticledivmainsectionph1~`h6ul/olli` 等

行级元素

display 属性值为 inline 的元素。

6. flex

布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

2009年,W3C提出了一种新的方案—Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

Flex是Flexible Box的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性.

.box{
  display: flex;
}

任何一个容器都可以指定为Flex布局。

采用Flex布局的元素,称为Flex容器(flex container),简称”容器”。

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。

.box {
  flex-direction: row | row-reverse | column | column-reverse;
}

项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。

  • row(默认值):主轴为水平方向,起点在左端。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

7. grid

网格是一组相交的水平线和垂直线,它定义了网格的列和行。

CSS 提供了一个基于网格的布局系统,带有行和列,可以让我们更轻松地设计网页,而无需使用浮动和定位。

当一个 HTML 元素将 display 属性设置为 grid 或 inline-grid 后,它就变成了一个网格容器,这个元素的所有直系子元素将成为网格元素。

grid: none;
grid: "a" 100px "b" 1fr;
grid: [linename1] "a" 100px [linename2];
grid: "a" 200px "b" min-content;
grid: "a" minmax(100px, max-content) "b" 20%;
grid: 100px / 200px;
grid: minmax(400px, min-content) / repeat(auto-fill, 50px);

grid: 200px / auto-flow;
grid: 30% / auto-flow dense;
grid: repeat(3, [line1 line2 line3] 200px) / auto-flow 300px;
grid: [line1] minmax(20em, max-content) / auto-flow dense 40%;

grid: auto-flow / 200px;
grid: auto-flow dense / 30%;
grid: auto-flow 300px / repeat(3, [line1 line2 line3] 200px);
grid: auto-flow dense 40% / [line1] minmax(20em, max-content);

grid: inherit;
grid: initial;
grid: unset;

我们通过 grid-template-columnsgrid-template-rows 属性来定义网格中的行和列。

这些属性定义了网格的轨道,一个网格轨道就是网格中任意两条线之间的空间。

在下图中你可以看到一个绿色框的轨道——网格的第一个行轨道。第二行有三个白色框轨道。

轨道可以使用任何长度单位进行定义。

网格引入了 fr 单位来帮助我们创建灵活的网格轨道。一个 fr 单位代表网格容器中可用空间的一等份。

以下实例定义了一个网格定义将创建三个相等宽度的轨道,这些轨道会随着可用空间增长和收缩。

一个网格单元是在一个网格元素中最小的单位, 从概念上来讲其实它和表格的一个单元格很像。现在再看回我们前面的一个例子, 一旦一个网格元素被定义在一个父级元素当中,那么他的子级元素将会排列在每个事先定义好的网格单元中。

列与列,行与行之间的交接处就是网格线。

Grid 会为我们创建编号的网格线来让我们来定位每一个网格元素。

#container {
  display: grid;
  grid: repeat(2, 60px) / auto-flow 80px;
}

#container > div {
  background-color: #8ca0ff;
  width: 50px;
  height: 50px;
}

网格线的编号顺序取决于文章的书写模式。在从左至右书写的语言中,编号为 1 的网格线位于最左边。在从右至左书写的语言中,编号为 1 的网格线位于最右边。

接下来我使用了 grid-column-start, grid-column-end, grid-row-start 和 grid-row-end 属性来演示如何使用网格线。

以下实例我们设置一个网格元素的网格线从第一列开始,第三列结束:

.item1 {
  grid-column-start: 1;
  grid-column-end: 3;
}

https://www.runoob.com/try/gridgarden/index.html

菜鸟教程的grid布局小游戏

实践练习例子:

特异度相关例子

下面的文字是什么颜色?

<div class="wrapper1">
  <div class="text1">文本1</div>
</div>
<style>
  .wrapper1 .text1 {
    color: red;
  }
  
  .text1 {
    color: black;
  }
</style>

正确答案是红色

虽然黑色的属性声明在后面,理应覆盖掉前面的红色,但是前面是两个类选择器的叠加,优先级是 0, 2, 0 ,后者只有一个类选择器,优先级是 0, 1, 0,所以最终浏览器应用了红色。

课后个人总结:

本节课中,我学习了CSS中特异度、属性继承、布局方式、盒子模型、块级元素和行级元素、flex、grid等概念,掌握了相关的知识和应用。 特异度是CSS中最重要的概念之一,让我们明白当一个元素能够匹配多个样式时,浏览器会根据优先级为元素赋予正确的样式。

属性的继承也是重要的概念,它会依赖于不同的CSS属性,有了它,我们可以让HTML元素的样式可以从父元素继承。

我们还学习了CSS中的布局方式,包括常规流/文档流、浮动、绝对定位等,以及flex、grid等网格布局。

熟练掌握这些知识,我们可以更好的制作网页,满足各种布局的需求。

引用参考:

课程ppt

https://developer.mozilla.org/zh-CN/docs/Web/CSS/grid

https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox

前端与 HTML | 青训营笔记

前端与 HTML | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 1 天

本堂课重点内容:

  1. 前端的介绍
  2. HTML 标签的简单介绍
  3. HTML 语义化

详细知识点介绍:

  1. 前端技术栈
  2. 前端的边界、开发环境(ide、浏览器)
  3. DOM 树的简单介绍
  4. HTML 语法简单介绍
  5. 各类常用标签(h1-h5、p、form、a)
  6. 常见的语义化标签(main/footer/section)
  7. 语义化的优势

实践练习例子:

1. 标题元素

使用 h1~`h6` 标签将标题文本包裹,标题会根据级别拥有默认的字重、字号和外边距。

<h1>一级标题</h1>
<h2>二级标题</h2>
<h3>三级标题</h3>
<h4>四级标题</h4>
<h5>五级标题</h5>
<h6>六级标题</h6>

2. 图片元素

使用 src 属性来确定图片链接,alt 属性用于表示图片无法显示时的替代文本

<img src="https://example.com/image" />
<img src="https://example.com" alt="显示不出来的图片" />

3. 单选框

radio 类型的 input 元素,相同 name 的单选框会归为同一组(只能选其中一个) 使用 label 标签来标识单选框的内容

<input type="radio" id="el1" name="group1" />
<label for="el1">第一组-选项1</label>
<input type="radio" id="el2" name="group1" />
<label for="el2">第一组-选项1</label>
<br>
<input type="radio" id="el3" name="group2" />
<label for="el3">第二组-选项1</label>
<input type="radio" id="el4" name="group2" />
<label for="el4">第二组-选项2</label>

4. 超链接

使用 a 标签包裹文本,href 属性是目标链接,默认在当前页面打开,为了在新标签页打开,可以将 target 属性设置为 _blank

<a href="https://example.com">在当前页面打开</a>
<a href="https://example.com" target="_blank">在新页面打开</a>

5. 有序列表

最外层使用 ol标签包裹,内部包含多个 li 标签

<ol>
    <li>第一</li>
    <li>第二</li>
    <li><em>第三</em></li>
</ol>

6. 无序列表

最外层使用 ul标签包裹,内部包含多个 li标签

<ul>
    <li>第一</li>
    <li>第二</li>
    <li><em>第三</em></li>
</ul>

课后个人总结:

本节课主要介绍了前端的技术栈,以及 HTML 标签的简单介绍和 HTML 语义化,通过本节课的学习,我对 HTML 标签有了一定的了解,掌握了如何使用标签来表达页面元素,并且学习了如何使用语义化标签来提高前端代码的可读性,以及提升代码的可维护性。

HTTP3初体验 | 青训营笔记

HTTP3初体验 | 青训营笔记

HTTP/3试水

I

小看一眼,发现全是编译啥的。应该会踩很多坑的,这次用国外没东西的小1h512M服务器试水一下。
目前cloudflare给出了一键开启http3。很好用,我在netlify托管的网页一键开开了。
1
2

II

现在要编译安装一个nginx,用的是https://github.com/cloudflare/quiche方案。
https://github.com/cloudflare/quiche/tree/master/nginx处理nginx的教程在这里,一步步跟上

  curl -O https://nginx.org/download/nginx-1.16.1.tar.gz
  tar xzvf nginx-1.16.1.tar.gz
  git clone --recursive https://github.com/cloudflare/quiche
  cd nginx-1.16.1
  patch -p01 < ../quiche/nginx/nginx-1.16.patch

在之前要apt upgrade一下避免有装不上的东西
3
外网服务器下的就是快😎
要编译了发现cmake没有 装个cmake先
apt install cmake

gcc g++也没有 装个

接下来

  ./configure                                 \
       --prefix=$PWD                           \
       --build="quiche-$(git --git-dir=../quiche/.git rev-parse --short HEAD)" \
       --with-http_ssl_module                  \
       --with-http_v2_module                   \
       --with-http_v3_module                   \
       --with-openssl=../quiche/quiche/deps/boringssl \
       --with-quiche=../quiche
  make

nginx configure 时候报错,
the HTTP rewrite module requires the PCRE library.
搜了 装这个 apt install libpcre3 libpcre3-dev

the HTTP gzip module requires the zlib library
搜了 装这个 apt install zlib1g-dev

又error 没有cargo 装个
apt install cargo

make好了

nginx -V

装是装好了 好像要一个https证书啊
acme签一个
https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E

events {
    worker_connections  1024;
}

http {
    server {
        # Enable QUIC and HTTP/3.
        listen 443 quic reuseport;

        # Enable HTTP/2 (optional).
        listen 443 ssl http2;

        ssl_certificate      cert.crt;
        ssl_certificate_key  cert.key;

        # Enable all TLS versions (TLSv1.3 is required for QUIC).
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

        # Add Alt-Svc header to negotiate HTTP/3.
        add_header alt-svc 'h3=":443"; ma=86400';
    }
}

签证书签了半天 签好了 放在

        ssl_certificate      cert.crt;
        ssl_certificate_key  cert.key;

里面
配置好nginx 注意网站不要在root下
在root下nginx访问不到直接403了
建一个/www/wwwroot/domin文件夹放东西

III

最后配置结果是这样滴

第一个

https://geekflare.com/tools/test/yn22t29e04y7v0u5jl61mcark2dwxmbn
6
这个网站会检测什么h3-xxx的版本协议 (看不懂,看懂了在写一篇
你没有这个版本号的协议这个网站就检测不出来好像 在这里改就行
add_header alt-svc 'h3=":443"; ma=86400';
我加了版本号114514🤓
add_header alt-svc h3=":443"; ma=86400; h3-114514=":443"; ma=86400
上面这个好像就检测这个alt-svc这个头

第二个

https://http3check.net/?host=http3.typescriptactions.xyz
7
这个就很正常了 不加版本直接检测h3的

第三个

然后是wappalyzer插件
8

第四个

然后是浏览器devtools的协议检查
9

IV

总结一下
2023-1-11 21:57:32 开始的
2023-1-12 00:53:12 写完这篇
🤥感觉没啥可总结的 国外服务器也测不了http3优势啥的
下班!睡觉!😝

java_crud环境配置教程

本文会从安装开始,到最后写出一个带命令行交互窗口的CRUD的小项目.

安装

安装JDK

安装地址

建议下载java17以防止有依赖问题

点击这个下载.msi安装包来安装

这里建议不要更改默认位置

安装完成🥳

设置环境变量

Windows开始菜单搜索环境变量

在系统环境变量下面点击新建

变量名设置为 JAVA_HOME

变量值设置为 C:\Program Files\Java\jdk-17.0.4.1 如果你有安装到其他目录请填其他目录

新建环境变量 CLASS_PATH

变量值为 .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

如下图所示

注意 变量值比较奇怪 可能理解不能 但是要照上填写

在系统变量里找到path 双击

点击新建 值填写

%JAVA_HOME%\bin

点击确定

接下来按 Windows键加R键 输入cmd点确定

在命令行里输入java --version

如有正常不报错则配置成功🥳

安装MySQL

MySQL :: Download MySQL Installer (Archived Versions)

双击打开

选default

直接next

yes

Execute

一路next

设一个密码

然后一路next

点check

点next

最后会跳出这么一个奇奇怪怪的 MySQL JS 的命令行 (说实话我没懂为什么有这玩意

(不过你可以在上面玩玩 JS 的特性 :)

和命令行同时弹出来的是workbench

MySQL已安装完成🥳

安装 JetBrain IntelliJ IDEA

下载地址

直接下载咯 0配置的

创建项目

新建项目

创建配置如图所示

点进 src/main/java/org/example/main运行一下试试

配置Maven

点击编辑配置

新建一个Maven配置

配置如图 保存应用

点击运行

build成功后会生成一个snapshot的jar

接下来是安装maven依赖项

打开pom.xml 右键点生成

搜索 mysql-connector 添加这个8版本的依赖

生成了如图的代码则成功

在与 dependencies 同级的标签下面 添加如下代码 配置maven依赖自动安装

 <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>org.example.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

点击右上角 重新加载maven变更

加载数据库&&创建数据表

在右边的侧边栏中找到数据库 添加一个MySQL

输入你刚设置的账号密码连接

弹出一个console

在里面输入

CREATE DATABASE `java_crud_demo` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

绿色框内是idea识别出来的sql语句 点击左上绿色箭头执行

右侧数据库栏里会出现你刚创建的数据库 但是里面没有表

在console里输入

create table if not exists `java_crud_demo`.`article_table`
(
    id     int unsigned auto_increment
        primary key,
    title  varchar(100) not null,
    author varchar(40)  not null,
    time   datetime     null
);

ctrl + a 全选中 然后点执行

生成了表article_table 里面没有数据

简单的填充数据 可以 console输入

INSERT INTO `java_crud_demo`.`article_table` (`title`, `author`, `time`)
VALUES ('标题', '作者', NOW());

插入完了记得点这个刷新

也可以直接在table预览界面

添加行 输入完毕后 点提交

数据库crud模板

新建一个crud.java

package org.example;

import java.sql.*;

public class Crud {

    // MySQL 8.0 以下版本 - JDBC 驱动名及数据库 URL
    //static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    //static final String DB_URL = "jdbc:mysql://localhost:3306/java_crud_demo";

    // MySQL 8.0 以上版本 - JDBC 驱动名及数据库 URL
    static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/java_crud_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";


    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "123456";

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try{
            // 注册 JDBC 驱动
            Class.forName(JDBC_DRIVER);

            // 打开链接
            System.out.println("连接数据库...");
            conn = DriverManager.getConnection(DB_URL,USER,PASS);

            // 执行查询
            System.out.println(" 实例化Statement对象...");
            stmt = conn.createStatement();
            String sql;
            sql = "SELECT id, title, author, time FROM java_crud_demo";
            ResultSet rs = stmt.executeQuery(sql);

            // 展开结果集数据库
            while(rs.next()){
                // 通过字段检索
                int id  = rs.getInt("id");
                String title = rs.getString("title");
                String author = rs.getString("author");
                String time = rs.getString("time");

                // 输出数据
                System.out.println("ID: " + id);
                System.out.println("标题:" + title);
                System.out.println("作者:" + author);
                System.out.println("时间:" + time);
            }
            // 完成后关闭
            rs.close();
            stmt.close();
            conn.close();
        }catch(SQLException se){
            // 处理 JDBC 错误
            se.printStackTrace();
        }catch(Exception e){
            // 处理 Class.forName 错误
            e.printStackTrace();
        }finally{
            // 关闭资源
            try{
                if(stmt!=null) stmt.close();
            }catch(SQLException se2){
            }// 什么都不做
            try{
                if(conn!=null) conn.close();
            }catch(SQLException se){
                se.printStackTrace();
            }
        }
        System.out.println("Goodbye!");
    }
}

接下来愉快写你的代码主逻辑咯🥳

双击打开jar

package org.example;

import java.io.File;

public class Main {
    public static void main(String[] args) {
        if (args.length == 0) {
            try {
                String path = System.getProperty("java.class.path");
                int lastIndex = path.lastIndexOf(File.separator) + 1;
                String file_name = path.substring(lastIndex);
                ProcessBuilder pb = new ProcessBuilder();
                pb.directory(new File("."));
                System.out.printf("%s %s %s %s %s %s %s", "cmd", "/c", "start", "java", "-jar", file_name, "eject");
                        pb.command("cmd", "/c", "start", "java", "-jar", file_name, "eject");
                pb.start();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.exit(0);
            }
        }
        else {
            //主函数
            System.out.println("Hello World!");
        }
    }
    
}

在main开头加上这样一段 他会帮你输入命令来打开一个弹出的新窗口啦🥳

结束

Maven 构建之后就得到了需要的jar包了🥳

抢红包获奖感言🤗

抢红包获奖感言🤗

呃呃,呃呃呃呃呃呃

首先恭喜TS的小号TypeScript Actions Bot在8月8号晚上的抢红包大赛中荣获金奖。

一共抢到了113.87CNY

13.87CNY吐出去了(就给你吐个零头略略略😋)

首先自证一下没有开挂


{
    "message": {
        "id": "08a0a3dd95ddf1ca1f10c7cea8041a123134343131353231383738373537343636362081e2800308bfef5cba2fdcb8f0138f78d0140f38d0148d4cdc39706",
        "channel_id": "9054023",
        "guild_id": "17780811658383776",
        "content": "[QQ红包]请使用新版手机QQ查收红包。",
        "timestamp": "2022-08-08T18:35:00+08:00",
        "author": {
            "id": "14411521877574666",
            "username": "Ljcbaby",
            "bot": false
        },
        "member": {
            "roles": [
                "2"
            ],
            "joined_at": "2022-07-21T14:09:37+08:00"
        }
    }
}

以上TSA能获取到的关于该条红包消息的全部内容
呃呃 显然开不了
我不知道野频道机器人怎么个返回格式 (TSA是官方的PythonSDK)
抢红包全靠手速捏 看看你的单身19年手速捏(没单身的别叫😡)

用处

此次资金会用于TS大号的灾后重建。
呃呃 简述一下TS怎么死的
TS在八月二号晚和大伙一起期待某个人下飞机,而且给大伙直播,但是直播中出现了呃呃呃呃呃呃呃呃呃呃呃(政)之类的内容,然后就如下图所示↓

😭TS大号有2个780天左右的火,2个630天左右的火,一个330天的。
呃呃呃全掉没了,tx开通svip两个月可以续活一个火,两个月是nm40块钱👿👿👿👿👿👿👿👿我真的气死了👿👿👿👿

其他

呃最后非常感谢卢佬和烧鸡哥😍富哥😍😍😍😍
卢佬说工资到了就是硬气直接发了3个88我哭死😭

呃呃呃刻晴还没穿呢 再拖会儿再拖会儿🤗