23": "开江县", 2959
+    "511724": "大竹县",
2960
+    "511725": "渠县",
2961
+    "511781": "万源市"
2962
+  },
2963
+  "511800": {
2964
+    "511802": "雨城区",
2965
+    "511803": "名山区",
2966
+    "511822": "荥经县",
2967
+    "511823": "汉源县",
2968
+    "511824": "石棉县",
2969
+    "511825": "天全县",
2970
+    "511826": "芦山县",
2971
+    "511827": "宝兴县"
2972
+  },
2973
+  "511900": {
2974
+    "511902": "巴州区",
2975
+    "511903": "恩阳区",
2976
+    "511921": "通江县",
2977
+    "511922": "南江县",
2978
+    "511923": "平昌县"
2979
+  },
2980
+  "512000": {
2981
+    "512002": "雁江区",
2982
+    "512021": "安岳县",
2983
+    "512022": "乐至县"
2984
+  },
2985
+  "513200": {
2986
+    "513201": "马尔康市",
2987
+    "513221": "汶川县",
2988
+    "513222": "理县",
2989
+    "513223": "茂县",
2990
+    "513224": "松潘县",
2991
+    "513225": "九寨沟县",
2992
+    "513226": "金川县",
2993
+    "513227": "小金县",
2994
+    "513228": "黑水县",
2995
+    "513230": "壤塘县",
2996
+    "513231": "阿坝县",
2997
+    "513232": "若尔盖县",
2998
+    "513233": "红原县"
2999
+  },
3000
+  "513300": {
3001
+    "513301": "康定市",
3002
+    "513322": "泸定县",
3003
+    "513323": "丹巴县",
3004
+    "513324": "九龙县",
3005
+    "513325": "雅江县",
3006
+    "513326": "道孚县",
3007
+    "513327": "炉霍县",
3008
+    "513328": "甘孜县",
3009
+    "513329": "新龙县",
3010
+    "513330": "德格县",
3011
+    "513331": "白玉县",
3012
+    "513332": "石渠县",
3013
+    "513333": "色达县",
3014
+    "513334": "理塘县",
3015
+    "513335": "巴塘县",
3016
+    "513336": "乡城县",
3017
+    "513337": "稻城县",
3018
+    "513338": "得荣县"
3019
+  },
3020
+  "513400": {
3021
+    "513401": "西昌市",
3022
+    "513422": "木里藏族自治县",
3023
+    "513423": "盐源县",
3024
+    "513424": "德昌县",
3025
+    "513425": "会理县",
3026
+    "513426": "会东县",
3027
+    "513427": "宁南县",
3028
+    "513428": "普格县",
3029
+    "513429": "布拖县",
3030
+    "513430": "金阳县",
3031
+    "513431": "昭觉县",
3032
+    "513432": "喜德县",
3033
+    "513433": "冕宁县",
3034
+    "513434": "越西县",
3035
+    "513435": "甘洛县",
3036
+    "513436": "美姑县",
3037
+    "513437": "雷波县"
3038
+  },
3039
+  "520000": {
3040
+    "520100": "贵阳市",
3041
+    "520200": "六盘水市",
3042
+    "520300": "遵义市",
3043
+    "520400": "安顺市",
3044
+    "520500": "毕节市",
3045
+    "520600": "铜仁市",
3046
+    "522300": "黔西南布依族苗族自治州",
3047
+    "522600": "黔东南苗族侗族自治州",
3048
+    "522700": "黔南布依族苗族自治州"
3049
+  },
3050
+  "520100": {
3051
+    "520102": "南明区",
3052
+    "520103": "云岩区",
3053
+    "520111": "花溪区",
3054
+    "520112": "乌当区",
3055
+    "520113": "白云区",
3056
+    "520115": "观山湖区",
3057
+    "520121": "开阳县",
3058
+    "520122": "息烽县",
3059
+    "520123": "修文县",
3060
+    "520181": "清镇市"
3061
+  },
3062
+  "520200": {
3063
+    "520201": "钟山区",
3064
+    "520203": "六枝特区",
3065
+    "520221": "水城县",
3066
+    "520222": "盘县"
3067
+  },
3068
+  "520300": {
3069
+    "520302": "红花岗区",
3070
+    "520303": "汇川区",
3071
+    "520304": "播州区",
3072
+    "520322": "桐梓县",
3073
+    "520323": "绥阳县",
3074
+    "520324": "正安县",
3075
+    "520325": "道真仡佬族苗族自治县",
3076
+    "520326": "务川仡佬族苗族自治县",
3077
+    "520327": "凤冈县",
3078
+    "520328": "湄潭县",
3079
+    "520329": "余庆县",
3080
+    "520330": "习水县",
3081
+    "520381": "赤水市",
3082
+    "520382": "仁怀市"
3083
+  },
3084
+  "520400": {
3085
+    "520402": "西秀区",
3086
+    "520403": "平坝区",
3087
+    "520422": "普定县",
3088
+    "520423": "镇宁布依族苗族自治县",
3089
+    "520424": "关岭布依族苗族自治县",
3090
+    "520425": "紫云苗族布依族自治县"
3091
+  },
3092
+  "520500": {
3093
+    "520502": "七星关区",
3094
+    "520521": "大方县",
3095
+    "520522": "黔西县",
3096
+    "520523": "金沙县",
3097
+    "520524": "织金县",
3098
+    "520525": "纳雍县",
3099
+    "520526": "威宁彝族回族苗族自治县",
3100
+    "520527": "赫章县"
3101
+  },
3102
+  "520600": {
3103
+    "520602": "碧江区",
3104
+    "520603": "万山区",
3105
+    "520621": "江口县",
3106
+    "520622": "玉屏侗族自治县",
3107
+    "520623": "石阡县",
3108
+    "520624": "思南县",
3109
+    "520625": "印江土家族苗族自治县",
3110
+    "520626": "德江县",
3111
+    "520627": "沿河土家族自治县",
3112
+    "520628": "松桃苗族自治县"
3113
+  },
3114
+  "522300": {
3115
+    "522301": "兴义市",
3116
+    "522322": "兴仁县",
3117
+    "522323": "普安县",
3118
+    "522324": "晴隆县",
3119
+    "522325": "贞丰县",
3120
+    "522326": "望谟县",
3121
+    "522327": "册亨县",
3122
+    "522328": "安龙县"
3123
+  },
3124
+  "522600": {
3125
+    "522601": "凯里市",
3126
+    "522622": "黄平县",
3127
+    "522623": "施秉县",
3128
+    "522624": "三穗县",
3129
+    "522625": "镇远县",
3130
+    "522626": "岑巩县",
3131
+    "522627": "天柱县",
3132
+    "522628": "锦屏县",
3133
+    "522629": "剑河县",
3134
+    "522630": "台江县",
3135
+    "522631": "黎平县",
3136
+    "522632": "榕江县",
3137
+    "522633": "从江县",
3138
+    "522634": "雷山县",
3139
+    "522635": "麻江县",
3140
+    "522636": "丹寨县"
3141
+  },
3142
+  "522700": {
3143
+    "522701": "都匀市",
3144
+    "522702": "福泉市",
3145
+    "522722": "荔波县",
3146
+    "522723": "贵定县",
3147
+    "522725": "瓮安县",
3148
+    "522726": "独山县",
3149
+    "522727": "平塘县",
3150
+    "522728": "罗甸县",
3151
+    "522729": "长顺县",
3152
+    "522730": "龙里县",
3153
+    "522731": "惠水县",
3154
+    "522732": "三都水族自治县"
3155
+  },
3156
+  "530000": {
3157
+    "530100": "昆明市",
3158
+    "530300": "曲靖市",
3159
+    "530400": "玉溪市",
3160
+    "530500": "保山市",
3161
+    "530600": "昭通市",
3162
+    "530700": "丽江市",
3163
+    "530800": "普洱市",
3164
+    "530900": "临沧市",
3165
+    "532300": "楚雄彝族自治州",
3166
+    "532500": "红河哈尼族彝族自治州",
3167
+    "532600": "文山壮族苗族自治州",
3168
+    "532800": "西双版纳傣族自治州",
3169
+    "532900": "大理白族自治州",
3170
+    "533100": "德宏傣族景颇族自治州",
3171
+    "533300": "怒江傈僳族自治州",
3172
+    "533400": "迪庆藏族自治州"
3173
+  },
3174
+  "530100": {
3175
+    "530102": "五华区",
3176
+    "530103": "盘龙区",
3177
+    "530111": "官渡区",
3178
+    "530112": "西山区",
3179
+    "530113": "东川区",
3180
+    "530114": "呈贡区",
3181
+    "530122": "晋宁县",
3182
+    "530124": "富民县",
3183
+    "530125": "宜良县",
3184
+    "530126": "石林彝族自治县",
3185
+    "530127": "嵩明县",
3186
+    "530128": "禄劝彝族苗族自治县",
3187
+    "530129": "寻甸回族彝族自治县",
3188
+    "530181": "安宁市"
3189
+  },
3190
+  "530300": {
3191
+    "530302": "麒麟区",
3192
+    "530303": "沾益区",
3193
+    "530321": "马龙县",
3194
+    "530322": "陆良县",
3195
+    "530323": "师宗县",
3196
+    "530324": "罗平县",
3197
+    "530325": "富源县",
3198
+    "530326": "会泽县",
3199
+    "530381": "宣威市"
3200
+  },
3201
+  "530400": {
3202
+    "530402": "红塔区",
3203
+    "530403": "江川区",
3204
+    "530422": "澄江县",
3205
+    "530423": "通海县",
3206
+    "530424": "华宁县",
3207
+    "530425": "易门县",
3208
+    "530426": "峨山彝族自治县",
3209
+    "530427": "新平彝族傣族自治县",
3210
+    "530428": "元江哈尼族彝族傣族自治县"
3211
+  },
3212
+  "530500": {
3213
+    "530502": "隆阳区",
3214
+    "530521": "施甸县",
3215
+    "530523": "龙陵县",
3216
+    "530524": "昌宁县",
3217
+    "530581": "腾冲市"
3218
+  },
3219
+  "530600": {
3220
+    "530602": "昭阳区",
3221
+    "530621": "鲁甸县",
3222
+    "530622": "巧家县",
3223
+    "530623": "盐津县",
3224
+    "530624": "大关县",
3225
+    "530625": "永善县",
3226
+    "530626": "绥江县",
3227
+    "530627": "镇雄县",
3228
+    "530628": "彝良县",
3229
+    "530629": "威信县",
3230
+    "530630": "水富县"
3231
+  },
3232
+  "530700": {
3233
+    "530702": "古城区",
3234
+    "530721": "玉龙纳西族自治县",
3235
+    "530722": "永胜县",
3236
+    "530723": "华坪县",
3237
+    "530724": "宁蒗彝族自治县"
3238
+  },
3239
+  "530800": {
3240
+    "530802": "思茅区",
3241
+    "530821": "宁洱哈尼族彝族自治县",
3242
+    "530822": "墨江哈尼族自治县",
3243
+    "530823": "景东彝族自治县",
3244
+    "530824": "景谷傣族彝族自治县",
3245
+    "530825": "镇沅彝族哈尼族拉祜族自治县",
3246
+    "530826": "江城哈尼族彝族自治县",
3247
+    "530827": "孟连傣族拉祜族佤族自治县",
3248
+    "530828": "澜沧拉祜族自治县",
3249
+    "530829": "西盟佤族自治县"
3250
+  },
3251
+  "530900": {
3252
+    "530902": "临翔区",
3253
+    "530921": "凤庆县",
3254
+    "530922": "云县",
3255
+    "530923": "永德县",
3256
+    "530924": "镇康县",
3257
+    "530925": "双江拉祜族佤族布朗族傣族自治县",
3258
+    "530926": "耿马傣族佤族自治县",
3259
+    "530927": "沧源佤族自治县"
3260
+  },
3261
+  "532300": {
3262
+    "532301": "楚雄市",
3263
+    "532322": "双柏县",
3264
+    "532323": "牟定县",
3265
+    "532324": "南华县",
3266
+    "532325": "姚安县",
3267
+    "532326": "大姚县",
3268
+    "532327": "永仁县",
3269
+    "532328": "元谋县",
3270
+    "532329": "武定县",
3271
+    "532331": "禄丰县"
3272
+  },
3273
+  "532500": {
3274
+    "532501": "个旧市",
3275
+    "532502": "开远市",
3276
+    "532503": "蒙自市",
3277
+    "532504": "弥勒市",
3278
+    "532523": "屏边苗族自治县",
3279
+    "532524": "建水县",
3280
+    "532525": "石屏县",
3281
+    "532527": "泸西县",
3282
+    "532528": "元阳县",
3283
+    "532529": "红河县",
3284
+    "532530": "金平苗族瑶族傣族自治县",
3285
+    "532531": "绿春县",
3286
+    "532532": "河口瑶族自治县"
3287
+  },
3288
+  "532600": {
3289
+    "532601": "文山市",
3290
+    "532622": "砚山县",
3291
+    "532623": "西畴县",
3292
+    "532624": "麻栗坡县",
3293
+    "532625": "马关县",
3294
+    "532626": "丘北县",
3295
+    "532627": "广南县",
3296
+    "532628": "富宁县"
3297
+  },
3298
+  "532800": {
3299
+    "532801": "景洪市",
3300
+    "532822": "勐海县",
3301
+    "532823": "勐腊县"
3302
+  },
3303
+  "532900": {
3304
+    "532901": "大理市",
3305
+    "532922": "漾濞彝族自治县",
3306
+    "532923": "祥云县",
3307
+    "532924": "宾川县",
3308
+    "532925": "弥渡县",
3309
+    "532926": "南涧彝族自治县",
3310
+    "532927": "巍山彝族回族自治县",
3311
+    "532928": "永平县",
3312
+    "532929": "云龙县",
3313
+    "532930": "洱源县",
3314
+    "532931": "剑川县",
3315
+    "532932": "鹤庆县"
3316
+  },
3317
+  "533100": {
3318
+    "533102": "瑞丽市",
3319
+    "533103": "芒市",
3320
+    "533122": "梁河县",
3321
+    "533123": "盈江县",
3322
+    "533124": "陇川县"
3323
+  },
3324
+  "533300": {
3325
+    "533301": "泸水市",
3326
+    "533323": "福贡县",
3327
+    "533324": "贡山独龙族怒族自治县",
3328
+    "533325": "兰坪白族普米族自治县"
3329
+  },
3330
+  "533400": {
3331
+    "533401": "香格里拉市",
3332
+    "533422": "德钦县",
3333
+    "533423": "维西傈僳族自治县"
3334
+  },
3335
+  "540000": {
3336
+    "540100": "拉萨市",
3337
+    "540200": "日喀则市",
3338
+    "540300": "昌都市",
3339
+    "540400": "林芝市",
3340
+    "540500": "山南市",
3341
+    "542400": "那曲地区",
3342
+    "542500": "阿里地区"
3343
+  },
3344
+  "540100": {
3345
+    "540102": "城关区",
3346
+    "540103": "堆龙德庆区",
3347
+    "540121": "林周县",
3348
+    "540122": "当雄县",
3349
+    "540123": "尼木县",
3350
+    "540124": "曲水县",
3351
+    "540126": "达孜县",
3352
+    "540127": "墨竹工卡县"
3353
+  },
3354
+  "540200": {
3355
+    "540202": "桑珠孜区",
3356
+    "540221": "南木林县",
3357
+    "540222": "江孜县",
3358
+    "540223": "定日县",
3359
+    "540224": "萨迦县",
3360
+    "540225": "拉孜县",
3361
+    "540226": "昂仁县",
3362
+    "540227": "谢通门县",
3363
+    "540228": "白朗县",
3364
+    "540229": "仁布县",
3365
+    "540230": "康马县",
3366
+    "540231": "定结县",
3367
+    "540232": "仲巴县",
3368
+    "540233": "亚东县",
3369
+    "540234": "吉隆县",
3370
+    "540235": "聂拉木县",
3371
+    "540236": "萨嘎县",
3372
+    "540237": "岗巴县"
3373
+  },
3374
+  "540300": {
3375
+    "540302": "卡若区",
3376
+    "540321": "江达县",
3377
+    "540322": "贡觉县",
3378
+    "540323": "类乌齐县",
3379
+    "540324": "丁青县",
3380
+    "540325": "察雅县",
3381
+    "540326": "八宿县",
3382
+    "540327": "左贡县",
3383
+    "540328": "芒康县",
3384
+    "540329": "洛隆县",
3385
+    "540330": "边坝县"
3386
+  },
3387
+  "540400": {
3388
+    "540402": "巴宜区",
3389
+    "540421": "工布江达县",
3390
+    "540422": "米林县",
3391
+    "540423": "墨脱县",
3392
+    "540424": "波密县",
3393
+    "540425": "察隅县",
3394
+    "540426": "朗县"
3395
+  },
3396
+  "540500": {
3397
+    "540502": "乃东区",
3398
+    "540521": "扎囊县",
3399
+    "540522": "贡嘎县",
3400
+    "540523": "桑日县",
3401
+    "540524": "琼结县",
3402
+    "540525": "曲松县",
3403
+    "540526": "措美县",
3404
+    "540527": "洛扎县",
3405
+    "540528": "加查县",
3406
+    "540529": "隆子县",
3407
+    "540530": "错那县",
3408
+    "540531": "浪卡子县"
3409
+  },
3410
+  "542400": {
3411
+    "542421": "那曲县",
3412
+    "542422": "嘉黎县",
3413
+    "542423": "比如县",
3414
+    "542424": "聂荣县",
3415
+    "542425": "安多县",
3416
+    "542426": "申扎县",
3417
+    "542427": "索县",
3418
+    "542428": "班戈县",
3419
+    "542429": "巴青县",
3420
+    "542430": "尼玛县",
3421
+    "542431": "双湖县"
3422
+  },
3423
+  "542500": {
3424
+    "542521": "普兰县",
3425
+    "542522": "札达县",
3426
+    "542523": "噶尔县",
3427
+    "542524": "日土县",
3428
+    "542525": "革吉县",
3429
+    "542526": "改则县",
3430
+    "542527": "措勤县"
3431
+  },
3432
+  "610000": {
3433
+    "610100": "西安市",
3434
+    "610200": "铜川市",
3435
+    "610300": "宝鸡市",
3436
+    "610400": "咸阳市",
3437
+    "610500": "渭南市",
3438
+    "610600": "延安市",
3439
+    "610700": "汉中市",
3440
+    "610800": "榆林市",
3441
+    "610900": "安康市",
3442
+    "611000": "商洛市"
3443
+  },
3444
+  "610100": {
3445
+    "610102": "新城区",
3446
+    "610103": "碑林区",
3447
+    "610104": "莲湖区",
3448
+    "610111": "灞桥区",
3449
+    "610112": "未央区",
3450
+    "610113": "雁塔区",
3451
+    "610114": "阎良区",
3452
+    "610115": "临潼区",
3453
+    "610116": "长安区",
3454
+    "610117": "高陵区",
3455
+    "610122": "蓝田县",
3456
+    "610124": "周至县",
3457
+    "610125": "户县"
3458
+  },
3459
+  "610200": {
3460
+    "610202": "王益区",
3461
+    "610203": "印台区",
3462
+    "610204": "耀州区",
3463
+    "610222": "宜君县"
3464
+  },
3465
+  "610300": {
3466
+    "610302": "渭滨区",
3467
+    "610303": "金台区",
3468
+    "610304": "陈仓区",
3469
+    "610322": "凤翔县",
3470
+    "610323": "岐山县",
3471
+    "610324": "扶风县",
3472
+    "610326": "眉县",
3473
+    "610327": "陇县",
3474
+    "610328": "千阳县",
3475
+    "610329": "麟游县",
3476
+    "610330": "凤县",
3477
+    "610331": "太白县"
3478
+  },
3479
+  "610400": {
3480
+    "610402": "秦都区",
3481
+    "610403": "杨陵区",
3482
+    "610404": "渭城区",
3483
+    "610422": "三原县",
3484
+    "610423": "泾阳县",
3485
+    "610424": "乾县",
3486
+    "610425": "礼泉县",
3487
+    "610426": "永寿县",
3488
+    "610427": "彬县",
3489
+    "610428": "长武县",
3490
+    "610429": "旬邑县",
3491
+    "610430": "淳化县",
3492
+    "610431": "武功县",
3493
+    "610481": "兴平市"
3494
+  },
3495
+  "610500": {
3496
+    "610502": "临渭区",
3497
+    "610503": "华州区",
3498
+    "610522": "潼关县",
3499
+    "610523": "大荔县",
3500
+    "610524": "合阳县",
3501
+    "610525": "澄城县",
3502
+    "610526": "蒲城县",
3503
+    "610527": "白水县",
3504
+    "610528": "富平县",
3505
+    "610581": "韩城市",
3506
+    "610582": "华阴市"
3507
+  },
3508
+  "610600": {
3509
+    "610602": "宝塔区",
3510
+    "610603": "安塞区",
3511
+    "610621": "延长县",
3512
+    "610622": "延川县",
3513
+    "610623": "子长县",
3514
+    "610625": "志丹县",
3515
+    "610626": "吴起县",
3516
+    "610627": "甘泉县",
3517
+    "610628": "富县",
3518
+    "610629": "洛川县",
3519
+    "610630": "宜川县",
3520
+    "610631": "黄龙县",
3521
+    "610632": "黄陵县"
3522
+  },
3523
+  "610700": {
3524
+    "610702": "汉台区",
3525
+    "610721": "南郑县",
3526
+    "610722": "城固县",
3527
+    "610723": "洋县",
3528
+    "610724": "西乡县",
3529
+    "610725": "勉县",
3530
+    "610726": "宁强县",
3531
+    "610727": "略阳县",
3532
+    "610728": "镇巴县",
3533
+    "610729": "留坝县",
3534
+    "610730": "佛坪县"
3535
+  },
3536
+  "610800": {
3537
+    "610802": "榆阳区",
3538
+    "610803": "横山区",
3539
+    "610821": "神木县",
3540
+    "610822": "府谷县",
3541
+    "610824": "靖边县",
3542
+    "610825": "定边县",
3543
+    "610826": "绥德县",
3544
+    "610827": "米脂县",
3545
+    "610828": "佳县",
3546
+    "610829": "吴堡县",
3547
+    "610830": "清涧县",
3548
+    "610831": "子洲县"
3549
+  },
3550
+  "610900": {
3551
+    "610902": "汉滨区",
3552
+    "610921": "汉阴县",
3553
+    "610922": "石泉县",
3554
+    "610923": "宁陕县",
3555
+    "610924": "紫阳县",
3556
+    "610925": "岚皋县",
3557
+    "610926": "平利县",
3558
+    "610927": "镇坪县",
3559
+    "610928": "旬阳县",
3560
+    "610929": "白河县"
3561
+  },
3562
+  "611000": {
3563
+    "611002": "商州区",
3564
+    "611021": "洛南县",
3565
+    "611022": "丹凤县",
3566
+    "611023": "商南县",
3567
+    "611024": "山阳县",
3568
+    "611025": "镇安县",
3569
+    "611026": "柞水县"
3570
+  },
3571
+  "620000": {
3572
+    "620100": "兰州市",
3573
+    "620200": "嘉峪关市",
3574
+    "620300": "金昌市",
3575
+    "620400": "白银市",
3576
+    "620500": "天水市",
3577
+    "620600": "武威市",
3578
+    "620700": "张掖市",
3579
+    "620800": "平凉市",
3580
+    "620900": "酒泉市",
3581
+    "621000": "庆阳市",
3582
+    "621100": "定西市",
3583
+    "621200": "陇南市",
3584
+    "622900": "临夏回族自治州",
3585
+    "623000": "甘南藏族自治州"
3586
+  },
3587
+  "620100": {
3588
+    "620102": "城关区",
3589
+    "620103": "七里河区",
3590
+    "620104": "西固区",
3591
+    "620105": "安宁区",
3592
+    "620111": "红古区",
3593
+    "620121": "永登县",
3594
+    "620122": "皋兰县",
3595
+    "620123": "榆中县"
3596
+  },
3597
+  "620300": {
3598
+    "620302": "金川区",
3599
+    "620321": "永昌县"
3600
+  },
3601
+  "620400": {
3602
+    "620402": "白银区",
3603
+    "620403": "平川区",
3604
+    "620421": "靖远县",
3605
+    "620422": "会宁县",
3606
+    "620423": "景泰县"
3607
+  },
3608
+  "620500": {
3609
+    "620502": "秦州区",
3610
+    "620503": "麦积区",
3611
+    "620521": "清水县",
3612
+    "620522": "秦安县",
3613
+    "620523": "甘谷县",
3614
+    "620524": "武山县",
3615
+    "620525": "张家川回族自治县"
3616
+  },
3617
+  "620600": {
3618
+    "620602": "凉州区",
3619
+    "620621": "民勤县",
3620
+    "620622": "古浪县",
3621
+    "620623": "天祝藏族自治县"
3622
+  },
3623
+  "620700": {
3624
+    "620702": "甘州区",
3625
+    "620721": "肃南裕固族自治县",
3626
+    "620722": "民乐县",
3627
+    "620723": "临泽县",
3628
+    "620724": "高台县",
3629
+    "620725": "山丹县"
3630
+  },
3631
+  "620800": {
3632
+    "620802": "崆峒区",
3633
+    "620821": "泾川县",
3634
+    "620822": "灵台县",
3635
+    "620823": "崇信县",
3636
+    "620824": "华亭县",
3637
+    "620825": "庄浪县",
3638
+    "620826": "静宁县"
3639
+  },
3640
+  "620900": {
3641
+    "620902": "肃州区",
3642
+    "620921": "金塔县",
3643
+    "620922": "瓜州县",
3644
+    "620923": "肃北蒙古族自治县",
3645
+    "620924": "阿克塞哈萨克族自治县",
3646
+    "620981": "玉门市",
3647
+    "620982": "敦煌市"
3648
+  },
3649
+  "621000": {
3650
+    "621002": "西峰区",
3651
+    "621021": "庆城县",
3652
+    "621022": "环县",
3653
+    "621023": "华池县",
3654
+    "621024": "合水县",
3655
+    "621025": "正宁县",
3656
+    "621026": "宁县",
3657
+    "621027": "镇原县"
3658
+  },
3659
+  "621100": {
3660
+    "621102": "安定区",
3661
+    "621121": "通渭县",
3662
+    "621122": "陇西县",
3663
+    "621123": "渭源县",
3664
+    "621124": "临洮县",
3665
+    "621125": "漳县",
3666
+    "621126": "岷县"
3667
+  },
3668
+  "621200": {
3669
+    "621202": "武都区",
3670
+    "621221": "成县",
3671
+    "621222": "文县",
3672
+    "621223": "宕昌县",
3673
+    "621224": "康县",
3674
+    "621225": "西和县",
3675
+    "621226": "礼县",
3676
+    "621227": "徽县",
3677
+    "621228": "两当县"
3678
+  },
3679
+  "622900": {
3680
+    "622901": "临夏市",
3681
+    "622921": "临夏县",
3682
+    "622922": "康乐县",
3683
+    "622923": "永靖县",
3684
+    "622924": "广河县",
3685
+    "622925": "和政县",
3686
+    "622926": "东乡族自治县",
3687
+    "622927": "积石山保安族东乡族撒拉族自治县"
3688
+  },
3689
+  "623000": {
3690
+    "623001": "合作市",
3691
+    "623021": "临潭县",
3692
+    "623022": "卓尼县",
3693
+    "623023": "舟曲县",
3694
+    "623024": "迭部县",
3695
+    "623025": "玛曲县",
3696
+    "623026": "碌曲县",
3697
+    "623027": "夏河县"
3698
+  },
3699
+  "630000": {
3700
+    "630100": "西宁市",
3701
+    "630200": "海东市",
3702
+    "632200": "海北藏族自治州",
3703
+    "632300": "黄南藏族自治州",
3704
+    "632500": "海南藏族自治州",
3705
+    "632600": "果洛藏族自治州",
3706
+    "632700": "玉树藏族自治州",
3707
+    "632800": "海西蒙古族藏族自治州"
3708
+  },
3709
+  "630100": {
3710
+    "630102": "城东区",
3711
+    "630103": "城中区",
3712
+    "630104": "城西区",
3713
+    "630105": "城北区",
3714
+    "630121": "大通回族土族自治县",
3715
+    "630122": "湟中县",
3716
+    "630123": "湟源县"
3717
+  },
3718
+  "630200": {
3719
+    "630202": "乐都区",
3720
+    "630203": "平安区",
3721
+    "630222": "民和回族土族自治县",
3722
+    "630223": "互助土族自治县",
3723
+    "630224": "化隆回族自治县",
3724
+    "630225": "循化撒拉族自治县"
3725
+  },
3726
+  "632200": {
3727
+    "632221": "门源回族自治县",
3728
+    "632222": "祁连县",
3729
+    "632223": "海晏县",
3730
+    "632224": "刚察县"
3731
+  },
3732
+  "632300": {
3733
+    "632321": "同仁县",
3734
+    "632322": "尖扎县",
3735
+    "632323": "泽库县",
3736
+    "632324": "河南蒙古族自治县"
3737
+  },
3738
+  "632500": {
3739
+    "632521": "共和县",
3740
+    "632522": "同德县",
3741
+    "632523": "贵德县",
3742
+    "632524": "兴海县",
3743
+    "632525": "贵南县"
3744
+  },
3745
+  "632600": {
3746
+    "632621": "玛沁县",
3747
+    "632622": "班玛县",
3748
+    "632623": "甘德县",
3749
+    "632624": "达日县",
3750
+    "632625": "久治县",
3751
+    "632626": "玛多县"
3752
+  },
3753
+  "632700": {
3754
+    "632701": "玉树市",
3755
+    "632722": "杂多县",
3756
+    "632723": "称多县",
3757
+    "632724": "治多县",
3758
+    "632725": "囊谦县",
3759
+    "632726": "曲麻莱县"
3760
+  },
3761
+  "632800": {
3762
+    "632801": "格尔木市",
3763
+    "632802": "德令哈市",
3764
+    "632821": "乌兰县",
3765
+    "632822": "都兰县",
3766
+    "632823": "天峻县"
3767
+  },
3768
+  "640000": {
3769
+    "640100": "银川市",
3770
+    "640200": "石嘴山市",
3771
+    "640300": "吴忠市",
3772
+    "640400": "固原市",
3773
+    "640500": "中卫市"
3774
+  },
3775
+  "640100": {
3776
+    "640104": "兴庆区",
3777
+    "640105": "西夏区",
3778
+    "640106": "金凤区",
3779
+    "640121": "永宁县",
3780
+    "640122": "贺兰县",
3781
+    "640181": "灵武市"
3782
+  },
3783
+  "640200": {
3784
+    "640202": "大武口区",
3785
+    "640205": "惠农区",
3786
+    "640221": "平罗县"
3787
+  },
3788
+  "640300": {
3789
+    "640302": "利通区",
3790
+    "640303": "红寺堡区",
3791
+    "640323": "盐池县",
3792
+    "640324": "同心县",
3793
+    "640381": "青铜峡市"
3794
+  },
3795
+  "640400": {
3796
+    "640402": "原州区",
3797
+    "640422": "西吉县",
3798
+    "640423": "隆德县",
3799
+    "640424": "泾源县",
3800
+    "640425": "彭阳县"
3801
+  },
3802
+  "640500": {
3803
+    "640502": "沙坡头区",
3804
+    "640521": "中宁县",
3805
+    "640522": "海原县"
3806
+  },
3807
+  "650000": {
3808
+    "650100": "乌鲁木齐市",
3809
+    "650200": "克拉玛依市",
3810
+    "650400": "吐鲁番市",
3811
+    "650500": "哈密市",
3812
+    "652300": "昌吉回族自治州",
3813
+    "652700": "博尔塔拉蒙古自治州",
3814
+    "652800": "巴音郭楞蒙古自治州",
3815
+    "652900": "阿克苏地区",
3816
+    "653000": "克孜勒苏柯尔克孜自治州",
3817
+    "653100": "喀什地区",
3818
+    "653200": "和田地区",
3819
+    "654000": "伊犁哈萨克自治州",
3820
+    "654200": "塔城地区",
3821
+    "654300": "阿勒泰地区",
3822
+    "659001": "石河子市",
3823
+    "659002": "阿拉尔市",
3824
+    "659003": "图木舒克市",
3825
+    "659004": "五家渠市",
3826
+    "659006": "铁门关市"
3827
+  },
3828
+  "650100": {
3829
+    "650102": "天山区",
3830
+    "650103": "沙依巴克区",
3831
+    "650104": "新市区",
3832
+    "650105": "水磨沟区",
3833
+    "650106": "头屯河区",
3834
+    "650107": "达坂城区",
3835
+    "650109": "米东区",
3836
+    "650121": "乌鲁木齐县"
3837
+  },
3838
+  "650200": {
3839
+    "650202": "独山子区",
3840
+    "650203": "克拉玛依区",
3841
+    "650204": "白碱滩区",
3842
+    "650205": "乌尔禾区"
3843
+  },
3844
+  "650400": {
3845
+    "650402": "高昌区",
3846
+    "650421": "鄯善县",
3847
+    "650422": "托克逊县"
3848
+  },
3849
+  "650500": {
3850
+    "650502": "伊州区",
3851
+    "650521": "巴里坤哈萨克自治县",
3852
+    "650522": "伊吾县"
3853
+  },
3854
+  "652300": {
3855
+    "652301": "昌吉市",
3856
+    "652302": "阜康市",
3857
+    "652323": "呼图壁县",
3858
+    "652324": "玛纳斯县",
3859
+    "652325": "奇台县",
3860
+    "652327": "吉木萨尔县",
3861
+    "652328": "木垒哈萨克自治县"
3862
+  },
3863
+  "652700": {
3864
+    "652701": "博乐市",
3865
+    "652702": "阿拉山口市",
3866
+    "652722": "精河县",
3867
+    "652723": "温泉县"
3868
+  },
3869
+  "652800": {
3870
+    "652801": "库尔勒市",
3871
+    "652822": "轮台县",
3872
+    "652823": "尉犁县",
3873
+    "652824": "若羌县",
3874
+    "652825": "且末县",
3875
+    "652826": "焉耆回族自治县",
3876
+    "652827": "和静县",
3877
+    "652828": "和硕县",
3878
+    "652829": "博湖县"
3879
+  },
3880
+  "652900": {
3881
+    "652901": "阿克苏市",
3882
+    "652922": "温宿县",
3883
+    "652923": "库车县",
3884
+    "652924": "沙雅县",
3885
+    "652925": "新和县",
3886
+    "652926": "拜城县",
3887
+    "652927": "乌什县",
3888
+    "652928": "阿瓦提县",
3889
+    "652929": "柯坪县"
3890
+  },
3891
+  "653000": {
3892
+    "653001": "阿图什市",
3893
+    "653022": "阿克陶县",
3894
+    "653023": "阿合奇县",
3895
+    "653024": "乌恰县"
3896
+  },
3897
+  "653100": {
3898
+    "653101": "喀什市",
3899
+    "653121": "疏附县",
3900
+    "653122": "疏勒县",
3901
+    "653123": "英吉沙县",
3902
+    "653124": "泽普县",
3903
+    "653125": "莎车县",
3904
+    "653126": "叶城县",
3905
+    "653127": "麦盖提县",
3906
+    "653128": "岳普湖县",
3907
+    "653129": "伽师县",
3908
+    "653130": "巴楚县",
3909
+    "653131": "塔什库尔干塔吉克自治县"
3910
+  },
3911
+  "653200": {
3912
+    "653201": "和田市",
3913
+    "653221": "和田县",
3914
+    "653222": "墨玉县",
3915
+    "653223": "皮山县",
3916
+    "653224": "洛浦县",
3917
+    "653225": "策勒县",
3918
+    "653226": "于田县",
3919
+    "653227": "民丰县"
3920
+  },
3921
+  "654000": {
3922
+    "654002": "伊宁市",
3923
+    "654003": "奎屯市",
3924
+    "654004": "霍尔果斯市",
3925
+    "654021": "伊宁县",
3926
+    "654022": "察布查尔锡伯自治县",
3927
+    "654023": "霍城县",
3928
+    "654024": "巩留县",
3929
+    "654025": "新源县",
3930
+    "654026": "昭苏县",
3931
+    "654027": "特克斯县",
3932
+    "654028": "尼勒克县"
3933
+  },
3934
+  "654200": {
3935
+    "654201": "塔城市",
3936
+    "654202": "乌苏市",
3937
+    "654221": "额敏县",
3938
+    "654223": "沙湾县",
3939
+    "654224": "托里县",
3940
+    "654225": "裕民县",
3941
+    "654226": "和布克赛尔蒙古自治县"
3942
+  },
3943
+  "654300": {
3944
+    "654301": "阿勒泰市",
3945
+    "654321": "布尔津县",
3946
+    "654322": "富蕴县",
3947
+    "654323": "福海县",
3948
+    "654324": "哈巴河县",
3949
+    "654325": "青河县",
3950
+    "654326": "吉木乃县"
3951
+  },
3952
+  "810000": {
3953
+    "810001": "中西區",
3954
+    "810002": "灣仔區",
3955
+    "810003": "東區",
3956
+    "810004": "南區",
3957
+    "810005": "油尖旺區",
3958
+    "810006": "深水埗區",
3959
+    "810007": "九龍城區",
3960
+    "810008": "黃大仙區",
3961
+    "810009": "觀塘區",
3962
+    "810010": "荃灣區",
3963
+    "810011": "屯門區",
3964
+    "810012": "元朗區",
3965
+    "810013": "北區",
3966
+    "810014": "大埔區",
3967
+    "810015": "西貢區",
3968
+    "810016": "沙田區",
3969
+    "810017": "葵青區",
3970
+    "810018": "離島區"
3971
+  },
3972
+  "820000": {
3973
+    "820001": "花地瑪堂區",
3974
+    "820002": "花王堂區",
3975
+    "820003": "望德堂區",
3976
+    "820004": "大堂區",
3977
+    "820005": "風順堂區",
3978
+    "820006": "嘉模堂區",
3979
+    "820007": "路氹填海區",
3980
+    "820008": "聖方濟各堂區"
3981
+  }
3982
+}

+ 7 - 0
data/tests.py

@@ -0,0 +1,7 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.test import TestCase
5
+
6
+
7
+# Create your tests here.

+ 7 - 0
data/views.py

@@ -0,0 +1,7 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.shortcuts import render
5
+
6
+
7
+# Create your views here.

+ 0 - 0
docs/COMMANDS.md


+ 2 - 0
docs/CRONTAB.md

@@ -0,0 +1,2 @@
1
+# 定时任务
2
+# m h  dom mon dow   command

+ 3 - 0
isort.sh

@@ -0,0 +1,3 @@
1
+#!/bin/bash
2
+
3
+isort .

+ 23 - 0
manage.py

@@ -0,0 +1,23 @@
1
+#!/usr/bin/env python
2
+import os
3
+import sys
4
+
5
+
6
+if __name__ == "__main__":
7
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tiwen.settings")
8
+    try:
9
+        from django.core.management import execute_from_command_line
10
+    except ImportError:
11
+        # The above import may fail for some other reason. Ensure that the
12
+        # issue is really that Django is missing to avoid masking other
13
+        # exceptions on Python 2.
14
+        try:
15
+            import django
16
+        except ImportError:
17
+            raise ImportError(
18
+                "Couldn't import Django. Are you sure it's installed and "
19
+                "available on your PYTHONPATH environment variable? Did you "
20
+                "forget to activate a virtual environment?"
21
+            )
22
+        raise
23
+    execute_from_command_line(sys.argv)

+ 10 - 0
pep8.sh

@@ -0,0 +1,10 @@
1
+#!/bin/bash
2
+
3
+# Ignoring autogenerated files
4
+#  -- Migration directories
5
+# Ignoring error codes
6
+#  -- E128 continuation line under-indented for visual indent
7
+#  -- E402 module level import not at top of file
8
+#  -- E501 line too long
9
+
10
+pycodestyle --exclude=build,migrations,.tox --ignore=E128,E402,E501 .

+ 8 - 0
requirements.txt

@@ -0,0 +1,8 @@
1
+StatusCode==1.0.0
2
+furl==2.1.3
3
+jsonfield==3.1.0
4
+mysqlclient==2.1.1
5
+rlog==0.3
6
+-r requirements_dj.txt
7
+-r requirements_pywe.txt
8
+-r requirements_redis.txt

+ 3 - 0
requirements_deploy.txt

@@ -0,0 +1,3 @@
1
+ipdb==0.13.9
2
+ipython==7.34.0
3
+uwsgi==2.0.20

+ 2 - 0
requirements_dev.txt

@@ -0,0 +1,2 @@
1
+isort==5.10.1
2
+pycodestyle==2.8.0

+ 15 - 0
requirements_dj.txt

@@ -0,0 +1,15 @@
1
+Django==3.2.14
2
+django-admin==2.0.1
3
+django-detect==1.0.20
4
+django-file==1.0.4
5
+django-json-render==1.0.3
6
+django-json-response==1.1.5
7
+django-logit==1.1.3
8
+django-models-ext==1.1.10
9
+django-redis-connector==1.0.4
10
+django-response==1.1.1
11
+django-rlog==1.0.7
12
+django-short-url==1.1.6
13
+django-six==1.0.4
14
+django-uniapi==1.0.10
15
+django-we==1.5.6

+ 2 - 0
requirements_pywe.txt

@@ -0,0 +1,2 @@
1
+pywe-oauth==1.1.1
2
+pywe-pay==1.0.14

+ 3 - 0
requirements_redis.txt

@@ -0,0 +1,3 @@
1
+hiredis==2.0.0
2
+redis==4.3.4
3
+redis-extensions==4.0.1

+ 4 - 0
sysctl.sh

@@ -0,0 +1,4 @@
1
+#!/bin/bash
2
+
3
+sudo sysctl -w net.core.somaxconn=65535
4
+sudo sysctl -w net.core.netdev_max_backlog=65535

+ 0 - 0
tiwen/__init__.py


+ 30 - 0
tiwen/basemodels.py

@@ -0,0 +1,30 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.db import models
4
+from django.utils.translation import gettext_lazy as _
5
+
6
+
7
+class BaseModelMixin(models.Model):
8
+    status = models.BooleanField(_('status'), default=True, help_text=_('Status'))
9
+    created_at = models.DateTimeField(_('created_at'), auto_now_add=True, editable=True, help_text=_('Create Time'))
10
+    updated_at = models.DateTimeField(_('updated_at'), auto_now=True, editable=True, help_text=_('Update Time'))
11
+
12
+    class Meta:
13
+        abstract = True
14
+
15
+
16
+class SexModelMixin(models.Model):
17
+    UNKNOWN = 0
18
+    MALE = 1
19
+    FEMALE = 2
20
+
21
+    SEX_TUPLE = (
22
+        (UNKNOWN, '未知'),
23
+        (MALE, '男'),
24
+        (FEMALE, '女'),
25
+    )
26
+
27
+    sex = models.IntegerField(_('sex'), choices=SEX_TUPLE, default=UNKNOWN, help_text=_('Sex'))
28
+
29
+    class Meta:
30
+        abstract = True

+ 93 - 0
tiwen/decorators.py

@@ -0,0 +1,93 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from functools import wraps
4
+
5
+from django.conf import settings
6
+from django.shortcuts import redirect
7
+from furl import furl
8
+from pywe_oauth import get_oauth_redirect_url
9
+from pywe_sign import check_signature
10
+
11
+from utils.error.errno_utils import SignatureStatusCode
12
+from utils.error.response_utils import response
13
+from utils.redis.connect import r
14
+
15
+
16
+def check_user_cookie(func=None, key=settings.COOKIE_USER_CHECK_KEY):
17
+    def decorator(func):
18
+        @wraps(func)
19
+        def returned_wrapper(request, *args, **kwargs):
20
+            user_id = request.get_signed_cookie(key, default='', salt=settings.COOKIE_SALT)
21
+            if not user_id:
22
+                return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', request.get_full_path()))
23
+            return func(request, *args, **kwargs)
24
+        return returned_wrapper
25
+
26
+    if not func:
27
+        def foo(func):
28
+            return decorator(func)
29
+        return foo
30
+
31
+    return decorator(func)
32
+
33
+
34
+def check_token(func=None, entry=None):
35
+    def decorator(func):
36
+        @wraps(func)
37
+        def returned_wrapper(request, *args, **kwargs):
38
+            if not settings.DEBUG and request.wechat:
39
+                vtoken = request.GET.get('vtoken', '') or request.POST.get('vtoken', '')
40
+                token_check_key = request.GET.get(settings.TOKEN_CHECK_KEY, '') or request.POST.get(settings.TOKEN_CHECK_KEY, '')
41
+                if not r.token_exists(token_check_key, vtoken):
42
+                    # 3rd OAuth
43
+                    # return redirect(settings.WECHAT_OAUTH2_REDIRECT_URL)
44
+                    # Current OAuth
45
+                    redirect_url = furl(entry or settings.WECHAT_OAUTH2_REDIRECT_ENTRY).add({}).url
46
+                    return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', redirect_url))
47
+            return func(request, *args, **kwargs)
48
+        return returned_wrapper
49
+
50
+    if not func:
51
+        def foo(func):
52
+            return decorator(func)
53
+        return foo
54
+
55
+    return decorator(func)
56
+
57
+
58
+def check_sign(func=None, method='POST'):
59
+    def decorator(func):
60
+        @wraps(func)
61
+        def returned_wrapper(request, *args, **kwargs):
62
+            if not settings.DEBUG and not check_signature(getattr(request, method).dict(), settings.PARAMS_SIGN_KEY):
63
+                return response(SignatureStatusCode.SIGNATURE_ERROR)
64
+            return func(request, *args, **kwargs)
65
+        return returned_wrapper
66
+
67
+    if not func:
68
+        def foo(func):
69
+            return decorator(func)
70
+        return foo
71
+
72
+    return decorator(func)
73
+
74
+
75
+def check_cookie(func=None, entry=None):
76
+    def decorator(func):
77
+        @wraps(func)
78
+        def returned_wrapper(request, *args, **kwargs):
79
+            if not settings.DEBUG and not request.COOKIES.get('user_id'):
80
+                # 3rd OAuth
81
+                # return redirect(settings.WECHAT_OAUTH2_REDIRECT_URL)
82
+                # Current OAuth
83
+                redirect_url = furl(entry or settings.WECHAT_OAUTH2_REDIRECT_ENTRY).add({}).url
84
+                return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', redirect_url))
85
+            return func(request, *args, **kwargs)
86
+        return returned_wrapper
87
+
88
+    if not func:
89
+        def foo(func):
90
+            return decorator(func)
91
+        return foo
92
+
93
+    return decorator(func)

+ 12 - 0
tiwen/deploy.bak/supervisor_commands/pollqueue.ini

@@ -0,0 +1,12 @@
1
+[program:pollqueue]
2
+command=/home/diors/env/bin/python /home/diors/work/tiwen/manage.py poll_queue
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_pollqueue_access.log
11
+stderr_logfile=/var/log/supervisor_pollqueue_error.log
12
+user=diors

+ 12 - 0
tiwen/deploy.bak/supervisor_commands/rlistlog.ini

@@ -0,0 +1,12 @@
1
+[program:rlistlog]
2
+command=/home/diors/env/bin/python /home/diors/work/tiwen/manage.py rlistlog --key=django:logit:tiwen --filename=/tmp/tiwen.logit.log
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_rlistlog_access.log
11
+stderr_logfile=/var/log/supervisor_rlistlog_error.log
12
+user=diors

+ 35 - 0
tiwen/deploy.bak/tiwen.ini

@@ -0,0 +1,35 @@
1
+# Refer: https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
2
+# tiwen_uwsgi.ini file
3
+[uwsgi]
4
+
5
+# Django-related settings
6
+# the base directory (full path)
7
+chdir           = /home/diors/work/tiwen
8
+# Django's wsgi file
9
+module          = tiwen.wsgi
10
+# the virtualenv (full path)
11
+# home            = /path/to/virtualenv
12
+
13
+# process-related settings
14
+# master
15
+master          = true
16
+# maximum number of worker processes
17
+processes       = 10
18
+# the socket (use the full path to be safe
19
+socket          = /home/diors/work/tiwen/tiwen/deploy/tiwen.sock
20
+# ... with appropriate permissions - may be needed
21
+chmod-socket    = 777
22
+# clear environment on exit
23
+vacuum          = true
24
+
25
+# connect() to unix:///home/xxx/xxx.sock failed (11: Resource temporarily unavailable) while connecting to upstream
26
+# Exec sysctl.sh first
27
+# #!/bin/bash
28
+# sudo sysctl -w net.core.somaxconn=65535
29
+# sudo sysctl -w net.core.netdev_max_backlog=65535
30
+reload-mercy    = 64
31
+max-requests    = 8192
32
+listen          = 8192
33
+
34
+# recv() failed (104: Connection reset by peer) while reading response header from upstream
35
+buffer-size     = 65535

+ 35 - 0
tiwen/deploy.bak/tiwen2.ini

@@ -0,0 +1,35 @@
1
+# Refer: https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
2
+# tiwen_uwsgi.ini file
3
+[uwsgi]
4
+
5
+# Django-related settings
6
+# the base directory (full path)
7
+chdir           = /home/diors/work/tiwen
8
+# Django's wsgi file
9
+module          = tiwen.wsgi
10
+# the virtualenv (full path)
11
+# home            = /path/to/virtualenv
12
+
13
+# process-related settings
14
+# master
15
+master          = true
16
+# maximum number of worker processes
17
+processes       = 10
18
+# the socket (use the full path to be safe
19
+socket          = /home/diors/work/tiwen/tiwen/deploy/tiwen2.sock
20
+# ... with appropriate permissions - may be needed
21
+chmod-socket    = 777
22
+# clear environment on exit
23
+vacuum          = true
24
+
25
+# connect() to unix:///home/xxx/xxx.sock failed (11: Resource temporarily unavailable) while connecting to upstream
26
+# Exec sysctl.sh first
27
+# #!/bin/bash
28
+# sudo sysctl -w net.core.somaxconn=65535
29
+# sudo sysctl -w net.core.netdev_max_backlog=65535
30
+reload-mercy    = 64
31
+max-requests    = 8192
32
+listen          = 8192
33
+
34
+# recv() failed (104: Connection reset by peer) while reading response header from upstream
35
+buffer-size     = 65535

+ 96 - 0
tiwen/deploy.bak/tiwen_nginx.conf

@@ -0,0 +1,96 @@
1
+# tiwen_nginx.conf
2
+
3
+# the upstream component nginx needs to connect to
4
+upstream tiwen {
5
+    # Single Server
6
+    # server unix:///home/diors/work/tiwen/tiwen/deploy/tiwen.sock; # for a file socket
7
+    # Multi Server
8
+    # server unix:///home/diors/work/tiwen/tiwen/deploy/tiwen.sock max_fails=0 weight=1; # for a file socket
9
+    # server unix:///home/diors/work/tiwen/tiwen/deploy/tiwen2.sock max_fails=0 weight=1; # for a file socket
10
+    server 127.0.0.1:8888; # for a web port socket (we'll use this first)
11
+}
12
+
13
+# configuration of the server
14
+server {
15
+    # the port your site will be served on
16
+    listen      80;
17
+    # the domain name it will serve for
18
+    server_name .a.com; # substitute your machine's IP address or FQDN
19
+    charset     utf-8;
20
+
21
+    # access_log off;  # 设置是否存储访问日志
22
+    # access_log /var/log/nginx/tiwen_access.log;
23
+    # error_log /var/log/nginx/tiwen_error.log;
24
+
25
+    # max upload size
26
+    client_max_body_size 75M;   # adjust to taste
27
+
28
+    # JS接口安全域名 & 业务域名 验证
29
+    location /xxx.txt {
30
+        alias /home/diors/work/tiwen/docs/we/xxx.txt;
31
+    }
32
+
33
+    # Django media
34
+    location /media  {
35
+        alias /home/diors/work/tiwen/media;  # your Django project's media files - amend as required
36
+    }
37
+
38
+    location /static {
39
+        alias /home/diors/work/tiwen/collect_static; # your Django project's static files - amend as required
40
+    }
41
+
42
+    # Finally, send all non-media requests to the Django server.
43
+    location / {
44
+        # uwsgi_pass  tiwen;
45
+        proxy_pass  http://tiwen;
46
+        include     /home/diors/work/tiwen/tiwen/deploy/uwsgi_params; # the uwsgi_params file you installed
47
+    }
48
+}
49
+
50
+# configuration of the server
51
+server {
52
+    # the port your site will be served on
53
+    listen      443;
54
+    # the domain name it will serve for
55
+    server_name .a.com; # substitute your machine's IP address or FQDN
56
+    charset     utf-8;
57
+
58
+    # access_log /var/log/nginx/tiwen_ssl_access.log;
59
+    # error_log /var/log/nginx/tiwen_ssl_error.log;
60
+
61
+    ssl on;
62
+    ssl_certificate   cert/214180103970874.pem;
63
+    ssl_certificate_key  cert/214180103970874.key;
64
+    ssl_session_timeout 5m;
65
+    # worker process * exited on signal 11
66
+    # 同一台服务器部署多个 SSL 转发,Nginx 默认是开启 Session 缓存的,导致冲突,验证不通过,连接直接退出
67
+    # 解决方案:禁用 Ningx SSL Session 的缓存
68
+    ssl_session_cache none;
69
+    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
70
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
71
+    ssl_prefer_server_ciphers on;
72
+
73
+    # max upload size
74
+    client_max_body_size 75M;   # adjust to taste
75
+
76
+    # JS接口安全域名 & 业务域名 验证
77
+    location /xxx.txt {
78
+        alias /home/diors/work/tiwen/docs/we/xxx.txt;
79
+    }
80
+
81
+    # Django media
82
+    location /media  {
83
+        alias /home/diors/work/tiwen/media;  # your Django project's media files - amend as required
84
+    }
85
+
86
+    location /static {
87
+        alias /home/diors/work/tiwen/collect_static; # your Django project's static files - amend as required
88
+    }
89
+
90
+    # Finally, send all non-media requests to the Django server.
91
+    location / {
92
+        # uwsgi_pass  tiwen;
93
+        proxy_pass  http://tiwen;
94
+        include     /home/diors/work/tiwen/tiwen/deploy/uwsgi_params; # the uwsgi_params file you installed
95
+    }
96
+}

+ 12 - 0
tiwen/deploy.bak/tiwen_supervisor.ini

@@ -0,0 +1,12 @@
1
+[program:tiwen]
2
+command=/home/diors/env/bin/uwsgi --ini /home/diors/work/tiwen/tiwen/deploy/tiwen.ini
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_tiwen_access.log
11
+stderr_logfile=/var/log/supervisor_tiwen_error.log
12
+user=diors

+ 12 - 0
tiwen/deploy.bak/tiwen_supervisor2.ini

@@ -0,0 +1,12 @@
1
+[program:tiwen2]
2
+command=/home/diors/env/bin/uwsgi --ini /home/diors/work/tiwen/tiwen/deploy/tiwen2.ini
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_tiwen2_access.log
11
+stderr_logfile=/var/log/supervisor_tiwen2_error.log
12
+user=diors

+ 15 - 0
tiwen/deploy.bak/uwsgi_params

@@ -0,0 +1,15 @@
1
+uwsgi_param	QUERY_STRING		$query_string;
2
+uwsgi_param	REQUEST_METHOD		$request_method;
3
+uwsgi_param	CONTENT_TYPE		$content_type;
4
+uwsgi_param	CONTENT_LENGTH		$content_length;
5
+
6
+uwsgi_param	REQUEST_URI		$request_uri;
7
+uwsgi_param	PATH_INFO		$document_uri;
8
+uwsgi_param	DOCUMENT_ROOT		$document_root;
9
+uwsgi_param	SERVER_PROTOCOL		$server_protocol;
10
+uwsgi_param	UWSGI_SCHEME		$scheme;
11
+
12
+uwsgi_param	REMOTE_ADDR		$remote_addr;
13
+uwsgi_param	REMOTE_PORT		$remote_port;
14
+uwsgi_param	SERVER_PORT		$server_port;
15
+uwsgi_param	SERVER_NAME		$server_name;

+ 5 - 0
tiwen/django_file_callback_settings.py

@@ -0,0 +1,5 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def DJANGO_FILE_UPLOAD_CALLBACK_FUNC(request, file_path=None, file_url=None):
5
+    """ DJANGO FILE UPLOAD Callback Func """

+ 84 - 0
tiwen/django_we_callback_settings.py

@@ -0,0 +1,84 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def DJANGO_WE_CFG_FUNC(request, state=None):
5
+    """ WeChat CFG Callback Func """
6
+
7
+
8
+def DJANGO_WE_QUOTE_STATE_FUNC(request, state):
9
+    """ WeChat Quote Callback Func """
10
+    from utils.redis.connect import r
11
+    return r.quote(state, short_uuid=True)
12
+
13
+
14
+def DJANGO_WE_UNQUOTE_STATE_FUNC(request, state):
15
+    """ WeChat UnQuote Callback Func """
16
+    from utils.redis.connect import r
17
+
18
+    # If Not Buf, When Wechat Multi Request, Unquote Will Get None
19
+    # Then Once Should ReOAuth, Will Raise Error
20
+    return r.unquote(state, buf=True) or state
21
+
22
+
23
+def DJANGO_WE_BASE_FUNC(code, state, access_info=None):
24
+    """ WeChat Base Redirect Callback Func """
25
+
26
+
27
+def DJANGO_WE_BASE_COOKIE_FUNC(code, state, access_info=None):
28
+    """ WeChat Base Set Cookie Redirect Callback Func """
29
+
30
+
31
+def DJANGO_WE_USERINFO_FUNC(code, state, access_info=None, userinfo=None):
32
+    """ WeChat Userinfo Redirect Callback Func """
33
+    from django.conf import settings
34
+
35
+    from utils.redis.connect import r
36
+    from utils.user.userinfo_save import userinfo_save
37
+
38
+    # Save profile or something else
39
+    user = userinfo_save(userinfo)
40
+
41
+    token_check_key = getattr(user, settings.TOKEN_CHECK_KEY)
42
+
43
+    return {
44
+        settings.TOKEN_CHECK_KEY: token_check_key,
45
+        'vtoken': r.token(token_check_key, ex=True, time=r.REDIS_EXPIRED_ONE_DAY, buf=False, short_uuid=True),
46
+    }
47
+
48
+
49
+def DJANGO_WE_USERINFO_COOKIE_FUNC(code, state, access_info=None, userinfo=None):
50
+    """ WeChat Userinfo Set Cookie Redirect Callback Func """
51
+    from django.conf import settings
52
+
53
+    from utils.user.userinfo_save import userinfo_save
54
+
55
+    # Save profile or something else
56
+    user = userinfo_save(userinfo)
57
+
58
+    token_check_key = getattr(user, settings.TOKEN_CHECK_KEY)
59
+
60
+    return {}, settings.TOKEN_CHECK_KEY, token_check_key
61
+
62
+
63
+def DJANGO_WE_SHARE_FUNC(request, state=None):
64
+    """ WeChat Share Callback Func """
65
+    # from django.conf import settings
66
+    # return settings.WECHAT_OAUTH2_REDIRECT_URL
67
+
68
+
69
+def DJANGO_WE_MESSAGE_CALLBACK_FUNC(request, xmldict, decrypted):
70
+    """ WeChat Message Callback Func """
71
+
72
+
73
+def DJANGO_WE_COMPONENT_AUTH_FUNC(request, xmldict, decrypted):
74
+    """ WeChat COMPONENT Auth Func """
75
+
76
+
77
+def DJANGO_WE_COMPONENT_CALLBACK_FUNC(request, appid, xmldict, decrypted):
78
+    """ WeChat COMPONENT Callback Func """
79
+
80
+
81
+def DJANGO_WE_REDIS_OBJ_FUNC(request):
82
+    """ WeChat Redis Object Callback Func """
83
+    from utils.redis.connect import r
84
+    return r

+ 16 - 0
tiwen/func_settings.py

@@ -0,0 +1,16 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import redis_extensions as redis
4
+
5
+
6
+def redis_conf(conf):
7
+    return {
8
+        'host': conf.get('HOST', 'localhost'),
9
+        'port': conf.get('PORT', 6379),
10
+        'password': '{0}:{1}'.format(conf.get('USER', ''), conf.get('PASSWORD', '')) if conf.get('USER') else '',
11
+        'db': conf.get('db', 0),
12
+    }
13
+
14
+
15
+def redis_connect(conf):
16
+    return redis.StrictRedisExtensions(connection_pool=redis.ConnectionPool(**redis_conf(conf)))

+ 17 - 0
tiwen/local_settings_bak.py

@@ -0,0 +1,17 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+# DEBUG = False
4
+
5
+ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'tiwen']
6
+
7
+# DOMAIN
8
+DOMAIN = 'http://a.com'
9
+
10
+# 邮件设置
11
+# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
12
+SERVER_EMAIL = 'error.notify@exmail.com'
13
+EMAIL_HOST_USER = 'error.notify@exmail.com'
14
+EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
15
+DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
16
+ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
17
+EMAIL_SUBJECT_PREFIX = '[Templet] '

+ 27 - 0
tiwen/local_settings_dev_bak.py

@@ -0,0 +1,27 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import os
4
+
5
+
6
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
8
+
9
+TEMPLATES = [
10
+    {
11
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
12
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
13
+        # 'APP_DIRS': True,
14
+        'OPTIONS': {
15
+            'context_processors': [
16
+                'django.template.context_processors.debug',
17
+                'django.template.context_processors.request',
18
+                'django.contrib.auth.context_processors.auth',
19
+                'django.contrib.messages.context_processors.messages',
20
+            ],
21
+            'loaders': [
22
+                'django.template.loaders.filesystem.Loader',
23
+                'django.template.loaders.app_directories.Loader',
24
+            ],
25
+        },
26
+    },
27
+]

+ 401 - 0
tiwen/settings.py

@@ -0,0 +1,401 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""
4
+Django settings for tiwen project.
5
+
6
+Generated by 'django-admin startproject' using Django 1.11.3.
7
+
8
+For more information on this file, see
9
+https://docs.djangoproject.com/en/1.11/topics/settings/
10
+
11
+For the full list of settings and their values, see
12
+https://docs.djangoproject.com/en/1.11/ref/settings/
13
+"""
14
+
15
+import os
16
+
17
+# try:
18
+#     from func_settings import redis_connect
19
+#     REDIS_CACHE = redis_connect(REDIS.get('default', {}))
20
+# except ImportError:
21
+#     REDIS_CACHE = None
22
+from django_redis_connector import connector
23
+
24
+
25
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
26
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27
+PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
28
+
29
+
30
+# Quick-start development settings - unsuitable for production
31
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
32
+
33
+# SECURITY WARNING: keep the secret key used in production secret!
34
+SECRET_KEY = '0=hpv21&am(7(k5ab!^zjvvl=ntj)^i@7)87t47uzumt_5rq$+'
35
+
36
+# SECURITY WARNING: don't run with debug turned on in production!
37
+DEBUG = True
38
+
39
+ALLOWED_HOSTS = []
40
+
41
+
42
+# Application definition
43
+
44
+INSTALLED_APPS = [
45
+    'django.contrib.admin',
46
+    'django.contrib.auth',
47
+    'django.contrib.contenttypes',
48
+    'django.contrib.sessions',
49
+    'django.contrib.messages',
50
+    'django.contrib.staticfiles',
51
+    # 'django_file_upload',
52
+    # 'django_short_url',
53
+    'django_uniapi',
54
+    'django_admin',
55
+    # 'django_rlog',
56
+    'django_we',
57
+    'commands',
58
+    'api',
59
+]
60
+
61
+MIDDLEWARE = [
62
+    'django.middleware.security.SecurityMiddleware',
63
+    'django.contrib.sessions.middleware.SessionMiddleware',
64
+    'django.middleware.common.CommonMiddleware',
65
+    # 'django.middleware.csrf.CsrfViewMiddleware',
66
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
67
+    'django.contrib.messages.middleware.MessageMiddleware',
68
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
69
+    'detect.middleware.UserAgentDetectionMiddleware',
70
+]
71
+
72
+ROOT_URLCONF = 'tiwen.urls'
73
+
74
+TEMPLATES = [
75
+    {
76
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
77
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
78
+        # 'APP_DIRS': True,
79
+        'OPTIONS': {
80
+            'context_processors': [
81
+                'django.template.context_processors.debug',
82
+                'django.template.context_processors.request',
83
+                'django.contrib.auth.context_processors.auth',
84
+                'django.contrib.messages.context_processors.messages',
85
+            ],
86
+            'loaders': [
87
+                ('django.template.loaders.cached.Loader', [
88
+                    'django.template.loaders.filesystem.Loader',
89
+                    'django.template.loaders.app_directories.Loader',
90
+                ]),
91
+            ],
92
+        },
93
+    },
94
+]
95
+
96
+WSGI_APPLICATION = 'tiwen.wsgi.application'
97
+
98
+
99
+# Database
100
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
101
+
102
+DATABASES = {
103
+    # Create Database
104
+    # CREATE DATABASE tiwen DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
105
+    'default': {
106
+        'ENGINE': 'django.db.backends.mysql',
107
+        'NAME': 'tiwen',
108
+        'USER': 'root',
109
+        'PASSWORD': '',
110
+        'HOST': '127.0.0.1',
111
+        'PORT': 3306,
112
+        'CONN_MAX_AGE': 600,
113
+        'OPTIONS': {
114
+            'charset': 'utf8mb4',
115
+        },
116
+    }
117
+}
118
+
119
+
120
+# Password validation
121
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
122
+
123
+AUTH_PASSWORD_VALIDATORS = [
124
+    {
125
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
126
+    },
127
+    {
128
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
129
+    },
130
+    {
131
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
132
+    },
133
+    {
134
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
135
+    },
136
+]
137
+
138
+
139
+# Internationalization
140
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
141
+
142
+LANGUAGE_CODE = 'zh-Hans'
143
+
144
+TIME_ZONE = 'Asia/Shanghai'
145
+
146
+USE_I18N = True
147
+
148
+USE_L10N = True
149
+
150
+USE_TZ = True
151
+
152
+
153
+# Static files (CSS, JavaScript, Images)
154
+# https://docs.djangoproject.com/en/1.11/howto/static-files/
155
+
156
+STATICFILES_DIRS = (
157
+    os.path.join(PROJ_DIR, 'static').replace('\\', '/'),
158
+)
159
+
160
+STATIC_ROOT = os.path.join(BASE_DIR, 'collect_static').replace('\\', '/')
161
+
162
+STATIC_URL = '/static/'
163
+
164
+STATICFILES_FINDERS = (
165
+    'django.contrib.staticfiles.finders.FileSystemFinder',
166
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
167
+    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
168
+)
169
+
170
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
171
+
172
+MEDIA_URL = '/media/'
173
+
174
+# File 设置
175
+FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880  # InMemoryUploadedFile 文件最大值,设置为 5 MB
176
+FILE_UPLOAD_PERMISSIONS = 0o644  # TemporaryUploadedFile 文件权限设置
177
+
178
+# DOMAIN
179
+DOMAIN = 'http://a.com'
180
+
181
+# Redis 设置
182
+REDIS = {
183
+    'default': {
184
+        'HOST': '127.0.0.1',
185
+        'PORT': 6379,
186
+        'USER': '',
187
+        'PASSWORD': '',
188
+        'db': 0,
189
+        'decode_responses': True,
190
+    }
191
+}
192
+
193
+# 微信设置
194
+WECHAT = {
195
+    'JSAPI': {
196
+        'trade_type': 'JSAPI',  # JSAPI-网页支付、NATIVE-原生支付、APP-APP支付、MICROPAY-刷卡支付
197
+        'token': '5201314',
198
+        'appID': '',
199
+        'appsecret': '',
200
+        'encodingaeskey': '',
201
+        'mchID': '',
202
+        'apiKey': '',
203
+        'mch_cert': '',
204
+        'mch_key': '',
205
+        'redpack': {
206
+
207
+        }
208
+    },
209
+}
210
+
211
+WECHAT_DEFAULT_CFG = 'JSAPI'
212
+
213
+# 微信唯一标识
214
+# Choices: 'unionid' or 'openid'
215
+#
216
+# models.py
217
+#   'unique_identifier': self.unionid if settings.WECHAT_UNIQUE_IDENTIFICATION == 'unionid' else self.openid,
218
+# views.py
219
+#   unique_identifier = request.POST.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
220
+#   profile = Profile.objects.get(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
221
+#
222
+# If not bind to OpenPlat, change `WECHAT_UNIQUE_IDENTIFICATION` as `openid`
223
+WECHAT_UNIQUE_IDENTIFICATION = 'unionid'
224
+
225
+# Token 错误重授权设置
226
+TOKEN_CHECK_KEY = ''
227
+# TOKEN_CHECK_KEY = 'user_id'
228
+WECHAT_OAUTH2_REDIRECT_ENTRY = ''
229
+WECHAT_OAUTH2_REDIRECT_URL = ''
230
+
231
+# Cookie 设置
232
+DJANGO_WE_COOKIE_MAX_AGE = COOKIE_MAX_AGE = 31536000  # 单位:秒,1年:365 * 24 * 60 * 60 = 31536000
233
+DJANGO_WE_COOKIE_SALT = COOKIE_SALT = 'djwe'  # Salt for ``set_signed_cookie``
234
+# Cookie 校验设置
235
+COOKIE_USER_CHECK_KEY = ''
236
+# COOKIE_USER_CHECK_KEY = 'user_id'
237
+
238
+# 邮件设置
239
+# https://docs.djangoproject.com/en/1.11/howto/error-reporting/#email-reports
240
+# When DEBUG is False, Django will email the users listed in the ADMINS setting
241
+# whenever your code raises an unhandled exception and results in an internal server error (HTTP status code 500).
242
+# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
243
+# Email address that error messages come from.
244
+SERVER_EMAIL = 'error.notify@exmail.com'
245
+# The email backend to use. For possible shortcuts see django.core.mail.
246
+# The default is to use the SMTP backend.
247
+# Third-party backends can be specified by providing a Python path
248
+# to a module that defines an EmailBackend class.
249
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
250
+# Host for sending email.
251
+EMAIL_HOST = 'smtp.exmail.qq.com'
252
+# Port for sending email.
253
+EMAIL_PORT = 25
254
+# Optional SMTP authentication information for EMAIL_HOST.
255
+EMAIL_HOST_USER = 'error.notify@exmail.com'
256
+EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
257
+EMAIL_USE_TLS = False
258
+EMAIL_USE_SSL = False
259
+EMAIL_SSL_CERTFILE = None
260
+EMAIL_SSL_KEYFILE = None
261
+# Default: None
262
+# Specifies a timeout in seconds for blocking operations like the connection attempt.
263
+# socket.error: [Errno 110] Connection timed out
264
+# 如果不设置 EMAIL_TIMEOUT,一旦 sock.connect 连接有问题 error: (110, 'Connection timed out')
265
+# 就会导致进程一直卡住,最终导致服务崩溃
266
+# EMAIL_TIMEOUT = None
267
+EMAIL_TIMEOUT = 3
268
+# Default email address to use for various automated correspondence from
269
+# the site managers.
270
+DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
271
+# People who get code error notifications.
272
+# In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')]
273
+# Default: [], not send code error notifications.
274
+# https://github.com/django/django/blob/5fcfe5361e5b8c9738b1ee4c1e9a6f293a7dda40/django/core/mail/__init__.py#L90
275
+# ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
276
+ADMINS = []
277
+# Not-necessarily-technical managers of the site. They get broken link
278
+# notifications and other various emails.
279
+MANAGERS = ADMINS
280
+# Subject-line prefix for email messages send with django.core.mail.mail_admins
281
+# or ...mail_managers.  Make sure to include the trailing space.
282
+EMAIL_SUBJECT_PREFIX = '[Templet] '
283
+
284
+# Django-Admin Settings
285
+DJANGO_ADMIN_DISABLE_DELETE_SELECTED = False
286
+
287
+# Django-FILE-UPLOAD Settings
288
+DJANGO_FILE_UPLOAD_USE_YM = True
289
+DJANGO_FILE_UPLOAD_USE_DT = True
290
+
291
+# Django-Logit Settings
292
+DJANGO_LOGIT_ENABLED = True
293
+DJANGO_LOGIT_BODY_FLAG = False
294
+DJANGO_LOGIT_RES_FLAG = False
295
+
296
+# Django-Onerror Settings
297
+DJANGO_ONERROR_ACCEPT_REPORT = True
298
+DJANGO_ONERROR_ADMIN_SITE_REGISTER = True
299
+
300
+# Django-Short-URL Settings
301
+# Redirect url when short url not exists
302
+DJANGO_SHORT_URL_REDIRECT_URL = ''
303
+
304
+# Django-We Settings
305
+DJANGO_WE_QUOTE_OR_NOT = True
306
+DJANGO_WE_MODEL_DISPLAY_OR_NOT = True
307
+# Enable Cookie or not
308
+# DJANGO_WE_BASE_REDIRECT_SET_COOKIE = False
309
+# DJANGO_WE_USERINFO_REDIRECT_SET_COOKIE = True
310
+# Cookie Config
311
+DJANGO_WE_COOKIE_MAX_AGE = COOKIE_MAX_AGE
312
+DJANGO_WE_COOKIE_SALT = COOKIE_SALT
313
+
314
+# 开发调试相关配置
315
+if DEBUG:
316
+    try:
317
+        from .local_settings_dev import *
318
+    except ImportError:
319
+        pass
320
+
321
+try:
322
+    from .local_settings import *
323
+except ImportError:
324
+    pass
325
+
326
+try:
327
+    from .django_file_callback_settings import *
328
+except ImportError:
329
+    pass
330
+
331
+try:
332
+    from .django_we_callback_settings import *
333
+except ImportError:
334
+    pass
335
+
336
+# 依赖 local_settings 中的配置
337
+# 微信授权设置
338
+# WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/oauth2?scope={{0}}&redirect_url={{1}}'.format(DOMAIN)
339
+# Shorten URL
340
+# ``o`` is short for oauth2
341
+# ``r`` is short for redirect_url
342
+WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/o?scope={{0}}&r={{1}}'.format(DOMAIN)
343
+WECHAT_OAUTH2_USERINFO_REDIRECT_URI = '{0}/we/o?r={{0}}'.format(DOMAIN)  # Scope default snsapi_userinfo
344
+WECHAT_BASE_REDIRECT_URI = '{0}/we/base_redirect'.format(DOMAIN)
345
+WECHAT_USERINFO_REDIRECT_URI = '{0}/we/userinfo_redirect'.format(DOMAIN)
346
+WECHAT_DIRECT_BASE_REDIRECT_URI = '{0}/we/direct_base_redirect'.format(DOMAIN)
347
+WECHAT_DIRECT_USERINFO_REDIRECT_URI = '{0}/we/direct_userinfo_redirect'.format(DOMAIN)
348
+
349
+# Redis 连接
350
+WECHAT_REDIS_OBJ = REDIS_CACHE = connector(REDIS.get('default', {}))
351
+
352
+# LOGGER 设置
353
+# python manage.py rlistlog --key=django:logit:tiwen --filename=/tmp/tiwen.logit.log
354
+LOGGING = {
355
+    'version': 1,
356
+    'disable_existing_loggers': False,
357
+    'formatters': {
358
+        'verbose': {
359
+            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
360
+        },
361
+        'simple': {
362
+            'format': '%(levelname)s %(message)s'
363
+        },
364
+    },
365
+    'handlers': {
366
+        'logit': {
367
+            'level': 'DEBUG',
368
+            'class': 'rlog.RedisListHandler',
369
+            'redis_client': REDIS_CACHE,
370
+            'key': 'django:logit:tiwen',
371
+            'formatter': 'verbose',
372
+        },
373
+        'console': {
374
+            'level': 'DEBUG',
375
+            'class': 'logging.StreamHandler',
376
+            'formatter': 'verbose'
377
+        },
378
+    },
379
+    'loggers': {
380
+        'logit': {
381
+            'handlers': ['logit'],
382
+            'level': 'DEBUG',
383
+            'propagate': True,
384
+        },
385
+        'console': {
386
+            'handlers': ['console'],
387
+            'level': 'DEBUG',
388
+            'propagate': True,
389
+        },
390
+    },
391
+}
392
+
393
+# MAX_BIGINT
394
+# Why Not ``sys.maxint``
395
+# n bit platform sys.maxint = 2 ** (n - 1) - 1
396
+# 64 bit 9223372036854775807, 32 bit 2147483647
397
+from django.db.models import BigIntegerField  # isort:skip
398
+MAX_BIGINT = BigIntegerField.MAX_BIGINT
399
+
400
+# DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
401
+DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

+ 68 - 0
tiwen/static/tiwen/css/common.css

@@ -0,0 +1,68 @@
1
+.text-center {
2
+    text-align: center;
3
+}
4
+
5
+/*
6
+  文本溢出省略,元素需要设置定宽度
7
+  */
8
+/* 单行 */
9
+.text_ellipsis {
10
+    overflow: hidden;
11
+    white-space: nowrap;
12
+    text-overflow: ellipsis;
13
+}
14
+/* 多行 */
15
+.a {
16
+    overflow: hidden;
17
+    text-overflow: ellipsis;
18
+    display: -webkit-box;
19
+    -webkit-line-clamp: 2;
20
+    -webkit-box-orient: vertical;
21
+}
22
+
23
+/*
24
+  浮动 & 清除浮动
25
+ */
26
+.fl {
27
+    float: left;
28
+}
29
+
30
+.fr {
31
+    float: right;
32
+}
33
+
34
+.clearfix {
35
+    zoom: 1;
36
+}
37
+
38
+.clearfix:after {
39
+    content: "\200B";
40
+    display: block;
41
+    height: 0;
42
+    clear: both;
43
+}
44
+
45
+.clearfix:after {content:"."; display:block; height:0; visibility:hidden; clear:both; }
46
+.clearfix { *zoom:1; }
47
+
48
+/*
49
+  解决安卓微信点击图片预览问题
50
+  这个会让 img 标签的点击事件失效,如果想要点击图片就要给上面再写一层
51
+  需要长按识别二维码的时候,则再单独给该图片添加
52
+  ```css
53
+  pointer-events: auto;
54
+  ```
55
+  来恢复对应的点击事件
56
+  */
57
+img {
58
+    pointer-events: none;
59
+}
60
+
61
+.clickable {
62
+    pointer-events: auto;
63
+}
64
+
65
+/* Input 自动填充后,移除 Chrome 默认黄色 */
66
+input:-webkit-autofill {
67
+    box-shadow: 0 0 0 1000px white inset !important;
68
+}

+ 574 - 0
tiwen/static/tiwen/js/jswe.js

@@ -0,0 +1,574 @@
1
+!(function(e, t) {
2
+    var config = {
3
+        wxconfig: 'http://api.tt4it.com/wx/jsapi_signature',
4
+        callback: 'callback'
5
+    }, wxData = {
6
+        debug: false,
7
+        imgUrl: '',
8
+        link: '',
9
+        desc: '',
10
+        title: '',
11
+        timeLine: ''
12
+    }, wxConfig = {
13
+        hide: false,
14
+        baseFlag: false,
15
+        baseHide: false,
16
+        close: false,
17
+        hideMenuItems: [],
18
+        showMenuItems: []
19
+    }, jsApiList = [
20
+        'checkJsApi',
21
+        'onMenuShareTimeline',
22
+        'onMenuShareAppMessage',
23
+        'onMenuShareQQ',
24
+        'onMenuShareWeibo',
25
+        'onMenuShareQZone',
26
+        'hideMenuItems',
27
+        'showMenuItems',
28
+        'hideAllNonBaseMenuItem',
29
+        'showAllNonBaseMenuItem',
30
+        'translateVoice',
31
+        'startRecord',
32
+        'stopRecord',
33
+        'onRecordEnd',
34
+        'playVoice',
35
+        'pauseVoice',
36
+        'stopVoice',
37
+        'uploadVoice',
38
+        'downloadVoice',
39
+        'chooseImage',
40
+        'previewImage',
41
+        'uploadImage',
42
+        'downloadImage',
43
+        'getLocalImgData',
44
+        'getNetworkType',
45
+        'openLocation',
46
+        'getLocation',
47
+        'hideOptionMenu',
48
+        'showOptionMenu',
49
+        'closeWindow',
50
+        'scanQRCode',
51
+        'chooseWXPay',
52
+        'openEnterpriseRedPacket',
53
+        'openProductSpecificView',
54
+        'addCard',
55
+        'chooseCard',
56
+        'openCard'
57
+    ], wxApiFun
58
+
59
+    function isEmpty(obj) {
60
+        if (obj == null) return true
61
+        if (obj.length > 0) return false
62
+        if (obj.length === 0) return true
63
+        for (var key in obj) {
64
+            if (Object.prototype.hasOwnProperty.call(obj, key)) return false
65
+        }
66
+        return true
67
+    }
68
+
69
+    function isNotEmpty(obj) {
70
+        return !isEmpty(obj)
71
+    }
72
+
73
+    function isOpenOnPC() {  // 判断当前网页是否在 PC 浏览器中打开
74
+        var ua = navigator.userAgent
75
+        return /windows nt/i.test(ua) || /macintosh/i.test(ua) || /linux x86_64/i.test(ua)
76
+    }
77
+
78
+    function isOpenInWeixin() {  // 判断当前网页是否在微信内置浏览器中打开
79
+        return /micromessenger/i.test(navigator.userAgent)
80
+    }
81
+
82
+    function getWeixinVersion() {
83
+        var ua = navigator.userAgent,
84
+            mt = ua.match(/micromessenger\/([\d.]+)/i)
85
+        return (mt ? mt[1] : '')
86
+    }
87
+
88
+    // This function checks whether Wechat is the appointed version or not
89
+    // Cmp: http://jsperf.com/regexp-test-vs-indexof-ignore-upper-and-lower
90
+    function isWeixinVersion(version) {
91
+        // return new RegExp('micromessenger/' + version , 'i').test(navigator.userAgent)
92
+        return navigator.userAgent.toLowerCase().indexOf('micromessenger/' + version) != -1
93
+    }
94
+
95
+    function hideOptionMenu() {
96
+        wxConfig.hide = true
97
+        fixedWxData()
98
+    }
99
+
100
+    function showOptionMenu() {
101
+        wxConfig.hide = false
102
+        fixedWxData()
103
+    }
104
+
105
+    function hideMenuItems(items) {
106
+        wxConfig.hideMenuItems = items
107
+        fixedWxData()
108
+    }
109
+
110
+    function showMenuItems(items) {
111
+        wxConfig.showMenuItems = items
112
+        fixedWxData()
113
+    }
114
+
115
+    function hideAllNonBaseMenuItem() {
116
+        wxConfig.baseFlag = true
117
+        wxConfig.baseHide = true
118
+        fixedWxData()
119
+    }
120
+
121
+    function showAllNonBaseMenuItem() {
122
+        wxConfig.baseFlag = true
123
+        wxConfig.baseHide = false
124
+        fixedWxData()
125
+    }
126
+
127
+    function closeWindow() {
128
+        wxConfig.close = true
129
+        fixedWxData()
130
+    }
131
+
132
+    function wxReady(data) {
133
+        data = typeof data === 'object' ? data : JSON.parse(data)
134
+        wx.config({
135
+            debug: wxData.debug,
136
+            appId: data.appId,
137
+            timestamp: data.timestamp,
138
+            nonceStr: data.nonceStr,
139
+            signature: data.signature,
140
+            jsApiList: jsApiList
141
+        })
142
+
143
+        var callbacks = {
144
+            trigger: function (res) {
145
+                // alert('用户点击发送给朋友')
146
+                if (JSWE.wxTrigger) {JSWE.wxTrigger(res)}
147
+            },
148
+            success: function (res) {
149
+                // alert('已分享')
150
+                if (JSWE.wxSuccess) {JSWE.wxSuccess(res)}
151
+            },
152
+            cancel: function (res) {
153
+                // alert('已取消')
154
+                if (JSWE.wxCancel) {JSWE.wxCancel(res)}
155
+            },
156
+            fail: function (res) {
157
+                // alert(JSON.stringify(res))
158
+                if (JSWE.wxFail) {JSWE.wxFail(res)}
159
+            }
160
+        }, shareInfo = function(flag) {
161
+            var _share = {
162
+                title: flag ? wxData.title : (wxData.timeLine || wxData.desc),
163
+                link: wxData.link,
164
+                imgUrl: wxData.imgUrl,
165
+                trigger: callbacks.trigger,
166
+                success: callbacks.success,
167
+                cancel: callbacks.cancel,
168
+                fail: callbacks.fail
169
+            }
170
+            if (flag) _share.desc = wxData.desc
171
+            return _share
172
+        }, wxShareApi = function() {
173
+            // 2. 分享接口
174
+            // 2.1 监听“分享给朋友”,按钮点击、自定义分享内容及分享结果接口
175
+            wx.onMenuShareAppMessage(shareInfo(1))
176
+            // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
177
+            wx.onMenuShareTimeline(shareInfo(0))
178
+            // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口
179
+            wx.onMenuShareQQ(shareInfo(1))
180
+            // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口
181
+            wx.onMenuShareWeibo(shareInfo(1))
182
+            // 2.5 监听“分享到QQ空间”按钮点击、自定义分享内容及分享结果接口
183
+            wx.onMenuShareQZone(shareInfo(1))
184
+        }, wxMenuApi = function () {
185
+            // 8. 界面操作接口
186
+            // 8.1 隐藏右上角菜单
187
+            // 8.2 显示右上角菜单
188
+            if (wxConfig.hide) {wx.hideOptionMenu()} else {wx.showOptionMenu()}
189
+            // 8.3 批量隐藏菜单项
190
+            if (isNotEmpty(wxConfig.hideMenuItems)) {
191
+                wx.hideMenuItems({
192
+                    menuList: wxConfig.hideMenuItems,
193
+                    success: function (res) {
194
+                        if (JSWE.wxHideMenuItemsSuccess) {JSWE.wxHideMenuItemsSuccess(res)}
195
+                    },
196
+                    fail: function (res) {
197
+                        if (JSWE.wxHideMenuItemsFail) {JSWE.wxHideMenuItemsFail(res)}
198
+                    }
199
+                })
200
+            }
201
+            // 8.4 批量显示菜单项
202
+            if (isNotEmpty(wxConfig.showMenuItems)) {
203
+                wx.showMenuItems({
204
+                    menuList: wxConfig.showMenuItems,
205
+                    success: function (res) {
206
+                        if (JSWE.wxShowMenuItemsSuccess) {JSWE.wxShowMenuItemsSuccess(res)}
207
+                    },
208
+                    fail: function (res) {
209
+                        if (JSWE.wxShowMenuItemsFail) {JSWE.wxShowMenuItemsFail(res)}
210
+                    }
211
+                })
212
+            }
213
+            // 8.5 隐藏所有非基本菜单项
214
+            // 8.6 显示所有被隐藏的非基本菜单项
215
+            if (wxConfig.baseFlag) {
216
+                if (wxConfig.baseHide) {wx.hideAllNonBaseMenuItem()} else {wx.showAllNonBaseMenuItem()}
217
+            }
218
+            // 8.7 关闭当前窗口
219
+            if (wxConfig.close) {wx.closeWindow()}
220
+        }, wxVoiceApi = function() {
221
+            // 4.3 监听录音自动停止
222
+            wx.onVoiceRecordEnd({
223
+                complete: function (res) {
224
+                    voice.localId = res.localId
225
+                    if (JSWE.wxVoiceRecordEnd) {JSWE.wxVoiceRecordEnd(res)}
226
+                }
227
+            })
228
+            // 4.7 监听录音播放停止
229
+            wx.onVoicePlayEnd({
230
+                complete: function (res) {
231
+                    if (JSWE.wxVoicePlayEnd) {JSWE.wxVoicePlayEnd(res)}
232
+                }
233
+            })
234
+        }, wxApi = function () {
235
+            wxShareApi()
236
+            wxMenuApi()
237
+            wxVoiceApi()
238
+        }
239
+
240
+        wx.ready(wxApi)
241
+
242
+        return wxApiFun = wxApi
243
+    }
244
+
245
+    if (isOpenInWeixin() || isOpenOnPC()) {
246
+        if ('undefined' !== typeof JSWE_CONF_UPDATE) JSWE_CONF_UPDATE(config)
247
+        $.ajax({
248
+            url: config.wxconfig,
249
+            type: 'get',
250
+            dataType: 'jsonp',
251
+            jsonpCallback: config.callback,
252
+            data: {
253
+                url: window.location.href.split('#')[0]
254
+            },
255
+            success: wxReady
256
+        })
257
+    }
258
+
259
+    function initWxData(data, flag) {
260
+        for(var d in data) {if (d in wxData) wxData[d] = data[d]}
261
+        if (flag) fixedWxData()
262
+    }
263
+
264
+    function changeWxData(key, value, flag) {
265
+        if (key in falDwxDataata) {wxData[key] = value}
266
+        if (flag) fixedWxData()
267
+    }
268
+
269
+    function fixedWxData() {
270
+        if ('undefined' !== typeof wxApiFun) wxApiFun()
271
+    }
272
+
273
+    // 3 智能接口
274
+    var voice = {
275
+        localId: '',
276
+        serverId: ''
277
+    }
278
+    // 3.1 识别音频并返回识别结果
279
+    function translateVoice() {
280
+        if (voice.localId == '') {
281
+            if (JSWE.wxTranslateVoiceEmpty) {JSWE.wxTranslateVoiceEmpty()}
282
+            return
283
+        }
284
+        wx.translateVoice({
285
+            localId: voice.localId,
286
+            complete: function (res) {
287
+                if (JSWE.wxTranslateVoiceComplete) {JSWE.wxTranslateVoiceComplete(res)}
288
+            }
289
+        })
290
+    }
291
+
292
+    // 4 音频接口
293
+    // 4.1 开始录音
294
+    function startRecord() {
295
+        wx.startRecord({
296
+            cancel: function () {
297
+                if (JSWE.wxStartRecordCancel) {JSWE.wxStartRecordCancel(res)}
298
+            }
299
+        })
300
+    }
301
+
302
+    // 4.2 停止录音
303
+    function stopRecord() {
304
+        wx.stopRecord({
305
+          success: function (res) {
306
+              voice.localId = res.localId
307
+              if (JSWE.wxStopRecordSuccess) {JSWE.wxStopRecordSuccess(res)}
308
+          },
309
+          fail: function (res) {
310
+              if (JSWE.wxStopRecordFail) {JSWE.wxStopRecordFail(res)}
311
+          }
312
+        })
313
+    }
314
+
315
+    // 4.4 播放音频
316
+    function playVoice() {
317
+        if (voice.localId == '') {
318
+            if (JSWE.wxPlayVoiceEmpty) {JSWE.wxPlayVoiceEmpty()}
319
+            return
320
+        }
321
+        wx.playVoice({
322
+            localId: voice.localId
323
+        })
324
+    }
325
+
326
+    // 4.5 暂停播放音频
327
+    function pauseVoice() {
328
+        if (voice.localId == '') {
329
+            if (JSWE.wxPauseVoiceEmpty) {JSWE.wxPauseVoiceEmpty()}
330
+            return
331
+        }
332
+        wx.pauseVoice({
333
+            localId: voice.localId
334
+        })
335
+    }
336
+
337
+    // 4.6 停止播放音频
338
+    function stopVoice() {
339
+        if (voice.localId == '') {
340
+            if (JSWE.wxStopVoiceEmpty) {JSWE.wxStopVoiceEmpty()}
341
+            return
342
+        }
343
+        wx.stopVoice({
344
+            localId: voice.localId
345
+        })
346
+    }
347
+
348
+    // 4.8 上传语音
349
+    function uploadVoice() {
350
+        var localId = voice.localId
351
+        if (localId == '') {
352
+            if (JSWE.wxUploadVoiceEmpty) {JSWE.wxUploadVoiceEmpty()}
353
+            return
354
+        }
355
+        wx.uploadVoice({
356
+            localId: localId,
357
+            success: function (res) {
358
+                voice.serverId = res.serverId
359
+                if (JSWE.wxUploadVoiceSuccess) {JSWE.wxUploadVoiceSuccess(res, localId)}
360
+            }
361
+        })
362
+    }
363
+
364
+    // 4.9 下载语音
365
+    function downloadVoice() {
366
+        var serverId = voice.serverId
367
+        if (serverId == '') {
368
+            if (JSWE.wxDownloadVoiceEmpty) {JSWE.wxDownloadVoiceEmpty()}
369
+            return
370
+        }
371
+        wx.downloadVoice({
372
+            serverId: serverId,
373
+            success: function (res) {
374
+                voice.localId = res.localId
375
+                if (JSWE.wxDownloadVoiceSuccess) {JSWE.wxDownloadVoiceSuccess(res, serverId)}
376
+            }
377
+        })
378
+    }
379
+
380
+    // 5 图片接口
381
+    var images = {
382
+        localIds: [],
383
+        serverIds: []
384
+    }
385
+    // 5.1 拍照、本地选图
386
+    function chooseImage(choose_params) {
387
+        if ('undefined' === typeof choose_params) choose_params = {}
388
+        wx.chooseImage({
389
+            count: choose_params.count || 9, // 默认9
390
+            sizeType: choose_params.sizeType || ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
391
+            sourceType: choose_params.sourceType || ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
392
+            success: function (res) {
393
+                images.localIds = res.localIds // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
394
+                // 判断是否直接上传
395
+                if (choose_params.directUpload) {setTimeout(uploadImages({localIds: images.localIds, isShowProgressTips: choose_params.isShowProgressTips || 1}), 100)}
396
+                // 拍照、本地选图成功后的回调函数
397
+                if (JSWE.wxChooseImageSuccess) {JSWE.wxChooseImageSuccess(res, choose_params.extras || {})}
398
+            }
399
+        })
400
+    }
401
+
402
+    // 5.2 图片预览
403
+    function previewImage(preview_params) {
404
+        wx.previewImage({
405
+            current: preview_params.current, // 当前显示图片的链接,不填则默认为 urls 的第一张
406
+            urls: preview_params.urls // 需要预览的图片链接列表
407
+        })
408
+    }
409
+
410
+    // 5.3 上传图片
411
+    function uploadImage(upload_params) {
412
+        // 上传图片为异步处理,重复上传同一图片,返回的serverId也是不同的
413
+        var localId = upload_params.localId
414
+        wx.uploadImage({
415
+            localId: localId, // 需要上传的图片的本地ID,由chooseImage接口获得
416
+            isShowProgressTips: upload_params.isShowProgressTips || 1, // 默认为1,显示进度提示
417
+            success: function (res) {
418
+                images.serverIds.push(res.serverId) // 返回图片的服务器端ID
419
+                // 上传图片成功后的回调函数
420
+                if (JSWE.wxUploadImageSuccess) {JSWE.wxUploadImageSuccess(res, localId)}
421
+            }
422
+        })
423
+    }
424
+
425
+    function uploadImages(upload_params) {
426
+        var localIds = upload_params.localIds, isShowProgressTips = upload_params.isShowProgressTips || 1
427
+        images.serverIds = []
428
+        for (var idx in localIds) {uploadImage({localId: localIds[idx], isShowProgressTips: isShowProgressTips})}
429
+    }
430
+
431
+    // 5.4 下载图片
432
+    function downloadImage(download_params) {
433
+        var serverId = download_params.serverId
434
+        wx.downloadImage({
435
+            serverId: serverId, // 需要下载的图片的服务器端ID,由uploadImage接口获得
436
+            isShowProgressTips: download_params.isShowProgressTips || 1, // 默认为1,显示进度提示
437
+            success: function (res) {
438
+                images.localId.push(res.localId)
439
+                if (JSWE.wxDownloadImageSuccess) {JSWE.wxDownloadImageSuccess(res, serverId)}
440
+            }
441
+        })
442
+    }
443
+
444
+    function downloadImages(download_params) {
445
+        var serverIds = download_params.serverIds, isShowProgressTips = download_params.isShowProgressTips || 1
446
+        images.localIds = []
447
+        for (var idx in serverIds) {downloadImage({serverId: serverIds[idx], isShowProgressTips: isShowProgressTips})}
448
+    }
449
+
450
+    function getLocalImgData(localId) {
451
+        wx.getLocalImgData({
452
+            localId: localId, // 图片的localID
453
+            success: function (res) {
454
+                // var localData = res.localData; // localData是图片的base64数据,可以用img标签显示
455
+                if (JSWE.wxGetLocalImgDataSuccess) {JSWE.wxGetLocalImgDataSuccess(res)}
456
+            }
457
+        })
458
+    }
459
+
460
+    // 9 微信原生接口
461
+    // 9.1.1 扫描二维码并返回结果
462
+    // 9.1.2 扫描二维码并返回结果
463
+    function scanQRCode(scan_params) {
464
+        if ('undefined' === typeof scan_params) scan_params = {}
465
+        wx.scanQRCode({
466
+            needResult: scan_params.needResult || 0,  // 默认为0,0扫描结果由微信处理,1直接返回扫描结果
467
+            scanType: scan_params.scanType || ['qrCode', 'barCode'],  // 可以指定扫二维码还是一维码,默认二者都有
468
+            success: function (res) {  // 当 needResult 为 1 时,扫码返回的结果
469
+                if (JSWE.wxScanQRCodeSuccess) {JSWE.wxScanQRCodeSuccess(res)}
470
+            }
471
+        })
472
+    }
473
+
474
+    // QRCode & BarCode is different
475
+    function parseScanQRCodeResultStr(resultStr) {
476
+        var strs = resultStr.split(',')
477
+        return strs[strs.length - 1]
478
+    }
479
+
480
+    // 10 微信支付接口
481
+    // 10.1 发起一个支付请求
482
+    function chooseWXPay(wxpay_params) {
483
+        wx.chooseWXPay({
484
+            timestamp: wxpay_params.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
485
+            nonceStr: wxpay_params.nonceStr, // 支付签名随机串,不长于 32 位
486
+            package: wxpay_params.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
487
+            signType: wxpay_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
488
+            paySign: wxpay_params.paySign, // 支付签名
489
+            success: function (res) {
490
+                // 支付成功后的回调函数
491
+                if (JSWE.wxPaySuccess) {JSWE.wxPaySuccess(res)}
492
+            }
493
+        })
494
+    }
495
+
496
+    // xx 微信原生企业红包接口
497
+    // xx.1 发起一个发送原生企业红包请求
498
+    function openEnterpriseRedPacket(wxredpack_params) {
499
+        wx.openEnterpriseRedPacket({
500
+            timeStamp: wxredpack_params.timeStamp, // 红包签名时间戳,注意原生企业红包接口timeStamp字段名需大写其中的S字符,而支付接口timeStamp字段名无需大写其中的S字符。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
501
+            nonceStr: wxredpack_params.nonceStr, // 红包签名随机串,不长于 32 位
502
+            package: encodeURIComponent(wxredpack_params.package), // 发放红包接口返回的prepay_id参数值,提交格式如:prepay_id=***)
503
+            signType: wxredpack_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
504
+            paySign: wxredpack_params.paySign, // 红包签名
505
+            success: function (res) {
506
+                // 发送原生企业红包成功后的回调函数
507
+                if (JSWE.wxEnterpriseRedPacketSuccess) {JSWE.wxEnterpriseRedPacketSuccess(res)}
508
+            }
509
+        })
510
+    }
511
+
512
+    var v = {
513
+        version: '1.0.5',
514
+
515
+        // Basic Vars
516
+        config: config,
517
+        wxData: wxData,
518
+        jsApiList: jsApiList,
519
+
520
+        isEmpty: isEmpty,
521
+        isNotEmpty: isNotEmpty,
522
+
523
+        // Weixin Function
524
+        isOpenInWeixin: isOpenInWeixin,
525
+        getWeixinVersion: getWeixinVersion,
526
+        isWeixinVersion: isWeixinVersion,
527
+
528
+        // Menu Function
529
+        hideOptionMenu: hideOptionMenu,
530
+        showOptionMenu: showOptionMenu,
531
+        hideMenuItems: hideMenuItems,
532
+        showMenuItems: showMenuItems,
533
+        hideAllNonBaseMenuItem: hideAllNonBaseMenuItem,
534
+        showAllNonBaseMenuItem: showAllNonBaseMenuItem,
535
+        closeWindow: closeWindow,
536
+
537
+        // Share Function
538
+        initWxData: initWxData,
539
+        changeWxData: changeWxData,
540
+        fixedWxData: fixedWxData,
541
+
542
+        // Voice Function
543
+        voice: voice,
544
+        translateVoice: translateVoice,
545
+        startRecord: startRecord,
546
+        stopRecord: stopRecord,
547
+        playVoice: playVoice,
548
+        pauseVoice: pauseVoice,
549
+        stopVoice: stopVoice,
550
+        uploadVoice: uploadVoice,
551
+        downloadVoice: downloadVoice,
552
+
553
+        // Image Function
554
+        images: images,
555
+        chooseImage: chooseImage,
556
+        previewImage: previewImage,
557
+        uploadImage: uploadImage,
558
+        uploadImages: uploadImages,
559
+        downloadImage: downloadImage,
560
+        downloadImages: downloadImages,
561
+        getLocalImgData: getLocalImgData,
562
+
563
+        // Scan Function
564
+        scanQRCode: scanQRCode,
565
+        parseScanQRCodeResultStr: parseScanQRCodeResultStr,
566
+
567
+        // Pay Function
568
+        chooseWXPay: chooseWXPay,
569
+
570
+        // EnterpriseRedPacket Function
571
+        openEnterpriseRedPacket: openEnterpriseRedPacket
572
+    }
573
+    e.JSWE = e.V = v
574
+})(window)

+ 56 - 0
tiwen/urls.py

@@ -0,0 +1,56 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""tiwen URL Configuration
4
+
5
+The `urlpatterns` list routes URLs to views. For more information please see:
6
+    https://docs.djangoproject.com/en/1.11/topics/http/urls/
7
+Examples:
8
+Function views
9
+    1. Add an import:  from my_app import views
10
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
11
+Class-based views
12
+    1. Add an import:  from other_app.views import Home
13
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
14
+Including another URLconf
15
+    1. Import the include() function: from django.conf.urls import url, include
16
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
17
+"""
18
+from django.conf import settings
19
+from django.conf.urls.static import static
20
+from django.contrib import admin
21
+from django.urls import include, re_path
22
+
23
+
24
+urlpatterns = [
25
+    re_path(r'^admin/', admin.site.urls),
26
+]
27
+
28
+urlpatterns += [
29
+    re_path(r'^api/', include(('api.urls', 'api'), namespace='api')),
30
+    re_path(r'^uniapi/', include(('django_uniapi.urls', 'uniapi'), namespace='uniapi')),
31
+]
32
+
33
+urlpatterns += [
34
+    # url(r'^s/', include(('django_short_url.urls', 'django_short_url'), namespace='django_short_url')),
35
+]
36
+
37
+urlpatterns += [
38
+    re_path(r'^w/', include(('django_we.urls', 'shortwechat'), namespace='shortwechat')),
39
+    re_path(r'^we/', include(('django_we.urls', 'wechat'), namespace='wechat')),
40
+]
41
+
42
+urlpatterns += [
43
+    # re_path(r'^p/', include(('page.urls', 'shortpage'), namespace='shortpage')),
44
+    # re_path(r'^page/', include(('page.urls', 'page'), namespace='page')),
45
+]
46
+
47
+if settings.DEBUG:
48
+    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
49
+    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
50
+
51
+# AdminSite
52
+admin.site.site_header = 'Django administration'
53
+admin.site.site_title = 'Django site admin'
54
+# Make site_url/index_title None to hidden
55
+admin.site.site_url = '/'
56
+admin.site.index_title = 'Site administration'

+ 17 - 0
tiwen/wsgi.py

@@ -0,0 +1,17 @@
1
+"""
2
+WSGI config for tiwen project.
3
+
4
+It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+For more information on this file, see
7
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
8
+"""
9
+
10
+import os
11
+
12
+from django.core.wsgi import get_wsgi_application
13
+
14
+
15
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tiwen.settings")
16
+
17
+application = get_wsgi_application()

+ 0 - 0
utils/__init__.py


+ 0 - 0
utils/error/__init__.py


+ 77 - 0
utils/error/errno_utils.py

@@ -0,0 +1,77 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from StatusCode import BaseStatusCode, StatusCodeField
4
+
5
+
6
+class ParamStatusCode(BaseStatusCode):
7
+    """ 4000xx 参数相关错误码 """
8
+    PARAM_NOT_FOUND = StatusCodeField(400000, 'Param Not Found', description='参数不存在')
9
+
10
+
11
+class ProfileStatusCode(BaseStatusCode):
12
+    """ 4001xx 用户相关错误码 """
13
+    PROFILE_NOT_FOUND = StatusCodeField(400101, 'Profile Not Found', description='用户不存在')
14
+
15
+
16
+class PhoneStatusCode(BaseStatusCode):
17
+    """ 4002xx 手机相关错误码 """
18
+    INVALID_PHONE = StatusCodeField(400200, 'Invalid Phone', description='非法手机号')
19
+    PHONE_NOT_FOUND = StatusCodeField(400201, 'Phone Not Found', description='手机号不存在')
20
+    PHONE_ALREADY_EXISTS = StatusCodeField(400202, 'Phone Already Exists', description='手机号已存在')
21
+
22
+
23
+class OrderStatusCode(BaseStatusCode):
24
+    """ 4040xx 订单/支付相关错误码 """
25
+    UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'Unified Order Fail', description='统一下单失败')
26
+    ORDER_NOT_FOUND = StatusCodeField(404001, 'Order Not Found', description='订单不存在')
27
+    # 订单支付状态
28
+    ORDER_NOT_PAY = StatusCodeField(404011, 'Order Not Pay', description='订单未支付')
29
+    ORDER_PAYING = StatusCodeField(404012, 'Order Paying', description='订单支付中')
30
+    ORDER_PAY_FAIL = StatusCodeField(404013, 'Order Pay Fail', description='微信支付失败')
31
+    # 通知校验状态
32
+    SIGN_CHECK_FAIL = StatusCodeField(404090, 'Sign Check Fail', description='签名校验失败')
33
+    FEE_CHECK_FAIL = StatusCodeField(404091, 'FEE Check Fail', description='金额校验失败')
34
+
35
+
36
+class PayStatusCode(BaseStatusCode):
37
+    """ 4041xx 支付相关错误码 """
38
+
39
+
40
+class WithdrawStatusCode(BaseStatusCode):
41
+    """ 4042xx 提现相关错误码 """
42
+    BALANCE_INSUFFICIENT = StatusCodeField(404200, 'Balance Insufficient', description='提现金额不足')
43
+
44
+
45
+class TokenStatusCode(BaseStatusCode):
46
+    """ 4090xx 票据相关错误码 """
47
+    TOKEN_NOT_FOUND = StatusCodeField(409001, 'Token Not Found', description='票据不存在')
48
+
49
+
50
+class SignatureStatusCode(BaseStatusCode):
51
+    """ 4091xx 签名校验错误 """
52
+    SIGNATURE_ERROR = StatusCodeField(409101, 'Signature Error', description='签名错误')
53
+
54
+
55
+class GVCodeStatusCode(BaseStatusCode):
56
+    """ 4092xx 图形验证码相关错误码 """
57
+    GRAPHIC_VCODE_ERROR = StatusCodeField(409201, 'Graphic VCode Error', description='图形验证码错误')
58
+
59
+
60
+class SVCodeStatusCode(BaseStatusCode):
61
+    """ 4093xx 短信验证码相关错误码 """
62
+    SMS_QUOTA_LIMIT = StatusCodeField(409300, 'SMS Quota Limit', description='短信次数超限')
63
+    SMS_VCODE_ERROR = StatusCodeField(409301, 'SMS VCode Error', description='验证码错误,请稍后重试')
64
+    SMS_VCODE_HAS_SEND = StatusCodeField(409302, 'SMS VCode Has Send', description='验证码已发送,请勿重复获取')
65
+
66
+
67
+class InsufficientStatusCode(BaseStatusCode):
68
+    """ 4095xx 不足相关错误码 """
69
+    BALANCE_INSUFFICIENT = StatusCodeField(409501, 'Balance Insufficient', description='余额不足')
70
+    INTEGRAL_INSUFFICIENT = StatusCodeField(409502, 'Integral Insufficient', description='积分不足')
71
+
72
+
73
+class PermissionStatusCode(BaseStatusCode):
74
+    """ 4099xx 权限相关错误码 """
75
+    PERMISSION_DENIED = StatusCodeField(409900, 'Permission Denied', description='权限不足')
76
+    UPLOAD_PERMISSION_DENIED = StatusCodeField(409910, 'Upload Permission Denied', description='上传权限不足')
77
+    UPDATE_PERMISSION_DENIED = StatusCodeField(409930, 'Update Permission Denied', description='更新权限不足')

+ 24 - 0
utils/error/response_utils.py

@@ -0,0 +1,24 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from json_response import JsonpResponse, JsonResponse
4
+from StatusCode import StatusCodeField
5
+
6
+
7
+def response_data(status_code=200, message=None, description=None, data={}, **kwargs):
8
+    return dict({
9
+        'status': status_code,
10
+        'message': message,
11
+        'description': description,
12
+        'data': data,
13
+    }, **kwargs)
14
+
15
+
16
+def response(status_code=200, message=None, description=None, data={}, msg_args=[], msg_kwargs={}, desc_args=[], desc_kwargs={}, request=None, callback=None, **kwargs):
17
+    # Final Message and Description
18
+    message, description = (message or status_code.message, description or status_code.description) if isinstance(status_code, StatusCodeField) else (message, description)
19
+    # Final Response Data
20
+    resp_data = response_data(status_code, (message or '').format(*msg_args, **msg_kwargs), (description or '').format(*desc_args, **desc_kwargs), data, **kwargs)
21
+    # Assign Callback
22
+    callback = callback or (request and request.GET.get('callback'))
23
+    # Call JsonResponse or JsonpResponse
24
+    return JsonpResponse(callback, resp_data, safe=False) if callback else JsonResponse(resp_data, safe=False)

+ 0 - 0
utils/redis/__init__.py


+ 6 - 0
utils/redis/connect.py

@@ -0,0 +1,6 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.conf import settings
4
+
5
+
6
+r = settings.REDIS_CACHE

+ 1 - 0
utils/redis/rkeys.py

@@ -0,0 +1 @@
1
+# -*- coding: utf-8 -*-

+ 0 - 0
utils/user/__init__.py


+ 18 - 0
utils/user/userinfo_save.py

@@ -0,0 +1,18 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def userinfo_save(userinfo):
5
+    """ Save profile or something else """
6
+    # from account.models import UserInfo
7
+    # from django.conf import settings
8
+    #
9
+    # unique_identifier = userinfo.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
10
+    #
11
+    # user, created = UserInfo.objects.select_for_update().get_or_create(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
12
+    # user.unionid = userinfo.get('unionid', '')
13
+    # user.openid = userinfo.get('openid', '')
14
+    # user.nickname = userinfo.get('nickname', '')
15
+    # user.avatar = userinfo.get('headimgurl', '')
16
+    # user.save()
17
+    #
18
+    # return user

kodo - Gogs: Go Git Service

Brak opisu

models.py 9.9KB

    # -*- coding: utf-8 -*- from django.db import models from django.utils.translation import ugettext_lazy as _ from django_models_ext import BaseModelMixin, upload_file_path, upload_file_url, upload_path from shortuuidfield import ShortUUIDField from TimeConvert import TimeConvert as tc from mch.models import ModelInfo, OperatorInfo from utils.qiniucdn import qiniu_file_url class MchInfoEncryptLogInfo(BaseModelMixin): plaintext = models.CharField(_(u'plaintext'), max_length=64, blank=True, null=True, help_text=u'待加密字符串', db_index=True, unique=True) alg = models.CharField(_(u'alg'), max_length=16, blank=True, null=True, help_text=u'加密算法') ciphertext = models.CharField(_(u'ciphertext'), max_length=64, blank=True, null=True, help_text=u'加密字符串') brand_pk = models.IntegerField(_(u'brand_pk'), default=0, help_text=u'品牌PK', db_index=True) model_pk = models.IntegerField(_(u'model_pk'), default=0, help_text=u'型号PK', db_index=True) distributor_pk = models.IntegerField(_(u'distributor_pk'), default=0, help_text=u'经销商PK', db_index=True) sn = models.CharField(_(u'sn'), max_length=32, blank=True, null=True, help_text=u'序列号', db_index=True) # 二维码版本 version = models.IntegerField(_(u'version'), default=2, help_text=u'二维码版本', db_index=True) # 一物一码 application_id = models.IntegerField(_(u'application_id'), default=0, help_text=u'申请单号', db_index=True) code = models.CharField(_(u'code'), max_length=16, blank=True, null=True, help_text=u'九位的字符串原始码', db_index=True) code_url = models.CharField(_(u'code_url'), max_length=128, blank=True, null=True, help_text=u'28位普通码字符 ', db_index=True) operator_id = models.CharField(_(u'operator_id'), max_length=32, blank=True, null=True, help_text=u'操作员唯一标识', db_index=True) class Meta: verbose_name = _(u'mchinfoencryptloginfo') verbose_name_plural = _(u'mchinfoencryptloginfo') def __unicode__(self): return '%d' % self.pk @property def admindata(self): model = ModelInfo.objects.get(pk=self.model_pk, status=True) try: operator_name = OperatorInfo.objects.get(operator_id=self.operator_id).name except OperatorInfo.DoesNotExist: operator_name = u'深圳捷成' return { 'sn': self.sn, 'model_pk': self.model_pk, 'model_uni_name': model.model_uni_name, 'model_name': model.model_name, 'operator_name': operator_name, 'created_at': tc.local_string(utc_dt=self.created_at) } class MchInfoDecryptLogInfo(BaseModelMixin): ciphertext = models.CharField(_(u'ciphertext'), max_length=64, blank=True, null=True, help_text=u'待解密字符串', db_index=True) brand_pk = models.IntegerField(_(u'brand_pk'), default=0, help_text=u'品牌PK', db_index=True) model_pk = models.IntegerField(_(u'model_pk'), default=0, help_text=u'型号PK', db_index=True) distributor_pk = models.IntegerField(_(u'distributor_pk'), default=0, help_text=u'经销商PK', db_index=True) sn = models.CharField(_(u'sn'), max_length=32, blank=True, null=True, help_text=u'序列号', db_index=True) # 一物一码 application_id = models.IntegerField(_(u'application_id'), default=0, help_text=u'申请单号', db_index=True) code = models.CharField(_(u'code'), max_length=16, blank=True, null=True, help_text=u'九位的字符串原始码', db_index=True) code_url = models.CharField(_(u'code_url'), max_length=128, blank=True, null=True, help_text=u'28位普通码字符 ', db_index=True) decrypt_count = models.IntegerField(_(u'decrypt_count'), default=1, help_text=u'解密次数') class Meta: verbose_name = _(u'mchinfodecryptloginfo') verbose_name_plural = _(u'mchinfodecryptloginfo') def __unicode__(self): return '%d' % self.pk class MchLogInfo(BaseModelMixin): log_id = ShortUUIDField(_(u'log_id'), max_length=32, help_text=u'日志唯一标识', db_index=True) log_file = models.FileField(_(u'log_file'), upload_to=upload_path, blank=True, null=True, help_text=u'日志文件') operator_id = models.CharField(_(u'operator_id'), max_length=32, blank=True, null=True, help_text=u'操作员唯一标识', db_index=True) app_version = models.IntegerField(_(u'app_version'), default=0, help_text=u'APP 版本号', db_index=True) class Meta: verbose_name = _(u'mchloginfo') verbose_name_plural = _(u'mchloginfo') def __unicode__(self): return '%d' % self.pk class MchSearchModelAndCameraLogInfo(BaseModelMixin): user_id = models.CharField(_(u'user_id'), max_length=32, blank=True, null=True, help_text=u'用户唯一标识', db_index=True) log_id = ShortUUIDField(_(u'log_id'), max_length=32, help_text=u'日志唯一标识', db_index=True) is_search_model = models.IntegerField(_(u'is_search_model'), default=0, help_text=u'搜索镜头型号', db_index=True) is_selected_model = models.IntegerField(_(u'is_selected_model'), default=0, help_text=u'搜索相机型号', db_index=True) is_search_camera = models.IntegerField(_(u'is_search_model'), default=0, help_text=u'搜索相机型号', db_index=True) is_search_model_camera = models.IntegerField(_(u'is_search_model'), default=0, help_text=u'搜索相机型号和镜头型号', db_index=True) is_search_camera_after_model = models.IntegerField(_(u'is_search_camera_after_model'), default=0, help_text=u'选择镜头后搜索相机型号', db_index=True) class Meta: verbose_name = _(u'mchsearchmodelandcameraloginfo') verbose_name_plural = _(u'mchsearchmodelandcameraloginfo') def __unicode__(self): return '%d' % self.pk class ComplementCodeLogInfo(BaseModelMixin): AUDIT_TODO = 0 AUDIT_PASS = 1 AUDIT_REFUSED = -1 AUDIT_STATUS_TUPLE = ( (AUDIT_TODO, u'待审核'), (AUDIT_PASS, u'审核通过'), (AUDIT_REFUSED, u'审核不通过'), ) user_id = models.CharField(_(u'user_id'), max_length=32, blank=True, null=True, help_text=u'用户唯一标识', db_index=True) log_id = ShortUUIDField(_(u'log_id'), max_length=32, help_text=u'日志唯一标识', db_index=True) name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'用户姓名') phone = models.CharField(_(u'phone'), max_length=11, blank=True, null=True, help_text=u'用户电话', db_index=True) model_id = models.CharField(_(u'model_id'), max_length=32, blank=True, null=True, help_text=u'型号唯一标识', db_index=True) model_name = models.CharField(_(u'model_name'), max_length=255, blank=True, null=True, help_text=u'型号名称') sn = models.CharField(_(u'sn'), max_length=32, blank=True, null=True, help_text=u'序列号', db_index=True) shot_image = models.ImageField(_(u'shot_image'), upload_to=upload_path, blank=True, null=True, help_text=u'镜头机身照片') invoice_image = models.ImageField(_(u'invoice_image'), upload_to=upload_path, blank=True, null=True, help_text=u'购买凭证照片') audit_status = models.IntegerField(_(u'audit_status'), choices=AUDIT_STATUS_TUPLE, default=AUDIT_TODO, help_text=u'审核状态') ciphertext = models.CharField(_(u'ciphertext'), max_length=64, blank=True, null=True, help_text=u'待解密字符串', db_index=True) is_contacted = models.BooleanField(_(u'is_contacted'), default=False, help_text=u'是否已联系用户') is_upload_qiniu = models.BooleanField(_(u'is_upload_qiniu'), default=False, help_text=u'是否已上传七牛') class Meta: verbose_name = _(u'补码记录') verbose_name_plural = _(u'补码记录') def __unicode__(self): return '%d' % self.pk @property def shot_image_url(self): # return qiniu_file_url(self.shot_image.name, bucket='tamron') if self.is_upload_qiniu else upload_file_url(self.shot_image) return qiniu_file_url(self.shot_image.name, bucket='tamron') @property def shot_image_path(self): return upload_file_path(self.shot_image) @property def invoice_image_url(self): # return qiniu_file_url(self.invoice_image.name, bucket='tamron') if self.is_upload_qiniu else upload_file_url(self.invoice_image) return qiniu_file_url(self.invoice_image.name, bucket='tamron') @property def invoice_image_path(self): return upload_file_path(self.invoice_image) @property def data(self): model = ModelInfo.objects.get(model_id=self.model_id) return { 'user_id': self.user_id, 'log_id': self.log_id, 'name': self.name, 'phone': self.phone, 'model_id': self.model_id, 'model_name': model.model_name, 'sn': self.sn, 'shot_image': self.shot_image_path, 'shot_image_url': self.shot_image_url, 'invoice_image': self.invoice_image_path, 'invoice_image_url': self.invoice_image_url, 'audit_status': self.audit_status, 'is_contacted': self.is_contacted, 'ciphertext': self.ciphertext, 'created_at': tc.local_string(utc_dt=self.created_at) } class AdministratorLoginLogInfo(BaseModelMixin): admin_id = models.CharField(_(u'admin_id'), max_length=32, blank=True, null=True, help_text=u'管理员唯一标识') admin_name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'管理员姓名') login_ip = models.CharField(_(u'login_ip'), max_length=32, blank=True, null=True, help_text=u'登录IP') login_at = models.DateTimeField(_(u'login_at'), blank=True, null=True, help_text=u'登录时间') @property def admindata(self): return { 'admin_id': self.admin_id, 'admin_name': self.admin_name, 'login_ip': self.login_ip, 'login_at': tc.local_string(utc_dt=self.login_at), }