diff --git a/.umirc.ts b/.umirc.ts index 72a17ca..5471ee3 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -102,8 +102,8 @@ export default defineConfig({ children: [ 'learn/blog-intro.md', 'learn/blog-create-project.md', - 'learn/blog-db-conn.md', 'learn/blog-api.md', + 'learn/blog-db-conn.md', 'learn/blog-category.md', 'learn/blog-post.md', 'learn/blog-tag.md', diff --git a/docs/learn/blog-api.md b/docs/learn/blog-api.md index 5d193f4..f65578c 100644 --- a/docs/learn/blog-api.md +++ b/docs/learn/blog-api.md @@ -31,7 +31,7 @@ export interface ResponseData { 导出数据构结定义`src/common/index.ts` ```ts -export * from './data/response-data'; +export * from "./data/response-data"; ``` 创建工具函数格式化输出结果`src/backend/utils/index.ts` diff --git a/docs/learn/blog-auth.md b/docs/learn/blog-auth.md index 2a0cc6c..a6bb2b1 100644 --- a/docs/learn/blog-auth.md +++ b/docs/learn/blog-auth.md @@ -5,17 +5,15 @@ type: learn lang: zh-CN --- -# 添加认证 +# 登录认证 -### 功能说明 - -示例设计的是一个单用户blog系统,所以只要用户登录成功token可以效验即允许用户操作。使用@malagu/security、jsonwebtoken、crypto-js等库。 +示例设计的是一个单用户 blog 系统,当用户登录成功返回 token ,请求传入 token 可以校验即允许用户操作。使用@malagu/security、@malagu/jwt、crypto-js等库 ### 添加依赖 ```bash -yarn add @malagu/security jsonwebtoken crypto-js -yarn add --dev @types/crypto-js @types/jsonwebtoken +yarn add @malagu/security @malagu/jwt crypto-js +yarn add --dev @types/crypto-js ``` 修改 `src/malagu-local.yml` 添加如下内容: @@ -26,301 +24,413 @@ backend: # 新增内容 logger: level: debug - - security: - enabled: false + jwt: + secret: abcdefg ``` -打开日志,禁用掉security默认的认证策略 - -### 添加token和加密工具函数 +### 添加工具函数 -创建token工具类 `src/backend/auth/token-utils.ts` 内容如下: +创建 `src/backend/utils/crypto.ts` 内容如下: ```ts -import { Service } from "@malagu/core"; -import * as JWT from "jsonwebtoken"; - -const tokenExpireTime = 1800; // 30分钟 -const privateKey = "privateKey"; // json-web-token的密钥,不能泄露 - -@Service() -export class TokenUtils { - async getToken(uid: number, username: string) { - return JWT.sign( - { uid, username }, privateKey, - { expiresIn: tokenExpireTime } - ); - } - - async verifyToken(token: string) { - let decoded: any = {}; - try { - decoded = JWT.verify(token, privateKey); - } catch (err: any) { - if (err.name === "TokenExpiredError") { - throw new Error("TokenExpired"); - } - throw new Error("TokenError"); - } +import * as SHA256 from "crypto-js/sha256"; - if (decoded.uid) { - return { - uid: decoded.uid, - user_name: decoded.user_name - }; - } - throw new Error("TokenNotMatch"); - } +export function sha256Encode(content: string) { + return SHA256(content).toString(); } ``` -创建加密工具函数 `src/backend/utils/crypto.ts` 内容如下: +### 添加用户实体 + +创建 `src/backend/entity/user.ts` 文件定义用户实体,内容如下: ```ts -import * as SHA256 from "crypto-js/sha256"; +import { BaseEntity, Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, + UpdateDateColumn, Unique } from "typeorm"; -export function sha256Encode(content: string) { - return SHA256(content).toString(); +@Entity({ name: "users" }) +@Unique(["username"]) +export class User extends BaseEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column() + username: string; + + @Column() + password: string; + + @Column() + desc: string; + + @CreateDateColumn({ name: "created_at" }) + createdAt: Date; + + @UpdateDateColumn({ name: "updated_at" }) + updatedAt: Date; } ``` -### 添加认证实现 - -认证提供者 `src/backend/auth/auth-provider.ts` +修改 `src/backend/entity/index.ts` 导出实体,添加内容如下: ```ts -import { Autowired, Component, Logger } from "@malagu/core"; -import { Context } from "@malagu/web/lib/node"; -import { RouteMetadataMatcher } from "@malagu/mvc/lib/node"; -import { AuthenticationProvider, Authentication, - BASE_AUTHENTICATION_PROVIDER_PRIORITY } from "@malagu/security/lib/node"; -import { TokenUtils } from "./token-utils"; -import { jsonFormat } from "../utils"; +export * from "./user"; +``` -interface AuthInfo extends Authentication { - uid: number; - name: string; -} +创建`src/backend/services/user-service.ts`文件处理用户加载,内容如下: -@Component(AuthenticationProvider) -export class CustomAuthenticationProviderImpl implements AuthenticationProvider { - priority:number = BASE_AUTHENTICATION_PROVIDER_PRIORITY+10; +```ts +import { Service } from "@malagu/core"; +import { UserService } from "@malagu/security/lib/node"; +import { User } from "../entity/user"; + +@Service({ id: UserService, rebind: true }) +export class UserServiceImpl implements UserService { + async load(username: string): Promise { + let user = await User.findOne({ where: { username: username } }); + return user; + } +} +``` - @Autowired() - tokenUtils: TokenUtils; +默认 [user-service](https://github.com/cellbang/malagu/blob/main/packages/security/src/node/user/user-service.ts) 实现 - @Autowired(Logger) - logger: Logger; +因为我们刚刚创建数据库,数据库中还没有用户 - @Autowired(RouteMetadataMatcher) - routeMetadataMatcher: RouteMetadataMatcher; +创建`src/backend/controllers/user-controller.ts`初始化用户数据、返回用户信息,内容如下: - responseNoAuth(statusCode: number = 401, message = "no auth") { - const res = Context.getResponse(); - res.statusCode = statusCode; - let result = jsonFormat(null, message); - res.body = JSON.stringify(result); - } +```ts +import { Controller, Get } from "@malagu/mvc/lib/node"; +import { User } from "../entity/user"; +import { Authenticated, SecurityContext } from "@malagu/security/lib/node" +import { sha256Encode } from "../utils/crypto"; +import { Value } from "@malagu/core"; +import { jsonFormat } from "../utils"; - async authenticate(): Promise { - this.logger.debug("----------------------------------------"); - this.logger.error("authenticate"); - this.logger.debug("----------------------------------------"); - const request = Context.getRequest(); - let headerToken = request.get("Token") || ""; - if (headerToken) { - headerToken = headerToken.trim(); - } - if (!headerToken) { - this.responseNoAuth(401, "no auth"); - } - try { - const tokenResult = await this.tokenUtils.verifyToken(headerToken); - return { - uid: tokenResult.uid, - name: tokenResult.user_name, - policies: [], - credentials: null, - principal: null, - authenticated: true, - next: true, - }; - } catch (error) { - this.responseNoAuth(403, "auth failure"); - } - return { - uid: -1, - name: "", - policies: [], - credentials: null, - principal: null, - authenticated: false, - next: false, - }; +@Controller("api/user") +export class UserController { + @Value("mode") + mode: string; + + @Get() + @Authenticated() + indexAction() { + const userInfo = SecurityContext.getAuthentication(); + return jsonFormat({ name: userInfo.name }); } - async support(): Promise { - this.logger.debug("----------------------------------------"); - this.logger.debug("support called"); - const routeMetadataMatcher = await this.routeMetadataMatcher.match(); - if (!routeMetadataMatcher) return false; - let { target, key: method} = routeMetadataMatcher.methodMetadata; - let needAuth = false; - if (target && typeof target.getAccess == 'function') { - needAuth = true; - const access = await target.getAccess(); - if (access.only && access.only.indexOf(method) < 0) { - needAuth = false; + @Get("create") + async createAction() { + if (this.mode && this.mode.indexOf('local') > -1) { + let user: any = { username: "admin", password: "123456", desc: "默认用户"}; + user.password = sha256Encode(user.password); + try { + let saved = await User.save(user); + let result = await User.findOne({ where: { username: user.username }}) as User; + return jsonFormat(result); } - else if (access.except && access.except.indexOf(method) > -1) { - needAuth = false; + catch(err) { + return jsonFormat(null, { message: err.message }); } } - this.logger.debug(`needAuth: ${needAuth}`); - this.logger.debug("----------------------------------------"); - return needAuth; + else { + return jsonFormat(null, { message: "not support" }); + } } } ``` -修改 `src/backend/controllers/post-controller.ts` 文件给`PostController`类添加`getAccess`方法配置权限信息,代码如下: +`正式环境请删除 createAction 方法` + +修改 `src/backend/controllers/index.ts` 导出`user-controller`,新增内容如下: + ```ts - getAccess() { - return { - except: ["index", "show"] - } - } +export * from "./user-controller"; ``` -这里表示`index`、`show`之外的方法都需要认证,也可以用`only`字段来配置只认证哪些方法。 +### 添加认证功能 -创建 `src/backend/auth/index.ts` 导出工具和认证类,内容如下: +创建`src/backend/authentication/user-checker.ts`文件处理用户检测,内容如下: ```ts -export * from "./token-utils"; -export * from "./auth-provider"; +import { Service } from "@malagu/core"; +import { UserChecker, UsernameNotFoundError } from "@malagu/security/lib/node"; + +@Service({id: UserChecker, rebind: true}) +export class UserCheckerImpl implements UserChecker { + + async check(user: any): Promise { + if (!user || !user.username) { + throw new UsernameNotFoundError("User account not found"); + } + } +} ``` -修改 `src/backend/module.ts` 导出认证模块,修改后内容如下: +创建`src/backend/authentication/password-encoder.ts`文件处理密码比较,内容如下: ```ts -import { autoBind } from "@malagu/core"; -import "./controllers"; -import "./auth"; -import { autoBindEntities } from "@malagu/typeorm"; -import * as entities from "./entity"; +import { Service } from "@malagu/core"; +import { PasswordEncoder } from "@malagu/security/lib/node"; +import { sha256Encode } from "../utils/crypto"; -autoBindEntities(entities); -export default autoBind(); -``` +@Service({ id: PasswordEncoder, rebind: true }) +export class PasswordEncoderImpl implements PasswordEncoder { + async encode(content: string): Promise { + return sha256Encode(content); + } -### 添加用户登录和token生成 + async matches(content: string, encoded: string): Promise { + let encodedContent = await this.encode(content); + return encodedContent === encoded; + } +} +``` -创建用户模型 `src/backend/entity/user.ts` 内容如下: +默认 [password-encoder](https://github.com/cellbang/malagu/blob/main/packages/security/src/node/crypto/password/password-encoder.ts) 实现 -```ts -import { BaseEntity, Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, JoinColumn } from "typeorm"; +创建`src/backend/authentication/authentication-success-handler.ts`文件登录成功时返回 token ,内容如下: -@Entity({ name: "users" }) -export class User extends BaseEntity { - @PrimaryGeneratedColumn() - id: number; +```typescript +import { Component, Autowired } from "@malagu/core"; +import { Context } from "@malagu/web/lib/node"; +import { AuthenticationSuccessHandler, Authentication } from "@malagu/security/lib/node"; +import { JwtService } from "@malagu/jwt"; +import { jsonFormat } from "../utils"; - @Column() - username: string; +@Component({ id: AuthenticationSuccessHandler, rebind: true }) +export class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler { + @Autowired(JwtService) + jwtService: JwtService; - @Column() - password: string; + async onAuthenticationSuccess(authentication: Authentication): Promise { + const response = Context.getResponse(); + let token = await this.jwtService.sign({ username: authentication.name }); + response.body = JSON.stringify(jsonFormat({ token })); + } +} +``` - @Column() - desc: string; +默认 [authentication-success-handler](https://github.com/cellbang/malagu/blob/main/packages/security/src/node/authentication/authentication-success-handler.ts) 实现 - @CreateDateColumn({ name: "created_at" }) - createdAt: Date; +创建`src/backend/authentication/security-context-store.ts`处理 header 带 Token 的请求,内容如下: - @UpdateDateColumn({ name: "updated_at" }) - updatedAt: Date; -} -``` +```typescript +import { Autowired, Component, Value } from "@malagu/core"; +import { User } from "@malagu/security"; +import { SecurityContext, SecurityContextStore, SecurityContextStrategy, UserMapper, UserService } from "@malagu/security/lib/node"; +import { Context } from "@malagu/web/lib/node"; +import { JwtService } from "@malagu/jwt"; -修改 `src/backend/entity/index.ts` 导出模型,添加内容如下: +@Component({ id: SecurityContextStore, rebind: true }) +export class SecurityContextStoreImpl implements SecurityContextStore { + @Value("malagu.security") + protected readonly options: any; -```ts -export * from "./user"; -``` + @Autowired(UserService) + protected readonly userService: UserService; -创建用户controller `src/backend/controllers/user-controller.ts` 内容如下: + @Autowired(SecurityContextStrategy) + protected readonly securityContextStrategy: SecurityContextStrategy; + + @Autowired(UserMapper) + protected readonly userMapper: UserMapper; -```ts -import { Autowired } from "@malagu/core"; -import { Controller, Post, Json, Body, Get } from "@malagu/mvc/lib/node"; -import { TokenUtils } from "../auth"; -import { User } from "../entity"; -import { ResponseData } from "../../common"; -import { jsonFormat } from "../utils"; -import { sha256Encode } from "../utils/crypto"; + @Autowired(JwtService) + jwtService: JwtService; -@Controller('api/user') -export class UserController { - @Autowired() - tokenUtils: TokenUtils; - - @Post("login") - async login( - @Body("username") username: string, - @Body("password") password: string - ): Promise> { - if (username && password) { - let passwordHash = sha256Encode(password); - // let passwordHash = password; - let user = await User.findOne({ where: { - username, password: passwordHash - } }); + async load(): Promise { + const request = Context.getRequest(); + const token = (request.get("Token") || "").trim() + const securityContext = await this.securityContextStrategy.create(); + if (token) { + const userInfo: any = await this.jwtService.verify(token); + const user = await this.userService.load(userInfo.username); if (user) { - let token = await this.tokenUtils.getToken(user.id, user.username); - return jsonFormat({ token, id: user.id, username: user.username, desc: user.desc }); + await this.userMapper.map(user); + securityContext.authentication = { + name: user.username, + principal: user, + credentials: "", + policies: user.policies, + authenticated: true + }; } - return jsonFormat(null, "用户名或密码不正确"); + } - return jsonFormat(null, "请输入用户名和密码登录") + return securityContext; + } + + async save(context: SecurityContext): Promise { } } ``` -因为没有注册功能,我们直接写一个方法创建一个用户,在`user-controller.ts`中添加如下代码: +创建 `src/backend/authentication/error-handler.ts` 添加认证错误处理,内容如下: ```ts - @Get("create") - async create(): Promise> { - let user: any = { username: "admin", password: "123456", desc: "默认用户"}; - user.password = sha256Encode(user.password); - let saved = await User.save(user); - let result = await User.findOne({ where: { username: user.username }}) as User; - return jsonFormat(result); +import { Autowired, Component, Value } from "@malagu/core"; +import { Context, ErrorHandler, RedirectStrategy } from "@malagu/web/lib/node"; +import { AUTHENTICATION_ERROR_HANDLER_PRIORITY, AuthenticationError, RequestCache } from "@malagu/security/lib/node"; +import { jsonFormat } from "../utils"; + +@Component(ErrorHandler) +export class AuthenticationErrorHandler implements ErrorHandler { + readonly priority: number = AUTHENTICATION_ERROR_HANDLER_PRIORITY + 100; + + @Value("malagu.security.basic.realm") + protected realm: string; + + @Value("malagu.security.basic.enabled") + protected readonly baseEnabled: boolean; + + @Value("malagu.security.loginPage") + protected loginPage: string; + + @Autowired(RedirectStrategy) + protected readonly redirectStrategy: RedirectStrategy; + + @Autowired(RequestCache) + protected readonly requestCache: RequestCache; + + canHandle(ctx: Context, err: Error): Promise { + let isAuthError = err instanceof AuthenticationError; + return Promise.resolve(isAuthError); + } + async handle(ctx: Context, err: AuthenticationError): Promise { + await this.requestCache.save(); + ctx.response.end(JSON.stringify(jsonFormat(null, err))); } +} ``` -注意,创建用户后请注释掉这个方法 +默认 [AuthenticationErrorHandler](https://github.com/cellbang/malagu/blob/main/packages/security/src/node/error/error-handler.ts) 实现 -修改 `src/backend/controllers/index.ts` 导出`user-controller` +创建 `src/backend/authentication/index.ts` 引入认证文件,内容如下: ```ts -export * from "./user-controller"; +export * from "./password-encoder"; +export * from "./user-checker"; +export * from "./authentication-success-handler"; +export * from "./security-context-store"; +export * from "./error-handler"; ``` -用户登录后会返回 id、token、username 等字段,请求需要授权的接口,在header中添带上token即可 +修改 `src/backend/index.ts` 引入认证文件,内容如下: + +```ts +import { autoBind } from "@malagu/core"; +import "./controllers"; +import { autoBindEntities } from "@malagu/typeorm"; +import * as entities from "./entity"; +import "./authentication"; +import "./services/user-service"; + +autoBindEntities(entities); +export default autoBind(); +``` + +修改`src/backend/controllers/post-controller.ts` 给 Create、Update、Delete 方法添加认证修饰器 Authenticated,内容如下: + +```ts +import { Controller, Get, Post, Patch, Delete, Param, Query, Body } from "@malagu/mvc/lib/node"; +import { Post as PostModel, Tag } from "../entity"; +import { ResponseData } from "../../common"; +import { jsonFormat } from '../utils'; +import { Authenticated } from "@malagu/security/lib/node"; + +@Controller('api/post') +export class PostController { + // 列表 + @Get() + async index(@Query("page") page: number = 1, @Query("size") size: number = 20): Promise> { + let posts: PostModel[] = await PostModel.find({ + take: size, + skip: size * (page - 1), + order: { id: "DESC" }, + relations: ["category"] + }); + return jsonFormat(posts); + } + // 查询 + @Get(":id") + async show(@Param("id") id: number): Promise> { + let post: PostModel = await PostModel.findOne({ + where: { id }, + relations: ["category", "tags"] + }) as PostModel; + return jsonFormat(post); + } + // 创建 + @Post() + @Authenticated() + async create(@Body("json") postData: string): Promise { + let post = JSON.parse(postData); + try { + if (post.tags) { + post.tags = await Tag.buildTags(post.tags); + } + let saved = await PostModel.save(post); + return jsonFormat(saved); + } + catch(e) { + return jsonFormat(e); + } + } + // 更新 + @Patch(":id") + @Authenticated() + async update(@Param("id") id: number, @Body("json") postData: string): Promise { + let saveData = JSON.parse(postData); + try { + let tagText = ""; + if (saveData.tags) { + tagText = saveData.tags; + delete saveData.tags; + } + let saved = await PostModel.update(id, saveData); + let post: PostModel = await PostModel.findOne({ where: { id } }) as PostModel; + post.tags = await Tag.buildTags(tagText); + await post.save(); + return jsonFormat(post); + } + catch(e) { + return jsonFormat(null, e); + } + } + + // 删除 + @Delete(":id") + @Authenticated() + async delete(@Param("id") id: number): Promise { + try { + let post: PostModel = await PostModel.findOne({ where: { id }}) as PostModel; + post.tags = []; + await post.save(); + let deleted = await PostModel.delete(id); + return jsonFormat(deleted); + } + catch(e) { + return jsonFormat(null, e); + } + } +} +``` + +用同样的方法修改 `src/backend/controllers/category-controller.ts` 文件 -### 命令行测试 -我们刚刚已经在 `src/backend/auth/auth-provider.ts` 拦截了 `/api/blog`,此处用这个地址来测试: + +### 命令行测试 ```bash -# 请求认证url此时返回需要认证 -curl -X DELETE http://localhost:3000/api/post/1 # 创建用户 curl http://localhost:3000/api/user/create +# 请求认证url此时返回需要认证 +curl -X DELETE http://localhost:3000/api/user # 登录获取token -curl -X POST -d "username=admin&password=123456" http://localhost:3000/api/user/login -curl -X DELETE -H 'token: ' http://localhost:3000/api/post/1 -``` \ No newline at end of file +curl -H 'Content-Type: application/json' -X POST -d '{"username": "admin", "password": "123456"}' http://localhost:3000/login +# 此时返回 {"code":0,"data":{"token":"xxx"},"message":""} +curl http://localhost:3000/api/user -H "Token: xxxx" +# 此时返回用户信息 +``` diff --git a/docs/learn/blog-category.md b/docs/learn/blog-category.md index cfe75e9..2264213 100644 --- a/docs/learn/blog-category.md +++ b/docs/learn/blog-category.md @@ -12,7 +12,7 @@ lang: zh-CN ```ts import { BaseEntity, Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, - UpdateDateColumn, OneToMany, JoinColumn } from "typeorm"; + UpdateDateColumn } from "typeorm"; @Entity({ name: "categories" }) export class Category extends BaseEntity { @@ -74,7 +74,7 @@ Category分类CURD接口及路径如下: 创建接口controller `src/backend/controllers/category-controller.ts` 内容如下: ```ts -import { Controller, Get, Json, Param, Post, +import { Controller, Get, Param, Post, Query, Body, Patch, Delete } from "@malagu/mvc/lib/node"; import { ResponseData } from "../../common"; import { Category } from "../entity"; @@ -94,8 +94,8 @@ export class CategoryController { } @Get(":id") - async show(@Param("id" ) id: number): Promise> { - let result = await Category.findOne({ id }); + async show(@Param("id" ) id: number): Promise> { + let result = await Category.findOne({ where: { id }}); return jsonFormat(result); } diff --git a/docs/learn/blog-create-project.md b/docs/learn/blog-create-project.md index a811b10..bcc635e 100644 --- a/docs/learn/blog-create-project.md +++ b/docs/learn/blog-create-project.md @@ -13,8 +13,8 @@ lang: zh-CN mkdir malagu-blog cd malagu-blog echo '{}' > package.json -yarn add --dev @malagu/cli yarn add @malagu/core @malagu/mvc +yarn add --dev @malagu/cli @malagu/cli-service ``` #### 编辑package.json @@ -34,45 +34,71 @@ yarn add @malagu/core @malagu/mvc ``` * keywords必须添加且其中必须有`malagu-component`,框架通过此配置来循环查找依赖链 +完整 package.json 内容如下: + +```json +{ + "name": "malagu-blog", + "keywords": ["malagu-component"], + "scripts": { + "start": "malagu serve", + "build": "malagu build" + }, + "dependencies": { + "@malagu/core": "^2.56.0", + "@malagu/mvc": "^2.56.0" + }, + "devDependencies": { + "@malagu/cli": "^2.56.0", + "@malagu/cli-service": "^2.56.0" + } +} +``` + #### 创建示例 在项目根目录创建 `tsconfig.json` 配置typescript编译参数 ```json { - "compilerOptions": { - "target": "esnext", - "module": "commonjs", - "strict": true, - "jsx": "preserve", - "moduleResolution": "node", - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "noImplicitAny": true, - "noEmitOnError": false, - "noImplicitThis": true, - "noUnusedLocals": true, - "strictNullChecks": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "downlevelIteration": true, - "strictPropertyInitialization": false, - "lib": [ - "es6", - "dom" - ], - "sourceMap": true, - "rootDir": "src", - "outDir": "lib", - "baseUrl": "src", - "paths": { - "~/*": ["*"] - } - }, - "include": [ - "src" - ] + "compilerOptions": { + "target": "ES2017", + "module": "commonjs", + "importHelpers": true, + "strict": true, + "jsx": "preserve", + "moduleResolution": "node", + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "noImplicitAny": true, + "noEmitOnError": false, + "noImplicitThis": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "downlevelIteration": true, + "strictPropertyInitialization": false, + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "rootDir": "src", + "outDir": "lib", + "baseUrl": "./src", + "paths": { + "~/*": ["*"], + "@/*": ["frontend/*"] + } + }, + "include": [ + "src" + ], + "ts-node": { + "transpileOnly": true + } } ``` diff --git a/docs/learn/blog-intro.md b/docs/learn/blog-intro.md index 774a5f8..27ab6b0 100644 --- a/docs/learn/blog-intro.md +++ b/docs/learn/blog-intro.md @@ -10,8 +10,6 @@ lang: zh-CN ### 说明 示例使用Malagu创建一个blog的接口服务,演示数据库CURD、登录及鉴权的使用。 -### 项目目录 -- src/frontend 前端 - src/backend 后端 - 数据库使用mariadb diff --git a/docs/learn/blog-post.md b/docs/learn/blog-post.md index 55fc528..368e32d 100644 --- a/docs/learn/blog-post.md +++ b/docs/learn/blog-post.md @@ -87,8 +87,9 @@ export class PostController { } // 查询 @Get(":id") - async show(@Param('id') id: number): Promise> { - let post: PostModel = await PostModel.findOne(id, { + async show(@Param("id") id: number): Promise> { + let post: PostModel = await PostModel.findOne({ + where: { id }, relations: ["category"] }) as PostModel; return jsonFormat(post); diff --git a/docs/learn/blog-tag.md b/docs/learn/blog-tag.md index ba2c874..f0d26c8 100644 --- a/docs/learn/blog-tag.md +++ b/docs/learn/blog-tag.md @@ -54,8 +54,8 @@ export class Tag extends BaseEntity { updatedAt: number; static async buildTags(tagText: string) { - let tagTextArray = tagText.split(',').map( - (item: string) => item.replace(/^\s|\s$/g, '') + let tagTextArray = tagText.split(",").map( + (item: string) => item.replace(/^\s|\s$/g, "") ); let tags = await Tag.find({ select: ["id", "title"], @@ -97,7 +97,7 @@ export * from "./tag"; ```ts import { BaseEntity, Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, - UpdateDateColumn, JoinColumn, ManyToMany, JoinTable, ManyToOne } from "typeorm"; + UpdateDateColumn, JoinColumn, ManyToOne, JoinTable, ManyToMany } from "typeorm"; import { Category } from "./category"; import { Tag } from "./tag"; @@ -146,7 +146,7 @@ export class Post extends BaseEntity { 修改 `src/backend/controller/post-controller.ts` 文件中的show、create、update、delete方法,修改后文件内容如下: ```typescript -import { Controller, Get, Post, Patch, Delete, Json, Param, Query, Body } from "@malagu/mvc/lib/node"; +import { Controller, Get, Post, Patch, Delete, Param, Query, Body } from "@malagu/mvc/lib/node"; import { Post as PostModel, Tag } from "../entity"; import { ResponseData } from "../../common"; import { jsonFormat } from '../utils'; @@ -166,8 +166,9 @@ export class PostController { } // 查询 @Get(":id") - async show(@Param('id') id: number): Promise> { - let post: PostModel = await PostModel.findOne(id, { + async show(@Param("id") id: number): Promise> { + let post: PostModel = await PostModel.findOne({ + where: { id }, relations: ["category", "tags"] }) as PostModel; return jsonFormat(post); @@ -198,7 +199,7 @@ export class PostController { delete saveData.tags; } let saved = await PostModel.update(id, saveData); - let post: PostModel = await PostModel.findOne(id) as PostModel; + let post: PostModel = await PostModel.findOne({ where: { id } }) as PostModel; post.tags = await Tag.buildTags(tagText); await post.save(); return jsonFormat(post); @@ -212,7 +213,7 @@ export class PostController { @Delete(":id") async delete(@Param("id") id: number): Promise { try { - let post: PostModel = await PostModel.findOne(id) as PostModel; + let post: PostModel = await PostModel.findOne({ where: { id }}) as PostModel; post.tags = []; await post.save(); let deleted = await PostModel.delete(id); diff --git a/docs/learn/index.md b/docs/learn/index.md index f807f15..a9068c3 100644 --- a/docs/learn/index.md +++ b/docs/learn/index.md @@ -19,8 +19,8 @@ lang: zh-CN ### Malagu Blog 项目 - 项目介绍 [链接](learn/blog-intro.md) - 创建项目 [链接](learn/blog-create-project.md) -- 连接数据库 [链接](learn/blog-db-conn.md) - 接口格式 [链接](learn/blog-api.md) +- 连接数据库 [链接](learn/blog-db-conn.md) - 分类接口 [链接](learn/blog-category.md) - blog接口 [链接](learn/blog-post.md) - 标签接口 [链接](learn/blog-tag.md)