国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

react + koa2打造點餐系統(tǒng)

enrecul101 / 2661人閱讀

摘要:后來本人覺得太麻煩了,便抽了點時間去開發(fā)一個專為都城點餐的端系統(tǒng),主要為了方便自己。通過解析配置,通過打包生成資源,然后前端服務(wù)將資源引入到中達(dá)到渲染效果。搭建自己的服務(wù)器也有好處,可以解決跨域問題,或者通過作為中間層請求后臺服務(wù)器。

前言

第一次寫文章,用作個人記錄和分享交流,不好之處還請諒解。因本人喜愛吃都城(健康),在公司叫的外賣都是都城,然后越來越多人跟著我點,而且每次都是我去統(tǒng)計人數(shù),每個人點餐詳情,我都是通過企業(yè)微信最后匯總到txt文本上再去打電話叫外賣,最后跟都城工作人員確認(rèn)防止多點少點(真是一把辛酸淚,誰讓我這么偉大呢?)。后來本人覺得太麻煩了,便抽了點時間去開發(fā)一個專為都城點餐的PC端系統(tǒng),主要為了方便自己。

涉及功能點

登錄注冊修改賬號密碼

查看訂餐列表

點餐功能

簡單聊天功能

評論功能

點贊功能

刪除評論功能

查看當(dāng)天所有訂單詳情功能

項目圖片
首頁

菜單列表頁

聊天頁
項目地址

github: https://github.com/FEA-Dven/d...

線上: https://dywsweb.com/food/login (賬號:admin, 密碼:123)

技術(shù)棧

前端: react + antd

后端: nodejs + koa2

目錄介紹
|---ducheng                                 最外層項目目錄 
    |---fontend                             前端項目
        |---app                             主要項目代碼 
            |---api                         請求api
            |---assets                      資源管理
            |---libs                        包含公用函數(shù)
            |---model                       redux狀態(tài)管理  
            |---router                      前端路由
            |---style                       前端樣式
            |---views                       前端頁面組件
                |---chat                    聊天頁
                |---component               前端組件
                |---index                   訂餐系統(tǒng)首頁
                |---login                   登錄頁
                |---App.js              
            |---config.js                   前端域名配置
            |---main.js                     項目主函數(shù)
        |---fontserver                      前端服務(wù)
            |---config                      前端服務(wù)配置
            |---controller                  前端服務(wù)控制層
            |---router                      前端服務(wù)路由
            |---utils                       前端服務(wù)公用庫
            |---views                       前端服務(wù)渲染模板
            |---app.js                      前端服務(wù)主函數(shù)
        |---node_modules        
        |---.babelrc            
        |---.gitignore      
        |---gulpfile.js          
        |---package.json
        |---pm2.prod.json                   構(gòu)建線上的前端服務(wù)pm2配置
        |---README.md      
        |---webpack.config.js               構(gòu)建配置
    |---backend                             后臺項目
        |---app                             主要項目代碼 
            |---controller                  控制層
            |---model                       模型層(操作數(shù)據(jù)庫)
            |---service                     服務(wù)層
            |---route                       路由
            |---validation                  參數(shù)校驗
        |---config                          服務(wù)配置參數(shù)
        |---library                         定義類庫
        |---logs                            存放日志
        |---middleware                      中間件
        |---node_modules                 
        |---sql                             數(shù)據(jù)庫sql語句在這里
        |---util                            公共函數(shù)庫
        |---app.js                          項目主函數(shù)
        |---package.json
前端項目小結(jié) 1、搭建自己的服務(wù)

項目沒有用到腳手架,而是自己搭建前端服務(wù)器,也是koa2框架。通過koa2解析webpack配置,通過webpack打包生成資源,然后前端服務(wù)將資源引入到xtpl中達(dá)到渲染效果。

搭建自己的服務(wù)器也有好處,可以解決跨域問題,或者通過node作為中間層請求后臺服務(wù)器。嗯,本項目這些好處都沒有用到。

if (isDev) {
    // koawebpack模快
    let koaWebpack = require("koa-webpack-middleware")
    let devMiddleware = koaWebpack.devMiddleware
    let hotMiddleware = koaWebpack.hotMiddleware
    let clientCompiler = require("webpack")(webpackConfig)
    app.use(devMiddleware(clientCompiler, {
        stats: {
            colors: true
        },
        publicPath: webpackConfig.output.publicPath,
    }))
    app.use(hotMiddleware(clientCompiler))
}

app.use(async function(ctx, next) { //設(shè)置環(huán)境和打包資源路徑
    if (isDev) {
        let assets ={}
        const publicPath = webpackConfig.output.publicPath
        assets.food = { js : publicPath + `food.js` }
        ctx.assets = assets
    } else {
        ctx.assets = require("../build/assets.json")
    }
    await next()
})
2、引入HappyPack快速打包
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length / 2 }); //根據(jù)CPU線程數(shù)創(chuàng)建線程池
plugins: [
    new HappyPack({
      id: "happyBabel",
      loaders: [{
        loader: "babel-loader?cacheDirectory=true",
      }],
      threadPool: happyThreadPool,
      verbose: true,
    }),
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify(env),
    })
].concat(isDev?[ 
    new webpack.HotModuleReplacementPlugin(),
]:[
    new AssetsPlugin({filename: "./build/assets.json"}),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new MiniCssExtractPlugin({
        filename: "[name].[hash:8].css",
        chunkFilename: "[id].[hash:8].css"
    }),
]),
3、封裝路由組件用作權(quán)限校驗
function requireAuthentication(Component) {
    // 組件有已登陸的模塊 直接返回 (防止從新渲染)
    if (Component.AuthenticatedComponent) {
        return Component.AuthenticatedComponent
    }
    // 創(chuàng)建驗證組件
    class AuthenticatedComponent extends React.Component {
        state = {
            login: true,
        }
        componentWillMount() {
            this.checkAuth();
        }
        componentWillReceiveProps(nextProps) {
            this.checkAuth();
        }
        checkAuth() {
            // 未登陸重定向到登陸頁面
            let login = UTIL.shouldRedirectToLogin();
            if (login) {
                window.location.href = "/food/login";
                return;
            }
            this.setState({ login: !login });
        }
        render() {
            if (this.state.login) {
                return 
            }
            return ""
        }
    }
    return AuthenticatedComponent
}
思路:這個權(quán)限校驗的組件將其他組件設(shè)為參數(shù)傳入,當(dāng)加載頁面的時候,權(quán)限校驗組件會先進(jìn)行權(quán)限校驗,當(dāng)瀏覽器沒有cookie指定的參數(shù)時,直接返回登錄頁

    
        
            
            
            
            
        
    
4、通過webpack設(shè)置主題色
{
    test: /.less|.css$/,
    use: [
        {
            loader: isDev ? "style-loader" : MiniCssExtractPlugin.loader
        }, {
            loader: "css-loader"
        }, {
            loader: "less-loader",
            options: {
                javascriptEnabled: true,
                modifyVars: {
                    "primary-color": "#0089ce",
                    "link-color": "#0089ce"
                },
            }
        }
    ]
}
5、其他

網(wǎng)頁保存cookie的用戶id,請求時放入header帶去服務(wù)器,識別哪個用戶操作

每個頁面都是零散的組件拼起來,所以組件之間的數(shù)據(jù)要處理好

后端項目小結(jié) 框架設(shè)計
主要分為 controller層, service層, model層。

controller層作用于接收參數(shù),然后做參數(shù)校驗,再將參數(shù)傳入到service層做業(yè)務(wù)邏輯

service層做業(yè)務(wù)邏輯

model層調(diào)用數(shù)據(jù)庫

數(shù)據(jù)庫詳情

數(shù)據(jù)庫用的是mysql

查詢數(shù)據(jù)庫用的是SQL查詢構(gòu)建器Knex

this.readMysql = new Knex({
    client: "mysql",
    debug: dbConfig.plat_read_mysql.debug,
    connection: {
        host: dbConfig.plat_read_mysql.host,
        user: dbConfig.plat_read_mysql.user,
        password: dbConfig.plat_read_mysql.password,
        database: dbConfig.plat_read_mysql.database,
        timezone: dbConfig.plat_read_mysql.timezone,
    },
    pool: {
        min: dbConfig.plat_read_mysql.minConnection,
        max: dbConfig.plat_read_mysql.maxConnection
    },
});
this.writeMysql = new Knex({
    client: "mysql",
    debug: dbConfig.plat_write_mysql.debug,
    connection: {
        host: dbConfig.plat_write_mysql.host,
        user: dbConfig.plat_write_mysql.user,
        password: dbConfig.plat_write_mysql.password,
        database: dbConfig.plat_write_mysql.database,
        timezone: dbConfig.plat_write_mysql.timezone,
    },
    pool: {
        min: dbConfig.plat_write_mysql.minConnection,
        max: dbConfig.plat_write_mysql.maxConnection
    },
});

上面代碼用了兩個查詢構(gòu)造器區(qū)分寫入數(shù)據(jù)庫動作和讀取數(shù)據(jù)庫動作

寫一個鑒權(quán)的中間件
checkHeader: async function(ctx, next) {
    await validator.validate(
        ctx.headerInput,
        userValidation.checkHeader.schema,
        userValidation.checkHeader.options
    )
    let cacheUserInfo = await db.redis.get(foodKeyDefines.userInfoCacheKey(ctx.headerInput.fid))
    cacheUserInfo = UTIL.jsonParse(cacheUserInfo);
    // 如果沒有redis層用戶信息和token信息不對稱,需要用戶重新登錄
    if (!cacheUserInfo || ctx.headerInput.token !== cacheUserInfo.token) {
        throw new ApiError("food.userAccessTokenForbidden");
    }
    await next();
}
使用鑒權(quán)中間件,拿一個路由作為例子
//引入
const routePermission = require("../../middleware/routePermission.js");
// 用戶點餐
router.post("/api/user/order", routePermission.checkHeader, userMenuController.userOrder);
請求錯誤碼封裝
定義一個請求錯誤類
class ApiError extends Error {
    /**
     * 構(gòu)造方法
     * @param errorName 錯誤名稱
     * @param params 錯誤信息參數(shù)
     */
    constructor(errorName, ...params) {
        super();
        let errorInfo = apiErrorDefines(errorName, params);
        this.name = errorName;
        this.code = errorInfo.code;
        this.status = errorInfo.status;
        this.message = errorInfo.message;
    }
}
錯誤碼定義
const defines = {
    "common.all": {code: 1000, message: "%s", status: 500},
    "request.paramError": {code: 1001, message: "參數(shù)錯誤 %s", status: 200},
    "access.forbidden": {code: 1010, message: "沒有操作權(quán)限", status: 403},
    "auth.notPermission": {code: 1011, message: "授權(quán)失敗 %s", status: 403},
    "role.notExist": {code: 1012, message: "角色不存在", status: 403},
    "auth.codeExpired": {code: 1013, message: "授權(quán)碼已失效", status: 403},
    "auth.codeError": {code: 1014, message: "授權(quán)碼錯誤", status: 403},
    "auth.pargramNotExist": {code: 1015, message: "程序不存在", status: 403},
    "auth.pargramSecretError": {code: 1016, message: "程序秘鑰錯誤", status: 403},
    "auth.pargramSecretEmpty": {code: 1016, message: "程序秘鑰為空,請后臺配置", status: 403},

    "db.queryError": { code: 1100, message: "數(shù)據(jù)庫查詢異常", status: 500 },
    "db.insertError": { code: 1101, message: "數(shù)據(jù)庫寫入異常", status: 500 },
    "db.updateError": { code: 1102, message: "數(shù)據(jù)庫更新異常", status: 500 },
    "db.deleteError": { code: 1103, message: "數(shù)據(jù)庫刪除異常", status: 500 },

    "redis.setError": { code: 1104, message: "redis設(shè)置異常", status: 500 },

    "food.illegalUser" : {code: 1201, message: "非法用戶", status: 403},
    "food.userHasExist" : {code: 1202, message: "用戶已經(jīng)存在", status: 200},
    "food.objectNotExist" : {code: 1203, message: "%s", status: 200},
    "food.insertMenuError": {code: 1204, message: "批量插入菜單失敗", status: 200},
    "food.userNameInvalid": {code: 1205, message: "我不信你叫這個名字", status: 200},
    "food.userOrderAlready": {code: 1206, message: "您已經(jīng)定過餐了", status: 200},
    "food.userNotOrderToday": {code: 1207, message: "您今天還沒有訂餐", status: 200},
    "food.orderIsEnd": {code: 1208, message: "訂餐已經(jīng)截止了,歡迎下次光臨", status: 200},
    "food.blackHouse": {code: 1209, message: "別搞太多騷操作", status: 200},
    "food.userAccessTokenForbidden": { code: 1210, message: "token失效", status: 403 },
    "food.userHasStared": { code: 1211, message: "此評論您已點過贊", status: 200 },
    "food.canNotReplySelf": { code: 1212, message: "不能回復(fù)自己的評論", status: 200 },
    "food.overReplyLimit": { code: 1213, message: "回復(fù)評論數(shù)已超過%s條,不能再回復(fù)", status: 200 }
};

module.exports = function (errorName, params) {
    if(defines[errorName]) {
        let result = {
            code: defines[errorName].code,
            message: defines[errorName].message,
            status: defines[errorName].status
        };

        params.forEach(element => {
            result.message = (result.message).replace("%s", element);
        });

        return result;
    }
    
    return {
        code: 1000,
        message: "服務(wù)器內(nèi)部錯誤",
        status: 500
    };
}
拋錯機(jī)制

當(dāng)程序判斷到有錯誤產(chǎn)生時,可以拋出錯誤給到前端,例如token不正確。

// 如果沒有redis層用戶信息和token信息不對稱,需要用戶重新登錄
if (!cacheUserInfo || ctx.headerInput.token !== cacheUserInfo.token) {
    throw new ApiError("food.userAccessTokenForbidden");
}
因為程序有一個回調(diào)處理的中間件,所以能捕捉到定義的ApiError
// requestError.js
module.exports = async function (ctx, next) {
    let beginTime = new Date().getTime();
    try {
        await next();
        let req = ctx.request;
        let res = ctx.response;
        let input = ctx.input;
        let endTime = new Date().getTime();
        let ip = req.get("X-Real-IP") || req.get("X-Forwarded-For") || req.ip;

        let fields = {
            status: res.status,
            accept: req.header["accept"],
            cookie: req.header["cookie"],
            ua: req.header["user-agent"],
            method: req.method,
            headers: ctx.headers,
            url: req.url,
            client_ip: ip,
            cost: endTime - beginTime,
            input: input
        };

        logger.getLogger("access").trace("requestSuccess", fields);
    } catch (e) {
        if (e.code === "ECONNREFUSED") {
            //數(shù)據(jù)庫連接失敗
            logger.getLogger("error").fatal("mysql連接失敗", e.message, e.code);
            e.code = 1;
            e.message = "數(shù)據(jù)庫連接異常";
        }

        if (e.code === "ER_DUP_ENTRY") {
            logger.getLogger("error").error("mysql操作異常", e.message, e.code);
            e.code = 1;
            e.message = "數(shù)據(jù)庫操作違反唯一約束";
        }

        if (e.code === "ETIMEDOUT") {
            logger.getLogger("error").error("mysql操作異常", e.message, e.code);
            e.code = 1;
            e.message = "數(shù)據(jù)庫連接超時";
        }


        let req = ctx.request;
        let res = ctx.response;
        let status = e.status || 500;
        let msg = e.message || e;
        let input = ctx.input;

        let endTime = new Date().getTime();
        let ip = req.get("X-Real-IP") || req.get("X-Forwarded-For") || req.ip;

        let fields = {
            status: res.status,
            accept: req.header["accept"],
            cookie: req.header["cookie"],
            ua: req.header["user-agent"],
            method: req.method,
            headers: ctx.headers,
            url: req.url,
            client_ip: ip,
            cost: endTime - beginTime,
            input: input,
            msg: msg
        };

        ctx.status = status;

        if (status === 500) {
            logger.getLogger("access").error("requestError", fields);
        } else {
            logger.getLogger("access").warn("requestException", fields);
        }
        let errCode = e.code || 1;
        if (!(parseInt(errCode) > 0)) {
            errCode = 1;
        }
        return response.output(ctx, {}, errCode, msg, status);
    }
};
在app.js中引入中間件
/**
 * 請求回調(diào)處理中間件
 */
app.use(require("./middleware/requestError.js"));
數(shù)據(jù)庫創(chuàng)建sql(命名不規(guī)范,請見諒)
CREATE DATABASE food_program;
USE food_program;
# 用戶表
CREATE TABLE t_food_user(
    fid int(11) auto_increment primary key COMMENT "用戶id",
    user_name varchar(255) NOT NULL COMMENT "用戶昵稱",
    password varchar(255) NOT NULL COMMENT "用戶密碼",
    role TINYINT(2) DEFAULT 0 COMMENT "用戶角色(項目關(guān)系,沒有用關(guān)聯(lián)表)",
    create_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "創(chuàng)建時間",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間",
    status TINYINT(2) DEFAULT 1 NOT NULL COMMENT "狀態(tài) 0:刪除, 1:正常",
    UNIQUE KEY `uidx_fid_user_name` (`fid`,`user_name`) USING BTREE
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "food 用戶表" ;

CREATE TABLE t_food_menu(
    menu_id int(11) auto_increment primary key COMMENT "菜單id",
    menu_name varchar(255) NOT NULL COMMENT "菜單昵稱",
    type TINYINT(2) DEFAULT 0 NOT NULL COMMENT "狀態(tài) 0:每日菜單, 1:常規(guī), 2:明爐燒臘",
    price int(11) NOT NULL COMMENT "價格",
    status TINYINT(2) DEFAULT 1 NOT NULL COMMENT "狀態(tài) 0:刪除, 1:正常",
    create_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "創(chuàng)建時間",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間",
    UNIQUE KEY `uidx_menu_id_menu_name` (`menu_id`,`menu_name`) USING BTREE,
    UNIQUE KEY `uidx_menu_id_menu_name_type` (`menu_id`,`menu_name`,`type`) USING BTREE
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "food 菜單列表" ;

CREATE TABLE t_food_user_menu_refs(
    id int(11) auto_increment primary key COMMENT "記錄id",
    fid int(11) NOT NULL COMMENT "用戶id",
    menu_id int(11) NOT NULL COMMENT "菜單id"
    create_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "創(chuàng)建時間",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間",
    status TINYINT(2) DEFAULT 1 NOT NULL COMMENT "狀態(tài) 0:刪除, 1:正常",
    KEY `idx_fid_menu_id` (`fid`,`menu_id`) USING BTREE,
    KEY `idx_fid_menu_id_status` (`fid`,`menu_id`,`status`) USING BTREE
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "用戶選擇什么菜單" ;

CREATE TABLE t_food_system(
    id int(11) auto_increment primary key COMMENT "系統(tǒng)id",
    order_end TINYINT(2) DEFAULT 0 NOT NULL COMMENT "訂單是否截止",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間"
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "都城訂單系統(tǒng)" ;

CREATE TABLE t_food_comment(
    comment_id int(11) auto_increment primary key COMMENT "評論id",
    fid int(11) NOT NULL COMMENT "用戶id",
    content TEXT COMMENT "評論內(nèi)容",
    star int(11) DEFAULT 0 NOT NULL COMMENT "點贊數(shù)",
    create_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "創(chuàng)建時間",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間"
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "都城聊天表" ;

CREATE TABLE t_food_reply(
    reply_id int(11) auto_increment primary key COMMENT "回復(fù)id",
    reply_fid int(11) NOT NULL COMMENT "回復(fù)用戶fid",
    comment_fid int(11) NOT NULL COMMENT "評論用戶fid",
    content TEXT COMMENT "回復(fù)內(nèi)容",
    create_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "創(chuàng)建時間",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間",
    KEY `idx_reply_fid_comment_fid` (`reply_fid`,`comment_fid`) USING BTREE
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "都城聊天表" ;

CREATE TABLE t_food_comment_star_refs(
    id int(11) auto_increment primary key COMMENT "關(guān)系id",
    comment_id int(11) NOT NULL COMMENT "評論id",
    comment_fid int(11) NOT NULL COMMENT "用戶id",
    star_fid int(11) NOT NULL COMMENT "點贊用戶fid",
    create_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "創(chuàng)建時間",
    update_time BIGINT(20) DEFAULT 0 NOT NULL COMMENT "修改時間",
    UNIQUE KEY `idx_comment_id_fid_star_fid` (`comment_id`,`fid`,`star_fid`) USING BTREE
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = "都城評論點贊關(guān)聯(lián)表" ;
項目部署 前端部署 本地開發(fā)
npm run dev
開發(fā)路徑
http://localhost:3006/food/login
線上部署
npm install pm2 -g

npm run build

會生成一個build的文件夾,里面是線上需要用到的資源

nginx設(shè)置
// /opt/food/fontend/build/ 是npm run build的文件夾路徑
location /assets/ {
   alias /opt/food/fontend/build/;
}
location / {
  proxy_pass http://127.0.0.1:3006/;
}
使用pm2開啟項目
pm2 start pm2.prod.json
后端部署 本地開發(fā)
pm2 start app.js --watch

開啟 --watch 模式監(jiān)聽項目日志

線上部署
pm2 start app.js

千萬不要開啟 --watch,因為沒請求一次服務(wù)會刷新產(chǎn)生數(shù)據(jù)庫和redis重連,導(dǎo)致報錯

結(jié)尾

開發(fā)完這個系統(tǒng)用了三個星期趕上寒冬我就離職了...然后去面試一些公司拿這個小玩意給面試官看,HR挺滿意的,就是不知道技術(shù)官滿不滿意。

歡迎大家來交流哦~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/104596.html

相關(guān)文章

  • </2016><2017>

    摘要:不覺間,已悄然離去恍然后,正慢慢襲來。已完成一期內(nèi)容,只包含買家點餐功能,二期準(zhǔn)備做賣家及支付功能。經(jīng)過考慮和評估,我決定對這兩個選擇進(jìn)行一個折中。項目部署,及代理轉(zhuǎn)發(fā)等配置。發(fā)現(xiàn)最近,已經(jīng)對非技術(shù)類書籍少了很多興趣。 不覺間,2016已悄然離去;恍然后,2017正慢慢襲來。 又到了總結(jié)過去,展望未來的時候了,那就先總結(jié)16年的收獲和經(jīng)驗教訓(xùn),再展望17年對自己及行業(yè)的一些期望吧。 1...

    wangshijun 評論0 收藏0
  • </2016><2017>

    摘要:不覺間,已悄然離去恍然后,正慢慢襲來。已完成一期內(nèi)容,只包含買家點餐功能,二期準(zhǔn)備做賣家及支付功能。經(jīng)過考慮和評估,我決定對這兩個選擇進(jìn)行一個折中。項目部署,及代理轉(zhuǎn)發(fā)等配置。發(fā)現(xiàn)最近,已經(jīng)對非技術(shù)類書籍少了很多興趣。 不覺間,2016已悄然離去;恍然后,2017正慢慢襲來。 又到了總結(jié)過去,展望未來的時候了,那就先總結(jié)16年的收獲和經(jīng)驗教訓(xùn),再展望17年對自己及行業(yè)的一些期望吧。 1...

    fxp 評論0 收藏0
  • 2017年1月前端月報

    摘要:平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。年以前看這個網(wǎng)址概況在線地址前端開發(fā)群月報提交原則技術(shù)文章新的為主。 平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 概況 在線地址:http://www.kancloud.cn/jsfront/month/82796 JS前端開發(fā)群月報 提交原則: 技...

    FuisonDesign 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<