npmrun 2 years ago
parent
commit
e2565cc9be
  1. 1
      .gitignore
  2. 11
      Dockerfile
  3. BIN
      data/data.db
  4. BIN
      public/upload/00018-4071989711_2023_05_16_1684169231914.png
  5. 3
      readme.md
  6. 5
      route.txt
  7. 4
      source/log4js_config.ts
  8. 5
      source/main.ts
  9. 0
      source/models/color.ts
  10. 127
      source/models/user.ts
  11. 4
      source/plugins/sequelize.ts
  12. 2
      source/route/views/login.ts
  13. 82
      source/route/views/upload/_upload.ts
  14. 149
      source/route/views/upload/index.ts
  15. 4
      source/route/views/user.ts
  16. 2
      source/run.ts
  17. 2
      source/schema/index.ts
  18. 2
      template/htmx/path/index.pug
  19. 1
      template/layout/layout.pug
  20. 4
      tsconfig.json
  21. 2
      types/global.d.ts

1
.gitignore

@ -2,4 +2,5 @@
.idea .idea
node_modules node_modules
log log
logs
dist dist

11
Dockerfile

@ -1,10 +1,12 @@
FROM node:16 FROM node:18
RUN npm install -g pnpm@7.1.0 && \ RUN npm install -g pnpm@8.4.0
npm config set registry https://registry.npm.taobao.org # && \
# npm config set registry https://registry.npm.taobao.org
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY ./data ./data
COPY ./dist ./dist COPY ./dist ./dist
COPY ./template ./template COPY ./template ./template
COPY ./patches ./patches COPY ./patches ./patches
@ -14,7 +16,8 @@ COPY package.json ./
COPY pnpm-lock.yaml ./ COPY pnpm-lock.yaml ./
RUN pnpm install RUN pnpm install --frozen-lockfile
# 使用 --frozen-lockfile 标志是为了确保只安装lock文件中指定的版本,而不会进行任何更新。这将确保您的依赖项与锁定文件中指定的版本完全一致。
EXPOSE 3388 EXPOSE 3388

BIN
source/db/data.db → data/data.db

Binary file not shown.

BIN
public/upload/00018-4071989711_2023_05_16_1684169231914.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

3
readme.md

@ -46,3 +46,6 @@ 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. 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

5
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*} 不需权限 : GET /htmx/path/{path*}
D:\1XYX\pro\hapi-demo\source\route\views对应路径: /home/topuser/Code/@project/hapi-demo/source/route/views对应路径:
不需权限(提供无需验证): GET /404 不需权限(提供无需验证): GET /404
不需权限(提供无需验证): GET / 不需权限(提供无需验证): GET /
不需权限(提供无需验证): GET /about 不需权限(提供无需验证): GET /about
@ -11,7 +11,6 @@ D:\1XYX\pro\hapi-demo\source\route\views对应路径:
不需权限 : GET /nav 不需权限 : GET /nav
不需权限(提供无需验证): GET /register 不需权限(提供无需验证): GET /register
不需权限 : POST /register 不需权限 : POST /register
需要权限 : POST /upload
需要权限 : POST /user 需要权限 : POST /user
需要权限 : GET /user 需要权限 : GET /user
需要权限 : GET /user/logout 需要权限 : GET /user/logout

4
source/log4js_config.ts

@ -5,11 +5,11 @@ export default function () {
appenders: { appenders: {
file: { file: {
type: "file", type: "file",
filename: path.resolve(__dirname, "../", "./log", "./Site.log"), filename: path.resolve(__dirname, "../", "./logs", "./Site.log"),
}, },
SQL: { SQL: {
type: "file", type: "file",
filename: path.resolve(__dirname, "../", "./log", "./SQL.log"), filename: path.resolve(__dirname, "../", "./logs", "./SQL.log"),
}, },
console: { console: {
type: "console", type: "console",

5
source/main.ts

@ -1,6 +1,7 @@
// require("dotenv").config(); // require("dotenv").config();
import { configure, getLogger } from "log4js" import { configure, getLogger } from "log4js"
import log4jsConfig from "./log4js_config" import log4jsConfig from "./log4js_config"
import { isDev } from "./util"
configure(log4jsConfig()) configure(log4jsConfig())
const loggerSite = getLogger("Site") const loggerSite = getLogger("Site")
@ -14,10 +15,10 @@ global.logger = logger
global.loggerSite = loggerSite global.loggerSite = loggerSite
global.loggerSQL = loggerSQL global.loggerSQL = loggerSQL
import("./run").then(mod=>{ import(isDev ? "./run" : "./run.js").then(mod => {
mod.run().then(server => { mod.run().then(server => {
global.server = server global.server = server
}).catch(err=>{ }).catch(err => {
console.error(err); console.error(err);
}) })

0
source/models/Color.ts → source/models/color.ts

127
source/models/user.ts

@ -1,86 +1,71 @@
import { Sequelize, DataTypes, Optional, Model } from "sequelize" import { Sequelize, DataTypes, Optional, Model } from "sequelize";
interface UserAttributes { interface UserAttributes {
id: number id: number;
username: string username: string;
password: string password: string;
nickname: string nickname: string;
email: string email: string;
avatar: string avatar: string;
tel: string tel: string;
createdAt?: Date;
createdAt?: Date updatedAt?: Date;
updatedAt?: Date deletedAt?: Date;
deletedAt?: Date
} }
export interface UserInput extends Optional<UserAttributes, "id"> { } export interface UserInput extends Optional<UserAttributes, "id"> { }
export interface UserOutput extends Required<UserAttributes> { } export interface UserOutput extends Required<UserAttributes> { }
export type TUserModel = ReturnType<typeof UserModel> export type TUserModel = ReturnType<typeof UserModel>
type DT = typeof DataTypes export default function UserModel(sequelize: Sequelize) {
export default function UserModel(sequelize: Sequelize, DataTypes: DT) { interface UserInstance
class User extends Model<UserAttributes, UserInput> implements UserAttributes { extends Model<UserAttributes, UserInput>,
public id!: number UserAttributes { }
public username!: string
public password!: string
public nickname: string
public email: string
public avatar: string
public tel: string
// timestamps! const User = sequelize.define<UserInstance>("user", {
public readonly createdAt!: Date id: {
public readonly updatedAt!: Date type: DataTypes.INTEGER,
public readonly deletedAt!: Date autoIncrement: true,
} primaryKey: true,
User.init( },
{ username: {
id: { type: DataTypes.STRING,
type: DataTypes.INTEGER, allowNull: false,
autoIncrement: true, },
primaryKey: true, password: {
}, type: DataTypes.STRING,
username: { allowNull: false,
type: DataTypes.STRING, },
allowNull: false, nickname: {
}, type: DataTypes.STRING,
password: { allowNull: false,
type: DataTypes.STRING,
allowNull: false,
},
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
},
}, },
{ email: {
modelName: "user", type: DataTypes.STRING,
sequelize,
underscored: true,
deletedAt: true,
timestamps: true,
paranoid: true, // 对模型施加了一个软删除
}, },
) avatar: {
// 覆盖User的toJSON方法 type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
},
}, {
underscored: true,
deletedAt: true,
paranoid: true,
timestamps: true,
});
User.prototype.toJSON = function () { User.prototype.toJSON = function () {
const values = Object.assign({}, this.get()) as UserAttributes const values = { ...this.get() } as UserAttributes;
delete values.password delete values.password;
delete values.deletedAt delete values.deletedAt;
return values return values;
} };
User.associate = function (models) { User.associate = function (models) {
// Define associations here
};
} return User;
return User
} }

4
source/plugins/sequelize.ts

@ -1,10 +1,10 @@
import { Sequelize } from "sequelize" import { Sequelize } from "sequelize"
import path from "path" import path from "path"
import { sourceDir } from "@/util" import { baseDir } from "@/util"
export const sequelize = new Sequelize({ export const sequelize = new Sequelize({
dialect: "sqlite", dialect: "sqlite",
storage: path.resolve(sourceDir, "./db/data.db"), storage: process.env.SQLITE_PATH || path.resolve(baseDir, "./data/data.db"),
// logging: false, // logging: false,
logging: loggerSQL.debug.bind(loggerSQL) // Alternative way to use custom logger, displays all messages logging: loggerSQL.debug.bind(loggerSQL) // Alternative way to use custom logger, displays all messages
}) })

2
source/route/views/login.ts

@ -33,7 +33,7 @@ export default class {
const { username, password, referrer } = request.payload as any const { username, password, referrer } = request.payload as any
const User = request.getModel("user") const User = request.getModel("user")
const account = <any>await User.findOne({ where: { username: username } }) const account = await User.findOne({ where: { username: username } })
if (!account || !(await bcrypt.compare(password, account.password))) { if (!account || !(await bcrypt.compare(password, account.password))) {
request.yar.flash("error", "Invalid username or password") request.yar.flash("error", "Invalid username or password")

82
source/route/views/upload/_upload.ts

@ -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)])
})
})
}

149
source/route/views/upload/index.ts

@ -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<any>(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)],
})
})
})
}

4
source/route/views/user.ts

@ -1,9 +1,7 @@
import { Req, Res, ReturnValue } from "#/global" import { Req, Res, ReturnValue } from "#/global"
import { UserSchema } from "@/schema"
import { auth, config, method, route, route_path, swagger, validate } from "@noderun/hapi-router" import { auth, config, method, route, route_path, swagger, validate } from "@noderun/hapi-router"
import path, { resolve } from "path" import path, { resolve } from "path"
import { gFail, uploadDir } from "@/util" import { gFail, uploadDir } from "@/util"
import { fileTypeFromFile, fileTypeFromStream } from "file-type"
import { dateTimeFormat } from "@/util/util" import { dateTimeFormat } from "@/util/util"
import fs from "fs-extra" import fs from "fs-extra"
import * as bcrypt from "bcrypt" import * as bcrypt from "bcrypt"
@ -13,7 +11,7 @@ function saveFile(file) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const filename = file.originalFilename const filename = file.originalFilename
const uploadedPath = file.path const uploadedPath = file.path
const filetype = await fileTypeFromFile(uploadedPath) const filetype = await (await import("file-type")).fileTypeFromFile(uploadedPath)
const _file = path.parse(filename) const _file = path.parse(filename)
if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) {
let _name = let _name =

2
source/run.ts

@ -41,7 +41,7 @@ const run = async (): Promise<Server> => {
options: [ options: [
{ {
name: "data", // identifier 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 // ignoredModels: [__dirname + "/server/models/**/*.js"], // OPTIONAL: paths/globs to ignore files
sequelize: sequelize, // sequelize instance sequelize: sequelize, // sequelize instance
sync: true, // sync models - default false sync: true, // sync models - default false

2
source/schema/index.ts

@ -10,7 +10,7 @@ export const UserSchema = Joi.object({
}).or("username", "email") }).or("username", "email")
export const RegisterUserSchema = Joi.object({ 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(), password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
confrim_pwd: Joi.ref("password"), confrim_pwd: Joi.ref("password"),
email: Joi.string() email: Joi.string()

2
template/htmx/path/index.pug

@ -1,4 +1,6 @@
include @/helper/flush.pug include @/helper/flush.pug
include @/helper/helper.pug
block var block var
-title="首页" // 网页标题 -title="首页" // 网页标题
title 首页 title 首页

1
template/layout/layout.pug

@ -1,5 +1,6 @@
doctype html doctype html
include @/helper/helper.pug include @/helper/helper.pug
include @/helper/flush.pug
block var block var
html(lang="zh-cn" class=hideHeader?"":"has-navbar-fixed-top") html(lang="zh-cn" class=hideHeader?"":"has-navbar-fixed-top")

4
tsconfig.json

@ -5,11 +5,11 @@
"compilerOptions": { "compilerOptions": {
"outDir": "./dist", "outDir": "./dist",
"allowJs": true, "allowJs": true,
"target": "es6", "target": "ESNext",
"sourceMap": false, "sourceMap": false,
"module": "commonjs", "module": "commonjs",
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "node", "moduleResolution": "nodenext",
"esModuleInterop": true, // esModuleInterop使import d from 'cjs'commonjs "esModuleInterop": true, // esModuleInterop使import d from 'cjs'commonjs
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {

2
types/global.d.ts

@ -33,7 +33,7 @@ declare module "@hapi/hapi" {
interface Server { interface Server {
yar: yar.ServerYar yar: yar.ServerYar
} }
interface ResponseToolkit {} interface ResponseToolkit { }
} }
export declare type Req = Request export declare type Req = Request

Loading…
Cancel
Save