diff --git a/.gitignore b/.gitignore index 2c5cb22..5f1c063 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .idea node_modules log +logs dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9663ca2..c3a494a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ -FROM node:16 +FROM node:18 -RUN npm install -g pnpm@7.1.0 && \ - npm config set registry https://registry.npm.taobao.org +RUN npm install -g pnpm@8.4.0 +# && \ +# npm config set registry https://registry.npm.taobao.org WORKDIR /usr/src/app +COPY ./data ./data COPY ./dist ./dist COPY ./template ./template COPY ./patches ./patches @@ -14,7 +16,8 @@ COPY package.json ./ COPY pnpm-lock.yaml ./ -RUN pnpm install +RUN pnpm install --frozen-lockfile +# 使用 --frozen-lockfile 标志是为了确保只安装lock文件中指定的版本,而不会进行任何更新。这将确保您的依赖项与锁定文件中指定的版本完全一致。 EXPOSE 3388 diff --git a/source/db/data.db b/data/data.db similarity index 98% rename from source/db/data.db rename to data/data.db index ae78046..ec1a18c 100644 Binary files a/source/db/data.db and b/data/data.db differ diff --git a/public/upload/00018-4071989711_2023_05_16_1684169231914.png b/public/upload/00018-4071989711_2023_05_16_1684169231914.png deleted file mode 100644 index ccd87b8..0000000 Binary files a/public/upload/00018-4071989711_2023_05_16_1684169231914.png and /dev/null differ diff --git a/readme.md b/readme.md index c907805..ce5a5a1 100644 --- a/readme.md +++ b/readme.md @@ -45,4 +45,7 @@ docker container ls -a https://blog.csdn.net/LIFENG0402/article/details/117930091 ## 使用事务 -https://sequelize.org/docs/v6/other-topics/transactions/#:~:text=The%20sequelize.transaction%20method%20accepts%20options.%20For%20unmanaged%20transactions%2C,%28options%29.%20For%20managed%20transactions%2C%20use%20sequelize.transaction%20%28options%2C%20callback%29. \ No newline at end of file +https://sequelize.org/docs/v6/other-topics/transactions/#:~:text=The%20sequelize.transaction%20method%20accepts%20options.%20For%20unmanaged%20transactions%2C,%28options%29.%20For%20managed%20transactions%2C%20use%20sequelize.transaction%20%28options%2C%20callback%29. + +docker-compose up -d +docker-compose down \ No newline at end of file diff --git a/route.txt b/route.txt index 23b1463..b9ffa42 100644 --- a/route.txt +++ b/route.txt @@ -1,6 +1,6 @@ -D:\1XYX\pro\hapi-demo\source\route\htmx对应路径: +/home/topuser/Code/@project/hapi-demo/source/route/htmx对应路径: 不需权限 : GET /htmx/path/{path*} -D:\1XYX\pro\hapi-demo\source\route\views对应路径: +/home/topuser/Code/@project/hapi-demo/source/route/views对应路径: 不需权限(提供无需验证): GET /404 不需权限(提供无需验证): GET / 不需权限(提供无需验证): GET /about @@ -11,7 +11,6 @@ D:\1XYX\pro\hapi-demo\source\route\views对应路径: 不需权限 : GET /nav 不需权限(提供无需验证): GET /register 不需权限 : POST /register - 需要权限 : POST /upload 需要权限 : POST /user 需要权限 : GET /user 需要权限 : GET /user/logout diff --git a/source/log4js_config.ts b/source/log4js_config.ts index 7da21e5..484b4f3 100644 --- a/source/log4js_config.ts +++ b/source/log4js_config.ts @@ -5,11 +5,11 @@ export default function () { appenders: { file: { type: "file", - filename: path.resolve(__dirname, "../", "./log", "./Site.log"), + filename: path.resolve(__dirname, "../", "./logs", "./Site.log"), }, SQL: { type: "file", - filename: path.resolve(__dirname, "../", "./log", "./SQL.log"), + filename: path.resolve(__dirname, "../", "./logs", "./SQL.log"), }, console: { type: "console", diff --git a/source/main.ts b/source/main.ts index 222b290..cb33baa 100644 --- a/source/main.ts +++ b/source/main.ts @@ -1,6 +1,7 @@ // require("dotenv").config(); import { configure, getLogger } from "log4js" import log4jsConfig from "./log4js_config" +import { isDev } from "./util" configure(log4jsConfig()) const loggerSite = getLogger("Site") @@ -14,12 +15,12 @@ global.logger = logger global.loggerSite = loggerSite global.loggerSQL = loggerSQL -import("./run").then(mod=>{ +import(isDev ? "./run" : "./run.js").then(mod => { mod.run().then(server => { global.server = server - }).catch(err=>{ + }).catch(err => { console.error(err); }) - + }) diff --git a/source/models/Color.ts b/source/models/color.ts similarity index 100% rename from source/models/Color.ts rename to source/models/color.ts diff --git a/source/models/user.ts b/source/models/user.ts index d72ec87..e6962c4 100644 --- a/source/models/user.ts +++ b/source/models/user.ts @@ -1,86 +1,71 @@ -import { Sequelize, DataTypes, Optional, Model } from "sequelize" +import { Sequelize, DataTypes, Optional, Model } from "sequelize"; interface UserAttributes { - id: number - username: string - password: string - nickname: string - email: string - avatar: string - tel: string - - createdAt?: Date - updatedAt?: Date - deletedAt?: Date + id: number; + username: string; + password: string; + nickname: string; + email: string; + avatar: string; + tel: string; + createdAt?: Date; + updatedAt?: Date; + deletedAt?: Date; } export interface UserInput extends Optional { } export interface UserOutput extends Required { } export type TUserModel = ReturnType -type DT = typeof DataTypes -export default function UserModel(sequelize: Sequelize, DataTypes: DT) { - class User extends Model implements UserAttributes { - public id!: number - public username!: string - public password!: string - public nickname: string - public email: string - public avatar: string - public tel: string +export default function UserModel(sequelize: Sequelize) { + interface UserInstance + extends Model, + UserAttributes { } - // timestamps! - public readonly createdAt!: Date - public readonly updatedAt!: Date - public readonly deletedAt!: Date - } - User.init( - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - username: { - type: DataTypes.STRING, - allowNull: false, - }, - password: { - type: DataTypes.STRING, - allowNull: false, - }, - nickname: { - type: DataTypes.STRING, - allowNull: false, - }, - email: { - type: DataTypes.STRING, - }, - avatar: { - type: DataTypes.STRING, - }, - tel: { - type: DataTypes.STRING, - }, + const User = sequelize.define("user", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + }, + nickname: { + type: DataTypes.STRING, + allowNull: false, + }, + email: { + type: DataTypes.STRING, }, - { - modelName: "user", - sequelize, - underscored: true, - deletedAt: true, - timestamps: true, - paranoid: true, // 对模型施加了一个软删除 + avatar: { + type: DataTypes.STRING, }, - ) - // 覆盖User的toJSON方法 + tel: { + type: DataTypes.STRING, + }, + }, { + underscored: true, + deletedAt: true, + paranoid: true, + timestamps: true, + }); + User.prototype.toJSON = function () { - const values = Object.assign({}, this.get()) as UserAttributes - delete values.password - delete values.deletedAt - return values - } + const values = { ...this.get() } as UserAttributes; + delete values.password; + delete values.deletedAt; + return values; + }; + User.associate = function (models) { - - } - return User -} \ No newline at end of file + // Define associations here + }; + + return User; +} diff --git a/source/plugins/sequelize.ts b/source/plugins/sequelize.ts index 6042163..92e6b06 100644 --- a/source/plugins/sequelize.ts +++ b/source/plugins/sequelize.ts @@ -1,10 +1,10 @@ import { Sequelize } from "sequelize" import path from "path" -import { sourceDir } from "@/util" +import { baseDir } from "@/util" export const sequelize = new Sequelize({ dialect: "sqlite", - storage: path.resolve(sourceDir, "./db/data.db"), + storage: process.env.SQLITE_PATH || path.resolve(baseDir, "./data/data.db"), // logging: false, logging: loggerSQL.debug.bind(loggerSQL) // Alternative way to use custom logger, displays all messages }) \ No newline at end of file diff --git a/source/route/views/login.ts b/source/route/views/login.ts index f41cfcd..570ea14 100644 --- a/source/route/views/login.ts +++ b/source/route/views/login.ts @@ -33,7 +33,7 @@ export default class { const { username, password, referrer } = request.payload as any const User = request.getModel("user") - const account = await User.findOne({ where: { username: username } }) + const account = await User.findOne({ where: { username: username } }) if (!account || !(await bcrypt.compare(password, account.password))) { request.yar.flash("error", "Invalid username or password") diff --git a/source/route/views/upload/_upload.ts b/source/route/views/upload/_upload.ts deleted file mode 100644 index 5b30500..0000000 --- a/source/route/views/upload/_upload.ts +++ /dev/null @@ -1,82 +0,0 @@ -import path from "path" -import { gSuccess, gFail, uploadDir, uploadPath } from "@/util" -import { dateTimeFormat } from "@/util/util" -import {fileTypeFromFile} from 'file-type'; -const fs = require("fs") -const multiparty = require("multiparty") - -function saveFile(file) { - return new Promise(async (resolve, reject) => { - const filename = file.originalFilename - const uploadedPath = file.path - const filetype = await fileTypeFromFile(uploadedPath) - const _file = path.parse(filename) - if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { - let _name = - _file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext - const dstPath = path.resolve(uploadDir, _name) - fs.rename(uploadedPath, dstPath, function (err) { - if (err) { - console.log("rename error: " + err) - reject() - } else { - resolve(path.resolve("/public/upload/"+_name)) - } - }) - } else { - fs.unlinkSync(uploadedPath) - reject(new Error(filename + "文件不是图片")) - } - }) -} - -export default function (payload) { - const form = new multiparty.Form({ - uploadDir: uploadDir, //路径需要对应自己的项目更改 - /*设置文件保存路径 */ - encoding: "utf-8", - /*编码设置 */ - maxFilesSize: 20000 * 1024 * 1024, - /*设置文件最大值 20MB */ - keepExtensions: true, - /*保留后缀*/ - }) - return new Promise(async (resolve, reject) => { - form.on("part", function (part) { - console.log(part.filename) - }) - form.on("progress", function (bytesReceived, bytesExpected) { - if (bytesExpected === null) { - return - } - - var percentComplete = (bytesReceived / bytesExpected) * 100 - console.log("the form is " + Math.floor(percentComplete) + "%" + " complete") - }) - form.parse(payload, async function (err, fields, files) { - // console.log(err, fields, files); - - if (err) { - resolve(err.message) - return - } - const errList = [] - const fileList = [] - for (let i = 0; i < files.file.length; i++) { - const file = files.file[i] - try { - const dstPath = await saveFile(file) - fileList.push(dstPath) - } catch (error) { - errList.push(error.message) - } - } - if (errList.length) { - resolve(gFail(null, errList.join("\n"))) - return - } - // resolve(h.view("views/upload.ejs")); - resolve([...new Set(fileList)]) - }) - }) -} diff --git a/source/route/views/upload/index.ts b/source/route/views/upload/index.ts deleted file mode 100644 index ad05448..0000000 --- a/source/route/views/upload/index.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { auth, config, method, route, swagger, validate } from "@noderun/hapi-router" -import UploadFunc from "./_upload" -import path, { resolve } from "path" -import { gFail, uploadDir } from "@/util" -import { fileTypeFromFile, fileTypeFromStream } from "file-type" -import { dateTimeFormat } from "@/util/util" -import fs from "fs-extra" -import { Req, Res } from "#/global" -import * as bcrypt from "bcrypt" -const multiparty = require("multiparty") - -export default class { - @config({ - payload: { - maxBytes: 20000 * 1024 * 1024, - output: "stream", - parse: false, - multipart: true, - timeout: false, - allow: ["multipart/form-data", "application/x-www-form-urlencoded"], - }, - }) - @method("POST") - @auth() - async index(request: Req, h: Res) { - const { id, username, nickname, email, tel } = request.auth.credentials - const { filelist, fields } = await Save(request.payload, request) - const result = {} - if (fields["username"] && fields["username"][0] && username !== fields["username"][0]) { - result["username"] = fields["username"][0] - } - if (fields["tel"] && fields["tel"][0] && tel !== fields["tel"][0]) { - result["tel"] = fields["tel"][0] - } - if (fields["nickname"] && fields["nickname"][0] && nickname !== fields["nickname"][0]) { - result["nickname"] = fields["nickname"][0] - } - if (fields["email"] && fields["email"][0] && email !== fields["email"][0]) { - result["email"] = fields["email"][0] - } - if (fields["password"] && fields["password"][0]) { - const pwd = fields["password"][0] - let salt = bcrypt.genSaltSync(10) - let pwdLock = bcrypt.hashSync(pwd, salt) - result["password"] = pwdLock - } - if (filelist && filelist[0]) { - result["avatar"] = filelist[0] - } - if (JSON.stringify(result) !== "{}") { - const UserModel = request.getModel("user") - const user = await UserModel.findOne({ where: { username: fields["username"] } }) - if (!!user && user.id !== id) { - request.yar.flash("error", "用户名已被他人占用") - return h.redirect("/user") - } - console.log(fields); - - await UserModel.update(result, { where: { id } }) - } - return h.redirect("/user") - } -} - -function saveFile(file) { - return new Promise(async (resolve, reject) => { - const filename = file.originalFilename - const uploadedPath = file.path - const filetype = await fileTypeFromFile(uploadedPath) - const _file = path.parse(filename) - if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { - let _name = - _file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext - const dstPath = path.resolve(uploadDir, _name) - fs.rename(uploadedPath, dstPath, function (err) { - if (err) { - console.log("rename error: " + err) - reject() - } else { - resolve("/public/upload/" + _name) - } - }) - } else { - fs.unlinkSync(uploadedPath) - reject(new Error(filename + "文件不是图片")) - } - }) -} - -function Save(payload, req: Req) { - const AttachmentModel = req.getModel("attachment") - const form = new multiparty.Form({ - uploadDir: uploadDir, //路径需要对应自己的项目更改 - /*设置文件保存路径 */ - encoding: "utf-8", - /*编码设置 */ - maxFilesSize: 20000 * 1024 * 1024, - /*设置文件最大值 20MB */ - keepExtensions: true, - /*保留后缀*/ - }) - return new Promise(async (resolve, reject) => { - form.on("part", function (part) { - console.log(1111) - console.log(part.filename) - }) - form.on("progress", function (bytesReceived, bytesExpected) { - if (bytesExpected === null) { - return - } - - // var percentComplete = (bytesReceived / bytesExpected) * 100 - // console.log("the form is " + Math.floor(percentComplete) + "%" + " complete") - }) - form.parse(payload, async function (err, fields, files) { - // console.log(err, fields, files); - if (err) { - reject(err.message) - return - } - const errList = [] - const fileList = [] - if (files && files.file && files.file.length) { - for (let i = 0; i < files.file.length; i++) { - const file = files.file[i] - if (file.originalFilename === "" && file.size === 0) { - const uploadedPath = file.path - fs.unlinkSync(uploadedPath) - continue - } - try { - const dstPath = await saveFile(file) - fileList.push(dstPath) - } catch (error) { - errList.push(error.message) - } - } - } - if (errList.length) { - resolve(gFail(null, errList.join("\n"))) - return - } - resolve({ - fields, - filelist: [...new Set(fileList)], - }) - }) - }) -} diff --git a/source/route/views/user.ts b/source/route/views/user.ts index 1315a15..bc75833 100644 --- a/source/route/views/user.ts +++ b/source/route/views/user.ts @@ -1,9 +1,7 @@ import { Req, Res, ReturnValue } from "#/global" -import { UserSchema } from "@/schema" import { auth, config, method, route, route_path, swagger, validate } from "@noderun/hapi-router" import path, { resolve } from "path" import { gFail, uploadDir } from "@/util" -import { fileTypeFromFile, fileTypeFromStream } from "file-type" import { dateTimeFormat } from "@/util/util" import fs from "fs-extra" import * as bcrypt from "bcrypt" @@ -13,7 +11,7 @@ function saveFile(file) { return new Promise(async (resolve, reject) => { const filename = file.originalFilename const uploadedPath = file.path - const filetype = await fileTypeFromFile(uploadedPath) + const filetype = await (await import("file-type")).fileTypeFromFile(uploadedPath) const _file = path.parse(filename) if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { let _name = diff --git a/source/run.ts b/source/run.ts index 829b17d..34feb48 100644 --- a/source/run.ts +++ b/source/run.ts @@ -41,7 +41,7 @@ const run = async (): Promise => { options: [ { name: "data", // identifier - models: ["source/models/**/*.ts"], // paths/globs to model files + models: isDev?["source/models/**/*.ts"]:["dist/models/**/*.js"], // paths/globs to model files // ignoredModels: [__dirname + "/server/models/**/*.js"], // OPTIONAL: paths/globs to ignore files sequelize: sequelize, // sequelize instance sync: true, // sync models - default false diff --git a/source/schema/index.ts b/source/schema/index.ts index 7e68eab..14eaf09 100644 --- a/source/schema/index.ts +++ b/source/schema/index.ts @@ -10,7 +10,7 @@ export const UserSchema = Joi.object({ }).or("username", "email") export const RegisterUserSchema = Joi.object({ - username: Joi.string().alphanum().min(6).max(35).required(), + username: Joi.string().alphanum().min(5).max(35).required(), password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(), confrim_pwd: Joi.ref("password"), email: Joi.string() diff --git a/template/htmx/path/index.pug b/template/htmx/path/index.pug index e823e68..cae0d4a 100644 --- a/template/htmx/path/index.pug +++ b/template/htmx/path/index.pug @@ -1,4 +1,6 @@ include @/helper/flush.pug +include @/helper/helper.pug + block var -title="首页" // 网页标题 title 首页 diff --git a/template/layout/layout.pug b/template/layout/layout.pug index 66b84c4..633546e 100644 --- a/template/layout/layout.pug +++ b/template/layout/layout.pug @@ -1,5 +1,6 @@ doctype html include @/helper/helper.pug +include @/helper/flush.pug block var html(lang="zh-cn" class=hideHeader?"":"has-navbar-fixed-top") diff --git a/tsconfig.json b/tsconfig.json index 7ee2177..92ff006 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,11 +5,11 @@ "compilerOptions": { "outDir": "./dist", "allowJs": true, - "target": "es6", + "target": "ESNext", "sourceMap": false, "module": "commonjs", "experimentalDecorators": true, - "moduleResolution": "node", + "moduleResolution": "nodenext", "esModuleInterop": true, // esModuleInterop选项的作用是支持使用import d from 'cjs'的方式引入commonjs包。 "baseUrl": ".", "paths": { diff --git a/types/global.d.ts b/types/global.d.ts index 0833b5a..fe2e402 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -33,7 +33,7 @@ declare module "@hapi/hapi" { interface Server { yar: yar.ServerYar } - interface ResponseToolkit {} + interface ResponseToolkit { } } export declare type Req = Request