豪翔天下

Change My World by Program

0%

  • Roadmap: 真的有好多还没开发完成的实用功能

安装与配置

常用命令

1
export PORT=3000 && npm run develop	# 更改启动端口

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// config/server.js, Server相关配置
module.exports = ({ env }) => ({
host: env('HOST', process.env.HOST),
port: env.int('PORT', process.env.PORT),
admin: {
url: 'admin', // 可以修改默认的后台路径,但是不能设置为/ root路径,关注https://github.com/strapi/strapi/issues/9302
auth: {
secret: env('ADMIN_JWT_SECRET', process.env.ADMIN_JWT_SECRET),
},
},
});

// config/api.js, API相关配置
module.exports = ({ env }) => ({
responses: {
privateAttributes: ['_v', 'id', 'created_at'],
},
rest: {
defaultLimit: 100,
maxLimit: 250,
},
});

// config/plugins.js, 插件相关配置
module.exports = {
graphql: { // graphql插件相关配置
endpoint: '/graphql',
shadowCRUD: true,
playgroundAlways: false,
depthLimit: 7,
amountLimit: 100,
apolloServer: {
tracing: false,
}
}
}

// extensions/users-permissions/config/security.json, 修改jwt token的配置
{
"jwt": {
"expiresIn": "3650d"
}
}

// .env, 有几个默认的Strapi相关的配置,例如是否开启更新提示等,默认都是关闭了的

自定义Role及权限

  • 免费版不能在web端直接进行配置,不过可以修改数据库,看一下数据库的表结构就能很好修改了,但问题是每次修改了types结构以后需要重新分配role的权限,否则权限会丢失,可以使用以下代码在重新启动应用时自动更新权限,但还是有个问题,如果应用不完全重启,仍然不会更新,因为应用没有完全重启的话admin的也不会更新的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // config/functions/bootstrap.js
    "use strict";

    const lodash = require('lodash');

    // Prevent permissions loss
    const setDefaultRolePermissions = async () => {
    const adminRole = await strapi.query("role", "admin").findOne({id: 1});
    const customRoles = await strapi.query("role", "admin").find({id_gt: 3});

    customRoles.forEach(customRole => {
    console.log(`Check ${customRole.name} permissions`);
    customRole.permissions.forEach(permission => {
    // compare permission fields with adminRole
    const realPermission = adminRole.permissions.find(p => p.action === permission.action && p.subject === permission.subject)
    if (permission.properties.fields && permission.action.includes('plugins::content-manager.explorer') &&(permission.properties.fields.length !== realPermission.properties.fields.length ||
    permission.properties.fields.length !== lodash.uniq(permission.properties.fields.concat( realPermission.properties.fields)).length)) {
    strapi.query("permission", "admin").update({id: permission.id}, {properties: realPermission.properties})
    }
    })
    })
    };


    module.exports = async () => {
    setTimeout(setDefaultRolePermissions, 3000); // 使用timeout是因为刚重启的时候数据库还没根据新的结构更新
    };

升级步骤

升级非常简单,直接在composer.json里面全局替换,例如将3.5.2全部替换成3.6.1,在执行以下命令即可

1
2
npm install
rm -rf .cache && rm -rf build/

修改admin系统文件

  • 只需要参照node_modules/strapi-admin/中的目录结构,在根目录新建admin文件夹与其保持一致,任何想要覆盖的都可以放在这里
  • 替换logo,可以在admin/src/assets/images中放置

其他特性

阅读全文 »

  • 代码风格交给eslint,其他的不要进行强制规定

在实际项目中,最好配合以下几个工具,让整个项目的代码风格统一

  • eslint:代码格式检查工具
  • lint-staged:对git的暂存文件进行lint检查
  • husky:git钩子,能够很方便地在项目中配置git的hook操作,通过它我们能够实现在代码提交时检查并尝试修复一些代码风格问题

安装与初始化

  1. 直接这样一起安装几个工具: npm install --save-dev husky lint-staged eslint
  2. 可以执行./node_modules/.bin/eslint --init对当前目录的项目进行eslint初始化,能够通过交互式的命令进行配置,完成后会在当前目录创建配置文件.eslintrc.js
阅读全文 »

  • ReactSSR框架
  • 渲染有三种方式: BSR(客户端渲染,Browser Side Render),SSG(静态页面生成,Static Site Generation),SSR(服务端渲染,Server Side Render)
  • 需要注意的是如果页面中有用js控制的部分(例如条件渲染),在SSR的时候不会直接渲染成DOM元素,虽然也能导出成静态HTML,但是仍然是前端js来控制的

基础配置

1
2
3
4
5
6
7
8
9
10
11
npx create-next-app@latest	# 初始化项目
npx create-next-app@latest --typescript # 使用typescript初始化

# 如果要使用tailwindcss,不推荐tailwindcss官方的脚手架,-e with-tailwindcss安装完成后typescript和eslint都没有,还是自己集成吧,也挺简单的。
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
# 根据文档https://tailwindcss.com/docs/guides/nextjs,替换tailwindcss中的purge和globals.css

npm install sass # 增加对sass、scss的支持

next -p 3001 # 指定启动端口

next.config.js

  • 每次修改必须重启应用
  • .env中设置的环境变量默认不会在后端渲染中被前端看到,但是如果以NEXT_PUBLIC_开头的环境变量,是能看到也能被前端直接使用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module.exports = {
env: { // 设置环境变量,设置后可以在jsx中直接用{process.env.customKey}获取值,环境变量还能设置在.env中
customKey: 'my-value',
},
images: {
domains: ['example.com'] // 定义哪些image允许展示
loader: 'imgix', // 如果图片存储在第三方,需要加这个设置
path: '', // 如果图片存储在第三方且使用相对路径需要添加域名在这里,但是如果是绝对路径,留空字符串即可
},
async redirects() { // 设置重定向规则
return [{
source: '/home',
destination: '/',
permanent: true // true表示永久重定向302,false表示暂时的,301
}]
},
async rewrites() { // 设置rewrites规则,将原来的路径进行重写以此来屏蔽实际的路径,浏览器url不会变化,但是该规则不适用于导出为纯静态的站点,如果是纯静态站点可能需要nginx等来配合
return [{
source: '/',
source: '/old-blog/:slug', // 也可以匹配参数
source: '/blog/:slug*', // 也可以模糊匹配
source: '/post/:slug(\\d{1,})', // 正则匹配
destination: '/signin',
permanent: true, // 重定向是否是permanent
has: [{
type: 'header', // 可以匹配header中是否有某个key
key: 'x-redirect-me'
}, {
type: 'query', // 可以匹配query参数
key: 'page',
value: 'home'
}, {
type: 'cookie', // 匹配cookie
key: 'authorized',
value: 'true'
}, {
type: 'header',
key: 'x-authorized',
value: '(?<authorized>yes|true)', // 可以提取value作为destination的值destination: '/home?authorized=:authorized',
}]
}]
}
}

路由

路由定义

  • 路由定义默认是根据pages文件夹下的文件名来的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pages/blog/first-post.js → /blog/first-post
pages/dashboard/settings/username.js → /dashboard/settings/username

pages/blog/[slug].js → /blog/:slug (/blog/hello-world)
pages/[username]/settings.js → /:username/settings (/foo/settings)
pages/post/[...all].js → /post/* (/post/2020/id/title)

// 可以通过context获取路由参数
export async function getServerSideProps (context: NextPageContext): Promise<any> {
console.log(context.query.all) // all得到的是一个数组,按每一级来
return {
props: {}
}
}

路由常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useRouter } from 'next/router';

const router = useRouter() // 等同于window.location
router.query.search // 获取参数
router.push('/signin'); // 路由跳转
router.replace('/signin'); // 路由跳转
router.push({pathname: '/post/[pid]', query: {pid: post.id}}) // 指定参数
router.pathname; // 获取当前的pathname,例如/signin
router.back(); // 返回上一页,即window.history.back()
router.reload(); // 刷新页面,即window.location.reload()
router.locale // 当前的locale
router.locales // 所有的locales
router.defaultLocale // 默认的locale

路由事件

包括:routeChangeStart、routeChangeComplete、routeChangeError、beforeHistoryChange、hashChangeStart、hashChangeComplete

页面组件

  • 通过变量渲染html,需要用<div dangerouslySetInnerHTML={{__html = '
    '}}

Layouts

  • 全局定义页面的layout

  • 所有页面都相同的layout可以这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // pages/_app.js
    import Layout from '../components/layout'

    export default function MyApp({ Component, pageProps }) {
    return (
    <Layout>
    <Component {...pageProps} />
    </Layout>
    )
    }
  • 如果不是所有页面的layout都相同,可以参考官方文档

  • 可以在Head里面插入全局的js,例如google analytics代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <Head>
    <script
    dangerouslySetInnerHTML={{
    __html: `[google analytics tracking code here]`
    }}
    />
    <link
    href="https://fonts.googleapis.com/css2?family=Inter&display=optional"
    rel="stylesheet"
    /> // 字体优化,能够在编译阶段就优化字体,这样在打开页面不会因为要获取字体文件而闪一下
    </Head>

Image

  • 需要在next.config.js中配置图片域名images.domains
  • 可以设置width、height、quality、priority、responsive自动修改图片显示大小
  • 但是毕竟是后端js程序在进行转换,不如直接使用cloudinary这样的服务速度快功能多
  • 如果图片存储在第三方需要添加配置images: loader: 'imgix'
  • width和height必填,除非layout=fill
1
2
3
4
5
6
7
8
9
import Image from 'next/image'

<Image
loader={myLoader}
src="me.png" // 要么是本地静态文件,要么用绝对路径,不能用//开头的路径
alt="Picture of the author"
width={500}
height={500}
/>

使用svg作为component

  • 和其他框架一样,都是用npm install @svgr/webpack --save-dev
  • 需要做如下配置:
1
2
3
4
5
6
7
8
9
10
11
// next.config.js 添加如下配置
module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"]
});

return config;
}
};
1
2
3
4
5
6
7
<Link href=''>
<a>title</a>
</Link>

<Link href=''>
<a><Image ... /></a> // 如果link下需要包含image,不能直接image,外面得加一层a标签
</Link>

后端渲染SSR

  • 在组件加载前就从接口获取数据,才能实现后端渲染,而不是前端去调用API
  • 需要注意的是通过服务端获取的props,必须直接传递到html中去,不要用useEffect等去传递给另外一个变量,那样就不会直接渲染到HTML中去了,浏览网页源代码发现他们只是在一个变量上,对SEO十分不友好
  • getServerSidePropsgetInitialProps都无法用在404页面上,如果是404页面只能在componentDidMount或者useEffect(() => {}, [])里面去请求获取数据了,官方说明
  • 判断当前是否是后端渲染有一个简单的办法,那就是typeof window === undefined
阅读全文 »

基本命令

  • npm install -g cordova ios-deploy
  • 如果要运行在xcode需要用xcode打开项目名.xcworkspace文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建工程
cordova create 目录名 com.haofly.mobile 应用名

# 平台管理
cordova platform add ios
cordova platform add android
cordova platform list # 列出当前添加了的平台
cordova platform rm android

# 编译运行,如果更新了www目录,可以直接重新build即可
cordova build # 编译所有平台
cordova build android # 仅编译指定平台,这条命令相当于cordova prepare android && codova compile android
cordova emulate ios # 启动模拟器
cordova run browser # 在指定平台运行

# 插件管理
cordova prepare # 复制assets中的文件到平台中去
cordova requirements # 检查依赖关系
cordova plugin search facebook # 搜索插件
cordova plugin ls # 列出当前已安装的插件
cordova plugin rm cordova-plugin-facebook4 # 移除插件
cordova plugin add cordova-plugin-facebook4 # 添加插件
corodva plugin add https://github.com/myproject#branch_name # 从github安装指定分支的cordova插件
阅读全文 »

后台配置

  • 配置是否需要用户登陆Settings->Checkout ->Customer accounts,如果用户登陆是可选的,那么可以做一个Continue as Guest按钮

Shopify前端开发

  • shopify的搜索功能无法做更多自定义,但是他们的搜索匹配方式有点奇怪,很多时候不能搜到我们想要的东西,不用去想了,没有解决方案

命令行工具

1
2
theme download	# 将shpify那边的主题文件同步到本地
theme watch # 监听本地文件改动,如果有改动会自动上传上去

Liquid手册

  • 因为是后端渲染的,不要尝试在liquid里面获取query参数(js除外),但是shopify的语法里有些方法可以获取到product/variant等参数的

模板结构

  • templates/index.liquid就是首页了
阅读全文 »

安装

  • Releases Note,他们的源代码没有在github上

  • 因为我目前使用的是付费的,所以是把Nova目录直接放到了根目录,升级的时候只需要在后台重新下载一个包覆盖即可

  • 安装步骤:和普通的包安装方式不一样,因为需要购买license,商用$199/project

资源

  • php artisan nova:resource Post生成资源管理类,一般和Model名一样即可
阅读全文 »

  • 每月1万分钟的免费额度,可以说相当不错的了

生成token

  • 无论是主持人还是用户还是录制UID进入频道前都需要先生成一个token
  • token的生成方式点击标题即可,里面有各种语言生成token的方式
  • 生成token必须提供一个UID,得自己找办法和数据库中原有的用户关联
  • token相关错误码

服务端

  • 服务端的Restful API都有频率限制且阈值并不高,这是官方提供的超出频率限制解决方法,可以参考一下,之前我以为hook能够帮助我减少很多请求,但发现并不如我的预期,主要是实效和顺序性的问题
阅读全文 »

最近在学习AWS的Redshift,它是基于PostgreSQL的,顺便学习下。

安装PostgreSQL

1
2
3
4
5
6
sudo apt-get install -y postgresql	# 安装后默认就启动了的
sudo apt-get install -y postgresql-client postgresql-client-common # 安装命令行客户端

# 完整卸载postgresql
sudo systemctl stop postgresql # 注意如果已经启动了postgresql必须在重装前将其停止,否则5432端口被占用后重新安装的实例默认会是5433端口
sudo apt-get --purge remove postgresql\* -y && sudo rm -rf /etc/postgresql-common/ && sudo rm -rf /etc/postgresql/

配置

  • 默认端口为5432,默认用户名为postgres
  • Mysql安装postgres命令行工具pg_dump等: brew install libpq
  • 配置文件默认在: /etc/postgresql/{version}/main/postgresql.conf,配置文件修改后需要重启sudo systemctl restart postgresql
    • 监听0.0.0.0,需要修改listen_addresses = 'localhost'为listen_addresses = '*'

系统表

  • pg_class: 记录数据库中的表,索引及视图
  • pg_namespace: 记录数据库中的名字空间
  • pg_attribute: 记录数据库中表的字段的详细信息。(attname字段名字,atttypid字段类型id,attlen字段长度,attnotnull)
  • pg_type: 记录数据库中所有的数据类型
  • pg_description: 记录数据库中对象的注释(表以及字段)。(objoid对象ID,objsubid字段号,description描述)
阅读全文 »

官方文档

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var swiper = new Swiper('.swiper-container', {
slidesPerView: 'auto', // 每一页的slide数量自动确定,这样可以做到不用整数个显示,也不会导致左右空白
spaceBetween: 10, // 每两个slide之间的间隔
centeredSlides: false, // 不用从居中开始,否则左边是空白的
pagination: {
el: '.swiper-pagination', // 分页元素位置
clickable: true,
},
direction: 'vertical', // horizontal滚动方向
width: 500, // 设置slide的高度和宽度,单位职能是px
height: 500,
breakpoints: { // 可以自定义不同的breakpoints里面的不同参数
1280: { // 屏幕宽度>=1280时候的breakpoings
width: 1000,
height: 1000,
direction: 'vertical', // 任何参数都可以breakpoint
}
}
scrollbar: {
el: '.swiper-scrollbar', // scrollbar的位置
clickable: true,
hide: true
},
navigation: { // 指定翻页按钮
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: { // 使用对应的事件
afterInit: function () {}
activeIndexChange: function () {}, // 这个事件触发时active图片还没改变
slideChange: function() {}, // 这个事件触发时图片就改变了
slideChangeTransitionEnd: function() {}, // 感觉这个方法才是真正的改变完成了
}
});
阅读全文 »

安装

1
pip install django-celery-beat

配置

  1. settings.py中添加如下配置
1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = (
...,
'django_celery_beat',
)

CELERY_BROKER_URL = 'sqla+sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3')
CELERY_IMPORTS = ['schedules'] # 指定需要在哪些目录扫描定时任务,如果不添加则会出现 Received unregistered task of type 错误
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
CELERY_RESULT_BACKEND = 'django-db' # 这是下面的django-celery-results保存结果使用的
  1. 使用命令python manage.py migrate创建数据库迁移,会生成这几张表:
1
2
3
django_celery_beat.models.PeriodicTask # 周期性任务
django_celery_beat.models.IntervalSchedule # 间隔性任务
django_celery_beat.models.CrontabSchedule # 定时任务
  1. 创建启动文件,在manage.py所在目录创建一个启动文件celery.py:
1
2
3
4
5
6
7
8
9
10
import os

from celery import Celery

from life_console import settings

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
celery_app = Celery("项目名", broker=settings.CELERY_BROKER_URL)
celery_app.config_from_object("django.conf:settings", namespace="CELERY")
celery_app.autodiscover_tasks()
阅读全文 »