Write the Code. Change the World.

分类目录
6月 15

运行起 dify 后,在控制台里可以看到登录调用的接口文件定义在这里:api\controllers\console\auth\login.py 现在想扩展新的接口,可以在当前目录下新建一个 register.py 文件,加入以下代码。

import os
import hashlib
import functools
import time
import logging
import secrets
import base64

from flask import make_response, request
from flask_restx import Resource
from pydantic import BaseModel, Field, field_validator, ValidationError
from sqlalchemy import select, update
from typing import Literal, Optional

from controllers.common.fields import SimpleResultOptionalDataResponse
from controllers.common.schema import (
    register_response_schema_models,
    register_schema_models,
)
from controllers.console import console_ns
from libs.helper import EmailStr
from libs.helper import timezone as validate_timezone_string
from libs.password import hash_password
from constants.languages import get_valid_language
from models import Account, Tenant, TenantAccountJoin
from extensions.ext_database import db
from services.errors.account import AccountRegisterError
from controllers.console.wraps import (
    decrypt_password_field,
    setup_required,
)

from sqlalchemy import func
from models import ApiToken

logger = logging.getLogger(__name__)

# 签名校验装饰器
def private_api_auth_required(f):
    @functools.wraps(f)
    def decorated(*args, **kwargs):
        private_secret = os.environ.get("CONSOLE_PRIVATE_API_SECRET")
        if not private_secret or len(private_secret) != 12:
            return {"result": "fail", "message": "Private API service unavailable"}, 503

        req_time = request.headers.get("X-API-TIME", "")
        req_sign = request.headers.get("X-API-SIGN", "")

        if not req_time or not req_sign:
            return {"result": "fail", "message": "Missing required headers"}, 400

        try:
            timestamp = int(req_time)
            if timestamp > 9999999999:
                timestamp = timestamp // 1000
        except (ValueError, TypeError):
            return {"result": "fail", "message": "Invalid timestamp"}, 400

        if abs(int(time.time()) - timestamp) > 300:
            return {"result": "fail", "message": "Request expired"}, 401

        calc_sign = (
            hashlib.md5(f"{private_secret}{req_time}".encode()).hexdigest().lower()
        )
        if calc_sign != req_sign.lower():
            return {"result": "fail", "message": "Invalid signature"}, 401

        return f(*args, **kwargs)

    return decorated

# 请求模型:字段改为 name,timezone 默认 Asia/Shanghai
class RegisterPayload(BaseModel):
    email: EmailStr = Field(..., description="邮箱")
    name: str = Field(..., min_length=1, max_length=50, description="用户名")
    password: str = Field(..., description="明文密码")
    language: str | None = Field(default=None, description="界面语言")
    timezone: str = Field(
        default="Asia/Shanghai", description="时区,默认 Asia/Shanghai"
    )

    @field_validator("timezone")
    @classmethod
    def validate_timezone(cls, value):
        return validate_timezone_string(value) if value else "Asia/Shanghai"

# 获取用户详情
class UserInfoPayload(BaseModel):
    account_id: str = Field(..., description="用户ID")

class GenerateApiTokenPayload(BaseModel):
    account_id: str = Field(..., description="用户ID")
    tenant_id: str = Field(..., description="租户ID")
    # 限定只能是 dataset / app
    type: Literal["dataset", "app"] = Field(
        ..., description="令牌类型,仅支持 dataset、app"
    )
    # 非必填,默认 None
    app_id: Optional[str] = Field(None, description="应用ID,type 为 app 时必填")

    @field_validator("app_id")
    def check_app_id_required(cls, v, values):
        # values.data 获取模型已解析的其他字段
        token_type = values.data.get("type")
        if token_type == "app" and not v:
            raise ValueError("当令牌类型为 app 时,app_id 不能为空")
        if token_type == "dataset" and v is not None:
            raise ValueError("当令牌类型为 dataset 时,无需传入 app_id")
        return v

register_schema_models(
    console_ns,
    RegisterPayload,
    UserInfoPayload,
    GenerateApiTokenPayload,
)

register_response_schema_models(console_ns, SimpleResultOptionalDataResponse)

def get_first_tenant():
    stmt = select(Tenant).order_by(Tenant.created_at)
    return db.session.scalar(stmt)

# 对齐 Dify 原生密码哈希逻辑
def create_password_hash(raw_pwd: str) -> tuple[str, str]:
    salt = secrets.token_bytes(16)
    hashed = hash_password(raw_pwd, salt)
    return base64.b64encode(hashed).decode("utf-8"), base64.b64encode(salt).decode(
        "utf-8"
    )

@console_ns.route("/register")
class RegisterApi(Resource):
    @setup_required
    @private_api_auth_required
    @console_ns.expect(console_ns.models[RegisterPayload.__name__])
    @console_ns.response(
        200, "Success", console_ns.models[SimpleResultOptionalDataResponse.__name__]
    )
    @decrypt_password_field
    def post(self):
        # 单独捕获参数校验异常
        try:
            args = RegisterPayload.model_validate(console_ns.payload)
        except ValidationError as ve:
            err_info = ve.errors()[0]
            field = ".".join(map(str, err_info["loc"]))
            msg = f"Parameter error: {field} - {err_info['msg']}"
            return {"result": "fail", "message": msg}, 403

        try:
            email = args.email.strip().lower()
            name = args.name.strip()
            raw_pwd = args.password.strip()
            lang = get_valid_language(args.language)
            tz = args.timezone.strip()

            # 邮箱去重
            exist_stmt = select(Account).where(Account.email == email)
            if db.session.scalar(exist_stmt):
                raise AccountRegisterError("Email already exists")

            # 生成密码
            pwd_hash, pwd_salt = create_password_hash(raw_pwd)

            # 新建账号(字段完全对齐数据库)
            new_account = Account(
                email=email,
                name=name,
                password=pwd_hash,
                password_salt=pwd_salt,
                interface_language=lang,
                timezone=tz,
            )
            db.session.add(new_account)
            db.session.flush()

            # 租户处理
            tenant = get_first_tenant()
            if not tenant:
                tenant = Tenant(name=f"{name}'s Workspace")
                db.session.add(tenant)
                db.session.flush()

            is_first_tenant = get_first_tenant() is None
            join_rel = TenantAccountJoin(
                account_id=new_account.id,
                tenant_id=tenant.id,
                role="owner" if is_first_tenant else "admin",
                current=True,
            )
            db.session.add(join_rel)

            # 更新当前租户标记
            update_all = (
                update(TenantAccountJoin)
                .where(TenantAccountJoin.account_id == new_account.id)
                .values(current=False)
            )
            db.session.execute(update_all)

            update_curr = (
                update(TenantAccountJoin)
                .where(
                    TenantAccountJoin.account_id == new_account.id,
                    TenantAccountJoin.tenant_id == tenant.id,
                )
                .values(current=True)
            )
            db.session.execute(update_curr)

            # 统一提交,事务原子化
            db.session.commit()

            return make_response(
                {
                    "result": "success",
                    "data": {
                        "account_id": str(new_account.id),
                        "email": new_account.email,
                        "name": new_account.name,
                        "tenant_id": str(tenant.id),
                        "tenant_name": tenant.name,
                    },
                }
            )

        except Exception as e:
            db.session.rollback()
            logger.exception("Register failed, rollback transaction")
            if isinstance(e, AccountRegisterError):
                return {"result": "fail", "message": str(e)}, 400
            return {"result": "fail", "message": "Register failed"}, 500

# ===================== 获取用户基本信息 =====================
@console_ns.route("/user/info")
class UserInfoApi(Resource):
    @setup_required
    @private_api_auth_required
    def get(self):
        try:
            # 从 URL 查询参数取值,而非 payload
            account_id = request.args.get("account_id", "").strip()
            if not account_id:
                return {"result": "fail", "message": "account_id is required"}, 403
            args = UserInfoPayload(account_id=account_id)
        except ValidationError as ve:
            err_info = ve.errors()[0]
            field = ".".join(map(str, err_info["loc"]))
            msg = f"Parameter error: {field} - {err_info['msg']}"
            return {"result": "fail", "message": msg}, 403

        try:
            account_id = args.account_id.strip()
            # 1. 查询单条用户基础信息
            user_stmt = select(
                Account.id,
                Account.email,
                Account.name,
                Account.status,
                Account.timezone,
                Account.interface_language,
                Account.created_at,
            ).where(Account.id == account_id)

            user_row = db.session.execute(user_stmt).first()
            if not user_row:
                return {"result": "success", "data": None, "message": "用户不存在"}

            # 2. 查询该用户关联的租户信息
            tenant_stmt = (
                select(
                    TenantAccountJoin.account_id,
                    TenantAccountJoin.tenant_id,
                    TenantAccountJoin.role,
                    TenantAccountJoin.current,
                    Tenant.name.label("tenant_name"),
                    Tenant.created_at.label("tenant_created_at"),
                )
                .join(Tenant, TenantAccountJoin.tenant_id == Tenant.id)
                .where(TenantAccountJoin.account_id == account_id)
            )
            tenant_row = db.session.execute(tenant_stmt).first()

            # 查询用户的 api_tokens
            api_token_stmt = select(
                ApiToken.id,
                ApiToken.tenant_id,
                ApiToken.account_id,
                ApiToken.type,
                ApiToken.token,
                ApiToken.app_id,
                ApiToken.last_used_at,
            ).where(ApiToken.account_id == account_id)
            api_token_rows = db.session.execute(api_token_stmt).all()

            api_token_list = []
            for row in api_token_rows:
                api_token_list.append(
                    {
                        "id": str(row.id),
                        "tenant_id": str(row.tenant_id),
                        "account_id": str(row.account_id),
                        "type": row.type,
                        "token": row.token,
                        "app_id": row.app_id,
                        "last_used_at": (
                            row.last_used_at.isoformat() if row.last_used_at else None
                        ),
                    }
                )

            # 3. 组装最终返回数据
            user_data = {
                "user_id": str(user_row.id),
                "email": user_row.email,
                "name": user_row.name,
                "timezone": user_row.timezone,
                "interface_language": user_row.interface_language,
                "created_at": (
                    user_row.created_at.isoformat() if user_row.created_at else None
                ),
                "tenant": tenant_row,
                "api_token_list": api_token_list,
            }
            return {"result": "success", "data": user_data}

        except Exception as e:
            logger.exception("Get user list failed")
            return {"result": "fail", "message": "Query failed"}, 500

# ===================== 创建 api key =====================
@console_ns.route("/generate/api_key")
class GenerateApiTokenApi(Resource):
    @setup_required
    @private_api_auth_required
    @console_ns.expect(console_ns.models[GenerateApiTokenPayload.__name__])
    def post(self):
        try:
            args = GenerateApiTokenPayload.model_validate(console_ns.payload)
        except ValidationError as ve:
            err_info = ve.errors()[0]
            field = ".".join(map(str, err_info["loc"]))
            msg = f"Parameter error: {field} - {err_info['msg']}"
            return {"result": "fail", "message": msg}, 403

        try:
            account_id = args.account_id.strip()
            tenant_id = args.tenant_id.strip()
            type = args.type
            app_id = args.app_id

            # 一个租户最多可以创建 10 个 api tokens(todo 要破掉)
            current_key_count = db.session.scalar(
                select(func.count(ApiToken.id)).where(ApiToken.tenant_id == tenant_id)
            )

            if current_key_count >= 10:
                logger.warning(
                    f"User {account_id} has reached maximum API key limit (10)"
                )
                return {"result": "fail", "message": "API key limit reached"}, 403

            # 2. 生成API Key(和Dify原生生成规则完全一致)
            token_prefix = "sk-"  # Dify原生用户API Key前缀
            key = ApiToken.generate_api_key(token_prefix, 24)

            # 3. 创建并保存API Token
            api_token = ApiToken()
            api_token.tenant_id = tenant_id
            api_token.account_id = account_id
            api_token.type = type
            api_token.token = key

            if app_id:
                api_token.app_id = app_id
            db.session.add(api_token)
            db.session.commit()

            return {"result": "success", "data": {"token": key}}

        except Exception as e:
            logger.exception("Get user list failed")
            return {"result": "fail", "message": "Query failed"}, 500

# ===================== 用户列表接口(含租户 + API Token) =====================
@console_ns.route("/user/list")
class UserListApi(Resource):
    @setup_required
    @private_api_auth_required
    def get(self):
        try:
            # 解析分页参数
            page = request.args.get("page", 1, type=int)
            page_size = request.args.get("page_size", 20, type=int)
            # 限制最大条数,防护大数据查询
            page_size = min(page_size, 100)
            offset = (page - 1) * page_size

            # 1. 查询用户总数
            total_stmt = select(func.count(Account.id))
            total = db.session.scalar(total_stmt) or 0

            # 2. 分页查询用户基础信息
            user_stmt = (
                select(
                    Account.id,
                    Account.email,
                    Account.name,
                    Account.timezone,
                    Account.interface_language,
                    Account.created_at,
                )
                .order_by(Account.created_at.desc())
                .offset(offset)
                .limit(page_size)
            )
            user_rows = db.session.execute(user_stmt).all()
            user_id_list = [row.id for row in user_rows]

            # 无用户直接返回空列表
            if not user_id_list:
                return {
                    "result": "success",
                    "data": {
                        "total": total,
                        "page": page,
                        "page_size": page_size,
                        "list": [],
                    },
                }

            # 3. 批量查询 用户-租户 关联信息(修复变量名笔误)
            tenant_stmt = (
                select(
                    TenantAccountJoin.account_id,
                    TenantAccountJoin.tenant_id,
                    TenantAccountJoin.role,
                    TenantAccountJoin.current,
                    Tenant.name.label("tenant_name"),
                    Tenant.created_at.label("tenant_created_at"),
                )
                .join(Tenant, TenantAccountJoin.tenant_id == Tenant.id)
                .where(TenantAccountJoin.account_id.in_(user_id_list))
            )
            # 此处原代码写错:tenant_rows → tenant_stmt
            tenant_rows = db.session.execute(tenant_stmt).all()

            # 按用户ID分组租户数据
            tenant_map = {}
            for row in tenant_rows:
                aid = row.account_id
                if aid not in tenant_map:
                    tenant_map[aid] = []
                tenant_map[aid].append(
                    {
                        "tenant_id": str(row.tenant_id),
                        "tenant_name": row.tenant_name,
                        "role": row.role,
                        "is_current": row.current,
                        "tenant_created_at": (
                            row.tenant_created_at.isoformat()
                            if row.tenant_created_at
                            else None
                        ),
                    }
                )

            # 5. 组装最终返回数据
            user_list = []
            for user in user_rows:
                user_id = user.id
                user_list.append(
                    {
                        "user_id": str(user_id),
                        "email": user.email,
                        "name": user.name,
                        "timezone": user.timezone,
                        "interface_language": user.interface_language,
                        "created_at": (
                            user.created_at.isoformat() if user.created_at else None
                        ),
                        "tenant_list": tenant_map.get(user_id, []),
                        # "api_token_list": token_map.get(user_id, []),
                    }
                )

            return {
                "result": "success",
                "data": {
                    "total": total,
                    "page": page,
                    "page_size": page_size,
                    "list": user_list,
                },
            }

        except Exception as e:
            logger.exception("Get user list failed")
            return {"result": "fail", "message": "Query failed"}, 500

并且将该文件注册进去。修改 api/controllers/console/init.py, 添加 "register" 就好。

然后看看最近这几个功能修改涉及到的文件。

最后,如果有需要可以去打一个镜像。

docker compose build api
5月 26

如果遇到安装插件失败。比如:

dfiy 安装插件失败,报错 failed to launch plugin: failed to install dependencies: failed to install dependencies: signal: killed, output: DEBUG Found workspace root: `/app/storage/cwd/langgenius/deepseek-0.0.15@725407927b04e236212083d20e92830d60fa944e42cd357ef6902c160414f6f1`
DEBUG Adding root workspace member: `/app/storage/cwd/langgenius/deepseek-0.0.15@725407927b04e236212083d20e92830d60fa944e42cd357ef6902c160414f6f1`
DEBUG Skipping `pyproject.toml` in `/app/storage/cwd/langgenius/deepseek-0.0.15@725407927b04e236212083d20e92830d6...multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
DEBUG Sending fresh GET request for: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Downloading pydantic-core (2.0MiB)
Downloading gevent (2.0MiB)
Downloading tiktoken (1.1MiB)
init process exited due to no activity for 120 seconds
failed to init environment

可以将超时时间设置长一点,还有可以配置 python 镜像。

# .env 中,将默认的 120 秒改为 300秒
PLUGIN_PYTHON_ENV_INIT_TIMEOUT=600
PLUGIN_DAEMON_TIMEOUT=600.0

还可以设置镜像

# PIP_MIRROR_URL=https://mirrors.aliyun.com
PIP_MIRROR_URL=https://pypi.tuna.tsinghua.edu.cn/simple
9月 28

markdown 转换成 pdf

### 安装 
pnpm install -g md-to-pdf
npx puppeteer browsers install chrome

### 转换
md-to-pdf ./hello.md
7月 09

项目打包,如果访问位置非对应根目录,是需要配置的。打包输出目录也是需要配置的(默认是 dist目录),chunk 也需要配置。

pnpm lint 执行检查的时候,应该对打包的文件忽略检查。这个也需要配置的。现在一个一个的来。

配置打包

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  base: '/admin/',
  build: {
    outDir: 'admin',
    emptyOutDir: true,
    chunkSizeWarningLimit: 3000,
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js', // 入口文件名
        chunkFileNames: 'assets/[name].[hash].js', // chunk 文件名
        assetFileNames: 'assets/[name].[hash].[ext]', // 静态资源文件名
      },
    },
  },
  plugins: [vue(), vueDevTools()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
})

这里输出目录是 admin,基础路径也是 admin。 配置的警官文件大小,设置了打包输出的文件名等。

执行打包 pnpm build,会将项目打包到 admin 目录下。

但是,当执行 pnpm lint 检查的时候,会对 admin 目录下的代码进行检查。这个是不需要检查的。所以需要配置 eslint.confg.ts 来控制该行为。

配置 eslint.confg.ts 来忽略检查

import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}'],
  },

  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),

  pluginVue.configs['flat/essential'],
  vueTsConfigs.recommended,
  ...pluginOxlint.configs['flat/recommended'],
  skipFormatting,
  {
    name: 'app/rules',
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  },
  {
    name: 'app/ignore',
    ignores: ['admin/**'],
  },
)

这个追加了以下配置。

  {
    name: 'app/ignore',
    ignores: ['admin/**'],
  },

最后,编辑 .gitignore 文件, 对 git 进行配置。让 git 忽略对 admin 目录里的文件进行版本控制。

7月 09

初始化项目时,在根目录会有一个 .vscode 目录。

里边有一个 extensions.json 文件,配置了推荐的 vscode 插件。

里边有一个 settings.json 文件,配置了 vscode 一些行为控制。比如保存时,自动格式化,使用什么插件格式化等。可以单独设置 vue,json,css 等文件的格式化插件。如下:

{
  "explorer.fileNesting.enabled": true,
  "explorer.fileNesting.patterns": {
    "tsconfig.json": "tsconfig.*.json, env.d.ts",
    "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
    "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .oxlint*, oxlint*, .prettier*, prettier*, .editorconfig"
  },
  "editor.codeActionsOnSave": {
    "source.fixAll": "explicit"
  },
  "editor.formatOnSave": true,
  "[vue]": {
    "editor.defaultFormatter": "Vue.volar"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "vscode.json-language-features"
  },
}

该配置文件不进行版本控制。可以增加一个 settings.json.example 来进行版本控制。

手动格式化和检查

# 手动格式
pnpm format

# 手动检查
pnpm lint
7月 09

https://cn.vite.dev/guide/env-and-mode

在本地运行和打包使用不同的配置的时候,环境变量的配置就很有作用。比如本地运行调用接口用的域名是 https://test.xxx.com,正式上线调用的接口是 https://www.xxx.com

先创建智能提示

编辑 env.d.ts 文件,内容如下。

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_BASE_URL: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

创建 env 文件

项目根目录添加 .env 文件。添加以下内容。

VITE_API_BASE_URL=https://test.xxx.com

项目根目录添加 .env.production 文件。添加以下内容。

VITE_API_BASE_URL=https://www.xxx.com

修改 ./src/home/index.vue 文件,测试

<template>
  <div>
    <h1>HI, {{ user.name }} , Welcome to the Home Page</h1>
    <h2>Email: {{ user.email }}</h2>
    <h2>Api: {{ api }}</h2>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const user = ref<User>({
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  createdAt: new Date(),
})

const api = ref(import.meta.env.VITE_API_BASE_URL)

</script>

<style scoped>
h1 {
  color: #42b983;
  font-size: 2em;
}
</style>

测试环境

pnpm dev

生产环境

pnpm dev --mode production

结果如图:

7月 09

定义基本类型

新建 ./src/types 目录,在目录下新建 global.d.ts,添加以下内容。

declare interface User {
  id: number
  name: string
  email: string
  createdAt: Date
}

修改 ./src/home/index.vue

<template>
  <div>
    <h1>HI, {{ user.name }} , Welcome to the Home Page</h1>
    <h2>Email: {{ user.email }}</h2>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const user = ref<User>({
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  createdAt: new Date(),
})
</script>

运行查看 pnpm dev

4月 18

docker 中运行 mysql 8.2,默认情况下,会占用 400M 左右的内存。运行两个就双倍。对于那种便宜的阿里云1核1G或2G的服务器,很容器就卡死。这个时候,就需要设置设置,降低 mysql 的内存消耗了。
其实,一个 mysql 占用 400M 内存,为什么要在一台服务器上装那么多呢。可以对 mysql 进行单独编排,然后通过同一网络的方式,让其他容器也都能访问这个 mysql 就可以了。出现前边那种局面往往是一个完整的项目会有 mysql,redis,php,python,java 等环境构成,而这些构成又放在一个 compose.yaml 中进行编排。那么多个项目后,就会出现多个重复的进程了。虽然对于磁盘空间不会多份重复,内存是在重复增长的。这个时候,设计项目组合的时候就需要考虑考虑。所以想单独剥离 mysql 出来。

可以使用 htop 命令来查看服务器的 cpu,内存使用情况。

可以使用 docker stats 来查看容器使用 cpu, 内存的情况。

优化使用 mysql 内存

新建 my.cnf 文件 ,位置是这样的 ./conf/mysql/my.cnf 。填充下边的代码。

[mysqld]
performance_schema_max_table_instances=400
# 缓存
table_definition_cache=400
# 用于监控MySQL server 在一个较低级别的运行过程中的资源消耗、资源东西
performance_schema=off
# 打开表的缓存
table_open_cache=64
# InnoDB缓冲池大小调整操作的块大小
innodb_buffer_pool_chunk_size=64M
# InnoDB 存储引擎的表数据和索引数据的最大内存缓冲区大小
innodb_buffer_pool_size=64M

在 compose.yaml 中,增加 volume,配置如下。

  mysql: 
    ……
    volumes:
        ……
      - ./conf/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    ports:
      - 3306:3306

然后重启 myslq。

docker-compose restart mysql

然后查看内存使用情况进行对比。

多个项目公用一个 mysql

可以给 mysql 进行单独编排,设置好网络。可以将该网络设置为主网络,其他容器可以通过 external 的方式公用同一个网络。这样各个容器间就方便通信了。

11月 28

tauri 时间相关的处理。一般,找 tauri 的 api,就是找 rust 的。tauri 获取当前时间,获取时间戳。

https://docs.rs/chrono/0.4.26/chrono/#date-and-time

https://www.cnblogs.com/yjmyzz/p/event-with-tauri.html

获取当前年月日时分秒

use chrono::{DateTime, Local};

fn now_datetime() -> String {
  let datetime: DateTime<Local> = Local::now();
  let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string();
  timestamp_str
}

需要在 Cargo.toml 中,加入以下配置。

[dependencies]
…
chrono = "0.4"