Code Monkey home page Code Monkey logo

case-ai-art's Introduction

内测case演示 AI魔法精灵

1. 项目需求

https://share.lanhuapp.com/#/invite?sid=lXKFUBEa

https://lanhuapp.com/link/#/invite?sid=lX0tcrqd

2. 项目初始化

2.1 初始化目录

mkdir -p fb-art
cd fb-art
curl -fsSL https://www.fireboom.io/install.sh | bash -s server

2.2 前端代码生成

我们使用 codefun 快速生成前端代码

https://ide.code.fun/projects/63aafc28d109e600121b3472/pages

稍做整理放到 miniapp 目录

或者使用已经整理好的代码

git clone -b miniapp https://github.com/fireboomio/case-ai-art.git miniapp
cd miniapp

3. 需求拆解生成数据库模型

// 用户
model AppUser {
  id             String   @id
  nickname       String
  avatar         String
  phone          String?
  provider       String
  providerId     String
  // 积分
  points         Int      @default(0)
  createdAt      DateTime @default(now())

  // 积分记录
  pointRecords PointRecord[]
  // 点赞记录
  likeRecords  LikeRecord[]

  // 创作
  creations Creation[]

  // 邀请我的人的ID
  inviteById   String?
  // 被谁邀请
  inviteBy     AppUser?  @relation("InviteHistory", fields: [inviteById], references: [id])
  // 我邀请的
  invitedUsers AppUser[] @relation("InviteHistory")
}

// 创作过程
model Creation {
  id        Int      @id @default(autoincrement())
  authorId  String
  author    AppUser  @relation(fields: [authorId], references: [id])
  // 创作时的参数 json 序列化
  args      String
  createdAt DateTime @default(now())

  artWorks ArtWork[]
}

// 作品
model ArtWork {
  id            Int          @id @default(autoincrement())
  creation      Creation     @relation(fields: [creationId], references: [id])
  creationId    Int
  // 作品存储地址
  url           String
  // 是否是草稿
  isDraft       Boolean      @default(true)
  // 是否发布到画廊
  published     Boolean      @default(false)
  // 发布时间
  publishAt     DateTime?
  // 点赞数
  likeCount     Int          @default(0)
  // 点赞记录
  likeRecords   LikeRecord[]
  // 推荐指数
  recommendRate Int          @default(0)
  // 被分享次数
  sharedCount   Int          @default(0)
}

// 点赞记录
model LikeRecord {
  id        Int      @id @default(autoincrement())
  artWorkId Int
  artWork   ArtWork  @relation(fields: [artWorkId], references: [id])
  userId    String
  user      AppUser  @relation(fields: [userId], references: [id])
  createdAt DateTime @default(now())
}

// 积分获得方式
enum PointWays {
  // 分享给好友
  ShareToFriend
  // 分享到群
  ShareToGroup
  // 观看广告
  WatchAD
  // 邀请好友加入
  Invite
  // 画图使用消耗
  Draw
}

// 积分获得记录
model PointRecord {
  id        Int       @id @default(autoincrement())
  userId    String
  user      AppUser   @relation(fields: [userId], references: [id])
  // 获得积分方式
  way       PointWays
  // 获得积分数
  point     Int
  createdAt DateTime  @default(now())
}

4. Fireboom接口创建

4.1 身份鉴权 - OIDC登录

Authing 配置

# 已失效,需要自行修改
clientId 63ae88490f3dff9d1a651cea
secert e8094233545dec4b3b7fc848eb04e306
issuer sail.authing.cn/oidc

业务表用户创建

import { AuthenticationHookRequest } from 'fireboom-wundersdk/server'

export default async function postAuthentication(hook: AuthenticationHookRequest) : Promise<void>{
  if (hook.user?.userId && hook.user.providerId !== 'admin') {
    const { provider, providerId, userId, picture, nickName } = hook.user
    const resp = await hook.internalClient.queries.FindOneAppUser({
      input: {
        id: userId
      }
    })
    const existedUser = resp?.data?.data
    if (!existedUser) {
      await hook.internalClient.mutations.CreateOneAppUser({
        input: {
          data: {
            id: userId,
            provider,
            providerId,
            nickname: nickName,
            avatar: picture,
            phone: '',
            points: 100
          }
        }
      })
    }
  }
}

需要前置2个查询

query FindOneAppUser($id: String!) {
  data: art_findFirstAppUser(where: {id: {equals: $id}}) {
    id
    avatar
    inviteById
    nickname
    phone
    points
    provider
    providerId
    createdAt
  }
}
mutation CreateOneAppUser($data: art_AppUserCreateInput!) @internalOperation {
  data: art_createOneAppUser(
    data: $data
  ) {
    id
  }
}

登录后就需要获取用户信息 fromClaim 功能

query GetUserinfo($userId: String @fromClaim(name: USERID)) {
  data: art_findFirstAppUser(where: {id: {equals: $userId}}) {
    avatar
    id
    nickname
    phone
    points
  }
}

修改用户基本信息接口

mutation UpdateUserBaseinfo( $nickname: String, $avatar: String,$id: String! @fromClaim(name: USERID)) {
  data: art_updateOneAppUser(
    data: {nickname: {set: $nickname}, avatar: {set: $avatar}}
    where: {id: $id}
  ) {
    id
    nickname
    avatar
  }
}

4.2 创作接口 - 自定义数据源能力

翻译

import fetch from '@web-std/fetch'
import { type FastifyBaseLogger } from 'fastify/types/logger'

export async function translate(str: string, logger: FastifyBaseLogger) {
  try {
    const resp = await fetch("https://www2.deepl.com/jsonrpc?method=LMT_handle_jobs", {
      "headers": {
        "accept": "*/*",
        "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,ja;q=0.6",
        "cache-control": "no-cache",
        "content-type": "application/json",
        "pragma": "no-cache",
        "sec-ch-ua": "\"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Google Chrome\";v=\"108\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"macOS\"",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-site",
        "cookie": "dapUid=30d87eb9-d7bc-4a37-85a1-31efc18da077; dl_session=fa.ee3ef862-c610-4c93-a99f-435934073958; userRecommendations=RC-3.2; releaseGroups=340.DF-2477.2.3_399.RC-3.2.8_469.DM-541.2.2_470.DM-542.2.2_471.DM-547.2.2_475.DM-544.2.2_604.DM-595.2.2_605.DM-585.2.3_606.DWFA-165.2.3_612.DM-538.2.2_622.B2B-158.2.1_633.DM-695.2.2_769.B2B-127.2.2_778.DM-705.1.1_863.DM-601.2.2_866.DM-592.2.2_867.DM-684.2.4_972.B2B-138.2.3_975.DM-609.1.2_1084.TG-1207.2.3_1085.TC-432.2.2_1086.TC-104.1.6_1090.DWFA-345.2.2_1092.SEO-44.2.2_1119.B2B-251.2.2_1202.DF-2381.1.2_1205.DAL-176.2.1_1207.DWFA-96.2.2_1219.DAL-136.1.2_1224.DAL-186.2.2_1245.SEO-113.2.2_1330.DF-3073.2.1_220.DF-1925.1.9_1338.AAEXP-724.1.1_1328.DWFA-285.1.1_774.DWFA-212.2.2_1342.AAEXP-728.1.1_1347.AAEXP-733.1.1_1354.AAEXP-740.1.1_1336.AAEXP-722.1.1_1339.AAEXP-725.2.1_1341.AAEXP-727.1.1_1337.AAEXP-723.2.1_1352.AAEXP-738.1.1_1353.AAEXP-739.1.1_1349.AAEXP-735.1.1_1350.AAEXP-736.1.1_1344.AAEXP-730.1.1_1351.AAEXP-737.1.1_1345.AAEXP-731.1.1_1327.DWFA-391.1.1_1343.AAEXP-729.2.1_1348.AAEXP-734.1.1_1340.AAEXP-726.2.1_1346.AAEXP-732.1.1_1335.AAEXP-721.2.1; LMTBID=v2|94536b41-e487-4a2f-9615-29ca9ba751b4|490dd240be839f71bae7772125afb228; userCountry=CN; privacySettings=%7B%22v%22%3A%221%22%2C%22t%22%3A1672790400%2C%22m%22%3A%22LAX%22%2C%22consent%22%3A%5B%22NECESSARY%22%2C%22PERFORMANCE%22%2C%22COMFORT%22%5D%7D; dapVn=2; dapSid=%7B%22sid%22%3A%22939602dd-b714-4278-b4e6-e0153929242c%22%2C%22lastUpdate%22%3A1672799309%7D",
        "Referer": "https://www.deepl.com/",
        "Referrer-Policy": "strict-origin-when-cross-origin"
      },
      "body": `{\"jsonrpc\":\"2.0\",\"method\": \"LMT_handle_jobs\",\"params\":{\"jobs\":[{\"kind\":\"default\",\"sentences\":[{\"text\":\"${str}\",\"id\":0,\"prefix\":\"\"}],\"raw_en_context_before\":[],\"raw_en_context_after\":[],\"preferred_num_beams\":4,\"quality\":\"fast\"}],\"lang\":{\"preference\":{\"weight\":{\"EN\":10},\"default\":\"default\"},\"source_lang_user_selected\":\"ZH\",\"target_lang\":\"EN\"},\"priority\":-1,\"commonJobParams\":{\"regionalVariant\":\"en-US\",\"mode\":\"translate\",\"browserType\":1},\"timestamp\":${+new Date()}},\"id\":${Math.ceil(Math.random() * 99999999)}}`,
      "method": "POST"
    });
    const res = await resp.json()
    if (res.result) {
      return res.result.translations[0].beams[0].sentences[0].text
    }
    return str
  } catch (error) {
    logger.error(error)
    return str
  }
}

AI 生成图片 - 自定义数据源

import { GraphQLObjectType, GraphQLSchema, GraphQLString, GraphQLNonNull, GraphQLID } from 'graphql'
import { FastifyBaseLogger } from 'fastify/types/logger'
import { InternalClient } from 'fireboom-wundersdk/server'
import { Mutations, Queries } from 'generated/fireboom.internal.client';
import { createClient } from 'generated/client'
import { translate } from './translate'

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      _dummy: { type: GraphQLString }
    }
  }),
  mutation: new GraphQLObjectType<{
    args: string,
    userId: string
  }, {
    wundergraph: {
      log: FastifyBaseLogger,
      internalClient: InternalClient<Queries, Mutations>
    }
  }>({
    name: 'Mutation',
    fields: {
      GeneratePictureWithAI: {
        args: {
          args: {
            type: new GraphQLNonNull(GraphQLString)
          },
          userId: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        type: new GraphQLObjectType({
          fields: {
            id: {
              type: GraphQLID,
            },
            url: {
              type: GraphQLString
            }
          },
          name: 'data'
        }),
        async resolve(_, input, ctx) {
          const { log, internalClient } = ctx.wundergraph
          const userId = input.userId
          const json = JSON.parse(input.args)
          if (json.prompt && /[\u4e00-\u9fa5]/.test(json.prompt)) {
            json.prompt = await translate(json.prompt, log)
          }
          try {
            const client = createClient()
            // 消耗积分
            const resp = await client.mutate({
              operationName: 'UsePoints',
              input: {id: userId }
            })
            if (resp.error) {
            	throw resp.error
            }
            // const data = await fetch('https://stablediffusionapi.com/api/v3/dreambooth', {
            //   method: 'post',
            //   headers: {
            //     'Content-Type': 'application/json'
            //   },
            //   body: JSON.stringify({
            //     key: 'EXKt6qfISnSLeFYlLpw9gqHirkCT8hWIpYWllNfuvCUHXBIYk8UoIkUkX8xk',
            //     samples: '1',
            //     num_inference_steps: '30',
            //     guidance_scale: 7.5,
            //     ...json
            //   })
            // }).then(resp => resp.json())
            const data = {
              status: 'success',
              messege: '',
              output: ['https://stable-diffusion-api.s3.amazonaws.com/generations/dd011494-4bd7-4257-9eb6-dc2269b1288c-0.png']
            }
            if (data.status === 'error') {
              throw new Error(data.messege)
            }
            if (data.status === 'success') {
              const resp1 = await internalClient.mutations.CreateOneCreation({ input: {args: input.args, userId }})
              if (!resp1.errors) {
                const resp2 = await internalClient.mutations.CreateOneDraft({ input: {
                  creationId: resp1.data!.data!.id!,
                  url: data.output[0]
                }})
                if (!resp2.errors) {
                  return {
                    url: data.output[0],
                    id: resp2.data!.data!.id!
                  }
                }
              }
            }
            return data
          } catch (error) {
            throw error
          }
        }
      }
    }
  })
})

记录创作过程

mutation CreateOneCreation($args: String!, $userId: String! @fromClaim(name: USERID)) @internalOperation {
  data: art_createOneCreation(
    data: {args: $args, AppUser: {connect: {id: $userId}}}
  ) {
    id
  }
}

根据输入创建草稿

mutation CreateOneDraft($url: String!, $creationId: Int!) @internalOperation {
  data: art_createOneArtWork(
    data: {url: $url, Creation: {connect: {id: $creationId}}, published: false, isDraft: true, likeCount: 0, recommendRate: 0, sharedCount: 0}
  ) {
    id
    url
  }
}

4.3 开始创作 - API快速创建

4.3.1 创作

发起创作

mutation GeneratePictureWithAI($args: String!, $userId: String! @fromClaim(name: USERID)) {
  data: ai_GeneratePictureWithAI(args: $args, userId: $userId) {
    id
    url
  }
}

增积分

mutation IncreasePoints($id: String!, $points: Int!) @internalOperation {
  data: art_updateOneAppUser(data: {points: {increment: $points}}, where: {id: $id}) {
    id
  }
}

减积分

mutation DecreasePoints($id: String!, $points: Int!) @internalOperation {
  data: art_updateOneAppUser(data: {points: {decrement: $points}}, where: {id: $id}) {
    id
  }
}

创作消耗积分

mutation UsePoints($id: String!) {
  data: art_createOnePointRecord(
    data: {
        way: Draw,
        AppUser: {connect: {id: $id}},
        point: -1
      }
  ) {
    id
  }
}

使用customResolve钩子判断积分是否足够

import { HookRequestWithInput } from 'generated/fireboom.hooks'
import { InjectedUsePointsInput, UsePointsResponse } from 'generated/models'

export default async function customResolve(hook: HookRequestWithInput<InjectedUsePointsInput>)
  //: Promise<void | UsePointsResponse>{ // 取消注释以使用严格的返回类型
  : Promise<any>{
  // TODO: 在此处添加代码
  const resp = await hook.internalClient.queries.FindOneAppUser({
    input: {
      id: hook.input?.id
    }
  })
  // 积分不足时,跳过后续流程直接返回
  const points = resp?.data?.data?.points ?? 0
  if (points < 1) {
    return {
      data: {},
      errors: [{
        message: '积分不足'
      }]
    }
  }
  // 修改用户积分
  await hook.internalClient.mutations.DecreasePoints({
    input: {
      points: 1,
      id: hook.input?.id
    }
  })
}

4.3.2 草稿转画夹

mutation MoveDraftToAlbum($id: Int!) {
  data: art_updateOneArtWork(
    data: {isDraft: {set: false}}
    where: {id: $id}
  ) {
    id
  }
}

4.3.3 画夹转画廊

mutation PublishMyArtWork($id: Int!, $date: DateTime @injectCurrentDateTime(format: ISO8601)) {
  data: art_updateOneArtWork(
    data: {publishAt: {set: $date}, published: {set: true}}
    where: {id: $id}
  ) {
    id
    publishAt
    published
  }
}

4.3.4 画廊列表详情 分页

最新

query GetWorksByNewest($skip: Int = 0, $take: Int = 10) {
  data: art_findManyArtWork(
    where: {isDraft: { equals: false}, published: {equals: true}}
    orderBy: [{ publishAt: desc }]
    skip: $skip
    take: $take
  ) {
    id
    likeCount
    sharedCount
    url
  }
}

最热

query GetWorksByHot($skip: Int = 0, $take: Int = 10) {
  data: art_findManyArtWork(
    where: {isDraft: { equals: false}, published: {equals: true}}
    orderBy: [{likeCount: desc}, { sharedCount: desc }, {recommendRate:desc}]
    skip: $skip
    take: $take
  ) {
    id
    likeCount
    sharedCount
    url
  }
}

推荐

query GetWorksByRecommend($skip: Int = 0, $take: Int = 10) {
  data: art_findManyArtWork(
    where: {isDraft: { equals: false}, published: {equals: true}}
    orderBy: [{recommendRate: desc}, {likeCount: desc}]
    skip: $skip
    take: $take
  ) {
    id
    likeCount
    recommendRate
    sharedCount
    url
  }
}

作品详情

query GetArtWorkDetail($id: Int!, $userId: String! @fromClaim(name: USERID)) {
  data: art_findFirstArtWork(where: {id: {equals: $id}}) {
    id
    likeCount
    sharedCount
    url
    args: Creation @transform(get: "args") {
      args
    }
    user: Creation @transform(get: "AppUser") {
      AppUser {
        avatar
        id
        nickname
      }
    }
    isDraft
    published
    likeRecords: LikeRecord(
      where: {AppUser: {is: {id: {equals: $userId}}}, ArtWork: {is: {id: {equals: $id}}}}
    ) {
      id
    }
  }
}

4.3.5 点赞

增加点赞数

mutation IncreaseArtWorkLikeCount($id: Int!) @internalOperation {
  data: art_updateOneArtWork(data: {likeCount: {increment: 1}}, where: {id: $id}) {
    id
  }
}

创建点赞记录

mutation LikeOneArtWork($userId: String! @fromClaim(name: USERID), $artWorkId: Int!) {
  data: art_createOneLikeRecord(
    data: {ArtWork: {connect: {id: $artWorkId}}, AppUser: {connect: {id: $userId}}}
  ) {
    id
  }
}

点赞后增加点赞数钩子

import { HookRequestWithInput, HookRequestWithResponse } from 'generated/fireboom.hooks'
import { InjectedLikeOneArtWorkInput, LikeOneArtWorkResponse } from 'generated/models'

export default async function postResolve(hook: HookRequestWithInput<InjectedLikeOneArtWorkInput> & HookRequestWithResponse<LikeOneArtWorkResponse>) : Promise<void>{
	// 增加like数
   await hook.internalClient.mutations.IncreaseArtWorkLikeCount({ input: {id: hook.input.artWorkId }})
}

减少点赞数

mutation DecreaseArtWorkLikeCount($id: Int!) @internalOperation {
  data: art_updateOneArtWork(data: {likeCount: {decrement: 1}}, where: {id: $id}) {
    id
  }
}

取消点赞记录

mutation UnlikeOneArtWork($userId: String! @fromClaim(name: USERID), $artWorkId: Int!) {
  data: art_deleteManyLikeRecord(
    where: {AppUser: {is: {id: {equals: $userId}}}, ArtWork: {is: {id: {equals: $artWorkId}}}}
  ) {
    count
  }
}

取消点赞钩子

import { HookRequestWithInput, HookRequestWithResponse } from 'generated/fireboom.hooks'
import { InjectedUnlikeOneArtWorkInput, UnlikeOneArtWorkResponse } from 'generated/models'

export default async function postResolve(hook: HookRequestWithInput<InjectedUnlikeOneArtWorkInput> & HookRequestWithResponse<UnlikeOneArtWorkResponse>) : Promise<void>{
	// like - 1
  await hook.internalClient.mutations.DecreaseArtWorkLikeCount({ input: {id: hook.input.artWorkId }})
}

4.3.6 个人画展

  • 我的草稿
query GetMyDrafts($userId: String! @fromClaim(name: USERID), $creationId: Int! @internal, $skip: Int = 0, $take: Int = 10) {
  data: art_findManyCreation(
    orderBy: {createdAt: desc}
    skip: $skip
    take: $take
    where: {AppUser: {is: {id: {equals: $userId}}}, ArtWork: {some: {isDraft: {equals: true}}}}
  ) {
    id @export(as: "creationId")
    count: _count {
      ArtWork
    }
    artWork: _join @transform(get: "art_findFirstArtWork") {
      art_findFirstArtWork(where: {creationId: {equals: $creationId}}) {
        id
        url
      }
    }
  }
}

草稿集

query GetDraftItems($draftId: Int!, $userId: String! @fromClaim(name: USERID)) {
  data: art_findManyArtWork(
    where: {creationId: {equals: $draftId}, isDraft: {equals: true}, Creation: {is: {authorId: {equals: $userId}}}}
  ) {
    id
    url
  }
}
  • 我的画夹
query GetMyAlbum($skip: Int = 0, $take: Int = 10, $userId: String! @fromClaim(name: USERID)) {
  data: art_findManyArtWork(
    where: {isDraft: {equals: false}, Creation: {is: {AppUser: {is: {id: {equals: $userId}}}}}}
    skip: $skip
    take: $take
  ) {
    id
    likeCount
    publishAt
    published
    recommendRate
    sharedCount
    url
  }
}
  • 我的画廊
query GetMyPublicAlbum($skip: Int = 0, $take: Int = 10, $userId: String! @fromClaim(name: USERID)) {
  data: art_findManyArtWork(
    where: {published: {equals: true}, Creation: {is: {AppUser: {is: {id: {equals: $userId}}}}}}
    skip: $skip
    take: $take
  ) {
    id
    likeCount
    sharedCount
    url
  }
}
  • 我的点赞
query GetMyLiked($skip: Int = 0, $take: Int = 10, $userId: String! @fromClaim(name: USERID)) {
  data: art_findManyLikeRecord(
    skip: $skip
    take: $take
    where: {AppUser: {is: {id: {equals: $userId}}}}
  ) {
    artWork: ArtWork {
      id
      url
      sharedCount
      likeCount
    }
  }
}

4.3.7 个人资料

获取我的积分

query GetMyPointRecords($way: [art_PointRecord_way], $userId: String! @fromClaim(name: USERID), $timeStart: DateTime!) {
  data: art_findManyPointRecord( where: {
    way: {in: $way},
    userId: {equals: $userId},
    createdAt: {gte: $timeStart}
  }) {
    id,
    way
  }
}

4.3.8 分享

  • 创建积分记录
mutation CreateOnePointRecord($way:art_PointRecord_way!, $point: Int! = 0, $userId: String!) {
  data: art_createOnePointRecord(
    data: {
      way: $way,
      AppUser: {connect: {id: $userId}},
      point: $point
    }
  ) {
    id
  }
}

使用 mutatingPreResolve钩子设置每种积分方式的分值

import { HookRequestWithInput } from 'generated/fireboom.hooks'
import { InjectedCreateOnePointRecordInput } from 'generated/models'

export default async function mutatingPreResolve(hook: HookRequestWithInput<InjectedCreateOnePointRecordInput>) : Promise<InjectedCreateOnePointRecordInput>{
  switch (hook.input.way) {
    case 'ShareToFriend':
      hook.input.point = 2
      break
    case 'ShareToGroup':
      hook.input.point = 2
      break
    case 'WatchAD':
      hook.input.point = 4
      break
    case 'Invite':
      hook.input.point = 10
      break
    default:
      hook.input.point = 0
  }
  return hook.input
}

使用customResolve钩子判断今日积分增加次数是否用完

import { HookRequestWithInput } from 'generated/fireboom.hooks'
import { InjectedCreateOnePointRecordInput, CreateOnePointRecordResponse } from 'generated/models'

export default async function customResolve(hook: HookRequestWithInput<InjectedCreateOnePointRecordInput>)
  : Promise<void | CreateOnePointRecordResponse> {
  // 根据用户查询今日分享次数,超出直接200返回
  const now = new Date(Math.floor(Date.now() / 86400000) * 86400000)
  const resp = await hook.internalClient.queries.GetMyPointRecords({
    input: {
      way: [hook.input.way],
      userId: hook.input.userId,
      timeStart: now.toISOString()
    }
  })
  const limitTimes = {
    ShareToFriend: 2,
    ShareToGroup: 2,
    WatchAD: 10
  }[hook.input.way] ?? 0
  const currentTimes = resp.data?.data?.length??0
  if ((currentTimes >= limitTimes) && limitTimes) {
    return {
      data:{}
    }
  }
}

使用postResolve钩子创建完记录后增加用户积分

import { HookRequestWithInput, HookRequestWithResponse } from 'generated/fireboom.hooks'
import { InjectedCreateOnePointRecordInput, CreateOnePointRecordResponse } from 'generated/models'

export default async function postResolve(hook: HookRequestWithInput<InjectedCreateOnePointRecordInput> & HookRequestWithResponse<CreateOnePointRecordResponse>): Promise<void> {
  await hook.internalClient.mutations.IncreasePoints({
    input: {
      id: hook.input.userId,
      points: hook.input.point
    }
  })
}
  • 增加积分接口
mutation IncreasePoints($id: String!, $points: Int!) @internalOperation {
  data: art_updateOneAppUser(data: {points: {increment: $points}}, where: {id: $id}) {
    id
  }
}
  • 增加作品分享次数
mutation IncreaseArtWorkShareCount($id: Int!) @internalOperation {
  data: art_updateOneArtWork(
    data: {sharedCount: {increment: 1}}
    where: {id: $id}
  ) {
    id
  }
}
  • 记录我的邀请者接口
mutation RecoredMyInviter($inviterId: String!, $userId: String! @fromClaim(name: USERID)) {
  data: art_updateOneAppUser(
    data: {AppUser: {connect: {id: $inviterId}}}
    where: {id: $userId}
  ) {
    id
  }
}

使用customResolve钩子在记录前判断邀请人状态

import { createClient } from 'generated/client'
import {HookRequestWithInput} from 'generated/fireboom.hooks'
import {InjectedRecordMyInviterInput, RecordMyInviterResponse} from 'generated/models'
export default async function customResolve(hook: HookRequestWithInput<InjectedRecordMyInviterInput>)
  //: Promise<void | RecordMyInviterResponse>{ // 取消注释以使用严格的返回类型
  : Promise<any> {
  // TODO: 在此处添加代码
  const currentUser = await hook.internalClient.queries.GetOneAppUser({
    input: {
      id: hook.input.userId
    }
  })
  if (!currentUser) {
    return {
      data: {}, errors: [{
        message: '用户不存在'
      }]
    }
  }
  if (currentUser.data?.data?.AppUser?.id) {
    return {
      data: {}, errors: [{
        message: '已有邀请人'
      }]
    }
  }

  const client = createClient()
  // 增加魔法值
  await client.mutate({
    operationName: 'CreateOnePointRecord',
    input: {
      way:'Invite',
      point: 0,
      userId: hook.input.userId
    }
  })
  await client.mutate({
    operationName: 'CreateOnePointRecord',
    input: {
      way:'Invite',
      point: 0,
      userId: hook.input.inviterId
    }
  })
}

使用批量新建创建接口GetOneAppUser,勾选AppUser的选项(如下图)

最后,需要将sdk/miniapp目录下的文件进行复制,替换到小程序的sdk目录下

4.4 管理后台接口

4.4.1 权限控制

在飞布控制台设置页面——安全——重定向URL中,增加 [ localhost:4321/oidc/callback ]

Authing 配置

# 已失效,需要自省替换
clientId 63ae88490f3dff9d1a651cea
secert e8094233545dec4b3b7fc848eb04e306
issuer sail.authing.cn/oidc

4.4.2 用户管理

AppUser表,批量创建 —— 详情接口 & 分页查询接口

4.4.3 创作列表

ArtWork表,批量创建 —— 删除接口 & 分页查询接口 (需要勾选下面的 Creation-AppUser 关联字段)

4.4.4 统计数据

  • 获取总用户和总作品
query GetCount {
  user: art_aggregateAppUser {
    _count {
      _all
    }
  }
  creation: art_aggregateCreation {
    _count {
      _all
    }
  }
}
  • 获取今日作品数量
query GetTodayCount($date: DateTime!) {
  creation: art_aggregateCreation(where: {createdAt: {gte: $date}}) {
    _count {
      _all
    }
  }
}
  • 每日作品数量——查询接口

每日作品数量——自定义数据源

import {GraphQLFloat, GraphQLObjectType, GraphQLSchema, GraphQLString, GraphQLList } from 'graphql';
import { prisma } from 'generated/prisma'

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      DailyCreation: {
            type: new GraphQLList(new GraphQLObjectType({
                name: 'DailyCreation',
                fields: {
                  date: {
                        type: GraphQLString,
                    },
                    total: {
                        type: GraphQLFloat
                    }
                },
            })),
            resolve() {
                return prisma.art.queryRaw(`SELECT DATE_FORMAT(createdAt,'%Y-%m-%d') date, COUNT(1) total from Creation GROUP BY date`, {});
            },
        },
    },
  }),
})
query QueryStatistic {
  data: statistics_DailyCreation {
    date
    total
  }
}

5. 上线部署

5.1 服务器环境准备

  • 钩子服务 node
  • 项目推送
rsync -avr  --exclude 'node_modules' --exclude 'fireboom' ./* fb-demo:/app/fb-art

rds mysql://fb:[email protected]/fb_test98

本地 mysql://root:fb-art@localhost:3306/art

上述地址仅做演示,请根据实际环境修改

5.2 Fireboom 生产环境启动

cd server/custom-ts
pm2 start npm -- start
cd ../
./fireboom dev
# 修改系统设置,然后关闭服务
./fireboom build
./fireboom start

访问 http://139.9.175.133:9123

case-ai-art's People

Contributors

erguotou520 avatar

Stargazers

 avatar HeisenBerg? avatar  avatar Zhou Mi Li avatar Anson avatar  avatar

case-ai-art's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.