From 7ef5a6d50cf746531dc15f8e842c1d56ba3ce6d4 Mon Sep 17 00:00:00 2001 From: DengBiao <2319963317@qq.com> Date: Thu, 17 Aug 2023 11:49:49 +0800 Subject: [PATCH] init project --- .gitignore | 43 +- Makefile | 32 + README.md | 49 +- app/admin/enum/enum_admin.go | 19 + app/admin/enum/enum_company.go | 19 + app/admin/enum/enum_permission_group.go | 19 + app/admin/enum/enum_qrcode.go | 67 ++ app/admin/enum/enum_role.go | 19 + app/admin/enum/enum_wx_official_account.go | 19 + .../hdl_central_kitchen_for_school.go | 844 ++++++++++++++++++ .../hdl/enterprise_manage/hdl_enterprise.go | 107 +++ app/admin/hdl/hdl_admin.go | 13 + app/admin/hdl/hdl_audit_center.go | 91 ++ app/admin/hdl/hdl_banner.go | 135 +++ app/admin/hdl/hdl_company.go | 127 +++ app/admin/hdl/hdl_demo.go | 19 + app/admin/hdl/hdl_enterprise.go | 323 +++++++ app/admin/hdl/hdl_file_upload.go | 50 ++ app/admin/hdl/hdl_login.go | 45 + app/admin/hdl/hdl_notice.go | 135 +++ app/admin/hdl/hdl_role.go | 479 ++++++++++ app/admin/hdl/hdl_set_center.go | 36 + app/admin/hdl/hdl_sys_cfg.go | 17 + app/admin/hdl/hdl_user.go | 122 +++ app/admin/lib/auth/auth.go | 39 + app/admin/lib/auth/base.go | 19 + app/admin/lib/validate/validate_comm.go | 33 + app/admin/lib/wx/wx_official_account.go | 85 ++ app/admin/md/md_app_redis_key.go | 12 + app/admin/md/md_banner.go | 12 + app/admin/md/md_central_kitchen_for_school.go | 18 + .../md/md_central_kitchen_for_school_order.go | 39 + ...ral_kitchen_for_school_package_with_day.go | 6 + ...md_central_kitchen_for_school_with_spec.go | 10 + app/admin/md/md_company.go | 17 + app/admin/md/md_enterprise.go | 53 ++ app/admin/md/md_enterprise_manage.go | 206 +++++ app/admin/md/md_login.go | 10 + app/admin/md/md_notice.go | 12 + app/admin/md/md_qrcode.go | 31 + app/admin/md/md_role.go | 85 ++ app/admin/md/md_set_center.go | 7 + app/admin/md/md_sys_cfg.go | 9 + app/admin/md/md_user.go | 30 + app/admin/md/md_wx_official_account.go | 14 + app/admin/mw/mw_admin_auth.go | 26 + app/admin/mw/mw_admin_permission.go | 26 + app/admin/mw/mw_cors.go | 29 + app/admin/mw/mw_limiter.go | 58 ++ app/admin/mw/mw_recovery.go | 57 ++ .../svc_central_kitchen_for_school.go | 531 +++++++++++ .../svc_enterprise_manage.go | 102 +++ .../svc_central_kitchen_for_school_order.go | 210 +++++ app/admin/svc/svc_admin.go | 30 + app/admin/svc/svc_auth.go | 51 ++ .../svc/svc_central_kitchen_for_school.go | 67 ++ .../svc_central_kitchen_for_school_package.go | 187 ++++ app/admin/svc/svc_enterprise.go | 91 ++ app/admin/svc/svc_login.go | 33 + app/admin/svc/svc_qrcode.go | 1 + app/admin/svc/svc_role.go | 183 ++++ app/admin/svc/svc_user.go | 97 ++ app/cfg/cfg_app.go | 43 + app/cfg/cfg_cache_key.go | 3 + app/cfg/init_cache.go | 9 + app/cfg/init_cfg.go | 48 + app/cfg/init_log.go | 20 + app/cfg/init_task.go | 42 + app/customer/enum/enum_qrcode.go | 67 ++ app/customer/enum/enum_wx_official_account.go | 19 + app/customer/hdl/hdl_alipay.go | 45 + app/customer/hdl/hdl_banner.go | 21 + .../hdl_central_kitchen_for_school_order.go | 278 ++++++ .../hdl_central_kitchen_for_school_package.go | 58 ++ app/customer/hdl/hdl_enterprise.go | 142 +++ app/customer/hdl/hdl_login.go | 118 +++ app/customer/hdl/hdl_notice.go | 21 + app/customer/hdl/hdl_pay.go | 107 +++ app/customer/hdl/hdl_sys_cfg.go | 17 + app/customer/hdl/hdl_user.go | 61 ++ app/customer/hdl/hdl_user_identity.go | 327 +++++++ .../hdl_faceCollection.go | 169 ++++ app/customer/lib/auth/auth.go | 39 + app/customer/lib/auth/base.go | 19 + app/customer/lib/validate/validate_comm.go | 33 + app/customer/md/md_app_redis_key.go | 7 + app/customer/md/md_curl_smart_pay.go | 76 ++ app/customer/md/md_enterprise.go | 43 + app/customer/md/md_login.go | 16 + app/customer/md/md_order.go | 31 + app/customer/md/md_pay.go | 17 + app/customer/md/md_qrcode.go | 31 + app/customer/md/md_sys_cfg.go | 9 + app/customer/md/md_user_identity.go | 20 + app/customer/md/md_wx_official_account.go | 14 + app/customer/mw/mw_cors.go | 29 + app/customer/mw/mw_customer_auth.go | 26 + app/customer/mw/mw_limiter.go | 58 ++ app/customer/mw/mw_recovery.go | 57 ++ .../svc_central_kitchen_for_school_order.go | 122 +++ app/customer/svc/svc_auth.go | 54 ++ .../svc_central_kitchen_for_school_package.go | 321 +++++++ app/customer/svc/svc_curl_smart_pay.go | 435 +++++++++ app/customer/svc/svc_enterprise.go | 67 ++ app/customer/svc/svc_login.go | 33 + app/customer/svc/svc_pay.go | 116 +++ app/customer/svc/svc_qrcode.go | 1 + app/customer/svc/svc_role.go | 5 + app/db/db.go | 114 +++ app/db/db_admin.go | 114 +++ app/db/db_admin_role.go | 81 ++ app/db/db_admin_role_permission.go | 19 + app/db/db_admin_role_permission_group.go | 17 + app/db/db_banner.go | 86 ++ .../db_central_kitchen_for_school_package.go | 92 ++ ..._central_kitchen_for_school_package_ord.go | 106 +++ ...ral_kitchen_for_school_package_with_day.go | 81 ++ app/db/db_central_kitchen_for_school_set.go | 74 ++ ...tral_kitchen_for_school_user_refund_day.go | 111 +++ ...entral_kitchen_for_school_user_with_day.go | 106 +++ ...db_central_kitchen_for_school_with_spec.go | 86 ++ app/db/db_class.go | 72 ++ app/db/db_class_with_user.go | 103 +++ app/db/db_company.go | 78 ++ app/db/db_enterprise.go | 86 ++ app/db/db_enterprise_grade_class.go | 15 + app/db/db_grade.go | 50 ++ app/db/db_notice.go | 86 ++ app/db/db_permission.go | 13 + app/db/db_permission_group.go | 23 + app/db/db_permission_group_permission.go | 13 + app/db/db_role.go | 80 ++ app/db/db_role_permission_group.go | 31 + app/db/db_self_support_for_school_info.go | 80 ++ app/db/db_self_support_for_user_face_info.go | 80 ++ app/db/db_sys_cfg.go | 119 +++ app/db/db_user.go | 91 ++ app/db/db_user_identity.go | 146 +++ app/db/model/admin.go | 12 + app/db/model/admin_role.go | 14 + app/db/model/banner.go | 14 + .../central_kitchen_for_school_package.go | 15 + .../central_kitchen_for_school_package_ord.go | 18 + ...ral_kitchen_for_school_package_with_day.go | 11 + .../model/central_kitchen_for_school_set.go | 16 + ...tral_kitchen_for_school_user_refund_day.go | 19 + ...entral_kitchen_for_school_user_with_day.go | 12 + .../central_kitchen_for_school_with_spec.go | 21 + app/db/model/class.go | 15 + app/db/model/class_with_user.go | 13 + app/db/model/company.go | 16 + app/db/model/enterprise.go | 17 + app/db/model/grade.go | 14 + app/db/model/notice.go | 14 + app/db/model/permission.go | 10 + app/db/model/permission_group.go | 14 + app/db/model/permission_group_permission.go | 13 + app/db/model/role.go | 10 + app/db/model/role_permission_group.go | 13 + app/db/model/self_support_for_school_info.go | 14 + .../model/self_support_for_user_face_info.go | 16 + app/db/model/sys_cfg.go | 7 + app/db/model/user.go | 16 + app/db/model/user_identity.go | 19 + app/e/code.go | 236 +++++ app/e/error.go | 72 ++ app/e/msg.go | 110 +++ app/e/set_cache.go | 8 + ..._central_kitchen_for_school_package_ord.go | 52 ++ ...tral_kitchen_for_school_user_refund_day.go | 25 + ...entral_kitchen_for_school_user_with_day.go | 46 + app/enum/enum_enterprise.go | 43 + app/enum/enum_sys_cfg.go | 82 ++ app/enum/enum_user_identity.go | 67 ++ app/lib/qiniu/bucket_create.go | 16 + app/lib/qiniu/bucket_delete.go | 18 + app/lib/qiniu/bucket_get_domain.go | 18 + app/lib/qiniu/init.go | 22 + app/lib/qiniu/req_img_upload.go | 54 ++ app/md/file.go | 54 ++ app/router/admin_router.go | 165 ++++ app/router/customer_router.go | 67 ++ app/svc/svc_file_img_upload.go | 68 ++ app/svc/svc_validate_comm.go | 33 + app/task/init.go | 92 ++ app/task/md/cron_key.go | 5 + app/task/svc/svc_cancel_order.go | 64 ++ app/task/task_cancel_order.go | 23 + app/utils/aes.go | 123 +++ app/utils/base64.go | 95 ++ app/utils/boolean.go | 26 + app/utils/cache/base.go | 421 +++++++++ app/utils/cache/cache/cache.go | 107 +++ app/utils/cache/cache/conv.go | 86 ++ app/utils/cache/cache/file.go | 241 +++++ app/utils/cache/cache/memory.go | 239 +++++ app/utils/cache/redis.go | 409 +++++++++ app/utils/cache/redis_cluster.go | 622 +++++++++++++ app/utils/cache/redis_pool.go | 324 +++++++ app/utils/cache/redis_pool_cluster.go | 617 +++++++++++++ app/utils/convert.go | 322 +++++++ app/utils/crypto.go | 19 + app/utils/curl.go | 209 +++++ app/utils/debug.go | 25 + app/utils/duplicate.go | 37 + app/utils/file.go | 22 + app/utils/file_and_dir.go | 29 + app/utils/format.go | 59 ++ app/utils/ip.go | 146 +++ app/utils/json.go | 17 + app/utils/logx/log.go | 245 +++++ app/utils/logx/output.go | 105 +++ app/utils/logx/sugar.go | 192 ++++ app/utils/map.go | 9 + app/utils/map_and_struct.go | 341 +++++++ app/utils/md5.go | 12 + app/utils/qrcode/decodeFile.go | 33 + app/utils/qrcode/getBase64.go | 43 + app/utils/qrcode/saveFile.go | 85 ++ app/utils/qrcode/writeWeb.go | 39 + app/utils/rand.go | 31 + app/utils/rsa.go | 170 ++++ app/utils/serialize.go | 23 + app/utils/shuffle.go | 48 + app/utils/sign_check.go | 125 +++ app/utils/slice.go | 26 + app/utils/slice_and_string.go | 47 + app/utils/string.go | 155 ++++ app/utils/time.go | 226 +++++ app/utils/url.go | 16 + app/utils/uuid.go | 76 ++ app/utils/validator_err_trans.go | 55 ++ app/utils/wx.go | 31 + bin/.gitignore | 2 + cmd/task/main.go | 43 + cmd_db.bat | 25 + cmd_db.sh | 21 + cmd_run.bat | 12 + cmd_run.sh | 8 + cmd_task.bat | 13 + cmd_task.sh | 9 + etc/db_tpl/config | 7 + etc/db_tpl/struct.go.tpl | 17 + etc/task.yml | 30 + go.mod | 37 + main.go | 59 ++ static/html/LandingPage.html | 179 ++++ static/html/article.html | 95 ++ static/html/kz_goods_share.html | 664 ++++++++++++++ static/image/rbac.png | Bin 0 -> 119311 bytes wap.conf | 35 + 251 files changed, 20291 insertions(+), 8 deletions(-) create mode 100644 Makefile create mode 100644 app/admin/enum/enum_admin.go create mode 100644 app/admin/enum/enum_company.go create mode 100644 app/admin/enum/enum_permission_group.go create mode 100644 app/admin/enum/enum_qrcode.go create mode 100644 app/admin/enum/enum_role.go create mode 100644 app/admin/enum/enum_wx_official_account.go create mode 100644 app/admin/hdl/enterprise_manage/hdl_central_kitchen_for_school.go create mode 100644 app/admin/hdl/enterprise_manage/hdl_enterprise.go create mode 100644 app/admin/hdl/hdl_admin.go create mode 100644 app/admin/hdl/hdl_audit_center.go create mode 100644 app/admin/hdl/hdl_banner.go create mode 100644 app/admin/hdl/hdl_company.go create mode 100644 app/admin/hdl/hdl_demo.go create mode 100644 app/admin/hdl/hdl_enterprise.go create mode 100644 app/admin/hdl/hdl_file_upload.go create mode 100644 app/admin/hdl/hdl_login.go create mode 100644 app/admin/hdl/hdl_notice.go create mode 100644 app/admin/hdl/hdl_role.go create mode 100644 app/admin/hdl/hdl_set_center.go create mode 100644 app/admin/hdl/hdl_sys_cfg.go create mode 100644 app/admin/hdl/hdl_user.go create mode 100644 app/admin/lib/auth/auth.go create mode 100644 app/admin/lib/auth/base.go create mode 100644 app/admin/lib/validate/validate_comm.go create mode 100644 app/admin/lib/wx/wx_official_account.go create mode 100644 app/admin/md/md_app_redis_key.go create mode 100644 app/admin/md/md_banner.go create mode 100644 app/admin/md/md_central_kitchen_for_school.go create mode 100644 app/admin/md/md_central_kitchen_for_school_order.go create mode 100644 app/admin/md/md_central_kitchen_for_school_package_with_day.go create mode 100644 app/admin/md/md_central_kitchen_for_school_with_spec.go create mode 100644 app/admin/md/md_company.go create mode 100644 app/admin/md/md_enterprise.go create mode 100644 app/admin/md/md_enterprise_manage.go create mode 100644 app/admin/md/md_login.go create mode 100644 app/admin/md/md_notice.go create mode 100644 app/admin/md/md_qrcode.go create mode 100644 app/admin/md/md_role.go create mode 100644 app/admin/md/md_set_center.go create mode 100644 app/admin/md/md_sys_cfg.go create mode 100644 app/admin/md/md_user.go create mode 100644 app/admin/md/md_wx_official_account.go create mode 100644 app/admin/mw/mw_admin_auth.go create mode 100644 app/admin/mw/mw_admin_permission.go create mode 100644 app/admin/mw/mw_cors.go create mode 100644 app/admin/mw/mw_limiter.go create mode 100644 app/admin/mw/mw_recovery.go create mode 100644 app/admin/svc/enterprise_manage/svc_central_kitchen_for_school.go create mode 100644 app/admin/svc/enterprise_manage/svc_enterprise_manage.go create mode 100644 app/admin/svc/order/svc_central_kitchen_for_school_order.go create mode 100644 app/admin/svc/svc_admin.go create mode 100644 app/admin/svc/svc_auth.go create mode 100644 app/admin/svc/svc_central_kitchen_for_school.go create mode 100644 app/admin/svc/svc_central_kitchen_for_school_package.go create mode 100644 app/admin/svc/svc_enterprise.go create mode 100644 app/admin/svc/svc_login.go create mode 100644 app/admin/svc/svc_qrcode.go create mode 100644 app/admin/svc/svc_role.go create mode 100644 app/admin/svc/svc_user.go create mode 100644 app/cfg/cfg_app.go create mode 100644 app/cfg/cfg_cache_key.go create mode 100644 app/cfg/init_cache.go create mode 100644 app/cfg/init_cfg.go create mode 100644 app/cfg/init_log.go create mode 100644 app/cfg/init_task.go create mode 100644 app/customer/enum/enum_qrcode.go create mode 100644 app/customer/enum/enum_wx_official_account.go create mode 100644 app/customer/hdl/hdl_alipay.go create mode 100644 app/customer/hdl/hdl_banner.go create mode 100644 app/customer/hdl/hdl_central_kitchen_for_school_order.go create mode 100644 app/customer/hdl/hdl_central_kitchen_for_school_package.go create mode 100644 app/customer/hdl/hdl_enterprise.go create mode 100644 app/customer/hdl/hdl_login.go create mode 100644 app/customer/hdl/hdl_notice.go create mode 100644 app/customer/hdl/hdl_pay.go create mode 100644 app/customer/hdl/hdl_sys_cfg.go create mode 100644 app/customer/hdl/hdl_user.go create mode 100644 app/customer/hdl/hdl_user_identity.go create mode 100644 app/customer/hdl/self_support_for_school/hdl_faceCollection.go create mode 100644 app/customer/lib/auth/auth.go create mode 100644 app/customer/lib/auth/base.go create mode 100644 app/customer/lib/validate/validate_comm.go create mode 100644 app/customer/md/md_app_redis_key.go create mode 100644 app/customer/md/md_curl_smart_pay.go create mode 100644 app/customer/md/md_enterprise.go create mode 100644 app/customer/md/md_login.go create mode 100644 app/customer/md/md_order.go create mode 100644 app/customer/md/md_pay.go create mode 100644 app/customer/md/md_qrcode.go create mode 100644 app/customer/md/md_sys_cfg.go create mode 100644 app/customer/md/md_user_identity.go create mode 100644 app/customer/md/md_wx_official_account.go create mode 100644 app/customer/mw/mw_cors.go create mode 100644 app/customer/mw/mw_customer_auth.go create mode 100644 app/customer/mw/mw_limiter.go create mode 100644 app/customer/mw/mw_recovery.go create mode 100644 app/customer/svc/order/svc_central_kitchen_for_school_order.go create mode 100644 app/customer/svc/svc_auth.go create mode 100644 app/customer/svc/svc_central_kitchen_for_school_package.go create mode 100644 app/customer/svc/svc_curl_smart_pay.go create mode 100644 app/customer/svc/svc_enterprise.go create mode 100644 app/customer/svc/svc_login.go create mode 100644 app/customer/svc/svc_pay.go create mode 100644 app/customer/svc/svc_qrcode.go create mode 100644 app/customer/svc/svc_role.go create mode 100644 app/db/db.go create mode 100644 app/db/db_admin.go create mode 100644 app/db/db_admin_role.go create mode 100644 app/db/db_admin_role_permission.go create mode 100644 app/db/db_admin_role_permission_group.go create mode 100644 app/db/db_banner.go create mode 100644 app/db/db_central_kitchen_for_school_package.go create mode 100644 app/db/db_central_kitchen_for_school_package_ord.go create mode 100644 app/db/db_central_kitchen_for_school_package_with_day.go create mode 100644 app/db/db_central_kitchen_for_school_set.go create mode 100644 app/db/db_central_kitchen_for_school_user_refund_day.go create mode 100644 app/db/db_central_kitchen_for_school_user_with_day.go create mode 100644 app/db/db_central_kitchen_for_school_with_spec.go create mode 100644 app/db/db_class.go create mode 100644 app/db/db_class_with_user.go create mode 100644 app/db/db_company.go create mode 100644 app/db/db_enterprise.go create mode 100644 app/db/db_enterprise_grade_class.go create mode 100644 app/db/db_grade.go create mode 100644 app/db/db_notice.go create mode 100644 app/db/db_permission.go create mode 100644 app/db/db_permission_group.go create mode 100644 app/db/db_permission_group_permission.go create mode 100644 app/db/db_role.go create mode 100644 app/db/db_role_permission_group.go create mode 100644 app/db/db_self_support_for_school_info.go create mode 100644 app/db/db_self_support_for_user_face_info.go create mode 100644 app/db/db_sys_cfg.go create mode 100644 app/db/db_user.go create mode 100644 app/db/db_user_identity.go create mode 100644 app/db/model/admin.go create mode 100644 app/db/model/admin_role.go create mode 100644 app/db/model/banner.go create mode 100644 app/db/model/central_kitchen_for_school_package.go create mode 100644 app/db/model/central_kitchen_for_school_package_ord.go create mode 100644 app/db/model/central_kitchen_for_school_package_with_day.go create mode 100644 app/db/model/central_kitchen_for_school_set.go create mode 100644 app/db/model/central_kitchen_for_school_user_refund_day.go create mode 100644 app/db/model/central_kitchen_for_school_user_with_day.go create mode 100644 app/db/model/central_kitchen_for_school_with_spec.go create mode 100644 app/db/model/class.go create mode 100644 app/db/model/class_with_user.go create mode 100644 app/db/model/company.go create mode 100644 app/db/model/enterprise.go create mode 100644 app/db/model/grade.go create mode 100644 app/db/model/notice.go create mode 100644 app/db/model/permission.go create mode 100644 app/db/model/permission_group.go create mode 100644 app/db/model/permission_group_permission.go create mode 100644 app/db/model/role.go create mode 100644 app/db/model/role_permission_group.go create mode 100644 app/db/model/self_support_for_school_info.go create mode 100644 app/db/model/self_support_for_user_face_info.go create mode 100644 app/db/model/sys_cfg.go create mode 100644 app/db/model/user.go create mode 100644 app/db/model/user_identity.go create mode 100644 app/e/code.go create mode 100644 app/e/error.go create mode 100644 app/e/msg.go create mode 100644 app/e/set_cache.go create mode 100644 app/enum/enum_central_kitchen_for_school_package_ord.go create mode 100644 app/enum/enum_central_kitchen_for_school_user_refund_day.go create mode 100644 app/enum/enum_central_kitchen_for_school_user_with_day.go create mode 100644 app/enum/enum_enterprise.go create mode 100644 app/enum/enum_sys_cfg.go create mode 100644 app/enum/enum_user_identity.go create mode 100644 app/lib/qiniu/bucket_create.go create mode 100644 app/lib/qiniu/bucket_delete.go create mode 100644 app/lib/qiniu/bucket_get_domain.go create mode 100644 app/lib/qiniu/init.go create mode 100644 app/lib/qiniu/req_img_upload.go create mode 100644 app/md/file.go create mode 100644 app/router/admin_router.go create mode 100644 app/router/customer_router.go create mode 100644 app/svc/svc_file_img_upload.go create mode 100644 app/svc/svc_validate_comm.go create mode 100644 app/task/init.go create mode 100644 app/task/md/cron_key.go create mode 100644 app/task/svc/svc_cancel_order.go create mode 100644 app/task/task_cancel_order.go create mode 100644 app/utils/aes.go create mode 100644 app/utils/base64.go create mode 100644 app/utils/boolean.go create mode 100644 app/utils/cache/base.go create mode 100644 app/utils/cache/cache/cache.go create mode 100644 app/utils/cache/cache/conv.go create mode 100644 app/utils/cache/cache/file.go create mode 100644 app/utils/cache/cache/memory.go create mode 100644 app/utils/cache/redis.go create mode 100644 app/utils/cache/redis_cluster.go create mode 100644 app/utils/cache/redis_pool.go create mode 100644 app/utils/cache/redis_pool_cluster.go create mode 100644 app/utils/convert.go create mode 100644 app/utils/crypto.go create mode 100644 app/utils/curl.go create mode 100644 app/utils/debug.go create mode 100644 app/utils/duplicate.go create mode 100644 app/utils/file.go create mode 100644 app/utils/file_and_dir.go create mode 100644 app/utils/format.go create mode 100644 app/utils/ip.go create mode 100644 app/utils/json.go create mode 100644 app/utils/logx/log.go create mode 100644 app/utils/logx/output.go create mode 100644 app/utils/logx/sugar.go create mode 100644 app/utils/map.go create mode 100644 app/utils/map_and_struct.go create mode 100644 app/utils/md5.go create mode 100644 app/utils/qrcode/decodeFile.go create mode 100644 app/utils/qrcode/getBase64.go create mode 100644 app/utils/qrcode/saveFile.go create mode 100644 app/utils/qrcode/writeWeb.go create mode 100644 app/utils/rand.go create mode 100644 app/utils/rsa.go create mode 100644 app/utils/serialize.go create mode 100644 app/utils/shuffle.go create mode 100644 app/utils/sign_check.go create mode 100644 app/utils/slice.go create mode 100644 app/utils/slice_and_string.go create mode 100644 app/utils/string.go create mode 100644 app/utils/time.go create mode 100644 app/utils/url.go create mode 100644 app/utils/uuid.go create mode 100644 app/utils/validator_err_trans.go create mode 100644 app/utils/wx.go create mode 100644 bin/.gitignore create mode 100644 cmd/task/main.go create mode 100644 cmd_db.bat create mode 100644 cmd_db.sh create mode 100644 cmd_run.bat create mode 100644 cmd_run.sh create mode 100644 cmd_task.bat create mode 100644 cmd_task.sh create mode 100644 etc/db_tpl/config create mode 100644 etc/db_tpl/struct.go.tpl create mode 100644 etc/task.yml create mode 100644 go.mod create mode 100644 main.go create mode 100644 static/html/LandingPage.html create mode 100644 static/html/article.html create mode 100644 static/html/kz_goods_share.html create mode 100644 static/image/rbac.png create mode 100644 wap.conf diff --git a/.gitignore b/.gitignore index f4d432a..9a8f686 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,46 @@ -# ---> Go # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib - # Test binary, built with `go test -c` *.test - # Output of the go coverage tool, specifically when used with LiteIDE *.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - +.idea +.vscode +*.log +.DS_Store +Thumbs.db +*.swp +*.swn +*.swo +*.swm +*.7z +*.zip +*.rar +*.tar +*.tar.gz +go.sum +/etc/cfg.yaml +images +test/test.json +etc/cfg.yml +t.json +t1.json +t2.json +t3.json +t.go +wait-for-it.sh +test.go +xorm +test.csv +nginx.conf +.devcontainer +.devcontainer/Dockerfile +.devcontainer/sources.list +/t1.go +/tmp/* +.idea/* +/.idea/modules.xml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e7e30c2 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: build clean tool lint help + +APP=applet + +all: build + +build: + go build -o ./bin/$(APP) ./cmd/main.go + +lite: + go build -ldflags "-s -w" -o ./bin/$(APP) ./cmd/main.go + +install: + #@go build -v . + go install ./cmd/... + +tool: + go vet ./...; true + gofmt -w . + +lint: + golint ./... + +clean: + rm -rf go-gin-example + go clean -i . + +help: + @echo "make: compile packages and dependencies" + @echo "make tool: run specified go tool" + @echo "make lint: golint ./..." + @echo "make clean: remove object files and cached files" \ No newline at end of file diff --git a/README.md b/README.md index 46d4b4f..10fbf31 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,50 @@ # smart_canteen -智慧食堂 \ No newline at end of file +- 智慧食堂 + +## 要看 nginx.conf 和 wap conf + +## 层级介绍 + +- hdl 做接收数据的报错, 数据校验 +- svc 做数据处理的报错, 数据转换 +- lib 只抛出错误给hdl或者svc进行处理, 不做数据校验 +- db 可以处理db错误,其它错误返回给svc进行处理 +- mw 中间件 +- md 结构体 + +#### 介绍 +基于gin的接口小程序 + +#### 软件架构 + +软件架构说明 + +#### 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +#### 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + +## swagger + +``` +// 参考:https://segmentfault.com/a/1190000013808421 +// 安装命令行 +go get -u github.com/swaggo/swag/cmd/swag +// 生成 +swag init +``` \ No newline at end of file diff --git a/app/admin/enum/enum_admin.go b/app/admin/enum/enum_admin.go new file mode 100644 index 0000000..17132bf --- /dev/null +++ b/app/admin/enum/enum_admin.go @@ -0,0 +1,19 @@ +package enum + +type AdminState int32 + +const ( + AdminStateForNormal = 1 + AdminStateForFreeze = 2 +) + +func (gt AdminState) String() string { + switch gt { + case AdminStateForNormal: + return "正常" + case AdminStateForFreeze: + return "冻结" + default: + return "未知" + } +} diff --git a/app/admin/enum/enum_company.go b/app/admin/enum/enum_company.go new file mode 100644 index 0000000..7966aac --- /dev/null +++ b/app/admin/enum/enum_company.go @@ -0,0 +1,19 @@ +package enum + +type CompanyState int32 + +const ( + CompanyStateForNormal = 1 + CompanyStateForFreeze = 2 +) + +func (gt CompanyState) String() string { + switch gt { + case CompanyStateForNormal: + return "正常" + case CompanyStateForFreeze: + return "冻结" + default: + return "未知" + } +} diff --git a/app/admin/enum/enum_permission_group.go b/app/admin/enum/enum_permission_group.go new file mode 100644 index 0000000..eda844a --- /dev/null +++ b/app/admin/enum/enum_permission_group.go @@ -0,0 +1,19 @@ +package enum + +type PermissionGroupState int32 + +const ( + PermissionGroupStateForNormal = 1 + PermissionGroupStateForDiscard = 2 +) + +func (gt PermissionGroupState) String() string { + switch gt { + case PermissionGroupStateForNormal: + return "正常" + case PermissionGroupStateForDiscard: + return "废弃" + default: + return "未知" + } +} diff --git a/app/admin/enum/enum_qrcode.go b/app/admin/enum/enum_qrcode.go new file mode 100644 index 0000000..12ab106 --- /dev/null +++ b/app/admin/enum/enum_qrcode.go @@ -0,0 +1,67 @@ +package enum + +type QrcodeBatchState int32 + +const ( + QrcodeBatchStateForUseIng = 1 + QrcodeBatchStateForUseAlready = 2 + QrcodeBatchStateForExpire = 3 + QrcodeBatchStateForCancel = 4 +) + +func (gt QrcodeBatchState) String() string { + switch gt { + case QrcodeBatchStateForUseIng: + return "使用中" + case QrcodeBatchStateForUseAlready: + return "使用完" + case QrcodeBatchStateForExpire: + return "已过期" + case QrcodeBatchStateForCancel: + return "已作废" + default: + return "未知" + } +} + +type QrcodeWithBatchRecordsSate int32 + +const ( + QrcodeWithBatchRecordsStateForWait = 1 + QrcodeWithBatchRecordsStateForAlready = 2 + QrcodeWithBatchRecordsStateForExpire = 3 + QrcodeWithBatchRecordsStateForCancel = 4 +) + +func (gt QrcodeWithBatchRecordsSate) String() string { + switch gt { + case QrcodeWithBatchRecordsStateForWait: + return "待使用" + case QrcodeWithBatchRecordsStateForAlready: + return "已使用" + case QrcodeWithBatchRecordsStateForExpire: + return "已过期" + case QrcodeWithBatchRecordsStateForCancel: + return "已作废" + default: + return "未知" + } +} + +type QrcodeSate int32 + +const ( + QrcodeSateAllowUse = 1 + QrcodeSateAllowNotUse = 2 +) + +func (gt QrcodeSate) String() string { + switch gt { + case QrcodeSateAllowUse: + return "可使用" + case QrcodeSateAllowNotUse: + return "不可用" + default: + return "未知" + } +} diff --git a/app/admin/enum/enum_role.go b/app/admin/enum/enum_role.go new file mode 100644 index 0000000..bd8763c --- /dev/null +++ b/app/admin/enum/enum_role.go @@ -0,0 +1,19 @@ +package enum + +type RoleState int32 + +const ( + RoleStateForNormal = 1 + RoleStateForFreeze = 2 +) + +func (gt RoleState) String() string { + switch gt { + case RoleStateForNormal: + return "正常" + case RoleStateForFreeze: + return "冻结" + default: + return "未知" + } +} diff --git a/app/admin/enum/enum_wx_official_account.go b/app/admin/enum/enum_wx_official_account.go new file mode 100644 index 0000000..e982771 --- /dev/null +++ b/app/admin/enum/enum_wx_official_account.go @@ -0,0 +1,19 @@ +package enum + +type WxOfficialAccountRequest string + +const ( + GetAccessToken = "cgi-bin/token" + QrcodeCreate = "cgi-bin/qrcode/create" +) + +func (gt WxOfficialAccountRequest) String() string { + switch gt { + case GetAccessToken: + return "获取 Access token" + case QrcodeCreate: + return "生成带参二维码" + default: + return "未知" + } +} diff --git a/app/admin/hdl/enterprise_manage/hdl_central_kitchen_for_school.go b/app/admin/hdl/enterprise_manage/hdl_central_kitchen_for_school.go new file mode 100644 index 0000000..65d8592 --- /dev/null +++ b/app/admin/hdl/enterprise_manage/hdl_central_kitchen_for_school.go @@ -0,0 +1,844 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/admin/svc" + svc2 "applet/app/admin/svc/enterprise_manage" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/enum" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func CentralKitchenForSchoolUserUpdate(c *gin.Context) { + var req md.CentralKitchenForSchoolUserUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + err = svc2.CentralKitchenForSchoolUserUpdate(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolUserDelete(c *gin.Context) { + var req md.CentralKitchenForSchoolUserDeleteReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + err = svc2.CentralKitchenForSchoolUserDelete(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolStudentList(c *gin.Context) { + var req md.CentralKitchenForSchoolStudentListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + resp, total, err := svc2.CentralKitchenForSchoolStudentList(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": resp, + "total": total, + "admission_list": []map[string]string{ + { + "name": "按班级", + "value": "admission_type_by_class", + }, + { + "name": "按年级", + "value": "admission_type_by_grade", + }, + }, + }, nil) + return +} + +func CentralKitchenForSchoolTeacherList(c *gin.Context) { + var req md.CentralKitchenForSchoolTeacherListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + resp, total, err := svc2.CentralKitchenForSchoolTeacherList(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": resp, + "total": total, + }, nil) + return +} + +func CentralKitchenForSchoolTeacherUpdate(c *gin.Context) { + var req md.CentralKitchenForSchoolTeacherUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + userIdentity, err := userIdentityDb.GetUserIdentity(req.UserIdentityId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if userIdentity == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + userIdentity.IdNo = req.IdNo + userIdentity.Name = req.Name + _, err = userIdentityDb.UserIdentityUpdate(req.UserIdentityId, userIdentity, "id_no", "name") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolStudentUpdate(c *gin.Context) { + var req md.CentralKitchenForSchoolStudentUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err = svc2.CentralKitchenForSchoolStudentUpdate(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolStudentAdmission(c *gin.Context) { + var req md.CentralKitchenForSchoolStudentAdmissionReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + err = svc2.CentralKitchenForSchoolStudentAdmission(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolStudentDelete(c *gin.Context) { + var req md.CentralKitchenForSchoolStudentDeleteReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + err = svc2.CentralKitchenForSchoolStudentDelete(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolTeacherDelete(c *gin.Context) { + var req md.CentralKitchenForSchoolTeacherDeleteReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + + //删除 user_identity + _, err = db.Db.Where("enterprise_id =?", req.EnterpriseId).In("id", req.UserIdentityIds).Delete(model.UserIdentity{}) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, "success", nil) + return +} + +func GetCentralKitchenForSchoolWithSpec(c *gin.Context) { + enterpriseId := utils.StrToInt(c.DefaultQuery("enterprise_id", "0")) + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(enterpriseId) + data, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, map[string]interface{}{ + "data": data, + }, nil) + return +} + +func SetCentralKitchenForSchoolWithSpec(c *gin.Context) { + var req md.SetCentralKitchenForSchoolWithSpecReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(req.EnterpriseId) + + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + _, err = centralKitchenForSchoolWithSpec.CentralKitchenForSchoolWithSpecDelete() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + now := time.Now() + if spec != nil { + insertConfirm, err1 := centralKitchenForSchoolWithSpec.CentralKitchenForSchoolWithSpecInsert(&model.CentralKitchenForSchoolWithSpec{ + IsOpenBreakfast: spec.IsOpenBreakfast, + IsOpenLunch: spec.IsOpenLunch, + IsOpenDinner: spec.IsOpenDinner, + EnterpriseId: req.EnterpriseId, + BreakfastUnitPrice: req.BreakfastUnitPrice, + LunchUnitPrice: req.LunchUnitPrice, + DinnerUnitPrice: req.DinnerUnitPrice, + BreakfastUnitPriceForTeacher: req.BreakfastUnitPriceForTeacher, + LunchUnitPriceForTeacher: req.LunchUnitPriceForTeacher, + DinnerUnitPriceForTeacher: req.DinnerUnitPriceForTeacher, + CreateAt: now, + UpdateAt: now, + }) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + if insertConfirm <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "新增数据失败") + return + } + } else { + insertConfirm, err1 := centralKitchenForSchoolWithSpec.CentralKitchenForSchoolWithSpecInsert(&model.CentralKitchenForSchoolWithSpec{ + EnterpriseId: req.EnterpriseId, + BreakfastUnitPrice: req.BreakfastUnitPrice, + LunchUnitPrice: req.LunchUnitPrice, + DinnerUnitPrice: req.DinnerUnitPrice, + BreakfastUnitPriceForTeacher: req.BreakfastUnitPriceForTeacher, + LunchUnitPriceForTeacher: req.LunchUnitPriceForTeacher, + DinnerUnitPriceForTeacher: req.DinnerUnitPriceForTeacher, + CreateAt: now, + UpdateAt: now, + }) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + if insertConfirm <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "新增数据失败") + return + } + } + + e.OutSuc(c, "success", nil) + return +} + +func ListCentralKitchenForSchoolPackage(c *gin.Context) { + var req md.ListCentralKitchenForSchoolPackageReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + centralKitchenForSchoolPackageDb := db.CentralKitchenForSchoolPackageDb{} + centralKitchenForSchoolPackageDb.Set() + list, total, err := centralKitchenForSchoolPackageDb.CentralKitchenForSchoolPackageList(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": list, + "total": total, + "state_list": []map[string]interface{}{ + { + "name": "可用", + "value": 1, + }, + { + "name": "不可用", + "value": 2, + }, + }, + }, nil) + return +} + +func DetailCentralKitchenForSchoolPackage(c *gin.Context) { + packageId := utils.StrToInt(c.DefaultQuery("package_id", "")) + centralKitchenForSchoolPackageDb := db.CentralKitchenForSchoolPackageDb{} + centralKitchenForSchoolPackageDb.Set() + centralKitchenForSchoolPackage, err := centralKitchenForSchoolPackageDb.GetCentralKitchenForSchoolPackage(packageId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if centralKitchenForSchoolPackage == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + centralKitchenForSchoolPackageWithDayDb := db.CentralKitchenForSchoolPackageWithDayDb{} + centralKitchenForSchoolPackageWithDayDb.Set(packageId) + centralKitchenForSchoolPackageWithDay, err := centralKitchenForSchoolPackageWithDayDb.FindCentralKitchenForSchoolPackageWithDay() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + var resp = md.SaveCentralKitchenForSchoolPackageReq{ + PackageId: centralKitchenForSchoolPackage.Id, + EnterpriseId: centralKitchenForSchoolPackage.EnterpriseId, + Year: centralKitchenForSchoolPackage.Year, + Month: centralKitchenForSchoolPackage.Month, + StartDate: centralKitchenForSchoolPackage.StartDate, + EndDate: centralKitchenForSchoolPackage.EndDate, + DateList: nil, + } + for _, v := range *centralKitchenForSchoolPackageWithDay { + resp.DateList = append(resp.DateList, struct { + Date string `json:"date"` + IsOpenBreakfast int32 `json:"is_open_breakfast"` + IsOpenLunch int32 `json:"is_open_lunch"` + IsOpenDinner int32 `json:"is_open_dinner"` + IsOpenReplenish int32 `json:"is_open_replenish"` + }{Date: v.Date, IsOpenBreakfast: int32(v.IsOpenBreakfast), IsOpenLunch: int32(v.IsOpenLunch), IsOpenDinner: int32(v.IsOpenDinner), IsOpenReplenish: int32(v.IsOpenReplenish)}) + } + e.OutSuc(c, resp, nil) + return +} + +func SaveCentralKitchenForSchoolPackage(c *gin.Context) { + var req md.SaveCentralKitchenForSchoolPackageReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + //判断是新增 / 编辑 + if req.PackageId > 0 { + err = svc.UpdateCentralKitchenForSchoolPackage(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + } else { + err = svc.AddCentralKitchenForSchoolPackage(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + } + e.OutSuc(c, "success", nil) + return +} + +func DeleteCentralKitchenForSchoolPackage(c *gin.Context) { + packageId := c.Param("id") + err := svc.DeleteCentralKitchenForSchoolPackage(utils.StrToInt(packageId)) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func SetBasicCentralKitchenForSchool(c *gin.Context) { + var req md.SetBasicCentralKitchenForSchoolReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + now := time.Now() + + //1、更新 central_kitchen_for_school_set + centralKitchenForSchoolSetDb := db.CentralKitchenForSchoolSetDb{} + centralKitchenForSchoolSetDb.Set(req.EnterpriseId) + set, err := centralKitchenForSchoolSetDb.GetCentralKitchenForSchoolSet() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if set != nil { + set.IsOpenTeacherReportMeal = req.IsOpenTeacherReportMeal + set.IsOpenReportMealForDay = req.IsOpenReportMealForDay + set.IsOpenReportMealForMonth = req.IsOpenReportMealForMonth + set.IsOpenReportMealForSemester = req.IsOpenReportMealForSemester + set.UpdateAt = now + _, err2 := centralKitchenForSchoolSetDb.CentralKitchenForSchoolSetUpdate(set.Id, set, "is_open_teacher_report_meal", "is_open_report_meal_for_day", "is_open_report_meal_for_month", "is_open_report_meal_for_semester", "update_at") + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } else { + _, err2 := centralKitchenForSchoolSetDb.CentralKitchenForSchoolSetInsert(&model.CentralKitchenForSchoolSet{ + EnterpriseId: req.EnterpriseId, + IsOpenTeacherReportMeal: req.IsOpenTeacherReportMeal, + IsOpenReportMealForDay: req.IsOpenReportMealForDay, + IsOpenReportMealForMonth: req.IsOpenReportMealForMonth, + IsOpenReportMealForSemester: req.IsOpenReportMealForSemester, + CreateAt: now, + UpdateAt: now, + }) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + + //2、更新 central_kitchen_for_school_with_spec + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(req.EnterpriseId) + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if spec != nil { + spec.IsOpenBreakfast = req.IsOpenBreakfast + spec.IsOpenLunch = req.IsOpenLunch + spec.IsOpenDinner = req.IsOpenDinner + spec.UpdateAt = now + _, err2 := centralKitchenForSchoolWithSpec.CentralKitchenForSchoolWithSpecUpdate(spec.Id, spec, "is_open_breakfast", "is_open_lunch", "is_open_dinner", "update_at") + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } else { + _, err2 := centralKitchenForSchoolWithSpec.CentralKitchenForSchoolWithSpecInsert(&model.CentralKitchenForSchoolWithSpec{ + EnterpriseId: req.EnterpriseId, + IsOpenBreakfast: req.IsOpenBreakfast, + IsOpenLunch: req.IsOpenLunch, + IsOpenDinner: req.IsOpenDinner, + CreateAt: now, + UpdateAt: now, + }) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + + //3、更新 `enterprise` + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + enterprise.Name = req.Name + enterprise.State = req.State + enterprise.UpdateAt = now + _, err = enterpriseDb.EnterpriseUpdate(enterprise, "name", "state", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, "success", nil) + return +} + +func GetBasicCentralKitchenForSchool(c *gin.Context) { + enterpriseId := utils.StrToInt(c.DefaultQuery("enterprise_id", "0")) + + //1、获取 central_kitchen_for_school_set + centralKitchenForSchoolSetDb := db.CentralKitchenForSchoolSetDb{} + centralKitchenForSchoolSetDb.Set(enterpriseId) + set, err := centralKitchenForSchoolSetDb.GetCentralKitchenForSchoolSet() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if set == nil { + set = &model.CentralKitchenForSchoolSet{ + EnterpriseId: enterpriseId, + IsOpenTeacherReportMeal: 0, + IsOpenReportMealForDay: 0, + IsOpenReportMealForMonth: 0, + IsOpenReportMealForSemester: 0, + CreateAt: time.Time{}, + UpdateAt: time.Time{}, + } + _, err2 := centralKitchenForSchoolSetDb.CentralKitchenForSchoolSetInsert(set) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + + //2、获取 central_kitchen_for_school_with_spec + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(enterpriseId) + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if spec == nil { + spec = &model.CentralKitchenForSchoolWithSpec{ + EnterpriseId: enterpriseId, + IsOpenBreakfast: 0, + IsOpenLunch: 0, + IsOpenDinner: 0, + BreakfastUnitPrice: "0", + LunchUnitPrice: "0", + DinnerUnitPrice: "0", + BreakfastUnitPriceForTeacher: "0", + LunchUnitPriceForTeacher: "0", + DinnerUnitPriceForTeacher: "0", + CreateAt: time.Time{}, + UpdateAt: time.Time{}, + } + _, err2 := centralKitchenForSchoolWithSpec.CentralKitchenForSchoolWithSpecInsert(spec) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + + //3、更新 `enterprise` + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(enterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + e.OutSuc(c, map[string]interface{}{ + "central_kitchen_for_school_set": set, + "central_kitchen_for_school_with_spec": spec, + "enterprise": enterprise, + }, nil) + return +} + +func CentralKitchenForSchoolOrdList(c *gin.Context) { + var req md.CentralKitchenForSchoolOrdListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + resp, total, err := svc2.CentralKitchenForSchoolOrdList(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": resp, + "total": total, + "kind_list": []map[string]string{ + { + "name": "按学期购买", + "value": "1", + }, + { + "name": "按月购买", + "value": "2", + }, + { + "name": "按天购买", + "value": "3", + }, + }, + "state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolPackageOrdState.String(enum.CentralKitchenForSchoolPackageOrdStateForWait), + "value": enum.CentralKitchenForSchoolPackageOrdStateForWait, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdState.String(enum.CentralKitchenForSchoolPackageOrdStateForSuccess), + "value": enum.CentralKitchenForSchoolPackageOrdStateForSuccess, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdState.String(enum.CentralKitchenForSchoolPackageOrdStateForFail), + "value": enum.CentralKitchenForSchoolPackageOrdStateForFail, + }, + }, + "ord_state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForWait), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForWait, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunding), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunding, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunded), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunded, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForComplete), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForComplete, + }, + }, + }, nil) + return +} + +func CentralKitchenForSchoolOrdRefund(c *gin.Context) { + var req md.CentralKitchenForSchoolOrdRefundReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + err = svc2.CentralKitchenForSchoolOrdRefund(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolOrdDetail(c *gin.Context) { + outTradeNo := c.DefaultQuery("out_trade_no", "") + centralKitchenForSchoolPackageOrd := db.CentralKitchenForSchoolPackageOrd{} + centralKitchenForSchoolPackageOrd.Set(outTradeNo) + ord, err := centralKitchenForSchoolPackageOrd.GetCentralKitchenForSchoolPackageOrd() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if ord == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应订单记录") + return + } + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + list, err := centralKitchenForSchoolUserWithDayDb.FindCentralKitchenForSchoolUserWithDayByOrdNo(ord.OutTradeNo) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, map[string]interface{}{ + "list": list, + "kind_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForBreakfast), + "value": enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForLunch), + "value": enum.CentralKitchenForSchoolUserWithDayKindForLunch, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForDinner), + "value": enum.CentralKitchenForSchoolUserWithDayKindForDinner, + }, + }, + "state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserWithDayState.String(enum.CentralKitchenForSchoolUserWithDayStateForWait), + "value": enum.CentralKitchenForSchoolUserWithDayStateForWait, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayState.String(enum.CentralKitchenForSchoolUserWithDayStateForAlready), + "value": enum.CentralKitchenForSchoolUserWithDayStateForAlready, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayState.String(enum.CentralKitchenForSchoolUserWithDayStateForCanceling), + "value": enum.CentralKitchenForSchoolUserWithDayStateForCanceling, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayState.String(enum.CentralKitchenForSchoolUserWithDayStateForCancel), + "value": enum.CentralKitchenForSchoolUserWithDayStateForCancel, + }, + }, + }, nil) + return +} diff --git a/app/admin/hdl/enterprise_manage/hdl_enterprise.go b/app/admin/hdl/enterprise_manage/hdl_enterprise.go new file mode 100644 index 0000000..4ab6b88 --- /dev/null +++ b/app/admin/hdl/enterprise_manage/hdl_enterprise.go @@ -0,0 +1,107 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/admin/svc" + svc2 "applet/app/admin/svc/enterprise_manage" + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "applet/app/utils" + "github.com/gin-gonic/gin" +) + +func EnterpriseManageInfo(c *gin.Context) { + enterpriseId := c.DefaultQuery("enterprise_id", "") + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(utils.StrToInt(enterpriseId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + var resp interface{} + switch enterprise.Kind { + case enum.EnterprisePvdByCentralKitchenForSchool: + err, resp = svc.CentralKitchenForSchoolInfo(utils.StrToInt(enterpriseId)) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + break + case enum.EnterprisePvdByCentralKitchenForFactory: + break + case enum.EnterprisePvdBySelfSupportForSchool: + break + case enum.EnterprisePvdBySelfSupportForFactory: + break + } + e.OutSuc(c, map[string]interface{}{ + "info": resp, + "kind": []map[string]interface{}{ + { + "name": enum.EnterprisePvd(enum.EnterprisePvdByCentralKitchenForSchool).String(), + "value": enum.EnterprisePvdByCentralKitchenForSchool, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdByCentralKitchenForFactory).String(), + "value": enum.EnterprisePvdByCentralKitchenForFactory, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdBySelfSupportForSchool).String(), + "value": enum.EnterprisePvdBySelfSupportForSchool, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdBySelfSupportForFactory).String(), + "value": enum.EnterprisePvdBySelfSupportForFactory, + }, + }, + }, nil) + return +} + +func UserIdentityList(c *gin.Context) { + var req md.EnterpriseUserListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + var resp interface{} + switch enterprise.Kind { + case enum.EnterprisePvdByCentralKitchenForSchool: + resp, err = svc2.EnterpriseUserListByCentralKitchenForSchool(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + break + case enum.EnterprisePvdByCentralKitchenForFactory: + break + case enum.EnterprisePvdBySelfSupportForSchool: + break + case enum.EnterprisePvdBySelfSupportForFactory: + break + } + e.OutSuc(c, resp, nil) + return +} diff --git a/app/admin/hdl/hdl_admin.go b/app/admin/hdl/hdl_admin.go new file mode 100644 index 0000000..0f8e728 --- /dev/null +++ b/app/admin/hdl/hdl_admin.go @@ -0,0 +1,13 @@ +package hdl + +import ( + "applet/app/admin/svc" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +func UserInfo(c *gin.Context) { + admInfo := svc.GetUser(c) + e.OutSuc(c, admInfo, nil) + return +} diff --git a/app/admin/hdl/hdl_audit_center.go b/app/admin/hdl/hdl_audit_center.go new file mode 100644 index 0000000..f48711d --- /dev/null +++ b/app/admin/hdl/hdl_audit_center.go @@ -0,0 +1,91 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + svc2 "applet/app/admin/svc/order" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" +) + +func CentralKitchenForSchoolOrderRefundList(c *gin.Context) { + var req md.CentralKitchenForSchoolOrderRefundListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + list, total, err := svc2.CentralKitchenForSchoolOrderRefundList(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": list, + "total": total, + "audit_kind_list": []map[string]interface{}{ + { + "name": "同意", + "value": 1, + }, + { + "name": "拒绝", + "value": 2, + }, + }, + "kind_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForBreakfast), + "value": enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForLunch), + "value": enum.CentralKitchenForSchoolUserWithDayKindForLunch, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForDinner), + "value": enum.CentralKitchenForSchoolUserWithDayKindForDinner, + }, + }, + "state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditing), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditing, + }, + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditPass), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditPass, + }, + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditReject), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditReject, + }, + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditComplete), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditComplete, + }, + }, + }, nil) + return +} + +func CentralKitchenForSchoolOrderRefundAudit(c *gin.Context) { + var req md.CentralKitchenForSchoolOrderRefundAuditReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err = svc2.CentralKitchenForSchoolOrderRefundAudit(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/admin/hdl/hdl_banner.go b/app/admin/hdl/hdl_banner.go new file mode 100644 index 0000000..4474015 --- /dev/null +++ b/app/admin/hdl/hdl_banner.go @@ -0,0 +1,135 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func BannerList(c *gin.Context) { + bannerDb := db.BannerDb{} + bannerDb.Set() + banners, err := bannerDb.FindBanner(0, 0) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": banners, + }, nil) + return +} + +func BannerSort(c *gin.Context) { + var args struct { + Ids []string `json:"ids" binding:"required"` + } + if err := c.ShouldBindJSON(&args); err != nil { + e.OutErr(c, e.ERR_INVALID_ARGS, err) + return + } + bannerDb := db.BannerDb{} + bannerDb.Set() + banners, err := bannerDb.FindBannerById(args.Ids) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + for k, v := range *banners { + v.Sort = k + _, err1 := bannerDb.BannerUpdate(&v, "sort") + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + } + e.OutSuc(c, "success", nil) +} + +func BannerAdd(c *gin.Context) { + var req md.BannerAddReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + bannerDb := db.BannerDb{} + bannerDb.Set() + now := time.Now() + banner := model.Banner{ + Name: req.Name, + ImgUrl: req.ImgUrl, + CreateAt: now, + UpdateAt: now, + } + _, err = bannerDb.BannerInsert(&banner) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func BannerUpdate(c *gin.Context) { + var req md.BannerUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + bannerDb := db.BannerDb{} + bannerDb.Set() + banner, err := bannerDb.GetBanner(req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if banner == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + now := time.Now() + banner.Name = req.Name + banner.ImgUrl = req.ImgUrl + banner.UpdateAt = now + _, err = bannerDb.BannerUpdate(banner, "name", "content", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func BannerDelete(c *gin.Context) { + id := c.Param("id") + bannerDb := db.BannerDb{} + bannerDb.Set() + company, err := bannerDb.GetBanner(utils.StrToInt(id)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if company == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + _, err = bannerDb.BannerDelete(id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/admin/hdl/hdl_company.go b/app/admin/hdl/hdl_company.go new file mode 100644 index 0000000..134d44e --- /dev/null +++ b/app/admin/hdl/hdl_company.go @@ -0,0 +1,127 @@ +package hdl + +import ( + "applet/app/admin/enum" + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func CompanyList(c *gin.Context) { + companyDb := db.CompanyDb{} + companyDb.Set() + companies, err := companyDb.FindCompany(0, 0) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": companies, + "state": []map[string]interface{}{ + { + "name": "正常", + "value": enum.CompanyStateForNormal, + }, + { + "name": "冻结", + "value": enum.CompanyStateForFreeze, + }, + }, + }, nil) + return +} + +func CompanyAdd(c *gin.Context) { + var req md.CompanyAddReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + companyDb := db.CompanyDb{} + companyDb.Set() + now := time.Now() + company := model.Company{ + Name: req.Name, + State: enum.CompanyStateForNormal, + Memo: req.Memo, + LeadName: req.LeadName, + LeadPhone: req.LeadPhone, + CreateAt: now, + UpdateAt: now, + } + _, err = companyDb.CompanyInsert(&company) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CompanyUpdate(c *gin.Context) { + var req md.CompanyUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + companyDb := db.CompanyDb{} + companyDb.Set() + company, err := companyDb.GetCompany(req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if company == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + now := time.Now() + company.Name = req.Name + company.LeadName = req.LeadName + company.LeadPhone = req.LeadPhone + if req.State != 0 { + company.State = req.State + } + company.UpdateAt = now + _, err = companyDb.CompanyUpdate(company, "name", "memo", "state", "lead_name", "lead_phone", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CompanyDelete(c *gin.Context) { + id := c.Param("id") + companyDb := db.CompanyDb{} + companyDb.Set() + company, err := companyDb.GetCompany(utils.StrToInt(id)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if company == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + _, err = companyDb.CompanyDelete(id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/admin/hdl/hdl_demo.go b/app/admin/hdl/hdl_demo.go new file mode 100644 index 0000000..b99fb66 --- /dev/null +++ b/app/admin/hdl/hdl_demo.go @@ -0,0 +1,19 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +// Demo 测试 +func Demo(c *gin.Context) { + qrcodeWithBatchRecordsDb := db.AdminDb{} + qrcodeWithBatchRecordsDb.Set() + data, _, err := qrcodeWithBatchRecordsDb.GetAdminRolePermission(1001) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, data, nil) +} diff --git a/app/admin/hdl/hdl_enterprise.go b/app/admin/hdl/hdl_enterprise.go new file mode 100644 index 0000000..8ddc80c --- /dev/null +++ b/app/admin/hdl/hdl_enterprise.go @@ -0,0 +1,323 @@ +package hdl + +import ( + enum2 "applet/app/admin/enum" + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/admin/svc" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/enum" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func EnterpriseList(c *gin.Context) { + var req md.EnterpriseListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + if req.Limit == 0 { + req.Limit = 10 + } + if req.Page == 0 { + req.Page = 10 + } + enterprises, total, err := svc.EnterpriseList(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, map[string]interface{}{ + "list": enterprises, + "total": total, + "state": []map[string]interface{}{ + { + "name": enum.EnterpriseState(enum.EnterpriseStateForNormal).String(), + "value": enum.EnterpriseStateForNormal, + }, + { + "name": enum.EnterpriseState(enum.EnterpriseStateForFreeze).String(), + "value": enum.EnterpriseStateForFreeze, + }, + }, + "kind": []map[string]interface{}{ + { + "name": enum.EnterprisePvd(enum.EnterprisePvdByCentralKitchenForSchool).String(), + "value": enum.EnterprisePvdByCentralKitchenForSchool, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdByCentralKitchenForFactory).String(), + "value": enum.EnterprisePvdByCentralKitchenForFactory, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdBySelfSupportForSchool).String(), + "value": enum.EnterprisePvdBySelfSupportForSchool, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdBySelfSupportForFactory).String(), + "value": enum.EnterprisePvdBySelfSupportForFactory, + }, + }, + }, nil) + return +} + +func Detail(c *gin.Context) { + enterpriseId := c.DefaultQuery("id", "") + detail, err := svc.EnterpriseDetail(utils.StrToInt(enterpriseId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, detail, nil) + return +} + +func SchoolBelowGrade(c *gin.Context) { + enterpriseId := c.DefaultQuery("enterprise_id", "") + gradeDb := db.GradeDb{} + gradeDb.Set(utils.StrToInt(enterpriseId)) + gradeList, err := gradeDb.FindGrade() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": gradeList, + }, nil) + return +} + +func SchoolGradeBelowClass(c *gin.Context) { + gradeId := c.DefaultQuery("grade_id", "") + classDb := db.ClassDb{} + classDb.Set(utils.StrToInt(gradeId)) + classList, err := classDb.FindClass() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": classList, + }, nil) + return +} + +func EnterpriseAdd(c *gin.Context) { + var req md.EnterpriseAddReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + now := time.Now() + var pvd = 2 + if req.Kind == enum.EnterprisePvdByCentralKitchenForSchool || req.Kind == enum.EnterprisePvdByCentralKitchenForFactory { + pvd = 1 + } + enterprise := model.Enterprise{ + Name: req.Name, + Pvd: int32(pvd), + Kind: req.Kind, + CompanyId: req.CompanyId, + State: enum2.CompanyStateForNormal, + Memo: req.Memo, + CreateAt: now, + UpdateAt: now, + } + _, err = enterpriseDb.EnterpriseInsert(&enterprise) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func EnterpriseDelete(c *gin.Context) { + var req md.EnterpriseDeleteReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err = svc.EnterpriseDelete(req.EnterpriseIds) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func EnterpriseUpdate(c *gin.Context) { + var req md.EnterpriseUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + + //1、更新 enterprise + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + var pvd = 2 + if req.Kind == enum.EnterprisePvdByCentralKitchenForSchool || req.Kind == enum.EnterprisePvdByCentralKitchenForFactory { + pvd = 1 + } + + now := time.Now() + enterprise.Name = req.Name + enterprise.Memo = req.Memo + enterprise.Pvd = int32(pvd) + enterprise.Kind = req.Kind + enterprise.CompanyId = req.CompanyId + enterprise.UpdateAt = now + _, err = enterpriseDb.EnterpriseUpdate(enterprise, "name", "memo", "pvd", "kind", "company_id", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + //2、删除 grade && class 数据 + gradeDb := db.GradeDb{} + gradeDb.Set(req.Id) + _, err = gradeDb.ClassDeleteBySessionForEnterprise(db.Db.NewSession(), req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + classDb := db.ClassDb{} + classDb.Set(0) + _, err = classDb.ClassDeleteBySessionForEnterprise(db.Db.NewSession(), req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + //新增 grade 数据 && class 数据 + for _, v := range req.GradeList { + insertId, err1 := gradeDb.GradeInsert(&model.Grade{ + EnterpriseId: req.Id, + Name: v.Name, + Memo: "", + CreateAt: now, + UpdateAt: now, + }) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + var classes []*model.Class + for _, v1 := range v.ClassList { + classes = append(classes, &model.Class{ + Name: v1.Name, + Memo: "", + GradeId: insertId, + EnterpriseId: req.Id, + CreateAt: now, + UpdateAt: now, + }) + } + + if len(classes) > 0 { + _, err2 := classDb.BatchAddClass(classes) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + } + + e.OutSuc(c, "success", nil) + return +} + +func EnterpriseAddGrade(c *gin.Context) { + var req md.EnterpriseUpdateStateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + now := time.Now() + enterprise.State = req.State + enterprise.UpdateAt = now + _, err = enterpriseDb.EnterpriseUpdate(enterprise, "state", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func EnterpriseUpdateState(c *gin.Context) { + var req md.EnterpriseUpdateStateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + now := time.Now() + enterprise.State = req.State + enterprise.UpdateAt = now + _, err = enterpriseDb.EnterpriseUpdate(enterprise, "state", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/admin/hdl/hdl_file_upload.go b/app/admin/hdl/hdl_file_upload.go new file mode 100644 index 0000000..b67583c --- /dev/null +++ b/app/admin/hdl/hdl_file_upload.go @@ -0,0 +1,50 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "applet/app/svc" + "applet/app/utils" + "github.com/gin-gonic/gin" + "github.com/mcuadros/go-defaults" +) + +func ImgReqUpload(c *gin.Context) { + var args struct { + DirName string `json:"dir_name,omitempty" default:"0"` + FileName string `json:"file_name" binding:"required" label:"文件名"` + FileSize int64 `json:"file_size" binding:"gt=1" label:"文件大小"` // 文件大小, 单位byte + } + defaults.SetDefaults(&args) + err := c.ShouldBindJSON(&args) + if err != nil { + err = svc.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + + scheme := "http" + if c.Request.TLS != nil { + scheme = "https" + } + // 拼装回调地址 + callbackUrl := scheme + "://" + c.Request.Host + "/api/file/upload/callback" + if true { // 本地使用内网穿透地址测试 + callbackUrl = "" + } + res, err := svc.ImgReqUpload("", args.DirName, args.FileName, callbackUrl, args.FileSize) + if err != nil { + e.OutErr(c, e.ERR_BAD_REQUEST, err.Error()) + return + } + my := utils.SerializeStr(res) + var my1 map[string]interface{} + utils.Unserialize([]byte(my), &my1) + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + my1["host"] = sysCfgDb.SysCfgGetWithDb(enum.FileBucketRegion) + e.OutSuc(c, my1, nil) + +} diff --git a/app/admin/hdl/hdl_login.go b/app/admin/hdl/hdl_login.go new file mode 100644 index 0000000..28869ad --- /dev/null +++ b/app/admin/hdl/hdl_login.go @@ -0,0 +1,45 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + md2 "applet/app/admin/md" + "applet/app/admin/svc" + "applet/app/db" + "applet/app/e" + "applet/app/utils" + "fmt" + "github.com/gin-gonic/gin" +) + +func Login(c *gin.Context) { + var req md2.LoginReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + adminDb := db.AdminDb{} + adminDb.Set() + admin, err := adminDb.GetAdminByUserName(req.UserName) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if utils.Md5(req.PassWord) != admin.Password { + e.OutErr(c, e.ERR_INVALID_ARGS, "密码错误") + return + } + ip := utils.GetIP(c.Request) + key := fmt.Sprintf(md2.AdminJwtTokenKey, ip, utils.AnyToString(admin.AdmId)) + token, err := svc.HandleLoginToken(key, admin) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, md2.LoginResponse{ + Token: token, + }, nil) + return +} diff --git a/app/admin/hdl/hdl_notice.go b/app/admin/hdl/hdl_notice.go new file mode 100644 index 0000000..3806a4a --- /dev/null +++ b/app/admin/hdl/hdl_notice.go @@ -0,0 +1,135 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func NoticeList(c *gin.Context) { + noticeDb := db.NoticeDb{} + noticeDb.Set() + notices, err := noticeDb.FindNotice(0, 0) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": notices, + }, nil) + return +} + +func NoticeSort(c *gin.Context) { + var args struct { + Ids []string `json:"ids" binding:"required"` + } + if err := c.ShouldBindJSON(&args); err != nil { + e.OutErr(c, e.ERR_INVALID_ARGS, err) + return + } + noticeDb := db.NoticeDb{} + noticeDb.Set() + notices, err := noticeDb.FindNoticeById(args.Ids) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + for k, v := range *notices { + v.Sort = k + _, err1 := noticeDb.NoticeUpdate(&v, "sort") + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + } + e.OutSuc(c, "success", nil) +} + +func NoticeAdd(c *gin.Context) { + var req md.NoticeAddReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + noticeDb := db.NoticeDb{} + noticeDb.Set() + now := time.Now() + notice := model.Notice{ + Name: req.Name, + Content: req.Content, + CreateAt: now, + UpdateAt: now, + } + _, err = noticeDb.NoticeInsert(¬ice) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func NoticeUpdate(c *gin.Context) { + var req md.NoticeUpdateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + noticeDb := db.NoticeDb{} + noticeDb.Set() + notice, err := noticeDb.GetNotice(req.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if notice == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + now := time.Now() + notice.Name = req.Name + notice.Content = req.Content + notice.UpdateAt = now + _, err = noticeDb.NoticeUpdate(notice, "name", "content", "update_at") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func NoticeDelete(c *gin.Context) { + id := c.Param("id") + noticeDb := db.NoticeDb{} + noticeDb.Set() + company, err := noticeDb.GetNotice(utils.StrToInt(id)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if company == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + _, err = noticeDb.NoticeDelete(id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/admin/hdl/hdl_role.go b/app/admin/hdl/hdl_role.go new file mode 100644 index 0000000..bf605b0 --- /dev/null +++ b/app/admin/hdl/hdl_role.go @@ -0,0 +1,479 @@ +package hdl + +import ( + "applet/app/admin/enum" + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/admin/svc" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func PermissionGroupList(c *gin.Context) { + roleId := c.DefaultQuery("role_id", "") + qrcodeWithBatchRecordsDb := db.PermissionGroupDb{} + qrcodeWithBatchRecordsDb.Set() + groupList, err := qrcodeWithBatchRecordsDb.FindPermissionGroup() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + roleDb := db.RoleDb{} + roleDb.Set(0) + list, _, err := roleDb.FindPermissionGroupByRole(utils.StrToInt(roleId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + var isHasPermissionGroupId []string + for _, v := range list { + isHasPermissionGroupId = append(isHasPermissionGroupId, utils.IntToStr(v.PermissionGroup.Id)) + } + + var tempResp = map[string]*md.PermissionGroupListResp{} + for _, v := range *groupList { + isCheck := false + if utils.InArr(utils.IntToStr(v.Id), isHasPermissionGroupId) { + isCheck = true + } + tempResp[utils.IntToStr(v.Id)] = &md.PermissionGroupListResp{ + Id: v.Id, + Name: v.Name, + State: v.State, + ParentId: v.ParentId, + CreateAt: v.CreateAt.Format("2006-01-02 15:04:05"), + UpdateAt: v.UpdateAt.Format("2006-01-02 15:04:05"), + IsCheck: isCheck, + } + } + + var resp []md.PermissionGroupListResp + for _, v := range tempResp { + if v.ParentId != 0 { + tempResp[utils.IntToStr(v.ParentId)].SubPermissionGroupList = append(tempResp[utils.IntToStr(v.ParentId)].SubPermissionGroupList, *v) + } else { + resp = append(resp, *v) + } + } + + e.OutSuc(c, map[string]interface{}{ + "list": resp, + "state": []map[string]interface{}{ + { + "name": enum.PermissionGroupState(enum.PermissionGroupStateForNormal).String(), + "value": enum.PermissionGroupStateForNormal, + }, + { + "name": enum.PermissionGroupState(enum.PermissionGroupStateForDiscard).String(), + "value": enum.PermissionGroupStateForDiscard, + }, + }, + }, nil) + return +} + +func RoleList(c *gin.Context) { + roleDb := db.RoleDb{} + roleDb.Set(0) + roleList, err := roleDb.FindRole() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + adminRoleDb := db.AdminRoleDb{} + adminRoleDb.Set() + adminDb := db.AdminDb{} + adminDb.Set() + var result []*md.RoleListResp + for _, v := range *roleList { + var temp md.RoleListResp + temp.Data = v + adminRole, err := adminRoleDb.GetAdminRoleByRole(v.Id) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if adminRole != nil { + admin, err := adminDb.GetAdmin(adminRole.AdmId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + temp.AdminList = append(temp.AdminList, struct { + Name string `json:"name"` + }{ + Name: admin.Username, + }) + } + result = append(result, &temp) + } + e.OutSuc(c, map[string]interface{}{ + "list": result, + "state": []map[string]interface{}{ + { + "name": enum.RoleState(enum.RoleStateForNormal).String(), + "value": enum.RoleStateForNormal, + }, + { + "name": enum.RoleState(enum.RoleStateForFreeze).String(), + "value": enum.RoleStateForFreeze, + }, + }, + }, nil) + return +} + +func AddRole(c *gin.Context) { + var req md.AddRoleReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + roleDb := db.RoleDb{} + roleDb.Set(0) + now := time.Now() + _, err = roleDb.RoleInsert(&model.Role{ + Name: req.Name, + State: enum.RoleStateForNormal, + Memo: req.Memo, + CreateAt: now.Format("2006-01-02 15:04:05"), + UpdateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, "success", nil) + return +} + +func UpdateRole(c *gin.Context) { + var req md.UpdateRoleReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + roleDb := db.RoleDb{} + roleDb.Set(req.RoleId) + role, err := roleDb.GetRole() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if role == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录") + return + } + role.Name = req.Name + role.Memo = req.Memo + _, err = roleDb.UpdateRole(role, "name", "memo") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func RoleBindPermissionGroup(c *gin.Context) { + var req md.RoleBindPermissionGroupReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err = svc.RoleBindPermissionGroup(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + + e.OutSuc(c, "success", nil) + return +} + +func UpdateRoleState(c *gin.Context) { + var req md.UpdateRoleStateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + roleDb := db.RoleDb{} + roleDb.Set(req.RoleId) + role, err := roleDb.GetRole() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if role == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录") + return + } + role.State = req.State + _, err = roleDb.UpdateRole(role, "state") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func DeleteRole(c *gin.Context) { + id := c.Param("id") + roleDb := db.RoleDb{} + roleDb.Set(utils.StrToInt(id)) + role, err := roleDb.GetRole() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if role == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录") + return + } + + err = svc.DeleteRole(utils.StrToInt(id)) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + + e.OutSuc(c, "success", nil) + return +} + +func AdminList(c *gin.Context) { + var req md.AdminListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + if req.Limit == 0 { + req.Limit = 10 + } + if req.Page == 0 { + req.Page = 10 + } + adminDb := db.AdminDb{} + adminDb.Set() + adminList, total, err := adminDb.FindAdmin(req.UserName, req.State, req.Page, req.Limit) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + var result []md.AdminListResp + for _, v := range adminList { + permissionGroupList, _, err1 := adminDb.FindAdminRolePermissionGroup(v.AdmId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + var roleList []string + for _, v1 := range permissionGroupList { + roleList = append(roleList, v1.Role.Name) + } + result = append(result, md.AdminListResp{ + AdmId: v.AdmId, + Username: v.Username, + State: v.State, + IsSuperAdministrator: v.IsSuperAdministrator, + Memo: v.Memo, + CreateAt: v.CreateAt, + UpdateAt: v.UpdateAt, + RoleList: roleList, + }) + } + + e.OutSuc(c, map[string]interface{}{ + "list": result, + "total": total, + "state": []map[string]interface{}{ + { + "name": enum.RoleState(enum.RoleStateForNormal).String(), + "value": enum.RoleStateForNormal, + }, + { + "name": enum.RoleState(enum.RoleStateForFreeze).String(), + "value": enum.RoleStateForFreeze, + }, + }, + }, nil) + return +} + +func UpdateAdminState(c *gin.Context) { + var req md.UpdateAdminStateReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + admDb := db.AdminDb{} + admDb.Set() + admin, err := admDb.GetAdmin(req.AdmId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if admin == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录") + return + } + admin.State = req.State + _, err = admDb.UpdateAdmin(admin, "state") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func UpdateAdmin(c *gin.Context) { + var req md.UpdateAdminReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + admDb := db.AdminDb{} + admDb.Set() + admin, err := admDb.GetAdmin(req.AdmId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if admin == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录") + return + } + admin.Username = req.Username + admin.Memo = req.Memo + admin.Password = utils.Md5(req.Password) + _, err = admDb.UpdateAdmin(admin, "username", "memo", "password") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func AddAdmin(c *gin.Context) { + var req md.AddAdminReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + admDb := db.AdminDb{} + admDb.Set() + + admId, err := admDb.CreateAdminId() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + admin := model.Admin{ + AdmId: admId, + Username: req.Username, + Password: utils.Md5(req.Password), + State: enum.AdminStateForNormal, + IsSuperAdministrator: 0, + Memo: req.Memo, + CreateAt: time.Now().Format("2006-01-02 15:04:05"), + UpdateAt: time.Now().Format("2006-01-02 15:04:05"), + } + _, err = admDb.AdminInsert(&admin) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func DeleteAdmin(c *gin.Context) { + admId := c.Param("adm_id") + admDb := db.AdminDb{} + admDb.Set() + err := svc.AdminDelete([]int{utils.StrToInt(admId)}) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func BindAdminRole(c *gin.Context) { + var req md.BindAdminRoleReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err = svc.BindAdminRole(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func AdminInfo(c *gin.Context) { + admId := c.DefaultQuery("adm_id", "") + admDb := db.AdminDb{} + admDb.Set() + admin, err := admDb.GetAdmin(utils.StrToInt(admId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + admin.Password = "" + e.OutSuc(c, map[string]interface{}{ + "info": admin, + "state": []map[string]interface{}{ + { + "name": enum.RoleState(enum.RoleStateForNormal).String(), + "value": enum.RoleStateForNormal, + }, + { + "name": enum.RoleState(enum.RoleStateForFreeze).String(), + "value": enum.RoleStateForFreeze, + }, + }, + }, nil) + return +} diff --git a/app/admin/hdl/hdl_set_center.go b/app/admin/hdl/hdl_set_center.go new file mode 100644 index 0000000..51d21a9 --- /dev/null +++ b/app/admin/hdl/hdl_set_center.go @@ -0,0 +1,36 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" +) + +func SetCenter(c *gin.Context) { + var req md.SetCenterReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + sysCfgDb.SysCfgUpdate(enum.AdministratorContactInfo, req.AdministratorContactInfo) + sysCfgDb.SysCfgUpdate(enum.CentralKitchenForSchoolReserveMealTime, req.CentralKitchenForSchoolReserveMealTime) + sysCfgDb.SysCfgUpdate(enum.CentralKitchenForSchoolCancelMealTime, req.CentralKitchenForSchoolCancelMealTime) + e.OutSuc(c, "success", nil) + return +} + +func GetCenter(c *gin.Context) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + res := sysCfgDb.SysCfgFindWithDb(enum.AdministratorContactInfo, enum.CentralKitchenForSchoolReserveMealTime, enum.CentralKitchenForSchoolCancelMealTime) + e.OutSuc(c, res, nil) + return +} diff --git a/app/admin/hdl/hdl_sys_cfg.go b/app/admin/hdl/hdl_sys_cfg.go new file mode 100644 index 0000000..39d2804 --- /dev/null +++ b/app/admin/hdl/hdl_sys_cfg.go @@ -0,0 +1,17 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" +) + +func GetSysCfg(c *gin.Context) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + res := sysCfgDb.SysCfgFindWithDb(enum.AppName, enum.OpenAppletPublicKey, enum.OpenAppletAppPublicKey, enum.OpenAppletAppPrivateKey, enum.OpenAppletAesKey, + enum.FileBucket, enum.FileExt, enum.FileAccessKey, enum.FileUserUploadMaxSize, enum.FileSecretKey, enum.FileBucketScheme, enum.FileBucketRegion, enum.FileBucketHost) + e.OutSuc(c, res, nil) + return +} diff --git a/app/admin/hdl/hdl_user.go b/app/admin/hdl/hdl_user.go new file mode 100644 index 0000000..079452a --- /dev/null +++ b/app/admin/hdl/hdl_user.go @@ -0,0 +1,122 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/admin/md" + "applet/app/admin/svc" + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "applet/app/utils" + "github.com/gin-gonic/gin" +) + +func UserList(c *gin.Context) { + var req md.UserListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + + list, err := svc.UserList(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": list, + "user_identity_kind_list": []map[string]interface{}{ + { + "name": enum.UserIdentity.String(enum.UserIdentityForCentralKitchenForStudent), + "value": enum.UserIdentityForCentralKitchenForStudent, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForCentralKitchenForTeacher), + "value": enum.UserIdentityForCentralKitchenForTeacher, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForCentralKitchenForWorker), + "value": enum.UserIdentityForCentralKitchenForWorker, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForSelfSupportForStudent), + "value": enum.UserIdentityForSelfSupportForStudent, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForSelfSupportForTeacher), + "value": enum.UserIdentityForSelfSupportForTeacher, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForSelfSupportForWorker), + "value": enum.UserIdentityForSelfSupportForWorker, + }, + }, + "enterprise_kind_list": []map[string]interface{}{ + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdByCentralKitchenForSchool), + "value": enum.EnterprisePvdByCentralKitchenForSchool, + }, + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdByCentralKitchenForFactory), + "value": enum.EnterprisePvdByCentralKitchenForFactory, + }, + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdBySelfSupportForSchool), + "value": enum.EnterprisePvdBySelfSupportForSchool, + }, + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdBySelfSupportForFactory), + "value": enum.EnterprisePvdBySelfSupportForFactory, + }, + }, + }, nil) + return +} + +func UserUpdate(c *gin.Context) { + var req md.UserList + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + + err = svc.UserUpdate(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func UserDelete(c *gin.Context) { + id := c.Param("id") + //TODO::判断当前用户下是否还绑定了身份 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(utils.StrToInt(id)) + identity, err := userIdentityDb.FindUserIdentity() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if len(*identity) > 0 { + e.OutErr(c, e.ERR_DB_ORM, "当前用户下存在未解绑身份") + return + } + + userDb := db.UserDb{} + userDb.Set() + _, err = userDb.DeleteUser(utils.StrToInt(id)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/admin/lib/auth/auth.go b/app/admin/lib/auth/auth.go new file mode 100644 index 0000000..6d86b8e --- /dev/null +++ b/app/admin/lib/auth/auth.go @@ -0,0 +1,39 @@ +package auth + +import ( + "errors" + "github.com/dgrijalva/jwt-go" + "time" +) + +// GenToken 生成JWT +func GenToken(admId int, username string) (string, error) { + // 创建一个我们自己的声明 + c := JWTUser{ + AdmId: admId, + Username: username, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 + Issuer: "smart_canteen", // 签发人 + }, + } + // 使用指定的签名方法创建签名对象 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + // 使用指定的secret签名并获得完整的编码后的字符串token + return token.SignedString(Secret) +} + +// ParseToken 解析JWT +func ParseToken(tokenString string) (*JWTUser, error) { + // 解析token + token, err := jwt.ParseWithClaims(tokenString, &JWTUser{}, func(token *jwt.Token) (i interface{}, err error) { + return Secret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*JWTUser); ok && token.Valid { // 校验token + return claims, nil + } + return nil, errors.New("invalid token") +} diff --git a/app/admin/lib/auth/base.go b/app/admin/lib/auth/base.go new file mode 100644 index 0000000..37d3b97 --- /dev/null +++ b/app/admin/lib/auth/base.go @@ -0,0 +1,19 @@ +package auth + +import ( + "time" + + "github.com/dgrijalva/jwt-go" +) + +// TokenExpireDuration is jwt 过期时间 +const TokenExpireDuration = time.Hour * 4380 + +var Secret = []byte("smart_canteen_admin") + +// JWTUser 如果想要保存更多信息,都可以添加到这个结构体中 +type JWTUser struct { + AdmId int `json:"adm_id"` + Username string `json:"username"` + jwt.StandardClaims +} diff --git a/app/admin/lib/validate/validate_comm.go b/app/admin/lib/validate/validate_comm.go new file mode 100644 index 0000000..9305d9e --- /dev/null +++ b/app/admin/lib/validate/validate_comm.go @@ -0,0 +1,33 @@ +package validate + +import ( + "applet/app/e" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +func HandleValidateErr(err error) error { + switch err.(type) { + case *json.UnmarshalTypeError: + return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误") + case validator.ValidationErrors: + errs := err.(validator.ValidationErrors) + transMsgMap := errs.Translate(utils.ValidatorTrans) // utils.ValidatorTrans \app\utils\validator_err_trans.go::ValidatorTransInit初始化获得 + transMsgOne := transMsgMap[GetOneKeyOfMapString(transMsgMap)] + return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne) + default: + _ = logx.Error(err) + return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err)) + } +} + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} diff --git a/app/admin/lib/wx/wx_official_account.go b/app/admin/lib/wx/wx_official_account.go new file mode 100644 index 0000000..0dc62d6 --- /dev/null +++ b/app/admin/lib/wx/wx_official_account.go @@ -0,0 +1,85 @@ +package wx + +import ( + "applet/app/admin/db" + enum2 "applet/app/admin/enum" + md2 "applet/app/admin/md" + "applet/app/utils" + "applet/app/utils/cache" + "encoding/json" + "errors" +) + +type OfficialAccount struct { + AccessToken string `json:"access_token"` + Appid string `json:"appid"` + Secret string `json:"secret"` +} + +func (officialAccount *OfficialAccount) Set() { // set方法 + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + officialAccount.Appid = sysCfgDb.SysCfgGetWithDb(enum2.WxOfficialAccountAppId) + officialAccount.Secret = sysCfgDb.SysCfgGetWithDb(enum2.WxOfficialAccountAppSecret) + officialAccount.AccessToken = officialAccount.createToken() +} + +func (officialAccount *OfficialAccount) createToken() (accessToken string) { + cacheKey := md2.WxOfficialAccountCacheKey + accessToken, _ = cache.GetString(cacheKey) + if accessToken != "" { + return + } + + url := md2.WxOfficialAccountRequestBaseUrl + enum2.GetAccessToken + post, err := utils.CurlPost(url, map[string]string{ + "appid": officialAccount.Appid, + "secret": officialAccount.Secret, + "grant_type": "client_credential", + }, nil) + + utils.FilePutContents("wx_official_account_create_token", "resp"+string(post)) + var data md2.CreateTokenResp + err = json.Unmarshal(post, &data) + if err != nil { + return + } + if data.AccessToken == "" { + panic(errors.New("获取 access_token 失败")) + } + + accessToken = data.AccessToken + cache.SetEx(cacheKey, accessToken, int(data.ExpiresIn-3600)) + return +} + +func (officialAccount *OfficialAccount) QrcodeCreate(sceneStr string) (qrcodeUrl string, err error) { + url := md2.WxOfficialAccountRequestBaseUrl + enum2.QrcodeCreate + "?access_token=" + officialAccount.AccessToken + //post, err := utils.CurlPost(url, map[string]interface{}{ + // "action_name": "QR_LIMIT_STR_SCENE", + // "action_info": map[string]interface{}{ + // "scene": map[string]string{ + // "scene_str": sceneStr, + // }, + // }, + //}, nil) + requestBody, _ := json.Marshal(map[string]interface{}{ + "action_name": "QR_STR_SCENE", + "expire_seconds": "6000", + "action_info": map[string]interface{}{ + "scene": map[string]string{ + "scene_str": sceneStr, + }, + }, + }) + post, err := utils.CurlPost(url, requestBody, nil) + + utils.FilePutContents("wx_official_account_qrcode_create", "resp"+string(post)) + var data md2.CreateQrcodeResp + err = json.Unmarshal(post, &data) + if err != nil { + return + } + qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + data.Ticket + return +} diff --git a/app/admin/md/md_app_redis_key.go b/app/admin/md/md_app_redis_key.go new file mode 100644 index 0000000..6f0865b --- /dev/null +++ b/app/admin/md/md_app_redis_key.go @@ -0,0 +1,12 @@ +package md + +// 缓存key统一管理 +const ( + AdminJwtTokenKey = "%s:smart_canteen_admin_jwt_token:%s" // jwt, 占位符:ip, admin:id + JwtTokenCacheTime = 3600 * 24 + AdminRolePermissionKey = "%s:smart_canteen_admin_role_permission:%s" // 占位符:ip, admin:id + AdminRolePermissionCacheTime = 3600 * 24 * 0.5 + CfgCacheTime = 86400 + AppCfgCacheKey = "one_item_one_code:%s" // 占位符: key的第一个字母 + WxOfficialAccountCacheKey = "wx_official_account" // 占位符: key的第一个字母 +) diff --git a/app/admin/md/md_banner.go b/app/admin/md/md_banner.go new file mode 100644 index 0000000..41e7dee --- /dev/null +++ b/app/admin/md/md_banner.go @@ -0,0 +1,12 @@ +package md + +type BannerAddReq struct { + Name string `json:"name" binding:"required" label:"名称"` + ImgUrl string `json:"img_url" label:"内容"` +} + +type BannerUpdateReq struct { + Id int `json:"id" binding:"required" label:"公司id"` + Name string `json:"name" binding:"required" label:"名称"` + ImgUrl string `json:"img_url" label:"内容"` +} diff --git a/app/admin/md/md_central_kitchen_for_school.go b/app/admin/md/md_central_kitchen_for_school.go new file mode 100644 index 0000000..a150a11 --- /dev/null +++ b/app/admin/md/md_central_kitchen_for_school.go @@ -0,0 +1,18 @@ +package md + +type CentralKitchenForSchoolInfoResp struct { + Name string `json:"name" label:"名称"` + Memo string `json:"memo" label:"备注"` + Kind string `json:"kind" label:"种类(1:央厨-学校 2:央厨-工厂 3:自营-学校 4:自营-工厂)"` + State string `json:"state" label:"状态(1:正常 2:冻结)"` + IsOpenTeacherReportMeal int `json:"is_open_teacher_report_meal" label:"教师报餐(1:开启 2:关闭)"` + IsOpenReportMealForDay int `json:"is_open_report_meal_for_day" label:"开启按天报餐(1:开启 2:关闭)"` + IsOpenReportMealForMonth int `json:"is_open_report_meal_for_month" label:"开启按月报餐(1:开启 2:关闭)"` + IsOpenReportMealForSemester int `json:"is_open_report_meal_for_semester" label:"开启按学期报餐(1:开启 2:关闭)"` + IsOpenBreakfast int `json:"is_open_breakfast" label:"是否开启早餐(1:开启 0:关闭)"` + IsOpenLunch int `json:"is_open_lunch" label:"是否开启午餐(1:开启 0:关闭)"` + IsOpenDinner int `json:"is_open_dinner" label:"是否开启晚餐(1:开启 0:关闭)"` + TeacherNum int64 `json:"teacher_num" label:"教师数量"` + StudentNum int64 `json:"student_num" label:"学生数量"` + ClassNum int64 `json:"class_num" label:"班级数量"` +} diff --git a/app/admin/md/md_central_kitchen_for_school_order.go b/app/admin/md/md_central_kitchen_for_school_order.go new file mode 100644 index 0000000..5b54152 --- /dev/null +++ b/app/admin/md/md_central_kitchen_for_school_order.go @@ -0,0 +1,39 @@ +package md + +type CentralKitchenForSchoolOrderRefundListResp struct { + Id int `json:"id" label:"退款id"` + OutTradeNo string `json:"out_trade_no" label:"订单号"` + OutRequestNo string `json:"out_request_no" label:"退款请求号"` + Name string `json:"name" label:"姓名"` + Phone string `json:"phone" label:"用户电话"` + EnterpriseName string `json:"enterprise_name" label:"学校名称"` + ClassName string `json:"class_name" label:"班级名称"` + GradeName string `json:"grade_name" label:"年纪名称"` + Kind int `json:"kind" label:"订餐类型"` + Amount string `json:"amount" label:"退款金额"` + State int `json:"state" label:"退款订单状态"` + CreateAt string `json:"create_at" label:"申请时间"` + Memo string `json:"memo" label:"备注"` +} + +type CentralKitchenForSchoolOrderRefundListReq struct { + EnterpriseId int `json:"enterprise_id" label:"单位id"` + Phone string `json:"phone" label:"用户电话"` + Name string `json:"name" label:"名称"` + Kind int `json:"kind" label:"订餐类型"` + State int `json:"state" label:"退款订单状态"` + StartDate string `json:"start_date" label:"开始时间"` + EndDate string `json:"end_date" label:"截止时间"` + OutTradeNo string `json:"out_trade_no" label:"订单号"` + OutRequestNo string `json:"out_request_no" label:"退款请求号"` + GradeId int `json:"grade_id" label:"年级"` + ClassId int `json:"class_id" label:"班级"` + Limit int `json:"limit"` + Page int `json:"page" ` +} + +type CentralKitchenForSchoolOrderRefundAuditReq struct { + Ids []string `json:"ids" label:"退款记录id"` + State int `json:"state" label:"审核状态"` + Memo string `json:"memo" label:"备注"` +} diff --git a/app/admin/md/md_central_kitchen_for_school_package_with_day.go b/app/admin/md/md_central_kitchen_for_school_package_with_day.go new file mode 100644 index 0000000..01aa81e --- /dev/null +++ b/app/admin/md/md_central_kitchen_for_school_package_with_day.go @@ -0,0 +1,6 @@ +package md + +const ( + OpenReplenish = 1 //开启-补餐 + CloseReplenish = 0 //关闭-补餐 +) diff --git a/app/admin/md/md_central_kitchen_for_school_with_spec.go b/app/admin/md/md_central_kitchen_for_school_with_spec.go new file mode 100644 index 0000000..c33970e --- /dev/null +++ b/app/admin/md/md_central_kitchen_for_school_with_spec.go @@ -0,0 +1,10 @@ +package md + +const ( + OpenBreakfast = 1 //开启-早餐 + CloseBreakfast = 0 //关闭-早餐 + OpenLunch = 1 //开启-午餐 + CloseLunch = 0 //关闭-午餐 + OpenDinner = 1 //开启-晚餐 + CloseDinner = 0 //关闭-晚餐 +) diff --git a/app/admin/md/md_company.go b/app/admin/md/md_company.go new file mode 100644 index 0000000..df35591 --- /dev/null +++ b/app/admin/md/md_company.go @@ -0,0 +1,17 @@ +package md + +type CompanyAddReq struct { + Name string `json:"name" binding:"required" label:"名称"` + Memo string `json:"memo" label:"备注"` + LeadName string `json:"lead_name" label:"负责人姓名"` + LeadPhone string `json:"lead_phone" label:"负责人手机号"` +} + +type CompanyUpdateReq struct { + Id int `json:"id" binding:"required" label:"公司id"` + State int32 `json:"state" label:"状态"` + Name string `json:"name" binding:"required" label:"名称"` + Memo string `json:"memo" binding:"required" label:"备注"` + LeadName string `json:"lead_name" label:"负责人姓名"` + LeadPhone string `json:"lead_phone" label:"负责人手机号"` +} diff --git a/app/admin/md/md_enterprise.go b/app/admin/md/md_enterprise.go new file mode 100644 index 0000000..48c9f78 --- /dev/null +++ b/app/admin/md/md_enterprise.go @@ -0,0 +1,53 @@ +package md + +import "applet/app/db/model" + +type EnterpriseAddReq struct { + Name string `json:"name" binding:"required" label:"名称"` + Pvd int32 `json:"pvd" label:"场景"` + Kind int32 `json:"kind" binding:"required" label:"种类(1:央厨-学校 2:央厨-工厂 3:自营-学校 4:自营-工厂)"` + CompanyId int `json:"company_id" binding:"required" label:"所属公司id"` + Memo string `json:"memo" label:"备注"` +} + +type EnterpriseUpdateReq struct { + Id int `json:"id" binding:"required" label:"企业id"` + Name string `json:"name" binding:"required" label:"名称"` + Pvd int32 `json:"pvd" label:"场景"` + Kind int32 `json:"kind" binding:"required" label:"种类(1:央厨-学校 2:央厨-工厂 3:自营-学校 4:自营-工厂)"` + CompanyId int `json:"company_id" binding:"required" label:"所属公司id"` + Memo string `json:"memo" label:"备注"` + State int32 `json:"state" label:"状态"` + GradeList []struct { + Name string `json:"name" label:"名称"` + ClassList []struct { + Name string `json:"name" label:"名称"` + } `json:"class_list" label:"班级"` + } `json:"grade_list" label:"年级"` +} + +type EnterpriseDeleteReq struct { + EnterpriseIds []int `json:"enterprise_ids"` +} + +type EnterpriseUpdateStateReq struct { + Id int `json:"id" binding:"required" label:"企业id"` + State int32 `json:"state" label:"状态"` +} + +type EnterpriseListReq struct { + Limit int `json:"limit"` + Page int `json:"page" ` + Name string `json:"name" label:"名称"` + Kind int `json:"kind" label:"种类"` +} + +type EnterpriseDetailResp struct { + Enterprise model.Enterprise `json:"enterprise"` + GradeList []GradeListStruct `json:"grade_list"` +} + +type GradeListStruct struct { + Grade model.Grade `json:"grade"` + ClassList []model.Class `json:"class_list"` +} diff --git a/app/admin/md/md_enterprise_manage.go b/app/admin/md/md_enterprise_manage.go new file mode 100644 index 0000000..06658de --- /dev/null +++ b/app/admin/md/md_enterprise_manage.go @@ -0,0 +1,206 @@ +package md + +type EnterpriseUserListReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Limit int `json:"limit" binding:"required"` + Page int `json:"page" binding:"required"` + Nickname string `json:"nickname" label:"支付宝昵称"` + Phone string `json:"phone" label:"手机号"` + IsTeacher int `json:"is_teacher" label:"是否教师"` +} + +type EnterpriseUserListByCentralKitchenForSchoolStruct struct { + Id int `json:"id" label:"支付宝昵称"` + Nickname string `json:"nickname" label:"支付宝昵称"` + Phone string `json:"phone" label:"手机号"` + Avatar string `json:"avatar" label:"头像"` + IsTeacher int `json:"is_teacher" label:"是否教师"` + CreateAt string `json:"create_at" label:"创建时间"` + UserIdentities []struct { + IdNo string `json:"id_no" label:"身份证号"` + SchoolName string `json:"school_name" label:"学校名"` + Name string `json:"name" label:"姓名"` + Grade string `json:"grade" label:"年级"` + GradeId int `json:"grade_id" label:"年级id"` + Class string `json:"class" label:"班级"` + ClassId int `json:"class_id" label:"班级id"` + } `json:"user_identities" label:"身份列表"` +} + +type EnterpriseUserListByCentralKitchenForSchoolResp struct { + List []EnterpriseUserListByCentralKitchenForSchoolStruct `json:"list"` + Total int64 `json:"total"` +} + +type CentralKitchenForSchoolUserUpdateReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Uid int `json:"uid" binding:"required" label:"用户id"` + Nickname string `json:"nickname" binding:"required" label:"支付宝昵称"` + Phone string `json:"phone" binding:"required" label:"手机号"` + BindUserIdentities []struct { + IdNo string `json:"id_no" label:"身份证号"` + Name string `json:"name" label:"姓名"` + GradeId int `json:"grade_id" label:"年级"` + ClassId int `json:"class_id" label:"班级"` + } `json:"user_identities" label:"身份列表"` +} + +type CentralKitchenForSchoolUserDeleteReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Uids []int `json:"uids" binding:"required" label:"用户id"` +} + +type CentralKitchenForSchoolStudentListReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Limit int `json:"limit" binding:"required"` + Page int `json:"page" binding:"required"` + Name string `json:"name" label:"姓名"` + IdNo string `json:"id_no" label:"身份证号"` + Phone string `json:"phone" label:"手机号"` + GradeId int `json:"grade_id" label:"年级id"` + ClassId int `json:"class_id" label:"班级id"` +} + +type CentralKitchenForSchoolTeacherListReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Limit int `json:"limit" binding:"required"` + Page int `json:"page" binding:"required"` + Name string `json:"name" label:"姓名"` + IdNo string `json:"id_no" label:"身份证号"` +} + +type CentralKitchenForSchoolStudentListResp struct { + IdNo string `json:"id_no" label:"身份证号"` + ParentPhone string `json:"parent_phone" label:"家长电话"` + Name string `json:"name" label:"姓名"` + Grade string `json:"grade" label:"年级"` + GradeId int `json:"grade_id" label:"年级id"` + Class string `json:"class" label:"班级"` + ClassId int `json:"class_id" label:"班级"` + UserIdentityId int `json:"user_identity_id" label:"用户身份id"` +} + +type CentralKitchenForSchoolTeacherListResp struct { + UserIdentityId int `json:"user_identity_id" label:"用户身份id"` + IdNo string `json:"id_no" label:"身份证号"` + Phone string `json:"parent_phone" label:"电话"` + Name string `json:"name" label:"姓名"` +} + +type CentralKitchenForSchoolTeacherUpdateReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + UserIdentityId int `json:"user_identity_id" binding:"required" label:"用户身份id"` + IdNo string `json:"id_no" label:"身份证号"` + Name string `json:"name" label:"姓名"` +} + +type CentralKitchenForSchoolStudentUpdateReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + UserIdentityId int `json:"user_identity_id" binding:"required" label:"用户身份id"` + IdNo string `json:"id_no" label:"身份证号"` + Name string `json:"name" label:"姓名"` + GradeId int `json:"grade_id" label:"年级id"` + ClassId int `json:"class_id" label:"班级id"` +} + +type CentralKitchenForSchoolStudentDeleteReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + UserIdentityIds []int `json:"user_identity_ids" binding:"required" label:"用户身份id"` +} + +type CentralKitchenForSchoolTeacherDeleteReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + UserIdentityIds []int `json:"user_identity_ids" binding:"required" label:"用户身份id"` +} + +type CentralKitchenForSchoolStudentAdmissionReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + ClassId int `json:"class_id" binding:"required" label:"班级id"` + GradeId int `json:"grade_id" binding:"required" label:"年级id"` +} + +type SetCentralKitchenForSchoolWithSpecReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + BreakfastUnitPrice string `json:"breakfast_unit_price" binding:"required" label:"早餐-单价"` + BreakfastUnitPriceForTeacher string `json:"breakfast_unit_price_for_teacher" binding:"required" label:"教师-早餐-单价"` + LunchUnitPrice string `json:"lunch_unit_price" binding:"required" label:"午餐-单价"` + LunchUnitPriceForTeacher string `json:"lunch_unit_price_for_teacher" binding:"required" label:"教师-午餐-单价"` + DinnerUnitPrice string `json:"dinner_unit_price" binding:"required" label:"晚餐-单价"` + DinnerUnitPriceForTeacher string `json:"dinner_unit_price_for_teacher" binding:"required" label:"教师-晚餐-单价"` +} + +type SaveCentralKitchenForSchoolPackageReq struct { + PackageId int `json:"package_id" label:"套餐ID"` + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Year string `json:"year" binding:"required" label:"年份"` + Month string `json:"month" binding:"required" label:"月份"` + StartDate string `json:"start_date" binding:"required" label:"开始时间"` + EndDate string `json:"end_date" binding:"required" label:"截止时间"` + State int `json:"state" label:"状态(1:可用 2:不可用)"` + DateList []struct { + Date string `json:"date"` + IsOpenBreakfast int32 `json:"is_open_breakfast"` + IsOpenLunch int32 `json:"is_open_lunch"` + IsOpenDinner int32 `json:"is_open_dinner"` + IsOpenReplenish int32 `json:"is_open_replenish"` + } `json:"date_list" binding:"required" label:"日期"` +} + +type SetBasicCentralKitchenForSchoolReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + State int32 `json:"state" label:"状态(1:正常 2:冻结)"` + Name string `json:"name" binding:"required" label:"名称"` + IsOpenBreakfast int `json:"is_open_breakfast" label:"是否开启早餐"` + IsOpenLunch int `json:"is_open_lunch" label:"是否开启午餐"` + IsOpenDinner int `json:"is_open_dinner" label:"是否开启晚餐"` + IsOpenReportMealForDay int `json:"is_open_report_meal_for_day" label:"开启按天报餐(1:开启 2:关闭)"` + IsOpenReportMealForMonth int `json:"is_open_report_meal_for_month" label:"开启按月报餐(1:开启 2:关闭)"` + IsOpenReportMealForSemester int `json:"is_open_report_meal_for_semester" label:"开启按学期报餐(1:开启 2:关闭)"` + IsOpenTeacherReportMeal int `json:"is_open_teacher_report_meal" label:"'教师报餐(1:开启 2:关闭)"` +} + +type ListCentralKitchenForSchoolPackageReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Page int `json:"page" label:"页码"` + Limit int `json:"limit" label:"每页数量"` + Year string `json:"year" label:"年份"` + Month string `json:"month" label:"月份"` +} + +type CentralKitchenForSchoolOrdRefundReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Ids []string `json:"ids" label:"ids" binding:"required"` +} + +type CentralKitchenForSchoolOrdListResp struct { + EnterpriseId int `json:"enterprise_id" ` + Uid int `json:"uid" ` + UserIdentityId int `json:"user_identity_id" ` + TotalPrice string `json:"total_price" ` + Kind int `json:"kind" ` + OutTradeNo string `json:"out_trade_no" ` + TradeNo string `json:"trade_no"` + State int `json:"state"` + OrdState int `json:"ord_state"` + CreateAt string `json:"create_at"` + Name string `json:"name" label:"姓名"` + Grade string `json:"grade" label:"年级"` + GradeId int `json:"grade_id" label:"年级id"` + Class string `json:"class" label:"班级"` + ClassId int `json:"class_id" label:"班级id"` +} + +type CentralKitchenForSchoolOrdListReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Limit int `json:"limit" binding:"required"` + Page int `json:"page" binding:"required"` + Name string `json:"name" label:"姓名"` + OutTradeNo string `json:"out_trade_no" label:"订单号"` + Kind int `json:"kind" label:"预定类型"` + Sate int `json:"state" label:"支付状态"` + OrdSate int `json:"ord_state" label:"订单状态"` + GradeId int `json:"grade_id" label:"年级id"` + ClassId int `json:"class_id" label:"班级id"` + StartDate string `json:"start_date" label:"开始时间"` + EndDate string `json:"end_date" label:"截止时间"` +} diff --git a/app/admin/md/md_login.go b/app/admin/md/md_login.go new file mode 100644 index 0000000..16b0897 --- /dev/null +++ b/app/admin/md/md_login.go @@ -0,0 +1,10 @@ +package md + +type LoginReq struct { + UserName string `json:"username" binding:"required" label:"登录账号"` + PassWord string `json:"password" binding:"required" label:"登录密码"` +} + +type LoginResponse struct { + Token string `json:"token"` +} diff --git a/app/admin/md/md_notice.go b/app/admin/md/md_notice.go new file mode 100644 index 0000000..8a13ae4 --- /dev/null +++ b/app/admin/md/md_notice.go @@ -0,0 +1,12 @@ +package md + +type NoticeAddReq struct { + Name string `json:"name" binding:"required" label:"名称"` + Content string `json:"content" label:"内容"` +} + +type NoticeUpdateReq struct { + Id int `json:"id" binding:"required" label:"公司id"` + Name string `json:"name" binding:"required" label:"名称"` + Content string `json:"content" label:"内容"` +} diff --git a/app/admin/md/md_qrcode.go b/app/admin/md/md_qrcode.go new file mode 100644 index 0000000..170893e --- /dev/null +++ b/app/admin/md/md_qrcode.go @@ -0,0 +1,31 @@ +package md + +const ( + QrcodeTotalNums = 100000 +) + +type QrcodeBatchListReq struct { + Page int `json:"page"` + Limit int `json:"limit"` +} + +type QrcodeBatchAddReq struct { + Name string `json:"name"` + ExpireDate string `json:"expire_date"` + List []QrcodeBatchAddReqList `json:"list"` + Memo string `json:"memo"` +} + +type QrcodeBatchAddReqList struct { + Num int `json:"num"` + Amount string `json:"amount"` +} + +type QrcodeBatchAddReqListDetail struct { + Num int `json:"num"` + WaitUseNum int `json:"wait_use_num"` + UsedNum int `json:"used_num"` + ExpiredNum int `json:"expired_num"` + CancelNum int `json:"cancel_num"` + Amount string `json:"amount"` +} diff --git a/app/admin/md/md_role.go b/app/admin/md/md_role.go new file mode 100644 index 0000000..a38fe81 --- /dev/null +++ b/app/admin/md/md_role.go @@ -0,0 +1,85 @@ +package md + +import ( + "applet/app/db/model" +) + +type RoleListResp struct { + Data model.Role `json:"data"` + AdminList []struct { + Name string `json:"name"` + } `json:"admin_list"` +} + +type UpdateRoleStateReq struct { + RoleId int `json:"role_id" binding:"required" label:"id"` + State int `json:"state" binding:"required" label:"状态"` +} + +type AddRoleReq struct { + Name string `json:"name" binding:"required" label:"名称"` + Memo string `json:"memo" binding:"required" label:"备注"` +} + +type UpdateRoleReq struct { + RoleId int `json:"role_id" binding:"required" label:"id"` + Name string `json:"name" binding:"required" label:"名称"` + Memo string `json:"memo" binding:"required" label:"备注"` +} + +type RoleBindPermissionGroupReq struct { + RoleId int `json:"role_id" binding:"required" label:"id"` + PermissionIds []int `json:"permission_ids" label:"权限组id"` +} + +type PermissionGroupListResp struct { + Id int `json:"id"` + Name string `json:"name"` + State int `json:"state"` + ParentId int `json:"parent_id"` + CreateAt string `json:"create_at"` + UpdateAt string `json:"update_at"` + IsCheck bool `json:"is_check"` + SubPermissionGroupList []PermissionGroupListResp `json:"sub_permission_group_list"` +} + +type AdminListReq struct { + Limit int `json:"limit"` + Page int `json:"page" ` + UserName string `json:"username"` + State int `json:"state"` +} + +type AdminListResp struct { + AdmId int `json:"adm_id"` + Username string `json:"username"` + State int32 `json:"state"` + IsSuperAdministrator int32 `json:"is_super_administrator"` + Memo string `json:"memo"` + CreateAt string `json:"create_at"` + UpdateAt string `json:"update_at"` + RoleList []string `json:"role_list"` +} + +type UpdateAdminStateReq struct { + AdmId int `json:"adm_id" binding:"required" label:"管理员id"` + State int32 `json:"state" binding:"required" label:"状态"` +} + +type AddAdminReq struct { + Username string `json:"username" binding:"required" label:"名称"` + Password string `json:"password" binding:"required" label:"密码"` + Memo string `json:"memo" label:"备注"` +} + +type UpdateAdminReq struct { + AdmId int `json:"adm_id" binding:"required" label:"管理员id"` + Username string `json:"username" binding:"required" label:"名称"` + Password string `json:"password" binding:"required" label:"密码"` + Memo string `json:"memo" label:"备注"` +} + +type BindAdminRoleReq struct { + AdmId int `json:"adm_id" binding:"required" label:"管理员id"` + RoleIds []int `json:"role_ids" label:"角色id"` +} diff --git a/app/admin/md/md_set_center.go b/app/admin/md/md_set_center.go new file mode 100644 index 0000000..7058ba2 --- /dev/null +++ b/app/admin/md/md_set_center.go @@ -0,0 +1,7 @@ +package md + +type SetCenterReq struct { + AdministratorContactInfo string `json:"administrator_contact_info" binding:"required" label:"管理员联系方式"` + CentralKitchenForSchoolReserveMealTime string `json:"central_kitchen_for_school_reserve_meal_time" binding:"required" label:"央厨预定用餐时间"` + CentralKitchenForSchoolCancelMealTime string `json:"central_kitchen_for_school_cancel_meal_time" binding:"required" label:"央厨取消用餐时间"` +} diff --git a/app/admin/md/md_sys_cfg.go b/app/admin/md/md_sys_cfg.go new file mode 100644 index 0000000..ff0ca45 --- /dev/null +++ b/app/admin/md/md_sys_cfg.go @@ -0,0 +1,9 @@ +package md + +type SetSysCfgReq struct { + WxMchApiV3Key string `json:"wx_mch_api_v3_key" label:"微信商户APIv3密钥"` + WxMchCertificateSerialNumber string `json:"wx_mch_certificate_serial_number" label:"微信商户证书序列号"` + WxMchId string `json:"wx_mch_id" label:"微信商户号"` + WxOfficialAccountAppId string `json:"wx_official_account_app_id" label:"微信公众号appId"` + WxOfficialAccountAppSecret string `json:"wx_official_account_app_secret" label:"微信公众号appSecret"` +} diff --git a/app/admin/md/md_user.go b/app/admin/md/md_user.go new file mode 100644 index 0000000..40b707a --- /dev/null +++ b/app/admin/md/md_user.go @@ -0,0 +1,30 @@ +package md + +type UserListReq struct { + Limit int `json:"limit" binding:"required"` + Page int `json:"page" binding:"required"` + Nickname string `json:"nickname" label:"支付宝昵称"` + Phone string `json:"phone" label:"手机号"` + CreateTimeStart string `json:"create_time_start" label:"创建时间-起始"` + CreateTimeEnd string `json:"create_time_end" label:"创建时间-截止"` +} + +type UserListResp struct { + List []UserList `json:"list"` + Total int64 `json:"total"` +} +type UserList struct { + Id int `json:"id" label:"id"` + Nickname string `json:"nickname" label:"支付宝昵称"` + Phone string `json:"phone" label:"联系电话"` + Avatar string `json:"avatar" label:"头像"` + CreateAt string `json:"create_at" label:"创建时间"` + BindUserIdentity []struct { + Id int `json:"id" label:"id"` + IdNo string `json:"id_no" label:"身份证号"` + Name string `json:"name" label:"姓名"` + EnterpriseName string `json:"enterprise_name" label:"企业名"` + EnterpriseKind int32 `json:"enterprise_kind" label:"企业类型"` + Kind int `json:"kind" label:"身份类型"` + } `json:"bind_user_identity" label:"绑定身份"` +} diff --git a/app/admin/md/md_wx_official_account.go b/app/admin/md/md_wx_official_account.go new file mode 100644 index 0000000..fdf8ecf --- /dev/null +++ b/app/admin/md/md_wx_official_account.go @@ -0,0 +1,14 @@ +package md + +const WxOfficialAccountRequestBaseUrl = "https://api.weixin.qq.com/" + +type CreateTokenResp struct { + AccessToken string `json:"access_token"` + ExpiresIn int64 `json:"expires_in"` +} + +type CreateQrcodeResp struct { + Ticket string `json:"ticket"` + ExpireSeconds int64 `json:"expire_seconds"` + Url string `json:"url"` +} diff --git a/app/admin/mw/mw_admin_auth.go b/app/admin/mw/mw_admin_auth.go new file mode 100644 index 0000000..f8a58be --- /dev/null +++ b/app/admin/mw/mw_admin_auth.go @@ -0,0 +1,26 @@ +package mw + +import ( + svc2 "applet/app/admin/svc" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +// Auth 检查签名 +func Auth(c *gin.Context) { + admin, err := svc2.CheckUser(c) + if err != nil { + switch err.(type) { + case e.E: + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + default: + e.OutErr(c, e.ERR_TOKEN_AUTH, err.Error()) + return + } + } + // 将当前请求的username信息保存到请求的上下文c上 + c.Set("admin", admin) + c.Next() +} diff --git a/app/admin/mw/mw_admin_permission.go b/app/admin/mw/mw_admin_permission.go new file mode 100644 index 0000000..32a9bfb --- /dev/null +++ b/app/admin/mw/mw_admin_permission.go @@ -0,0 +1,26 @@ +package mw + +import ( + "applet/app/admin/md" + "applet/app/admin/svc" + "applet/app/e" + "applet/app/utils" + "fmt" + "github.com/gin-gonic/gin" +) + +// CheckPermission 检查权限 +func CheckPermission(c *gin.Context) { + admin := svc.GetUser(c) + rolePermissionKey := fmt.Sprintf(md.AdminRolePermissionKey, utils.GetIP(c.Request), utils.AnyToString(admin.AdmId)) + isHasPermission, err := svc.CheckUserRole(rolePermissionKey, c.Request.RequestURI, admin.AdmId) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + if !isHasPermission { + e.OutErr(c, e.ERR_FORBIDEN, "当前用户暂未拥有该路由权限,请联系管理员") + return + } + c.Next() +} diff --git a/app/admin/mw/mw_cors.go b/app/admin/mw/mw_cors.go new file mode 100644 index 0000000..3433553 --- /dev/null +++ b/app/admin/mw/mw_cors.go @@ -0,0 +1,29 @@ +package mw + +import ( + "github.com/gin-gonic/gin" +) + +// cors跨域 +func Cors(c *gin.Context) { + // 放行所有OPTIONS方法 + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + origin := c.Request.Header.Get("Origin") // 请求头部 + if origin != "" { + c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问来源域 + c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求 + // header的类型 + c.Header("Access-Control-Allow-Headers", "Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma,X-Mx-ReqToken") + // 允许跨域设置,可以返回其他子段 + // 跨域关键设置 让浏览器可以解析 + c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 + c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true + c.Set("Content-Type", "Application/json") // 设置返回格式是json + } + c.Next() +} diff --git a/app/admin/mw/mw_limiter.go b/app/admin/mw/mw_limiter.go new file mode 100644 index 0000000..4eb5299 --- /dev/null +++ b/app/admin/mw/mw_limiter.go @@ -0,0 +1,58 @@ +package mw + +import ( + "bytes" + "io/ioutil" + + "github.com/gin-gonic/gin" + + "applet/app/utils" + "applet/app/utils/cache" +) + +// 限流器 +func Limiter(c *gin.Context) { + limit := 100 // 限流次数 + ttl := 1 // 限流过期时间 + ip := c.ClientIP() + // 读取token或者ip + token := c.GetHeader("Authorization") + // 判断是否已经超出限额次数 + method := c.Request.Method + host := c.Request.Host + uri := c.Request.URL.String() + + buf := make([]byte, 2048) + num, _ := c.Request.Body.Read(buf) + body := buf[:num] + // Write body back + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + Md5 := utils.Md5(ip + token + method + host + uri + string(body)) + if cache.Exists(Md5) { + c.AbortWithStatusJSON(429, gin.H{ + "code": 429, + "msg": "don't repeat the request", + "data": struct{}{}, + }) + return + } + // 2s后没返回自动释放 + go cache.SetEx(Md5, "0", ttl) + key := "LIMITER_" + ip + reqs, _ := cache.GetInt(key) + if reqs >= limit { + c.AbortWithStatusJSON(429, gin.H{ + "code": 429, + "msg": "too many requests", + "data": struct{}{}, + }) + return + } + if reqs > 0 { + go cache.Incr(key) + } else { + go cache.SetEx(key, 1, ttl) + } + c.Next() + go cache.Del(Md5) +} diff --git a/app/admin/mw/mw_recovery.go b/app/admin/mw/mw_recovery.go new file mode 100644 index 0000000..b32cc82 --- /dev/null +++ b/app/admin/mw/mw_recovery.go @@ -0,0 +1,57 @@ +package mw + +import ( + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func Recovery(logger *zap.Logger, stack bool) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + if brokenPipe { + logger.Error(c.Request.URL.Path, + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + // If the connection is dead, we can't write a status to it. + c.Error(err.(error)) + c.Abort() + return + } + + if stack { + logger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + zap.String("stack", string(debug.Stack())), + ) + } else { + logger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + c.AbortWithStatus(http.StatusInternalServerError) + } + }() + c.Next() + } +} diff --git a/app/admin/svc/enterprise_manage/svc_central_kitchen_for_school.go b/app/admin/svc/enterprise_manage/svc_central_kitchen_for_school.go new file mode 100644 index 0000000..e694b5c --- /dev/null +++ b/app/admin/svc/enterprise_manage/svc_central_kitchen_for_school.go @@ -0,0 +1,531 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + enum2 "applet/app/enum" + "errors" + "fmt" + "strings" + "time" +) + +func CentralKitchenForSchoolUserUpdate(req md.CentralKitchenForSchoolUserUpdateReq) (err error) { + //1、删除当前用户&&当前单位下的所有身份对应的 `class_with_user` 记录 + var userIdentities []model.UserIdentity + err = db.Db.Where("uid =? AND enterprise_id =?", req.Uid, req.EnterpriseId).Find(&userIdentities) + if err != nil { + return + } + var userIdentityIds []int + for _, v := range userIdentities { + userIdentityIds = append(userIdentityIds, v.Id) + } + _, err = db.Db.In("user_identity_id", userIdentityIds).Delete(model.ClassWithUser{}) + if err != nil { + return + } + + session := db.Db.NewSession() + defer session.Close() + session.Begin() + //2、删除当前用户&&当前单位下的所有身份 + _, err = session.Where("uid =? and enterprise_id =?", req.Uid, req.EnterpriseId).Delete(model.UserIdentity{}) + if err != nil { + _ = session.Rollback() + return + } + + //3、新增数据 + now := time.Now() + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(req.Uid) + + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + for _, v := range req.BindUserIdentities { + if v.ClassId == -1 { + //TODO::为老师身份 + _, err3 := userIdentityDb.UserIdentityInsertBySession(session, &model.UserIdentity{ + Uid: req.Uid, + Name: v.Name, + IdNo: v.IdNo, + Kind: enum2.UserIdentityKindForCommon, + Identity: enum2.UserIdentityForCentralKitchenForTeacher, + EnterpriseId: req.EnterpriseId, + Memo: "", + CreateAt: now, + UpdateAt: now, + }) + if err3 != nil { + _ = session.Rollback() + return err3 + } + continue + } + + insertId, err1 := userIdentityDb.UserIdentityInsertBySession(session, &model.UserIdentity{ + Uid: req.Uid, + Name: v.Name, + IdNo: v.IdNo, + Kind: enum2.UserIdentityKindForCommon, + Identity: enum2.UserIdentityForCentralKitchenForStudent, + EnterpriseId: req.EnterpriseId, + Memo: "", + CreateAt: now, + UpdateAt: now, + }) + if err1 != nil { + _ = session.Rollback() + return err1 + } + + _, err2 := classWithUserDb.ClassWithUserInsertBySession(session, &model.ClassWithUser{ + UserIdentityId: insertId, + ClassId: v.ClassId, + CreateAt: now, + UpdateAt: now, + }) + if err2 != nil { + _ = session.Rollback() + return err1 + } + } + + return session.Commit() +} + +func CentralKitchenForSchoolUserDelete(req md.CentralKitchenForSchoolUserDeleteReq) (err error) { + //1、删除 class_with_user + var userIdentities []model.UserIdentity + err = db.Db.Where("enterprise_id =?", req.EnterpriseId).In("uid", req.Uids).Find(&userIdentities) + if err != nil { + return + } + var userIdentityIds []int + for _, v := range userIdentities { + userIdentityIds = append(userIdentityIds, v.Id) + } + _, err = db.Db.In("user_identity_id", userIdentityIds).Delete(model.ClassWithUser{}) + if err != nil { + return + } + + //2、删除 user_identity + _, err = db.Db.Where("enterprise_id =?", req.EnterpriseId).In("uid", req.Uids).Delete(model.UserIdentity{}) + if err != nil { + return + } + return +} + +func CentralKitchenForSchoolStudentList(req md.CentralKitchenForSchoolStudentListReq) (resp []md.CentralKitchenForSchoolStudentListResp, count int64, err error) { + var classWithUserIdentityIdsOne []int + var classWithUserIdentityIdsTwo []int + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + if req.ClassId != 0 { + classWithUsers, err2 := classWithUserDb.FindUserIdentity(req.ClassId) + if err2 != nil { + return nil, 0, err2 + } + for _, v := range *classWithUsers { + classWithUserIdentityIdsOne = append(classWithUserIdentityIdsOne, v.UserIdentityId) + } + } + if req.GradeId != 0 { + classDb := db.ClassDb{} + classDb.Set(req.GradeId) + classes, err3 := classDb.FindClass() + if err3 != nil { + return nil, 0, err3 + } + var classesId []int + for _, v := range *classes { + classesId = append(classesId, v.Id) + } + classWithUsers, err4 := classWithUserDb.FindUserIdentity(classesId) + if err4 != nil { + return nil, 0, err4 + } + for _, v := range *classWithUsers { + classWithUserIdentityIdsTwo = append(classWithUserIdentityIdsTwo, v.UserIdentityId) + } + } + sess := db.Db.Where("user_identity.enterprise_id =?", req.EnterpriseId).And("identity =?", enum2.UserIdentityForCentralKitchenForStudent) + if req.Name != "" { + sess.And("user_identity.name like ?", "%"+req.Name+"%") + } + if req.IdNo != "" { + sess.And("user_identity.id_no like ?", "%"+req.IdNo+"%") + } + if len(classWithUserIdentityIdsOne) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsOne) + } + if len(classWithUserIdentityIdsTwo) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsTwo) + } + + var m []*db.UserIdentityWithUser + count, err = sess. + Join("LEFT", "user", "user_identity.uid = user.id"). + Join("LEFT", "class_with_user", "class_with_user.user_identity_id = user_identity.id"). + Join("LEFT", "class", "class_with_user.class_id = class.id"). + Join("LEFT", "grade", "class.grade_id = grade.id"). + Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return nil, 0, err + } + + for _, v := range m { + resp = append(resp, md.CentralKitchenForSchoolStudentListResp{ + IdNo: v.UserIdentity.IdNo, + ParentPhone: v.User.Phone, + Name: v.UserIdentity.Name, + Grade: v.Grade.Name, + GradeId: v.Grade.Id, + Class: v.Class.Name, + ClassId: v.Class.Id, + UserIdentityId: v.UserIdentity.Id, + }) + } + return +} + +func CentralKitchenForSchoolTeacherList(req md.CentralKitchenForSchoolTeacherListReq) (resp []md.CentralKitchenForSchoolTeacherListResp, count int64, err error) { + var classWithUserIdentityIdsOne []int + var classWithUserIdentityIdsTwo []int + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + + sess := db.Db.Where("user_identity.enterprise_id =?", req.EnterpriseId).And("user_identity.identity =?", enum2.UserIdentityForCentralKitchenForTeacher) + if req.Name != "" { + sess.And("user_identity.name like ?", "%"+req.Name+"%") + } + if req.IdNo != "" { + sess.And("user_identity.id_no like ?", "%"+req.IdNo+"%") + } + if len(classWithUserIdentityIdsOne) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsOne) + } + if len(classWithUserIdentityIdsTwo) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsTwo) + } + + var m []*db.UserIdentityWithUser + count, err = sess. + Join("LEFT", "user", "user_identity.uid = user.id"). + Join("LEFT", "class_with_user", "class_with_user.user_identity_id = user_identity.id"). + Join("LEFT", "class", "class_with_user.class_id = class.id"). + Join("LEFT", "grade", "class.grade_id = grade.id"). + Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return nil, 0, err + } + + for _, v := range m { + resp = append(resp, md.CentralKitchenForSchoolTeacherListResp{ + UserIdentityId: v.UserIdentity.Id, + IdNo: v.UserIdentity.IdNo, + Phone: v.User.Phone, + Name: v.UserIdentity.Name, + }) + } + return +} + +func CentralKitchenForSchoolStudentUpdate(req md.CentralKitchenForSchoolStudentUpdateReq) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + + //1、修改 `user_identity` + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + userIdentity, err := userIdentityDb.GetUserIdentity(req.UserIdentityId) + if err != nil { + return + } + if userIdentity == nil { + return errors.New("未查询到对应记录") + } + userIdentity.IdNo = req.IdNo + userIdentity.Name = req.Name + _, err = userIdentityDb.UserIdentityUpdate(req.UserIdentityId, userIdentity, "id_no", "name") + if err != nil { + return + } + + //2、更新 `class_with_user` + _, err = session.Where("user_identity_id =?", req.UserIdentityId).Delete(model.ClassWithUser{}) + if err != nil { + _ = session.Rollback() + return + } + + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + _, err = classWithUserDb.ClassWithUserInsertBySession(session, &model.ClassWithUser{ + UserIdentityId: req.UserIdentityId, + ClassId: req.ClassId, + CreateAt: time.Time{}, + UpdateAt: time.Time{}, + }) + + return session.Commit() +} + +func CentralKitchenForSchoolStudentDelete(req md.CentralKitchenForSchoolStudentDeleteReq) (err error) { + //1、删除 class_with_user + _, err = db.Db.In("user_identity_id", req.UserIdentityIds).Delete(model.ClassWithUser{}) + if err != nil { + return + } + + //2、删除 user_identity + _, err = db.Db.Where("enterprise_id =?", req.EnterpriseId).In("id", req.UserIdentityIds).Delete(model.UserIdentity{}) + if err != nil { + return + } + return +} + +func CentralKitchenForSchoolStudentAdmission(req md.CentralKitchenForSchoolStudentAdmissionReq) (err error) { + //1、判断 "按年级" / "按班级" 升级 + if req.ClassId != 0 { + // 删除 class_with_user 记录 + _, err = db.Db.Where("class_id = ?", req.ClassId).Delete(model.ClassWithUser{}) + if err != nil { + return + } + } + if req.GradeId != 0 { + // 查找班级记录 + classDb := db.ClassDb{} + classDb.Set(req.GradeId) + classes, err1 := classDb.FindClass() + if err1 != nil { + return err1 + } + var classesIds []int + for _, v := range *classes { + classesIds = append(classesIds, v.Id) + } + + // 删除 class_with_user 记录 + _, err = db.Db.In("class_id", classesIds).Delete(model.ClassWithUser{}) + if err != nil { + return + } + } + + return +} + +func CentralKitchenForSchoolOrdList(req md.CentralKitchenForSchoolOrdListReq) (resp []md.CentralKitchenForSchoolOrdListResp, count int64, err error) { + var classWithUserIdentityIdsOne []int + var classWithUserIdentityIdsTwo []int + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + if req.ClassId != 0 { + classWithUsers, err2 := classWithUserDb.FindUserIdentity(req.ClassId) + if err2 != nil { + return nil, 0, err2 + } + for _, v := range *classWithUsers { + classWithUserIdentityIdsOne = append(classWithUserIdentityIdsOne, v.UserIdentityId) + } + } + if req.GradeId != 0 { + classDb := db.ClassDb{} + classDb.Set(req.GradeId) + classes, err3 := classDb.FindClass() + if err3 != nil { + return nil, 0, err3 + } + var classesId []int + for _, v := range *classes { + classesId = append(classesId, v.Id) + } + classWithUsers, err4 := classWithUserDb.FindUserIdentity(classesId) + if err4 != nil { + return nil, 0, err4 + } + for _, v := range *classWithUsers { + classWithUserIdentityIdsTwo = append(classWithUserIdentityIdsTwo, v.UserIdentityId) + } + } + sess := db.Db.Where("central_kitchen_for_school_package_ord.enterprise_id =?", req.EnterpriseId) + if req.StartDate != "" { + sess.And("central_kitchen_for_school_package_ord.create_at >= ?", req.StartDate) + } + if req.EndDate != "" { + sess.And("central_kitchen_for_school_package_ord.create_at <= ?", req.EndDate) + } + if req.Kind != 0 { + sess.And("central_kitchen_for_school_package_ord.kind = ", req.Kind) + } + if req.Sate != 0 { + sess.And("central_kitchen_for_school_package_ord.state = ", req.Sate) + } + if req.OrdSate != 0 { + sess.And("central_kitchen_for_school_package_ord.ord_state = ", req.OrdSate) + } + if req.OutTradeNo != "" { + sess.And("central_kitchen_for_school_package_ord.out_trade_no like ?", "%"+req.OutTradeNo+"%") + } + + if req.Name != "" { + sess.And("user_identity.name like ?", "%"+req.Name+"%") + } + if len(classWithUserIdentityIdsOne) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsOne) + } + if len(classWithUserIdentityIdsTwo) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsTwo) + } + + var m []*db.CentralKitchenForSchoolPackageOrdWithUserIdentity + count, err = sess. + Join("LEFT", "user_identity", "central_kitchen_for_school_package_ord.user_identity_id = user_identity.id"). + Join("LEFT", "class_with_user", "class_with_user.user_identity_id = user_identity.id"). + Join("LEFT", "class", "class_with_user.class_id = class.id"). + Join("LEFT", "grade", "class.grade_id = grade.id"). + Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return nil, 0, err + } + + for _, v := range m { + resp = append(resp, md.CentralKitchenForSchoolOrdListResp{ + EnterpriseId: req.EnterpriseId, + Uid: v.UserIdentity.Uid, + UserIdentityId: v.UserIdentity.Id, + TotalPrice: v.CentralKitchenForSchoolPackageOrd.TotalPrice, + Kind: v.CentralKitchenForSchoolPackageOrd.Kind, + OutTradeNo: v.CentralKitchenForSchoolPackageOrd.OutTradeNo, + TradeNo: v.CentralKitchenForSchoolPackageOrd.TradeNo, + State: v.CentralKitchenForSchoolPackageOrd.State, + OrdState: v.CentralKitchenForSchoolPackageOrd.OrdState, + CreateAt: v.CentralKitchenForSchoolPackageOrd.CreateAt, + Name: v.UserIdentity.Name, + Grade: v.Grade.Name, + GradeId: v.Grade.Id, + Class: v.Class.Name, + ClassId: v.Class.Id, + }) + } + return +} + +func CentralKitchenForSchoolOrdRefund(req md.CentralKitchenForSchoolOrdRefundReq) (err error) { + //1、查询出所有 `central_kitchen_for_school_user_with_day` 记录 + var m []model.CentralKitchenForSchoolUserWithDay + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + err = centralKitchenForSchoolUserWithDayDb.Db.In("id", req.Ids).Find(&m) + if err != nil { + return err + } + + //2、更改 `central_kitchen_for_school_user_with_day` 的 state 为 退款中 + sql := "update central_kitchen_for_school_user_with_day set status = %s where id In (%s)" + idsStr := strings.Join(req.Ids, ",") + sql = fmt.Sprintf(sql, enum2.CentralKitchenForSchoolUserWithDayStateForCanceling, idsStr) + fmt.Println(sql) + _, err = db.ExecuteOriginalSql(db.Db, sql) + if err != nil { + return err + } + + //3、循环处理数据 + var dealOutTradeNo map[string]string + var centralKitchenForSchoolUserRefundDays []*model.CentralKitchenForSchoolUserRefundDay + now := time.Now() + for _, v := range m { + dealOutTradeNo[v.OrdNo] = v.OrdNo + centralKitchenForSchoolUserRefundDays = append(centralKitchenForSchoolUserRefundDays, &model.CentralKitchenForSchoolUserRefundDay{ + Uid: v.Uid, + IdentityId: v.IdentityId, + RecordsId: v.Id, + State: enum2.CentralKitchenForSchoolUserRefundDayStateForAuditing, + Amount: v.Amount, + Memo: "", + CreateAt: now, + UpdateAt: now, + }) + } + + //4、处理 `central_kitchen_for_school_package_ord` 的 订单状态(ord_state) + for _, v := range dealOutTradeNo { + err1 := JudgePackageOrdOrdState(v) + if err1 != nil { + return err1 + } + } + + //5、新增 `central_kitchen_for_school_user_refund_day` 数据 + centralKitchenForSchoolUserRefundDayDb := db.CentralKitchenForSchoolUserRefundDayDb{} + centralKitchenForSchoolUserRefundDayDb.Set(0) + _, err = centralKitchenForSchoolUserRefundDayDb.BatchAddCentralKitchenForSchoolUserRefundDays(centralKitchenForSchoolUserRefundDays) + return +} + +// JudgePackageOrdOrdState 判断订单状态 +func JudgePackageOrdOrdState(outTradeNo string) (err error) { + centralKitchenForSchoolPackageOrd := db.CentralKitchenForSchoolPackageOrd{} + centralKitchenForSchoolPackageOrd.Set(outTradeNo) + ord, err := centralKitchenForSchoolPackageOrd.GetCentralKitchenForSchoolPackageOrd() + if err != nil { + return + } + var ordState, oldOrdState int + oldOrdState = ord.OrdState + ordState = oldOrdState + + var m model.CentralKitchenForSchoolUserWithDay + total, err := db.Db.Where("ord_no =?", outTradeNo).Count(&m) + if err != nil { + return + } + + //1、判断是否有 `待就餐` + count, err := db.Db.Where("ord_no =?", outTradeNo).And("state =?", enum2.CentralKitchenForSchoolUserWithDayStateForWait).Count(&m) + if err != nil { + return + } + if count == 0 { + ordState = enum2.CentralKitchenForSchoolPackageOrdOrdStateForComplete + } + + //2、判断是否有 `已退款` / `部分退款` + count, err = db.Db.Where("ord_no =?", outTradeNo).And("state =?", enum2.CentralKitchenForSchoolUserWithDayStateForCancel).Count(&m) + if err != nil { + return + } + if count > 0 && count < total { + ordState = enum2.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded + } + if count > 0 && count != total { + ordState = enum2.CentralKitchenForSchoolPackageOrdOrdStateForRefunded + } + + //3、判断是否有 `退款中` + count, err = db.Db.Where("ord_no =?", outTradeNo).And("state =?", enum2.CentralKitchenForSchoolUserWithDayStateForCanceling).Count(&m) + if err != nil { + return + } + if count > 0 { + ordState = enum2.CentralKitchenForSchoolPackageOrdOrdStateForRefunding + } + + if ordState != oldOrdState { + ord.OrdState = ordState + _, err2 := centralKitchenForSchoolPackageOrd.CentralKitchenForSchoolPackageOrdUpdate(ord, "ord_state") + if err2 != nil { + return err2 + } + } + return +} diff --git a/app/admin/svc/enterprise_manage/svc_enterprise_manage.go b/app/admin/svc/enterprise_manage/svc_enterprise_manage.go new file mode 100644 index 0000000..002a15c --- /dev/null +++ b/app/admin/svc/enterprise_manage/svc_enterprise_manage.go @@ -0,0 +1,102 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + enum2 "applet/app/enum" + "applet/app/utils" + "fmt" +) + +func EnterpriseUserListByCentralKitchenForSchool(req md.EnterpriseUserListReq) (resp md.EnterpriseUserListByCentralKitchenForSchoolResp, err error) { + //1、判断是否过滤 "教师" + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + teacherUserIdentities, err := userIdentityDb.FindUserIdentityForEnterpriseByIdentity(req.EnterpriseId, enum2.UserIdentityForCentralKitchenForTeacher) + if err != nil { + return + } + userIdentities, err := userIdentityDb.FindUserIdentityForEnterprise(req.EnterpriseId) + if err != nil { + return + } + var teacherUserIdentitiesMap = map[string][]model.UserIdentity{} + var userIdentitiesMap = map[string][]model.UserIdentity{} + var uids, teacherUids []int + for _, v := range *teacherUserIdentities { + teacherUserIdentitiesMap[utils.IntToStr(v.Uid)] = append(teacherUserIdentitiesMap[utils.IntToStr(v.Uid)], v) + teacherUids = append(teacherUids, v.Uid) + } + for _, v := range *userIdentities { + userIdentitiesMap[utils.IntToStr(v.Uid)] = append(userIdentitiesMap[utils.IntToStr(v.Uid)], v) + uids = append(uids, v.Uid) + } + + var m []model.User + sess := db.Db.In("id", uids) + if req.IsTeacher == 1 { + sess.In("id", teacherUids) + } + if req.IsTeacher == 2 { + sess.NotIn("id", teacherUids) + } + if req.Nickname != "" { + sess.And("nickname like ?", "%"+req.Nickname+"%") + } + if req.Phone != "" { + sess.And("phone like ?", "%"+req.Phone+"%") + } + count, err := sess.Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return + } + resp.Total = count + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + for _, v := range m { + temp := md.EnterpriseUserListByCentralKitchenForSchoolStruct{ + Id: v.Id, + Nickname: v.Nickname, + Phone: v.Phone, + Avatar: v.Avatar, + IsTeacher: 0, + CreateAt: v.CreateAt.Format("2006-01-02 15:04:05"), + } + for _, v1 := range userIdentitiesMap[utils.IntToStr(v.Id)] { + fmt.Println(">>>>>>>>>>>>", userIdentitiesMap[utils.IntToStr(v.Id)]) + //TODO::判断是否为老师 + if v1.Identity == enum2.UserIdentityForCentralKitchenForTeacher { + temp.UserIdentities = append(temp.UserIdentities, struct { + IdNo string `json:"id_no" label:"身份证号"` + SchoolName string `json:"school_name" label:"学校名"` + Name string `json:"name" label:"姓名"` + Grade string `json:"grade" label:"年级"` + GradeId int `json:"grade_id" label:"年级id"` + Class string `json:"class" label:"班级"` + ClassId int `json:"class_id" label:"班级id"` + }{IdNo: v1.IdNo, SchoolName: "教师", Name: v1.Name, Grade: "教师", Class: "教师", GradeId: 0, ClassId: 0}) + temp.IsTeacher = 1 + } else { + data, err2 := classWithUserDb.GetInfoByUserIdentityId(v1.Id) + if err2 != nil { + return resp, err2 + } + if data == nil { + continue + } + temp.UserIdentities = append(temp.UserIdentities, struct { + IdNo string `json:"id_no" label:"身份证号"` + SchoolName string `json:"school_name" label:"学校名"` + Name string `json:"name" label:"姓名"` + Grade string `json:"grade" label:"年级"` + GradeId int `json:"grade_id" label:"年级id"` + Class string `json:"class" label:"班级"` + ClassId int `json:"class_id" label:"班级id"` + }{IdNo: data.UserIdentity.IdNo, SchoolName: data.Enterprise.Name, Name: data.UserIdentity.Name, Grade: data.Grade.Name, Class: data.Class.Name, GradeId: data.Grade.Id, ClassId: data.Class.Id}) + } + } + resp.List = append(resp.List, temp) + } + return +} diff --git a/app/admin/svc/order/svc_central_kitchen_for_school_order.go b/app/admin/svc/order/svc_central_kitchen_for_school_order.go new file mode 100644 index 0000000..98796da --- /dev/null +++ b/app/admin/svc/order/svc_central_kitchen_for_school_order.go @@ -0,0 +1,210 @@ +package svc + +import ( + "applet/app/admin/md" + svc "applet/app/admin/svc/enterprise_manage" + md2 "applet/app/customer/md" + svc2 "applet/app/customer/svc" + "applet/app/db" + "applet/app/enum" + "applet/app/utils" + "errors" +) + +func CentralKitchenForSchoolOrderRefundList(req md.CentralKitchenForSchoolOrderRefundListReq) (resp []md.CentralKitchenForSchoolOrderRefundListResp, total int64, err error) { + var m []*db.CentralKitchenForSchoolUserRefundDayWithData + sess := db.Db.Where("1=1") + if req.EnterpriseId != 0 { + sess.And("enterprise.id =?", req.EnterpriseId) + } + if req.Phone != "" { + sess.And("user.phone like ?", "%"+req.Name+"%") + } + if req.Name != "" { + sess.And("user_identity.name like ?", "%"+req.Phone+"%") + } + if req.Kind != 0 { + sess.And("central_kitchen_for_school_user_with_day.kind = ?", req.Kind) + } + if req.State != 0 { + sess.And("central_kitchen_for_school_user_refund_day.state = ?", req.State) + } + if req.StartDate != "" { + sess.And("central_kitchen_for_school_user_refund_day.create_at >= ?", req.StartDate) + } + if req.EndDate != "" { + sess.And("central_kitchen_for_school_user_refund_day.create_at <= ?", req.EndDate) + } + if req.OutTradeNo != "" { + sess.And("central_kitchen_for_school_user_refund_day.out_trade_no like ?", "%"+req.OutTradeNo+"%") + } + if req.OutRequestNo != "" { + sess.And("central_kitchen_for_school_user_refund_day.out_request_no like ?", "%"+req.OutRequestNo+"%") + } + + var classWithUserIdentityIdsOne []int + var classWithUserIdentityIdsTwo []int + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + if req.ClassId != 0 { + classWithUsers, err2 := classWithUserDb.FindUserIdentity(req.ClassId) + if err2 != nil { + return nil, 0, err2 + } + for _, v := range *classWithUsers { + classWithUserIdentityIdsOne = append(classWithUserIdentityIdsOne, v.UserIdentityId) + } + } + if req.GradeId != 0 { + classDb := db.ClassDb{} + classDb.Set(req.GradeId) + classes, err3 := classDb.FindClass() + if err3 != nil { + return nil, 0, err3 + } + var classesId []int + for _, v := range *classes { + classesId = append(classesId, v.Id) + } + classWithUsers, err4 := classWithUserDb.FindUserIdentity(classesId) + if err4 != nil { + return nil, 0, err4 + } + for _, v := range *classWithUsers { + classWithUserIdentityIdsTwo = append(classWithUserIdentityIdsTwo, v.UserIdentityId) + } + } + if len(classWithUserIdentityIdsOne) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsOne) + } + if len(classWithUserIdentityIdsTwo) > 0 { + sess.In("user_identity.id", classWithUserIdentityIdsTwo) + } + + total, err = sess. + Join("LEFT", "central_kitchen_for_school_user_with_day", "central_kitchen_for_school_user_refund_day.records_id = central_kitchen_for_school_user_with_day.id"). + Join("LEFT", "central_kitchen_for_school_package_ord", "central_kitchen_for_school_user_refund_day.out_trade_no = central_kitchen_for_school_package_ord.out_trade_no"). + Join("LEFT", "user_identity", "central_kitchen_for_school_user_with_day.identity_id = user_identity.id"). + Join("LEFT", "user", "user_identity.uid = user.id"). + Join("LEFT", "enterprise", "user_identity.enterprise_id = enterprise.id"). + Join("LEFT", "class_with_user", "user_identity.id = class_with_user.user_identity_id"). + Join("LEFT", "class", "class_with_user.class_id = class.id"). + Join("LEFT", "grade", "class.grade_id = grade.id"). + Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return nil, 0, err + } + + for _, v := range m { + resp = append(resp, md.CentralKitchenForSchoolOrderRefundListResp{ + Id: v.CentralKitchenForSchoolUserRefundDay.Id, + OutTradeNo: v.CentralKitchenForSchoolUserRefundDay.OutTradeNo, + OutRequestNo: v.CentralKitchenForSchoolUserRefundDay.OutRequestNo, + Name: v.UserIdentity.Name, + Phone: v.User.Phone, + EnterpriseName: v.Enterprise.Name, + ClassName: v.Class.Name, + GradeName: v.Grade.Name, + Kind: v.CentralKitchenForSchoolUserWithDay.Kind, + Amount: v.CentralKitchenForSchoolUserRefundDay.Amount, + State: v.CentralKitchenForSchoolUserRefundDay.State, + CreateAt: v.CentralKitchenForSchoolUserRefundDay.CreateAt.Format("2006-01-02 15:04:05"), + Memo: v.CentralKitchenForSchoolUserRefundDay.Memo, + }) + } + return +} + +func CentralKitchenForSchoolOrderRefundAudit(req md.CentralKitchenForSchoolOrderRefundAuditReq) (err error) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + sysCfg, err := sysCfgDb.SysCfgGetOne(enum.JsapiPayAppAutToken) + if err != nil { + return + } + + session := db.Db.NewSession() + defer session.Close() + session.Begin() + + //限制30条 + if len(req.Ids) > 30 { + err = errors.New("受理数据过长,请分批次处理") + _ = session.Rollback() + return + } + centralKitchenForSchoolUserRefundDayDb := db.CentralKitchenForSchoolUserRefundDayDb{} + centralKitchenForSchoolUserRefundDayDb.Set(0) + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + for _, v := range req.Ids { + returnDay, err1 := centralKitchenForSchoolUserRefundDayDb.GetCentralKitchenForSchoolUserRefundDay(utils.StrToInt(v)) + if err1 != nil { + _ = session.Rollback() + return err1 + } + if returnDay.State != enum.CentralKitchenForSchoolUserRefundDayStateForAuditing { + _ = session.Rollback() + return errors.New("请勿重复审核申请单!") + } + userWithDay, err3 := centralKitchenForSchoolUserWithDayDb.GetCentralKitchenForSchoolUserWithDay(returnDay.RecordsId) + if err3 != nil { + _ = session.Rollback() + return err3 + } + + //1、更新状态 + if req.State == 1 { + returnDay.State = enum.CentralKitchenForSchoolUserRefundDayStateForAuditPass + userWithDay.State = enum.CentralKitchenForSchoolUserWithDayStateForCancel + } else { + returnDay.State = enum.CentralKitchenForSchoolUserRefundDayStateForAuditReject + userWithDay.State = enum.CentralKitchenForSchoolUserWithDayStateForWait + } + returnDay.Memo = req.Memo + updateAck, err2 := centralKitchenForSchoolUserRefundDayDb.CentralKitchenForSchoolUserRefundDayUpdate(returnDay.Id, returnDay, "state", "memo") + if err2 != nil { + _ = session.Rollback() + return err2 + } + if updateAck <= 0 { + _ = session.Rollback() + err = errors.New("更新退款订单记录状态失败") + return + } + updateAck1, err4 := centralKitchenForSchoolUserWithDayDb.CentralKitchenForSchoolUserWithDayUpdate(userWithDay.Id, userWithDay, "state") + if err4 != nil { + _ = session.Rollback() + return err4 + } + if updateAck1 <= 0 { + _ = session.Rollback() + err = errors.New("更新退款就餐记录状态失败") + return + } + + //2、处理订单状态 + err5 := svc.JudgePackageOrdOrdState(returnDay.OutTradeNo) + if err5 != nil { + _ = session.Rollback() + return err5 + } + + //3、调用支付宝进行退款 + if req.State == 1 { + err6, _ := svc2.CurlAlipayTradeRefund(md2.CurlAlipayTradeRefundReq{ + OutTradeNo: returnDay.OutTradeNo, + RefundAmount: returnDay.Amount, + RefundReason: "央厨订餐退款", + OutRequestNo: returnDay.OutRequestNo, + AppAuthToken: sysCfg.Val, + }) + if err6 != nil { + _ = session.Rollback() + return err6 + } + } + } + + return session.Commit() +} diff --git a/app/admin/svc/svc_admin.go b/app/admin/svc/svc_admin.go new file mode 100644 index 0000000..d0bb67f --- /dev/null +++ b/app/admin/svc/svc_admin.go @@ -0,0 +1,30 @@ +package svc + +import ( + "applet/app/db" +) + +func AdminDelete(admIds []int) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + //1、删除 `admin` + adminDb := db.AdminDb{} + adminDb.Set() + _, err = adminDb.AdminDeleteBySession(session, admIds) + if err != nil { + _ = session.Rollback() + return + } + + //2、删除 `admin_role` + adminRoleDb := db.AdminRoleDb{} + adminRoleDb.Set() + _, err = adminRoleDb.AdminDeleteBySessionForAdmId(session, admIds) + if err != nil { + _ = session.Rollback() + return + } + + return session.Commit() +} diff --git a/app/admin/svc/svc_auth.go b/app/admin/svc/svc_auth.go new file mode 100644 index 0000000..189dc23 --- /dev/null +++ b/app/admin/svc/svc_auth.go @@ -0,0 +1,51 @@ +package svc + +import ( + "applet/app/admin/lib/auth" + "applet/app/db" + "applet/app/db/model" + "errors" + "github.com/gin-gonic/gin" + "strings" +) + +func GetUser(c *gin.Context) *model.Admin { + user, _ := c.Get("admin") + if user == nil { + return &model.Admin{ + AdmId: 0, + Username: "", + Password: "", + State: 0, + CreateAt: "", + UpdateAt: "", + } + } + return user.(*model.Admin) +} + +func CheckUser(c *gin.Context) (*model.Admin, error) { + token := c.GetHeader("Authorization") + if token == "" { + return nil, errors.New("token not exist") + } + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if !(len(parts) == 2 && parts[0] == "Bearer") { + return nil, errors.New("token format error") + } + // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 + mc, err := auth.ParseToken(parts[1]) + if err != nil { + return nil, err + } + + // 获取admin + adminDb := db.AdminDb{} + adminDb.Set() + admin, err := adminDb.GetAdmin(mc.AdmId) + if err != nil { + return nil, err + } + return admin, nil +} diff --git a/app/admin/svc/svc_central_kitchen_for_school.go b/app/admin/svc/svc_central_kitchen_for_school.go new file mode 100644 index 0000000..6082141 --- /dev/null +++ b/app/admin/svc/svc_central_kitchen_for_school.go @@ -0,0 +1,67 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/enum" +) + +func CentralKitchenForSchoolInfo(enterpriseId int) (err error, resp md.CentralKitchenForSchoolInfoResp) { + //1、查询`enterprise` + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(enterpriseId) + if err != nil { + return + } + resp.Name = enterprise.Name + resp.Memo = enterprise.Memo + resp.Kind = enum.EnterprisePvd(enterprise.Kind).String() + resp.State = enum.EnterpriseState(enterprise.State).String() + + //2、查询`central_kitchen_for_school_with_spec` + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(enterpriseId) + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + return + } + if spec != nil { + resp.IsOpenBreakfast = spec.IsOpenBreakfast + resp.IsOpenLunch = spec.IsOpenLunch + resp.IsOpenDinner = spec.IsOpenDinner + } + + //3、查询`central_kitchen_for_school_set` + centralKitchenForSchoolSetDb := db.CentralKitchenForSchoolSetDb{} + centralKitchenForSchoolSetDb.Set(enterpriseId) + set, err := centralKitchenForSchoolSetDb.GetCentralKitchenForSchoolSet() + if err != nil { + return + } + if set != nil { + resp.IsOpenTeacherReportMeal = set.IsOpenTeacherReportMeal + resp.IsOpenReportMealForDay = set.IsOpenReportMealForDay + resp.IsOpenReportMealForMonth = set.IsOpenReportMealForMonth + resp.IsOpenReportMealForSemester = set.IsOpenReportMealForSemester + } + + //4、统计 "教师"、 "学生"、 "班级" 数量 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + resp.StudentNum, err = userIdentityDb.CountUserIdentityForEnterprise(enterpriseId, enum.UserIdentityForCentralKitchenForStudent) + if err != nil { + return + } + resp.TeacherNum, err = userIdentityDb.CountUserIdentityForEnterprise(enterpriseId, enum.UserIdentityForCentralKitchenForTeacher) + if err != nil { + return + } + classDb := db.ClassDb{} + classDb.Set(0) + resp.ClassNum, err = classDb.CountClassForEnterprise(enterpriseId) + if err != nil { + return + } + return +} diff --git a/app/admin/svc/svc_central_kitchen_for_school_package.go b/app/admin/svc/svc_central_kitchen_for_school_package.go new file mode 100644 index 0000000..9a151d8 --- /dev/null +++ b/app/admin/svc/svc_central_kitchen_for_school_package.go @@ -0,0 +1,187 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + "errors" + "time" +) + +func AddCentralKitchenForSchoolPackage(req md.SaveCentralKitchenForSchoolPackageReq) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + now := time.Now() + //1、查询`central_kitchen_for_school_with_spec` + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(req.EnterpriseId) + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + _ = session.Rollback() + return err + } + //1、新增`central_kitchen_for_school_package` + centralKitchenForSchoolPackageDb := db.CentralKitchenForSchoolPackageDb{} + packageId, err := centralKitchenForSchoolPackageDb.CentralKitchenForSchoolPackageInsertBySession(session, &model.CentralKitchenForSchoolPackage{ + EnterpriseId: req.EnterpriseId, + Year: req.Year, + Month: req.Month, + TotalPrice: "", + StartDate: req.StartDate, + EndDate: req.EndDate, + State: 1, + CreateAt: now.Format("2006-01-02 15:04:05"), + UpdateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + _ = session.Rollback() + return err + } + + //2、批量新增 `central_kitchen_for_school_package_with_day` + var centralKitchenForSchoolPackageWithDays []*model.CentralKitchenForSchoolPackageWithDay + for _, v := range req.DateList { + var isOpenBreakfast, isOpenLunch, isOpenDinner, isOpenReplenish = md.CloseBreakfast, md.CloseLunch, md.CloseDinner, md.CloseReplenish + if spec.IsOpenBreakfast == md.OpenBreakfast && v.IsOpenBreakfast == md.OpenBreakfast { + isOpenBreakfast = md.OpenBreakfast + } + if spec.IsOpenLunch == md.OpenLunch && v.IsOpenLunch == md.OpenLunch { + isOpenLunch = md.OpenLunch + } + if spec.IsOpenDinner == md.OpenDinner && v.IsOpenDinner == md.OpenDinner { + isOpenDinner = md.OpenDinner + } + if v.IsOpenReplenish == md.OpenReplenish { + isOpenReplenish = md.OpenReplenish + } + centralKitchenForSchoolPackageWithDays = append(centralKitchenForSchoolPackageWithDays, &model.CentralKitchenForSchoolPackageWithDay{ + Date: v.Date, + PackageId: packageId, + IsOpenBreakfast: isOpenBreakfast, + IsOpenLunch: isOpenLunch, + IsOpenDinner: isOpenDinner, + IsOpenReplenish: isOpenReplenish, + }) + } + centralKitchenForSchoolPackageWithDayDb := db.CentralKitchenForSchoolPackageWithDayDb{} + centralKitchenForSchoolPackageWithDayDb.Set(packageId) + _, err = centralKitchenForSchoolPackageWithDayDb.BatchAddCentralKitchenForSchoolPackageWithDaysBySession(session, centralKitchenForSchoolPackageWithDays) + if err != nil { + _ = session.Rollback() + return err + } + return session.Commit() +} + +func UpdateCentralKitchenForSchoolPackage(req md.SaveCentralKitchenForSchoolPackageReq) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + now := time.Now() + //1、查询`central_kitchen_for_school_with_spec` + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(req.EnterpriseId) + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + _ = session.Rollback() + return err + } + //2、修改`central_kitchen_for_school_package` + centralKitchenForSchoolPackageDb := db.CentralKitchenForSchoolPackageDb{} + centralKitchenForSchoolPackageDb.Set() + centralKitchenForSchoolPackage, err := centralKitchenForSchoolPackageDb.GetCentralKitchenForSchoolPackage(req.PackageId) + if err != nil { + _ = session.Rollback() + return err + } + if centralKitchenForSchoolPackage == nil { + _ = session.Rollback() + return errors.New("未查询到对应套餐记录") + } + centralKitchenForSchoolPackage.Year = req.Year + centralKitchenForSchoolPackage.Month = req.Month + centralKitchenForSchoolPackage.StartDate = req.StartDate + centralKitchenForSchoolPackage.EndDate = req.EndDate + centralKitchenForSchoolPackage.State = req.State + centralKitchenForSchoolPackage.UpdateAt = now.Format("2006-01-02 15:04:05") + affected, err := centralKitchenForSchoolPackageDb.CentralKitchenForSchoolPackageUpdateBySession(session, centralKitchenForSchoolPackage.Id, centralKitchenForSchoolPackage) + if err != nil { + _ = session.Rollback() + return + } + if affected == 0 { + _ = session.Rollback() + return errors.New("更新套餐记录失败") + } + + //3、删除 `central_kitchen_for_school_package_with_day` 旧数据 + centralKitchenForSchoolPackageWithDayDb := db.CentralKitchenForSchoolPackageWithDayDb{} + centralKitchenForSchoolPackageWithDayDb.Set(centralKitchenForSchoolPackage.Id) + _, err = centralKitchenForSchoolPackageWithDayDb.CentralKitchenForSchoolPackageWithDayDeleteBySession(session) + if err != nil { + _ = session.Rollback() + return + } + + //4、批量新增 `central_kitchen_for_school_package_with_day` + var centralKitchenForSchoolPackageWithDays []*model.CentralKitchenForSchoolPackageWithDay + for _, v := range req.DateList { + var isOpenBreakfast, isOpenLunch, isOpenDinner, isOpenReplenish = md.CloseBreakfast, md.CloseLunch, md.CloseDinner, md.CloseReplenish + if spec.IsOpenBreakfast == md.OpenBreakfast && v.IsOpenBreakfast == md.OpenBreakfast { + isOpenBreakfast = md.OpenBreakfast + } + if spec.IsOpenLunch == md.OpenLunch && v.IsOpenLunch == md.OpenLunch { + isOpenLunch = md.OpenLunch + } + if spec.IsOpenDinner == md.OpenDinner && v.IsOpenDinner == md.OpenDinner { + isOpenDinner = md.OpenDinner + } + if v.IsOpenReplenish == md.OpenReplenish { + isOpenReplenish = md.OpenReplenish + } + centralKitchenForSchoolPackageWithDays = append(centralKitchenForSchoolPackageWithDays, &model.CentralKitchenForSchoolPackageWithDay{ + Date: v.Date, + PackageId: centralKitchenForSchoolPackage.Id, + IsOpenBreakfast: isOpenBreakfast, + IsOpenLunch: isOpenLunch, + IsOpenDinner: isOpenDinner, + IsOpenReplenish: isOpenReplenish, + }) + } + _, err = centralKitchenForSchoolPackageWithDayDb.BatchAddCentralKitchenForSchoolPackageWithDaysBySession(session, centralKitchenForSchoolPackageWithDays) + if err != nil { + _ = session.Rollback() + return err + } + return session.Commit() +} + +func DeleteCentralKitchenForSchoolPackage(packageId int) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + //1、删除`central_kitchen_for_school_package` + centralKitchenForSchoolPackageDb := db.CentralKitchenForSchoolPackageDb{} + centralKitchenForSchoolPackageDb.Set() + centralKitchenForSchoolPackage, err := centralKitchenForSchoolPackageDb.GetCentralKitchenForSchoolPackage(packageId) + if err != nil { + _ = session.Rollback() + return err + } + if centralKitchenForSchoolPackage == nil { + _ = session.Rollback() + return errors.New("未查询到对应套餐记录") + } + centralKitchenForSchoolPackage.IsDelete = 1 + affected, err := centralKitchenForSchoolPackageDb.CentralKitchenForSchoolPackageUpdateBySession(session, centralKitchenForSchoolPackage.Id, centralKitchenForSchoolPackage, "is_delete") + if err != nil { + _ = session.Rollback() + return + } + if affected == 0 { + _ = session.Rollback() + return errors.New("删除套餐记录失败") + } + return session.Commit() +} diff --git a/app/admin/svc/svc_enterprise.go b/app/admin/svc/svc_enterprise.go new file mode 100644 index 0000000..80b622c --- /dev/null +++ b/app/admin/svc/svc_enterprise.go @@ -0,0 +1,91 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" +) + +func EnterpriseList(req md.EnterpriseListReq) (m []model.Enterprise, total int64, err error) { + sess := db.Db.Desc("id").Limit(req.Limit, (req.Page-1)*req.Limit) + if req.Name != "" { + sess.And("name like ?", "%"+req.Name+"%") + } + if req.Kind != 0 { + sess.And("kind = ?", req.Kind) + } + total, err = sess.FindAndCount(&m) + if err != nil { + return + } + return +} + +func EnterpriseDetail(enterpriseId int) (data md.EnterpriseDetailResp, err error) { + //1、查找所在单位 + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(enterpriseId) + if err != nil { + return + } + data.Enterprise = *enterprise + + //2、查找对应年级 + gradeDb := db.GradeDb{} + gradeDb.Set(enterpriseId) + gradeList, err := gradeDb.FindGrade() + if err != nil { + return + } + + for _, v := range *gradeList { + //3、查找对应班级 + classDb := db.ClassDb{} + classDb.Set(v.Id) + classes, err1 := classDb.FindClass() + if err1 != nil { + err = err1 + return + } + data.GradeList = append(data.GradeList, md.GradeListStruct{ + Grade: v, + ClassList: *classes, + }) + } + return +} + +func EnterpriseDelete(enterpriseIds []int) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + //1、删除所在单位 + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + _, err = enterpriseDb.EnterpriseDeleteBySession(session, enterpriseIds) + if err != nil { + _ = session.Rollback() + return + } + + //2、删除年级 + gradeDb := db.GradeDb{} + gradeDb.Set(0) + _, err = gradeDb.ClassDeleteBySessionForEnterprise(session, enterpriseIds) + if err != nil { + _ = session.Rollback() + return + } + + //3、删除班级 + classDb := db.ClassDb{} + classDb.Set(0) + _, err = classDb.ClassDeleteBySessionForEnterprise(session, enterpriseIds) + if err != nil { + _ = session.Rollback() + return + } + + return session.Commit() +} diff --git a/app/admin/svc/svc_login.go b/app/admin/svc/svc_login.go new file mode 100644 index 0000000..9b0f90e --- /dev/null +++ b/app/admin/svc/svc_login.go @@ -0,0 +1,33 @@ +package svc + +import ( + "applet/app/admin/lib/auth" + "applet/app/admin/md" + "applet/app/db/model" + "applet/app/utils/cache" + "applet/app/utils/logx" +) + +func HandleLoginToken(cacheKey string, admin *model.Admin) (string, error) { + // 获取之前生成的token + token, err := cache.GetString(cacheKey) + if err != nil { + _ = logx.Error(err) + } + // 没有获取到 + if err != nil || token == "" { + // 生成token + token, err = auth.GenToken(admin.AdmId, admin.Username) + if err != nil { + return "", err + } + // 缓存token + _, err = cache.SetEx(cacheKey, token, md.JwtTokenCacheTime) + if err != nil { + return "", err + } + return token, nil + } + + return token, nil +} diff --git a/app/admin/svc/svc_qrcode.go b/app/admin/svc/svc_qrcode.go new file mode 100644 index 0000000..b3463c0 --- /dev/null +++ b/app/admin/svc/svc_qrcode.go @@ -0,0 +1 @@ +package svc diff --git a/app/admin/svc/svc_role.go b/app/admin/svc/svc_role.go new file mode 100644 index 0000000..b2825e7 --- /dev/null +++ b/app/admin/svc/svc_role.go @@ -0,0 +1,183 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/utils" + "applet/app/utils/cache" + "encoding/json" + "errors" + "regexp" + "strings" + "time" +) + +func CheckUserRole(cacheKey, uri string, admId int) (isHasPermission bool, err error) { + uri = utils.UriFilterExcludeQueryString(uri) //去除uri中?后的query参数 + isHasPermission = false + var rolePermission []string + var rolePermissionString string + rolePermissionString, _ = cache.GetString(cacheKey) + //if rolePermissionString != "" { + if false { + if err = json.Unmarshal([]byte(rolePermissionString), &rolePermission); err != nil { + return + } + } else { + qrcodeWithBatchRecordsDb := db.AdminDb{} + qrcodeWithBatchRecordsDb.Set() + list, _, err := qrcodeWithBatchRecordsDb.GetAdminRolePermission(admId) + if err != nil { + return isHasPermission, err + } + for _, v := range list { + rolePermission = append(rolePermission, v.Permission.Action) + } + marshal, err := json.Marshal(rolePermission) + if err != nil { + return isHasPermission, err + } + rolePermissionString = string(marshal) + _, err = cache.SetEx(cacheKey, rolePermissionString, md.AdminRolePermissionCacheTime) + } + + if utils.InArr(uri, rolePermission) { + isHasPermission = true + } else { + //正则匹配占位符情况 + compileRegex := regexp.MustCompile("[0-9]+") + matchArr := compileRegex.FindAllString(uri, -1) + if len(matchArr) > 0 { + uri = strings.Replace(uri, matchArr[len(matchArr)-1], ":id", 1) + if utils.InArr(uri, rolePermission) { + isHasPermission = true + } + } + } + return +} + +func DeleteRole(roleId int) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + + //1、删除 `role` + roleDb := db.RoleDb{} + roleDb.Set(roleId) + _, err = roleDb.RoleDeleteBySession(session, roleId) + if err != nil { + _ = session.Rollback() + return + } + + //2、删除 `role_permission_group` + rolePermissionGroupDb := db.RolePermissionGroupDb{} + rolePermissionGroupDb.Set() + _, err = rolePermissionGroupDb.RolePermissionGroupDeleteForRoleBySession(session, roleId) + if err != nil { + _ = session.Rollback() + return + } + + //3、删除 `admin_role` + adminRoleDb := db.AdminRoleDb{} + adminRoleDb.Set() + _, err = adminRoleDb.AdminRoleDeleteForRoleBySession(session, roleId) + if err != nil { + _ = session.Rollback() + return + } + + return session.Commit() +} + +func RoleBindPermissionGroup(req md.RoleBindPermissionGroupReq) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + //1、查询 `role` + roleDb := db.RoleDb{} + roleDb.Set(req.RoleId) + role, err := roleDb.GetRole() + if err != nil { + return + } + if role == nil { + return errors.New("未查询到相应记录") + } + + //1、删除 `role_permission_group` + rolePermissionGroupDb := db.RolePermissionGroupDb{} + rolePermissionGroupDb.Set() + _, err = rolePermissionGroupDb.RolePermissionGroupDeleteForRoleBySession(session, req.RoleId) + if err != nil { + _ = session.Rollback() + return + } + + //2、新增 `role_permission_group`` + var mm []*model.RolePermissionGroup + now := time.Now() + for _, v := range req.PermissionIds { + mm = append(mm, &model.RolePermissionGroup{ + RoleId: role.Id, + GroupId: v, + CreateAt: now, + UpdateAt: now, + }) + } + _, err = rolePermissionGroupDb.BatchAddRolePermissionGroupBySession(session, mm) + if err != nil { + _ = session.Rollback() + return + } + + return session.Commit() +} + +func BindAdminRole(req md.BindAdminRoleReq) (err error) { + session := db.Db.NewSession() + defer session.Close() + session.Begin() + //1、查询 `role` + adminDb := db.AdminDb{} + adminDb.Set() + role, err := adminDb.GetAdmin(req.AdmId) + if err != nil { + return + } + if role == nil { + return errors.New("未查询到相应记录") + } + + //1、删除 `admin_role` + adminRoleDb := db.AdminRoleDb{} + adminRoleDb.Set() + _, err = adminRoleDb.AdminRoleDeleteBySession(session, req.AdmId) + if err != nil { + _ = session.Rollback() + return + } + + //2、新增 `删除 `admin_role`` + var mm []*model.AdminRole + now := time.Now() + for _, v := range req.RoleIds { + mm = append(mm, &model.AdminRole{ + AdmId: req.AdmId, + RoleId: v, + State: 1, + CreateAt: now, + UpdateAt: now, + }) + } + _, err = adminRoleDb.BatchAddAdminRoleBySession(session, mm) + if err != nil { + _ = session.Rollback() + return + } + + return session.Commit() +} diff --git a/app/admin/svc/svc_user.go b/app/admin/svc/svc_user.go new file mode 100644 index 0000000..d513d51 --- /dev/null +++ b/app/admin/svc/svc_user.go @@ -0,0 +1,97 @@ +package svc + +import ( + "applet/app/admin/md" + "applet/app/db" + "applet/app/db/model" + "errors" +) + +func UserList(req md.UserListReq) (resp md.UserListResp, err error) { + var m []model.User + userDb := db.UserDb{} + userDb.Set() + sess := userDb.Db.Join("LEFT", "user_identity", "user.id = user_identity.uid"). + Join("LEFT", "enterprise", "user_identity.enterprise_id = enterprise.id").Limit(req.Limit, (req.Page-1)*req.Limit) + sess.Where("1=1") + if req.Phone != "" { + sess.And("user.phone like ?", "%"+req.Phone+"%") + } + if req.Nickname != "" { + sess.And("user.nickname like ?", "%"+req.Nickname+"%") + } + if req.CreateTimeStart != "" { + sess.And("user.create_at >= ?", req.CreateTimeStart) + } + if req.CreateTimeEnd != "" { + sess.And("user.create_at <= ?", req.CreateTimeEnd) + } + resp.Total, err = sess.Desc("user.id").FindAndCount(&m) + if err != nil { + return + } + + for _, v := range m { + //获取用户身份信息 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(v.Id) + identity, err1 := userIdentityDb.FindUserIdentity() + if err1 != nil { + err = err1 + return + } + var temp = md.UserList{ + Id: v.Id, + Nickname: v.Nickname, + Phone: v.Phone, + Avatar: v.Avatar, + CreateAt: v.CreateAt.Format("2006-01-02 15:04:05"), + } + for _, v1 := range *identity { + temp.BindUserIdentity = append(temp.BindUserIdentity, struct { + Id int `json:"id" label:"id"` + IdNo string `json:"id_no" label:"身份证号"` + Name string `json:"name" label:"姓名"` + EnterpriseName string `json:"enterprise_name" label:"企业名"` + EnterpriseKind int32 `json:"enterprise_kind" label:"企业类型"` + Kind int `json:"kind" label:"身份类型"` + }{Id: v1.UserIdentity.Id, IdNo: v1.UserIdentity.IdNo, Name: v1.UserIdentity.Name, EnterpriseName: v1.Enterprise.Name, EnterpriseKind: v1.Enterprise.Kind, Kind: v1.UserIdentity.Kind}) + } + resp.List = append(resp.List, temp) + } + return +} + +func UserUpdate(req md.UserList) (err error) { + userDb := db.UserDb{} + userDb.Set() + user, err := userDb.GetUser(req.Id) + if err != nil { + return err + } + if user == nil { + return errors.New("未获取到用户信息记录") + } + + for _, v := range req.BindUserIdentity { + //获取用户身份信息 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(v.Id) + identity, err1 := userIdentityDb.GetUserIdentity(v.Id) + if err1 != nil { + err = err1 + return + } + if user == nil { + return errors.New("未获取到用户身份信息记录") + } + identity.Name = v.Name + identity.IdNo = v.IdNo + _, err2 := userIdentityDb.UserIdentityUpdate(identity.Id, identity, "name", "id_no") + if err2 != nil { + err = err2 + return + } + } + return +} diff --git a/app/cfg/cfg_app.go b/app/cfg/cfg_app.go new file mode 100644 index 0000000..3bcadea --- /dev/null +++ b/app/cfg/cfg_app.go @@ -0,0 +1,43 @@ +package cfg + +import ( + "time" +) + +type Config struct { + Debug bool `yaml:"debug"` + Prd bool `yaml:"prd"` + SrvAddr string `yaml:"srv_addr"` + RedisAddr string `yaml:"redis_addr"` + SmartCanteenPay string `yaml:"smart_canteen_pay"` + DB DBCfg `yaml:"db"` + Log LogCfg `yaml:"log"` +} + +//数据库配置结构体 +type DBCfg struct { + Host string `yaml:"host"` //ip及端口 + Name string `yaml:"name"` //库名 + User string `yaml:"user"` //用户 + Psw string `yaml:"psw"` //密码 + ShowLog bool `yaml:"show_log"` //是否显示SQL语句 + MaxLifetime time.Duration `yaml:"max_lifetime"` + MaxOpenConns int `yaml:"max_open_conns"` + MaxIdleConns int `yaml:"max_idle_conns"` + Path string `yaml:"path"` //日志文件存放路径 +} + +//日志配置结构体 +type LogCfg struct { + AppName string `yaml:"app_name" ` + Level string `yaml:"level"` + IsStdOut bool `yaml:"is_stdout"` + TimeFormat string `yaml:"time_format"` // second, milli, nano, standard, iso, + Encoding string `yaml:"encoding"` // console, json + + IsFileOut bool `yaml:"is_file_out"` + FileDir string `yaml:"file_dir"` + FileName string `yaml:"file_name"` + FileMaxSize int `yaml:"file_max_size"` + FileMaxAge int `yaml:"file_max_age"` +} diff --git a/app/cfg/cfg_cache_key.go b/app/cfg/cfg_cache_key.go new file mode 100644 index 0000000..c091909 --- /dev/null +++ b/app/cfg/cfg_cache_key.go @@ -0,0 +1,3 @@ +package cfg + +// 统一管理缓存 diff --git a/app/cfg/init_cache.go b/app/cfg/init_cache.go new file mode 100644 index 0000000..873657f --- /dev/null +++ b/app/cfg/init_cache.go @@ -0,0 +1,9 @@ +package cfg + +import ( + "applet/app/utils/cache" +) + +func InitCache() { + cache.NewRedis(RedisAddr) +} diff --git a/app/cfg/init_cfg.go b/app/cfg/init_cfg.go new file mode 100644 index 0000000..3aba140 --- /dev/null +++ b/app/cfg/init_cfg.go @@ -0,0 +1,48 @@ +package cfg + +import ( + "flag" + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +//配置文件数据,全局变量 +var ( + Debug bool + Prd bool + SrvAddr string + SmartCanteenPay string + RedisAddr string + DB *DBCfg + Log *LogCfg +) + +//初始化配置文件,将cfg.yml读入到内存 +func InitCfg() { + //用指定的名称、默认值、使用信息注册一个string类型flag。 + path := flag.String("c", "etc/cfg.yml", "config file") + //解析命令行参数写入注册的flag里。 + //解析之后,flag的值可以直接使用。 + flag.Parse() + var ( + c []byte + err error + conf *Config + ) + if c, err = ioutil.ReadFile(*path); err != nil { + panic(err) + } + //yaml.Unmarshal反序列化映射到Config + if err = yaml.Unmarshal(c, &conf); err != nil { + panic(err) + } + //数据读入内存 + Prd = conf.Prd + Debug = conf.Debug + DB = &conf.DB + Log = &conf.Log + RedisAddr = conf.RedisAddr + SmartCanteenPay = conf.SmartCanteenPay + SrvAddr = conf.SrvAddr +} diff --git a/app/cfg/init_log.go b/app/cfg/init_log.go new file mode 100644 index 0000000..0f31eb5 --- /dev/null +++ b/app/cfg/init_log.go @@ -0,0 +1,20 @@ +package cfg + +import "applet/app/utils/logx" + +func InitLog() { + logx.InitDefaultLogger(&logx.LogConfig{ + AppName: Log.AppName, + Level: Log.Level, + StacktraceLevel: "error", + IsStdOut: Log.IsStdOut, + TimeFormat: Log.TimeFormat, + Encoding: Log.Encoding, + IsFileOut: Log.IsFileOut, + FileDir: Log.FileDir, + FileName: Log.FileName, + FileMaxSize: Log.FileMaxSize, + FileMaxAge: Log.FileMaxAge, + Skip: 2, + }) +} diff --git a/app/cfg/init_task.go b/app/cfg/init_task.go new file mode 100644 index 0000000..0eec20e --- /dev/null +++ b/app/cfg/init_task.go @@ -0,0 +1,42 @@ +package cfg + +import ( + "flag" + "io/ioutil" + + "gopkg.in/yaml.v2" + + mc "applet/app/utils/cache/cache" + "applet/app/utils/logx" +) + +func InitTaskCfg() { + path := flag.String("c", "etc/task.yml", "config file") + flag.Parse() + var ( + c []byte + err error + conf *Config + ) + if c, err = ioutil.ReadFile(*path); err != nil { + panic(err) + } + if err = yaml.Unmarshal(c, &conf); err != nil { + panic(err) + } + Prd = conf.Prd + Debug = conf.Debug + DB = &conf.DB + Log = &conf.Log + RedisAddr = conf.RedisAddr +} + +var MemCache mc.Cache + +func InitMemCache() { + var err error + MemCache, err = mc.NewCache("memory", `{"interval":60}`) + if err != nil { + logx.Fatal(err.Error()) + } +} diff --git a/app/customer/enum/enum_qrcode.go b/app/customer/enum/enum_qrcode.go new file mode 100644 index 0000000..12ab106 --- /dev/null +++ b/app/customer/enum/enum_qrcode.go @@ -0,0 +1,67 @@ +package enum + +type QrcodeBatchState int32 + +const ( + QrcodeBatchStateForUseIng = 1 + QrcodeBatchStateForUseAlready = 2 + QrcodeBatchStateForExpire = 3 + QrcodeBatchStateForCancel = 4 +) + +func (gt QrcodeBatchState) String() string { + switch gt { + case QrcodeBatchStateForUseIng: + return "使用中" + case QrcodeBatchStateForUseAlready: + return "使用完" + case QrcodeBatchStateForExpire: + return "已过期" + case QrcodeBatchStateForCancel: + return "已作废" + default: + return "未知" + } +} + +type QrcodeWithBatchRecordsSate int32 + +const ( + QrcodeWithBatchRecordsStateForWait = 1 + QrcodeWithBatchRecordsStateForAlready = 2 + QrcodeWithBatchRecordsStateForExpire = 3 + QrcodeWithBatchRecordsStateForCancel = 4 +) + +func (gt QrcodeWithBatchRecordsSate) String() string { + switch gt { + case QrcodeWithBatchRecordsStateForWait: + return "待使用" + case QrcodeWithBatchRecordsStateForAlready: + return "已使用" + case QrcodeWithBatchRecordsStateForExpire: + return "已过期" + case QrcodeWithBatchRecordsStateForCancel: + return "已作废" + default: + return "未知" + } +} + +type QrcodeSate int32 + +const ( + QrcodeSateAllowUse = 1 + QrcodeSateAllowNotUse = 2 +) + +func (gt QrcodeSate) String() string { + switch gt { + case QrcodeSateAllowUse: + return "可使用" + case QrcodeSateAllowNotUse: + return "不可用" + default: + return "未知" + } +} diff --git a/app/customer/enum/enum_wx_official_account.go b/app/customer/enum/enum_wx_official_account.go new file mode 100644 index 0000000..e982771 --- /dev/null +++ b/app/customer/enum/enum_wx_official_account.go @@ -0,0 +1,19 @@ +package enum + +type WxOfficialAccountRequest string + +const ( + GetAccessToken = "cgi-bin/token" + QrcodeCreate = "cgi-bin/qrcode/create" +) + +func (gt WxOfficialAccountRequest) String() string { + switch gt { + case GetAccessToken: + return "获取 Access token" + case QrcodeCreate: + return "生成带参二维码" + default: + return "未知" + } +} diff --git a/app/customer/hdl/hdl_alipay.go b/app/customer/hdl/hdl_alipay.go new file mode 100644 index 0000000..ebdbeea --- /dev/null +++ b/app/customer/hdl/hdl_alipay.go @@ -0,0 +1,45 @@ +package hdl + +import ( + "applet/app/customer/lib/validate" + "applet/app/customer/md" + "applet/app/customer/svc" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +func AesDecrypt(c *gin.Context) { + var req md.CurlAesDecrypt + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err, result := svc.AesDecrypt(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, result, nil) + return +} + +func SystemOauthToken(c *gin.Context) { + var req md.SystemOauthTokenReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err, result := svc.SystemOauthToken(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, result, nil) + return +} diff --git a/app/customer/hdl/hdl_banner.go b/app/customer/hdl/hdl_banner.go new file mode 100644 index 0000000..2203b0a --- /dev/null +++ b/app/customer/hdl/hdl_banner.go @@ -0,0 +1,21 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +func BannerList(c *gin.Context) { + bannerDb := db.BannerDb{} + bannerDb.Set() + banners, err := bannerDb.FindBanner(0, 0) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": banners, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_central_kitchen_for_school_order.go b/app/customer/hdl/hdl_central_kitchen_for_school_order.go new file mode 100644 index 0000000..5d17cd9 --- /dev/null +++ b/app/customer/hdl/hdl_central_kitchen_for_school_order.go @@ -0,0 +1,278 @@ +package hdl + +import ( + "applet/app/admin/lib/validate" + "applet/app/customer/md" + svc "applet/app/customer/svc/order" + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" +) + +func CentralKitchenForSchoolOrderList(c *gin.Context) { + var req md.CentralKitchenForSchoolOrderListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + list, total, err := svc.OrderList(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": list, + "total": total, + "ord_state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForWait), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForWait, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunding), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunding, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunded), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunded, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForComplete), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForComplete, + }, + }, + "kind_list": []map[string]string{ + { + "name": "按学期购买", + "value": "1", + }, + { + "name": "按月购买", + "value": "2", + }, + { + "name": "按天购买", + "value": "3", + }, + }, + }, nil) + return +} + +func CentralKitchenForSchoolOrderDetail(c *gin.Context) { + outTradeNo := c.DefaultQuery("out_trade_no", "") + centralKitchenForSchoolPackageOrd := db.CentralKitchenForSchoolPackageOrd{} + centralKitchenForSchoolPackageOrd.Set(outTradeNo) + ord, err := centralKitchenForSchoolPackageOrd.GetCentralKitchenForSchoolPackageOrd() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if ord == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应订单记录") + return + } + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + list, err := centralKitchenForSchoolUserWithDayDb.FindCentralKitchenForSchoolUserWithDayByOrdNo(ord.OutTradeNo) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + var total, totalBreakfast, totalLunch, totalDinner int + total = len(*list) + for _, v := range *list { + if v.Kind == enum.CentralKitchenForSchoolUserWithDayKindForBreakfast { + totalBreakfast++ + } + if v.Kind == enum.CentralKitchenForSchoolUserWithDayKindForLunch { + totalLunch++ + } + if v.Kind == enum.CentralKitchenForSchoolUserWithDayKindForDinner { + totalDinner++ + } + } + + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(ord.EnterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "enterprise": enterprise, + "total": total, + "ord_info": ord, + "total_breakfast": totalBreakfast, + "total_lunch": totalLunch, + "total_dinner": totalDinner, + "ord_state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForWait), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForWait, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunding), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunding, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunded), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForRefunded, + }, + { + "name": enum.CentralKitchenForSchoolPackageOrdOrdState.String(enum.CentralKitchenForSchoolPackageOrdOrdStateForComplete), + "value": enum.CentralKitchenForSchoolPackageOrdOrdStateForComplete, + }, + }, + "kind_list": []map[string]string{ + { + "name": "按学期购买", + "value": "1", + }, + { + "name": "按月购买", + "value": "2", + }, + { + "name": "按天购买", + "value": "3", + }, + }, + }, nil) + return +} + +func CentralKitchenForSchoolOrderBelowWithDay(c *gin.Context) { + outTradeNo := c.DefaultQuery("out_trade_no", "") + centralKitchenForSchoolPackageOrd := db.CentralKitchenForSchoolPackageOrd{} + centralKitchenForSchoolPackageOrd.Set(outTradeNo) + ord, err := centralKitchenForSchoolPackageOrd.GetCentralKitchenForSchoolPackageOrd() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if ord == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应订单记录") + return + } + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + list, err := centralKitchenForSchoolUserWithDayDb.FindCentralKitchenForSchoolUserWithDayByOrdNoAndState(ord.OutTradeNo, enum.CentralKitchenForSchoolUserWithDayStateForWait) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, map[string]interface{}{ + "list": list, + "allow_refund_state": enum.CentralKitchenForSchoolUserWithDayStateForWait, + "kind_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForBreakfast), + "value": enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForLunch), + "value": enum.CentralKitchenForSchoolUserWithDayKindForLunch, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForDinner), + "value": enum.CentralKitchenForSchoolUserWithDayKindForDinner, + }, + }, + }, nil) + return +} + +func CentralKitchenForSchoolOrderRefund(c *gin.Context) { + var req md.CentralKitchenForSchoolOrderRefundReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + err = svc.CentralKitchenForSchoolOrderRefund(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolOrderRefundList(c *gin.Context) { + var req md.CentralKitchenForSchoolOrderRefundListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + list, total, err := svc.CentralKitchenForSchoolOrderRefundList(req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": list, + "total": total, + "kind_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForBreakfast), + "value": enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForLunch), + "value": enum.CentralKitchenForSchoolUserWithDayKindForLunch, + }, + { + "name": enum.CentralKitchenForSchoolUserWithDayKind.String(enum.CentralKitchenForSchoolUserWithDayKindForDinner), + "value": enum.CentralKitchenForSchoolUserWithDayKindForDinner, + }, + }, + "state_list": []map[string]interface{}{ + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditing), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditing, + }, + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditPass), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditPass, + }, + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditReject), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditReject, + }, + { + "name": enum.CentralKitchenForSchoolUserRefundDayState.String(enum.CentralKitchenForSchoolUserRefundDayStateForAuditComplete), + "value": enum.CentralKitchenForSchoolUserRefundDayStateForAuditComplete, + }, + }, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_central_kitchen_for_school_package.go b/app/customer/hdl/hdl_central_kitchen_for_school_package.go new file mode 100644 index 0000000..dace63b --- /dev/null +++ b/app/customer/hdl/hdl_central_kitchen_for_school_package.go @@ -0,0 +1,58 @@ +package hdl + +import ( + "applet/app/customer/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/utils" + "github.com/gin-gonic/gin" + "time" +) + +func CentralKitchenForSchoolPackage(c *gin.Context) { + enterpriseId := utils.StrToInt(c.DefaultQuery("enterprise_id", "")) + //1、查询出当前合适的package + var m []model.CentralKitchenForSchoolPackage + now := time.Now().Format("2006-01-02 15:04:05") + err := db.Db.Where("enterprise_id =?", enterpriseId).Where("end_date > ?", now).And("is_delete = 0").And("state = 1").Desc("end_date").Find(&m) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + //2、循环拼接数据 + var resp []md.FindCentralKitchenForSchoolPackageReq + for _, v := range m { + centralKitchenForSchoolPackageWithDayDb := db.CentralKitchenForSchoolPackageWithDayDb{} + centralKitchenForSchoolPackageWithDayDb.Set(v.Id) + centralKitchenForSchoolPackageWithDay, err1 := centralKitchenForSchoolPackageWithDayDb.FindCentralKitchenForSchoolPackageWithDay() + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + var tempResp = md.FindCentralKitchenForSchoolPackageReq{ + PackageId: v.Id, + EnterpriseId: v.EnterpriseId, + Year: v.Year, + Month: v.Month, + StartDate: v.StartDate, + EndDate: v.EndDate, + DateList: nil, + } + for _, v1 := range *centralKitchenForSchoolPackageWithDay { + tempResp.DateList = append(tempResp.DateList, struct { + Date string `json:"date"` + IsOpenBreakfast int32 `json:"is_open_breakfast"` + IsOpenLunch int32 `json:"is_open_lunch"` + IsOpenDinner int32 `json:"is_open_dinner"` + IsOpenReplenish int32 `json:"is_open_replenish"` + }{Date: v1.Date, IsOpenBreakfast: int32(v1.IsOpenBreakfast), IsOpenLunch: int32(v1.IsOpenLunch), IsOpenDinner: int32(v1.IsOpenDinner), IsOpenReplenish: int32(v1.IsOpenReplenish)}) + } + resp = append(resp, tempResp) + } + e.OutSuc(c, map[string]interface{}{ + "list": resp, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_enterprise.go b/app/customer/hdl/hdl_enterprise.go new file mode 100644 index 0000000..77531c0 --- /dev/null +++ b/app/customer/hdl/hdl_enterprise.go @@ -0,0 +1,142 @@ +package hdl + +import ( + "applet/app/customer/lib/validate" + "applet/app/customer/md" + "applet/app/customer/svc" + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "applet/app/utils" + "github.com/gin-gonic/gin" +) + +func EnterpriseList(c *gin.Context) { + var req md.EnterpriseListReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + if req.Limit == 0 { + req.Limit = 10 + } + if req.Page == 0 { + req.Page = 10 + } + enterprises, total, err := svc.EnterpriseList(req) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + e.OutSuc(c, map[string]interface{}{ + "list": enterprises, + "total": total, + "kind": []map[string]interface{}{ + { + "name": enum.EnterprisePvd(enum.EnterprisePvdByCentralKitchenForSchool).String(), + "value": enum.EnterprisePvdByCentralKitchenForSchool, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdByCentralKitchenForFactory).String(), + "value": enum.EnterprisePvdByCentralKitchenForFactory, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdBySelfSupportForSchool).String(), + "value": enum.EnterprisePvdBySelfSupportForSchool, + }, + { + "name": enum.EnterprisePvd(enum.EnterprisePvdBySelfSupportForFactory).String(), + "value": enum.EnterprisePvdBySelfSupportForFactory, + }, + }, + }, nil) + return +} + +func EnterpriseInfo(c *gin.Context) { + enterpriseId := utils.StrToInt(c.DefaultQuery("enterprise_id", "")) + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(enterpriseId) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if enterprise == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应记录") + return + } + + var resp interface{} + //判断单位类型 + switch enterprise.Kind { + case enum.EnterprisePvdByCentralKitchenForSchool: + err, resp = svc.CentralKitchenForSchoolInfo(enterpriseId) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + break + } + + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + res := sysCfgDb.SysCfgFindWithDb(enum.AdministratorContactInfo, enum.CentralKitchenForSchoolReserveMealTime) + e.OutSuc(c, map[string]interface{}{ + "info": resp, + "set_center": res, + "enterprise_kind_list": []map[string]interface{}{ + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdByCentralKitchenForSchool), + "value": enum.EnterprisePvdByCentralKitchenForSchool, + }, + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdByCentralKitchenForFactory), + "value": enum.EnterprisePvdByCentralKitchenForFactory, + }, + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdBySelfSupportForSchool), + "value": enum.EnterprisePvdBySelfSupportForSchool, + }, + { + "name": enum.EnterprisePvd.String(enum.EnterprisePvdBySelfSupportForFactory), + "value": enum.EnterprisePvdBySelfSupportForFactory, + }, + }, + }, nil) + return +} + +func SchoolBelowGrade(c *gin.Context) { + enterpriseId := c.DefaultQuery("enterprise_id", "") + gradeDb := db.GradeDb{} + gradeDb.Set(utils.StrToInt(enterpriseId)) + gradeList, err := gradeDb.FindGrade() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": gradeList, + }, nil) + return +} + +func SchoolGradeBelowClass(c *gin.Context) { + gradeId := c.DefaultQuery("grade_id", "") + classDb := db.ClassDb{} + classDb.Set(utils.StrToInt(gradeId)) + classList, err := classDb.FindClass() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": classList, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_login.go b/app/customer/hdl/hdl_login.go new file mode 100644 index 0000000..8c00a4d --- /dev/null +++ b/app/customer/hdl/hdl_login.go @@ -0,0 +1,118 @@ +package hdl + +import ( + "applet/app/customer/lib/validate" + "applet/app/customer/md" + "applet/app/customer/svc" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + svc2 "applet/app/svc" + "applet/app/utils" + "fmt" + "github.com/gin-gonic/gin" + "time" +) + +func Login(c *gin.Context) { + var req md.LoginReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + userDb := db.UserDb{} + userDb.Set() + user, err := userDb.GetUserByPhone(req.Phone) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if user == nil { + e.OutErr(c, e.ERR_NO_DATA, "用户信息不存在") + return + } + ip := utils.GetIP(c.Request) + key := fmt.Sprintf(md.UserJwtTokenKey, ip, utils.AnyToString(user.Id)) + token, err := svc.HandleLoginToken(key, user) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, md.LoginResponse{ + Token: token, + }, nil) + return +} + +func Register(c *gin.Context) { + var req md.RegisterReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = svc2.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + + now := time.Now() + userDb := db.UserDb{} + userDb.Set() + user, err := userDb.GetUserByPhone(req.Phone) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if user == nil { + user = &model.User{ + UserId: req.UserId, + Nickname: req.Nickname, + Avatar: req.Avatar, + Phone: req.Phone, + Memo: "", + CreateAt: now, + UpdateAt: now, + } + insertAffected, err1 := userDb.UserInsert(user) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1) + return + } + if insertAffected <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "新增用户数据失败") + return + } + } else { + user.UserId = req.UserId + if req.Nickname != "" { + user.Nickname = req.Nickname + } + if req.Avatar != "" { + user.Avatar = req.Avatar + } + user.UpdateAt = now + updateAffected, err1 := userDb.UpdateUser(user, "user_id", "nickname", "avatar", "update_at") + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1) + return + } + if updateAffected <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "更新用户数据失败") + return + } + } + + ip := utils.GetIP(c.Request) + key := fmt.Sprintf(md.UserJwtTokenKey, ip, utils.AnyToString(user.Id)) + token, err := svc.HandleLoginToken(key, user) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, md.LoginResponse{ + Token: token, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_notice.go b/app/customer/hdl/hdl_notice.go new file mode 100644 index 0000000..6317b08 --- /dev/null +++ b/app/customer/hdl/hdl_notice.go @@ -0,0 +1,21 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +func NoticeList(c *gin.Context) { + noticeDb := db.NoticeDb{} + noticeDb.Set() + notices, err := noticeDb.FindNotice(0, 0) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "list": notices, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_pay.go b/app/customer/hdl/hdl_pay.go new file mode 100644 index 0000000..86f39bd --- /dev/null +++ b/app/customer/hdl/hdl_pay.go @@ -0,0 +1,107 @@ +package hdl + +import ( + "applet/app/customer/lib/validate" + "applet/app/customer/md" + "applet/app/customer/svc" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/enum" + "encoding/json" + "github.com/gin-gonic/gin" +) + +func BuyPackage(c *gin.Context) { + var req md.BuyPackageReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + outTradeNo, tradeNo, total, err := svc.BuyPackage(c, req) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, map[string]interface{}{ + "out_trade_no": outTradeNo, + "trade_no": tradeNo, + "total": total, + }, nil) + return +} + +func OrdState(c *gin.Context) { + outTradeNo := c.DefaultQuery("out_trade_no", "") + centralKitchenForSchoolPackageOrd := db.CentralKitchenForSchoolPackageOrd{} + centralKitchenForSchoolPackageOrd.Set(outTradeNo) + ord, err := centralKitchenForSchoolPackageOrd.GetCentralKitchenForSchoolPackageOrd() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if ord == nil { + e.OutErr(c, e.ERR_NO_DATA, "未查询到对应订单记录") + return + } + if ord.State == enum.CentralKitchenForSchoolPackageOrdStateForWait { + //处于待支付状态,请求支付宝同步订单状态 + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + sysCfg, err1 := sysCfgDb.SysCfgGetOne(enum.JsapiPayAppAutToken) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + + err2, result := svc.CurlAlipayTradeQuery(md.CurlAlipayTradeQueryReq{ + OutTradeNo: outTradeNo, + AppAuthToken: sysCfg.Val, + }) + if err2 != nil { + e.OutErr(c, e.ERR, err2.Error()) + return + } + if result.TradeStatus == "TRADE_CLOSED" { + ord.State = enum.CentralKitchenForSchoolPackageOrdStateForFail + } + + if result.TradeStatus == "TRADE_SUCCESS" { + ord.State = enum.CentralKitchenForSchoolPackageOrdStateForSuccess + ord.OrdState = enum.CentralKitchenForSchoolPackageOrdOrdStateForSuccess + + //TODO::将预留数据插入到 `central_kitchen_for_school_user_with_day` + var data []*model.CentralKitchenForSchoolUserWithDay + err4 := json.Unmarshal([]byte(ord.WithDayData), &data) + if err4 != nil { + e.OutErr(c, e.ERR, err4.Error()) + return + } + for _, v := range data { + v.OrdNo = outTradeNo + } + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + _, err5 := centralKitchenForSchoolUserWithDayDb.BatchAddCentralKitchenForSchoolUserWithDays(data) + if err != nil { + e.OutErr(c, e.ERR, err5.Error()) + return + } + } + + _, err3 := centralKitchenForSchoolPackageOrd.CentralKitchenForSchoolPackageOrdUpdate(ord, "state", "ord_state") + if err3 != nil { + e.OutErr(c, e.ERR_DB_ORM, err3.Error()) + return + } + } + + e.OutSuc(c, map[string]interface{}{ + "sate": ord.State, + "sate_zh": enum.CentralKitchenForSchoolPackageOrdState.String(enum.CentralKitchenForSchoolPackageOrdState(ord.State)), + }, nil) + return +} diff --git a/app/customer/hdl/hdl_sys_cfg.go b/app/customer/hdl/hdl_sys_cfg.go new file mode 100644 index 0000000..fcbff5b --- /dev/null +++ b/app/customer/hdl/hdl_sys_cfg.go @@ -0,0 +1,17 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" +) + +func GetSysCfg(c *gin.Context) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + res := sysCfgDb.SysCfgFindWithDb(enum.AppName, enum.OpenAppletAppid, enum.OpenAppletPublicKey, enum.OpenAppletAppPublicKey, enum.OpenAppletAppPrivateKey, enum.OpenAppletAesKey, + enum.FileBucket, enum.FileExt, enum.FileAccessKey, enum.FileUserUploadMaxSize, enum.FileSecretKey, enum.FileBucketScheme, enum.FileBucketRegion, enum.FileBucketHost) + e.OutSuc(c, res, nil) + return +} diff --git a/app/customer/hdl/hdl_user.go b/app/customer/hdl/hdl_user.go new file mode 100644 index 0000000..5035c10 --- /dev/null +++ b/app/customer/hdl/hdl_user.go @@ -0,0 +1,61 @@ +package hdl + +import ( + "applet/app/customer/svc" + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" +) + +func UserInfo(c *gin.Context) { + //1、获取用户信息 + userInfo := svc.GetUser(c) + + //2、获取用户身份信息 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(userInfo.Id) + identity, err := userIdentityDb.FindUserIdentity() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + var identityList []map[string]interface{} + for _, v := range *identity { + identityList = append(identityList, map[string]interface{}{ + "identity": v.UserIdentity, + "enterprise": v.Enterprise, + }) + } + e.OutSuc(c, map[string]interface{}{ + "user_info": userInfo, + "user_identity": identityList, + "user_identity_kind_list": []map[string]interface{}{ + { + "name": enum.UserIdentity.String(enum.UserIdentityForCentralKitchenForStudent), + "value": enum.UserIdentityForCentralKitchenForStudent, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForCentralKitchenForTeacher), + "value": enum.UserIdentityForCentralKitchenForTeacher, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForCentralKitchenForWorker), + "value": enum.UserIdentityForCentralKitchenForWorker, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForSelfSupportForStudent), + "value": enum.UserIdentityForSelfSupportForStudent, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForSelfSupportForTeacher), + "value": enum.UserIdentityForSelfSupportForTeacher, + }, + { + "name": enum.UserIdentity.String(enum.UserIdentityForSelfSupportForWorker), + "value": enum.UserIdentityForSelfSupportForWorker, + }, + }, + }, nil) + return +} diff --git a/app/customer/hdl/hdl_user_identity.go b/app/customer/hdl/hdl_user_identity.go new file mode 100644 index 0000000..6aec16e --- /dev/null +++ b/app/customer/hdl/hdl_user_identity.go @@ -0,0 +1,327 @@ +package hdl + +import ( + "applet/app/customer/lib/validate" + "applet/app/customer/md" + "applet/app/customer/svc" + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/enum" + "github.com/gin-gonic/gin" + "time" +) + +func SaveCentralKitchenForSchoolUserIdentity(c *gin.Context) { + var req md.SaveCentralKitchenForSchoolUserIdentityReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + user := svc.GetUser(c) + var identity = enum.UserIdentityForCentralKitchenForStudent + if req.IsTeacher { + identity = enum.UserIdentityForCentralKitchenForTeacher + } + + //1、判断当前身份是否已绑定 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(user.Id) + isHasUserIdentity, err := userIdentityDb.UserIdentityExist(req.EnterpriseId, req.IdNo) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if isHasUserIdentity != nil { + e.OutErr(c, e.ERR, "当前身份信息已被绑定使用") + return + } + + //2、新增身份信息 + now := time.Time{} + userIdentity := &model.UserIdentity{ + Uid: user.Id, + Name: req.Name, + IdNo: req.IdNo, + Kind: enum.UserIdentityKindForCommon, + Identity: identity, + EnterpriseId: req.EnterpriseId, + State: enum.UserIdentityStateForNormal, + Memo: "", + CreateAt: now, + UpdateAt: now, + } + insertAffected, err := userIdentityDb.UserIdentityInsert(userIdentity) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if insertAffected <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "新增身份数据失败") + return + } + + //3、新增 class_with_user 记录 + if !req.IsTeacher { + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + _, err = classWithUserDb.ClassWithUserInsert(&model.ClassWithUser{ + UserIdentityId: insertAffected, + ClassId: req.ClassId, + CreateAt: now, + UpdateAt: now, + }) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + } + e.OutSuc(c, "success", nil) + return +} + +func SaveSelfSupportForSchoolUserIdentity(c *gin.Context) { + var req md.SaveSelfSupportForSchoolUserIdentityReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + user := svc.GetUser(c) + now := time.Time{} + var identity = enum.UserIdentityForSelfSupportForStudent + var kind = enum.UserIdentityKindForCommon + if req.Kind == enum.UserIdentityKindForWorker { + kind = enum.UserIdentityKindForWorker + identity = enum.UserIdentityForSelfSupportForWorker + } else { + if req.IsTeacher { + identity = enum.UserIdentityForCentralKitchenForTeacher + } + } + + //1、判断当前身份是否已绑定 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(user.Id) + isHasUserIdentity, err := userIdentityDb.UserIdentityExist(req.EnterpriseId, req.IdNo) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + if isHasUserIdentity == nil { + if identity != enum.UserIdentityForSelfSupportForStudent { + e.OutErr(c, e.ERR_NO_DATA, "当前身份信息不存在") + return + } + //2、新增身份信息 + userIdentity := &model.UserIdentity{ + Uid: user.Id, + Name: req.Name, + IdNo: req.IdNo, + Kind: kind, + Identity: identity, + EnterpriseId: req.EnterpriseId, + State: enum.UserIdentityStateForNormal, + Memo: "", + CreateAt: now, + UpdateAt: now, + } + insertAffected, err := userIdentityDb.UserIdentityInsert(userIdentity) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if insertAffected <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "新增身份数据失败") + return + } + //3、新增 class_with_user 记录 + if !req.IsTeacher { + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + _, err = classWithUserDb.ClassWithUserInsert(&model.ClassWithUser{ + UserIdentityId: insertAffected, + ClassId: req.ClassId, + CreateAt: now, + UpdateAt: now, + }) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + } + } else { + if isHasUserIdentity.Uid != 0 { + e.OutErr(c, e.ERR, "当前身份信息已被绑定使用") + return + } + //4、修改身份信息 + isHasUserIdentity.Uid = user.Id + isHasUserIdentity.Name = req.Name + _, err = userIdentityDb.UserIdentityUpdate(isHasUserIdentity.Id, isHasUserIdentity, "uid", "name") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + if identity == enum.UserIdentityForSelfSupportForStudent { + //5、修改 class_with_user 记录 + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + classWithUser, err1 := classWithUserDb.GetClassWithUserByUserIdentityId(isHasUserIdentity.Id) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + if classWithUser == nil { + _, err2 := classWithUserDb.ClassWithUserInsert(&model.ClassWithUser{ + UserIdentityId: isHasUserIdentity.Id, + ClassId: req.ClassId, + CreateAt: now, + UpdateAt: now, + }) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } else { + classWithUser.ClassId = req.ClassId + _, err2 := classWithUserDb.ClassWithUserUpdateByUserIdentity(isHasUserIdentity.Id, classWithUser, "class_id") + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + } + } + e.OutSuc(c, "success", nil) + return +} + +func CentralKitchenForSchoolMyReserve(c *gin.Context) { + //user := svc.GetUser(c) + var req md.SaveSelfSupportForSchoolUserIdentityReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + user := svc.GetUser(c) + now := time.Time{} + var identity = enum.UserIdentityForSelfSupportForStudent + var kind = enum.UserIdentityKindForCommon + if req.Kind == enum.UserIdentityKindForWorker { + kind = enum.UserIdentityKindForWorker + identity = enum.UserIdentityForSelfSupportForWorker + } else { + if req.IsTeacher { + identity = enum.UserIdentityForCentralKitchenForTeacher + } + } + + //1、判断当前身份是否已绑定 + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(user.Id) + isHasUserIdentity, err := userIdentityDb.UserIdentityExist(req.EnterpriseId, req.IdNo) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + if isHasUserIdentity == nil { + if identity != enum.UserIdentityForSelfSupportForStudent { + e.OutErr(c, e.ERR_NO_DATA, "当前身份信息不存在") + return + } + //2、新增身份信息 + userIdentity := &model.UserIdentity{ + Uid: user.Id, + Name: req.Name, + IdNo: req.IdNo, + Kind: kind, + Identity: identity, + EnterpriseId: req.EnterpriseId, + State: enum.UserIdentityStateForNormal, + Memo: "", + CreateAt: now, + UpdateAt: now, + } + insertAffected, err := userIdentityDb.UserIdentityInsert(userIdentity) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if insertAffected <= 0 { + e.OutErr(c, e.ERR_DB_ORM, "新增身份数据失败") + return + } + //3、新增 class_with_user 记录 + if !req.IsTeacher { + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + _, err = classWithUserDb.ClassWithUserInsert(&model.ClassWithUser{ + UserIdentityId: insertAffected, + ClassId: req.ClassId, + CreateAt: now, + UpdateAt: now, + }) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + } + } else { + if isHasUserIdentity.Uid != 0 { + e.OutErr(c, e.ERR, "当前身份信息已被绑定使用") + return + } + //4、修改身份信息 + isHasUserIdentity.Uid = user.Id + isHasUserIdentity.Name = req.Name + _, err = userIdentityDb.UserIdentityUpdate(isHasUserIdentity.Id, isHasUserIdentity, "uid", "name") + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + if identity == enum.UserIdentityForSelfSupportForStudent { + //5、修改 class_with_user 记录 + classWithUserDb := db.ClassWithUserDb{} + classWithUserDb.Set() + classWithUser, err1 := classWithUserDb.GetClassWithUserByUserIdentityId(isHasUserIdentity.Id) + if err1 != nil { + e.OutErr(c, e.ERR_DB_ORM, err1.Error()) + return + } + if classWithUser == nil { + _, err2 := classWithUserDb.ClassWithUserInsert(&model.ClassWithUser{ + UserIdentityId: isHasUserIdentity.Id, + ClassId: req.ClassId, + CreateAt: now, + UpdateAt: now, + }) + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } else { + classWithUser.ClassId = req.ClassId + _, err2 := classWithUserDb.ClassWithUserUpdateByUserIdentity(isHasUserIdentity.Id, classWithUser, "class_id") + if err2 != nil { + e.OutErr(c, e.ERR_DB_ORM, err2.Error()) + return + } + } + } + } + e.OutSuc(c, "success", nil) + return +} diff --git a/app/customer/hdl/self_support_for_school/hdl_faceCollection.go b/app/customer/hdl/self_support_for_school/hdl_faceCollection.go new file mode 100644 index 0000000..ee1faec --- /dev/null +++ b/app/customer/hdl/self_support_for_school/hdl_faceCollection.go @@ -0,0 +1,169 @@ +package hdl + +import ( + "applet/app/customer/md" + "applet/app/customer/svc" + "applet/app/db" + "applet/app/e" + "applet/app/utils" + "github.com/gin-gonic/gin" +) + +func EducateSceneTokenQuery(c *gin.Context) { + userIdentityId := c.DefaultQuery("user_identity_id", "") + userToken := c.DefaultQuery("user_token", "") + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + identity, err := userIdentityDb.GetUserIdentity(utils.StrToInt(userIdentityId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + selfSupportForSchoolInfoDb := db.SelfSupportForSchoolInfoDb{} + selfSupportForSchoolInfoDb.Set(identity.EnterpriseId) + selfSupportForSchoolInfo, err := selfSupportForSchoolInfoDb.GetSelfSupportForSchoolInfo() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if selfSupportForSchoolInfo == nil { + e.OutErr(c, e.ERR_NO_DATA, "当前学校暂未完成《一脸通行入驻》") + return + } + //请求支付宝 alipay.commerce.educate.scene.token.query(查询刷脸用户开通详细信息) + err, info := svc.CurlEducateSceneTokenQuery(md.CurlEducateSceneTokenReq{ + CertNo: identity.IdNo, + StudentName: identity.Name, + OutUserId: utils.IntToStr(identity.Id), + SchoolStdCode: selfSupportForSchoolInfo.SchoolStdCode, + }, identity.Id, userToken) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, info, nil) + return +} + +func EducateSceneTokenCreateForApplet(c *gin.Context) { + userIdentityId := c.DefaultQuery("user_identity_id", "") + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + identity, err := userIdentityDb.GetUserIdentity(utils.StrToInt(userIdentityId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + selfSupportForSchoolInfoDb := db.SelfSupportForSchoolInfoDb{} + selfSupportForSchoolInfoDb.Set(identity.EnterpriseId) + selfSupportForSchoolInfo, err := selfSupportForSchoolInfoDb.GetSelfSupportForSchoolInfo() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if selfSupportForSchoolInfo == nil { + e.OutErr(c, e.ERR_NO_DATA, "当前学校暂未完成《一脸通行入驻》") + return + } + //请求支付宝 alipay.commerce.educate.scene.token.query(查询刷脸用户开通详细信息) + err, info := svc.CurlEducateSceneTokenCreateForApplet(md.CurlEducateSceneTokenReq{ + CertNo: identity.IdNo, + StudentName: identity.Name, + OutUserId: utils.IntToStr(identity.Id), + SchoolStdCode: selfSupportForSchoolInfo.SchoolStdCode, + }) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, info, nil) + return +} + +func EducateFacepayApply(c *gin.Context) { + faceOpenId := c.DefaultQuery("face_open_id", "") + userIdentityId := c.DefaultQuery("user_identity_id", "") + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + identity, err := userIdentityDb.GetUserIdentity(utils.StrToInt(userIdentityId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + selfSupportForSchoolInfoDb := db.SelfSupportForSchoolInfoDb{} + selfSupportForSchoolInfoDb.Set(identity.EnterpriseId) + selfSupportForSchoolInfo, err := selfSupportForSchoolInfoDb.GetSelfSupportForSchoolInfo() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if selfSupportForSchoolInfo == nil { + e.OutErr(c, e.ERR_NO_DATA, "当前学校暂未完成《一脸通行入驻》") + return + } + + //请求支付宝 alipay.commerce.educate.scene.token.query(查询刷脸用户开通详细信息) + selfSupportForUserFaceInfoDb := db.SelfSupportForUserFaceInfoDb{} + selfSupportForUserFaceInfoDb.Set(utils.StrToInt(userIdentityId)) + faceInfo, err := selfSupportForUserFaceInfoDb.GetSelfSupportForUserFaceInfo() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if faceInfo == nil { + e.OutErr(c, e.ERR_NO_DATA, "当前用户身份未查询到一脸通行相关数据") + return + } + err, info := svc.CurlEducateFacepayApply(md.CurlEducateFacepayApplyReq{ + FaceUid: faceInfo.UserId, + FaceOpenId: faceOpenId, + SchoolStdCode: selfSupportForSchoolInfo.SchoolStdCode, + }) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, info, nil) + return +} + +func EducateSceneTokenCreateForConcentratedCollectApplet(c *gin.Context) { + user := svc.GetUser(c) + userIdentityId := c.DefaultQuery("user_identity_id", "") + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + identity, err := userIdentityDb.GetUserIdentity(utils.StrToInt(userIdentityId)) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + selfSupportForSchoolInfoDb := db.SelfSupportForSchoolInfoDb{} + selfSupportForSchoolInfoDb.Set(identity.EnterpriseId) + selfSupportForSchoolInfo, err := selfSupportForSchoolInfoDb.GetSelfSupportForSchoolInfo() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + if selfSupportForSchoolInfo == nil { + e.OutErr(c, e.ERR_NO_DATA, "当前学校暂未完成《一脸通行入驻》") + return + } + //请求支付宝 alipay.commerce.educate.scene.token.query(查询刷脸用户开通详细信息) + err, info := svc.CurlEducateSceneTokenCreateForConcentratedCollectApplet(md.CurlEducateSceneTokenReq{ + CertNo: identity.IdNo, + StudentName: identity.Name, + OutUserId: utils.IntToStr(identity.Id), + SchoolStdCode: selfSupportForSchoolInfo.SchoolStdCode, + OperatorUserId: user.UserId, + }) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, info, nil) + return +} diff --git a/app/customer/lib/auth/auth.go b/app/customer/lib/auth/auth.go new file mode 100644 index 0000000..3dad178 --- /dev/null +++ b/app/customer/lib/auth/auth.go @@ -0,0 +1,39 @@ +package auth + +import ( + "errors" + "github.com/dgrijalva/jwt-go" + "time" +) + +// GenToken 生成JWT +func GenToken(userId int, username string) (string, error) { + // 创建一个我们自己的声明 + c := JWTUser{ + UserId: userId, + Username: username, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 + Issuer: "smart_canteen", // 签发人 + }, + } + // 使用指定的签名方法创建签名对象 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + // 使用指定的secret签名并获得完整的编码后的字符串token + return token.SignedString(Secret) +} + +// ParseToken 解析JWT +func ParseToken(tokenString string) (*JWTUser, error) { + // 解析token + token, err := jwt.ParseWithClaims(tokenString, &JWTUser{}, func(token *jwt.Token) (i interface{}, err error) { + return Secret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*JWTUser); ok && token.Valid { // 校验token + return claims, nil + } + return nil, errors.New("invalid token") +} diff --git a/app/customer/lib/auth/base.go b/app/customer/lib/auth/base.go new file mode 100644 index 0000000..ecf66c3 --- /dev/null +++ b/app/customer/lib/auth/base.go @@ -0,0 +1,19 @@ +package auth + +import ( + "time" + + "github.com/dgrijalva/jwt-go" +) + +// TokenExpireDuration is jwt 过期时间 +const TokenExpireDuration = time.Hour * 4380 + +var Secret = []byte("smart_canteen_customer") + +// JWTUser 如果想要保存更多信息,都可以添加到这个结构体中 +type JWTUser struct { + UserId int `json:"user_id"` + Username string `json:"username"` + jwt.StandardClaims +} diff --git a/app/customer/lib/validate/validate_comm.go b/app/customer/lib/validate/validate_comm.go new file mode 100644 index 0000000..9305d9e --- /dev/null +++ b/app/customer/lib/validate/validate_comm.go @@ -0,0 +1,33 @@ +package validate + +import ( + "applet/app/e" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +func HandleValidateErr(err error) error { + switch err.(type) { + case *json.UnmarshalTypeError: + return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误") + case validator.ValidationErrors: + errs := err.(validator.ValidationErrors) + transMsgMap := errs.Translate(utils.ValidatorTrans) // utils.ValidatorTrans \app\utils\validator_err_trans.go::ValidatorTransInit初始化获得 + transMsgOne := transMsgMap[GetOneKeyOfMapString(transMsgMap)] + return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne) + default: + _ = logx.Error(err) + return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err)) + } +} + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} diff --git a/app/customer/md/md_app_redis_key.go b/app/customer/md/md_app_redis_key.go new file mode 100644 index 0000000..5b4a0e9 --- /dev/null +++ b/app/customer/md/md_app_redis_key.go @@ -0,0 +1,7 @@ +package md + +// 缓存key统一管理 +const ( + UserJwtTokenKey = "%s:smart_canteen_user_jwt_token:%s" // jwt, 占位符:ip, user:id + JwtTokenCacheTime = 3600 * 24 * 1 +) diff --git a/app/customer/md/md_curl_smart_pay.go b/app/customer/md/md_curl_smart_pay.go new file mode 100644 index 0000000..738befc --- /dev/null +++ b/app/customer/md/md_curl_smart_pay.go @@ -0,0 +1,76 @@ +package md + +type CurlAesDecrypt struct { + Content string `json:"content" binding:"required" label:"解密内容"` +} + +type SystemOauthTokenReq struct { + Code string `json:"code" binding:"required" label:"授权码"` +} + +type CurlAlipayTradeCreateReq struct { + Config struct { + PayAliAppId string `json:"pay_ali_app_id" binding:"required" label:"支付宝开放平台-第三方应用-appid"` + PayAliPrivateKey string `json:"pay_ali_private_key" binding:"required" label:"支付宝开放平台-第三方应用-接口加签-应用私钥"` + PayAliPublicKey string `json:"pay_ali_public_key" binding:"required" label:"支付宝开放平台-第三方应用-接口加签-支付宝公钥"` + } `json:"config" binding:"required" label:"配置信息"` + BuyerId string `json:"buyer_id" binding:"required" label:"买家支付宝用户ID"` + TotalAmount string `json:"total_amount" binding:"required" label:"订单总金额"` + OutTradeNo string `json:"out_trade_no" binding:"required" label:"商户订单号"` + Subject string `json:"subject" binding:"required" label:"订单标题"` + AppAuthToken string `json:"app_auth_token" binding:"required" label:"应用授权token"` +} + +type CurlAlipayTradeCreateResp struct { + Code string `json:"code" label:"响应码"` + Msg string `json:"msg" label:"响应消息"` + OutTradeNo string `json:"out_trade_no" label:"商户订单号"` + TradeNo string `json:"trade_no" label:"支付宝交易号"` +} + +type CurlAlipayTradeQueryReq struct { + OutTradeNo string `json:"out_trade_no" label:"商户订单号"` + AppAuthToken string `json:"app_auth_token" binding:"required" label:"应用授权token"` +} + +type CurlAlipayTradeQueryResp struct { + Code string `json:"code" label:"响应码"` + Msg string `json:"msg" label:"响应消息"` + OutTradeNo string `json:"out_trade_no" label:"商户订单号"` + TradeNo string `json:"trade_no" label:"支付宝交易号"` + TradeStatus string `json:"trade_status" label:"交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款)"` +} + +type CurlAlipayTradeRefundReq struct { + OutTradeNo string `json:"out_trade_no" label:"商户订单号"` + RefundAmount string `json:"refund_amount" label:"退款金额"` + RefundReason string `json:"refund_reason" label:"退款原因"` + OutRequestNo string `json:"out_request_no" label:"退款订单号"` + AppAuthToken string `json:"app_auth_token" binding:"required" label:"应用授权token"` +} + +type CurlEducateSceneTokenReq struct { + CertNo string `json:"cert_no" label:"身份证号码"` + StudentName string `json:"student_name" label:"姓名"` + OutUserId string `json:"out_user_id" label:"服务商内部用户唯一ID,用于与支付宝用户 ID 进行关联"` + SchoolStdCode string `json:"school_std_code" label:"学校外标"` + OperatorUserId string `json:"operator_user_id" label:"采集人员的支付宝会员标识。(当sub_code为SCHOOL_PAYMENT_REMOTE_OPEN_ACCOUNT时,当前参数必选)"` +} + +type CurlEducateFacepayApplyReq struct { + FaceUid string `json:"face_uid" label:"刷脸用户id"` + FaceOpenId string `json:"face_open_id" label:"刷脸用户openId"` + SchoolStdCode string `json:"school_std_code" label:"学校外标"` +} + +type CurlAlipayPlanetEcocampusApiRosterSignUpInfoReq struct { + FaceUid string `json:"face_uid" label:"学生刷脸编号(不可修改)"` + ParentUid string `json:"parent_uid" label:"家长uid(可修改)"` + ParentLogonId string `json:"parent_logon_id" label:"家长支付宝账号脱敏信息(可修改)"` + RosterName string `json:"roster_name" label:"学生姓名(可修改)"` + OutRosterCode string `json:"out_roster_code" label:"外部花名册编号(不可修改)"` + SchoolCode string `json:"school_code" label:"学校内标(不可修改)"` + SchoolName string `json:"school_name" label:"学校名称(不可修改)"` + ScanFacePayStatus string `json:"scan_face_pay_status" label:"刷脸支付开通状态(可修改) 开通状态:ON,关闭状态:OFF(新增时,不传值默认OFF)"` + FaceOpenStatus string `json:"face_open_status" label:"刷脸开通状态(可修改)"` +} diff --git a/app/customer/md/md_enterprise.go b/app/customer/md/md_enterprise.go new file mode 100644 index 0000000..1d69558 --- /dev/null +++ b/app/customer/md/md_enterprise.go @@ -0,0 +1,43 @@ +package md + +type EnterpriseListReq struct { + Limit int `json:"limit"` + Page int `json:"page" ` + Name string `json:"name" ` +} + +type CentralKitchenForSchoolInfoResp struct { + Name string `json:"name" label:"名称"` + Memo string `json:"memo" label:"备注"` + Kind int32 `json:"kind" label:"种类(1:央厨-学校 2:央厨-工厂 3:自营-学校 4:自营-工厂)"` + State int32 `json:"state" label:"状态(1:正常 2:冻结)"` + IsOpenTeacherReportMeal int `json:"is_open_teacher_report_meal" label:"教师报餐(1:开启 2:关闭)"` + IsOpenReportMealForDay int `json:"is_open_report_meal_for_day" label:"开启按天报餐(1:开启 2:关闭)"` + IsOpenReportMealForMonth int `json:"is_open_report_meal_for_month" label:"开启按月报餐(1:开启 2:关闭)"` + IsOpenReportMealForSemester int `json:"is_open_report_meal_for_semester" label:"开启按学期报餐(1:开启 2:关闭)"` + IsOpenBreakfast int `json:"is_open_breakfast" label:"是否开启早餐(1:开启 0:关闭)"` + IsOpenLunch int `json:"is_open_lunch" label:"是否开启午餐(1:开启 0:关闭)"` + IsOpenDinner int `json:"is_open_dinner" label:"是否开启晚餐(1:开启 0:关闭)"` + BreakfastUnitPrice string `json:"breakfast_unit_price" label:"早餐-单价"` + BreakfastUnitPriceForTeacher string `json:"breakfast_unit_price_for_teacher" label:"教师-早餐-单价"` + LunchUnitPrice string `json:"lunch_unit_price" label:"午餐-单价"` + LunchUnitPriceForTeacher string `json:"lunch_unit_price_for_teacher" label:"教师-午餐-单价"` + DinnerUnitPrice string `json:"dinner_unit_price" label:"晚餐-单价"` + DinnerUnitPriceForTeacher string `json:"dinner_unit_price_for_teacher" label:"教师-晚餐-单价"` +} + +type FindCentralKitchenForSchoolPackageReq struct { + PackageId int `json:"package_id" label:"套餐ID"` + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Year string `json:"year" binding:"required" label:"年份"` + Month string `json:"month" binding:"required" label:"月份"` + StartDate string `json:"start_date" binding:"required" label:"开始时间"` + EndDate string `json:"end_date" binding:"required" label:"截止时间"` + DateList []struct { + Date string `json:"date"` + IsOpenBreakfast int32 `json:"is_open_breakfast"` + IsOpenLunch int32 `json:"is_open_lunch"` + IsOpenDinner int32 `json:"is_open_dinner"` + IsOpenReplenish int32 `json:"is_open_replenish"` + } `json:"date_list" binding:"required" label:"日期"` +} diff --git a/app/customer/md/md_login.go b/app/customer/md/md_login.go new file mode 100644 index 0000000..40cba9e --- /dev/null +++ b/app/customer/md/md_login.go @@ -0,0 +1,16 @@ +package md + +type LoginReq struct { + Phone string `json:"phone" binding:"required" label:"登录手机号"` +} + +type LoginResponse struct { + Token string `json:"token"` +} + +type RegisterReq struct { + UserId string `json:"user_id" binding:"required" label:"支付宝用户的唯一userId"` + Nickname string `json:"nickname" label:"支付宝昵称"` + Avatar string `json:"avatar" label:"支付宝头像"` + Phone string `json:"phone" binding:"required" label:"手机号"` +} diff --git a/app/customer/md/md_order.go b/app/customer/md/md_order.go new file mode 100644 index 0000000..86313cf --- /dev/null +++ b/app/customer/md/md_order.go @@ -0,0 +1,31 @@ +package md + +type CentralKitchenForSchoolOrderListReq struct { + UserIdentityId int `json:"user_identity_id" label:"用户身份id"` + Limit int `json:"limit"` + Page int `json:"page" ` + OrdState int `json:"ord_state" ` +} + +type CentralKitchenForSchoolOrderRefundReq struct { + OutTradeNo string `json:"out_trade_no" label:"订单号"` + Id int `json:"id" binding:"required" label:"就餐记录id"` +} + +type CentralKitchenForSchoolOrderRefundListReq struct { + UserIdentityId int `json:"user_identity_id" label:"用户身份id"` + Limit int `json:"limit"` + Page int `json:"page" ` + State int `json:"state" ` +} + +type CentralKitchenForSchoolOrderRefundListResp struct { + OutTradeNo string `json:"out_trade_no" label:"订单号"` + OutRequestNo string `json:"out_request_no" label:"退款请求号"` + Kind int `json:"kind" label:"预定类型"` + CreateAt string `json:"create_at" label:"退款时间"` + Date string `json:"date" label:"预定时间"` + State int `json:"state" label:"退款状态"` + Memo string `json:"memo" label:"备注"` + Amount string `json:"amount" label:"退款金额"` +} diff --git a/app/customer/md/md_pay.go b/app/customer/md/md_pay.go new file mode 100644 index 0000000..ca1b114 --- /dev/null +++ b/app/customer/md/md_pay.go @@ -0,0 +1,17 @@ +package md + +type BuyPackageReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + UserIdentityId int `json:"user_identity_id" binding:"required" label:"用户身份id"` + PackageId int `json:"package_id" label:"套餐ID"` + Kind int `json:"kind" binding:"required" label:"购买类型(1:按学期购买 2:按月购买 3:按天购买)"` + IsBuyBreakfast int `json:"is_buy_breakfast" label:"是否购买早餐(1:是 2:否)"` + IsBuyLunch int `json:"is_buy_lunch" label:"是否购买午餐(1:是 2:否)"` + IsBuyDinner int `json:"is_buy_dinner" label:"是否购买晚餐(1:是 2:否)"` + WithDays []struct { + Date string `json:"date" label:"日期"` + IsBuyBreakfast int `json:"is_buy_breakfast" label:"是否购买早餐(1:是 2:否)"` + IsBuyLunch int `json:"is_buy_lunch" label:"是否购买午餐(1:是 2:否)"` + IsBuyDinner int `json:"is_buy_dinner" label:"是否购买晚餐(1:是 2:否)"` + } `json:"with_days" label:"包含天数"` +} diff --git a/app/customer/md/md_qrcode.go b/app/customer/md/md_qrcode.go new file mode 100644 index 0000000..170893e --- /dev/null +++ b/app/customer/md/md_qrcode.go @@ -0,0 +1,31 @@ +package md + +const ( + QrcodeTotalNums = 100000 +) + +type QrcodeBatchListReq struct { + Page int `json:"page"` + Limit int `json:"limit"` +} + +type QrcodeBatchAddReq struct { + Name string `json:"name"` + ExpireDate string `json:"expire_date"` + List []QrcodeBatchAddReqList `json:"list"` + Memo string `json:"memo"` +} + +type QrcodeBatchAddReqList struct { + Num int `json:"num"` + Amount string `json:"amount"` +} + +type QrcodeBatchAddReqListDetail struct { + Num int `json:"num"` + WaitUseNum int `json:"wait_use_num"` + UsedNum int `json:"used_num"` + ExpiredNum int `json:"expired_num"` + CancelNum int `json:"cancel_num"` + Amount string `json:"amount"` +} diff --git a/app/customer/md/md_sys_cfg.go b/app/customer/md/md_sys_cfg.go new file mode 100644 index 0000000..ff0ca45 --- /dev/null +++ b/app/customer/md/md_sys_cfg.go @@ -0,0 +1,9 @@ +package md + +type SetSysCfgReq struct { + WxMchApiV3Key string `json:"wx_mch_api_v3_key" label:"微信商户APIv3密钥"` + WxMchCertificateSerialNumber string `json:"wx_mch_certificate_serial_number" label:"微信商户证书序列号"` + WxMchId string `json:"wx_mch_id" label:"微信商户号"` + WxOfficialAccountAppId string `json:"wx_official_account_app_id" label:"微信公众号appId"` + WxOfficialAccountAppSecret string `json:"wx_official_account_app_secret" label:"微信公众号appSecret"` +} diff --git a/app/customer/md/md_user_identity.go b/app/customer/md/md_user_identity.go new file mode 100644 index 0000000..9fe2ee9 --- /dev/null +++ b/app/customer/md/md_user_identity.go @@ -0,0 +1,20 @@ +package md + +type SaveCentralKitchenForSchoolUserIdentityReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + IsTeacher bool `json:"is_teacher" label:"是否教师"` + Name string `json:"name" binding:"required" label:"姓名"` + IdNo string `json:"id_no" binding:"required" label:"身份证号码"` + GradeId int `json:"grade_id" label:"年级id"` + ClassId int `json:"class_id" label:"班级id"` +} + +type SaveSelfSupportForSchoolUserIdentityReq struct { + EnterpriseId int `json:"enterprise_id" binding:"required" label:"企业id"` + Kind int `json:"kind" binding:"required" label:"类型(1:普通用户 2:工作人员)"` + IsTeacher bool `json:"is_teacher" label:"是否教师"` + Name string `json:"name" binding:"required" label:"姓名"` + IdNo string `json:"id_no" binding:"required" label:"身份证号码"` + GradeId int `json:"grade_id" label:"年级id"` + ClassId int `json:"class_id" label:"班级id"` +} diff --git a/app/customer/md/md_wx_official_account.go b/app/customer/md/md_wx_official_account.go new file mode 100644 index 0000000..fdf8ecf --- /dev/null +++ b/app/customer/md/md_wx_official_account.go @@ -0,0 +1,14 @@ +package md + +const WxOfficialAccountRequestBaseUrl = "https://api.weixin.qq.com/" + +type CreateTokenResp struct { + AccessToken string `json:"access_token"` + ExpiresIn int64 `json:"expires_in"` +} + +type CreateQrcodeResp struct { + Ticket string `json:"ticket"` + ExpireSeconds int64 `json:"expire_seconds"` + Url string `json:"url"` +} diff --git a/app/customer/mw/mw_cors.go b/app/customer/mw/mw_cors.go new file mode 100644 index 0000000..3433553 --- /dev/null +++ b/app/customer/mw/mw_cors.go @@ -0,0 +1,29 @@ +package mw + +import ( + "github.com/gin-gonic/gin" +) + +// cors跨域 +func Cors(c *gin.Context) { + // 放行所有OPTIONS方法 + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + origin := c.Request.Header.Get("Origin") // 请求头部 + if origin != "" { + c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问来源域 + c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求 + // header的类型 + c.Header("Access-Control-Allow-Headers", "Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma,X-Mx-ReqToken") + // 允许跨域设置,可以返回其他子段 + // 跨域关键设置 让浏览器可以解析 + c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 + c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true + c.Set("Content-Type", "Application/json") // 设置返回格式是json + } + c.Next() +} diff --git a/app/customer/mw/mw_customer_auth.go b/app/customer/mw/mw_customer_auth.go new file mode 100644 index 0000000..75f54e0 --- /dev/null +++ b/app/customer/mw/mw_customer_auth.go @@ -0,0 +1,26 @@ +package mw + +import ( + "applet/app/customer/svc" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +// 检查权限, 签名等等 +func Auth(c *gin.Context) { + user, err := svc.CheckUser(c) + if err != nil { + switch err.(type) { + case e.E: + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + default: + e.OutErr(c, e.ERR_UNAUTHORIZED, err.Error()) + return + } + } + // 将当前请求的username信息保存到请求的上下文c上 + c.Set("user", user) + c.Next() +} diff --git a/app/customer/mw/mw_limiter.go b/app/customer/mw/mw_limiter.go new file mode 100644 index 0000000..4eb5299 --- /dev/null +++ b/app/customer/mw/mw_limiter.go @@ -0,0 +1,58 @@ +package mw + +import ( + "bytes" + "io/ioutil" + + "github.com/gin-gonic/gin" + + "applet/app/utils" + "applet/app/utils/cache" +) + +// 限流器 +func Limiter(c *gin.Context) { + limit := 100 // 限流次数 + ttl := 1 // 限流过期时间 + ip := c.ClientIP() + // 读取token或者ip + token := c.GetHeader("Authorization") + // 判断是否已经超出限额次数 + method := c.Request.Method + host := c.Request.Host + uri := c.Request.URL.String() + + buf := make([]byte, 2048) + num, _ := c.Request.Body.Read(buf) + body := buf[:num] + // Write body back + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + Md5 := utils.Md5(ip + token + method + host + uri + string(body)) + if cache.Exists(Md5) { + c.AbortWithStatusJSON(429, gin.H{ + "code": 429, + "msg": "don't repeat the request", + "data": struct{}{}, + }) + return + } + // 2s后没返回自动释放 + go cache.SetEx(Md5, "0", ttl) + key := "LIMITER_" + ip + reqs, _ := cache.GetInt(key) + if reqs >= limit { + c.AbortWithStatusJSON(429, gin.H{ + "code": 429, + "msg": "too many requests", + "data": struct{}{}, + }) + return + } + if reqs > 0 { + go cache.Incr(key) + } else { + go cache.SetEx(key, 1, ttl) + } + c.Next() + go cache.Del(Md5) +} diff --git a/app/customer/mw/mw_recovery.go b/app/customer/mw/mw_recovery.go new file mode 100644 index 0000000..b32cc82 --- /dev/null +++ b/app/customer/mw/mw_recovery.go @@ -0,0 +1,57 @@ +package mw + +import ( + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func Recovery(logger *zap.Logger, stack bool) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + if brokenPipe { + logger.Error(c.Request.URL.Path, + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + // If the connection is dead, we can't write a status to it. + c.Error(err.(error)) + c.Abort() + return + } + + if stack { + logger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + zap.String("stack", string(debug.Stack())), + ) + } else { + logger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + c.AbortWithStatus(http.StatusInternalServerError) + } + }() + c.Next() + } +} diff --git a/app/customer/svc/order/svc_central_kitchen_for_school_order.go b/app/customer/svc/order/svc_central_kitchen_for_school_order.go new file mode 100644 index 0000000..11d693f --- /dev/null +++ b/app/customer/svc/order/svc_central_kitchen_for_school_order.go @@ -0,0 +1,122 @@ +package svc + +import ( + "applet/app/admin/svc/enterprise_manage" + "applet/app/customer/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/enum" + "applet/app/utils" + "errors" + "time" +) + +func OrderList(req md.CentralKitchenForSchoolOrderListReq) (m []model.CentralKitchenForSchoolPackageOrd, total int64, err error) { + sess := db.Db.Desc("id").Where("user_identity_id =?", req.UserIdentityId).Limit(req.Limit, (req.Page-1)*req.Limit) + if req.OrdState != 0 { + sess.And("ord_state = ?", req.OrdState) + } + total, err = sess.FindAndCount(&m) + if err != nil { + return + } + return +} + +func CentralKitchenForSchoolOrderRefund(req md.CentralKitchenForSchoolOrderRefundReq) (err error) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + cfg, err := sysCfgDb.SysCfgGetOne(enum.CentralKitchenForSchoolReserveMealTime) + if err != nil { + return + } + + centralKitchenForSchoolUserWithDayDb := db.CentralKitchenForSchoolUserWithDayDb{} + centralKitchenForSchoolUserWithDayDb.Set(0) + centralKitchenForSchoolUserWithDay, err := centralKitchenForSchoolUserWithDayDb.GetCentralKitchenForSchoolUserWithDay(req.Id) + if err != nil { + return err + } + if centralKitchenForSchoolUserWithDay == nil { + err = errors.New("未查询到订餐记录") + return + } + //1、判断是否为“待就餐”状态 + if centralKitchenForSchoolUserWithDay.State != enum.CentralKitchenForSchoolUserWithDayStateForWait { + err = errors.New("当前订餐不可退订") + return + } + + //2、判断是否为可退餐时间段 + now := time.Now() + today, _ := time.Parse("2006-01-02", time.Now().Format("2006-01-02")) + centralKitchenForSchoolReserveMealTime, _ := time.Parse("2006-01-02 15:04:05", now.Format("2006-01-02")+" "+cfg.Val+":00") + date, _ := time.Parse("2006-01-02", centralKitchenForSchoolUserWithDay.Date) + if today.Equal(date) { + //2.2、判断是否过了今日可订餐时间 + if now.After(centralKitchenForSchoolReserveMealTime) { + err = errors.New("当前订餐不在可退订时间范围内") + return + } + } + + //3、处理订餐记录状态 + centralKitchenForSchoolUserWithDay.State = enum.CentralKitchenForSchoolUserWithDayStateForCanceling + updateAck, err := centralKitchenForSchoolUserWithDayDb.CentralKitchenForSchoolUserWithDayUpdate(centralKitchenForSchoolUserWithDay.Id, centralKitchenForSchoolUserWithDay, "state") + if err != nil { + return err + } + if updateAck <= 0 { + err = errors.New("更新订餐记录状态失败") + return + } + + //4、处理订单状态 + err = svc.JudgePackageOrdOrdState(req.OutTradeNo) + if err != nil { + return err + } + + //5、新增退款记录 central_kitchen_for_school_user_refund_day + outRequestNo := utils.OrderUUID(centralKitchenForSchoolUserWithDay.Uid) + centralKitchenForSchoolUserRefundDayDb := db.CentralKitchenForSchoolUserRefundDayDb{} + centralKitchenForSchoolUserRefundDayDb.Set(0) + _, err = centralKitchenForSchoolUserRefundDayDb.CentralKitchenForSchoolUserRefundDayInsert(&model.CentralKitchenForSchoolUserRefundDay{ + OutTradeNo: req.OutTradeNo, + OutRequestNo: outRequestNo, + Uid: centralKitchenForSchoolUserWithDay.Uid, + IdentityId: centralKitchenForSchoolUserWithDay.IdentityId, + RecordsId: centralKitchenForSchoolUserWithDay.Id, + State: enum.CentralKitchenForSchoolUserRefundDayStateForAuditing, + Amount: centralKitchenForSchoolUserWithDay.Amount, + Memo: "", + CreateAt: now, + UpdateAt: now, + }) + return +} + +func CentralKitchenForSchoolOrderRefundList(req md.CentralKitchenForSchoolOrderRefundListReq) (resp []md.CentralKitchenForSchoolOrderRefundListResp, total int64, err error) { + var m []*db.CentralKitchenForSchoolUserRefundDayWithCentralKitchenForSchoolUserWithDay + sess := db.Db.Where("central_kitchen_for_school_user_refund_day.identity_id =?", req.UserIdentityId) + total, err = sess. + Join("LEFT", "central_kitchen_for_school_user_with_day", "central_kitchen_for_school_user_refund_day.records_id = central_kitchen_for_school_user_with_day.id"). + Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return nil, 0, err + } + + for _, v := range m { + resp = append(resp, md.CentralKitchenForSchoolOrderRefundListResp{ + OutTradeNo: v.CentralKitchenForSchoolUserRefundDay.OutTradeNo, + OutRequestNo: v.CentralKitchenForSchoolUserRefundDay.OutRequestNo, + Kind: v.CentralKitchenForSchoolUserWithDay.Kind, + CreateAt: v.CentralKitchenForSchoolUserRefundDay.CreateAt.Format("2006-01-02 15:04:05"), + Date: v.CentralKitchenForSchoolUserWithDay.Date, + State: v.CentralKitchenForSchoolUserRefundDay.State, + Memo: v.CentralKitchenForSchoolUserRefundDay.Memo, + Amount: v.CentralKitchenForSchoolUserRefundDay.Amount, + }) + } + return +} diff --git a/app/customer/svc/svc_auth.go b/app/customer/svc/svc_auth.go new file mode 100644 index 0000000..ed08ef6 --- /dev/null +++ b/app/customer/svc/svc_auth.go @@ -0,0 +1,54 @@ +package svc + +import ( + "applet/app/customer/lib/auth" + "applet/app/db" + "applet/app/db/model" + "errors" + "github.com/gin-gonic/gin" + "strings" + "time" +) + +func GetUser(c *gin.Context) *model.User { + user, _ := c.Get("user") + if user == nil { + return &model.User{ + Id: 0, + UserId: "", + Nickname: "", + Avatar: "", + Phone: "", + Memo: "", + CreateAt: time.Time{}, + UpdateAt: time.Time{}, + } + } + return user.(*model.User) +} + +func CheckUser(c *gin.Context) (*model.User, error) { + token := c.GetHeader("Authorization") + if token == "" { + return nil, errors.New("token not exist") + } + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if !(len(parts) == 2 && parts[0] == "Bearer") { + return nil, errors.New("token format error") + } + // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 + mc, err := auth.ParseToken(parts[1]) + if err != nil { + return nil, err + } + + // 获取user + userDb := db.UserDb{} + userDb.Set() + user, err := userDb.GetUser(mc.UserId) + if err != nil { + return nil, err + } + return user, nil +} diff --git a/app/customer/svc/svc_central_kitchen_for_school_package.go b/app/customer/svc/svc_central_kitchen_for_school_package.go new file mode 100644 index 0000000..955a1b7 --- /dev/null +++ b/app/customer/svc/svc_central_kitchen_for_school_package.go @@ -0,0 +1,321 @@ +package svc + +import ( + "applet/app/admin/md" + md2 "applet/app/customer/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/enum" + "applet/app/utils" + "time" +) + +func CalcBySchoolTerm(uid int, isTeacher bool, buyPackageReq md2.BuyPackageReq) (totalPrice float64, data []*model.CentralKitchenForSchoolUserWithDay, err error) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + cfg, err := sysCfgDb.SysCfgGetOne(enum.CentralKitchenForSchoolReserveMealTime) + if err != nil { + return + } + + //1、查询出套餐基础设置 + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(buyPackageReq.EnterpriseId) + centralKitchenForSchoolWithSpecData, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + return + } + + //2、查询出当前合适的package + var m []model.CentralKitchenForSchoolPackage + now := time.Now() + today, _ := time.Parse("2006-01-02", time.Now().Format("2006-01-02")) + err = db.Db.Where("enterprise_id =?", buyPackageReq.EnterpriseId).And("end_date > ?", now.Format("2006-01-02 15:04:05")).And("is_delete = 0").And("state = 1").Desc("end_date").Find(&m) + if err != nil { + return + } + centralKitchenForSchoolReserveMealTime, _ := time.Parse("2006-01-02 15:04:05", now.Format("2006-01-02")+" "+cfg.Val+":00") + + //3、循环拼接数据 + for _, v := range m { + centralKitchenForSchoolPackageWithDayDb := db.CentralKitchenForSchoolPackageWithDayDb{} + centralKitchenForSchoolPackageWithDayDb.Set(v.Id) + centralKitchenForSchoolPackageWithDay, err1 := centralKitchenForSchoolPackageWithDayDb.FindCentralKitchenForSchoolPackageWithDay() + if err1 != nil { + return + } + for _, v1 := range *centralKitchenForSchoolPackageWithDay { + //3.1、判断是否小于今天 + date, _ := time.Parse("2006-01-02", v1.Date) + if today.After(date) { + continue + } + if today.Equal(date) { + //2.2、判断是否过了今日可订餐时间 + if now.After(centralKitchenForSchoolReserveMealTime) { + continue + } + } + //3.3、计算价格 && 组装数据 + if v1.IsOpenBreakfast == md.OpenBreakfast && buyPackageReq.IsBuyBreakfast == 1 { + //早餐 + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.BreakfastUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.BreakfastUnitPrice + } + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Amount: amount, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + + if v1.IsOpenLunch == md.OpenLunch && buyPackageReq.IsBuyLunch == 1 { + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.LunchUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.LunchUnitPrice + } + totalPrice += utils.StrToFloat64(amount) + + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Amount: amount, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForLunch, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + + if v1.IsOpenDinner == md.OpenDinner && buyPackageReq.IsBuyDinner == 1 { + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.DinnerUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.DinnerUnitPrice + } + totalPrice += utils.StrToFloat64(amount) + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForDinner, + Date: v1.Date, + Amount: amount, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + } + + } + return +} + +func CalcByMonth(uid int, isTeacher bool, buyPackageReq md2.BuyPackageReq) (totalPrice float64, data []*model.CentralKitchenForSchoolUserWithDay, err error) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + cfg, err := sysCfgDb.SysCfgGetOne(enum.CentralKitchenForSchoolReserveMealTime) + if err != nil { + return + } + + //1、查询出套餐基础设置 + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(buyPackageReq.EnterpriseId) + centralKitchenForSchoolWithSpecData, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + return + } + + //2、查询出当前合适的package + var m []model.CentralKitchenForSchoolPackage + now := time.Now() + today, _ := time.Parse("2006-01-02", time.Now().Format("2006-01-02")) + err = db.Db.Where("enterprise_id =?", buyPackageReq.EnterpriseId).And("id =?", buyPackageReq.PackageId).And("is_delete = 0").And("state = 1").Find(&m) + if err != nil { + return + } + centralKitchenForSchoolReserveMealTime, _ := time.Parse("2006-01-02 15:04:05", now.Format("2006-01-02")+" "+cfg.Val+":00") + + //3、循环拼接数据 + for _, v := range m { + centralKitchenForSchoolPackageWithDayDb := db.CentralKitchenForSchoolPackageWithDayDb{} + centralKitchenForSchoolPackageWithDayDb.Set(v.Id) + centralKitchenForSchoolPackageWithDay, err1 := centralKitchenForSchoolPackageWithDayDb.FindCentralKitchenForSchoolPackageWithDay() + if err1 != nil { + return + } + for _, v1 := range *centralKitchenForSchoolPackageWithDay { + //3.1、判断是否小于今天 + date, _ := time.Parse("2006-01-02", v1.Date) + if today.After(date) { + continue + } + if today.Equal(date) { + //2.2、判断是否过了今日可订餐时间 + if now.After(centralKitchenForSchoolReserveMealTime) { + continue + } + } + + //3.3、计算价格 && 组装数据 + if v1.IsOpenBreakfast == md.OpenBreakfast && buyPackageReq.IsBuyBreakfast == 1 { + //早餐 + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.BreakfastUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.BreakfastUnitPrice + } + + totalPrice += utils.StrToFloat64(amount) + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Amount: amount, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + + if v1.IsOpenLunch == md.OpenLunch && buyPackageReq.IsBuyLunch == 1 { + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.LunchUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.LunchUnitPrice + } + totalPrice += utils.StrToFloat64(amount) + + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Amount: amount, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForLunch, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + + if v1.IsOpenDinner == md.OpenDinner && buyPackageReq.IsBuyDinner == 1 { + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.DinnerUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.DinnerUnitPrice + } + totalPrice += utils.StrToFloat64(amount) + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForDinner, + Amount: amount, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + } + } + return +} + +func CalcByDay(uid int, isTeacher bool, buyPackageReq md2.BuyPackageReq) (totalPrice float64, data []*model.CentralKitchenForSchoolUserWithDay, err error) { + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + cfg, err := sysCfgDb.SysCfgGetOne(enum.CentralKitchenForSchoolReserveMealTime) + if err != nil { + return + } + + //1、查询出套餐基础设置 + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(buyPackageReq.EnterpriseId) + centralKitchenForSchoolWithSpecData, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + return + } + + now := time.Now() + today, _ := time.Parse("2006-01-02", time.Now().Format("2006-01-02")) + centralKitchenForSchoolReserveMealTime, _ := time.Parse("2006-01-02 15:04:05", now.Format("2006-01-02")+" "+cfg.Val+":00") + + //2、循环拼接数据 + for _, v1 := range buyPackageReq.WithDays { + //2.1、判断是否小于今天 + date, _ := time.Parse("2006-01-02", v1.Date) + if today.After(date) { + continue + } + if today.Equal(date) { + //2.2、判断是否过了今日可订餐时间 + if now.After(centralKitchenForSchoolReserveMealTime) { + continue + } + } + + //2.3、计算价格 && 组装数据 + if v1.IsBuyBreakfast == 1 && buyPackageReq.IsBuyBreakfast == 1 { + //早餐 + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.BreakfastUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.BreakfastUnitPrice + } + + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForBreakfast, + Date: v1.Date, + Amount: amount, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + totalPrice += utils.StrToFloat64(amount) + } + + if v1.IsBuyLunch == 1 && buyPackageReq.IsBuyLunch == 1 { + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.LunchUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.LunchUnitPrice + } + totalPrice += utils.StrToFloat64(amount) + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForLunch, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + Amount: amount, + }) + } + + if v1.IsBuyDinner == 1 && buyPackageReq.IsBuyDinner == 1 { + var amount string + if isTeacher { + amount = centralKitchenForSchoolWithSpecData.DinnerUnitPriceForTeacher + } else { + amount = centralKitchenForSchoolWithSpecData.DinnerUnitPrice + } + data = append(data, &model.CentralKitchenForSchoolUserWithDay{ + Uid: uid, + IdentityId: buyPackageReq.UserIdentityId, + Kind: enum.CentralKitchenForSchoolUserWithDayKindForDinner, + Amount: amount, + Date: v1.Date, + State: enum.CentralKitchenForSchoolUserWithDayStateForWait, + }) + } + } + + return +} diff --git a/app/customer/svc/svc_curl_smart_pay.go b/app/customer/svc/svc_curl_smart_pay.go new file mode 100644 index 0000000..a9ae12c --- /dev/null +++ b/app/customer/svc/svc_curl_smart_pay.go @@ -0,0 +1,435 @@ +package svc + +import ( + "applet/app/cfg" + "applet/app/customer/md" + "applet/app/db" + "applet/app/db/model" + "applet/app/utils" + "encoding/json" + "errors" + "time" +) + +func AesDecrypt(args md.CurlAesDecrypt) (error, interface{}) { + url := cfg.SmartCanteenPay + "/alipay/decrypt/index" + utils.FilePutContents("AesDecrypt", utils.SerializeStr(map[string]interface{}{ + "data": args, + })) + bytes, err := utils.CurlPost(url, utils.Serialize(args), nil) + if err != nil { + return err, nil + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("AesDecrypt", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return err, nil + } + if result.Code != 0 { + if result.Msg != "" { + return errors.New(result.Msg), nil + } + return errors.New("请求智慧餐厅支付 异常/失败"), nil + } + return nil, result.Data +} + +func SystemOauthToken(args md.SystemOauthTokenReq) (error, interface{}) { + url := cfg.SmartCanteenPay + "/alipay/systemOauthToken/index" + utils.FilePutContents("SystemOauthToken", utils.SerializeStr(map[string]interface{}{ + "data": args, + })) + bytes, err := utils.CurlPost(url, utils.Serialize(args), nil) + if err != nil { + return err, nil + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("SystemOauthToken", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return err, nil + } + if result.Code != 0 { + if result.Msg != "" { + return errors.New(result.Msg), nil + } + return errors.New("请求智慧餐厅支付 异常/失败"), nil + } + return nil, result.Data +} + +func CurlAlipayTradeCreate(args md.CurlAlipayTradeCreateReq) (err error, resp md.CurlAlipayTradeCreateResp) { + url := cfg.SmartCanteenPay + "/alipay/pay/tradeCreate" + utils.FilePutContents("CurlAlipayTradeCreate", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + bytes, err := utils.CurlPost(url, utils.Serialize(args), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlAlipayTradeCreate", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + err = json.Unmarshal(utils.Serialize(result.Data), &resp) + if err != nil { + return + } + return +} + +func CurlAlipayTradeQuery(args md.CurlAlipayTradeQueryReq) (err error, resp md.CurlAlipayTradeQueryResp) { + url := cfg.SmartCanteenPay + "/alipay/pay/tradeCommonQuery" + utils.FilePutContents("CurlAlipayTradeQuery", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + bytes, err := utils.CurlPost(url, utils.Serialize(args), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlAlipayTradeQuery", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + err = json.Unmarshal(utils.Serialize(result.Data), &resp) + if err != nil { + return + } + return +} + +func CurlAlipayTradeRefund(args md.CurlAlipayTradeRefundReq) (err error, resp interface{}) { + url := cfg.SmartCanteenPay + "/alipay/pay/tradeRefund" + utils.FilePutContents("CurlAlipayTradeRefund", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + bytes, err := utils.CurlPost(url, utils.Serialize(args), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlAlipayTradeRefund", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + return nil, result.Data +} + +func CurlEducateSceneTokenQuery(args md.CurlEducateSceneTokenReq, userIdentityId int, userToken string) (err error, resp interface{}) { + utils.FilePutContents("CurlEducateSceneTokenQuery", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + + if userToken == "" { + //1、TODO::生成用户信息token(使用 alipay.commerce.educate.scene.token.create 接口生成用户信息token) + url := cfg.SmartCanteenPay + "/alipay/faceCollection/educateSceneTokenCreate" + bytes, err1 := utils.CurlPost(url, utils.Serialize(map[string]string{ + "cert_no": args.CertNo, + "school_std_code": args.SchoolStdCode, + "student_name": args.StudentName, + "out_user_id": args.OutUserId, + "cert_type": "1", + "sub_code": "SCHOOL_FACE_PASS_QUERY", + }), nil) + if err1 != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlEducateSceneTokenCreate", utils.SerializeStr(result)) + err1 = json.Unmarshal(bytes, &result) + if err1 != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err1 = errors.New(result.Msg) + return + } + err1 = errors.New("请求智慧餐厅支付 异常/失败") + return + } + resultMap, ok := result.Data.(map[string]interface{}) + if !ok { + err1 = errors.New("获取 user_token 失败") + return + } + if resultMap["code"].(string) != "10000" { + return nil, resultMap + } + userToken = resultMap["token"].(string) + } + + //2、TODO::查询用户一脸通行开通状态:根据获取到的用户信息token,使用 alipay.commerce.educate.scene.token.query 接口查询用户人脸采集状态 + url := cfg.SmartCanteenPay + "/alipay/faceCollection/educateSceneTokenQuery" + bytes, err := utils.CurlPost(url, utils.Serialize(map[string]string{ + "user_token": userToken, + }), nil) + if err != nil { + return + } + + var result1 struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlEducateSceneTokenQuery", utils.SerializeStr(result1)) + err = json.Unmarshal(bytes, &result1) + if err != nil { + return + } + if result1.Code != 0 { + if result1.Msg != "" { + err = errors.New(result1.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + // 更新/新增 `self_support_for_user_facel_info` + selfSupportForUserFaceInfoDb := db.SelfSupportForUserFaceInfoDb{} + selfSupportForUserFaceInfoDb.Set(userIdentityId) + info, err := selfSupportForUserFaceInfoDb.GetSelfSupportForUserFaceInfo() + if err != nil { + return + } + + now := time.Now().Format("2006-01-02 15:04:05") + var res map[string]string + utils.Unserialize([]byte(utils.SerializeStr(result1.Data)), &res) + + if info == nil { + info = &model.SelfSupportForUserFaceInfo{ + UserIdentityId: userIdentityId, + CollectFaceType: 1, + SchoolCode: res["school_code"], + SchoolStdCode: res["school_std_code"], + ParentUserId: res["parent_user_id"], + ParentLogonId: res["parent_logon_id"], + UserId: res["user_id"], + SchoolFacePassStatus: res["school_face_pass_status"], + SchoolFacePaymentStatus: res["school_face_payment_status"], + CreateAt: now, + UpdateAt: now, + } + _, err2 := selfSupportForUserFaceInfoDb.SelfSupportForUserFaceInfoInsert(info) + if err2 != nil { + return err2, nil + } + } else { + info.ParentUserId = res["parent_user_id"] + info.ParentLogonId = res["parent_logon_id"] + info.UserId = res["user_id"] + info.SchoolFacePassStatus = res["school_face_pass_status"] + info.SchoolFacePaymentStatus = res["school_face_payment_status"] + info.UpdateAt = now + _, err2 := selfSupportForUserFaceInfoDb.SelfSupportForUserFaceInfoUpdate(info, "parent_user_id", "parent_logon_id", "user_id", "school_face_pass_status", "school_face_payment_status", "create_at") + if err2 != nil { + return err2, nil + } + } + + return nil, result1.Data +} + +func CurlEducateSceneTokenCreateForApplet(args md.CurlEducateSceneTokenReq) (err error, resp interface{}) { + utils.FilePutContents("CurlEducateSceneTokenCreateForApplet", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + + //1、TODO::生成用户信息token(使用 alipay.commerce.educate.scene.token.create 接口生成用户信息token) + url := cfg.SmartCanteenPay + "/alipay/faceCollection/educateSceneTokenCreate" + bytes, err := utils.CurlPost(url, utils.Serialize(map[string]string{ + "cert_no": args.CertNo, + "school_std_code": args.SchoolStdCode, + "student_name": args.StudentName, + "out_user_id": args.OutUserId, + "cert_type": "1", + "sub_code": "SCHOOL_FACE_PASS_NAVIGATE", + }), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlEducateSceneTokenCreateForApplet", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + + return nil, result.Data +} + +func CurlEducateSceneTokenCreateForConcentratedCollectApplet(args md.CurlEducateSceneTokenReq) (err error, resp interface{}) { + utils.FilePutContents("CurlEducateSceneTokenCreateForConcentratedCollectApplet", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + + //1、TODO::生成用户信息token(使用 alipay.commerce.educate.scene.token.create 接口生成用户信息token) + url := cfg.SmartCanteenPay + "/alipay/faceCollection/educateSceneTokenCreate" + bytes, err := utils.CurlPost(url, utils.Serialize(map[string]string{ + "cert_no": args.CertNo, + "school_std_code": args.SchoolStdCode, + "student_name": args.StudentName, + "out_user_id": args.OutUserId, + "cert_type": "1", + "sub_code": "SCHOOL_PAYMENT_REMOTE_OPEN_ACCOUNT", + "operator_user_id": args.OperatorUserId, //采集人员的支付宝会员标识。(当sub_code为SCHOOL_PAYMENT_REMOTE_OPEN_ACCOUNT时,当前参数必选) + }), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlEducateSceneTokenCreateForConcentratedCollectApplet", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + + return nil, result.Data +} + +func CurlEducateFacepayApply(args md.CurlEducateFacepayApplyReq) (err error, resp interface{}) { + utils.FilePutContents("CurlEducateFacepayApply", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + + //1、TODO::alipay.commerce.educate.facepay.apply(人脸开通支付申请) + url := cfg.SmartCanteenPay + "/alipay/faceScanPay/educateFacepayApply" + bytes, err := utils.CurlPost(url, utils.Serialize(map[string]string{ + "face_uid": args.FaceUid, + "school_std_code": args.SchoolStdCode, + "face_open_id": args.FaceOpenId, + }), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlEducateFacepayApply", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + + return nil, result.Data +} + +func CurlAlipayPlanetEcocampusApiRosterSignUpInfo(args md.CurlAlipayPlanetEcocampusApiRosterSignUpInfoReq) (err error, resp interface{}) { + url := cfg.SmartCanteenPay + "/alipay/bPass/alipayPlanetEcocampusApiRosterSignUpInfo" + utils.FilePutContents("CurlAlipayPlanetEcocampusApiRosterSignUpInfo", utils.SerializeStr(map[string]interface{}{ + "args": args, + })) + bytes, err := utils.CurlPost(url, utils.Serialize(args), nil) + if err != nil { + return + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + } + utils.FilePutContents("CurlAlipayPlanetEcocampusApiRosterSignUpInfo", utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return + } + if result.Code != 0 { + if result.Msg != "" { + err = errors.New(result.Msg) + return + } + err = errors.New("请求智慧餐厅支付 异常/失败") + return + } + return nil, result.Data +} diff --git a/app/customer/svc/svc_enterprise.go b/app/customer/svc/svc_enterprise.go new file mode 100644 index 0000000..930ae92 --- /dev/null +++ b/app/customer/svc/svc_enterprise.go @@ -0,0 +1,67 @@ +package svc + +import ( + "applet/app/customer/md" + "applet/app/db" + "applet/app/db/model" +) + +func EnterpriseList(req md.EnterpriseListReq) (m []model.Enterprise, total int64, err error) { + eg := db.Db.Where("1=1") + if req.Name != "" { + eg.And("name like ?", "%"+req.Name+"%") + } + total, err = eg.Limit(req.Limit, (req.Page-1)*req.Limit).FindAndCount(&m) + if err != nil { + return + } + return +} + +func CentralKitchenForSchoolInfo(enterpriseId int) (err error, resp md.CentralKitchenForSchoolInfoResp) { + //1、查询`enterprise` + enterpriseDb := db.EnterpriseDb{} + enterpriseDb.Set() + enterprise, err := enterpriseDb.GetEnterprise(enterpriseId) + if err != nil { + return + } + resp.Name = enterprise.Name + resp.Memo = enterprise.Memo + resp.Kind = enterprise.Kind + resp.State = enterprise.State + + //2、查询`central_kitchen_for_school_with_spec` + centralKitchenForSchoolWithSpec := db.CentralKitchenForSchoolWithSpec{} + centralKitchenForSchoolWithSpec.Set(enterpriseId) + spec, err := centralKitchenForSchoolWithSpec.GetCentralKitchenForSchoolWithSpec() + if err != nil { + return + } + if spec != nil { + resp.IsOpenBreakfast = spec.IsOpenBreakfast + resp.IsOpenLunch = spec.IsOpenLunch + resp.IsOpenDinner = spec.IsOpenDinner + resp.BreakfastUnitPrice = spec.BreakfastUnitPrice + resp.BreakfastUnitPriceForTeacher = spec.BreakfastUnitPriceForTeacher + resp.LunchUnitPrice = spec.LunchUnitPrice + resp.LunchUnitPriceForTeacher = spec.LunchUnitPriceForTeacher + resp.DinnerUnitPrice = spec.DinnerUnitPrice + resp.DinnerUnitPriceForTeacher = spec.DinnerUnitPriceForTeacher + } + + //3、查询`central_kitchen_for_school_set` + centralKitchenForSchoolSetDb := db.CentralKitchenForSchoolSetDb{} + centralKitchenForSchoolSetDb.Set(enterpriseId) + set, err := centralKitchenForSchoolSetDb.GetCentralKitchenForSchoolSet() + if err != nil { + return + } + if set != nil { + resp.IsOpenTeacherReportMeal = set.IsOpenTeacherReportMeal + resp.IsOpenReportMealForDay = set.IsOpenReportMealForDay + resp.IsOpenReportMealForMonth = set.IsOpenReportMealForMonth + resp.IsOpenReportMealForSemester = set.IsOpenReportMealForSemester + } + return +} diff --git a/app/customer/svc/svc_login.go b/app/customer/svc/svc_login.go new file mode 100644 index 0000000..23d2c38 --- /dev/null +++ b/app/customer/svc/svc_login.go @@ -0,0 +1,33 @@ +package svc + +import ( + "applet/app/customer/lib/auth" + "applet/app/customer/md" + "applet/app/db/model" + "applet/app/utils/cache" + "applet/app/utils/logx" +) + +func HandleLoginToken(cacheKey string, user *model.User) (string, error) { + // 获取之前生成的token + token, err := cache.GetString(cacheKey) + if err != nil { + _ = logx.Error(err) + } + // 没有获取到 + if err != nil || token == "" { + // 生成token + token, err = auth.GenToken(user.Id, user.Phone) + if err != nil { + return "", err + } + // 缓存token + _, err = cache.SetEx(cacheKey, token, md.JwtTokenCacheTime) + if err != nil { + return "", err + } + return token, nil + } + + return token, nil +} diff --git a/app/customer/svc/svc_pay.go b/app/customer/svc/svc_pay.go new file mode 100644 index 0000000..d9b7f6c --- /dev/null +++ b/app/customer/svc/svc_pay.go @@ -0,0 +1,116 @@ +package svc + +import ( + "applet/app/customer/md" + "applet/app/db" + "applet/app/db/model" + enum2 "applet/app/enum" + "applet/app/utils" + "errors" + "github.com/gin-gonic/gin" + "time" +) + +func BuyPackage(c *gin.Context, req md.BuyPackageReq) (outTradeNo, tradeNo, total string, err error) { + user := GetUser(c) + session := db.Db.NewSession() + defer session.Close() + session.Begin() + if err != nil { + _ = session.Rollback() + return + } + + //1、判断是否为教师 + isTeacher := false + userIdentityDb := db.UserIdentityDb{} + userIdentityDb.Set(0) + userIdentity, err := userIdentityDb.GetUserIdentity(req.UserIdentityId) + if err != nil { + return + } + if userIdentity == nil { + err = errors.New("未查询到对应身份记录") + return + } + if userIdentity.Kind == enum2.UserIdentityForCentralKitchenForTeacher { + isTeacher = true + } + + //2、计算数据(1:按学期购买 2:按月购买 3:按天购买) + var totalPrice float64 + var data []*model.CentralKitchenForSchoolUserWithDay + if req.Kind == 1 { + totalPrice, data, err = CalcBySchoolTerm(user.Id, isTeacher, req) + if err != nil { + return + } + } + if req.Kind == 2 { + totalPrice, data, err = CalcByMonth(user.Id, isTeacher, req) + if err != nil { + return + } + } + if req.Kind == 3 { + totalPrice, data, err = CalcByDay(user.Id, isTeacher, req) + if err != nil { + return + } + } + total = utils.Float64ToStr(totalPrice) + + //3、生成订单号 + outTradeNo = utils.OrderUUID(user.Id) + + //4、请求 alipay.trade.create(统一收单交易创建接口) + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + sysCfg := sysCfgDb.SysCfgFindWithDb(enum2.OpenAlipayAppid, enum2.OpenAlipayAppPrivateKey, enum2.OpenAlipayPublicKey, enum2.JsapiPayAppAutToken) + err, resp := CurlAlipayTradeCreate(md.CurlAlipayTradeCreateReq{ + Config: struct { + PayAliAppId string `json:"pay_ali_app_id" binding:"required" label:"支付宝开放平台-第三方应用-appid"` + PayAliPrivateKey string `json:"pay_ali_private_key" binding:"required" label:"支付宝开放平台-第三方应用-接口加签-应用私钥"` + PayAliPublicKey string `json:"pay_ali_public_key" binding:"required" label:"支付宝开放平台-第三方应用-接口加签-支付宝公钥"` + }{ + PayAliAppId: sysCfg[enum2.OpenAlipayAppid], + PayAliPrivateKey: sysCfg[enum2.OpenAlipayAppPrivateKey], + PayAliPublicKey: sysCfg[enum2.OpenAlipayPublicKey], + }, + BuyerId: user.UserId, + TotalAmount: total, + OutTradeNo: outTradeNo, + Subject: "购买食堂套餐", + AppAuthToken: sysCfg[enum2.JsapiPayAppAutToken], + }) + if err != nil { + return + } + + //5、插入订单记录 + now := time.Now() + centralKitchenForSchoolPackageOrd := db.CentralKitchenForSchoolPackageOrd{} + centralKitchenForSchoolPackageOrd.Set(outTradeNo) + _, err = centralKitchenForSchoolPackageOrd.CentralKitchenForSchoolPackageOrdInsertBySession(session, &model.CentralKitchenForSchoolPackageOrd{ + EnterpriseId: req.EnterpriseId, + Uid: user.Id, + UserIdentityId: req.UserIdentityId, + TotalPrice: total, + Kind: req.Kind, + OutTradeNo: outTradeNo, + TradeNo: resp.TradeNo, + State: enum2.CentralKitchenForSchoolPackageOrdStateForWait, + OrdState: enum2.CentralKitchenForSchoolPackageOrdOrdStateForWait, + ReqContent: string(utils.Serialize(req)), + WithDayData: string(utils.Serialize(data)), + CreateAt: now.Format("2006-01-02 15:04:05"), + UpdateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + _ = session.Rollback() + return + } + session.Commit() + tradeNo = resp.TradeNo + return +} diff --git a/app/customer/svc/svc_qrcode.go b/app/customer/svc/svc_qrcode.go new file mode 100644 index 0000000..b3463c0 --- /dev/null +++ b/app/customer/svc/svc_qrcode.go @@ -0,0 +1 @@ +package svc diff --git a/app/customer/svc/svc_role.go b/app/customer/svc/svc_role.go new file mode 100644 index 0000000..6337b0c --- /dev/null +++ b/app/customer/svc/svc_role.go @@ -0,0 +1,5 @@ +package svc + +func CheckUserRole(cacheKey, uri string) (isHasPermission bool, err error) { + return +} diff --git a/app/db/db.go b/app/db/db.go new file mode 100644 index 0000000..ea6f235 --- /dev/null +++ b/app/db/db.go @@ -0,0 +1,114 @@ +package db + +import ( + "database/sql" + "fmt" + "os" + + _ "github.com/go-sql-driver/mysql" + "xorm.io/xorm" + "xorm.io/xorm/log" + + "applet/app/cfg" + "applet/app/utils/logx" +) + +var Db *xorm.Engine + +func InitDB(c *cfg.DBCfg) error { + var err error + if Db, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", c.User, c.Psw, c.Host, c.Name)); err != nil { + return err + } + Db.SetConnMaxLifetime(c.MaxLifetime) + Db.SetMaxOpenConns(c.MaxOpenConns) + Db.SetMaxIdleConns(c.MaxIdleConns) + if err = Db.Ping(); err != nil { + return err + } + if c.ShowLog { + Db.ShowSQL(true) + Db.Logger().SetLevel(0) + f, err := os.OpenFile(c.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777) + if err != nil { + os.RemoveAll(c.Path) + if f, err = os.OpenFile(c.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777); err != nil { + return err + } + } + logger := log.NewSimpleLogger(f) + logger.ShowSQL(true) + Db.SetLogger(logger) + } + return nil +} + +/********************************************* 公用方法 *********************************************/ + +// DbInsertBatch 数据批量插入 +func DbInsertBatch(Db *xorm.Engine, m ...interface{}) error { + if len(m) == 0 { + return nil + } + id, err := Db.Insert(m...) + if id == 0 || err != nil { + return logx.Warn("cannot insert data :", err) + } + return nil +} + +// QueryNativeString 查询原生sql +func QueryNativeString(Db *xorm.Engine, sql string, args ...interface{}) ([]map[string]string, error) { + results, err := Db.SQL(sql, args...).QueryString() + return results, err +} +func QueryNativeStringSess(sess *xorm.Session, sql string, args ...interface{}) ([]map[string]string, error) { + results, err := sess.SQL(sql, args...).QueryString() + return results, err +} + +// CommonInsert 插入一条或多条数据 +func CommonInsert(Db *xorm.Engine, data interface{}) (int64, error) { + row, err := Db.Insert(data) + return row, err +} + +// UpdateComm 根据主键更新 +func UpdateComm(Db *xorm.Engine, id interface{}, model interface{}) (int64, error) { + row, err := Db.ID(id).Update(model) + return row, err +} + +// InsertOneComm 插入一条数据 +func InsertOneComm(Db *xorm.Engine, model interface{}) (int64, error) { + row, err := Db.InsertOne(model) + return row, err +} + +// GetComm 获取一条数据 +// payload *model +// return *model,has,err +func GetComm(Db *xorm.Engine, model interface{}) (interface{}, bool, error) { + has, err := Db.Get(model) + if err != nil { + _ = logx.Warn(err) + return nil, false, err + } + return model, has, nil +} + +// ExecuteOriginalSql 执行原生sql +func ExecuteOriginalSql(Db *xorm.Engine, sql string) (sql.Result, error) { + result, err := Db.Exec(sql) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + return result, nil +} + +// InsertCommWithSession common insert +func InsertCommWithSession(session *xorm.Session, model interface{}) (int64, error) { + row, err := session.InsertOne(model) + return row, err +} diff --git a/app/db/db_admin.go b/app/db/db_admin.go new file mode 100644 index 0000000..3a8284c --- /dev/null +++ b/app/db/db_admin.go @@ -0,0 +1,114 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type AdminDb struct { + Db *xorm.Engine `json:"db"` +} + +func (adminDb *AdminDb) Set() { // set方法 + adminDb.Db = Db +} + +func (adminDb *AdminDb) CreateAdminId() (admId int, err error) { + m := new(model.Admin) + has, err := adminDb.Db.Desc("adm_id").Get(m) + if err != nil { + return 0, logx.Error(err) + } + if has == false { + return 0, nil + } + admId = m.AdmId + 1 + return admId, nil +} + +func (adminDb *AdminDb) AdminDeleteBySession(session *xorm.Session, admId interface{}) (int64, error) { + if reflect.TypeOf(admId).Kind() == reflect.Slice { + return session.In("adm_id", admId).Delete(model.Admin{}) + } else { + return session.Where("id = ?", admId).Delete(model.Admin{}) + } +} + +func (adminDb *AdminDb) GetAdmin(id int) (m *model.Admin, err error) { + m = new(model.Admin) + has, err := adminDb.Db.Where("adm_id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (adminDb *AdminDb) GetAdminByUserName(userName string) (m *model.Admin, err error) { + m = new(model.Admin) + has, err := adminDb.Db.Where("username =?", userName).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (adminDb *AdminDb) GetAdminRolePermission(admId int) (list []*AdminRolePermission, total int64, err error) { + total, err = adminDb.Db.Where("admin.adm_id =?", admId). + Join("LEFT", "admin_role", "admin.adm_id = admin_role.adm_id"). + Join("LEFT", "role", "admin_role.role_id = role.id"). + Join("LEFT", "role_permission_group", "role.id = role_permission_group.role_id"). + Join("LEFT", "permission_group", "role_permission_group.group_id = permission_group.id"). + Join("LEFT", "permission_group_permission", "permission_group.id = permission_group_permission.group_id"). + Join("LEFT", "permission", "permission_group_permission.permission_id = permission.id"). + FindAndCount(&list) + return +} + +func (adminDb *AdminDb) FindAdminRolePermissionGroup(admId int) (list []*AdminRolePermissionGroup, total int64, err error) { + total, err = adminDb.Db.Where("admin.adm_id =?", admId). + Join("LEFT", "admin_role", "admin.adm_id = admin_role.adm_id"). + Join("LEFT", "role", "admin_role.role_id = role.id"). + Join("LEFT", "role_permission_group", "role.id = role_permission_group.role_id"). + Join("LEFT", "permission_group", "role_permission_group.group_id = permission_group.id"). + FindAndCount(&list) + return +} + +func (adminDb *AdminDb) FindAdmin(username string, state, page, limit int) (list []model.Admin, total int64, err error) { + sess := adminDb.Db.Desc("adm_id").Limit(limit, (page-1)*limit) + if username != "" { + sess.And("username like ?", "%"+username+"%") + } + if state != 0 { + sess.And("state = ?", state) + } + total, err = sess.FindAndCount(&list) + if err != nil { + return nil, 0, err + } + return +} + +func (adminDb *AdminDb) UpdateAdmin(m *model.Admin, columns ...string) (int64, error) { + affected, err := adminDb.Db.Where("adm_id =?", m.AdmId).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (adminDb *AdminDb) AdminInsert(m *model.Admin) (int64, error) { + insertAffected, err := adminDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return insertAffected, nil +} diff --git a/app/db/db_admin_role.go b/app/db/db_admin_role.go new file mode 100644 index 0000000..83331a1 --- /dev/null +++ b/app/db/db_admin_role.go @@ -0,0 +1,81 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type AdminRoleDb struct { + Db *xorm.Engine `json:"db"` +} + +func (adminRoleDb *AdminRoleDb) Set() { // set方法 + adminRoleDb.Db = Db +} + +func (adminRoleDb *AdminRoleDb) GetAdminRole(id int) (m *model.AdminRole, err error) { + m = new(model.AdminRole) + has, err := adminRoleDb.Db.Where("adm_id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (adminRoleDb *AdminRoleDb) AdminDeleteBySessionForAdmId(session *xorm.Session, admId interface{}) (int64, error) { + if reflect.TypeOf(admId).Kind() == reflect.Slice { + return session.In("adm_id", admId).Delete(model.AdminRole{}) + } else { + return session.Where("adm_id = ?", admId).Delete(model.AdminRole{}) + } +} + +func (adminRoleDb *AdminRoleDb) GetAdminRoleByRole(id int) (m *model.AdminRole, err error) { + m = new(model.AdminRole) + has, err := adminRoleDb.Db.Where("role_id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (adminRoleDb *AdminRoleDb) AdminRoleDeleteForRoleBySession(session *xorm.Session, roleId interface{}) (int64, error) { + if reflect.TypeOf(roleId).Kind() == reflect.Slice { + return session.In("role_id", roleId).Delete(model.AdminRole{}) + } else { + return session.Where("role_id = ?", roleId).Delete(model.AdminRole{}) + } +} + +func (adminRoleDb *AdminRoleDb) AdminRoleDeleteBySession(session *xorm.Session, id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return session.In("id", id).Delete(model.AdminRole{}) + } else { + return session.Where("adm_id = ?", id).Delete(model.AdminRole{}) + } +} + +func (adminRoleDb *AdminRoleDb) BatchAddAdminRoleBySession(session *xorm.Session, mm []*model.AdminRole) (int64, error) { + affected, err := session.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +type AdminRole struct { + model.Admin `xorm:"extends"` + model.AdminRole `xorm:"extends"` +} + +func (AdminRole) TableName() string { + return "admin_role" +} diff --git a/app/db/db_admin_role_permission.go b/app/db/db_admin_role_permission.go new file mode 100644 index 0000000..2bde123 --- /dev/null +++ b/app/db/db_admin_role_permission.go @@ -0,0 +1,19 @@ +package db + +import ( + model2 "applet/app/db/model" +) + +type AdminRolePermission struct { + model2.Admin `xorm:"extends"` + model2.AdminRole `xorm:"extends"` + model2.Role `xorm:"extends"` + model2.RolePermissionGroup `xorm:"extends"` + model2.PermissionGroup `xorm:"extends"` + model2.PermissionGroupPermission `xorm:"extends"` + model2.Permission `xorm:"extends"` +} + +func (AdminRolePermission) TableName() string { + return "admin" +} diff --git a/app/db/db_admin_role_permission_group.go b/app/db/db_admin_role_permission_group.go new file mode 100644 index 0000000..96fcccc --- /dev/null +++ b/app/db/db_admin_role_permission_group.go @@ -0,0 +1,17 @@ +package db + +import ( + model2 "applet/app/db/model" +) + +type AdminRolePermissionGroup struct { + model2.Admin `xorm:"extends"` + model2.AdminRole `xorm:"extends"` + model2.Role `xorm:"extends"` + model2.RolePermissionGroup `xorm:"extends"` + model2.PermissionGroup `xorm:"extends"` +} + +func (AdminRolePermissionGroup) TableName() string { + return "admin" +} diff --git a/app/db/db_banner.go b/app/db/db_banner.go new file mode 100644 index 0000000..38f7403 --- /dev/null +++ b/app/db/db_banner.go @@ -0,0 +1,86 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type BannerDb struct { + Db *xorm.Engine `json:"db"` +} + +func (bannerDb *BannerDb) Set() { // set方法 + bannerDb.Db = Db +} + +func (bannerDb *BannerDb) GetBanner(id int) (m *model.Banner, err error) { + m = new(model.Banner) + has, err := bannerDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (bannerDb *BannerDb) FindBannerById(ids interface{}) (*[]model.Banner, error) { + var m []model.Banner + if err := bannerDb.Db.In("id", ids).Desc("sort").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (bannerDb *BannerDb) FindBanner(limit, start int) (*[]model.Banner, error) { + var m []model.Banner + if limit == 0 || start == 0 { + if err := bannerDb.Db.Desc("sort").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := bannerDb.Db.Desc("sort").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (bannerDb *BannerDb) GetBannerByName(name string) (m *model.Banner, err error) { + m = new(model.Banner) + has, err := bannerDb.Db.Where("name =?", name).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (bannerDb *BannerDb) BannerInsert(m *model.Banner) (int, error) { + _, err := bannerDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (bannerDb *BannerDb) BannerUpdate(m *model.Banner, columns ...string) (int64, error) { + affected, err := bannerDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (bannerDb *BannerDb) BannerDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.Banner{}) + } else { + return Db.Where("id = ?", id).Delete(model.Banner{}) + } +} diff --git a/app/db/db_central_kitchen_for_school_package.go b/app/db/db_central_kitchen_for_school_package.go new file mode 100644 index 0000000..efbfed7 --- /dev/null +++ b/app/db/db_central_kitchen_for_school_package.go @@ -0,0 +1,92 @@ +package db + +import ( + "applet/app/admin/md" + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolPackageDb struct { + Db *xorm.Engine `json:"db"` +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) Set() { // set方法 + centralKitchenForSchoolPackageDb.Db = Db +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) GetCentralKitchenForSchoolPackage(id int) (m *model.CentralKitchenForSchoolPackage, err error) { + m = new(model.CentralKitchenForSchoolPackage) + has, err := centralKitchenForSchoolPackageDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) FindCentralKitchenForSchoolPackage() (*[]model.CentralKitchenForSchoolPackage, error) { + var m []model.CentralKitchenForSchoolPackage + if err := centralKitchenForSchoolPackageDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) CentralKitchenForSchoolPackageInsert(m *model.CentralKitchenForSchoolPackage) (int, error) { + _, err := centralKitchenForSchoolPackageDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) CentralKitchenForSchoolPackageInsertBySession(session *xorm.Session, m *model.CentralKitchenForSchoolPackage) (int, error) { + _, err := session.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) CentralKitchenForSchoolPackageDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.CentralKitchenForSchoolPackage{}) + } else { + return Db.Where("id = ?", id).Delete(model.CentralKitchenForSchoolPackage{}) + } +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) CentralKitchenForSchoolPackageUpdateBySession(session *xorm.Session, id interface{}, m *model.CentralKitchenForSchoolPackage, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = session.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = session.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolPackageDb *CentralKitchenForSchoolPackageDb) CentralKitchenForSchoolPackageList(req md.ListCentralKitchenForSchoolPackageReq) (m []model.CentralKitchenForSchoolPackage, total int64, err error) { + sess := centralKitchenForSchoolPackageDb.Db.Where("enterprise_id =?", req.EnterpriseId).Desc("id").Limit(req.Limit, (req.Page-1)*req.Limit) + if req.Year != "" { + sess.And("year = ?", req.Year) + } + if req.Month != "" { + sess.And("month = ?", req.Month) + } + total, err = sess.And("is_delete = 0").FindAndCount(&m) + if err != nil { + return + } + return +} diff --git a/app/db/db_central_kitchen_for_school_package_ord.go b/app/db/db_central_kitchen_for_school_package_ord.go new file mode 100644 index 0000000..b8adf5b --- /dev/null +++ b/app/db/db_central_kitchen_for_school_package_ord.go @@ -0,0 +1,106 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolPackageOrd struct { + Db *xorm.Engine `json:"db"` + OutTradeNo string `json:"out_trade_no"` +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) Set(outTradeNo string) { // set方法 + centralKitchenForSchoolPackageOrdDb.Db = Db + centralKitchenForSchoolPackageOrdDb.OutTradeNo = outTradeNo +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) GetCentralKitchenForSchoolPackageOrdById(id int) (m *model.CentralKitchenForSchoolPackageOrd, err error) { + m = new(model.CentralKitchenForSchoolPackageOrd) + has, err := centralKitchenForSchoolPackageOrdDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) GetCentralKitchenForSchoolPackageOrd() (m *model.CentralKitchenForSchoolPackageOrd, err error) { + m = new(model.CentralKitchenForSchoolPackageOrd) + has, err := centralKitchenForSchoolPackageOrdDb.Db.Where("out_trade_no =?", centralKitchenForSchoolPackageOrdDb.OutTradeNo).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) CentralKitchenForSchoolPackageOrdInsert(m *model.CentralKitchenForSchoolPackageOrd) (int, error) { + _, err := centralKitchenForSchoolPackageOrdDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) CentralKitchenForSchoolPackageOrdInsertBySession(session *xorm.Session, m *model.CentralKitchenForSchoolPackageOrd) (int, error) { + _, err := session.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) BatchAddCentralKitchenForSchoolPackageOrds(mm []*model.CentralKitchenForSchoolPackageOrd) (int64, error) { + affected, err := centralKitchenForSchoolPackageOrdDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) CentralKitchenForSchoolPackageOrdDeleteById(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.CentralKitchenForSchoolPackageOrd{}) + } else { + return Db.Where("id = ?", id).Delete(model.CentralKitchenForSchoolPackageOrd{}) + } +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) CentralKitchenForSchoolPackageOrdDelete() (int64, error) { + return Db.Where("out_trade_no = ?", centralKitchenForSchoolPackageOrdDb.OutTradeNo).Delete(model.CentralKitchenForSchoolPackageOrd{}) +} + +func (centralKitchenForSchoolPackageOrdDb *CentralKitchenForSchoolPackageOrd) CentralKitchenForSchoolPackageOrdUpdate(m *model.CentralKitchenForSchoolPackageOrd, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = centralKitchenForSchoolPackageOrdDb.Db.Where("out_trade_no=?", centralKitchenForSchoolPackageOrdDb.OutTradeNo).Cols(forceColums...).Update(m) + } else { + affected, err = centralKitchenForSchoolPackageOrdDb.Db.Where("out_trade_no=?", centralKitchenForSchoolPackageOrdDb.OutTradeNo).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} + +type CentralKitchenForSchoolPackageOrdWithUserIdentity struct { + model.CentralKitchenForSchoolPackageOrd `xorm:"extends"` + model.UserIdentity `xorm:"extends"` + model.ClassWithUser `xorm:"extends"` + model.Class `xorm:"extends"` + model.Grade `xorm:"extends"` +} + +func (CentralKitchenForSchoolPackageOrdWithUserIdentity) TableName() string { + return "central_kitchen_for_school_package_ord" +} diff --git a/app/db/db_central_kitchen_for_school_package_with_day.go b/app/db/db_central_kitchen_for_school_package_with_day.go new file mode 100644 index 0000000..8c15ea2 --- /dev/null +++ b/app/db/db_central_kitchen_for_school_package_with_day.go @@ -0,0 +1,81 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolPackageWithDayDb struct { + Db *xorm.Engine `json:"db"` + PackageId int `json:"package_id"` +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) Set(packageId int) { // set方法 + centralKitchenForSchoolPackageWithDayDb.Db = Db + centralKitchenForSchoolPackageWithDayDb.PackageId = packageId +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) GetCentralKitchenForSchoolPackageWithDay(id int) (m *model.CentralKitchenForSchoolPackageWithDay, err error) { + m = new(model.CentralKitchenForSchoolPackageWithDay) + has, err := centralKitchenForSchoolPackageWithDayDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) FindCentralKitchenForSchoolPackageWithDay() (*[]model.CentralKitchenForSchoolPackageWithDay, error) { + var m []model.CentralKitchenForSchoolPackageWithDay + if err := centralKitchenForSchoolPackageWithDayDb.Db.Where("package_id =?", centralKitchenForSchoolPackageWithDayDb.PackageId).Asc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) CentralKitchenForSchoolPackageWithDayInsert(m *model.CentralKitchenForSchoolPackageWithDay) (int, error) { + _, err := centralKitchenForSchoolPackageWithDayDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) BatchAddCentralKitchenForSchoolPackageWithDaysBySession(session *xorm.Session, mm []*model.CentralKitchenForSchoolPackageWithDay) (int64, error) { + affected, err := session.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) BatchAddCentralKitchenForSchoolPackageWithDays(mm []*model.CentralKitchenForSchoolPackageWithDay) (int64, error) { + affected, err := centralKitchenForSchoolPackageWithDayDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) CentralKitchenForSchoolPackageWithDayDeleteBySession(session *xorm.Session) (int64, error) { + return Db.Where("package_id = ?", centralKitchenForSchoolPackageWithDayDb.PackageId).Delete(model.CentralKitchenForSchoolPackageWithDay{}) +} + +func (centralKitchenForSchoolPackageWithDayDb *CentralKitchenForSchoolPackageWithDayDb) CentralKitchenForSchoolPackageWithDayUpdate(Db *xorm.Engine, id interface{}, m *model.CentralKitchenForSchoolPackageWithDay, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = Db.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = Db.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_central_kitchen_for_school_set.go b/app/db/db_central_kitchen_for_school_set.go new file mode 100644 index 0000000..4f1fbdc --- /dev/null +++ b/app/db/db_central_kitchen_for_school_set.go @@ -0,0 +1,74 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolSetDb struct { + Db *xorm.Engine `json:"db"` + EnterpriseId int `json:"enterprise_id"` +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) Set(enterpriseId int) { // set方法 + centralKitchenForSchoolSetDb.Db = Db + centralKitchenForSchoolSetDb.EnterpriseId = enterpriseId +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) GetCentralKitchenForSchoolSet() (m *model.CentralKitchenForSchoolSet, err error) { + m = new(model.CentralKitchenForSchoolSet) + has, err := centralKitchenForSchoolSetDb.Db.Where("enterprise_id =?", centralKitchenForSchoolSetDb.EnterpriseId).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) CentralKitchenForSchoolSetInsert(m *model.CentralKitchenForSchoolSet) (int, error) { + _, err := centralKitchenForSchoolSetDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) BatchAddCentralKitchenForSchoolSets(mm []*model.CentralKitchenForSchoolSet) (int64, error) { + affected, err := centralKitchenForSchoolSetDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) CentralKitchenForSchoolSetDeleteById(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.CentralKitchenForSchoolSet{}) + } else { + return Db.Where("id = ?", id).Delete(model.CentralKitchenForSchoolSet{}) + } +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) CentralKitchenForSchoolSetDelete() (int64, error) { + return Db.Where("enterprise_id = ?", centralKitchenForSchoolSetDb.EnterpriseId).Delete(model.CentralKitchenForSchoolSet{}) +} + +func (centralKitchenForSchoolSetDb *CentralKitchenForSchoolSetDb) CentralKitchenForSchoolSetUpdate(id interface{}, m *model.CentralKitchenForSchoolSet, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = centralKitchenForSchoolSetDb.Db.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = centralKitchenForSchoolSetDb.Db.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_central_kitchen_for_school_user_refund_day.go b/app/db/db_central_kitchen_for_school_user_refund_day.go new file mode 100644 index 0000000..2df045d --- /dev/null +++ b/app/db/db_central_kitchen_for_school_user_refund_day.go @@ -0,0 +1,111 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolUserRefundDayDb struct { + Db *xorm.Engine `json:"db"` + RecordsId int `json:"records_id"` +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) Set(recordsId int) { // set方法 + centralKitchenForSchoolUserRefundDayDb.Db = Db + centralKitchenForSchoolUserRefundDayDb.RecordsId = recordsId +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) GetCentralKitchenForSchoolUserRefundDay(id int) (m *model.CentralKitchenForSchoolUserRefundDay, err error) { + m = new(model.CentralKitchenForSchoolUserRefundDay) + has, err := centralKitchenForSchoolUserRefundDayDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) FindCentralKitchenForSchoolUserRefundDay() (*[]model.CentralKitchenForSchoolUserRefundDay, error) { + var m []model.CentralKitchenForSchoolUserRefundDay + if err := centralKitchenForSchoolUserRefundDayDb.Db.Where("records_id =?", centralKitchenForSchoolUserRefundDayDb.RecordsId).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) FindCentralKitchenForSchoolUserRefundDayByUid(uid int) (*[]model.CentralKitchenForSchoolUserRefundDay, error) { + var m []model.CentralKitchenForSchoolUserRefundDay + if err := centralKitchenForSchoolUserRefundDayDb.Db.Where("uid =?", uid).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) CentralKitchenForSchoolUserRefundDayInsert(m *model.CentralKitchenForSchoolUserRefundDay) (int, error) { + _, err := centralKitchenForSchoolUserRefundDayDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) BatchAddCentralKitchenForSchoolUserRefundDays(mm []*model.CentralKitchenForSchoolUserRefundDay) (int64, error) { + affected, err := centralKitchenForSchoolUserRefundDayDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) CentralKitchenForSchoolUserRefundDayDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.CentralKitchenForSchoolUserRefundDay{}) + } else { + return Db.Where("id = ?", id).Delete(model.CentralKitchenForSchoolUserRefundDay{}) + } +} + +func (centralKitchenForSchoolUserRefundDayDb *CentralKitchenForSchoolUserRefundDayDb) CentralKitchenForSchoolUserRefundDayUpdate(id interface{}, m *model.CentralKitchenForSchoolUserRefundDay, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = centralKitchenForSchoolUserRefundDayDb.Db.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = centralKitchenForSchoolUserRefundDayDb.Db.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} + +type CentralKitchenForSchoolUserRefundDayWithCentralKitchenForSchoolUserWithDay struct { + model.CentralKitchenForSchoolUserRefundDay `xorm:"extends"` + model.CentralKitchenForSchoolUserWithDay `xorm:"extends"` +} + +func (CentralKitchenForSchoolUserRefundDayWithCentralKitchenForSchoolUserWithDay) TableName() string { + return "central_kitchen_for_school_user_refund_day" +} + +type CentralKitchenForSchoolUserRefundDayWithData struct { + model.CentralKitchenForSchoolUserRefundDay `xorm:"extends"` + model.CentralKitchenForSchoolUserWithDay `xorm:"extends"` + model.CentralKitchenForSchoolPackageOrd `xorm:"extends"` + model.UserIdentity `xorm:"extends"` + model.ClassWithUser `xorm:"extends"` + model.Enterprise `xorm:"extends"` + model.Class `xorm:"extends"` + model.Grade `xorm:"extends"` + model.User `xorm:"extends"` +} + +func (CentralKitchenForSchoolUserRefundDayWithData) TableName() string { + return "central_kitchen_for_school_user_refund_day" +} diff --git a/app/db/db_central_kitchen_for_school_user_with_day.go b/app/db/db_central_kitchen_for_school_user_with_day.go new file mode 100644 index 0000000..d717d7e --- /dev/null +++ b/app/db/db_central_kitchen_for_school_user_with_day.go @@ -0,0 +1,106 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolUserWithDayDb struct { + Db *xorm.Engine `json:"db"` + Uid int `json:"uid"` +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) Set(uid int) { // set方法 + centralKitchenForSchoolUserWithDayDb.Db = Db + centralKitchenForSchoolUserWithDayDb.Uid = uid +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) GetCentralKitchenForSchoolUserWithDay(id int) (m *model.CentralKitchenForSchoolUserWithDay, err error) { + m = new(model.CentralKitchenForSchoolUserWithDay) + has, err := centralKitchenForSchoolUserWithDayDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) FindCentralKitchenForSchoolUserWithDay() (*[]model.CentralKitchenForSchoolUserWithDay, error) { + var m []model.CentralKitchenForSchoolUserWithDay + if err := centralKitchenForSchoolUserWithDayDb.Db.Where("uid =?", centralKitchenForSchoolUserWithDayDb.Uid).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) FindCentralKitchenForSchoolUserWithDayByOrdNo(ordNo string) (*[]model.CentralKitchenForSchoolUserWithDay, error) { + var m []model.CentralKitchenForSchoolUserWithDay + if err := centralKitchenForSchoolUserWithDayDb.Db.Where("ord_no =?", ordNo).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) FindCentralKitchenForSchoolUserWithDayByOrdNoAndState(ordNo string, state int) (*[]model.CentralKitchenForSchoolUserWithDay, error) { + var m []model.CentralKitchenForSchoolUserWithDay + if err := centralKitchenForSchoolUserWithDayDb.Db.Where("ord_no =?", ordNo).And("state =?", state).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) CentralKitchenForSchoolUserWithDayInsert(m *model.CentralKitchenForSchoolUserWithDay) (int, error) { + _, err := centralKitchenForSchoolUserWithDayDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) BatchAddCentralKitchenForSchoolUserWithDays(mm []*model.CentralKitchenForSchoolUserWithDay) (int64, error) { + affected, err := centralKitchenForSchoolUserWithDayDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) CentralKitchenForSchoolUserWithDayDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.CentralKitchenForSchoolUserWithDay{}) + } else { + return Db.Where("id = ?", id).Delete(model.CentralKitchenForSchoolUserWithDay{}) + } +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) CentralKitchenForSchoolUserWithDayUpdate(id interface{}, m *model.CentralKitchenForSchoolUserWithDay, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = centralKitchenForSchoolUserWithDayDb.Db.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = centralKitchenForSchoolUserWithDayDb.Db.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolUserWithDayDb *CentralKitchenForSchoolUserWithDayDb) CentralKitchenForSchoolUserWithDayBatchUpdate(ids interface{}, m []*model.CentralKitchenForSchoolUserWithDay) (int64, error) { + var ( + affected int64 + err error + ) + affected, err = centralKitchenForSchoolUserWithDayDb.Db.In("id", ids).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_central_kitchen_for_school_with_spec.go b/app/db/db_central_kitchen_for_school_with_spec.go new file mode 100644 index 0000000..aa25507 --- /dev/null +++ b/app/db/db_central_kitchen_for_school_with_spec.go @@ -0,0 +1,86 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CentralKitchenForSchoolWithSpec struct { + Db *xorm.Engine `json:"db"` + EnterpriseId int `json:"enterprise_id"` +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) Set(enterpriseId int) { // set方法 + centralKitchenForSchoolWithSpecDb.Db = Db + centralKitchenForSchoolWithSpecDb.EnterpriseId = enterpriseId +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) GetCentralKitchenForSchoolWithSpecById(id int) (m *model.CentralKitchenForSchoolWithSpec, err error) { + m = new(model.CentralKitchenForSchoolWithSpec) + has, err := centralKitchenForSchoolWithSpecDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) GetCentralKitchenForSchoolWithSpec() (m *model.CentralKitchenForSchoolWithSpec, err error) { + m = new(model.CentralKitchenForSchoolWithSpec) + has, err := centralKitchenForSchoolWithSpecDb.Db.Where("enterprise_id =?", centralKitchenForSchoolWithSpecDb.EnterpriseId).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) CentralKitchenForSchoolWithSpecInsert(m *model.CentralKitchenForSchoolWithSpec) (int, error) { + _, err := centralKitchenForSchoolWithSpecDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) BatchAddCentralKitchenForSchoolWithSpecs(mm []*model.CentralKitchenForSchoolWithSpec) (int64, error) { + affected, err := centralKitchenForSchoolWithSpecDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) CentralKitchenForSchoolWithSpecDeleteById(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.CentralKitchenForSchoolWithSpec{}) + } else { + return Db.Where("id = ?", id).Delete(model.CentralKitchenForSchoolWithSpec{}) + } +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) CentralKitchenForSchoolWithSpecDelete() (int64, error) { + return Db.Where("enterprise_id = ?", centralKitchenForSchoolWithSpecDb.EnterpriseId).Delete(model.CentralKitchenForSchoolWithSpec{}) +} + +func (centralKitchenForSchoolWithSpecDb *CentralKitchenForSchoolWithSpec) CentralKitchenForSchoolWithSpecUpdate(id interface{}, m *model.CentralKitchenForSchoolWithSpec, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = centralKitchenForSchoolWithSpecDb.Db.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = centralKitchenForSchoolWithSpecDb.Db.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_class.go b/app/db/db_class.go new file mode 100644 index 0000000..60d38b3 --- /dev/null +++ b/app/db/db_class.go @@ -0,0 +1,72 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type ClassDb struct { + Db *xorm.Engine `json:"db"` + GradeId int `json:"enterprise_id"` +} + +func (classDb *ClassDb) Set(gradeId int) { // set方法 + classDb.Db = Db + classDb.GradeId = gradeId +} + +func (classDb *ClassDb) FindClass() (*[]model.Class, error) { + var m []model.Class + if classDb.GradeId == 0 { + if err := classDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := classDb.Db.Desc("id").Where("grade_id =?", classDb.GradeId).Find(&m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (classDb *ClassDb) ClassDeleteBySession(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return classDb.Db.In("id", id).Delete(model.Class{}) + } else { + return classDb.Db.Where("id = ?", id).Delete(model.Class{}) + } +} + +func (classDb *ClassDb) ClassDeleteBySessionForGrade(session *xorm.Session, gradeId interface{}) (int64, error) { + if reflect.TypeOf(gradeId).Kind() == reflect.Slice { + return session.In("grade_id", gradeId).Delete(model.Class{}) + } else { + return session.Where("grade_id = ?", gradeId).Delete(model.Class{}) + } +} +func (classDb *ClassDb) ClassDeleteBySessionForEnterprise(session *xorm.Session, enterpriseId interface{}) (int64, error) { + if reflect.TypeOf(enterpriseId).Kind() == reflect.Slice { + return session.In("enterprise_id", enterpriseId).Delete(model.Class{}) + } else { + return session.Where("enterprise_id = ?", enterpriseId).Delete(model.Class{}) + } +} + +func (classDb *ClassDb) BatchAddClass(mm []*model.Class) (int64, error) { + affected, err := classDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (classDb *ClassDb) CountClassForEnterprise(enterpriseId int) (total int64, err error) { + var m model.Class + total, err = classDb.Db.Where("enterprise_id =?", enterpriseId).Count(&m) + if err != nil { + return + } + return +} diff --git a/app/db/db_class_with_user.go b/app/db/db_class_with_user.go new file mode 100644 index 0000000..ba47d89 --- /dev/null +++ b/app/db/db_class_with_user.go @@ -0,0 +1,103 @@ +package db + +import ( + model2 "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type ClassWithUser struct { + model2.ClassWithUser `xorm:"extends"` + model2.UserIdentity `xorm:"extends"` + model2.Class `xorm:"extends"` + model2.Grade `xorm:"extends"` + model2.Enterprise `xorm:"extends"` +} + +func (ClassWithUser) TableName() string { + return "class_with_user" +} + +type ClassWithUserDb struct { + Db *xorm.Engine `json:"db"` +} + +func (classWithUserDb *ClassWithUserDb) Set() { // set方法 + classWithUserDb.Db = Db +} + +func (classWithUserDb *ClassWithUserDb) GetInfoByUserIdentityId(userIdentityId int) (m *ClassWithUser, err error) { + m = new(ClassWithUser) + has, err := classWithUserDb.Db.Where("class_with_user.user_identity_id =?", userIdentityId). + Join("LEFT", "user_identity", "class_with_user.user_identity_id = user_identity.id"). + Join("LEFT", "class", "class_with_user.class_id = class.id"). + Join("LEFT", "grade", "class.grade_id = grade.id"). + Join("LEFT", "enterprise", "grade.enterprise_id = enterprise.id"). + Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (classWithUserDb *ClassWithUserDb) GetClassWithUserByUserIdentityId(userIdentityId int) (m *model2.ClassWithUser, err error) { + m = new(model2.ClassWithUser) + has, err := classWithUserDb.Db.Where("user_identity_id =?", userIdentityId).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (classWithUserDb *ClassWithUserDb) ClassWithUserInsert(m *model2.ClassWithUser) (int, error) { + _, err := classWithUserDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (classWithUserDb *ClassWithUserDb) ClassWithUserInsertBySession(session *xorm.Session, m *model2.ClassWithUser) (int, error) { + _, err := session.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (classWithUserDb *ClassWithUserDb) FindUserIdentity(classId interface{}) (*[]model2.ClassWithUser, error) { + var m []model2.ClassWithUser + if reflect.TypeOf(classId).Kind() == reflect.Slice { + if err := classWithUserDb.Db.In("class_id", classId).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := classWithUserDb.Db.Where("class_id =?", classId).Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (classWithUserDb *ClassWithUserDb) ClassWithUserUpdateByUserIdentity(userIdentityId interface{}, m *model2.ClassWithUser, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = classWithUserDb.Db.Where("user_identity_id=?", userIdentityId).Cols(forceColums...).Update(m) + } else { + affected, err = classWithUserDb.Db.Where("user_identity_id=?", userIdentityId).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_company.go b/app/db/db_company.go new file mode 100644 index 0000000..47d0801 --- /dev/null +++ b/app/db/db_company.go @@ -0,0 +1,78 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type CompanyDb struct { + Db *xorm.Engine `json:"db"` +} + +func (companyDb *CompanyDb) Set() { // set方法 + companyDb.Db = Db +} + +func (companyDb *CompanyDb) GetCompany(id int) (m *model.Company, err error) { + m = new(model.Company) + has, err := companyDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (companyDb *CompanyDb) FindCompany(limit, start int) (*[]model.Company, error) { + var m []model.Company + if limit == 0 || start == 0 { + if err := companyDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := companyDb.Db.Desc("id").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (companyDb *CompanyDb) GetCompanyByName(name string) (m *model.Company, err error) { + m = new(model.Company) + has, err := companyDb.Db.Where("name =?", name).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (companyDb *CompanyDb) CompanyInsert(m *model.Company) (int, error) { + _, err := companyDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (companyDb *CompanyDb) CompanyUpdate(m *model.Company, columns ...string) (int64, error) { + affected, err := companyDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (companyDb *CompanyDb) CompanyDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.Company{}) + } else { + return Db.Where("id = ?", id).Delete(model.Company{}) + } +} diff --git a/app/db/db_enterprise.go b/app/db/db_enterprise.go new file mode 100644 index 0000000..bcb82b4 --- /dev/null +++ b/app/db/db_enterprise.go @@ -0,0 +1,86 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type EnterpriseDb struct { + Db *xorm.Engine `json:"db"` +} + +func (enterpriseDb *EnterpriseDb) Set() { // set方法 + enterpriseDb.Db = Db +} + +func (enterpriseDb *EnterpriseDb) GetEnterprise(id int) (m *model.Enterprise, err error) { + m = new(model.Enterprise) + has, err := enterpriseDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (enterpriseDb *EnterpriseDb) EnterpriseDeleteBySession(session *xorm.Session, id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return session.In("id", id).Delete(model.Enterprise{}) + } else { + return session.Where("id = ?", id).Delete(model.Enterprise{}) + } +} + +func (enterpriseDb *EnterpriseDb) FindEnterprise(limit, start int) (*[]model.Enterprise, error) { + var m []model.Enterprise + if limit == 0 || start == 0 { + if err := enterpriseDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := enterpriseDb.Db.Desc("id").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (enterpriseDb *EnterpriseDb) GetEnterpriseByName(name string) (m *model.Enterprise, err error) { + m = new(model.Enterprise) + has, err := enterpriseDb.Db.Where("name =?", name).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (enterpriseDb *EnterpriseDb) EnterpriseInsert(m *model.Enterprise) (int, error) { + _, err := enterpriseDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (enterpriseDb *EnterpriseDb) EnterpriseUpdate(m *model.Enterprise, columns ...string) (int64, error) { + affected, err := enterpriseDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (adminDb *AdminDb) FindEnterpriseGradeClass(enterpriseId int) (list []*AdminRolePermission, total int64, err error) { + total, err = adminDb.Db.Where("enterprise.id =?", enterpriseId). + Join("LEFT", "grade", "enterprise.id = grade.enterprise_id"). + Join("LEFT", "class", "class.grade_id = grade.id"). + FindAndCount(&list) + return +} diff --git a/app/db/db_enterprise_grade_class.go b/app/db/db_enterprise_grade_class.go new file mode 100644 index 0000000..0b25f14 --- /dev/null +++ b/app/db/db_enterprise_grade_class.go @@ -0,0 +1,15 @@ +package db + +import ( + model2 "applet/app/db/model" +) + +type EnterpriseGradeClass struct { + model2.Enterprise `xorm:"extends"` + model2.Grade `xorm:"extends"` + model2.Class `xorm:"extends"` +} + +func (EnterpriseGradeClass) TableName() string { + return "enterprise" +} diff --git a/app/db/db_grade.go b/app/db/db_grade.go new file mode 100644 index 0000000..ec04217 --- /dev/null +++ b/app/db/db_grade.go @@ -0,0 +1,50 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type GradeDb struct { + Db *xorm.Engine `json:"db"` + EnterpriseId int `json:"enterprise_id"` +} + +func (gradeDb *GradeDb) Set(enterpriseId int) { // set方法 + gradeDb.Db = Db + gradeDb.EnterpriseId = enterpriseId +} + +func (gradeDb *GradeDb) FindGrade() (*[]model.Grade, error) { + var m []model.Grade + if err := gradeDb.Db.Desc("id").Where("enterprise_id =?", gradeDb.EnterpriseId).Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (gradeDb *GradeDb) GradeDeleteBySession(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return gradeDb.Db.In("id", id).Delete(model.Grade{}) + } else { + return gradeDb.Db.Where("id = ?", id).Delete(model.Grade{}) + } +} + +func (gradeDb *GradeDb) ClassDeleteBySessionForEnterprise(session *xorm.Session, enterpriseId interface{}) (int64, error) { + if reflect.TypeOf(enterpriseId).Kind() == reflect.Slice { + return session.In("enterprise_id", enterpriseId).Delete(model.Grade{}) + } else { + return session.Where("enterprise_id = ?", enterpriseId).Delete(model.Grade{}) + } +} + +func (gradeDb *GradeDb) GradeInsert(m *model.Grade) (int, error) { + _, err := gradeDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} diff --git a/app/db/db_notice.go b/app/db/db_notice.go new file mode 100644 index 0000000..8e9b08f --- /dev/null +++ b/app/db/db_notice.go @@ -0,0 +1,86 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type NoticeDb struct { + Db *xorm.Engine `json:"db"` +} + +func (noticeDb *NoticeDb) Set() { // set方法 + noticeDb.Db = Db +} + +func (noticeDb *NoticeDb) GetNotice(id int) (m *model.Notice, err error) { + m = new(model.Notice) + has, err := noticeDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (noticeDb *NoticeDb) FindNoticeById(ids interface{}) (*[]model.Notice, error) { + var m []model.Notice + if err := noticeDb.Db.In("id", ids).Desc("sort").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (noticeDb *NoticeDb) FindNotice(limit, start int) (*[]model.Notice, error) { + var m []model.Notice + if limit == 0 || start == 0 { + if err := noticeDb.Db.Asc("sort").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := noticeDb.Db.Asc("sort").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (noticeDb *NoticeDb) GetNoticeByName(name string) (m *model.Notice, err error) { + m = new(model.Notice) + has, err := noticeDb.Db.Where("name =?", name).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (noticeDb *NoticeDb) NoticeInsert(m *model.Notice) (int, error) { + _, err := noticeDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (noticeDb *NoticeDb) NoticeUpdate(m *model.Notice, columns ...string) (int64, error) { + affected, err := noticeDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (noticeDb *NoticeDb) NoticeDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.Notice{}) + } else { + return Db.Where("id = ?", id).Delete(model.Notice{}) + } +} diff --git a/app/db/db_permission.go b/app/db/db_permission.go new file mode 100644 index 0000000..ee4e724 --- /dev/null +++ b/app/db/db_permission.go @@ -0,0 +1,13 @@ +package db + +import ( + "xorm.io/xorm" +) + +type PermissionDb struct { + Db *xorm.Engine `json:"db"` +} + +func (permissionDb *PermissionDb) Set() { // set方法 + permissionDb.Db = Db +} diff --git a/app/db/db_permission_group.go b/app/db/db_permission_group.go new file mode 100644 index 0000000..5538e6b --- /dev/null +++ b/app/db/db_permission_group.go @@ -0,0 +1,23 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +type PermissionGroupDb struct { + Db *xorm.Engine `json:"db"` +} + +func (permissionGroupDb *PermissionGroupDb) Set() { // set方法 + permissionGroupDb.Db = Db +} + +func (permissionGroupDb *PermissionGroupDb) FindPermissionGroup() (*[]model.PermissionGroup, error) { + var m []model.PermissionGroup + if err := permissionGroupDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} diff --git a/app/db/db_permission_group_permission.go b/app/db/db_permission_group_permission.go new file mode 100644 index 0000000..e5fb0c1 --- /dev/null +++ b/app/db/db_permission_group_permission.go @@ -0,0 +1,13 @@ +package db + +import ( + "xorm.io/xorm" +) + +type PermissionGroupPermissionDb struct { + Db *xorm.Engine `json:"db"` +} + +func (PermissionGroupPermissionDb *PermissionGroupPermissionDb) Set() { // set方法 + PermissionGroupPermissionDb.Db = Db +} diff --git a/app/db/db_role.go b/app/db/db_role.go new file mode 100644 index 0000000..830c1af --- /dev/null +++ b/app/db/db_role.go @@ -0,0 +1,80 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type RoleDb struct { + Db *xorm.Engine `json:"db"` + Id int `json:"id"` +} + +func (roleDb *RoleDb) Set(id int) { // set方法 + roleDb.Db = Db + roleDb.Id = id +} + +func (roleDb *RoleDb) FindRole() (*[]model.Role, error) { + var m []model.Role + if err := roleDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (roleDb *RoleDb) GetRole() (m *model.Role, err error) { + m = new(model.Role) + has, err := roleDb.Db.Where("id = ?", roleDb.Id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (roleDb *RoleDb) UpdateRole(m *model.Role, columns ...string) (int64, error) { + affected, err := roleDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (roleDb *RoleDb) RoleDeleteBySession(session *xorm.Session, id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return session.In("id", id).Delete(model.Role{}) + } else { + return session.Where("id = ?", id).Delete(model.Role{}) + } +} + +func (roleDb *RoleDb) RoleInsert(m *model.Role) (int, error) { + _, err := roleDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (roleDb *RoleDb) FindPermissionGroupByRole(roleId int) (list []*RolePermissionGroup, total int64, err error) { + total, err = roleDb.Db.Where("role.id =?", roleId). + Join("LEFT", "role_permission_group", "role_permission_group.role_id = role.id"). + Join("LEFT", "permission_group", "permission_group.id = role_permission_group.group_id"). + FindAndCount(&list) + return +} + +type RolePermissionGroup struct { + model.Role `xorm:"extends"` + model.RolePermissionGroup `xorm:"extends"` + model.PermissionGroup `xorm:"extends"` +} + +func (RolePermissionGroup) TableName() string { + return "role" +} diff --git a/app/db/db_role_permission_group.go b/app/db/db_role_permission_group.go new file mode 100644 index 0000000..1f3a88d --- /dev/null +++ b/app/db/db_role_permission_group.go @@ -0,0 +1,31 @@ +package db + +import ( + "applet/app/db/model" + "reflect" + "xorm.io/xorm" +) + +type RolePermissionGroupDb struct { + Db *xorm.Engine `json:"db"` +} + +func (rolePermissionGroupDb *RolePermissionGroupDb) Set() { // set方法 + rolePermissionGroupDb.Db = Db +} + +func (rolePermissionGroupDb *RolePermissionGroupDb) RolePermissionGroupDeleteForRoleBySession(session *xorm.Session, roleId interface{}) (int64, error) { + if reflect.TypeOf(roleId).Kind() == reflect.Slice { + return session.In("role_id", roleId).Delete(model.RolePermissionGroup{}) + } else { + return session.Where("role_id = ?", roleId).Delete(model.RolePermissionGroup{}) + } +} + +func (rolePermissionGroupDb *RolePermissionGroupDb) BatchAddRolePermissionGroupBySession(session *xorm.Session, mm []*model.RolePermissionGroup) (int64, error) { + affected, err := session.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_self_support_for_school_info.go b/app/db/db_self_support_for_school_info.go new file mode 100644 index 0000000..b52a09e --- /dev/null +++ b/app/db/db_self_support_for_school_info.go @@ -0,0 +1,80 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type SelfSupportForSchoolInfoDb struct { + Db *xorm.Engine `json:"db"` + EnterpriseId int `json:"enterprise_id"` +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) Set(enterpriseId int) { // set方法 + selfSupportForSchoolInfoDb.Db = Db + selfSupportForSchoolInfoDb.EnterpriseId = enterpriseId +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) GetSelfSupportForSchoolInfo() (m *model.SelfSupportForSchoolInfo, err error) { + m = new(model.SelfSupportForSchoolInfo) + has, err := selfSupportForSchoolInfoDb.Db.Where("enterprise_id =?", selfSupportForSchoolInfoDb.EnterpriseId).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) FindSelfSupportForSchoolInfo(limit, start int) (*[]model.SelfSupportForSchoolInfo, error) { + var m []model.SelfSupportForSchoolInfo + if limit == 0 || start == 0 { + if err := selfSupportForSchoolInfoDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := selfSupportForSchoolInfoDb.Db.Desc("id").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) GetSelfSupportForSchoolInfoByName(name string) (m *model.SelfSupportForSchoolInfo, err error) { + m = new(model.SelfSupportForSchoolInfo) + has, err := selfSupportForSchoolInfoDb.Db.Where("name =?", name).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) SelfSupportForSchoolInfoInsert(m *model.SelfSupportForSchoolInfo) (int, error) { + _, err := selfSupportForSchoolInfoDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) SelfSupportForSchoolInfoUpdate(m *model.SelfSupportForSchoolInfo, columns ...string) (int64, error) { + affected, err := selfSupportForSchoolInfoDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (selfSupportForSchoolInfoDb *SelfSupportForSchoolInfoDb) SelfSupportForSchoolInfoDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.SelfSupportForSchoolInfo{}) + } else { + return Db.Where("id = ?", id).Delete(model.SelfSupportForSchoolInfo{}) + } +} diff --git a/app/db/db_self_support_for_user_face_info.go b/app/db/db_self_support_for_user_face_info.go new file mode 100644 index 0000000..5bc7bb8 --- /dev/null +++ b/app/db/db_self_support_for_user_face_info.go @@ -0,0 +1,80 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type SelfSupportForUserFaceInfoDb struct { + Db *xorm.Engine `json:"db"` + UserIdentityId int `json:"user_identity_id"` +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) Set(userIdentityId int) { // set方法 + selfSupportForUserFaceInfoDb.Db = Db + selfSupportForUserFaceInfoDb.UserIdentityId = userIdentityId +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) GetSelfSupportForUserFaceInfo() (m *model.SelfSupportForUserFaceInfo, err error) { + m = new(model.SelfSupportForUserFaceInfo) + has, err := selfSupportForUserFaceInfoDb.Db.Where("user_identity_id =?", selfSupportForUserFaceInfoDb.UserIdentityId).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) FindSelfSupportForUserFaceInfo(limit, start int) (*[]model.SelfSupportForUserFaceInfo, error) { + var m []model.SelfSupportForUserFaceInfo + if limit == 0 || start == 0 { + if err := selfSupportForUserFaceInfoDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := selfSupportForUserFaceInfoDb.Db.Desc("id").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) GetSelfSupportForUserFaceInfoByName(name string) (m *model.SelfSupportForUserFaceInfo, err error) { + m = new(model.SelfSupportForUserFaceInfo) + has, err := selfSupportForUserFaceInfoDb.Db.Where("name =?", name).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) SelfSupportForUserFaceInfoInsert(m *model.SelfSupportForUserFaceInfo) (int, error) { + _, err := selfSupportForUserFaceInfoDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) SelfSupportForUserFaceInfoUpdate(m *model.SelfSupportForUserFaceInfo, columns ...string) (int64, error) { + affected, err := selfSupportForUserFaceInfoDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (selfSupportForUserFaceInfoDb *SelfSupportForUserFaceInfoDb) SelfSupportForUserFaceInfoDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.SelfSupportForUserFaceInfo{}) + } else { + return Db.Where("id = ?", id).Delete(model.SelfSupportForUserFaceInfo{}) + } +} diff --git a/app/db/db_sys_cfg.go b/app/db/db_sys_cfg.go new file mode 100644 index 0000000..cd20e71 --- /dev/null +++ b/app/db/db_sys_cfg.go @@ -0,0 +1,119 @@ +package db + +import ( + "applet/app/admin/md" + "applet/app/db/model" + "applet/app/utils/cache" + "applet/app/utils/logx" + "fmt" + "xorm.io/xorm" +) + +type SysCfgDb struct { + Db *xorm.Engine `json:"db"` +} + +func (sysCfgDb *SysCfgDb) Set() { // set方法 + sysCfgDb.Db = Db +} + +func (sysCfgDb *SysCfgDb) SysCfgGetAll() (*[]model.SysCfg, error) { + var cfgList []model.SysCfg + if err := Db.Cols("key,val,memo").Find(&cfgList); err != nil { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +func (sysCfgDb *SysCfgDb) SysCfgGetOneNoDataNoErr(key string) (*model.SysCfg, error) { + var cfgList model.SysCfg + has, err := Db.Where("`key`=?", key).Get(&cfgList) + if err != nil { + return nil, logx.Error(err) + } + if !has { + return nil, nil + } + return &cfgList, nil +} + +func (sysCfgDb *SysCfgDb) SysCfgGetOne(key string) (*model.SysCfg, error) { + var cfgList model.SysCfg + if has, err := Db.Where("`key`=?", key).Get(&cfgList); err != nil || has == false { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +func (sysCfgDb *SysCfgDb) SysCfgInsert(key, val, memo string) bool { + cfg := model.SysCfg{Key: key, Val: val, Memo: memo} + _, err := Db.InsertOne(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} + +func (sysCfgDb *SysCfgDb) SysCfgUpdate(key, val string) bool { + cfg := model.SysCfg{Key: key, Val: val} + _, err := Db.Where("`key`=?", key).Cols("val").Update(&cfg) + if err != nil { + logx.Error(err) + return false + } + sysCfgDb.SysCfgDel(key) + return true +} + +func (sysCfgDb *SysCfgDb) SysCfgGetWithDb(HKey string) string { + cacheKey := fmt.Sprintf(md.AppCfgCacheKey, HKey[0:1]) + get, err := cache.HGetString(cacheKey, HKey) + if err != nil || get == "" { + cfg, err := sysCfgDb.SysCfgGetOne(HKey) + if err != nil || cfg == nil { + _ = logx.Error(err) + return "" + } + + // key是否存在 + cacheKeyExist := false + if cache.Exists(cacheKey) { + cacheKeyExist = true + } + + // 设置缓存 + _, err = cache.HSet(cacheKey, HKey, cfg.Val) + if err != nil { + _ = logx.Error(err) + return "" + } + if !cacheKeyExist { // 如果是首次设置 设置过期时间 + _, err := cache.Expire(cacheKey, md.CfgCacheTime) + if err != nil { + _ = logx.Error(err) + return "" + } + } + return cfg.Val + } + return get +} + +func (sysCfgDb *SysCfgDb) SysCfgDel(HKey string) error { + cacheKey := fmt.Sprintf(md.AppCfgCacheKey, HKey[0:1]) + _, err := cache.HDel(cacheKey, HKey) + if err != nil { + return err + } + return nil +} + +func (sysCfgDb *SysCfgDb) SysCfgFindWithDb(keys ...string) map[string]string { + res := map[string]string{} + for _, v := range keys { + val := sysCfgDb.SysCfgGetWithDb(v) + res[v] = val + } + return res +} diff --git a/app/db/db_user.go b/app/db/db_user.go new file mode 100644 index 0000000..47c95b6 --- /dev/null +++ b/app/db/db_user.go @@ -0,0 +1,91 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type UserDb struct { + Db *xorm.Engine `json:"db"` + CompanyId int `json:"company_id"` +} + +func (userDb *UserDb) Set() { // set方法 + userDb.Db = Db +} + +func (userDb *UserDb) GetUser(id int) (m *model.User, err error) { + m = new(model.User) + has, err := userDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (userDb *UserDb) GetUserByName(userName string) (m *model.User, err error) { + m = new(model.User) + has, err := userDb.Db.Where("name =?", userName).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (userDb *UserDb) GetUserByPhone(phone string) (m *model.User, err error) { + m = new(model.User) + has, err := userDb.Db.Where("phone =?", phone).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (userDb *UserDb) FindUser(limit, start int) (*[]model.User, error) { + var m []model.User + if limit == 0 || start == 0 { + if err := userDb.Db.Desc("id").Find(&m); err != nil { + return nil, logx.Error(err) + } + } else { + if err := userDb.Db.Desc("id").Limit(limit, start).Find(m); err != nil { + return nil, logx.Error(err) + } + } + return &m, nil +} + +func (userDb *UserDb) UserInsert(m *model.User) (int, error) { + _, err := userDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (userDb *UserDb) UpdateUser(m *model.User, columns ...string) (int64, error) { + affected, err := userDb.Db.Where("id =?", m.Id).Cols(columns...).Update(m) + if err != nil { + return 0, err + } + return affected, nil +} + +func (userDb *UserDb) DeleteUser(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return userDb.Db.In("enterprise_id", id).Delete(model.User{}) + } else { + return userDb.Db.Where("enterprise_id = ?", id).Delete(model.User{}) + } +} diff --git a/app/db/db_user_identity.go b/app/db/db_user_identity.go new file mode 100644 index 0000000..6da4a2a --- /dev/null +++ b/app/db/db_user_identity.go @@ -0,0 +1,146 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "reflect" + "xorm.io/xorm" +) + +type UserIdentityDb struct { + Db *xorm.Engine `json:"db"` + Uid int `json:"uid"` +} + +func (userIdentityDb *UserIdentityDb) Set(uid int) { // set方法 + userIdentityDb.Db = Db + userIdentityDb.Uid = uid +} + +func (userIdentityDb *UserIdentityDb) GetUserIdentity(id int) (m *model.UserIdentity, err error) { + m = new(model.UserIdentity) + has, err := userIdentityDb.Db.Where("id =?", id).Get(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (userIdentityDb *UserIdentityDb) UserIdentityExist(enterpriseId int, idNo string) (m *model.UserIdentity, err error) { + m = new(model.UserIdentity) + has, err := userIdentityDb.Db.Where("enterprise_id =?", enterpriseId).And("id_no =?", idNo).Exist(m) + if err != nil { + return nil, logx.Error(err) + } + if has == false { + return nil, nil + } + return m, nil +} + +func (userIdentityDb *UserIdentityDb) FindUserIdentity() (*[]UserIdentityWithEnterprise, error) { + var m []UserIdentityWithEnterprise + if err := userIdentityDb.Db. + Join("LEFT", "enterprise", "user_identity.enterprise_id = enterprise.id"). + Where("user_identity.uid =?", userIdentityDb.Uid).Desc("user_identity.id").Find(&m); err != nil { + return nil, logx.Error(err) + } + return &m, nil +} + +func (userIdentityDb *UserIdentityDb) UserIdentityInsert(m *model.UserIdentity) (int, error) { + _, err := userIdentityDb.Db.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (userIdentityDb *UserIdentityDb) UserIdentityInsertBySession(session *xorm.Session, m *model.UserIdentity) (int, error) { + _, err := session.InsertOne(m) + if err != nil { + return 0, err + } + return m.Id, nil +} + +func (userIdentityDb *UserIdentityDb) BatchAddUserIdentities(mm []*model.UserIdentity) (int64, error) { + affected, err := userIdentityDb.Db.Insert(mm) + if err != nil { + return 0, err + } + return affected, nil +} + +func (userIdentityDb *UserIdentityDb) UserIdentityDelete(id interface{}) (int64, error) { + if reflect.TypeOf(id).Kind() == reflect.Slice { + return Db.In("id", id).Delete(model.UserIdentity{}) + } else { + return Db.Where("id = ?", id).Delete(model.UserIdentity{}) + } +} + +func (userIdentityDb *UserIdentityDb) UserIdentityUpdate(id interface{}, m *model.UserIdentity, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = userIdentityDb.Db.Where("id=?", id).Cols(forceColums...).Update(m) + } else { + affected, err = userIdentityDb.Db.Where("id=?", id).Update(m) + } + if err != nil { + return 0, err + } + return affected, nil +} + +func (userIdentityDb *UserIdentityDb) CountUserIdentityForEnterprise(enterpriseId, identity int) (total int64, err error) { + var m model.UserIdentity + total, err = userIdentityDb.Db.Where("enterprise_id =? AND identity =?", enterpriseId, identity).Count(&m) + if err != nil { + return + } + return +} +func (userIdentityDb *UserIdentityDb) FindUserIdentityForEnterpriseByIdentity(enterpriseId, identity int) (*[]model.UserIdentity, error) { + var m []model.UserIdentity + err := userIdentityDb.Db.Where("enterprise_id =? AND identity =?", enterpriseId, identity).Find(&m) + if err != nil { + return nil, err + } + return &m, nil +} +func (userIdentityDb *UserIdentityDb) FindUserIdentityForEnterprise(enterpriseId int) (*[]model.UserIdentity, error) { + var m []model.UserIdentity + err := userIdentityDb.Db.Where("enterprise_id =?", enterpriseId).Find(&m) + if err != nil { + return nil, err + } + return &m, nil +} + +type UserIdentityWithUser struct { + model.UserIdentity `xorm:"extends"` + model.User `xorm:"extends"` + model.ClassWithUser `xorm:"extends"` + model.Class `xorm:"extends"` + model.Grade `xorm:"extends"` +} + +func (UserIdentityWithUser) TableName() string { + return "user_identity" +} + +type UserIdentityWithEnterprise struct { + model.UserIdentity `xorm:"extends"` + model.Enterprise `xorm:"extends"` +} + +func (UserIdentityWithEnterprise) TableName() string { + return "user_identity" +} diff --git a/app/db/model/admin.go b/app/db/model/admin.go new file mode 100644 index 0000000..d9bcbb3 --- /dev/null +++ b/app/db/model/admin.go @@ -0,0 +1,12 @@ +package model + +type Admin struct { + AdmId int `json:"adm_id" xorm:"not null comment('管理员id') INT(11)"` + Username string `json:"username" xorm:"not null default '' comment('用户名') VARCHAR(255)"` + Password string `json:"password" xorm:"not null default '' comment('密码') VARCHAR(255)"` + State int32 `json:"state" xorm:"not null default 1 comment('状态') TINYINT(1)"` + IsSuperAdministrator int32 `json:"is_super_administrator" xorm:"not null default 0 comment('是否为超级管理员(0:否 1:是)') TINYINT(1)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注信息') VARCHAR(255)"` + CreateAt string `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt string `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/admin_role.go b/app/db/model/admin_role.go new file mode 100644 index 0000000..379d278 --- /dev/null +++ b/app/db/model/admin_role.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type AdminRole struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + AdmId int `json:"adm_id" xorm:"not null default 0 comment('管理员id') INT(11)"` + RoleId int `json:"role_id" xorm:"not null default 0 comment('角色id') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/banner.go b/app/db/model/banner.go new file mode 100644 index 0000000..896b2fc --- /dev/null +++ b/app/db/model/banner.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type Banner struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + ImgUrl string `json:"img_url" xorm:"not null default '' comment('轮播图url') VARCHAR(255)"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') TINYINT(3)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/central_kitchen_for_school_package.go b/app/db/model/central_kitchen_for_school_package.go new file mode 100644 index 0000000..2d2fffa --- /dev/null +++ b/app/db/model/central_kitchen_for_school_package.go @@ -0,0 +1,15 @@ +package model + +type CentralKitchenForSchoolPackage struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('所属单位id') INT(11)"` + Year string `json:"year" xorm:"not null default '0000' comment('年份') VARCHAR(50)"` + Month string `json:"month" xorm:"not null default '00' comment('月份') VARCHAR(50)"` + TotalPrice string `json:"total_price" xorm:"not null default 0.00 comment('总价') DECIMAL(8,2)"` + StartDate string `json:"start_date" xorm:"not null default '0000-00-00' comment('起始时间') CHAR(50)"` + EndDate string `json:"end_date" xorm:"not null default '0000-00-00' comment('截止时间') CHAR(50)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:可用 2:不可用)') TINYINT(1)"` + IsDelete int `json:"is_delete" xorm:"not null default 0 comment('是否删除(0:未删除 1:已删除)') TINYINT(1)"` + CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/central_kitchen_for_school_package_ord.go b/app/db/model/central_kitchen_for_school_package_ord.go new file mode 100644 index 0000000..8c4ea80 --- /dev/null +++ b/app/db/model/central_kitchen_for_school_package_ord.go @@ -0,0 +1,18 @@ +package model + +type CentralKitchenForSchoolPackageOrd struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('所属单位id') INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户id') INT(11)"` + UserIdentityId int `json:"user_identity_id" xorm:"not null default 0 comment('用户身份id') INT(11)"` + TotalPrice string `json:"total_price" xorm:"not null default 0.00 comment('总价') DECIMAL(8,2)"` + Kind int `json:"kind" xorm:"not null default 1 comment('购买类型(1:按学期购买 2:按月购买 3:按天购买)') TINYINT(1)"` + OutTradeNo string `json:"out_trade_no" xorm:"not null default '' comment('商户订单号') VARCHAR(255)"` + TradeNo string `json:"trade_no" xorm:"not null default '' comment('支付宝交易号') VARCHAR(255)"` + State int `json:"state" xorm:"not null default 0 comment('支付状态(0:待支付 1:支付成功 2:支付失败)') TINYINT(1)"` + OrdState int `json:"ord_state" xorm:"not null default 0 comment('订单状态(0:待支付 1:预约成功 2:退款中 3:部分退款 4:已退款 5:已完成)') TINYINT(1)"` + ReqContent string `json:"req_content" xorm:"comment('请求内容') TEXT"` + WithDayData string `json:"with_day_data" xorm:"comment('待支付成功插入 central_kitchen_for_school_user_with_day') TEXT"` + CreateAt string `json:"create_at" xorm:"not null pk default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/central_kitchen_for_school_package_with_day.go b/app/db/model/central_kitchen_for_school_package_with_day.go new file mode 100644 index 0000000..599c5a1 --- /dev/null +++ b/app/db/model/central_kitchen_for_school_package_with_day.go @@ -0,0 +1,11 @@ +package model + +type CentralKitchenForSchoolPackageWithDay struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Date string `json:"date" xorm:"not null default '0000-00-00' comment('日期') CHAR(50)"` + PackageId int `json:"package_id" xorm:"not null default 0 comment('套餐id') INT(11)"` + IsOpenBreakfast int `json:"is_open_breakfast" xorm:"not null default 0 comment('是否开启早餐(1:开启 0:关闭)') TINYINT(1)"` + IsOpenLunch int `json:"is_open_lunch" xorm:"not null default 0 comment('是否开启午餐(1:开启 0:关闭)') TINYINT(1)"` + IsOpenDinner int `json:"is_open_dinner" xorm:"not null default 0 comment('是否开启晚餐(1:开启 0:关闭)') TINYINT(1)"` + IsOpenReplenish int `json:"is_open_replenish" xorm:"default 0 comment('是否开启补餐(1:开启 0:关闭)') TINYINT(1)"` +} diff --git a/app/db/model/central_kitchen_for_school_set.go b/app/db/model/central_kitchen_for_school_set.go new file mode 100644 index 0000000..dc38fe2 --- /dev/null +++ b/app/db/model/central_kitchen_for_school_set.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type CentralKitchenForSchoolSet struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('所属单位id') INT(11)"` + IsOpenTeacherReportMeal int `json:"is_open_teacher_report_meal" xorm:"not null default 1 comment('教师报餐(1:开启 2:关闭)') TINYINT(1)"` + IsOpenReportMealForDay int `json:"is_open_report_meal_for_day" xorm:"not null default 1 comment('开启按天报餐(1:开启 2:关闭)') TINYINT(1)"` + IsOpenReportMealForMonth int `json:"is_open_report_meal_for_month" xorm:"not null default 1 comment('开启按月报餐(1:开启 2:关闭)') TINYINT(1)"` + IsOpenReportMealForSemester int `json:"is_open_report_meal_for_semester" xorm:"not null default 1 comment('开启按学期报餐(1:开启 2:关闭)') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/central_kitchen_for_school_user_refund_day.go b/app/db/model/central_kitchen_for_school_user_refund_day.go new file mode 100644 index 0000000..17fe6de --- /dev/null +++ b/app/db/model/central_kitchen_for_school_user_refund_day.go @@ -0,0 +1,19 @@ +package model + +import ( + "time" +) + +type CentralKitchenForSchoolUserRefundDay struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + OutTradeNo string `json:"out_trade_no" xorm:"default '' comment('订单号') VARCHAR(50)"` + OutRequestNo string `json:"out_request_no" xorm:"not null default '' comment('退款请求号') VARCHAR(50)"` + Uid int `json:"uid" xorm:"not null default 0 comment('uid') INT(11)"` + IdentityId int `json:"identity_id" xorm:"not null default 0 comment('身份id') INT(11)"` + RecordsId int `json:"records_id" xorm:"not null default 0 comment('记录id') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:审核中 2:审核通过 3:审核拒绝 4:退款已完成)') TINYINT(1)"` + Amount string `json:"amount" xorm:"not null default 0.00 comment('金额') DECIMAL(6,2)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/central_kitchen_for_school_user_with_day.go b/app/db/model/central_kitchen_for_school_user_with_day.go new file mode 100644 index 0000000..90090fb --- /dev/null +++ b/app/db/model/central_kitchen_for_school_user_with_day.go @@ -0,0 +1,12 @@ +package model + +type CentralKitchenForSchoolUserWithDay struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + OrdNo string `json:"ord_no" xorm:"not null default '' comment('订单号') VARCHAR(50)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户id') INT(11)"` + IdentityId int `json:"identity_id" xorm:"not null default 0 comment('身份id') INT(11)"` + Kind int `json:"kind" xorm:"not null default 1 comment('就餐类型(1:早餐 2:午餐 3:晚餐)') TINYINT(1)"` + Amount string `json:"amount" xorm:"not null default '' comment('金额') CHAR(50)"` + Date string `json:"date" xorm:"not null default '0000-00-00' comment('日期') CHAR(50)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:待就餐 2:已就餐 3:退款中 4:已退款)') TINYINT(1)"` +} diff --git a/app/db/model/central_kitchen_for_school_with_spec.go b/app/db/model/central_kitchen_for_school_with_spec.go new file mode 100644 index 0000000..0d18bd9 --- /dev/null +++ b/app/db/model/central_kitchen_for_school_with_spec.go @@ -0,0 +1,21 @@ +package model + +import ( + "time" +) + +type CentralKitchenForSchoolWithSpec struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null comment('单位id') INT(11)"` + IsOpenBreakfast int `json:"is_open_breakfast" xorm:"not null default 0 comment('是否开启早餐(1:开启 0:关闭)') TINYINT(1)"` + IsOpenLunch int `json:"is_open_lunch" xorm:"default 0 comment('是否开启午餐(1:开启 0:关闭)') TINYINT(1)"` + IsOpenDinner int `json:"is_open_dinner" xorm:"default 0 comment('是否开启晚餐(1:开启 0:关闭)') TINYINT(1)"` + BreakfastUnitPrice string `json:"breakfast_unit_price" xorm:"not null default 0.00 comment('早餐-单价') DECIMAL(4,2)"` + LunchUnitPrice string `json:"lunch_unit_price" xorm:"not null default 0.00 comment('午餐-单价') DECIMAL(4,2)"` + DinnerUnitPrice string `json:"dinner_unit_price" xorm:"not null default 0.00 comment('晚餐-单价') DECIMAL(4,2)"` + BreakfastUnitPriceForTeacher string `json:"breakfast_unit_price_for_teacher" xorm:"not null default 0.00 comment('教师-早餐-单价') DECIMAL(4,2)"` + LunchUnitPriceForTeacher string `json:"lunch_unit_price_for_teacher" xorm:"not null default 0.00 comment('教师-午餐-单价') DECIMAL(4,2)"` + DinnerUnitPriceForTeacher string `json:"dinner_unit_price_for_teacher" xorm:"not null default 0.00 comment('教师-晚餐-单价') DECIMAL(4,2)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/class.go b/app/db/model/class.go new file mode 100644 index 0000000..eec0e3d --- /dev/null +++ b/app/db/model/class.go @@ -0,0 +1,15 @@ +package model + +import ( + "time" +) + +type Class struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` + GradeId int `json:"grade_id" xorm:"not null default 0 comment('年级id') INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('单位id') INT(11)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/class_with_user.go b/app/db/model/class_with_user.go new file mode 100644 index 0000000..a88b1c9 --- /dev/null +++ b/app/db/model/class_with_user.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type ClassWithUser struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + UserIdentityId int `json:"user_identity_id" xorm:"not null default 0 comment('用户身份id') INT(11)"` + ClassId int `json:"class_id" xorm:"not null default 0 INT(11)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/company.go b/app/db/model/company.go new file mode 100644 index 0000000..4bed1a3 --- /dev/null +++ b/app/db/model/company.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type Company struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + State int32 `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"` + LeadName string `json:"lead_name" xorm:"not null default '' comment('负责人姓名') VARCHAR(50)"` + LeadPhone string `json:"lead_phone" xorm:"not null default '' comment('负责人手机号') VARCHAR(50)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注信息') VARCHAR(244)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/enterprise.go b/app/db/model/enterprise.go new file mode 100644 index 0000000..acfe562 --- /dev/null +++ b/app/db/model/enterprise.go @@ -0,0 +1,17 @@ +package model + +import ( + "time" +) + +type Enterprise struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + Pvd int32 `json:"pvd" xorm:"not null default 1 comment('场景(1:央厨配送 2:自营食堂)') TINYINT(1)"` + Kind int32 `json:"kind" xorm:"not null default 1 comment('种类(1:央厨-学校 2:央厨-工厂 3:自营-学校 4:自营-工厂)') TINYINT(1)"` + CompanyId int `json:"company_id" xorm:"not null default 0 comment('所属公司id') INT(11)"` + State int32 `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注信息') VARCHAR(244)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/grade.go b/app/db/model/grade.go new file mode 100644 index 0000000..55ad272 --- /dev/null +++ b/app/db/model/grade.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type Grade struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('单位id') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/notice.go b/app/db/model/notice.go new file mode 100644 index 0000000..730ec42 --- /dev/null +++ b/app/db/model/notice.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type Notice struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + Content string `json:"content" xorm:"not null comment('内容(json存储)') TEXT"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') TINYINT(3)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/permission.go b/app/db/model/permission.go new file mode 100644 index 0000000..d4c214a --- /dev/null +++ b/app/db/model/permission.go @@ -0,0 +1,10 @@ +package model + +type Permission struct { + Id int `json:"id" xorm:"not null comment('管理员id') INT(11)"` + Name string `json:"name" xorm:"not null default 0' comment('名称') VARCHAR(255)"` + Action string `json:"action" xorm:"not null default 0' comment('路由') VARCHAR(255)"` + State int32 `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:废弃)') TINYINT(1)"` + CreateAt string `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt string `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/permission_group.go b/app/db/model/permission_group.go new file mode 100644 index 0000000..07e0655 --- /dev/null +++ b/app/db/model/permission_group.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type PermissionGroup struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + ParentId int `json:"parent_id" xorm:"not null default 0 comment('父级id') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:废弃)') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/permission_group_permission.go b/app/db/model/permission_group_permission.go new file mode 100644 index 0000000..f10a20c --- /dev/null +++ b/app/db/model/permission_group_permission.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type PermissionGroupPermission struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + GroupId int `json:"group_id" xorm:"not null default 0 comment('权限组id') INT(11)"` + PermissionId int `json:"permission_id" xorm:"not null default 0 comment('权限id') INT(11)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/role.go b/app/db/model/role.go new file mode 100644 index 0000000..4b46b20 --- /dev/null +++ b/app/db/model/role.go @@ -0,0 +1,10 @@ +package model + +type Role struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` + CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/role_permission_group.go b/app/db/model/role_permission_group.go new file mode 100644 index 0000000..a73a2c3 --- /dev/null +++ b/app/db/model/role_permission_group.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type RolePermissionGroup struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + RoleId int `json:"role_id" xorm:"not null default 0 comment('角色id') INT(11)"` + GroupId int `json:"group_id" xorm:"not null default 0 comment('权限组id') INT(11)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/self_support_for_school_info.go b/app/db/model/self_support_for_school_info.go new file mode 100644 index 0000000..4f0f168 --- /dev/null +++ b/app/db/model/self_support_for_school_info.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type SelfSupportForSchoolInfo struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('单位id') INT(11)"` + SchoolCode string `json:"school_code" xorm:"not null default '' comment('学校内标') VARCHAR(255)"` + SchoolStdCode string `json:"school_std_code" xorm:"not null default '' comment('学校外标') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/self_support_for_user_face_info.go b/app/db/model/self_support_for_user_face_info.go new file mode 100644 index 0000000..db6607c --- /dev/null +++ b/app/db/model/self_support_for_user_face_info.go @@ -0,0 +1,16 @@ +package model + +type SelfSupportForUserFaceInfo struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + UserIdentityId int `json:"user_identity_id" xorm:"not null default 0 comment('用户身份id') INT(11)"` + CollectFaceType int `json:"collect_face_type" xorm:"not null default 1 comment('采集人脸方式(1:个采 2:集采)') TINYINT(1)"` + SchoolCode string `json:"school_code" xorm:"not null default '' comment('学校内标') VARCHAR(255)"` + SchoolStdCode string `json:"school_std_code" xorm:"not null default '' comment('学校外标') VARCHAR(255)"` + ParentUserId string `json:"parent_user_id" xorm:"not null default '' comment('用于开通一脸通行的支付宝账户uid(如果是父母为孩子开通,则为父母支付宝uid;如果是用户为本人开通,则为本人支付宝uid)') VARCHAR(255)"` + ParentLogonId string `json:"parent_logon_id" xorm:"not null default '' comment('家长支付宝账户的脱敏信息') VARCHAR(255)"` + UserId string `json:"user_id" xorm:"not null default '' comment('刷脸用户id') VARCHAR(255)"` + SchoolFacePassStatus string `json:"school_face_pass_status" xorm:"not null default '' comment('校园一脸通行开通状态(开通:OPEN 关闭:CLOSE)') CHAR(50)"` + SchoolFacePaymentStatus string `json:"school_face_payment_status" xorm:"not null default '' comment('校园一脸通行刷脸支付开通状态(开通:OPEN 关闭:CLOSE)') CHAR(50)"` + CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/sys_cfg.go b/app/db/model/sys_cfg.go new file mode 100644 index 0000000..22d906b --- /dev/null +++ b/app/db/model/sys_cfg.go @@ -0,0 +1,7 @@ +package model + +type SysCfg struct { + Key string `json:"key" xorm:"not null pk comment('键') VARCHAR(127)"` + Val string `json:"val" xorm:"comment('值') TEXT"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` +} diff --git a/app/db/model/user.go b/app/db/model/user.go new file mode 100644 index 0000000..3776483 --- /dev/null +++ b/app/db/model/user.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type User struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + UserId string `json:"user_id" xorm:"not null default '' comment('支付宝用户的唯一userId') VARCHAR(255)"` + Nickname string `json:"nickname" xorm:"not null default '' comment('支付宝昵称') VARCHAR(255)"` + Avatar string `json:"avatar" xorm:"not null default '' comment('支付宝头像') VARCHAR(255)"` + Phone string `json:"phone" xorm:"not null default '' comment('手机号') VARCHAR(255)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注信息') VARCHAR(244)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/db/model/user_identity.go b/app/db/model/user_identity.go new file mode 100644 index 0000000..f51b40b --- /dev/null +++ b/app/db/model/user_identity.go @@ -0,0 +1,19 @@ +package model + +import ( + "time" +) + +type UserIdentity struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户id') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + IdNo string `json:"id_no" xorm:"not null default '' comment('身份证号码') VARCHAR(255)"` + Kind int `json:"kind" xorm:"not null default 1 comment('类型(1:普通用户 2:工作人员)') TINYINT(1)"` + Identity int `json:"identity" xorm:"not null default 1 comment('身份类型(1:央厨-学生 2:央厨-教职员工 3:央厨-工作人员 4:自营-学生 5:自营-教职员工 6:自营-工作人员)') TINYINT(1)"` + EnterpriseId int `json:"enterprise_id" xorm:"not null default 0 comment('所属单位id') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注信息') VARCHAR(244)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"` +} diff --git a/app/e/code.go b/app/e/code.go new file mode 100644 index 0000000..cc8be46 --- /dev/null +++ b/app/e/code.go @@ -0,0 +1,236 @@ +package e + +const ( + // 200 因为部分第三方接口不能返回错误头,因此在此定义部分错误 + ERR_FILE_SAVE = 200001 + // 400 系列 + ERR_BAD_REQUEST = 400000 + ERR_INVALID_ARGS = 400001 + ERR_API_RESPONSE = 400002 + ERR_NO_DATA = 400003 + ERR_MOBILE_NIL = 400004 + ERR_MOBILE_MATH = 400005 + ERR_FILE_EXT = 400006 + ERR_FILE_MAX_SIZE = 400007 + ERR_SIGN = 400008 + ERR_PASSWORD_MATH = 400009 + ERR_PROVIDER_RESPONSE = 400010 + ERR_AES_ENCODE = 400011 + ERR_ADMIN_API = 400012 + ERR_QINIUAPI_RESPONSE = 400013 + ERR_URL_TURNCHAIN = 400014 + + // 401 未授权 + ERR_UNAUTHORIZED = 401000 + ERR_NOT_AUTH = 401001 + ERR_SMS_AUTH = 401002 + ERR_TOKEN_AUTH = 401003 + ERR_TOKEN_FORMAT = 401004 + ERR_TOKEN_GEN = 401005 + ERR_CACHE_SET = 401006 + // 403 禁止 + ERR_FORBIDEN = 403000 + ERR_PLATFORM = 403001 + ERR_MOBILE_EXIST = 403002 + ERR_USER_NO_EXIST = 403003 + ERR_MOBILE_NO_EXIST = 403004 + ERR_FORBIDEN_VALID = 403005 + ERR_RELATE_ERR = 403006 + ERR_REPEAT_RELATE = 403007 + ERR_MOB_FORBIDEN = 403008 + ERR_MOB_SMS_NO_AVA = 403009 + ERR_USER_IS_REG = 403010 + ERR_MASTER_ID = 403011 + ERR_CASH_OUT_TIME = 403012 + ERR_CASH_OUT_FEE = 403013 + ERR_CASH_OUT_USER_NOT_FOUND = 403014 + ERR_CASH_OUT_FAIL = 403015 + ERR_CASH_OUT_TIMES = 403016 + ERR_CASH_OUT_MINI = 403017 + ERR_CASH_OUT_MUT = 403018 + ERR_CASH_OUT_NOT_DECIMAL = 403019 + ERR_CASH_OUT_NOT_DAY_AVA = 403020 + ERR_USER_LEVEL_PAY_CHECK_TASK_NO_DONE = 403021 + ERR_USER_LEVEL_PAY_CHECK_NO_CROSS = 403022 + ERR_USER_LEVEL_ORD_EXP = 403023 + ERR_IS_BIND_THIRDPARTY = 403024 + ERR_USER_LEVEL_UPDATE_CHECK_TASK_NO_DONE = 403025 + ERR_USER_LEVEL_UPDATE_CHECK_NOT_FOUND_ORDER = 403026 + ERR_USER_LEVEL_UPDATE_REPEAT = 403027 + ERR_USER_NO_ACTIVE = 403028 + ERR_USER_IS_BAN = 403029 + ERR_ALIPAY_SETTING = 403030 + ERR_ALIPAY_ORDERTYPE = 403031 + ERR_CLIPBOARD_UNSUP = 403032 + ERR_SYSUNION_CONFIG = 403033 + ERR_WECAHT_MINI = 403034 + ERR_WECAHT_MINI_CACHE = 403035 + ERR_WECAHT_MINI_DECODE = 403036 + ERR_WECHAT_MINI_ACCESSTOKEN = 403037 + ERR_CURRENT_VIP_LEVEL_AUDITING = 403038 + ERR_LEVEL_RENEW_SHOULD_KEEP_CURRENT = 403039 + ERR_LEVEL_UPGRADE_APPLY_AUDITTING = 403040 + ERR_LEVEL_TASK_PAY_TYPE = 403041 + ERR_BALANCE_NOT_ENOUGH = 403042 + ERR_ADMIN_PUSH = 403043 + ERR_PLAN = 403044 + ERR_MOB_CONFIG = 403045 + ERR_BAlANCE_PAY_ORDERTYPE = 403046 + ERR_PHONE_EXISTED = 403047 + ERR_NOT_RESULT = 403048 + ERR_REVIEW = 403049 + ERR_USER_LEVEL_HAS_PAID = 403050 + ERR_USER_BIND_OWN = 403051 + ERR_PARENTUID_ERR = 403052 + ERR_USER_DEL = 403053 + ERR_SEARCH_ERR = 403054 + ERR_LEVEL_REACH_TOP = 403055 + ERR_USER_CHECK_ERR = 403056 + ERR_PASSWORD_ERR = 403057 + // 404 + ERR_USER_NOTFOUND = 404001 + ERR_SUP_NOTFOUND = 404002 + ERR_LEVEL_MAP = 404003 + ERR_MOD_NOTFOUND = 404004 + ERR_CLIPBOARD_PARSE = 404005 + ERR_NOT_FAN = 404006 + ERR_USER_LEVEL = 404007 + ERR_LACK_PAY_CFG = 404008 + ERR_NOT_LEVEL_TASK = 404009 + ERR_ITEM_NOT_FOUND = 404010 + ERR_WX_CHECKFILE_NOTFOUND = 404011 + + // 429 请求频繁 + ERR_TOO_MANY_REQUESTS = 429000 + // 500 系列 + ERR = 500000 + ERR_UNMARSHAL = 500001 + ERR_UNKNOWN = 500002 + ERR_SMS = 500003 + ERR_ARKID_REGISTER = 500004 + ERR_ARKID_WHITELIST = 500005 + ERR_ARKID_LOGIN = 500006 + ERR_CFG = 500007 + ERR_DB_ORM = 500008 + ERR_CFG_CACHE = 500009 + ERR_ZHIMENG_CONVERT_ERR = 500010 + ERR_ALIPAY_ERR = 500011 + ERR_ALIPAY_ORDER_ERR = 500012 + ERR_PAY_ERR = 500013 + ERR_IS_BIND_THIRDOTHER = 500014 +) + +var MsgFlags = map[int]string{ + // 200 + ERR_FILE_SAVE: "文件保存失败", + // 400 + ERR_BAD_REQUEST: "请求失败", + ERR_INVALID_ARGS: "请求参数错误", + ERR_API_RESPONSE: "API错误", + ERR_QINIUAPI_RESPONSE: "七牛请求API错误", + ERR_URL_TURNCHAIN: "转链失败", + ERR_NO_DATA: "暂无数据", + ERR_MOBILE_NIL: "电话号码不能为空", + ERR_MOBILE_MATH: "电话号码输入有误", + ERR_FILE_MAX_SIZE: "文件上传大小超限", + ERR_FILE_EXT: "文件类型不支持", + ERR_SIGN: "签名校验失败", + ERR_PROVIDER_RESPONSE: "提供商接口错误", + ERR_AES_ENCODE: "加解密错误", + ERR_ADMIN_API: "后台接口请求失败", + // 401 + ERR_NOT_AUTH: "请登录后操作", + ERR_SMS_AUTH: "验证码过期或无效", + ERR_UNAUTHORIZED: "验证用户失败", + ERR_TOKEN_FORMAT: "Token格式不对", + ERR_TOKEN_GEN: "生成Token失败", + ERR_CACHE_SET: "生成缓存失败", + // 403 + ERR_FORBIDEN: "禁止访问", + ERR_PLATFORM: "平台不支持", + ERR_MOBILE_EXIST: "该号码已注册过", + ERR_USER_NO_EXIST: "用户没有注册或账号密码不正确", + ERR_PASSWORD_ERR: "输入两次密码不一致", + ERR_RELATE_ERR: "推荐人不能是自己的粉丝", + ERR_PARENTUID_ERR: "推荐人不存在", + ERR_TOKEN_AUTH: "登录信息失效,请重新登录", + ERR_MOB_SMS_NO_AVA: "短信余额不足或智盟短信配置失败", + ERR_USER_IS_REG: "用户已注册", + ERR_MASTER_ID: "找不到对应站长的数据库", + ERR_CASH_OUT_TIME: "非可提现时间段", + ERR_CASH_OUT_USER_NOT_FOUND: "收款账号不存在", + ERR_CASH_OUT_FAIL: "提现失败", + ERR_CASH_OUT_FEE: "提现金额必须大于手续费", + ERR_CASH_OUT_TIMES: "当日提现次数已达上线", + ERR_CASH_OUT_MINI: "申请提现金额未达到最低金额要求", + ERR_CASH_OUT_MUT: "申请提现金额未达到整数倍要求", + ERR_CASH_OUT_NOT_DECIMAL: "提现申请金额只能是整数", + ERR_CASH_OUT_NOT_DAY_AVA: "不在可提现日期范围内", + ERR_USER_LEVEL_PAY_CHECK_TASK_NO_DONE: "请先完成其他任务", + ERR_USER_LEVEL_PAY_CHECK_NO_CROSS: "无法跨越升级", + ERR_USER_LEVEL_ORD_EXP: "付费订单已失效", + ERR_IS_BIND_THIRDPARTY: "该用户已经绑定了", + ERR_IS_BIND_THIRDOTHER: "该账号已经被绑定了", + ERR_USER_LEVEL_UPDATE_CHECK_TASK_NO_DONE: "请完成指定任务", + ERR_USER_LEVEL_UPDATE_CHECK_NOT_FOUND_ORDER: "没有找到对应的订单", + ERR_USER_LEVEL_UPDATE_REPEAT: "不允许重复升级", + ERR_USER_NO_ACTIVE: "账户没激活", + ERR_USER_IS_BAN: "账户已被冻结", + ERR_SYSUNION_CONFIG: "联盟设置错误,请检查配置", + ERR_WECAHT_MINI: "小程序响应错误,请检查小程序配置", + ERR_WECAHT_MINI_CACHE: "获取小程序缓存失败", + ERR_WECAHT_MINI_DECODE: "小程序解密失败", + ERR_WECHAT_MINI_ACCESSTOKEN: "无法获取accesstoekn", + ERR_CURRENT_VIP_LEVEL_AUDITING: "当前等级正在审核中", + ERR_LEVEL_RENEW_SHOULD_KEEP_CURRENT: "续费只能在当前等级续费", + ERR_LEVEL_UPGRADE_APPLY_AUDITTING: "已有申请正在审核中,暂时不能申请", + ERR_LEVEL_TASK_PAY_TYPE: "任务付费类型错误", + ERR_BALANCE_NOT_ENOUGH: "余额不足", + ERR_ADMIN_PUSH: "后台MOB推送错误", + ERR_PLAN: "分拥方案出错", + ERR_MOB_CONFIG: "Mob 配置错误", + ERR_BAlANCE_PAY_ORDERTYPE: "无效余额支付订单类型", + ERR_PHONE_EXISTED: "手机号码已存在", + ERR_NOT_RESULT: "已加载完毕", + ERR_REVIEW: "审核模板错误", + ERR_USER_LEVEL_HAS_PAID: "该等级已经付过款", + // 404 + ERR_USER_NOTFOUND: "用户不存在", + ERR_USER_DEL: "账号被删除,如有疑问请联系客服", + ERR_SUP_NOTFOUND: "上级用户不存在", + ERR_LEVEL_MAP: "无等级映射关系", + ERR_MOD_NOTFOUND: "没有找到对应模块", + ERR_CLIPBOARD_PARSE: "无法解析剪切板内容", + ERR_NOT_FAN: "没有粉丝", + ERR_CLIPBOARD_UNSUP: "不支持该平台", + ERR_USER_LEVEL: "该等级已不存在", + ERR_LACK_PAY_CFG: "支付配置不完整", + ERR_NOT_LEVEL_TASK: "等级任务查找错误", + ERR_ITEM_NOT_FOUND: "找不到对应商品", + ERR_WX_CHECKFILE_NOTFOUND: "找不到微信校验文件", + ERR_USER_BIND_OWN: "不能填写自己的邀请码", + // 429 + ERR_TOO_MANY_REQUESTS: "请求频繁,请稍后重试", + // 500 内部错误 + ERR: "接口错误", + ERR_SMS: "短信发送出错", + ERR_CFG: "服务器配置错误", + ERR_UNMARSHAL: "JSON解码错误", + ERR_UNKNOWN: "未知错误", + ERR_ARKID_LOGIN: "登录失败", + ERR_MOBILE_NO_EXIST: "该用户未设定手机号", + ERR_FORBIDEN_VALID: "验证码错误", + ERR_CFG_CACHE: "获取配置缓存失败", + ERR_DB_ORM: "数据操作失败", + ERR_REPEAT_RELATE: "重复关联", + ERR_ZHIMENG_CONVERT_ERR: "智盟转链失败", + ERR_MOB_FORBIDEN: "Mob调用失败", + ERR_ALIPAY_ERR: "支付宝参数错误", + ERR_ALIPAY_SETTING: "请在后台正确配置支付宝", + ERR_ALIPAY_ORDERTYPE: "无效支付宝订单类型", + ERR_ALIPAY_ORDER_ERR: "订单创建错误", + ERR_PAY_ERR: "未找到支付方式", + ERR_SEARCH_ERR: "暂无该分类商品", + ERR_LEVEL_REACH_TOP: "已经是最高等级", + ERR_USER_CHECK_ERR: "校验失败", +} diff --git a/app/e/error.go b/app/e/error.go new file mode 100644 index 0000000..2564174 --- /dev/null +++ b/app/e/error.go @@ -0,0 +1,72 @@ +package e + +import ( + "fmt" + "path" + "runtime" +) + +type E struct { + Code int // 错误码 + msg string // 报错代码 + st string // 堆栈信息 +} + +func NewErrCode(code int) error { + if msg, ok := MsgFlags[code]; ok { + return E{code, msg, stack(3)} + } + return E{ERR_UNKNOWN, "unknown", stack(3)} +} + +func NewErr(code int, msg string) error { + return E{code, msg, stack(3)} +} + +func NewErrf(code int, msg string, args ...interface{}) error { + return E{code, fmt.Sprintf(msg, args), stack(3)} +} + +func (e E) Error() string { + return e.msg +} + +func stack(skip int) string { + stk := make([]uintptr, 32) + str := "" + l := runtime.Callers(skip, stk[:]) + for i := 0; i < l; i++ { + f := runtime.FuncForPC(stk[i]) + name := f.Name() + file, line := f.FileLine(stk[i]) + str += fmt.Sprintf("\n%-30s[%s:%d]", name, path.Base(file), line) + } + return str +} + +// ErrorIsAccountBan is 检查这个账号是否被禁用的错误 +func ErrorIsAccountBan(e error) bool { + err, ok := e.(E) + if ok && err.Code == 403029 { + return true + } + return false +} + +// ErrorIsAccountNoActive is 检查这个账号是否被禁用的错误 +func ErrorIsAccountNoActive(e error) bool { + err, ok := e.(E) + if ok && err.Code == 403028 { + return true + } + return false +} + +// ErrorIsUserDel is 检查这个账号是否被删除 +func ErrorIsUserDel(e error) bool { + err, ok := e.(E) + if ok && err.Code == 403053 { + return true + } + return false +} diff --git a/app/e/msg.go b/app/e/msg.go new file mode 100644 index 0000000..ed226ae --- /dev/null +++ b/app/e/msg.go @@ -0,0 +1,110 @@ +package e + +import ( + "applet/app/utils" + "encoding/json" + "net/http" + + "github.com/gin-gonic/gin" + + "applet/app/utils/logx" +) + +// GetMsg get error information based on Code +// 因为这里code是自己控制的, 因此没考虑报错信息 +func GetMsg(code int) (int, string) { + if msg, ok := MsgFlags[code]; ok { + return code / 1000, msg + } + if http.StatusText(code) == "" { + code = 200 + } + return code, MsgFlags[ERR_BAD_REQUEST] +} + +// 成功输出, fields 是额外字段, 与code, msg同级 +func OutSuc(c *gin.Context, data interface{}, fields map[string]interface{}) { + res := gin.H{ + "code": 1, + "msg": "ok", + "data": data, + } + if fields != nil { + for k, v := range fields { + res[k] = v + } + } + if utils.GetApiVersion(c) > 0 { //加了签名校验只返回加密的字符串 + jsonData, _ := json.Marshal(res) + str := utils.ResultAes(c, jsonData) + c.Writer.WriteString(str) + } else { + c.AbortWithStatusJSON(200, res) + } +} + +func OutSucPure(c *gin.Context, data interface{}, fields map[string]interface{}) { + res := gin.H{ + "code": 1, + "msg": "ok", + "data": data, + } + if fields != nil { + for k, v := range fields { + res[k] = v + } + } + c.Abort() + c.PureJSON(200, res) +} + +// 错误输出 +func OutErr(c *gin.Context, code int, err ...interface{}) { + statusCode, msg := GetMsg(code) + if len(err) > 0 && err[0] != nil { + e := err[0] + switch v := e.(type) { + case E: + statusCode = v.Code / 1000 + msg = v.Error() + logx.Error(v.msg + ": " + v.st) // 记录堆栈信息 + case error: + logx.Error(v) + break + case string: + msg = v + case int: + if _, ok := MsgFlags[v]; ok { + msg = MsgFlags[v] + } + } + } + if utils.GetApiVersion(c) > 0 { //加了签名校验只返回加密的字符串 + jsonData, _ := json.Marshal(gin.H{ + "code": code, + "msg": msg, + "data": []struct{}{}, + }) + str := utils.ResultAes(c, jsonData) + if code > 100000 { + code = int(utils.FloatFormat(float64(code/1000), 0)) + } + c.Status(500) + c.Writer.WriteString(str) + } else { + c.AbortWithStatusJSON(statusCode, gin.H{ + "code": code, + "msg": msg, + "data": []struct{}{}, + }) + } +} + +// 重定向 +func OutRedirect(c *gin.Context, code int, loc string) { + if code < 301 || code > 308 { + code = 303 + } + c.Redirect(code, loc) + c.Abort() +} diff --git a/app/e/set_cache.go b/app/e/set_cache.go new file mode 100644 index 0000000..45337a1 --- /dev/null +++ b/app/e/set_cache.go @@ -0,0 +1,8 @@ +package e + +func SetCache(cacheTime int64) map[string]interface{} { + if cacheTime == 0 { + return map[string]interface{}{"cache_time": cacheTime} + } + return map[string]interface{}{"cache_time": cacheTime} +} diff --git a/app/enum/enum_central_kitchen_for_school_package_ord.go b/app/enum/enum_central_kitchen_for_school_package_ord.go new file mode 100644 index 0000000..4bcc76d --- /dev/null +++ b/app/enum/enum_central_kitchen_for_school_package_ord.go @@ -0,0 +1,52 @@ +package enum + +type CentralKitchenForSchoolPackageOrdState int32 + +const ( + CentralKitchenForSchoolPackageOrdStateForWait = 0 + CentralKitchenForSchoolPackageOrdStateForSuccess = 1 + CentralKitchenForSchoolPackageOrdStateForFail = 2 +) + +func (gt CentralKitchenForSchoolPackageOrdState) String() string { + switch gt { + case CentralKitchenForSchoolPackageOrdStateForWait: + return "待支付" + case CentralKitchenForSchoolPackageOrdStateForSuccess: + return "支付成功" + case CentralKitchenForSchoolPackageOrdStateForFail: + return "支付失败" + default: + return "未知" + } +} + +type CentralKitchenForSchoolPackageOrdOrdState int32 + +const ( + CentralKitchenForSchoolPackageOrdOrdStateForWait = 0 + CentralKitchenForSchoolPackageOrdOrdStateForSuccess = 1 + CentralKitchenForSchoolPackageOrdOrdStateForRefunding = 2 + CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded = 3 + CentralKitchenForSchoolPackageOrdOrdStateForRefunded = 4 + CentralKitchenForSchoolPackageOrdOrdStateForComplete = 5 +) + +func (gt CentralKitchenForSchoolPackageOrdOrdState) String() string { + switch gt { + case CentralKitchenForSchoolPackageOrdOrdStateForWait: + return "待支付" + case CentralKitchenForSchoolPackageOrdOrdStateForSuccess: + return "预约成功" + case CentralKitchenForSchoolPackageOrdOrdStateForRefunding: + return "退款中" + case CentralKitchenForSchoolPackageOrdOrdStateForPartRefunded: + return "部分退款" + case CentralKitchenForSchoolPackageOrdOrdStateForRefunded: + return "已退款" + case CentralKitchenForSchoolPackageOrdOrdStateForComplete: + return "已完成" + default: + return "未知" + } +} diff --git a/app/enum/enum_central_kitchen_for_school_user_refund_day.go b/app/enum/enum_central_kitchen_for_school_user_refund_day.go new file mode 100644 index 0000000..0a8237e --- /dev/null +++ b/app/enum/enum_central_kitchen_for_school_user_refund_day.go @@ -0,0 +1,25 @@ +package enum + +type CentralKitchenForSchoolUserRefundDayState int32 + +const ( + CentralKitchenForSchoolUserRefundDayStateForAuditing = 1 + CentralKitchenForSchoolUserRefundDayStateForAuditPass = 2 + CentralKitchenForSchoolUserRefundDayStateForAuditReject = 3 + CentralKitchenForSchoolUserRefundDayStateForAuditComplete = 4 +) + +func (gt CentralKitchenForSchoolUserRefundDayState) String() string { + switch gt { + case CentralKitchenForSchoolUserRefundDayStateForAuditing: + return "审核中" + case CentralKitchenForSchoolUserRefundDayStateForAuditPass: + return "审核通过" + case CentralKitchenForSchoolUserRefundDayStateForAuditReject: + return "审核拒绝" + case CentralKitchenForSchoolUserRefundDayStateForAuditComplete: + return "退款已完成" + default: + return "未知" + } +} diff --git a/app/enum/enum_central_kitchen_for_school_user_with_day.go b/app/enum/enum_central_kitchen_for_school_user_with_day.go new file mode 100644 index 0000000..e3f7c92 --- /dev/null +++ b/app/enum/enum_central_kitchen_for_school_user_with_day.go @@ -0,0 +1,46 @@ +package enum + +type CentralKitchenForSchoolUserWithDayKind int32 + +const ( + CentralKitchenForSchoolUserWithDayKindForBreakfast = 1 + CentralKitchenForSchoolUserWithDayKindForLunch = 2 + CentralKitchenForSchoolUserWithDayKindForDinner = 3 +) + +func (gt CentralKitchenForSchoolUserWithDayKind) String() string { + switch gt { + case CentralKitchenForSchoolUserWithDayKindForBreakfast: + return "早餐" + case CentralKitchenForSchoolUserWithDayKindForLunch: + return "午餐" + case CentralKitchenForSchoolUserWithDayKindForDinner: + return "晚餐" + default: + return "未知" + } +} + +type CentralKitchenForSchoolUserWithDayState int32 + +const ( + CentralKitchenForSchoolUserWithDayStateForWait = 1 + CentralKitchenForSchoolUserWithDayStateForAlready = 2 + CentralKitchenForSchoolUserWithDayStateForCanceling = 3 + CentralKitchenForSchoolUserWithDayStateForCancel = 4 +) + +func (gt CentralKitchenForSchoolUserWithDayState) String() string { + switch gt { + case CentralKitchenForSchoolUserWithDayStateForWait: + return "待就餐" + case CentralKitchenForSchoolUserWithDayStateForAlready: + return "已就餐" + case CentralKitchenForSchoolUserWithDayStateForCanceling: + return "退款中" + case CentralKitchenForSchoolUserWithDayStateForCancel: + return "已退款" + default: + return "未知" + } +} diff --git a/app/enum/enum_enterprise.go b/app/enum/enum_enterprise.go new file mode 100644 index 0000000..9af0e09 --- /dev/null +++ b/app/enum/enum_enterprise.go @@ -0,0 +1,43 @@ +package enum + +type EnterpriseState int32 + +const ( + EnterpriseStateForNormal = 1 + EnterpriseStateForFreeze = 2 +) + +func (gt EnterpriseState) String() string { + switch gt { + case EnterpriseStateForNormal: + return "正常" + case EnterpriseStateForFreeze: + return "冻结" + default: + return "未知" + } +} + +type EnterprisePvd int32 + +const ( + EnterprisePvdByCentralKitchenForSchool = 1 + EnterprisePvdByCentralKitchenForFactory = 2 + EnterprisePvdBySelfSupportForSchool = 3 + EnterprisePvdBySelfSupportForFactory = 4 +) + +func (gt EnterprisePvd) String() string { + switch gt { + case EnterprisePvdByCentralKitchenForSchool: + return "央厨-学校" + case EnterprisePvdByCentralKitchenForFactory: + return "央厨-工厂" + case EnterprisePvdBySelfSupportForSchool: + return "自营-学校" + case EnterprisePvdBySelfSupportForFactory: + return "自营-工厂" + default: + return "未知" + } +} diff --git a/app/enum/enum_sys_cfg.go b/app/enum/enum_sys_cfg.go new file mode 100644 index 0000000..23bafe1 --- /dev/null +++ b/app/enum/enum_sys_cfg.go @@ -0,0 +1,82 @@ +package enum + +type SysCfg string + +const ( + AppName = "app_name" + OpenAlipayAesKey = "open_alipay_aes_key" + OpenAlipayAppPrivateKey = "open_alipay_app_private_key" + OpenAlipayAppPublicKey = "open_alipay_app_public_key" + OpenAlipayPublicKey = "open_alipay_public_key" + OpenAppletAesKey = "open_applet_aes_key" + OpenAppletAppPrivateKey = "open_applet_app_private_key" + OpenAppletAppPublicKey = "open_applet_app_public_key" + OpenAppletPublicKey = "open_applet_public_key" + OpenAlipayAppid = "open_alipay_appid" + OpenAppletAppid = "open_applet_appid" + FileBucketHost = "file_bucket_host" + FileBucketRegion = "file_bucket_region" + FileBucketScheme = "file_bucket_scheme" + FileExt = "file_ext" + FileSecretKey = "file_secret_key" + FileUserUploadMaxSize = "file_user_upload_max_size" + FileAccessKey = "file_access_key" + FileBucket = "file_bucket" + AdministratorContactInfo = "administrator_contact_info" + CentralKitchenForSchoolReserveMealTime = "central_kitchen_for_school_reserve_meal_time" + CentralKitchenForSchoolCancelMealTime = "central_kitchen_for_school_cancel_meal_time" + JsapiPayAppAutToken = "jsapi_pay_app_auth_token" +) + +func (gt SysCfg) String() string { + switch gt { + case AppName: + return "项目名称" + case OpenAlipayAesKey: + return "支付宝开放平台-第三方应用-接口内容加密-aesKey" + case OpenAlipayAppPrivateKey: + return "支付宝开放平台-第三方应用-接口加签-应用私钥" + case OpenAlipayAppPublicKey: + return "支付宝开放平台-第三方应用-接口加签-应用公钥" + case OpenAlipayPublicKey: + return "支付宝开放平台-第三方应用-接口加签-支付宝公钥" + case OpenAppletAesKey: + return "支付宝开放平台-小程序-接口内容加密-aesKey" + case OpenAppletAppPrivateKey: + return "支付宝开放平台-小程序-接口加签-应用私钥" + case OpenAppletAppPublicKey: + return "支付宝开放平台-小程序-接口加签-应用公钥" + case OpenAppletPublicKey: + return "支付宝开放平台-小程序-接口加签-支付宝公钥" + case OpenAlipayAppid: + return "支付宝开放平台-第三方应用-appid" + case OpenAppletAppid: + return "支付宝开放平台-小程序-appid" + case FileBucketHost: + return "对象存储域名" + case FileBucketRegion: + return "文件所属区域" + case FileExt: + return "文件上传后缀,后台用户不限制后缀" + case FileBucketScheme: + return "文件上传模式" + case FileSecretKey: + return "对象存储SecretToken" + case FileUserUploadMaxSize: + return "用户单文件最大上传大小,byte" + case FileAccessKey: + return "对象存储AccessToken" + case FileBucket: + return "对象存储bucket" + case AdministratorContactInfo: + return "管理员联系方式" + case CentralKitchenForSchoolReserveMealTime: + return "央厨预定用餐时间" + case CentralKitchenForSchoolCancelMealTime: + return "央厨取消用餐时间" + case JsapiPayAppAutToken: + return "支付宝开放平台-jsapi支付-app_auth_token" + default: + return "未知" + } +} diff --git a/app/enum/enum_user_identity.go b/app/enum/enum_user_identity.go new file mode 100644 index 0000000..8d20d07 --- /dev/null +++ b/app/enum/enum_user_identity.go @@ -0,0 +1,67 @@ +package enum + +type UserIdentity int32 + +const ( + UserIdentityForCentralKitchenForStudent = 1 + UserIdentityForCentralKitchenForTeacher = 2 + UserIdentityForCentralKitchenForWorker = 3 + UserIdentityForSelfSupportForStudent = 4 + UserIdentityForSelfSupportForTeacher = 5 + UserIdentityForSelfSupportForWorker = 6 +) + +func (gt UserIdentity) String() string { + switch gt { + case UserIdentityForCentralKitchenForStudent: + return "央厨-学生" + case UserIdentityForCentralKitchenForTeacher: + return "央厨-教职员工" + case UserIdentityForCentralKitchenForWorker: + return "央厨-工作人员" + case UserIdentityForSelfSupportForStudent: + return "自营-学生" + case UserIdentityForSelfSupportForTeacher: + return "自营-教职员工" + case UserIdentityForSelfSupportForWorker: + return "自营-工作人员" + default: + return "未知" + } +} + +type UserIdentityKind int32 + +const ( + UserIdentityKindForCommon = 1 + UserIdentityKindForWorker = 2 +) + +func (gt UserIdentityKind) String() string { + switch gt { + case UserIdentityKindForCommon: + return "普通用户" + case UserIdentityKindForWorker: + return "工作人员" + default: + return "未知" + } +} + +type UserIdentityState int32 + +const ( + UserIdentityStateForNormal = 1 + UserIdentityStateForFreeze = 2 +) + +func (gt UserIdentityState) String() string { + switch gt { + case UserIdentityStateForNormal: + return "正常" + case UserIdentityStateForFreeze: + return "冻结" + default: + return "未知" + } +} diff --git a/app/lib/qiniu/bucket_create.go b/app/lib/qiniu/bucket_create.go new file mode 100644 index 0000000..28d8106 --- /dev/null +++ b/app/lib/qiniu/bucket_create.go @@ -0,0 +1,16 @@ +package qiniu + +import ( + "github.com/qiniu/api.v7/v7/auth" + "github.com/qiniu/api.v7/v7/storage" +) + +func BucketCreate() error { + mac := auth.New(AK, SK) + cfg := storage.Config{ + // 是否使用https域名进行资源管理 + UseHTTPS: false, + } + bucketManager := storage.NewBucketManager(mac, &cfg) + return bucketManager.CreateBucket("", storage.RIDHuanan) +} diff --git a/app/lib/qiniu/bucket_delete.go b/app/lib/qiniu/bucket_delete.go new file mode 100644 index 0000000..6d41521 --- /dev/null +++ b/app/lib/qiniu/bucket_delete.go @@ -0,0 +1,18 @@ +package qiniu + +import ( + "github.com/qiniu/api.v7/v7/auth" + "github.com/qiniu/api.v7/v7/storage" +) + +func BucketDelete(bucketName string) error { + mac := auth.New(AK, SK) + + cfg := storage.Config{ + // 是否使用https域名进行资源管理 + UseHTTPS: false, + } + + bucketManager := storage.NewBucketManager(mac, &cfg) + return bucketManager.DropBucket(bucketName) +} diff --git a/app/lib/qiniu/bucket_get_domain.go b/app/lib/qiniu/bucket_get_domain.go new file mode 100644 index 0000000..f4cee3a --- /dev/null +++ b/app/lib/qiniu/bucket_get_domain.go @@ -0,0 +1,18 @@ +package qiniu + +import ( + "github.com/qiniu/api.v7/v7/auth" + "github.com/qiniu/api.v7/v7/storage" +) + +func BucketGetDomain(bucketName string) (string, error) { + mac := auth.New(AK, SK) + + cfg := storage.Config{UseHTTPS: false} + bucketManager := storage.NewBucketManager(mac, &cfg) + b, err := bucketManager.ListBucketDomains(bucketName) + if err != nil { + return "", err + } + return b[0].Domain, nil +} diff --git a/app/lib/qiniu/init.go b/app/lib/qiniu/init.go new file mode 100644 index 0000000..1d4346a --- /dev/null +++ b/app/lib/qiniu/init.go @@ -0,0 +1,22 @@ +package qiniu + +import ( + "applet/app/utils" +) + +var ( + AK = "MmxNdai23egjNUHjdzEVaTPdPCIbWzENz9BQuak3" + SK = "mElaFlM9O16rXp-ihoQdJ9KOH56naKm3MoyQBA59" + BUCKET = "dev-fnuoos" // 桶子名称 + BUCKET_SCHEME = "http" + BUCKET_REGION = "up-z2.qiniup.com" + Expires uint64 = 3600 +) + +func Init(ak, sk, bucket, region, scheme string) { + AK, SK, BUCKET, BUCKET_REGION, BUCKET_SCHEME = ak, sk, bucket, region, scheme +} + +func Sign(t string) string { + return utils.Md5(AK + SK + t) +} diff --git a/app/lib/qiniu/req_img_upload.go b/app/lib/qiniu/req_img_upload.go new file mode 100644 index 0000000..aad4d9d --- /dev/null +++ b/app/lib/qiniu/req_img_upload.go @@ -0,0 +1,54 @@ +package qiniu + +import ( + "applet/app/md" + "applet/app/utils" + "time" + + "github.com/qiniu/api.v7/v7/auth/qbox" + _ "github.com/qiniu/api.v7/v7/conf" + "github.com/qiniu/api.v7/v7/storage" +) + +// 请求图片上传地址信息 +func ReqImgUpload(f *md.FileCallback, callbackUrl string) interface{} { + if ext := utils.FileExt(f.FileName); ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "gif" || ext == "bmp" || ext == "webp" { + f.Width = "$(imageInfo.width)" + f.Height = "$(imageInfo.height)" + } + f.Provider = "qiniu" + f.FileSize = "$(fsize)" + f.Hash = "$(etag)" + f.Bucket = "$(bucket)" + f.Mime = "$(mimeType)" + f.Time = utils.Int64ToStr(time.Now().Unix()) + f.Sign = Sign(f.Time) + putPolicy := storage.PutPolicy{ + Scope: BUCKET + ":" + f.FileName, // 使用覆盖方式时候必须请求里面有key,否则报错 + Expires: Expires, + ForceSaveKey: true, + SaveKey: f.FileName, + MimeLimit: "image/*", // 只允许上传图片 + CallbackURL: callbackUrl, + CallbackBody: utils.SerializeStr(f), + CallbackBodyType: "application/json", + } + return &struct { + Method string `json:"method"` + Key string `json:"key"` + Host string `json:"host"` + Token string `json:"token"` + }{Key: f.FileName, Method: "POST", Host: BUCKET_SCHEME + "://" + BUCKET_REGION, Token: putPolicy.UploadToken(qbox.NewMac(AK, SK))} +} + +/* +form表单上传 +地址 : http://upload-z2.qiniup.com +header + - Content-Type : multipart/form-data + +body : + - key : 文件名 + - token : 生成token + - file : 待上传文件 +*/ diff --git a/app/md/file.go b/app/md/file.go new file mode 100644 index 0000000..db52eea --- /dev/null +++ b/app/md/file.go @@ -0,0 +1,54 @@ +package md + +// 用户拥有上传权限的目录, 目录ID + +const ( + FILE_DIR_FEEDBACK = "feedback" + FILE_DIR_AVATAR = "avatar" + FILE_DIR_QRCODE = "qrcode" + FILE_DIR_STYLE = "style" +) + +var ( + FileUserDir = map[string]string{ + FILE_DIR_FEEDBACK: "4", // 用户反馈 + FILE_DIR_AVATAR: "5", // 用户头像 + FILE_DIR_QRCODE: "6", // 用户微信二维码 + FILE_DIR_STYLE: "7", // 用户样式 + } +) + +// 文件回调信息 +type FileCallback struct { + Uid string `json:"uid"` + DirId string `json:"dir_id"` + Provider string `json:"provider"` // 供应商 + FileName string `json:"fname"` // 原文件名 + FileSize string `json:"fsize"` + Hash string `json:"hash"` + Bucket string `json:"bucket"` + Mime string `json:"mime"` + Width string `json:"w,omitempty"` + Height string `json:"h,omitempty"` + Time string `json:"time"` // 默认一个小时内要上传完毕,否则超时 + Sign string `json:"sign"` // 签名 +} + +type FileList struct { + Path string `json:"path"` + DirId int `json:"dir_id"` + FileName string `json:"f_name"` // 显示名称 + StgName string `json:"stg_name"` // 存储名字 + Ext string `json:"ext"` // 后缀名, png,jpg等 + FileSize string `json:"f_size"` + Provider string `json:"provider"` // 存储供应商 + Hash string `json:"hash"` + Bucket string `json:"bucket"` + Width int `json:"w"` + Height int `json:"h"` + Mime string `json:"mime"` + IsAdm bool `json:"is_adm"` //是否管理后台上传 + IsDir bool `json:"is_dir"` //是否文件夹 + CreateAt int `json:"create_at"` + Url string `json:"url"` +} diff --git a/app/router/admin_router.go b/app/router/admin_router.go new file mode 100644 index 0000000..840c5ec --- /dev/null +++ b/app/router/admin_router.go @@ -0,0 +1,165 @@ +package router + +import ( + hdl2 "applet/app/admin/hdl" + hdl "applet/app/admin/hdl/enterprise_manage" + "applet/app/admin/mw" + "applet/app/cfg" + "github.com/gin-gonic/gin" +) + +// Init 初始化路由 +func Init() *gin.Engine { + mode := "release" + if cfg.Debug { + mode = "debug" + } + gin.SetMode(mode) + //创建一个新的启动器 + r := gin.New() + + // 是否打印访问日志, 在非正式环境都打印 + if mode != "release" { + r.Use(gin.Logger()) + } + r.Use(gin.Recovery()) + + r.GET("/favicon.ico", func(c *gin.Context) { + c.Status(204) + }) + r.NoRoute(func(c *gin.Context) { + c.JSON(404, gin.H{"code": 404, "msg": "page not found", "data": []struct{}{}}) + }) + r.NoMethod(func(c *gin.Context) { + c.JSON(405, gin.H{"code": 405, "msg": "method not allowed", "data": []struct{}{}}) + }) + r.Use(mw.Cors) + AdminRoute(r.Group("/api/admin")) + CustomerInit(r.Group("/api/v1")) + return r +} + +func rCompany(r *gin.RouterGroup) { + r.GET("/list", hdl2.CompanyList) + r.POST("/add", hdl2.CompanyAdd) + r.POST("/update", hdl2.CompanyUpdate) + r.DELETE("/delete/:id", hdl2.CompanyDelete) +} + +func rNotice(r *gin.RouterGroup) { + r.GET("/list", hdl2.NoticeList) + r.POST("/add", hdl2.NoticeAdd) + r.POST("/sort", hdl2.NoticeSort) + r.POST("/update", hdl2.NoticeUpdate) + r.DELETE("/delete/:id", hdl2.NoticeUpdate) +} + +func rBanner(r *gin.RouterGroup) { + r.GET("/list", hdl2.BannerList) + r.POST("/add", hdl2.BannerAdd) + r.POST("/sort", hdl2.BannerSort) + r.POST("/update", hdl2.BannerUpdate) + r.DELETE("/delete/:id", hdl2.BannerDelete) +} + +func rOss(r *gin.RouterGroup) { + r.POST("/upload/token", hdl2.ImgReqUpload) // 文件上传获取七牛云上传token +} + +func rSetCenter(r *gin.RouterGroup) { + r.GET("/get", hdl2.GetCenter) // 设置中心-获取 + r.POST("/set", hdl2.SetCenter) // 设置中心-设置 +} +func rAuditCenter(r *gin.RouterGroup) { + r.POST("/centralKitchenForSchoolOrderRefundList", hdl2.CentralKitchenForSchoolOrderRefundList) //审核中心-央厨-学校-订单退款列表 + r.POST("/centralKitchenForSchoolOrderRefundAudit", hdl2.CentralKitchenForSchoolOrderRefundAudit) //审核中心-央厨-学校-订单退款审核 +} + +func rUser(r *gin.RouterGroup) { + r.POST("/list", hdl2.UserList) //列表 + r.POST("/update", hdl2.UserUpdate) //编辑 + r.DELETE("/delete/:id", hdl2.UserDelete) //删除 +} + +func rEnterprise(r *gin.RouterGroup) { + r.POST("/list", hdl2.EnterpriseList) + r.POST("/add", hdl2.EnterpriseAdd) + r.POST("/update", hdl2.EnterpriseUpdate) + r.POST("/delete", hdl2.EnterpriseDelete) + r.POST("/updateState", hdl2.EnterpriseUpdateState) + r.POST("/addGrade", hdl2.EnterpriseAddGrade) + r.GET("/detail", hdl2.Detail) + r.GET("/schoolBelowGrade", hdl2.SchoolBelowGrade) //"学校"下年级 + r.GET("/schoolGradeBelowClass", hdl2.SchoolGradeBelowClass) //"学校"年级下班级 +} + +func rEnterpriseManage(r *gin.RouterGroup) { + r.GET("/info", hdl.EnterpriseManageInfo) //校企管理信息 + r.POST("/userIdentityList", hdl.UserIdentityList) //用户列表 + r.POST("/centralKitchenForSchool/userUpdate", hdl.CentralKitchenForSchoolUserUpdate) //"央厨-学校"用户编辑 + r.POST("/centralKitchenForSchool/userDelete", hdl.CentralKitchenForSchoolUserDelete) //"央厨-学校"用户删除 + + r.POST("/centralKitchenForSchool/studentList", hdl.CentralKitchenForSchoolStudentList) //"央厨-学校"学生列表 + r.POST("/centralKitchenForSchool/studentUpdate", hdl.CentralKitchenForSchoolStudentUpdate) //"央厨-学校"学生编辑 + r.POST("/centralKitchenForSchool/studentDelete", hdl.CentralKitchenForSchoolStudentDelete) //"央厨-学校"学生删除 + r.POST("/centralKitchenForSchool/studentAdmission", hdl.CentralKitchenForSchoolStudentAdmission) //"央厨-学校"学生升学 + r.POST("/centralKitchenForSchool/teacherList", hdl.CentralKitchenForSchoolTeacherList) //"央厨-学校"教师列表 + r.POST("/centralKitchenForSchool/teacherUpdate", hdl.CentralKitchenForSchoolTeacherUpdate) //"央厨-学校"教师编辑 + r.POST("/centralKitchenForSchool/teacherDelete", hdl.CentralKitchenForSchoolTeacherDelete) //"央厨-学校"教师删除 + + r.POST("/centralKitchenForSchool/ordList", hdl.CentralKitchenForSchoolOrdList) //"央厨-学校"订单列表 + r.GET("/centralKitchenForSchool/ordDetail", hdl.CentralKitchenForSchoolOrdDetail) //"央厨-学校"订单详情 + r.POST("/centralKitchenForSchool/ordRefund", hdl.CentralKitchenForSchoolOrdRefund) //"央厨-学校"订单退款 + + r.POST("/setBasicCentralKitchenForSchool", hdl.SetBasicCentralKitchenForSchool) //"央厨-学校"设置基础设置 + r.GET("/getBasicCentralKitchenForSchool", hdl.GetBasicCentralKitchenForSchool) //"央厨-学校"获取基础设置 + r.POST("/setCentralKitchenForSchoolWithSpec", hdl.SetCentralKitchenForSchoolWithSpec) //设置"央厨-学校-规格" + r.GET("/getCentralKitchenForSchoolWithSpec", hdl.GetCentralKitchenForSchoolWithSpec) //获取"央厨-学校-规格" + r.POST("/listCentralKitchenForSchoolPackage", hdl.ListCentralKitchenForSchoolPackage) //"央厨-学校-套餐" 列表 + r.GET("/detailCentralKitchenForSchoolPackage", hdl.DetailCentralKitchenForSchoolPackage) //"央厨-学校-套餐" 详情 + r.POST("/saveCentralKitchenForSchoolPackage", hdl.SaveCentralKitchenForSchoolPackage) //新增/编辑 "央厨-学校-套餐" + r.DELETE("/deleteCentralKitchenForSchoolPackage/:id", hdl.DeleteCentralKitchenForSchoolPackage) //删除 "央厨-学校-套餐" + + r.POST("/selfSupportForSchool/studentList", hdl.CentralKitchenForSchoolStudentList) //"央厨-学校"学生列表 + +} + +func rRole(r *gin.RouterGroup) { + r.GET("/roleList", hdl2.RoleList) //角色列表 + r.POST("/addRole", hdl2.AddRole) //角色添加 + r.POST("/roleBindPermissionGroup", hdl2.RoleBindPermissionGroup) //角色绑定权限组 + r.POST("/updateRoleState", hdl2.UpdateRoleState) //修改角色状态 + r.POST("/updateRole", hdl2.UpdateRole) //修改角色状态 + r.DELETE("/deleteRole/:id", hdl2.DeleteRole) //删除角色 + r.GET("/permissionGroupList", hdl2.PermissionGroupList) //权限组列表 + r.POST("/adminList", hdl2.AdminList) //管理员列表 + r.POST("/updateAdminState", hdl2.UpdateAdminState) //修改管理员状态 + r.POST("/updateAdmin", hdl2.UpdateAdmin) //修改管理员信息 + r.POST("/addAdmin", hdl2.AddAdmin) //新增管理员 + r.DELETE("/deleteAdmin/:adm_id", hdl2.DeleteAdmin) //删除管理员 + r.GET("/adminInfo", hdl2.AdminInfo) //获取管理员信息 + r.POST("/bindAdminRole", hdl2.BindAdminRole) //绑定角色 +} + +func AdminRoute(r *gin.RouterGroup) { + + r.POST("/login", hdl2.Login) + + r.Use(mw.Auth) //检测登录状态 + r.GET("/demo", hdl2.Demo) + r.GET("/userInfo", hdl2.UserInfo) //用户信息 + r.Any("/changePwd", hdl2.Demo) //修改密码 + r.GET("/sysCfg", hdl2.GetSysCfg) //基础配置-获取 + + r.Use(mw.CheckPermission) //检测权限 + rOss(r.Group("/oss")) + rCompany(r.Group("/company")) //公司管理 + rNotice(r.Group("/notice")) //公告管理 + rBanner(r.Group("/banner")) //轮播图管理 + rEnterprise(r.Group("/enterprise")) //校企管理 + rEnterpriseManage(r.Group("/enterpriseManage")) //校企管理详情 + rRole(r.Group("/role")) //权限管理 + rSetCenter(r.Group("/setCenter")) //设置中心 + rUser(r.Group("/user")) //用户管理 + rAuditCenter(r.Group("/auditCenter")) //审核中心 +} diff --git a/app/router/customer_router.go b/app/router/customer_router.go new file mode 100644 index 0000000..f2d550b --- /dev/null +++ b/app/router/customer_router.go @@ -0,0 +1,67 @@ +package router + +import ( + "applet/app/customer/hdl" + selfSupportForSchoolhdl "applet/app/customer/hdl/self_support_for_school" + "applet/app/customer/mw" + "github.com/gin-gonic/gin" +) + +func CustomerInit(r *gin.RouterGroup) { + rPay(r.Group("/pay")) + rCentralKitchenForSchoolOrder(r.Group("/order/centralKitchenForSchool")) + rSelfSupportForSchool(r.Group("/selfSupportForSchool")) + + r.POST("/login", hdl.Login) + r.POST("/register", hdl.Register) + r.POST("/aesDecrypt", hdl.AesDecrypt) + r.POST("/systemOauthToken", hdl.SystemOauthToken) + r.GET("/getSysCfg", hdl.GetSysCfg) //获取基础配置 + r.Use(mw.Auth) //检测登录状态 + r.GET("/userInfo", hdl.UserInfo) //用户信息 + + r.Group("/enterprise") + { + r.GET("enterprise/info", hdl.EnterpriseInfo) // 单位信息 + r.GET("enterprise/schoolBelowGrade", hdl.SchoolBelowGrade) //"学校"下年级 + r.GET("enterprise/schoolGradeBelowClass", hdl.SchoolGradeBelowClass) //"学校"年级下班级 + r.POST("enterprise/list", hdl.EnterpriseList) //"校企列表 + r.POST("enterprise/centralKitchenForSchool/saveUserIdentity", hdl.SaveCentralKitchenForSchoolUserIdentity) //"央厨-学校"新增身份信息 + r.GET("enterprise/centralKitchenForSchool/package", hdl.CentralKitchenForSchoolPackage) //"央厨-学校"获取套餐 + r.GET("enterprise/centralKitchenForSchool/myReserve", hdl.CentralKitchenForSchoolMyReserve) //"央厨-学校"我的预定 + r.POST("enterprise/selfSupportForSchool/saveUserIdentity", hdl.SaveSelfSupportForSchoolUserIdentity) //"自营-学校"新增身份信息 + } + + r.Group("/notice") + { + r.GET("notice/list", hdl.NoticeList) + } + + r.Group("/banner") + { + r.GET("banner/list", hdl.BannerList) + } +} + +func rPay(r *gin.RouterGroup) { + r.Use(mw.Auth) //检测登录状态 + r.POST("/buyPackage", hdl.BuyPackage) // 购买套餐 + r.GET("/ordState", hdl.OrdState) // 查看订单支付状态 +} + +func rCentralKitchenForSchoolOrder(r *gin.RouterGroup) { + r.Use(mw.Auth) //检测登录状态 + r.POST("/list", hdl.CentralKitchenForSchoolOrderList) // 央厨学校-订单列表 + r.GET("/belowWithDay", hdl.CentralKitchenForSchoolOrderBelowWithDay) // 央厨学校-订单下订餐日期数据 + r.GET("/detail", hdl.CentralKitchenForSchoolOrderDetail) // 央厨学校-订单详情 + r.POST("/refund", hdl.CentralKitchenForSchoolOrderRefund) // 央厨学校-订单退款申请 + r.POST("/refundList", hdl.CentralKitchenForSchoolOrderRefundList) // 央厨学校-订单退款列表 +} + +func rSelfSupportForSchool(r *gin.RouterGroup) { //自营学校 + r.Use(mw.Auth) //检测登录状态 + r.GET("/educateSceneTokenQuery", selfSupportForSchoolhdl.EducateSceneTokenQuery) // 自营学校-查询刷脸用户开通详细信息 + r.GET("/educateSceneTokenCreateForApplet", selfSupportForSchoolhdl.EducateSceneTokenCreateForApplet) // 自营学校-教育场景token生成处理器(作用于 跳转到一脸通行小程序采集人脸) + r.GET("/educateSceneTokenCreateForConcentratedCollectApplet", selfSupportForSchoolhdl.EducateSceneTokenCreateForConcentratedCollectApplet) // 自营学校-教育场景token生成处理器(作用于 跳转到集采小程序) + r.GET("/educateFacepayApply", selfSupportForSchoolhdl.EducateFacepayApply) // 自营学校-创建刷脸支付开通标识 +} diff --git a/app/svc/svc_file_img_upload.go b/app/svc/svc_file_img_upload.go new file mode 100644 index 0000000..ab49113 --- /dev/null +++ b/app/svc/svc_file_img_upload.go @@ -0,0 +1,68 @@ +package svc + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/enum" + "applet/app/lib/qiniu" + "applet/app/md" + "applet/app/utils" + "errors" + "fmt" + "strings" + "time" +) + +// 请求文件上传 +func ImgReqUpload(uid, dirName, fname, callbackUrl string, fsize int64) (interface{}, error) { + ext := utils.FileExt(fname) + if err := initStg(fsize, ext); err != nil { + return nil, err + } + pureFileName := strings.Replace(fname, "."+ext, "", 1) + pureFileName += "-" + utils.RandString(6, utils.AnyToString(time.Now().UnixNano())) + newName := dirName + "/" + pureFileName + "." + ext + + f := &md.FileCallback{ + Uid: uid, + DirId: dirName, + FileName: newName, + } + return qiniu.ReqImgUpload(f, callbackUrl), nil +} + +func initStg(fsize int64, ext string) error { + // 获取上传配置 + fileCfg := make(map[string]string) + sysCfgDb := db.SysCfgDb{} + sysCfgDb.Set() + fileCfg[enum.FileBucket] = sysCfgDb.SysCfgGetWithDb(enum.FileBucket) + fileCfg[enum.FileBucketHost] = sysCfgDb.SysCfgGetWithDb(enum.FileBucketHost) + fileCfg[enum.FileAccessKey] = sysCfgDb.SysCfgGetWithDb(enum.FileAccessKey) + fileCfg[enum.FileSecretKey] = sysCfgDb.SysCfgGetWithDb(enum.FileSecretKey) + fileCfg[enum.FileBucketRegion] = sysCfgDb.SysCfgGetWithDb(enum.FileBucketRegion) + fileCfg[enum.FileUserUploadMaxSize] = sysCfgDb.SysCfgGetWithDb(enum.FileUserUploadMaxSize) + fileCfg[enum.FileExt] = sysCfgDb.SysCfgGetWithDb(enum.FileExt) + fileCfg[enum.FileBucketScheme] = sysCfgDb.SysCfgGetWithDb(enum.FileBucketScheme) + utils.FilePutContents("initStg", fmt.Sprintf("[KEY_CFG_FILE_BUCKET]:%s ; [KEY_CFG_FILE_HOST]:%s ; [KEY_CFG_FILE_AK]::%s;"+ + " [KEY_CFG_FILE_SK]::%s, ;[KEY_CFG_FILE_REGION]::%s, ;[KEY_CFG_FILE_MAX_SIZE]::%s, ;[KEY_CFG_FILE_EXT]::%s, ;[KEY_CFG_FILE_SCHEME]::%s, "+ + ">>>>>>>>>>>>>>>>>>>>", fileCfg[enum.FileBucket], fileCfg[enum.FileBucketHost], fileCfg[enum.FileAccessKey], fileCfg[enum.FileSecretKey], + fileCfg[enum.FileBucketRegion], fileCfg[enum.FileUserUploadMaxSize], fileCfg[enum.FileExt], fileCfg[enum.FileBucketScheme])) + for _, v := range fileCfg { + if v == "" { + return errors.New("上传配置不完整") + } + } + + qiniu.Init(fileCfg[enum.FileAccessKey], fileCfg[enum.FileSecretKey], fileCfg[enum.FileBucket], fileCfg[enum.FileBucketRegion], fileCfg[enum.FileBucketScheme]) + + // 检查文件大小限制 + if utils.StrToInt64(fileCfg[enum.FileUserUploadMaxSize]) < fsize { + return e.NewErrCode(e.ERR_FILE_MAX_SIZE) + } + // 检查文件后缀 + if !strings.Contains(fileCfg[enum.FileExt], ext) { + return e.NewErrCode(e.ERR_FILE_EXT) + } + return nil +} diff --git a/app/svc/svc_validate_comm.go b/app/svc/svc_validate_comm.go new file mode 100644 index 0000000..7d7ff38 --- /dev/null +++ b/app/svc/svc_validate_comm.go @@ -0,0 +1,33 @@ +package svc + +import ( + "applet/app/e" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +func HandleValidateErr(err error) error { + switch err.(type) { + case *json.UnmarshalTypeError: + return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误") + case validator.ValidationErrors: + errs := err.(validator.ValidationErrors) + transMsgMap := errs.Translate(utils.ValidatorTrans) // utils.ValidatorTrans \app\utils\validator_err_trans.go::ValidatorTransInit初始化获得 + transMsgOne := transMsgMap[GetOneKeyOfMapString(transMsgMap)] + return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne) + default: + _ = logx.Error(err) + return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err)) + } +} + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} diff --git a/app/task/init.go b/app/task/init.go new file mode 100644 index 0000000..1781319 --- /dev/null +++ b/app/task/init.go @@ -0,0 +1,92 @@ +package task + +import ( + "applet/app/admin/db/model" + taskMd "applet/app/task/md" + "time" + + "applet/app/utils/logx" + "github.com/robfig/cron/v3" + "xorm.io/xorm" +) + +var ( + timer *cron.Cron + jobs = map[string]func(*xorm.Engine, string){} + baseEntryId cron.EntryID + entryIds []cron.EntryID + taskCfgList map[string]*[]model.SysCfg + ch = make(chan int, 30) + workerNum = 15 // 智盟跟单并发数量 + otherCh = make(chan int, 30) + otherWorkerNum = 18 // 淘宝, 苏宁, 考拉并发量 +) + +func Init() { + // 初始化任务列表 + initTasks() + var err error + timer = cron.New() + // reload为初始化数据库方法 + if baseEntryId, err = timer.AddFunc("@every 15m", reload); err != nil { + _ = logx.Fatal(err) + } +} + +func Run() { + reload() + timer.Start() + _ = logx.Info("auto tasks running...") +} + +func reload() { + // 重新初始化数据库 + + if len(taskCfgList) > 0 { + // 删除原有所有任务 + if len(entryIds) > 0 { + for _, v := range entryIds { + if v != baseEntryId { + timer.Remove(v) + } + } + entryIds = nil + } + var ( + entryId cron.EntryID + err error + ) + // 添加任务 + for dbName, v := range taskCfgList { + for _, vv := range *v { + if _, ok := jobs[vv.Key]; ok && vv.Val != "" { + // fmt.Println(vv.Val) + if entryId, err = timer.AddFunc(vv.Val, doTask(dbName, vv.Key)); err == nil { + entryIds = append(entryIds, entryId) + } + } + } + } + + } +} + +func doTask(dbName, fnName string) func() { + return func() { + begin := time.Now().Local() + end := time.Now().Local() + logx.Infof( + "[%s] AutoTask <%s> started at <%s>, ended at <%s> duration <%s>", + dbName, + fnName, + begin.Format("2006-01-02 15:04:05.000"), + end.Format("2006-01-02 15:04:05.000"), + time.Duration(end.UnixNano()-begin.UnixNano()).String(), + ) + } +} + +// 增加自动任务队列 +func initTasks() { + jobs[taskMd.MallCronOrderCancel] = taskCancelOrder // 取消订单 +} diff --git a/app/task/md/cron_key.go b/app/task/md/cron_key.go new file mode 100644 index 0000000..b38ccc8 --- /dev/null +++ b/app/task/md/cron_key.go @@ -0,0 +1,5 @@ +package md + +const ( + MallCronOrderCancel = "mall_cron_order_cancel" // 取消订单任务 +) diff --git a/app/task/svc/svc_cancel_order.go b/app/task/svc/svc_cancel_order.go new file mode 100644 index 0000000..0c35b5f --- /dev/null +++ b/app/task/svc/svc_cancel_order.go @@ -0,0 +1,64 @@ +package svc + +import ( + "applet/app/db" + "applet/app/utils" + "applet/app/utils/logx" + "errors" + "fmt" + "time" + "xorm.io/xorm" +) + +func CancelOrder(eg *xorm.Engine, dbName string) { + fmt.Println("cancel order...") + defer func() { + if err := recover(); err != nil { + _ = logx.Error(err) + } + }() + + timeStr, err := getCancelCfg(eg, dbName) + if err != nil { + fmt.Println(err.Error()) + return + } + + now := time.Now() + // x 分钟后取消订单 + expTime := now.Add(-time.Hour * time.Duration(utils.StrToInt64(timeStr))) + expTimeStr := utils.Time2String(expTime, "") + + page := 1 + + for { + isEmpty, err := handleOnePage(eg, dbName, expTimeStr) + if err != nil { + _ = logx.Error(err) + break + } + if isEmpty { + break + } + + if page > 100 { + break + } + + page += 1 + + } +} + +func handleOnePage(eg *xorm.Engine, dbName, expTimeStr string) (isEmpty bool, err error) { + return false, nil +} + +func getCancelCfg(eg *xorm.Engine, masterId string) (string, error) { + cfg := db.SysCfgGetWithDb(eg, masterId, "order_expiration_time") + + if cfg == "" { + return "", errors.New("order_expiration_time no found") + } + return cfg, nil +} diff --git a/app/task/task_cancel_order.go b/app/task/task_cancel_order.go new file mode 100644 index 0000000..2e45bbb --- /dev/null +++ b/app/task/task_cancel_order.go @@ -0,0 +1,23 @@ +package task + +import ( + "applet/app/task/svc" + "math/rand" + "time" + "xorm.io/xorm" +) + +// 取消订单 +func taskCancelOrder(eg *xorm.Engine, dbName string) { + for { + if len(ch) > workerNum { + time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) + } else { + goto START + } + } +START: + ch <- 1 + svc.CancelOrder(eg, dbName) + <-ch +} diff --git a/app/utils/aes.go b/app/utils/aes.go new file mode 100644 index 0000000..8f5aaac --- /dev/null +++ b/app/utils/aes.go @@ -0,0 +1,123 @@ +package utils + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func AesEncrypt(rawData, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + rawData = PKCS5Padding(rawData, blockSize) + // rawData = ZeroPadding(rawData, block.BlockSize()) + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) + encrypted := make([]byte, len(rawData)) + // 根据CryptBlocks方法的说明,如下方式初始化encrypted也可以 + // encrypted := rawData + blockMode.CryptBlocks(encrypted, rawData) + return encrypted, nil +} + +func AesDecrypt(encrypted, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + rawData := make([]byte, len(encrypted)) + // rawData := encrypted + blockMode.CryptBlocks(rawData, encrypted) + rawData = PKCS5UnPadding(rawData) + // rawData = ZeroUnPadding(rawData) + return rawData, nil +} + +func ZeroPadding(cipherText []byte, blockSize int) []byte { + padding := blockSize - len(cipherText)%blockSize + padText := bytes.Repeat([]byte{0}, padding) + return append(cipherText, padText...) +} + +func ZeroUnPadding(rawData []byte) []byte { + length := len(rawData) + unPadding := int(rawData[length-1]) + return rawData[:(length - unPadding)] +} + +func PKCS5Padding(cipherText []byte, blockSize int) []byte { + padding := blockSize - len(cipherText)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(cipherText, padText...) +} + +func PKCS5UnPadding(rawData []byte) []byte { + length := len(rawData) + // 去掉最后一个字节 unPadding 次 + unPadding := int(rawData[length-1]) + return rawData[:(length - unPadding)] +} + +// 填充0 +func zeroFill(key *string) { + l := len(*key) + if l != 16 && l != 24 && l != 32 { + if l < 16 { + *key = *key + fmt.Sprintf("%0*d", 16-l, 0) + } else if l < 24 { + *key = *key + fmt.Sprintf("%0*d", 24-l, 0) + } else if l < 32 { + *key = *key + fmt.Sprintf("%0*d", 32-l, 0) + } else { + *key = string([]byte(*key)[:32]) + } + } +} + +type AesCrypt struct { + Key []byte + Iv []byte +} + +func (a *AesCrypt) Encrypt(data []byte) ([]byte, error) { + aesBlockEncrypt, err := aes.NewCipher(a.Key) + if err != nil { + println(err.Error()) + return nil, err + } + + content := pKCS5Padding(data, aesBlockEncrypt.BlockSize()) + cipherBytes := make([]byte, len(content)) + aesEncrypt := cipher.NewCBCEncrypter(aesBlockEncrypt, a.Iv) + aesEncrypt.CryptBlocks(cipherBytes, content) + return cipherBytes, nil +} + +func (a *AesCrypt) Decrypt(src []byte) (data []byte, err error) { + decrypted := make([]byte, len(src)) + var aesBlockDecrypt cipher.Block + aesBlockDecrypt, err = aes.NewCipher(a.Key) + if err != nil { + println(err.Error()) + return nil, err + } + aesDecrypt := cipher.NewCBCDecrypter(aesBlockDecrypt, a.Iv) + aesDecrypt.CryptBlocks(decrypted, src) + return pKCS5Trimming(decrypted), nil +} + +func pKCS5Padding(cipherText []byte, blockSize int) []byte { + padding := blockSize - len(cipherText)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(cipherText, padText...) +} + +func pKCS5Trimming(encrypt []byte) []byte { + padding := encrypt[len(encrypt)-1] + return encrypt[:len(encrypt)-int(padding)] +} diff --git a/app/utils/base64.go b/app/utils/base64.go new file mode 100644 index 0000000..ee16553 --- /dev/null +++ b/app/utils/base64.go @@ -0,0 +1,95 @@ +package utils + +import ( + "encoding/base64" + "fmt" +) + +const ( + Base64Std = iota + Base64Url + Base64RawStd + Base64RawUrl +) + +func Base64StdEncode(str interface{}) string { + return Base64Encode(str, Base64Std) +} + +func Base64StdDecode(str interface{}) string { + return Base64Decode(str, Base64Std) +} + +func Base64UrlEncode(str interface{}) string { + return Base64Encode(str, Base64Url) +} + +func Base64UrlDecode(str interface{}) string { + return Base64Decode(str, Base64Url) +} + +func Base64RawStdEncode(str interface{}) string { + return Base64Encode(str, Base64RawStd) +} + +func Base64RawStdDecode(str interface{}) string { + return Base64Decode(str, Base64RawStd) +} + +func Base64RawUrlEncode(str interface{}) string { + return Base64Encode(str, Base64RawUrl) +} + +func Base64RawUrlDecode(str interface{}) string { + return Base64Decode(str, Base64RawUrl) +} + +func Base64Encode(str interface{}, encode int) string { + newEncode := base64Encode(encode) + if newEncode == nil { + return "" + } + switch v := str.(type) { + case string: + return newEncode.EncodeToString([]byte(v)) + case []byte: + return newEncode.EncodeToString(v) + } + return newEncode.EncodeToString([]byte(fmt.Sprint(str))) +} + +func Base64Decode(str interface{}, encode int) string { + var err error + var b []byte + newEncode := base64Encode(encode) + if newEncode == nil { + return "" + } + switch v := str.(type) { + case string: + b, err = newEncode.DecodeString(v) + case []byte: + b, err = newEncode.DecodeString(string(v)) + default: + return "" + } + if err != nil { + return "" + } + return string(b) +} + +func base64Encode(encode int) *base64.Encoding { + switch encode { + case Base64Std: + return base64.StdEncoding + case Base64Url: + return base64.URLEncoding + case Base64RawStd: + return base64.RawStdEncoding + case Base64RawUrl: + return base64.RawURLEncoding + default: + return nil + } +} diff --git a/app/utils/boolean.go b/app/utils/boolean.go new file mode 100644 index 0000000..d64c876 --- /dev/null +++ b/app/utils/boolean.go @@ -0,0 +1,26 @@ +package utils + +import "reflect" + +// 检验一个值是否为空 +func Empty(val interface{}) bool { + v := reflect.ValueOf(val) + switch v.Kind() { + case reflect.String, reflect.Array: + return v.Len() == 0 + case reflect.Map, reflect.Slice: + return v.Len() == 0 || v.IsNil() + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface()) +} \ No newline at end of file diff --git a/app/utils/cache/base.go b/app/utils/cache/base.go new file mode 100644 index 0000000..64648dd --- /dev/null +++ b/app/utils/cache/base.go @@ -0,0 +1,421 @@ +package cache + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +const ( + redisDialTTL = 10 * time.Second + redisReadTTL = 3 * time.Second + redisWriteTTL = 3 * time.Second + redisIdleTTL = 10 * time.Second + redisPoolTTL = 10 * time.Second + redisPoolSize int = 512 + redisMaxIdleConn int = 64 + redisMaxActive int = 512 +) + +var ( + ErrNil = errors.New("nil return") + ErrWrongArgsNum = errors.New("args num error") + ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64") +) + +// 以下为提供类型转换 + +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return reply, nil + case int8: + return int(reply), nil + case int16: + return int(reply), nil + case int32: + return int(reply), nil + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case uint: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int(reply), nil + case uint16: + return int(reply), nil + case uint32: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint64: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 0) + return int(n), err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply) +} + +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return int64(reply), nil + case int8: + return int64(reply), nil + case int16: + return int64(reply), nil + case int32: + return int64(reply), nil + case int64: + return reply, nil + case uint: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int64(reply), nil + case uint16: + return int64(reply), nil + case uint32: + return int64(reply), nil + case uint64: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply) +} + +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case uint: + return uint64(reply), nil + case uint8: + return uint64(reply), nil + case uint16: + return uint64(reply), nil + case uint32: + return uint64(reply), nil + case uint64: + return reply, nil + case int: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int8: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int16: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int32: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int64: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply) +} + +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + + var value float64 + err = nil + switch v := reply.(type) { + case float32: + value = float64(v) + case float64: + value = v + case int: + value = float64(v) + case int8: + value = float64(v) + case int16: + value = float64(v) + case int32: + value = float64(v) + case int64: + value = float64(v) + case uint: + value = float64(v) + case uint8: + value = float64(v) + case uint16: + value = float64(v) + case uint32: + value = float64(v) + case uint64: + value = float64(v) + case []byte: + data := string(v) + if len(data) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(string(v), 64) + case string: + if len(v) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(v, 64) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v) + } + + return value, err +} + +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case bool: + return reply, nil + case int64: + return reply != 0, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(data) + case string: + if len(reply) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(reply) + case nil: + return false, ErrNil + case error: + return false, reply + } + return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply) +} + +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + if len(reply) == 0 { + return nil, ErrNil + } + return reply, nil + case string: + data := []byte(reply) + if len(data) == 0 { + return nil, ErrNil + } + return data, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply) +} + +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + + value := "" + err = nil + switch v := reply.(type) { + case string: + if len(v) == 0 { + return "", ErrNil + } + + value = v + case []byte: + if len(v) == 0 { + return "", ErrNil + } + + value = string(v) + case int: + value = strconv.FormatInt(int64(v), 10) + case int8: + value = strconv.FormatInt(int64(v), 10) + case int16: + value = strconv.FormatInt(int64(v), 10) + case int32: + value = strconv.FormatInt(int64(v), 10) + case int64: + value = strconv.FormatInt(v, 10) + case uint: + value = strconv.FormatUint(uint64(v), 10) + case uint8: + value = strconv.FormatUint(uint64(v), 10) + case uint16: + value = strconv.FormatUint(uint64(v), 10) + case uint32: + value = strconv.FormatUint(uint64(v), 10) + case uint64: + value = strconv.FormatUint(v, 10) + case float32: + value = strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + value = strconv.FormatFloat(v, 'f', -1, 64) + case bool: + value = strconv.FormatBool(v) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v) + } + + return value, err +} + +func Strings(reply interface{}, err error) ([]string, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([]string, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + switch subReply := reply[i].(type) { + case string: + result[i] = subReply + case []byte: + result[i] = string(subReply) + default: + return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i]) + } + } + return result, nil + case []string: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply) +} + +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply) +} diff --git a/app/utils/cache/cache/cache.go b/app/utils/cache/cache/cache.go new file mode 100644 index 0000000..e43c5f0 --- /dev/null +++ b/app/utils/cache/cache/cache.go @@ -0,0 +1,107 @@ +package cache + +import ( + "fmt" + "time" +) + +var c Cache + +type Cache interface { + // get cached value by key. + Get(key string) interface{} + // GetMulti is a batch version of Get. + GetMulti(keys []string) []interface{} + // set cached value with key and expire time. + Put(key string, val interface{}, timeout time.Duration) error + // delete cached value by key. + Delete(key string) error + // increase cached int value by key, as a counter. + Incr(key string) error + // decrease cached int value by key, as a counter. + Decr(key string) error + // check if cached value exists or not. + IsExist(key string) bool + // clear all cache. + ClearAll() error + // start gc routine based on config string settings. + StartAndGC(config string) error +} + +// Instance is a function create a new Cache Instance +type Instance func() Cache + +var adapters = make(map[string]Instance) + +// Register makes a cache adapter available by the adapter name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, adapter Instance) { + if adapter == nil { + panic("cache: Register adapter is nil") + } + if _, ok := adapters[name]; ok { + panic("cache: Register called twice for adapter " + name) + } + adapters[name] = adapter +} + +// NewCache Create a new cache driver by adapter name and config string. +// config need to be correct JSON as string: {"interval":360}. +// it will start gc automatically. +func NewCache(adapterName, config string) (adapter Cache, err error) { + instanceFunc, ok := adapters[adapterName] + if !ok { + err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) + return + } + adapter = instanceFunc() + err = adapter.StartAndGC(config) + if err != nil { + adapter = nil + } + return +} + +func InitCache(adapterName, config string) (err error) { + instanceFunc, ok := adapters[adapterName] + if !ok { + err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) + return + } + c = instanceFunc() + err = c.StartAndGC(config) + if err != nil { + c = nil + } + return +} + +func Get(key string) interface{} { + return c.Get(key) +} + +func GetMulti(keys []string) []interface{} { + return c.GetMulti(keys) +} +func Put(key string, val interface{}, ttl time.Duration) error { + return c.Put(key, val, ttl) +} +func Delete(key string) error { + return c.Delete(key) +} +func Incr(key string) error { + return c.Incr(key) +} +func Decr(key string) error { + return c.Decr(key) +} +func IsExist(key string) bool { + return c.IsExist(key) +} +func ClearAll() error { + return c.ClearAll() +} +func StartAndGC(cfg string) error { + return c.StartAndGC(cfg) +} diff --git a/app/utils/cache/cache/conv.go b/app/utils/cache/cache/conv.go new file mode 100644 index 0000000..6b700ae --- /dev/null +++ b/app/utils/cache/cache/conv.go @@ -0,0 +1,86 @@ +package cache + +import ( + "fmt" + "strconv" +) + +// GetString convert interface to string. +func GetString(v interface{}) string { + switch result := v.(type) { + case string: + return result + case []byte: + return string(result) + default: + if v != nil { + return fmt.Sprint(result) + } + } + return "" +} + +// GetInt convert interface to int. +func GetInt(v interface{}) int { + switch result := v.(type) { + case int: + return result + case int32: + return int(result) + case int64: + return int(result) + default: + if d := GetString(v); d != "" { + value, _ := strconv.Atoi(d) + return value + } + } + return 0 +} + +// GetInt64 convert interface to int64. +func GetInt64(v interface{}) int64 { + switch result := v.(type) { + case int: + return int64(result) + case int32: + return int64(result) + case int64: + return result + default: + + if d := GetString(v); d != "" { + value, _ := strconv.ParseInt(d, 10, 64) + return value + } + } + return 0 +} + +// GetFloat64 convert interface to float64. +func GetFloat64(v interface{}) float64 { + switch result := v.(type) { + case float64: + return result + default: + if d := GetString(v); d != "" { + value, _ := strconv.ParseFloat(d, 64) + return value + } + } + return 0 +} + +// GetBool convert interface to bool. +func GetBool(v interface{}) bool { + switch result := v.(type) { + case bool: + return result + default: + if d := GetString(v); d != "" { + value, _ := strconv.ParseBool(d) + return value + } + } + return false +} diff --git a/app/utils/cache/cache/file.go b/app/utils/cache/cache/file.go new file mode 100644 index 0000000..5c4e366 --- /dev/null +++ b/app/utils/cache/cache/file.go @@ -0,0 +1,241 @@ +package cache + +import ( + "bytes" + "crypto/md5" + "encoding/gob" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strconv" + "time" +) + +// FileCacheItem is basic unit of file cache adapter. +// it contains data and expire time. +type FileCacheItem struct { + Data interface{} + LastAccess time.Time + Expired time.Time +} + +// FileCache Config +var ( + FileCachePath = "cache" // cache directory + FileCacheFileSuffix = ".bin" // cache file suffix + FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files. + FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever. +) + +// FileCache is cache adapter for file storage. +type FileCache struct { + CachePath string + FileSuffix string + DirectoryLevel int + EmbedExpiry int +} + +// NewFileCache Create new file cache with no config. +// the level and expiry need set in method StartAndGC as config string. +func NewFileCache() Cache { + // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} + return &FileCache{} +} + +// StartAndGC will start and begin gc for file cache. +// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0} +func (fc *FileCache) StartAndGC(config string) error { + + var cfg map[string]string + json.Unmarshal([]byte(config), &cfg) + if _, ok := cfg["CachePath"]; !ok { + cfg["CachePath"] = FileCachePath + } + if _, ok := cfg["FileSuffix"]; !ok { + cfg["FileSuffix"] = FileCacheFileSuffix + } + if _, ok := cfg["DirectoryLevel"]; !ok { + cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) + } + if _, ok := cfg["EmbedExpiry"]; !ok { + cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10) + } + fc.CachePath = cfg["CachePath"] + fc.FileSuffix = cfg["FileSuffix"] + fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"]) + fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"]) + + fc.Init() + return nil +} + +// Init will make new dir for file cache if not exist. +func (fc *FileCache) Init() { + if ok, _ := exists(fc.CachePath); !ok { // todo : error handle + _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle + } +} + +// get cached file name. it's md5 encoded. +func (fc *FileCache) getCacheFileName(key string) string { + m := md5.New() + io.WriteString(m, key) + keyMd5 := hex.EncodeToString(m.Sum(nil)) + cachePath := fc.CachePath + switch fc.DirectoryLevel { + case 2: + cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4]) + case 1: + cachePath = filepath.Join(cachePath, keyMd5[0:2]) + } + + if ok, _ := exists(cachePath); !ok { // todo : error handle + _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle + } + + return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)) +} + +// Get value from file cache. +// if non-exist or expired, return empty string. +func (fc *FileCache) Get(key string) interface{} { + fileData, err := FileGetContents(fc.getCacheFileName(key)) + if err != nil { + return "" + } + var to FileCacheItem + GobDecode(fileData, &to) + if to.Expired.Before(time.Now()) { + return "" + } + return to.Data +} + +// GetMulti gets values from file cache. +// if non-exist or expired, return empty string. +func (fc *FileCache) GetMulti(keys []string) []interface{} { + var rc []interface{} + for _, key := range keys { + rc = append(rc, fc.Get(key)) + } + return rc +} + +// Put value into file cache. +// timeout means how long to keep this file, unit of ms. +// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever. +func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error { + gob.Register(val) + + item := FileCacheItem{Data: val} + if timeout == FileCacheEmbedExpiry { + item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years + } else { + item.Expired = time.Now().Add(timeout) + } + item.LastAccess = time.Now() + data, err := GobEncode(item) + if err != nil { + return err + } + return FilePutContents(fc.getCacheFileName(key), data) +} + +// Delete file cache value. +func (fc *FileCache) Delete(key string) error { + filename := fc.getCacheFileName(key) + if ok, _ := exists(filename); ok { + return os.Remove(filename) + } + return nil +} + +// Incr will increase cached int value. +// fc value is saving forever unless Delete. +func (fc *FileCache) Incr(key string) error { + data := fc.Get(key) + var incr int + if reflect.TypeOf(data).Name() != "int" { + incr = 0 + } else { + incr = data.(int) + 1 + } + fc.Put(key, incr, FileCacheEmbedExpiry) + return nil +} + +// Decr will decrease cached int value. +func (fc *FileCache) Decr(key string) error { + data := fc.Get(key) + var decr int + if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { + decr = 0 + } else { + decr = data.(int) - 1 + } + fc.Put(key, decr, FileCacheEmbedExpiry) + return nil +} + +// IsExist check value is exist. +func (fc *FileCache) IsExist(key string) bool { + ret, _ := exists(fc.getCacheFileName(key)) + return ret +} + +// ClearAll will clean cached files. +// not implemented. +func (fc *FileCache) ClearAll() error { + return nil +} + +// check file exist. +func exists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +// FileGetContents Get bytes to file. +// if non-exist, create this file. +func FileGetContents(filename string) (data []byte, e error) { + return ioutil.ReadFile(filename) +} + +// FilePutContents Put bytes to file. +// if non-exist, create this file. +func FilePutContents(filename string, content []byte) error { + return ioutil.WriteFile(filename, content, os.ModePerm) +} + +// GobEncode Gob encodes file cache item. +func GobEncode(data interface{}) ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(data) + if err != nil { + return nil, err + } + return buf.Bytes(), err +} + +// GobDecode Gob decodes file cache item. +func GobDecode(data []byte, to *FileCacheItem) error { + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + return dec.Decode(&to) +} + +func init() { + Register("file", NewFileCache) +} diff --git a/app/utils/cache/cache/memory.go b/app/utils/cache/cache/memory.go new file mode 100644 index 0000000..0cc5015 --- /dev/null +++ b/app/utils/cache/cache/memory.go @@ -0,0 +1,239 @@ +package cache + +import ( + "encoding/json" + "errors" + "sync" + "time" +) + +var ( + // DefaultEvery means the clock time of recycling the expired cache items in memory. + DefaultEvery = 60 // 1 minute +) + +// MemoryItem store memory cache item. +type MemoryItem struct { + val interface{} + createdTime time.Time + lifespan time.Duration +} + +func (mi *MemoryItem) isExpire() bool { + // 0 means forever + if mi.lifespan == 0 { + return false + } + return time.Now().Sub(mi.createdTime) > mi.lifespan +} + +// MemoryCache is Memory cache adapter. +// it contains a RW locker for safe map storage. +type MemoryCache struct { + sync.RWMutex + dur time.Duration + items map[string]*MemoryItem + Every int // run an expiration check Every clock time +} + +// NewMemoryCache returns a new MemoryCache. +func NewMemoryCache() Cache { + cache := MemoryCache{items: make(map[string]*MemoryItem)} + return &cache +} + +// Get cache from memory. +// if non-existed or expired, return nil. +func (bc *MemoryCache) Get(name string) interface{} { + bc.RLock() + defer bc.RUnlock() + if itm, ok := bc.items[name]; ok { + if itm.isExpire() { + return nil + } + return itm.val + } + return nil +} + +// GetMulti gets caches from memory. +// if non-existed or expired, return nil. +func (bc *MemoryCache) GetMulti(names []string) []interface{} { + var rc []interface{} + for _, name := range names { + rc = append(rc, bc.Get(name)) + } + return rc +} + +// Put cache to memory. +// if lifespan is 0, it will be forever till restart. +func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error { + bc.Lock() + defer bc.Unlock() + bc.items[name] = &MemoryItem{ + val: value, + createdTime: time.Now(), + lifespan: lifespan, + } + return nil +} + +// Delete cache in memory. +func (bc *MemoryCache) Delete(name string) error { + bc.Lock() + defer bc.Unlock() + if _, ok := bc.items[name]; !ok { + return errors.New("key not exist") + } + delete(bc.items, name) + if _, ok := bc.items[name]; ok { + return errors.New("delete key error") + } + return nil +} + +// Incr increase cache counter in memory. +// it supports int,int32,int64,uint,uint32,uint64. +func (bc *MemoryCache) Incr(key string) error { + bc.RLock() + defer bc.RUnlock() + itm, ok := bc.items[key] + if !ok { + return errors.New("key not exist") + } + switch itm.val.(type) { + case int: + itm.val = itm.val.(int) + 1 + case int32: + itm.val = itm.val.(int32) + 1 + case int64: + itm.val = itm.val.(int64) + 1 + case uint: + itm.val = itm.val.(uint) + 1 + case uint32: + itm.val = itm.val.(uint32) + 1 + case uint64: + itm.val = itm.val.(uint64) + 1 + default: + return errors.New("item val is not (u)int (u)int32 (u)int64") + } + return nil +} + +// Decr decrease counter in memory. +func (bc *MemoryCache) Decr(key string) error { + bc.RLock() + defer bc.RUnlock() + itm, ok := bc.items[key] + if !ok { + return errors.New("key not exist") + } + switch itm.val.(type) { + case int: + itm.val = itm.val.(int) - 1 + case int64: + itm.val = itm.val.(int64) - 1 + case int32: + itm.val = itm.val.(int32) - 1 + case uint: + if itm.val.(uint) > 0 { + itm.val = itm.val.(uint) - 1 + } else { + return errors.New("item val is less than 0") + } + case uint32: + if itm.val.(uint32) > 0 { + itm.val = itm.val.(uint32) - 1 + } else { + return errors.New("item val is less than 0") + } + case uint64: + if itm.val.(uint64) > 0 { + itm.val = itm.val.(uint64) - 1 + } else { + return errors.New("item val is less than 0") + } + default: + return errors.New("item val is not int int64 int32") + } + return nil +} + +// IsExist check cache exist in memory. +func (bc *MemoryCache) IsExist(name string) bool { + bc.RLock() + defer bc.RUnlock() + if v, ok := bc.items[name]; ok { + return !v.isExpire() + } + return false +} + +// ClearAll will delete all cache in memory. +func (bc *MemoryCache) ClearAll() error { + bc.Lock() + defer bc.Unlock() + bc.items = make(map[string]*MemoryItem) + return nil +} + +// StartAndGC start memory cache. it will check expiration in every clock time. +func (bc *MemoryCache) StartAndGC(config string) error { + var cf map[string]int + json.Unmarshal([]byte(config), &cf) + if _, ok := cf["interval"]; !ok { + cf = make(map[string]int) + cf["interval"] = DefaultEvery + } + dur := time.Duration(cf["interval"]) * time.Second + bc.Every = cf["interval"] + bc.dur = dur + go bc.vacuum() + return nil +} + +// check expiration. +func (bc *MemoryCache) vacuum() { + bc.RLock() + every := bc.Every + bc.RUnlock() + + if every < 1 { + return + } + for { + <-time.After(bc.dur) + if bc.items == nil { + return + } + if keys := bc.expiredKeys(); len(keys) != 0 { + bc.clearItems(keys) + } + } +} + +// expiredKeys returns key list which are expired. +func (bc *MemoryCache) expiredKeys() (keys []string) { + bc.RLock() + defer bc.RUnlock() + for key, itm := range bc.items { + if itm.isExpire() { + keys = append(keys, key) + } + } + return +} + +// clearItems removes all the items which key in keys. +func (bc *MemoryCache) clearItems(keys []string) { + bc.Lock() + defer bc.Unlock() + for _, key := range keys { + delete(bc.items, key) + } +} + +func init() { + Register("memory", NewMemoryCache) +} diff --git a/app/utils/cache/redis.go b/app/utils/cache/redis.go new file mode 100644 index 0000000..2199787 --- /dev/null +++ b/app/utils/cache/redis.go @@ -0,0 +1,409 @@ +package cache + +import ( + "encoding/json" + "errors" + "log" + "strings" + "time" + + redigo "github.com/gomodule/redigo/redis" +) + +// configuration +type Config struct { + Server string + Password string + MaxIdle int // Maximum number of idle connections in the pool. + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + KeyPrefix string // prefix to all keys; example is "dev environment name" + KeyDelimiter string // delimiter to be used while appending keys; example is ":" + KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?" +} + +var pool *redigo.Pool +var conf *Config + +func NewRedis(addr string) { + if addr == "" { + panic("\nredis connect string cannot be empty\n") + } + pool = &redigo.Pool{ + MaxIdle: redisMaxIdleConn, + IdleTimeout: redisIdleTTL, + MaxActive: redisMaxActive, + // MaxConnLifetime: redisDialTTL, + Wait: true, + Dial: func() (redigo.Conn, error) { + c, err := redigo.Dial("tcp", addr, + redigo.DialConnectTimeout(redisDialTTL), + redigo.DialReadTimeout(redisReadTTL), + redigo.DialWriteTimeout(redisWriteTTL), + ) + if err != nil { + log.Println("Redis Dial failed: ", err) + return nil, err + } + return c, err + }, + TestOnBorrow: func(c redigo.Conn, t time.Time) error { + _, err := c.Do("PING") + if err != nil { + log.Println("Unable to ping to redis server:", err) + } + return err + }, + } + conn := pool.Get() + defer conn.Close() + if conn.Err() != nil { + println("\nredis connect " + addr + " error: " + conn.Err().Error()) + } else { + println("\nredis connect " + addr + " success!\n") + } +} + +func Do(cmd string, args ...interface{}) (reply interface{}, err error) { + conn := pool.Get() + defer conn.Close() + return conn.Do(cmd, args...) +} + +func GetPool() *redigo.Pool { + return pool +} + +func ParseKey(key string, vars []string) (string, error) { + arr := strings.Split(key, conf.KeyPlaceholder) + actualKey := "" + if len(arr) != len(vars)+1 { + return "", errors.New("redis/connection.go: Insufficient arguments to parse key") + } else { + for index, val := range arr { + if index == 0 { + actualKey = arr[index] + } else { + actualKey += vars[index-1] + val + } + } + } + return getPrefixedKey(actualKey), nil +} + +func getPrefixedKey(key string) string { + return conf.KeyPrefix + conf.KeyDelimiter + key +} +func StripEnvKey(key string) string { + return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) +} +func SplitKey(key string) []string { + return strings.Split(key, conf.KeyDelimiter) +} +func Expire(key string, ttl int) (interface{}, error) { + return Do("EXPIRE", key, ttl) +} +func Persist(key string) (interface{}, error) { + return Do("PERSIST", key) +} + +func Del(key string) (interface{}, error) { + return Do("DEL", key) +} +func Set(key string, data interface{}) (interface{}, error) { + // set + return Do("SET", key, data) +} +func SetNX(key string, data interface{}) (interface{}, error) { + return Do("SETNX", key, data) +} +func SetEx(key string, data interface{}, ttl int) (interface{}, error) { + return Do("SETEX", key, ttl, data) +} + +func SetJson(key string, data interface{}, ttl int) bool { + c, err := json.Marshal(data) + if err != nil { + return false + } + if ttl < 1 { + _, err = Set(key, c) + } else { + _, err = SetEx(key, c, ttl) + } + if err != nil { + return false + } + return true +} + +func GetJson(key string, dst interface{}) error { + b, err := GetBytes(key) + if err != nil { + return err + } + if err = json.Unmarshal(b, dst); err != nil { + return err + } + return nil +} + +func Get(key string) (interface{}, error) { + // get + return Do("GET", key) +} +func GetTTL(key string) (time.Duration, error) { + ttl, err := redigo.Int64(Do("TTL", key)) + return time.Duration(ttl) * time.Second, err +} +func GetBytes(key string) ([]byte, error) { + return redigo.Bytes(Do("GET", key)) +} +func GetString(key string) (string, error) { + return redigo.String(Do("GET", key)) +} +func GetStringMap(key string) (map[string]string, error) { + return redigo.StringMap(Do("GET", key)) +} +func GetInt(key string) (int, error) { + return redigo.Int(Do("GET", key)) +} +func GetInt64(key string) (int64, error) { + return redigo.Int64(Do("GET", key)) +} +func GetStringLength(key string) (int, error) { + return redigo.Int(Do("STRLEN", key)) +} +func ZAdd(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, score, data) +} +func ZAddNX(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, "NX", score, data) +} +func ZRem(key string, data interface{}) (interface{}, error) { + return Do("ZREM", key, data) +} +func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { + if withScores { + return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) + } + return redigo.Values(Do("ZRANGE", key, start, end)) +} +func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) { + return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end)) +} +func ZCard(setName string) (int64, error) { + return redigo.Int64(Do("ZCARD", setName)) +} +func ZScan(setName string) (int64, error) { + return redigo.Int64(Do("ZCARD", setName)) +} +func SAdd(setName string, data interface{}) (interface{}, error) { + return Do("SADD", setName, data) +} +func SCard(setName string) (int64, error) { + return redigo.Int64(Do("SCARD", setName)) +} +func SIsMember(setName string, data interface{}) (bool, error) { + return redigo.Bool(Do("SISMEMBER", setName, data)) +} +func SMembers(setName string) ([]string, error) { + return redigo.Strings(Do("SMEMBERS", setName)) +} +func SRem(setName string, data interface{}) (interface{}, error) { + return Do("SREM", setName, data) +} +func HSet(key string, HKey string, data interface{}) (interface{}, error) { + return Do("HSET", key, HKey, data) +} + +func HGet(key string, HKey string) (interface{}, error) { + return Do("HGET", key, HKey) +} + +func HMGet(key string, hashKeys ...string) ([]interface{}, error) { + ret, err := Do("HMGET", key, hashKeys) + if err != nil { + return nil, err + } + reta, ok := ret.([]interface{}) + if !ok { + return nil, errors.New("result not an array") + } + return reta, nil +} + +func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { + if len(hashKeys) == 0 || len(hashKeys) != len(vals) { + var ret interface{} + return ret, errors.New("bad length") + } + input := []interface{}{key} + for i, v := range hashKeys { + input = append(input, v, vals[i]) + } + return Do("HMSET", input...) +} + +func HGetString(key string, HKey string) (string, error) { + return redigo.String(Do("HGET", key, HKey)) +} +func HGetFloat(key string, HKey string) (float64, error) { + f, err := redigo.Float64(Do("HGET", key, HKey)) + return f, err +} +func HGetInt(key string, HKey string) (int, error) { + return redigo.Int(Do("HGET", key, HKey)) +} +func HGetInt64(key string, HKey string) (int64, error) { + return redigo.Int64(Do("HGET", key, HKey)) +} +func HGetBool(key string, HKey string) (bool, error) { + return redigo.Bool(Do("HGET", key, HKey)) +} +func HDel(key string, HKey string) (interface{}, error) { + return Do("HDEL", key, HKey) +} + +func HGetAll(key string) (map[string]interface{}, error) { + vals, err := redigo.Values(Do("HGETALL", key)) + if err != nil { + return nil, err + } + num := len(vals) / 2 + result := make(map[string]interface{}, num) + for i := 0; i < num; i++ { + key, _ := redigo.String(vals[2*i], nil) + result[key] = vals[2*i+1] + } + return result, nil +} + +func FlushAll() bool { + res, _ := redigo.String(Do("FLUSHALL")) + if res == "" { + return false + } + return true +} + +// NOTE: Use this in production environment with extreme care. +// Read more here:https://redigo.io/commands/keys +func Keys(pattern string) ([]string, error) { + return redigo.Strings(Do("KEYS", pattern)) +} + +func HKeys(key string) ([]string, error) { + return redigo.Strings(Do("HKEYS", key)) +} + +func Exists(key string) bool { + count, err := redigo.Int(Do("EXISTS", key)) + if count == 0 || err != nil { + return false + } + return true +} + +func Incr(key string) (int64, error) { + return redigo.Int64(Do("INCR", key)) +} + +func Decr(key string) (int64, error) { + return redigo.Int64(Do("DECR", key)) +} + +func IncrBy(key string, incBy int64) (int64, error) { + return redigo.Int64(Do("INCRBY", key, incBy)) +} + +func DecrBy(key string, decrBy int64) (int64, error) { + return redigo.Int64(Do("DECRBY", key)) +} + +func IncrByFloat(key string, incBy float64) (float64, error) { + return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) +} + +func DecrByFloat(key string, decrBy float64) (float64, error) { + return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) +} + +// use for message queue +func LPush(key string, data interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} + +func LPop(key string) (interface{}, error) { + return Do("LPOP", key) +} + +func LPopString(key string) (string, error) { + return redigo.String(Do("LPOP", key)) +} +func LPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("LPOP", key)) + return f, err +} +func LPopInt(key string) (int, error) { + return redigo.Int(Do("LPOP", key)) +} +func LPopInt64(key string) (int64, error) { + return redigo.Int64(Do("LPOP", key)) +} + +func RPush(key string, data interface{}) (interface{}, error) { + // set + return Do("RPUSH", key, data) +} + +func RPop(key string) (interface{}, error) { + return Do("RPOP", key) +} + +func RPopString(key string) (string, error) { + return redigo.String(Do("RPOP", key)) +} +func RPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("RPOP", key)) + return f, err +} +func RPopInt(key string) (int, error) { + return redigo.Int(Do("RPOP", key)) +} +func RPopInt64(key string) (int64, error) { + return redigo.Int64(Do("RPOP", key)) +} + +func Scan(cursor int64, pattern string, count int64) (int64, []string, error) { + var items []string + var newCursor int64 + + values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) + if err != nil { + return 0, nil, err + } + values, err = redigo.Scan(values, &newCursor, &items) + if err != nil { + return 0, nil, err + } + return newCursor, items, nil +} + + +func LPushMax(key string, data ...interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} \ No newline at end of file diff --git a/app/utils/cache/redis_cluster.go b/app/utils/cache/redis_cluster.go new file mode 100644 index 0000000..901f30c --- /dev/null +++ b/app/utils/cache/redis_cluster.go @@ -0,0 +1,622 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/go-redis/redis" +) + +var pools *redis.ClusterClient + +func NewRedisCluster(addrs []string) error { + opt := &redis.ClusterOptions{ + Addrs: addrs, + PoolSize: redisPoolSize, + PoolTimeout: redisPoolTTL, + IdleTimeout: redisIdleTTL, + DialTimeout: redisDialTTL, + ReadTimeout: redisReadTTL, + WriteTimeout: redisWriteTTL, + } + pools = redis.NewClusterClient(opt) + if err := pools.Ping().Err(); err != nil { + return err + } + return nil +} + +func RCGet(key string) (interface{}, error) { + res, err := pools.Get(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSet(key string, value interface{}) error { + err := pools.Set(key, value, 0).Err() + return convertError(err) +} +func RCGetSet(key string, value interface{}) (interface{}, error) { + res, err := pools.GetSet(key, value).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSetNx(key string, value interface{}) (int64, error) { + res, err := pools.SetNX(key, value, 0).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCSetEx(key string, value interface{}, timeout int64) error { + _, err := pools.Set(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + return nil +} + +// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 +func RCSetNxEx(key string, value interface{}, timeout int64) error { + res, err := pools.SetNX(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + if res { + return nil + } + return ErrNil +} +func RCMGet(keys ...string) ([]interface{}, error) { + res, err := pools.MGet(keys...).Result() + return res, convertError(err) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func RCMSet(kvs map[string]interface{}) error { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return err + } + pairs = append(pairs, k, val) + } + return convertError(pools.MSet(pairs).Err()) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func RCMSetNX(kvs map[string]interface{}) (bool, error) { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return false, err + } + pairs = append(pairs, k, val) + } + res, err := pools.MSetNX(pairs).Result() + return res, convertError(err) +} +func RCExpireAt(key string, timestamp int64) (int64, error) { + res, err := pools.ExpireAt(key, time.Unix(timestamp, 0)).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCDel(keys ...string) (int64, error) { + args := make([]interface{}, 0, len(keys)) + for _, key := range keys { + args = append(args, key) + } + res, err := pools.Del(keys...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCIncr(key string) (int64, error) { + res, err := pools.Incr(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCIncrBy(key string, delta int64) (int64, error) { + res, err := pools.IncrBy(key, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCExpire(key string, duration int64) (int64, error) { + res, err := pools.Expire(key, time.Duration(duration)*time.Second).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCExists(key string) (bool, error) { + res, err := pools.Exists(key).Result() + if err != nil { + return false, convertError(err) + } + if res > 0 { + return true, nil + } + return false, nil +} +func RCHGet(key string, field string) (interface{}, error) { + res, err := pools.HGet(key, field).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCHLen(key string) (int64, error) { + res, err := pools.HLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCHSet(key string, field string, val interface{}) error { + value, err := String(val, nil) + if err != nil && err != ErrNil { + return err + } + _, err = pools.HSet(key, field, value).Result() + if err != nil { + return convertError(err) + } + return nil +} +func RCHDel(key string, fields ...string) (int64, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + res, err := pools.HDel(key, fields...).Result() + if err != nil { + return 0, convertError(err) + } + return res, nil +} + +func RCHMGet(key string, fields ...string) (interface{}, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + if len(fields) == 0 { + return nil, ErrNil + } + res, err := pools.HMGet(key, fields...).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCHMSet(key string, kvs ...interface{}) error { + if len(kvs) == 0 { + return nil + } + if len(kvs)%2 != 0 { + return ErrWrongArgsNum + } + var err error + v := map[string]interface{}{} // todo change + v["field"], err = String(kvs[0], nil) + if err != nil && err != ErrNil { + return err + } + v["value"], err = String(kvs[1], nil) + if err != nil && err != ErrNil { + return err + } + pairs := make([]string, 0, len(kvs)-2) + if len(kvs) > 2 { + for _, kv := range kvs[2:] { + kvString, err := String(kv, nil) + if err != nil && err != ErrNil { + return err + } + pairs = append(pairs, kvString) + } + } + v["paris"] = pairs + _, err = pools.HMSet(key, v).Result() + if err != nil { + return convertError(err) + } + return nil +} + +func RCHKeys(key string) ([]string, error) { + res, err := pools.HKeys(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCHVals(key string) ([]interface{}, error) { + res, err := pools.HVals(key).Result() + if err != nil { + return nil, convertError(err) + } + rs := make([]interface{}, 0, len(res)) + for _, res := range res { + rs = append(rs, res) + } + return rs, nil +} +func RCHGetAll(key string) (map[string]string, error) { + vals, err := pools.HGetAll(key).Result() + if err != nil { + return nil, convertError(err) + } + return vals, nil +} +func RCHIncrBy(key, field string, delta int64) (int64, error) { + res, err := pools.HIncrBy(key, field, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCZAdd(key string, kvs ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(kvs)+1) + args = append(args, key) + args = append(args, kvs...) + if len(kvs) == 0 { + return 0, nil + } + if len(kvs)%2 != 0 { + return 0, ErrWrongArgsNum + } + zs := make([]redis.Z, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + idx := i / 2 + score, err := Float64(kvs[i], nil) + if err != nil && err != ErrNil { + return 0, err + } + zs[idx].Score = score + zs[idx].Member = kvs[i+1] + } + res, err := pools.ZAdd(key, zs...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCZRem(key string, members ...string) (int64, error) { + args := make([]interface{}, 0, len(members)) + args = append(args, key) + for _, member := range members { + args = append(args, member) + } + res, err := pools.ZRem(key, members).Result() + if err != nil { + return res, convertError(err) + } + return res, err +} + +func RCZRange(key string, min, max int64, withScores bool) (interface{}, error) { + res := make([]interface{}, 0) + if withScores { + zs, err := pools.ZRangeWithScores(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, z := range zs { + res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) + } + } else { + ms, err := pools.ZRange(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, m := range ms { + res = append(res, m) + } + } + return res, nil +} +func RCZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { + opt := new(redis.ZRangeBy) + opt.Min = strconv.FormatInt(int64(min), 10) + opt.Max = strconv.FormatInt(int64(max), 10) + opt.Count = -1 + opt.Offset = 0 + vals, err := pools.ZRangeByScoreWithScores(key, *opt).Result() + if err != nil { + return nil, convertError(err) + } + res := make(map[string]int64, len(vals)) + for _, val := range vals { + key, err := String(val.Member, nil) + if err != nil && err != ErrNil { + return nil, err + } + res[key] = int64(val.Score) + } + return res, nil +} +func RCLRange(key string, start, stop int64) (interface{}, error) { + res, err := pools.LRange(key, start, stop).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCLSet(key string, index int, value interface{}) error { + err := pools.LSet(key, int64(index), value).Err() + return convertError(err) +} +func RCLLen(key string) (int64, error) { + res, err := pools.LLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCLRem(key string, count int, value interface{}) (int, error) { + val, _ := value.(string) + res, err := pools.LRem(key, int64(count), val).Result() + if err != nil { + return int(res), convertError(err) + } + return int(res), nil +} +func RCTTl(key string) (int64, error) { + duration, err := pools.TTL(key).Result() + if err != nil { + return int64(duration.Seconds()), convertError(err) + } + return int64(duration.Seconds()), nil +} +func RCLPop(key string) (interface{}, error) { + res, err := pools.LPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCRPop(key string) (interface{}, error) { + res, err := pools.RPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCBLPop(key string, timeout int) (interface{}, error) { + res, err := pools.BLPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, err + } + return res[1], nil +} +func RCBRPop(key string, timeout int) (interface{}, error) { + res, err := pools.BRPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, convertError(err) + } + return res[1], nil +} +func RCLPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + return err + } + vals = append(vals, val) + } + _, err := pools.LPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} +func RCRPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + if err == ErrNil { + continue + } + return err + } + if val == "" { + continue + } + vals = append(vals, val) + } + _, err := pools.RPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func RCBRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { + res, err := pools.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func RCRPopLPush(srcKey string, destKey string) (interface{}, error) { + res, err := pools.RPopLPush(srcKey, destKey).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCSAdd(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := pools.SAdd(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSPop(key string) ([]byte, error) { + res, err := pools.SPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSIsMember(key string, member interface{}) (bool, error) { + m, _ := member.(string) + res, err := pools.SIsMember(key, m).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSRem(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := pools.SRem(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSMembers(key string) ([]string, error) { + res, err := pools.SMembers(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCScriptLoad(luaScript string) (interface{}, error) { + res, err := pools.ScriptLoad(luaScript).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCEvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, sha1, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := pools.EvalSha(sha1, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCEval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, luaScript, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := pools.Eval(luaScript, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCGetBit(key string, offset int64) (int64, error) { + res, err := pools.GetBit(key, offset).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSetBit(key string, offset uint32, value int) (int, error) { + res, err := pools.SetBit(key, int64(offset), value).Result() + return int(res), convertError(err) +} +func RCGetClient() *redis.ClusterClient { + return pools +} +func convertError(err error) error { + if err == redis.Nil { + // 为了兼容redis 2.x,这里不返回 ErrNil,ErrNil在调用redis_cluster_reply函数时才返回 + return nil + } + return err +} diff --git a/app/utils/cache/redis_pool.go b/app/utils/cache/redis_pool.go new file mode 100644 index 0000000..ca38b3f --- /dev/null +++ b/app/utils/cache/redis_pool.go @@ -0,0 +1,324 @@ +package cache + +import ( + "errors" + "log" + "strings" + "time" + + redigo "github.com/gomodule/redigo/redis" +) + +type RedisPool struct { + *redigo.Pool +} + +func NewRedisPool(cfg *Config) *RedisPool { + return &RedisPool{&redigo.Pool{ + MaxIdle: cfg.MaxIdle, + IdleTimeout: cfg.IdleTimeout, + MaxActive: cfg.MaxActive, + Wait: cfg.Wait, + Dial: func() (redigo.Conn, error) { + c, err := redigo.Dial("tcp", cfg.Server) + if err != nil { + log.Println("Redis Dial failed: ", err) + return nil, err + } + if cfg.Password != "" { + if _, err := c.Do("AUTH", cfg.Password); err != nil { + c.Close() + log.Println("Redis AUTH failed: ", err) + return nil, err + } + } + return c, err + }, + TestOnBorrow: func(c redigo.Conn, t time.Time) error { + _, err := c.Do("PING") + if err != nil { + log.Println("Unable to ping to redis server:", err) + } + return err + }, + }} +} + +func (p *RedisPool) Do(cmd string, args ...interface{}) (reply interface{}, err error) { + conn := pool.Get() + defer conn.Close() + return conn.Do(cmd, args...) +} + +func (p *RedisPool) GetPool() *redigo.Pool { + return pool +} + +func (p *RedisPool) ParseKey(key string, vars []string) (string, error) { + arr := strings.Split(key, conf.KeyPlaceholder) + actualKey := "" + if len(arr) != len(vars)+1 { + return "", errors.New("redis/connection.go: Insufficient arguments to parse key") + } else { + for index, val := range arr { + if index == 0 { + actualKey = arr[index] + } else { + actualKey += vars[index-1] + val + } + } + } + return getPrefixedKey(actualKey), nil +} + +func (p *RedisPool) getPrefixedKey(key string) string { + return conf.KeyPrefix + conf.KeyDelimiter + key +} +func (p *RedisPool) StripEnvKey(key string) string { + return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) +} +func (p *RedisPool) SplitKey(key string) []string { + return strings.Split(key, conf.KeyDelimiter) +} +func (p *RedisPool) Expire(key string, ttl int) (interface{}, error) { + return Do("EXPIRE", key, ttl) +} +func (p *RedisPool) Persist(key string) (interface{}, error) { + return Do("PERSIST", key) +} + +func (p *RedisPool) Del(key string) (interface{}, error) { + return Do("DEL", key) +} +func (p *RedisPool) Set(key string, data interface{}) (interface{}, error) { + // set + return Do("SET", key, data) +} +func (p *RedisPool) SetNX(key string, data interface{}) (interface{}, error) { + return Do("SETNX", key, data) +} +func (p *RedisPool) SetEx(key string, data interface{}, ttl int) (interface{}, error) { + return Do("SETEX", key, ttl, data) +} +func (p *RedisPool) Get(key string) (interface{}, error) { + // get + return Do("GET", key) +} +func (p *RedisPool) GetStringMap(key string) (map[string]string, error) { + // get + return redigo.StringMap(Do("GET", key)) +} + +func (p *RedisPool) GetTTL(key string) (time.Duration, error) { + ttl, err := redigo.Int64(Do("TTL", key)) + return time.Duration(ttl) * time.Second, err +} +func (p *RedisPool) GetBytes(key string) ([]byte, error) { + return redigo.Bytes(Do("GET", key)) +} +func (p *RedisPool) GetString(key string) (string, error) { + return redigo.String(Do("GET", key)) +} +func (p *RedisPool) GetInt(key string) (int, error) { + return redigo.Int(Do("GET", key)) +} +func (p *RedisPool) GetStringLength(key string) (int, error) { + return redigo.Int(Do("STRLEN", key)) +} +func (p *RedisPool) ZAdd(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, score, data) +} +func (p *RedisPool) ZRem(key string, data interface{}) (interface{}, error) { + return Do("ZREM", key, data) +} +func (p *RedisPool) ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { + if withScores { + return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) + } + return redigo.Values(Do("ZRANGE", key, start, end)) +} +func (p *RedisPool) SAdd(setName string, data interface{}) (interface{}, error) { + return Do("SADD", setName, data) +} +func (p *RedisPool) SCard(setName string) (int64, error) { + return redigo.Int64(Do("SCARD", setName)) +} +func (p *RedisPool) SIsMember(setName string, data interface{}) (bool, error) { + return redigo.Bool(Do("SISMEMBER", setName, data)) +} +func (p *RedisPool) SMembers(setName string) ([]string, error) { + return redigo.Strings(Do("SMEMBERS", setName)) +} +func (p *RedisPool) SRem(setName string, data interface{}) (interface{}, error) { + return Do("SREM", setName, data) +} +func (p *RedisPool) HSet(key string, HKey string, data interface{}) (interface{}, error) { + return Do("HSET", key, HKey, data) +} + +func (p *RedisPool) HGet(key string, HKey string) (interface{}, error) { + return Do("HGET", key, HKey) +} + +func (p *RedisPool) HMGet(key string, hashKeys ...string) ([]interface{}, error) { + ret, err := Do("HMGET", key, hashKeys) + if err != nil { + return nil, err + } + reta, ok := ret.([]interface{}) + if !ok { + return nil, errors.New("result not an array") + } + return reta, nil +} + +func (p *RedisPool) HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { + if len(hashKeys) == 0 || len(hashKeys) != len(vals) { + var ret interface{} + return ret, errors.New("bad length") + } + input := []interface{}{key} + for i, v := range hashKeys { + input = append(input, v, vals[i]) + } + return Do("HMSET", input...) +} + +func (p *RedisPool) HGetString(key string, HKey string) (string, error) { + return redigo.String(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetFloat(key string, HKey string) (float64, error) { + f, err := redigo.Float64(Do("HGET", key, HKey)) + return float64(f), err +} +func (p *RedisPool) HGetInt(key string, HKey string) (int, error) { + return redigo.Int(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetInt64(key string, HKey string) (int64, error) { + return redigo.Int64(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetBool(key string, HKey string) (bool, error) { + return redigo.Bool(Do("HGET", key, HKey)) +} +func (p *RedisPool) HDel(key string, HKey string) (interface{}, error) { + return Do("HDEL", key, HKey) +} +func (p *RedisPool) HGetAll(key string) (map[string]interface{}, error) { + vals, err := redigo.Values(Do("HGETALL", key)) + if err != nil { + return nil, err + } + num := len(vals) / 2 + result := make(map[string]interface{}, num) + for i := 0; i < num; i++ { + key, _ := redigo.String(vals[2*i], nil) + result[key] = vals[2*i+1] + } + return result, nil +} + +// NOTE: Use this in production environment with extreme care. +// Read more here:https://redigo.io/commands/keys +func (p *RedisPool) Keys(pattern string) ([]string, error) { + return redigo.Strings(Do("KEYS", pattern)) +} + +func (p *RedisPool) HKeys(key string) ([]string, error) { + return redigo.Strings(Do("HKEYS", key)) +} + +func (p *RedisPool) Exists(key string) (bool, error) { + count, err := redigo.Int(Do("EXISTS", key)) + if count == 0 { + return false, err + } else { + return true, err + } +} + +func (p *RedisPool) Incr(key string) (int64, error) { + return redigo.Int64(Do("INCR", key)) +} + +func (p *RedisPool) Decr(key string) (int64, error) { + return redigo.Int64(Do("DECR", key)) +} + +func (p *RedisPool) IncrBy(key string, incBy int64) (int64, error) { + return redigo.Int64(Do("INCRBY", key, incBy)) +} + +func (p *RedisPool) DecrBy(key string, decrBy int64) (int64, error) { + return redigo.Int64(Do("DECRBY", key)) +} + +func (p *RedisPool) IncrByFloat(key string, incBy float64) (float64, error) { + return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) +} + +func (p *RedisPool) DecrByFloat(key string, decrBy float64) (float64, error) { + return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) +} + +// use for message queue +func (p *RedisPool) LPush(key string, data interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} + +func (p *RedisPool) LPop(key string) (interface{}, error) { + return Do("LPOP", key) +} + +func (p *RedisPool) LPopString(key string) (string, error) { + return redigo.String(Do("LPOP", key)) +} +func (p *RedisPool) LPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("LPOP", key)) + return float64(f), err +} +func (p *RedisPool) LPopInt(key string) (int, error) { + return redigo.Int(Do("LPOP", key)) +} +func (p *RedisPool) LPopInt64(key string) (int64, error) { + return redigo.Int64(Do("LPOP", key)) +} + +func (p *RedisPool) RPush(key string, data interface{}) (interface{}, error) { + // set + return Do("RPUSH", key, data) +} + +func (p *RedisPool) RPop(key string) (interface{}, error) { + return Do("RPOP", key) +} + +func (p *RedisPool) RPopString(key string) (string, error) { + return redigo.String(Do("RPOP", key)) +} +func (p *RedisPool) RPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("RPOP", key)) + return float64(f), err +} +func (p *RedisPool) RPopInt(key string) (int, error) { + return redigo.Int(Do("RPOP", key)) +} +func (p *RedisPool) RPopInt64(key string) (int64, error) { + return redigo.Int64(Do("RPOP", key)) +} + +func (p *RedisPool) Scan(cursor int64, pattern string, count int64) (int64, []string, error) { + var items []string + var newCursor int64 + + values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) + if err != nil { + return 0, nil, err + } + values, err = redigo.Scan(values, &newCursor, &items) + if err != nil { + return 0, nil, err + } + + return newCursor, items, nil +} diff --git a/app/utils/cache/redis_pool_cluster.go b/app/utils/cache/redis_pool_cluster.go new file mode 100644 index 0000000..cd1911b --- /dev/null +++ b/app/utils/cache/redis_pool_cluster.go @@ -0,0 +1,617 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/go-redis/redis" +) + +type RedisClusterPool struct { + client *redis.ClusterClient +} + +func NewRedisClusterPool(addrs []string) (*RedisClusterPool, error) { + opt := &redis.ClusterOptions{ + Addrs: addrs, + PoolSize: 512, + PoolTimeout: 10 * time.Second, + IdleTimeout: 10 * time.Second, + DialTimeout: 10 * time.Second, + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + } + c := redis.NewClusterClient(opt) + if err := c.Ping().Err(); err != nil { + return nil, err + } + return &RedisClusterPool{client: c}, nil +} + +func (p *RedisClusterPool) Get(key string) (interface{}, error) { + res, err := p.client.Get(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) Set(key string, value interface{}) error { + err := p.client.Set(key, value, 0).Err() + return convertError(err) +} +func (p *RedisClusterPool) GetSet(key string, value interface{}) (interface{}, error) { + res, err := p.client.GetSet(key, value).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) SetNx(key string, value interface{}) (int64, error) { + res, err := p.client.SetNX(key, value, 0).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) SetEx(key string, value interface{}, timeout int64) error { + _, err := p.client.Set(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + return nil +} + +// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 +func (p *RedisClusterPool) SetNxEx(key string, value interface{}, timeout int64) error { + res, err := p.client.SetNX(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + if res { + return nil + } + return ErrNil +} +func (p *RedisClusterPool) MGet(keys ...string) ([]interface{}, error) { + res, err := p.client.MGet(keys...).Result() + return res, convertError(err) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func (p *RedisClusterPool) MSet(kvs map[string]interface{}) error { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return err + } + pairs = append(pairs, k, val) + } + return convertError(p.client.MSet(pairs).Err()) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func (p *RedisClusterPool) MSetNX(kvs map[string]interface{}) (bool, error) { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return false, err + } + pairs = append(pairs, k, val) + } + res, err := p.client.MSetNX(pairs).Result() + return res, convertError(err) +} +func (p *RedisClusterPool) ExpireAt(key string, timestamp int64) (int64, error) { + res, err := p.client.ExpireAt(key, time.Unix(timestamp, 0)).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) Del(keys ...string) (int64, error) { + args := make([]interface{}, 0, len(keys)) + for _, key := range keys { + args = append(args, key) + } + res, err := p.client.Del(keys...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Incr(key string) (int64, error) { + res, err := p.client.Incr(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) IncrBy(key string, delta int64) (int64, error) { + res, err := p.client.IncrBy(key, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Expire(key string, duration int64) (int64, error) { + res, err := p.client.Expire(key, time.Duration(duration)*time.Second).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) Exists(key string) (bool, error) { // todo (bool, error) + res, err := p.client.Exists(key).Result() + if err != nil { + return false, convertError(err) + } + if res > 0 { + return true, nil + } + return false, nil +} +func (p *RedisClusterPool) HGet(key string, field string) (interface{}, error) { + res, err := p.client.HGet(key, field).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) HLen(key string) (int64, error) { + res, err := p.client.HLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HSet(key string, field string, val interface{}) error { + value, err := String(val, nil) + if err != nil && err != ErrNil { + return err + } + _, err = p.client.HSet(key, field, value).Result() + if err != nil { + return convertError(err) + } + return nil +} +func (p *RedisClusterPool) HDel(key string, fields ...string) (int64, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + res, err := p.client.HDel(key, fields...).Result() + if err != nil { + return 0, convertError(err) + } + return res, nil +} + +func (p *RedisClusterPool) HMGet(key string, fields ...string) (interface{}, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + if len(fields) == 0 { + return nil, ErrNil + } + res, err := p.client.HMGet(key, fields...).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HMSet(key string, kvs ...interface{}) error { + if len(kvs) == 0 { + return nil + } + if len(kvs)%2 != 0 { + return ErrWrongArgsNum + } + var err error + v := map[string]interface{}{} // todo change + v["field"], err = String(kvs[0], nil) + if err != nil && err != ErrNil { + return err + } + v["value"], err = String(kvs[1], nil) + if err != nil && err != ErrNil { + return err + } + pairs := make([]string, 0, len(kvs)-2) + if len(kvs) > 2 { + for _, kv := range kvs[2:] { + kvString, err := String(kv, nil) + if err != nil && err != ErrNil { + return err + } + pairs = append(pairs, kvString) + } + } + v["paris"] = pairs + _, err = p.client.HMSet(key, v).Result() + if err != nil { + return convertError(err) + } + return nil +} + +func (p *RedisClusterPool) HKeys(key string) ([]string, error) { + res, err := p.client.HKeys(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HVals(key string) ([]interface{}, error) { + res, err := p.client.HVals(key).Result() + if err != nil { + return nil, convertError(err) + } + rs := make([]interface{}, 0, len(res)) + for _, res := range res { + rs = append(rs, res) + } + return rs, nil +} +func (p *RedisClusterPool) HGetAll(key string) (map[string]string, error) { + vals, err := p.client.HGetAll(key).Result() + if err != nil { + return nil, convertError(err) + } + return vals, nil +} +func (p *RedisClusterPool) HIncrBy(key, field string, delta int64) (int64, error) { + res, err := p.client.HIncrBy(key, field, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ZAdd(key string, kvs ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(kvs)+1) + args = append(args, key) + args = append(args, kvs...) + if len(kvs) == 0 { + return 0, nil + } + if len(kvs)%2 != 0 { + return 0, ErrWrongArgsNum + } + zs := make([]redis.Z, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + idx := i / 2 + score, err := Float64(kvs[i], nil) + if err != nil && err != ErrNil { + return 0, err + } + zs[idx].Score = score + zs[idx].Member = kvs[i+1] + } + res, err := p.client.ZAdd(key, zs...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ZRem(key string, members ...string) (int64, error) { + args := make([]interface{}, 0, len(members)) + args = append(args, key) + for _, member := range members { + args = append(args, member) + } + res, err := p.client.ZRem(key, members).Result() + if err != nil { + return res, convertError(err) + } + return res, err +} + +func (p *RedisClusterPool) ZRange(key string, min, max int64, withScores bool) (interface{}, error) { + res := make([]interface{}, 0) + if withScores { + zs, err := p.client.ZRangeWithScores(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, z := range zs { + res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) + } + } else { + ms, err := p.client.ZRange(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, m := range ms { + res = append(res, m) + } + } + return res, nil +} +func (p *RedisClusterPool) ZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { + opt := new(redis.ZRangeBy) + opt.Min = strconv.FormatInt(int64(min), 10) + opt.Max = strconv.FormatInt(int64(max), 10) + opt.Count = -1 + opt.Offset = 0 + vals, err := p.client.ZRangeByScoreWithScores(key, *opt).Result() + if err != nil { + return nil, convertError(err) + } + res := make(map[string]int64, len(vals)) + for _, val := range vals { + key, err := String(val.Member, nil) + if err != nil && err != ErrNil { + return nil, err + } + res[key] = int64(val.Score) + } + return res, nil +} +func (p *RedisClusterPool) LRange(key string, start, stop int64) (interface{}, error) { + res, err := p.client.LRange(key, start, stop).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) LSet(key string, index int, value interface{}) error { + err := p.client.LSet(key, int64(index), value).Err() + return convertError(err) +} +func (p *RedisClusterPool) LLen(key string) (int64, error) { + res, err := p.client.LLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) LRem(key string, count int, value interface{}) (int, error) { + val, _ := value.(string) + res, err := p.client.LRem(key, int64(count), val).Result() + if err != nil { + return int(res), convertError(err) + } + return int(res), nil +} +func (p *RedisClusterPool) TTl(key string) (int64, error) { + duration, err := p.client.TTL(key).Result() + if err != nil { + return int64(duration.Seconds()), convertError(err) + } + return int64(duration.Seconds()), nil +} +func (p *RedisClusterPool) LPop(key string) (interface{}, error) { + res, err := p.client.LPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) RPop(key string) (interface{}, error) { + res, err := p.client.RPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) BLPop(key string, timeout int) (interface{}, error) { + res, err := p.client.BLPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, err + } + return res[1], nil +} +func (p *RedisClusterPool) BRPop(key string, timeout int) (interface{}, error) { + res, err := p.client.BRPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, convertError(err) + } + return res[1], nil +} +func (p *RedisClusterPool) LPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + return err + } + vals = append(vals, val) + } + _, err := p.client.LPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} +func (p *RedisClusterPool) RPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + if err == ErrNil { + continue + } + return err + } + if val == "" { + continue + } + vals = append(vals, val) + } + _, err := p.client.RPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func (p *RedisClusterPool) BRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { + res, err := p.client.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func (p *RedisClusterPool) RPopLPush(srcKey string, destKey string) (interface{}, error) { + res, err := p.client.RPopLPush(srcKey, destKey).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SAdd(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := p.client.SAdd(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SPop(key string) ([]byte, error) { + res, err := p.client.SPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) SIsMember(key string, member interface{}) (bool, error) { + m, _ := member.(string) + res, err := p.client.SIsMember(key, m).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SRem(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := p.client.SRem(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SMembers(key string) ([]string, error) { + res, err := p.client.SMembers(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ScriptLoad(luaScript string) (interface{}, error) { + res, err := p.client.ScriptLoad(luaScript).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) EvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, sha1, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := p.client.EvalSha(sha1, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Eval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, luaScript, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := p.client.Eval(luaScript, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) GetBit(key string, offset int64) (int64, error) { + res, err := p.client.GetBit(key, offset).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SetBit(key string, offset uint32, value int) (int, error) { + res, err := p.client.SetBit(key, int64(offset), value).Result() + return int(res), convertError(err) +} +func (p *RedisClusterPool) GetClient() *redis.ClusterClient { + return pools +} diff --git a/app/utils/convert.go b/app/utils/convert.go new file mode 100644 index 0000000..a638d37 --- /dev/null +++ b/app/utils/convert.go @@ -0,0 +1,322 @@ +package utils + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "math" + "strconv" + "strings" +) + +func ToString(raw interface{}, e error) (res string) { + if e != nil { + return "" + } + return AnyToString(raw) +} + +func ToInt64(raw interface{}, e error) int64 { + if e != nil { + return 0 + } + return AnyToInt64(raw) +} + +func AnyToBool(raw interface{}) bool { + switch i := raw.(type) { + case float32, float64, int, int64, uint, uint8, uint16, uint32, uint64, int8, int16, int32: + return i != 0 + case []byte: + return i != nil + case string: + if i == "false" { + return false + } + return i != "" + case error: + return false + case nil: + return true + } + val := fmt.Sprint(raw) + val = strings.TrimLeft(val, "&") + if strings.TrimLeft(val, "{}") == "" { + return false + } + if strings.TrimLeft(val, "[]") == "" { + return false + } + // ptr type + b, err := json.Marshal(raw) + if err != nil { + return false + } + if strings.TrimLeft(string(b), "\"\"") == "" { + return false + } + if strings.TrimLeft(string(b), "{}") == "" { + return false + } + return true +} + +func AnyToInt64(raw interface{}) int64 { + switch i := raw.(type) { + case string: + res, _ := strconv.ParseInt(i, 10, 64) + return res + case []byte: + return BytesToInt64(i) + case int: + return int64(i) + case int64: + return i + case uint: + return int64(i) + case uint8: + return int64(i) + case uint16: + return int64(i) + case uint32: + return int64(i) + case uint64: + return int64(i) + case int8: + return int64(i) + case int16: + return int64(i) + case int32: + return int64(i) + case float32: + return int64(i) + case float64: + return int64(i) + case error: + return 0 + case bool: + if i { + return 1 + } + return 0 + } + return 0 +} + +func AnyToString(raw interface{}) string { + switch i := raw.(type) { + case []byte: + return string(i) + case int: + return strconv.FormatInt(int64(i), 10) + case int64: + return strconv.FormatInt(i, 10) + case float32: + return Float64ToStr(float64(i)) + case float64: + return Float64ToStr(i) + case uint: + return strconv.FormatInt(int64(i), 10) + case uint8: + return strconv.FormatInt(int64(i), 10) + case uint16: + return strconv.FormatInt(int64(i), 10) + case uint32: + return strconv.FormatInt(int64(i), 10) + case uint64: + return strconv.FormatInt(int64(i), 10) + case int8: + return strconv.FormatInt(int64(i), 10) + case int16: + return strconv.FormatInt(int64(i), 10) + case int32: + return strconv.FormatInt(int64(i), 10) + case string: + return i + case error: + return i.Error() + case bool: + return strconv.FormatBool(i) + } + return fmt.Sprintf("%#v", raw) +} + +func AnyToFloat64(raw interface{}) float64 { + switch i := raw.(type) { + case []byte: + f, _ := strconv.ParseFloat(string(i), 64) + return f + case int: + return float64(i) + case int64: + return float64(i) + case float32: + return float64(i) + case float64: + return i + case uint: + return float64(i) + case uint8: + return float64(i) + case uint16: + return float64(i) + case uint32: + return float64(i) + case uint64: + return float64(i) + case int8: + return float64(i) + case int16: + return float64(i) + case int32: + return float64(i) + case string: + f, _ := strconv.ParseFloat(i, 64) + return f + case bool: + if i { + return 1 + } + } + return 0 +} + +func ToByte(raw interface{}, e error) []byte { + if e != nil { + return []byte{} + } + switch i := raw.(type) { + case string: + return []byte(i) + case int: + return Int64ToBytes(int64(i)) + case int64: + return Int64ToBytes(i) + case float32: + return Float32ToByte(i) + case float64: + return Float64ToByte(i) + case uint: + return Int64ToBytes(int64(i)) + case uint8: + return Int64ToBytes(int64(i)) + case uint16: + return Int64ToBytes(int64(i)) + case uint32: + return Int64ToBytes(int64(i)) + case uint64: + return Int64ToBytes(int64(i)) + case int8: + return Int64ToBytes(int64(i)) + case int16: + return Int64ToBytes(int64(i)) + case int32: + return Int64ToBytes(int64(i)) + case []byte: + return i + case error: + return []byte(i.Error()) + case bool: + if i { + return []byte("true") + } + return []byte("false") + } + return []byte(fmt.Sprintf("%#v", raw)) +} + +func Int64ToBytes(i int64) []byte { + var buf = make([]byte, 8) + binary.BigEndian.PutUint64(buf, uint64(i)) + return buf +} + +func BytesToInt64(buf []byte) int64 { + return int64(binary.BigEndian.Uint64(buf)) +} + +func StrToInt(s string) int { + res, _ := strconv.Atoi(s) + return res +} + +func StrToInt64(s string) int64 { + res, _ := strconv.ParseInt(s, 10, 64) + return res +} + +func Float32ToByte(float float32) []byte { + bits := math.Float32bits(float) + bytes := make([]byte, 4) + binary.LittleEndian.PutUint32(bytes, bits) + + return bytes +} + +func ByteToFloat32(bytes []byte) float32 { + bits := binary.LittleEndian.Uint32(bytes) + return math.Float32frombits(bits) +} + +func Float64ToByte(float float64) []byte { + bits := math.Float64bits(float) + bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, bits) + return bytes +} + +func ByteToFloat64(bytes []byte) float64 { + bits := binary.LittleEndian.Uint64(bytes) + return math.Float64frombits(bits) +} + +func Float64ToStr(f float64) string { + return strconv.FormatFloat(f, 'f', 2, 64) +} +func Float64ToStrPrec1(f float64) string { + return strconv.FormatFloat(f, 'f', 1, 64) +} + +func Float32ToStr(f float32) string { + return Float64ToStr(float64(f)) +} + +func StrToFloat64(s string) float64 { + res, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return res +} + +func StrToFloat32(s string) float32 { + res, err := strconv.ParseFloat(s, 32) + if err != nil { + return 0 + } + return float32(res) +} + +func StrToBool(s string) bool { + b, _ := strconv.ParseBool(s) + return b +} + +func BoolToStr(b bool) string { + if b { + return "true" + } + return "false" +} + +func FloatToInt64(f float64) int64 { + return int64(f) +} + +func IntToStr(i int) string { + return strconv.Itoa(i) +} + +func Int64ToStr(i int64) string { + return strconv.FormatInt(i, 10) +} diff --git a/app/utils/crypto.go b/app/utils/crypto.go new file mode 100644 index 0000000..56289c5 --- /dev/null +++ b/app/utils/crypto.go @@ -0,0 +1,19 @@ +package utils + +import ( + "crypto/md5" + "encoding/base64" + "fmt" +) + +func GetMd5(raw []byte) string { + h := md5.New() + h.Write(raw) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func GetBase64Md5(raw []byte) string { + h := md5.New() + h.Write(raw) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/app/utils/curl.go b/app/utils/curl.go new file mode 100644 index 0000000..0a45607 --- /dev/null +++ b/app/utils/curl.go @@ -0,0 +1,209 @@ +package utils + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strings" + "time" +) + +var CurlDebug bool + +func CurlGet(router string, header map[string]string) ([]byte, error) { + return curl(http.MethodGet, router, nil, header) +} +func CurlGetJson(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl_new(http.MethodGet, router, body, header) +} + +// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string +func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPost, router, body, header) +} + +func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPut, router, body, header) +} + +// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string +func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPatch, router, body, header) +} + +// CurlDelete is curl delete +func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodDelete, router, body, header) +} + +func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) { + var reqBody io.Reader + contentType := "application/json" + switch v := body.(type) { + case string: + reqBody = strings.NewReader(v) + case []byte: + reqBody = bytes.NewReader(v) + case map[string]string: + val := url.Values{} + for k, v := range v { + val.Set(k, v) + } + reqBody = strings.NewReader(val.Encode()) + contentType = "application/x-www-form-urlencoded" + case map[string]interface{}: + val := url.Values{} + for k, v := range v { + val.Set(k, v.(string)) + } + reqBody = strings.NewReader(val.Encode()) + contentType = "application/x-www-form-urlencoded" + } + if header == nil { + header = map[string]string{"Content-Type": contentType} + } + if _, ok := header["Content-Type"]; !ok { + header["Content-Type"] = contentType + } + resp, er := CurlReq(method, router, reqBody, header) + if er != nil { + return nil, er + } + res, err := ioutil.ReadAll(resp.Body) + if CurlDebug { + blob := SerializeStr(body) + if contentType != "application/json" { + blob = HttpBuild(body) + } + fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", + router, + time.Now().Format("2006-01-02 15:04:05.000"), + method, + contentType, + HttpBuildQuery(header), + blob, + err, + SerializeStr(resp.Header), + string(res), + ) + } + resp.Body.Close() + return res, err +} + +func curl_new(method, router string, body interface{}, header map[string]string) ([]byte, error) { + var reqBody io.Reader + contentType := "application/json" + + if header == nil { + header = map[string]string{"Content-Type": contentType} + } + if _, ok := header["Content-Type"]; !ok { + header["Content-Type"] = contentType + } + resp, er := CurlReq(method, router, reqBody, header) + if er != nil { + return nil, er + } + res, err := ioutil.ReadAll(resp.Body) + if CurlDebug { + blob := SerializeStr(body) + if contentType != "application/json" { + blob = HttpBuild(body) + } + fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", + router, + time.Now().Format("2006-01-02 15:04:05.000"), + method, + contentType, + HttpBuildQuery(header), + blob, + err, + SerializeStr(resp.Header), + string(res), + ) + } + resp.Body.Close() + return res, err +} + +func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) { + req, _ := http.NewRequest(method, router, reqBody) + if header != nil { + for k, v := range header { + req.Header.Set(k, v) + } + } + // 绕过github等可能因为特征码返回503问题 + // https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/ + defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009, + 0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a} + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...), + }, + }, + // 获取301重定向 + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + return client.Do(req) +} + +// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序 a=123&b=321 +func HttpBuildQuery(args map[string]string, sortAsc ...bool) string { + str := "" + if len(args) == 0 { + return str + } + if len(sortAsc) > 0 { + keys := make([]string, 0, len(args)) + for k := range args { + keys = append(keys, k) + } + if sortAsc[0] { + sort.Strings(keys) + } else { + sort.Sort(sort.Reverse(sort.StringSlice(keys))) + } + for _, k := range keys { + str += "&" + k + "=" + args[k] + } + } else { + for k, v := range args { + str += "&" + k + "=" + v + } + } + return str[1:] +} + +func HttpBuild(body interface{}, sortAsc ...bool) string { + params := map[string]string{} + if args, ok := body.(map[string]interface{}); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + if args, ok := body.(map[string]string); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + if args, ok := body.(map[string]int); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + return AnyToString(body) +} diff --git a/app/utils/debug.go b/app/utils/debug.go new file mode 100644 index 0000000..bb2e9d3 --- /dev/null +++ b/app/utils/debug.go @@ -0,0 +1,25 @@ +package utils + +import ( + "fmt" + "os" + "strconv" + "time" +) + +func Debug(args ...interface{}) { + s := "" + l := len(args) + if l < 1 { + fmt.Println("please input some data") + os.Exit(0) + } + i := 1 + for _, v := range args { + s += fmt.Sprintf("【"+strconv.Itoa(i)+"】: %#v\n", v) + i++ + } + s = "******************** 【DEBUG - " + time.Now().Format("2006-01-02 15:04:05") + "】 ********************\n" + s + "******************** 【DEBUG - END】 ********************\n" + fmt.Println(s) + os.Exit(0) +} diff --git a/app/utils/duplicate.go b/app/utils/duplicate.go new file mode 100644 index 0000000..17cea88 --- /dev/null +++ b/app/utils/duplicate.go @@ -0,0 +1,37 @@ +package utils + +func RemoveDuplicateString(elms []string) []string { + res := make([]string, 0, len(elms)) + temp := map[string]struct{}{} + for _, item := range elms { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + res = append(res, item) + } + } + return res +} + +func RemoveDuplicateInt(elms []int) []int { + res := make([]int, 0, len(elms)) + temp := map[int]struct{}{} + for _, item := range elms { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + res = append(res, item) + } + } + return res +} + +func RemoveDuplicateInt64(elms []int64) []int64 { + res := make([]int64, 0, len(elms)) + temp := map[int64]struct{}{} + for _, item := range elms { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + res = append(res, item) + } + } + return res +} diff --git a/app/utils/file.go b/app/utils/file.go new file mode 100644 index 0000000..93ed08f --- /dev/null +++ b/app/utils/file.go @@ -0,0 +1,22 @@ +package utils + +import ( + "os" + "path" + "strings" + "time" +) + +// 获取文件后缀 +func FileExt(fname string) string { + return strings.ToLower(strings.TrimLeft(path.Ext(fname), ".")) +} + +func FilePutContents(fileName string, content string) { + fd, _ := os.OpenFile("./tmp/"+fileName+".log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + fd_time := time.Now().Format("2006-01-02 15:04:05") + fd_content := strings.Join([]string{"[", fd_time, "] ", content, "\n"}, "") + buf := []byte(fd_content) + fd.Write(buf) + fd.Close() +} diff --git a/app/utils/file_and_dir.go b/app/utils/file_and_dir.go new file mode 100644 index 0000000..93141f9 --- /dev/null +++ b/app/utils/file_and_dir.go @@ -0,0 +1,29 @@ +package utils + +import "os" + +// 判断所给路径文件、文件夹是否存在 +func Exists(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +// 判断所给路径是否为文件夹 +func IsDir(path string) bool { + s, err := os.Stat(path) + if err != nil { + return false + } + return s.IsDir() +} + +// 判断所给路径是否为文件 +func IsFile(path string) bool { + return !IsDir(path) +} diff --git a/app/utils/format.go b/app/utils/format.go new file mode 100644 index 0000000..997fe80 --- /dev/null +++ b/app/utils/format.go @@ -0,0 +1,59 @@ +package utils + +import ( + "math" +) + +func CouponFormat(data string) string { + switch data { + case "0.00", "0", "": + return "" + default: + return Int64ToStr(FloatToInt64(StrToFloat64(data))) + } +} +func CommissionFormat(data string) string { + if StrToFloat64(data) > 0 { + return data + } + + return "" +} + +func HideString(src string, hLen int) string { + str := []rune(src) + if hLen == 0 { + hLen = 4 + } + hideStr := "" + for i := 0; i < hLen; i++ { + hideStr += "*" + } + hideLen := len(str) / 2 + showLen := len(str) - hideLen + if hideLen == 0 || showLen == 0 { + return hideStr + } + subLen := showLen / 2 + if subLen == 0 { + return string(str[:showLen]) + hideStr + } + s := string(str[:subLen]) + s += hideStr + s += string(str[len(str)-subLen:]) + return s +} + +//SaleCountFormat is 格式化销量 +func SaleCountFormat(s string) string { + return s + "已售" +} + +// 小数格式化 +func FloatFormat(f float64, i int) float64 { + if i > 14 { + return f + } + p := math.Pow10(i) + return float64(int64((f+0.000000000000009)*p)) / p +} diff --git a/app/utils/ip.go b/app/utils/ip.go new file mode 100644 index 0000000..6ed8286 --- /dev/null +++ b/app/utils/ip.go @@ -0,0 +1,146 @@ +package utils + +import ( + "errors" + "math" + "net" + "net/http" + "strings" +) + +func GetIP(r *http.Request) string { + ip := ClientPublicIP(r) + if ip == "" { + ip = ClientIP(r) + } + if ip == "" { + ip = "0000" + } + return ip +} + +// HasLocalIPddr 检测 IP 地址字符串是否是内网地址 +// Deprecated: 此为一个错误名称错误拼写的函数,计划在将来移除,请使用 HasLocalIPAddr 函数 +func HasLocalIPddr(ip string) bool { + return HasLocalIPAddr(ip) +} + +// HasLocalIPAddr 检测 IP 地址字符串是否是内网地址 +func HasLocalIPAddr(ip string) bool { + return HasLocalIP(net.ParseIP(ip)) +} + +// HasLocalIP 检测 IP 地址是否是内网地址 +// 通过直接对比ip段范围效率更高,详见:https://github.com/thinkeridea/go-extend/issues/2 +func HasLocalIP(ip net.IP) bool { + if ip.IsLoopback() { + return true + } + + ip4 := ip.To4() + if ip4 == nil { + return false + } + + return ip4[0] == 10 || // 10.0.0.0/8 + (ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12 + (ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16 + (ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16 +} + +// ClientIP 尽最大努力实现获取客户端 IP 的算法。 +// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。 +func ClientIP(r *http.Request) string { + ip := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-For"), ",")[0]) + if ip != "" { + return ip + } + + ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")) + if ip != "" { + return ip + } + + if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil { + return ip + } + + return "" +} + +// ClientPublicIP 尽最大努力实现获取客户端公网 IP 的算法。 +// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。 +func ClientPublicIP(r *http.Request) string { + var ip string + for _, ip = range strings.Split(r.Header.Get("X-Forwarded-For"), ",") { + if ip = strings.TrimSpace(ip); ip != "" && !HasLocalIPAddr(ip) { + return ip + } + } + + if ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")); ip != "" && !HasLocalIPAddr(ip) { + return ip + } + + if ip = RemoteIP(r); !HasLocalIPAddr(ip) { + return ip + } + + return "" +} + +// RemoteIP 通过 RemoteAddr 获取 IP 地址, 只是一个快速解析方法。 +func RemoteIP(r *http.Request) string { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + return ip +} + +// IPString2Long 把ip字符串转为数值 +func IPString2Long(ip string) (uint, error) { + b := net.ParseIP(ip).To4() + if b == nil { + return 0, errors.New("invalid ipv4 format") + } + + return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24, nil +} + +// Long2IPString 把数值转为ip字符串 +func Long2IPString(i uint) (string, error) { + if i > math.MaxUint32 { + return "", errors.New("beyond the scope of ipv4") + } + + ip := make(net.IP, net.IPv4len) + ip[0] = byte(i >> 24) + ip[1] = byte(i >> 16) + ip[2] = byte(i >> 8) + ip[3] = byte(i) + + return ip.String(), nil +} + +// IP2Long 把net.IP转为数值 +func IP2Long(ip net.IP) (uint, error) { + b := ip.To4() + if b == nil { + return 0, errors.New("invalid ipv4 format") + } + + return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24, nil +} + +// Long2IP 把数值转为net.IP +func Long2IP(i uint) (net.IP, error) { + if i > math.MaxUint32 { + return nil, errors.New("beyond the scope of ipv4") + } + + ip := make(net.IP, net.IPv4len) + ip[0] = byte(i >> 24) + ip[1] = byte(i >> 16) + ip[2] = byte(i >> 8) + ip[3] = byte(i) + + return ip, nil +} diff --git a/app/utils/json.go b/app/utils/json.go new file mode 100644 index 0000000..998bcec --- /dev/null +++ b/app/utils/json.go @@ -0,0 +1,17 @@ +package utils + +import ( + "bytes" + "encoding/json" +) + +func JsonMarshal(interface{}) { + +} + +// 不科学计数法 +func JsonDecode(data []byte, v interface{}) error { + d := json.NewDecoder(bytes.NewReader(data)) + d.UseNumber() + return d.Decode(v) +} diff --git a/app/utils/logx/log.go b/app/utils/logx/log.go new file mode 100644 index 0000000..ca11223 --- /dev/null +++ b/app/utils/logx/log.go @@ -0,0 +1,245 @@ +package logx + +import ( + "os" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type LogConfig struct { + AppName string `yaml:"app_name" json:"app_name" toml:"app_name"` + Level string `yaml:"level" json:"level" toml:"level"` + StacktraceLevel string `yaml:"stacktrace_level" json:"stacktrace_level" toml:"stacktrace_level"` + IsStdOut bool `yaml:"is_stdout" json:"is_stdout" toml:"is_stdout"` + TimeFormat string `yaml:"time_format" json:"time_format" toml:"time_format"` // second, milli, nano, standard, iso, + Encoding string `yaml:"encoding" json:"encoding" toml:"encoding"` // console, json + Skip int `yaml:"skip" json:"skip" toml:"skip"` + + IsFileOut bool `yaml:"is_file_out" json:"is_file_out" toml:"is_file_out"` + FileDir string `yaml:"file_dir" json:"file_dir" toml:"file_dir"` + FileName string `yaml:"file_name" json:"file_name" toml:"file_name"` + FileMaxSize int `yaml:"file_max_size" json:"file_max_size" toml:"file_max_size"` + FileMaxAge int `yaml:"file_max_age" json:"file_max_age" toml:"file_max_age"` +} + +var ( + l *LogX = defaultLogger() + conf *LogConfig +) + +// default logger setting +func defaultLogger() *LogX { + conf = &LogConfig{ + Level: "debug", + StacktraceLevel: "error", + IsStdOut: true, + TimeFormat: "standard", + Encoding: "console", + Skip: 2, + } + writers := []zapcore.WriteSyncer{os.Stdout} + lg, lv := newZapLogger(setLogLevel(conf.Level), setLogLevel(conf.StacktraceLevel), conf.Encoding, conf.TimeFormat, conf.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + return &LogX{logger: lg, atomLevel: lv} +} + +// initial standard log, if you don't init, it will use default logger setting +func InitDefaultLogger(cfg *LogConfig) { + var writers []zapcore.WriteSyncer + if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { + writers = append(writers, os.Stdout) + } + if cfg.IsFileOut { + writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) + } + + lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + if cfg.AppName != "" { + lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 + } + l = &LogX{logger: lg, atomLevel: lv} +} + +// create a new logger +func NewLogger(cfg *LogConfig) *LogX { + var writers []zapcore.WriteSyncer + if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { + writers = append(writers, os.Stdout) + } + if cfg.IsFileOut { + writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) + } + + lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + if cfg.AppName != "" { + lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 + } + return &LogX{logger: lg, atomLevel: lv} +} + +// create a new zaplog logger +func newZapLogger(level, stacktrace zapcore.Level, encoding, timeType string, skip int, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) { + encCfg := zapcore.EncoderConfig{ + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeDuration: zapcore.NanosDurationEncoder, + EncodeLevel: zapcore.LowercaseLevelEncoder, + } + setTimeFormat(timeType, &encCfg) // set time type + atmLvl := zap.NewAtomicLevel() // set level + atmLvl.SetLevel(level) + encoder := zapcore.NewJSONEncoder(encCfg) // 确定encoder格式 + if encoding == "console" { + encoder = zapcore.NewConsoleEncoder(encCfg) + } + return zap.New(zapcore.NewCore(encoder, output, atmLvl), zap.AddCaller(), zap.AddStacktrace(stacktrace), zap.AddCallerSkip(skip)), &atmLvl +} + +// set log level +func setLogLevel(lvl string) zapcore.Level { + switch strings.ToLower(lvl) { + case "panic": + return zapcore.PanicLevel + case "fatal": + return zapcore.FatalLevel + case "error": + return zapcore.ErrorLevel + case "warn", "warning": + return zapcore.WarnLevel + case "info": + return zapcore.InfoLevel + default: + return zapcore.DebugLevel + } +} + +// set time format +func setTimeFormat(timeType string, z *zapcore.EncoderConfig) { + switch strings.ToLower(timeType) { + case "iso": // iso8601 standard + z.EncodeTime = zapcore.ISO8601TimeEncoder + case "sec": // only for unix second, without millisecond + z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendInt64(t.Unix()) + } + case "second": // unix second, with millisecond + z.EncodeTime = zapcore.EpochTimeEncoder + case "milli", "millisecond": // millisecond + z.EncodeTime = zapcore.EpochMillisTimeEncoder + case "nano", "nanosecond": // nanosecond + z.EncodeTime = zapcore.EpochNanosTimeEncoder + default: // standard format + z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02 15:04:05.000")) + } + } +} + +func GetLevel() string { + switch l.atomLevel.Level() { + case zapcore.PanicLevel: + return "panic" + case zapcore.FatalLevel: + return "fatal" + case zapcore.ErrorLevel: + return "error" + case zapcore.WarnLevel: + return "warn" + case zapcore.InfoLevel: + return "info" + default: + return "debug" + } +} + +func SetLevel(lvl string) { + l.atomLevel.SetLevel(setLogLevel(lvl)) +} + +// temporary add call skip +func AddCallerSkip(skip int) *LogX { + l.logger.WithOptions(zap.AddCallerSkip(skip)) + return l +} + +// permanent add call skip +func AddDepth(skip int) *LogX { + l.logger = l.logger.WithOptions(zap.AddCallerSkip(skip)) + return l +} + +// permanent add options +func AddOptions(opts ...zap.Option) *LogX { + l.logger = l.logger.WithOptions(opts...) + return l +} + +func AddField(k string, v interface{}) { + l.logger.With(zap.Any(k, v)) +} + +func AddFields(fields map[string]interface{}) *LogX { + for k, v := range fields { + l.logger.With(zap.Any(k, v)) + } + return l +} + +// Normal log +func Debug(e interface{}, args ...interface{}) error { + return l.Debug(e, args...) +} +func Info(e interface{}, args ...interface{}) error { + return l.Info(e, args...) +} +func Warn(e interface{}, args ...interface{}) error { + return l.Warn(e, args...) +} +func Error(e interface{}, args ...interface{}) error { + return l.Error(e, args...) +} +func Panic(e interface{}, args ...interface{}) error { + return l.Panic(e, args...) +} +func Fatal(e interface{}, args ...interface{}) error { + return l.Fatal(e, args...) +} + +// Format logs +func Debugf(format string, args ...interface{}) error { + return l.Debugf(format, args...) +} +func Infof(format string, args ...interface{}) error { + return l.Infof(format, args...) +} +func Warnf(format string, args ...interface{}) error { + return l.Warnf(format, args...) +} +func Errorf(format string, args ...interface{}) error { + return l.Errorf(format, args...) +} +func Panicf(format string, args ...interface{}) error { + return l.Panicf(format, args...) +} +func Fatalf(format string, args ...interface{}) error { + return l.Fatalf(format, args...) +} + +func formatFieldMap(m FieldMap) []Field { + var res []Field + for k, v := range m { + res = append(res, zap.Any(k, v)) + } + return res +} diff --git a/app/utils/logx/output.go b/app/utils/logx/output.go new file mode 100644 index 0000000..ef33f0b --- /dev/null +++ b/app/utils/logx/output.go @@ -0,0 +1,105 @@ +package logx + +import ( + "bytes" + "io" + "os" + "path/filepath" + "time" + + "gopkg.in/natefinch/lumberjack.v2" +) + +// output interface +type WriteSyncer interface { + io.Writer + Sync() error +} + +// split writer +func NewRollingFile(dir, filename string, maxSize, MaxAge int) WriteSyncer { + s, err := os.Stat(dir) + if err != nil || !s.IsDir() { + os.RemoveAll(dir) + if err := os.MkdirAll(dir, 0766); err != nil { + panic(err) + } + } + return newLumberjackWriteSyncer(&lumberjack.Logger{ + Filename: filepath.Join(dir, filename), + MaxSize: maxSize, // megabytes, MB + MaxAge: MaxAge, // days + LocalTime: true, + Compress: false, + }) +} + +type lumberjackWriteSyncer struct { + *lumberjack.Logger + buf *bytes.Buffer + logChan chan []byte + closeChan chan interface{} + maxSize int +} + +func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer { + ws := &lumberjackWriteSyncer{ + Logger: l, + buf: bytes.NewBuffer([]byte{}), + logChan: make(chan []byte, 5000), + closeChan: make(chan interface{}), + maxSize: 1024, + } + go ws.run() + return ws +} + +func (l *lumberjackWriteSyncer) run() { + ticker := time.NewTicker(1 * time.Second) + + for { + select { + case <-ticker.C: + if l.buf.Len() > 0 { + l.sync() + } + case bs := <-l.logChan: + _, err := l.buf.Write(bs) + if err != nil { + continue + } + if l.buf.Len() > l.maxSize { + l.sync() + } + case <-l.closeChan: + l.sync() + return + } + } +} + +func (l *lumberjackWriteSyncer) Stop() { + close(l.closeChan) +} + +func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) { + b := make([]byte, len(bs)) + for i, c := range bs { + b[i] = c + } + l.logChan <- b + return 0, nil +} + +func (l *lumberjackWriteSyncer) Sync() error { + return nil +} + +func (l *lumberjackWriteSyncer) sync() error { + defer l.buf.Reset() + _, err := l.Logger.Write(l.buf.Bytes()) + if err != nil { + return err + } + return nil +} diff --git a/app/utils/logx/sugar.go b/app/utils/logx/sugar.go new file mode 100644 index 0000000..ab380fc --- /dev/null +++ b/app/utils/logx/sugar.go @@ -0,0 +1,192 @@ +package logx + +import ( + "errors" + "fmt" + "strconv" + + "go.uber.org/zap" +) + +type LogX struct { + logger *zap.Logger + atomLevel *zap.AtomicLevel +} + +type Field = zap.Field +type FieldMap map[string]interface{} + +// 判断其他类型--start +func getFields(msg string, format bool, args ...interface{}) (string, []Field) { + var str []interface{} + var fields []zap.Field + if len(args) > 0 { + for _, v := range args { + if f, ok := v.(Field); ok { + fields = append(fields, f) + } else if f, ok := v.(FieldMap); ok { + fields = append(fields, formatFieldMap(f)...) + } else { + str = append(str, AnyToString(v)) + } + } + if format { + return fmt.Sprintf(msg, str...), fields + } + str = append([]interface{}{msg}, str...) + return fmt.Sprintln(str...), fields + } + return msg, []Field{} +} + +func (l *LogX) Debug(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Debug(msg, field...) + } + return e +} +func (l *LogX) Info(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Info(msg, field...) + } + return e +} +func (l *LogX) Warn(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Warn(msg, field...) + } + return e +} +func (l *LogX) Error(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Error(msg, field...) + } + return e +} +func (l *LogX) DPanic(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.DPanic(msg, field...) + } + return e +} +func (l *LogX) Panic(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Panic(msg, field...) + } + return e +} +func (l *LogX) Fatal(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Fatal(msg, field...) + } + return e +} + +func checkErr(s interface{}) (string, error) { + switch e := s.(type) { + case error: + return e.Error(), e + case string: + return e, errors.New(e) + case []byte: + return string(e), nil + default: + return "", nil + } +} + +func (l *LogX) LogError(err error) error { + return l.Error(err.Error()) +} + +func (l *LogX) Debugf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Debug(s, f...) + return errors.New(s) +} + +func (l *LogX) Infof(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Info(s, f...) + return errors.New(s) +} + +func (l *LogX) Warnf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Warn(s, f...) + return errors.New(s) +} + +func (l *LogX) Errorf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Error(s, f...) + return errors.New(s) +} + +func (l *LogX) DPanicf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.DPanic(s, f...) + return errors.New(s) +} + +func (l *LogX) Panicf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Panic(s, f...) + return errors.New(s) +} + +func (l *LogX) Fatalf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Fatal(s, f...) + return errors.New(s) +} + +func AnyToString(raw interface{}) string { + switch i := raw.(type) { + case []byte: + return string(i) + case int: + return strconv.FormatInt(int64(i), 10) + case int64: + return strconv.FormatInt(i, 10) + case float32: + return strconv.FormatFloat(float64(i), 'f', 2, 64) + case float64: + return strconv.FormatFloat(i, 'f', 2, 64) + case uint: + return strconv.FormatInt(int64(i), 10) + case uint8: + return strconv.FormatInt(int64(i), 10) + case uint16: + return strconv.FormatInt(int64(i), 10) + case uint32: + return strconv.FormatInt(int64(i), 10) + case uint64: + return strconv.FormatInt(int64(i), 10) + case int8: + return strconv.FormatInt(int64(i), 10) + case int16: + return strconv.FormatInt(int64(i), 10) + case int32: + return strconv.FormatInt(int64(i), 10) + case string: + return i + case error: + return i.Error() + } + return fmt.Sprintf("%#v", raw) +} diff --git a/app/utils/map.go b/app/utils/map.go new file mode 100644 index 0000000..d9f3b7a --- /dev/null +++ b/app/utils/map.go @@ -0,0 +1,9 @@ +package utils + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} diff --git a/app/utils/map_and_struct.go b/app/utils/map_and_struct.go new file mode 100644 index 0000000..34904ce --- /dev/null +++ b/app/utils/map_and_struct.go @@ -0,0 +1,341 @@ +package utils + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" +) + +func Map2Struct(vals map[string]interface{}, dst interface{}) (err error) { + return Map2StructByTag(vals, dst, "json") +} + +func Map2StructByTag(vals map[string]interface{}, dst interface{}, structTag string) (err error) { + defer func() { + e := recover() + if e != nil { + if v, ok := e.(error); ok { + err = fmt.Errorf("Panic: %v", v.Error()) + } else { + err = fmt.Errorf("Panic: %v", e) + } + } + }() + + pt := reflect.TypeOf(dst) + pv := reflect.ValueOf(dst) + + if pv.Kind() != reflect.Ptr || pv.Elem().Kind() != reflect.Struct { + return fmt.Errorf("not a pointer of struct") + } + + var f reflect.StructField + var ft reflect.Type + var fv reflect.Value + + for i := 0; i < pt.Elem().NumField(); i++ { + f = pt.Elem().Field(i) + fv = pv.Elem().Field(i) + ft = f.Type + + if f.Anonymous || !fv.CanSet() { + continue + } + + tag := f.Tag.Get(structTag) + + name, option := parseTag(tag) + + if name == "-" { + continue + } + + if name == "" { + name = strings.ToLower(f.Name) + } + val, ok := vals[name] + + if !ok { + if option == "required" { + return fmt.Errorf("'%v' not found", name) + } + if len(option) != 0 { + val = option // default value + } else { + //fv.Set(reflect.Zero(ft)) // TODO set zero value or just ignore it? + continue + } + } + + // convert or set value to field + vv := reflect.ValueOf(val) + vt := reflect.TypeOf(val) + + if vt.Kind() != reflect.String { + // try to assign and convert + if vt.AssignableTo(ft) { + fv.Set(vv) + continue + } + + if vt.ConvertibleTo(ft) { + fv.Set(vv.Convert(ft)) + continue + } + + return fmt.Errorf("value type not match: field=%v(%v) value=%v(%v)", f.Name, ft.Kind(), val, vt.Kind()) + } + s := strings.TrimSpace(vv.String()) + if len(s) == 0 && option == "required" { + return fmt.Errorf("value of required argument can't not be empty") + } + fk := ft.Kind() + + // convert string to value + if fk == reflect.Ptr && ft.Elem().Kind() == reflect.String { + fv.Set(reflect.ValueOf(&s)) + continue + } + if fk == reflect.Ptr || fk == reflect.Struct { + err = convertJsonValue(s, name, fv) + } else if fk == reflect.Slice { + err = convertSlice(s, f.Name, ft, fv) + } else { + err = convertValue(fk, s, f.Name, fv) + } + + if err != nil { + return err + } + continue + } + + return nil +} + +func Struct2Map(s interface{}) map[string]interface{} { + return Struct2MapByTag(s, "json") +} +func Struct2MapByTag(s interface{}, tagName string) map[string]interface{} { + t := reflect.TypeOf(s) + v := reflect.ValueOf(s) + + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { + t = t.Elem() + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil + } + + m := make(map[string]interface{}) + + for i := 0; i < t.NumField(); i++ { + fv := v.Field(i) + ft := t.Field(i) + + if !fv.CanInterface() { + continue + } + + if ft.PkgPath != "" { // unexported + continue + } + + var name string + var option string + tag := ft.Tag.Get(tagName) + if tag != "" { + ts := strings.Split(tag, ",") + if len(ts) == 1 { + name = ts[0] + } else if len(ts) > 1 { + name = ts[0] + option = ts[1] + } + if name == "-" { + continue // skip this field + } + if name == "" { + name = strings.ToLower(ft.Name) + } + if option == "omitempty" { + if isEmpty(&fv) { + continue // skip empty field + } + } + } else { + name = strings.ToLower(ft.Name) + } + + if ft.Anonymous && fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if (ft.Anonymous && fv.Kind() == reflect.Struct) || + (ft.Anonymous && fv.Kind() == reflect.Ptr && fv.Elem().Kind() == reflect.Struct) { + + // embedded struct + embedded := Struct2MapByTag(fv.Interface(), tagName) + for embName, embValue := range embedded { + m[embName] = embValue + } + } else if option == "string" { + kind := fv.Kind() + if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 { + m[name] = strconv.FormatInt(fv.Int(), 10) + } else if kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 { + m[name] = strconv.FormatUint(fv.Uint(), 10) + } else if kind == reflect.Float32 || kind == reflect.Float64 { + m[name] = strconv.FormatFloat(fv.Float(), 'f', 2, 64) + } else { + m[name] = fv.Interface() + } + } else { + m[name] = fv.Interface() + } + } + + return m +} + +func isEmpty(v *reflect.Value) bool { + k := v.Kind() + if k == reflect.Bool { + return v.Bool() == false + } else if reflect.Int < k && k < reflect.Int64 { + return v.Int() == 0 + } else if reflect.Uint < k && k < reflect.Uintptr { + return v.Uint() == 0 + } else if k == reflect.Float32 || k == reflect.Float64 { + return v.Float() == 0 + } else if k == reflect.Array || k == reflect.Map || k == reflect.Slice || k == reflect.String { + return v.Len() == 0 + } else if k == reflect.Interface || k == reflect.Ptr { + return v.IsNil() + } + return false +} + +func convertSlice(s string, name string, ft reflect.Type, fv reflect.Value) error { + var err error + et := ft.Elem() + + if et.Kind() == reflect.Ptr || et.Kind() == reflect.Struct { + return convertJsonValue(s, name, fv) + } + + ss := strings.Split(s, ",") + + if len(s) == 0 || len(ss) == 0 { + return nil + } + + fs := reflect.MakeSlice(ft, 0, len(ss)) + + for _, si := range ss { + ev := reflect.New(et).Elem() + + err = convertValue(et.Kind(), si, name, ev) + if err != nil { + return err + } + fs = reflect.Append(fs, ev) + } + + fv.Set(fs) + + return nil +} + +func convertJsonValue(s string, name string, fv reflect.Value) error { + var err error + d := StringToSlice(s) + + if fv.Kind() == reflect.Ptr { + if fv.IsNil() { + fv.Set(reflect.New(fv.Type().Elem())) + } + } else { + fv = fv.Addr() + } + + err = json.Unmarshal(d, fv.Interface()) + + if err != nil { + return fmt.Errorf("invalid json '%v': %v, %v", name, err.Error(), s) + } + + return nil +} + +func convertValue(kind reflect.Kind, s string, name string, fv reflect.Value) error { + if !fv.CanAddr() { + return fmt.Errorf("can not addr: %v", name) + } + + if kind == reflect.String { + fv.SetString(s) + return nil + } + + if kind == reflect.Bool { + switch s { + case "true": + fv.SetBool(true) + case "false": + fv.SetBool(false) + case "1": + fv.SetBool(true) + case "0": + fv.SetBool(false) + default: + return fmt.Errorf("invalid bool: %v value=%v", name, s) + } + return nil + } + + if reflect.Int <= kind && kind <= reflect.Int64 { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return fmt.Errorf("invalid int: %v value=%v", name, s) + } + fv.SetInt(i) + + } else if reflect.Uint <= kind && kind <= reflect.Uint64 { + i, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return fmt.Errorf("invalid int: %v value=%v", name, s) + } + fv.SetUint(i) + + } else if reflect.Float32 == kind || kind == reflect.Float64 { + i, err := strconv.ParseFloat(s, 64) + + if err != nil { + return fmt.Errorf("invalid float: %v value=%v", name, s) + } + + fv.SetFloat(i) + } else { + // not support or just ignore it? + // return fmt.Errorf("type not support: field=%v(%v) value=%v(%v)", name, ft.Kind(), val, vt.Kind()) + } + return nil +} + +func parseTag(tag string) (string, string) { + tags := strings.Split(tag, ",") + + if len(tags) <= 0 { + return "", "" + } + + if len(tags) == 1 { + return tags[0], "" + } + + return tags[0], tags[1] +} diff --git a/app/utils/md5.go b/app/utils/md5.go new file mode 100644 index 0000000..52c108d --- /dev/null +++ b/app/utils/md5.go @@ -0,0 +1,12 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" +) + +func Md5(str string) string { + h := md5.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/app/utils/qrcode/decodeFile.go b/app/utils/qrcode/decodeFile.go new file mode 100644 index 0000000..f50fb28 --- /dev/null +++ b/app/utils/qrcode/decodeFile.go @@ -0,0 +1,33 @@ +package qrcode + +import ( + "image" + _ "image/jpeg" + _ "image/png" + "os" + + "github.com/makiuchi-d/gozxing" + "github.com/makiuchi-d/gozxing/qrcode" +) + +func DecodeFile(fi string) (string, error) { + file, err := os.Open(fi) + if err != nil { + return "", err + } + img, _, err := image.Decode(file) + if err != nil { + return "", err + } + // prepare BinaryBitmap + bmp, err := gozxing.NewBinaryBitmapFromImage(img) + if err != nil { + return "", err + } + // decode image + result, err := qrcode.NewQRCodeReader().Decode(bmp, nil) + if err != nil { + return "", err + } + return result.String(), nil +} diff --git a/app/utils/qrcode/getBase64.go b/app/utils/qrcode/getBase64.go new file mode 100644 index 0000000..11d149c --- /dev/null +++ b/app/utils/qrcode/getBase64.go @@ -0,0 +1,43 @@ +package qrcode + +// 生成登录二维码图片, 方便在网页上显示 + +import ( + "bytes" + "encoding/base64" + "image/jpeg" + "image/png" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func GetJPGBase64(content string, edges ...int) string { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区 + jpeg.Encode(emptyBuff, img, nil) + dist := make([]byte, 50000) // 开辟存储空间 + base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64 + return "data:image/png;base64," + string(dist) // 输出图片base64(type = []byte) +} + +func GetPNGBase64(content string, edges ...int) string { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区 + png.Encode(emptyBuff, img) + dist := make([]byte, 50000) // 开辟存储空间 + base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64 + return string(dist) // 输出图片base64(type = []byte) +} diff --git a/app/utils/qrcode/saveFile.go b/app/utils/qrcode/saveFile.go new file mode 100644 index 0000000..4854783 --- /dev/null +++ b/app/utils/qrcode/saveFile.go @@ -0,0 +1,85 @@ +package qrcode + +// 生成登录二维码图片 + +import ( + "errors" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" + "strings" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func SaveJpegFile(filePath, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + return writeFile(filePath, img, "jpg") +} + +func SavePngFile(filePath, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + return writeFile(filePath, img, "png") +} + +func writeFile(filePath string, img image.Image, format string) error { + if err := createDir(filePath); err != nil { + return err + } + file, err := os.Create(filePath) + defer file.Close() + if err != nil { + return err + } + switch strings.ToLower(format) { + case "png": + err = png.Encode(file, img) + break + case "jpg": + err = jpeg.Encode(file, img, nil) + default: + return errors.New("format not accept") + } + if err != nil { + return err + } + return nil +} + +func createDir(filePath string) error { + var err error + // filePath, _ = filepath.Abs(filePath) + dirPath := filepath.Dir(filePath) + dirInfo, err := os.Stat(dirPath) + if err != nil { + if !os.IsExist(err) { + err = os.MkdirAll(dirPath, 0777) + if err != nil { + return err + } + } else { + return err + } + } else { + if dirInfo.IsDir() { + return nil + } + return errors.New("directory is a file") + } + return nil +} diff --git a/app/utils/qrcode/writeWeb.go b/app/utils/qrcode/writeWeb.go new file mode 100644 index 0000000..57e1e92 --- /dev/null +++ b/app/utils/qrcode/writeWeb.go @@ -0,0 +1,39 @@ +package qrcode + +import ( + "bytes" + "image/jpeg" + "image/png" + "net/http" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func WritePng(w http.ResponseWriter, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + buff := bytes.NewBuffer(nil) + png.Encode(buff, img) + w.Header().Set("Content-Type", "image/png") + _, err := w.Write(buff.Bytes()) + return err +} + +func WriteJpg(w http.ResponseWriter, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + buff := bytes.NewBuffer(nil) + jpeg.Encode(buff, img, nil) + w.Header().Set("Content-Type", "image/jpg") + _, err := w.Write(buff.Bytes()) + return err +} diff --git a/app/utils/rand.go b/app/utils/rand.go new file mode 100644 index 0000000..0024fd0 --- /dev/null +++ b/app/utils/rand.go @@ -0,0 +1,31 @@ +package utils + +import ( + crand "crypto/rand" + "fmt" + "math/big" + "math/rand" + "time" +) + +func RandString(l int, c ...string) string { + var ( + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + str string + num *big.Int + ) + if len(c) > 0 { + chars = c[0] + } + chrLen := int64(len(chars)) + for len(str) < l { + num, _ = crand.Int(crand.Reader, big.NewInt(chrLen)) + str += string(chars[num.Int64()]) + } + return str +} + +func RandNum() string { + seed := time.Now().UnixNano() + rand.Int63() + return fmt.Sprintf("%05v", rand.New(rand.NewSource(seed)).Int31n(1000000)) +} diff --git a/app/utils/rsa.go b/app/utils/rsa.go new file mode 100644 index 0000000..fb8274a --- /dev/null +++ b/app/utils/rsa.go @@ -0,0 +1,170 @@ +package utils + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "log" + "os" +) + +// 生成私钥文件 TODO 未指定路径 +func RsaKeyGen(bits int) error { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return err + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + priFile, err := os.Create("private.pem") + if err != nil { + return err + } + err = pem.Encode(priFile, block) + priFile.Close() + if err != nil { + return err + } + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + pubFile, err := os.Create("public.pem") + if err != nil { + return err + } + err = pem.Encode(pubFile, block) + pubFile.Close() + if err != nil { + return err + } + return nil +} + +// 生成私钥文件, 返回 privateKey , publicKey, error +func RsaKeyGenText(bits int) (string, string, error) { // bits 字节位 1024/2048 + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return "", "", err + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + priBuff := bytes.NewBuffer(nil) + err = pem.Encode(priBuff, block) + if err != nil { + return "", "", err + } + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", "", err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + pubBuff := bytes.NewBuffer(nil) + err = pem.Encode(pubBuff, block) + if err != nil { + return "", "", err + } + return priBuff.String(), pubBuff.String(), nil +} + +// 加密 +func RsaEncrypt(rawData, publicKey []byte) ([]byte, error) { + block, _ := pem.Decode(publicKey) + if block == nil { + return nil, errors.New("public key error") + } + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + pub := pubInterface.(*rsa.PublicKey) + return rsa.EncryptPKCS1v15(rand.Reader, pub, rawData) +} + +// 公钥加密 +func RsaEncrypts(data, keyBytes []byte) []byte { + //解密pem格式的公钥 + block, _ := pem.Decode(keyBytes) + if block == nil { + panic(errors.New("public key error")) + } + // 解析公钥 + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic(err) + } + // 类型断言 + pub := pubInterface.(*rsa.PublicKey) + //加密 + ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data) + if err != nil { + panic(err) + } + return ciphertext +} + +// 解密 +func RsaDecrypt(cipherText, privateKey []byte) ([]byte, error) { + block, _ := pem.Decode(privateKey) + if block == nil { + return nil, errors.New("private key error") + } + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.DecryptPKCS1v15(rand.Reader, priv, cipherText) +} + +// 从证书获取公钥 +func OpensslPemGetPublic(pathOrString string) (interface{}, error) { + var certPem []byte + var err error + if IsFile(pathOrString) && Exists(pathOrString) { + certPem, err = ioutil.ReadFile(pathOrString) + if err != nil { + return nil, err + } + if string(certPem) == "" { + return nil, errors.New("empty pem file") + } + } else { + if pathOrString == "" { + return nil, errors.New("empty pem string") + } + certPem = StringToSlice(pathOrString) + } + block, rest := pem.Decode(certPem) + if block == nil || block.Type != "PUBLIC KEY" { + //log.Fatal("failed to decode PEM block containing public key") + return nil, errors.New("failed to decode PEM block containing public key") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Got a %T, with remaining data: %q", pub, rest) + return pub, nil +} diff --git a/app/utils/serialize.go b/app/utils/serialize.go new file mode 100644 index 0000000..1ac4d80 --- /dev/null +++ b/app/utils/serialize.go @@ -0,0 +1,23 @@ +package utils + +import ( + "encoding/json" +) + +func Serialize(data interface{}) []byte { + res, err := json.Marshal(data) + if err != nil { + return []byte{} + } + return res +} + +func Unserialize(b []byte, dst interface{}) { + if err := json.Unmarshal(b, dst); err != nil { + dst = nil + } +} + +func SerializeStr(data interface{}, arg ...interface{}) string { + return string(Serialize(data)) +} diff --git a/app/utils/shuffle.go b/app/utils/shuffle.go new file mode 100644 index 0000000..2c845a8 --- /dev/null +++ b/app/utils/shuffle.go @@ -0,0 +1,48 @@ +package utils + +import ( + "math/rand" + "time" +) + +// 打乱随机字符串 +func ShuffleString(s *string) { + if len(*s) > 1 { + b := []byte(*s) + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(b), func(x, y int) { + b[x], b[y] = b[y], b[x] + }) + *s = string(b) + } +} + +// 打乱随机slice +func ShuffleSliceBytes(b []byte) { + if len(b) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(b), func(x, y int) { + b[x], b[y] = b[y], b[x] + }) + } +} + +// 打乱slice int +func ShuffleSliceInt(i []int) { + if len(i) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(i), func(x, y int) { + i[x], i[y] = i[y], i[x] + }) + } +} + +// 打乱slice interface +func ShuffleSliceInterface(i []interface{}) { + if len(i) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(i), func(x, y int) { + i[x], i[y] = i[y], i[x] + }) + } +} diff --git a/app/utils/sign_check.go b/app/utils/sign_check.go new file mode 100644 index 0000000..798f63d --- /dev/null +++ b/app/utils/sign_check.go @@ -0,0 +1,125 @@ +package utils + +import ( + "applet/app/utils/logx" + "fmt" + "github.com/forgoer/openssl" + "github.com/gin-gonic/gin" + "github.com/syyongx/php2go" + "strings" +) + +var publicKey = []byte(`-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFQD7RL2tDNuwdg0jTfV0zjAzh +WoCWfGrcNiucy2XUHZZU2oGhHv1N10qu3XayTDD4pu4sJ73biKwqR6ZN7IS4Sfon +vrzaXGvrTG4kmdo3XrbrkzmyBHDLTsJvv6pyS2HPl9QPSvKDN0iJ66+KN8QjBpw1 +FNIGe7xbDaJPY733/QIDAQAB +-----END PUBLIC KEY-----`) + +var privateKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCFQD7RL2tDNuwdg0jTfV0zjAzhWoCWfGrcNiucy2XUHZZU2oGh +Hv1N10qu3XayTDD4pu4sJ73biKwqR6ZN7IS4SfonvrzaXGvrTG4kmdo3Xrbrkzmy +BHDLTsJvv6pyS2HPl9QPSvKDN0iJ66+KN8QjBpw1FNIGe7xbDaJPY733/QIDAQAB +AoGADi14wY8XDY7Bbp5yWDZFfV+QW0Xi2qAgSo/k8gjeK8R+I0cgdcEzWF3oz1Q2 +9d+PclVokAAmfj47e0AmXLImqMCSEzi1jDBUFIRoJk9WE1YstE94mrCgV0FW+N/u ++L6OgZcjmF+9dHKprnpaUGQuUV5fF8j0qp8S2Jfs3Sw+dOECQQCQnHALzFjmXXIR +Ez3VSK4ZoYgDIrrpzNst5Hh6AMDNZcG3CrCxlQrgqjgTzBSr3ZSavvkfYRj42STk +TqyX1tQFAkEA6+O6UENoUTk2lG7iO/ta7cdIULnkTGwQqvkgLIUjk6w8E3sBTIfw +rerTEmquw5F42HHE+FMrRat06ZN57lENmQJAYgUHlZevcoZIePZ35Qfcqpbo4Gc8 +Fpm6vwKr/tZf2Vlt0qo2VkhWFS6L0C92m4AX6EQmDHT+Pj7BWNdS+aCuGQJBAOkq +NKPZvWdr8jNOV3mKvxqB/U0uMigIOYGGtvLKt5vkh42J7ILFbHW8w95UbWMKjDUG +X/hF3WQEUo//Imsa2yECQHSZIpJxiTRueoDiyRt0LH+jdbYFUu/6D0UIYXhFvP/p +EZX+hfCfUnNYX59UVpRjSZ66g0CbCjuBPOhmOD+hDeQ= +-----END RSA PRIVATE KEY-----`) + +func GetApiVersion(c *gin.Context) int { + var apiVersion = c.GetHeader("apiVersion") + if StrToInt(apiVersion) == 0 { //没有版本号先不校验 + apiVersion = c.GetHeader("Apiversion") + } + if StrToInt(apiVersion) == 0 { //没有版本号先不校验 + apiVersion = c.GetHeader("api_version") + } + return StrToInt(apiVersion) +} + +//签名校验 +func SignCheck(c *gin.Context) bool { + var apiVersion = GetApiVersion(c) + if apiVersion == 0 { //没有版本号先不校验 + return true + } + //1.通过rsa 解析出 aes + var key = c.GetHeader("key") + + //拼接对应参数 + var uri = c.Request.RequestURI + var query = GetQueryParam(uri) + fmt.Println(query) + query["timestamp"] = c.GetHeader("timestamp") + query["nonce"] = c.GetHeader("nonce") + query["key"] = key + token := c.GetHeader("Authorization") + if token != "" { + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if len(parts) == 2 && parts[0] == "Bearer" { + token = parts[1] + } + } + query["token"] = token + //2.query参数按照 ASCII 码从小到大排序 + str := JoinStringsInASCII(query, "&", false, false, "") + //3.拼上密钥 + secret := "" + if InArr(c.GetHeader("platform"), []string{"android", "ios"}) { + secret = c.GetString("app_api_secret_key") + } else if c.GetHeader("platform") == "wap" { + secret = c.GetString("h5_api_secret_key") + } else { + secret = c.GetString("applet_api_secret_key") + } + str = fmt.Sprintf("%s&secret=%s", str, secret) + fmt.Println(str) + //4.md5加密 转小写 + sign := strings.ToLower(Md5(str)) + //5.判断跟前端传来的sign是否一致 + if sign != c.GetHeader("sign") { + return false + } + return true +} + +func ResultAes(c *gin.Context, raw []byte) string { + var key = c.GetHeader("key") + base, _ := php2go.Base64Decode(key) + aes, err := RsaDecrypt([]byte(base), privateKey) + if err != nil { + logx.Info(err) + return "" + } + + str, _ := openssl.AesECBEncrypt(raw, aes, openssl.PKCS7_PADDING) + value := php2go.Base64Encode(string(str)) + fmt.Println(value) + + return value +} + +func ResultAesDecrypt(c *gin.Context, raw string) string { + var key = c.GetHeader("key") + base, _ := php2go.Base64Decode(key) + aes, err := RsaDecrypt([]byte(base), privateKey) + if err != nil { + logx.Info(err) + return "" + } + fmt.Println(raw) + value1, _ := php2go.Base64Decode(raw) + if value1 == "" { + return "" + } + str1, _ := openssl.AesECBDecrypt([]byte(value1), aes, openssl.PKCS7_PADDING) + + return string(str1) +} diff --git a/app/utils/slice.go b/app/utils/slice.go new file mode 100644 index 0000000..30bd4ee --- /dev/null +++ b/app/utils/slice.go @@ -0,0 +1,26 @@ +package utils + +// ContainsString is 字符串是否包含在字符串切片里 +func ContainsString(array []string, val string) (index int) { + index = -1 + for i := 0; i < len(array); i++ { + if array[i] == val { + index = i + return + } + } + return +} + +func PaginateSliceInt64(x []int64, skip int, size int) []int64 { + if skip > len(x) { + skip = len(x) + } + + end := skip + size + if end > len(x) { + end = len(x) + } + + return x[skip:end] +} diff --git a/app/utils/slice_and_string.go b/app/utils/slice_and_string.go new file mode 100644 index 0000000..3ae6946 --- /dev/null +++ b/app/utils/slice_and_string.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "reflect" + "strings" + "unsafe" +) + +// string与slice互转,零copy省内存 + +// zero copy to change slice to string +func Slice2String(b []byte) (s string) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pString.Data = pBytes.Data + pString.Len = pBytes.Len + return +} + +// no copy to change string to slice +func StringToSlice(s string) (b []byte) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pBytes.Data = pString.Data + pBytes.Len = pString.Len + pBytes.Cap = pString.Len + return +} + +// 任意slice合并 +func SliceJoin(sep string, elems ...interface{}) string { + l := len(elems) + if l == 0 { + return "" + } + if l == 1 { + s := fmt.Sprint(elems[0]) + sLen := len(s) - 1 + if s[0] == '[' && s[sLen] == ']' { + return strings.Replace(s[1:sLen], " ", sep, -1) + } + return s + } + sep = strings.Replace(fmt.Sprint(elems), " ", sep, -1) + return sep[1 : len(sep)-1] +} diff --git a/app/utils/string.go b/app/utils/string.go new file mode 100644 index 0000000..e7142ef --- /dev/null +++ b/app/utils/string.go @@ -0,0 +1,155 @@ +package utils + +import ( + "fmt" + "github.com/syyongx/php2go" + "reflect" + "sort" + "strings" +) + +func Implode(glue string, args ...interface{}) string { + data := make([]string, len(args)) + for i, s := range args { + data[i] = fmt.Sprint(s) + } + return strings.Join(data, glue) +} + +//字符串是否在数组里 +func InArr(target string, str_array []string) bool { + for _, element := range str_array { + if target == element { + return true + } + } + return false +} + +//把数组的值放到key里 +func ArrayColumn(array interface{}, key string) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + t := reflect.TypeOf(array) + v := reflect.ValueOf(array) + if t.Kind() != reflect.Slice { + return nil, nil + } + if v.Len() == 0 { + return nil, nil + } + for i := 0; i < v.Len(); i++ { + indexv := v.Index(i) + if indexv.Type().Kind() != reflect.Struct { + return nil, nil + } + mapKeyInterface := indexv.FieldByName(key) + if mapKeyInterface.Kind() == reflect.Invalid { + return nil, nil + } + mapKeyString, err := InterfaceToString(mapKeyInterface.Interface()) + if err != nil { + return nil, err + } + result[mapKeyString] = indexv.Interface() + } + return result, err +} + +//转string +func InterfaceToString(v interface{}) (result string, err error) { + switch reflect.TypeOf(v).Kind() { + case reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + result = fmt.Sprintf("%v", v) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + result = fmt.Sprintf("%v", v) + case reflect.String: + result = v.(string) + default: + err = nil + } + return result, err +} + +func HideTrueName(name string) string { + res := "**" + if name != "" { + runs := []rune(name) + leng := len(runs) + if leng <= 3 { + res = string(runs[0:1]) + res + } else if leng < 5 { + res = string(runs[0:2]) + res + } else if leng < 10 { + res = string(runs[0:2]) + "***" + string(runs[leng-2:leng]) + } else if leng < 16 { + res = string(runs[0:3]) + "****" + string(runs[leng-3:leng]) + } else { + res = string(runs[0:4]) + "*****" + string(runs[leng-4:leng]) + } + } + return res +} +func GetQueryParam(uri string) map[string]string { + //根据问号分割路由还是query参数 + uriList := strings.Split(uri, "?") + var query = make(map[string]string, 0) + //有参数才处理 + if len(uriList) == 2 { + //分割query参数 + var queryList = strings.Split(uriList[1], "&") + if len(queryList) > 0 { + //key value 分别赋值 + for _, v := range queryList { + var valueList = strings.Split(v, "=") + if len(valueList) == 2 { + value, _ := php2go.URLDecode(valueList[1]) + if value == "" { + value = valueList[1] + } + query[valueList[0]] = value + } + } + } + } + return query +} + +//JoinStringsInASCII 按照规则,参数名ASCII码从小到大排序后拼接 +//data 待拼接的数据 +//sep 连接符 +//onlyValues 是否只包含参数值,true则不包含参数名,否则参数名和参数值均有 +//includeEmpty 是否包含空值,true则包含空值,否则不包含,注意此参数不影响参数名的存在 +//exceptKeys 被排除的参数名,不参与排序及拼接 +func JoinStringsInASCII(data map[string]string, sep string, onlyValues, includeEmpty bool, exceptKeys ...string) string { + var list []string + var keyList []string + m := make(map[string]int) + if len(exceptKeys) > 0 { + for _, except := range exceptKeys { + m[except] = 1 + } + } + for k := range data { + if _, ok := m[k]; ok { + continue + } + value := data[k] + if !includeEmpty && value == "" { + continue + } + if onlyValues { + keyList = append(keyList, k) + } else { + list = append(list, fmt.Sprintf("%s=%s", k, value)) + } + } + if onlyValues { + sort.Strings(keyList) + for _, v := range keyList { + list = append(list, AnyToString(data[v])) + } + } else { + sort.Strings(list) + } + return strings.Join(list, sep) +} diff --git a/app/utils/time.go b/app/utils/time.go new file mode 100644 index 0000000..6860a57 --- /dev/null +++ b/app/utils/time.go @@ -0,0 +1,226 @@ +package utils + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +func StrToTime(s string) (int64, error) { + // delete all not int characters + if s == "" { + return time.Now().Unix(), nil + } + r := make([]rune, 14) + l := 0 + // 过滤除数字以外的字符 + for _, v := range s { + if '0' <= v && v <= '9' { + r[l] = v + l++ + if l == 14 { + break + } + } + } + for l < 14 { + r[l] = '0' // 补0 + l++ + } + t, err := time.Parse("20060102150405", string(r)) + if err != nil { + return 0, err + } + return t.Unix(), nil +} + +func TimeToStr(unixSecTime interface{}, layout ...string) string { + i := AnyToInt64(unixSecTime) + if i == 0 { + return "" + } + f := "2006-01-02 15:04:05" + if len(layout) > 0 { + f = layout[0] + } + return time.Unix(i, 0).Format(f) +} +func Time2String(date time.Time, format string) string { + if format == "" { + format = "2006-01-02 15:04:05" + } + timeS := date.Format(format) + if timeS == "0001-01-01 00:00:00" { + return "" + } + return timeS +} + +func FormatNanoUnix() string { + return strings.Replace(time.Now().Format("20060102150405.0000000"), ".", "", 1) +} + +func TimeParse(format, src string) (time.Time, error) { + return time.ParseInLocation(format, src, time.Local) +} + +func TimeParseStd(src string) time.Time { + t, _ := TimeParse("2006-01-02 15:04:05", src) + return t +} + +func TimeStdParseUnix(src string) int64 { + t, err := TimeParse("2006-01-02 15:04:05", src) + if err != nil { + return 0 + } + return t.Unix() +} + +// 获取一个当前时间 时间间隔 时间戳 +func GetTimeInterval(unit string, amount int) (startTime, endTime int64) { + t := time.Now() + nowTime := t.Unix() + tmpTime := int64(0) + switch unit { + case "years": + tmpTime = time.Date(t.Year()+amount, t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() + case "months": + tmpTime = time.Date(t.Year(), t.Month()+time.Month(amount), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() + case "days": + tmpTime = time.Date(t.Year(), t.Month(), t.Day()+amount, t.Hour(), 0, 0, 0, t.Location()).Unix() + case "hours": + tmpTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour()+amount, 0, 0, 0, t.Location()).Unix() + } + if amount > 0 { + startTime = nowTime + endTime = tmpTime + } else { + startTime = tmpTime + endTime = nowTime + } + return +} + +// 几天前 +func TimeInterval(newTime int) string { + now := time.Now().Unix() + newTime64 := AnyToInt64(newTime) + if newTime64 >= now { + return "刚刚" + } + interval := now - newTime64 + switch { + case interval < 60: + return AnyToString(interval) + "秒前" + case interval < 60*60: + return AnyToString(interval/60) + "分前" + case interval < 60*60*24: + return AnyToString(interval/60/60) + "小时前" + case interval < 60*60*24*30: + return AnyToString(interval/60/60/24) + "天前" + case interval < 60*60*24*30*12: + return AnyToString(interval/60/60/24/30) + "月前" + default: + return AnyToString(interval/60/60/24/30/12) + "年前" + } +} + +// 时分秒字符串转时间戳,传入示例:8:40 or 8:40:10 +func HmsToUnix(str string) (int64, error) { + t := time.Now() + arr := strings.Split(str, ":") + if len(arr) < 2 { + return 0, errors.New("Time format error") + } + h, _ := strconv.Atoi(arr[0]) + m, _ := strconv.Atoi(arr[1]) + s := 0 + if len(arr) == 3 { + s, _ = strconv.Atoi(arr[3]) + } + formatted1 := fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), h, m, s) + res, err := time.ParseInLocation("20060102150405", formatted1, time.Local) + if err != nil { + return 0, err + } else { + return res.Unix(), nil + } +} + +// 获取特定时间范围 +func GetTimeRange(s string) map[string]int64 { + t := time.Now() + var stime, etime time.Time + + switch s { + case "today": + stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "yesterday": + stime = time.Date(t.Year(), t.Month(), t.Day()-1, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + case "within_seven_days": + // 前6天0点 + stime = time.Date(t.Year(), t.Month(), t.Day()-6, 0, 0, 0, 0, t.Location()) + // 明天 0点 + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "current_month": + stime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, t.Location()) + case "last_month": + stime = time.Date(t.Year(), t.Month()-1, 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + } + + return map[string]int64{ + "start": stime.Unix(), + "end": etime.Unix(), + } +} + +// 获取特定时间范围 +func GetDateTimeRangeStr(s string) (string, string) { + t := time.Now() + var stime, etime time.Time + + switch s { + case "today": + stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "yesterday": + stime = time.Date(t.Year(), t.Month(), t.Day()-1, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + case "within_seven_days": + // 前6天0点 + stime = time.Date(t.Year(), t.Month(), t.Day()-6, 0, 0, 0, 0, t.Location()) + // 明天 0点 + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "current_month": + stime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, t.Location()) + case "last_month": + stime = time.Date(t.Year(), t.Month()-1, 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + } + + return stime.Format("2006-01-02 15:04:05"), etime.Format("2006-01-02 15:04:05") +} + +//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。 +func GetFirstDateOfMonth(d time.Time) time.Time { + d = d.AddDate(0, 0, -d.Day()+1) + return GetZeroTime(d) +} + +//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 +func GetLastDateOfMonth(d time.Time) time.Time { + return GetFirstDateOfMonth(d).AddDate(0, 1, -1) +} + +//获取某一天的0点时间 +func GetZeroTime(d time.Time) time.Time { + return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) +} diff --git a/app/utils/url.go b/app/utils/url.go new file mode 100644 index 0000000..f0c7d6b --- /dev/null +++ b/app/utils/url.go @@ -0,0 +1,16 @@ +package utils + +import ( + "net/url" + "strings" +) + +func UriFilterExcludeQueryString(uri string) string { + URL, _ := url.Parse(uri) + + clearUri := strings.ReplaceAll(uri, URL.RawQuery, "") + + clearUri = strings.TrimRight(clearUri, "?") + + return strings.TrimRight(clearUri, "/") +} diff --git a/app/utils/uuid.go b/app/utils/uuid.go new file mode 100644 index 0000000..da7018b --- /dev/null +++ b/app/utils/uuid.go @@ -0,0 +1,76 @@ +package utils + +import ( + "github.com/sony/sonyflake" + + "applet/app/utils/logx" + "fmt" + "math/rand" + "time" +) + +const ( + KC_RAND_KIND_NUM = 0 // 纯数字 + KC_RAND_KIND_LOWER = 1 // 小写字母 + KC_RAND_KIND_UPPER = 2 // 大写字母 + KC_RAND_KIND_ALL = 3 // 数字、大小写字母 +) + +func newUUID() *[16]byte { + u := &[16]byte{} + rand.Read(u[:16]) + u[8] = (u[8] | 0x80) & 0xBf + u[6] = (u[6] | 0x40) & 0x4f + return u +} + +func UUIDString() string { + u := newUUID() + return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} + +func UUIDHexString() string { + u := newUUID() + return fmt.Sprintf("%x%x%x%x%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} +func UUIDBinString() string { + u := newUUID() + return fmt.Sprintf("%s", [16]byte(*u)) +} + +func Krand(size int, kind int) []byte { + ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size) + isAll := kind > 2 || kind < 0 + rand.Seed(time.Now().UnixNano()) + for i := 0; i < size; i++ { + if isAll { // random ikind + ikind = rand.Intn(3) + } + scope, base := kinds[ikind][0], kinds[ikind][1] + result[i] = uint8(base + rand.Intn(scope)) + } + return result +} + +// OrderUUID is only num for uuid +func OrderUUID(uid int) string { + ustr := IntToStr(uid) + tstr := Int64ToStr(time.Now().Unix()) + ulen := len(ustr) + tlen := len(tstr) + rlen := 18 - ulen - tlen + krb := Krand(rlen, KC_RAND_KIND_NUM) + return ustr + tstr + string(krb) +} + +var flake *sonyflake.Sonyflake + +func GenId() int64 { + + id, err := flake.NextID() + if err != nil { + _ = logx.Errorf("flake.NextID() failed with %s\n", err) + panic(err) + } + return int64(id) +} diff --git a/app/utils/validator_err_trans.go b/app/utils/validator_err_trans.go new file mode 100644 index 0000000..29d97bf --- /dev/null +++ b/app/utils/validator_err_trans.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/locales/en" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + enTranslations "github.com/go-playground/validator/v10/translations/en" + chTranslations "github.com/go-playground/validator/v10/translations/zh" + "reflect" +) + +var ValidatorTrans ut.Translator + +// ValidatorTransInit 验证器错误信息翻译初始化 +// local 通常取决于 http 请求头的 'Accept-Language' +func ValidatorTransInit(local string) (err error) { + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + zhT := zh.New() //chinese + enT := en.New() //english + uni := ut.New(enT, zhT, enT) + + var o bool + ValidatorTrans, o = uni.GetTranslator(local) + if !o { + return fmt.Errorf("uni.GetTranslator(%s) failed", local) + } + // 注册一个方法,从自定义标签label中获取值(用在把字段名映射为中文) + v.RegisterTagNameFunc(func(field reflect.StructField) string { + label := field.Tag.Get("label") + if label == "" { + return field.Name + } + return label + }) + // 注册翻译器 + switch local { + case "en": + err = enTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + case "zh": + err = chTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + default: + err = enTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + } + return + } + return +} + +// ValidatorTransInitZh 验证器错误信息翻译为中文初始化 +func ValidatorTransInitZh() (err error) { + return ValidatorTransInit("zh") +} diff --git a/app/utils/wx.go b/app/utils/wx.go new file mode 100644 index 0000000..6967da5 --- /dev/null +++ b/app/utils/wx.go @@ -0,0 +1,31 @@ +package utils + +import ( + "crypto/sha1" + "encoding/hex" + "sort" + "strings" +) + +// CheckSignature 微信公众号签名检查 +func CheckSignature(signature, timestamp, nonce, token string) bool { + arr := []string{timestamp, nonce, token} + // 字典序排序 + sort.Strings(arr) + + n := len(timestamp) + len(nonce) + len(token) + var b strings.Builder + b.Grow(n) + for i := 0; i < len(arr); i++ { + b.WriteString(arr[i]) + } + + return Sha1(b.String()) == signature +} + +// 进行Sha1编码 +func Sha1(str string) string { + h := sha1.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/cmd/task/main.go b/cmd/task/main.go new file mode 100644 index 0000000..6da7e4f --- /dev/null +++ b/cmd/task/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "applet/app/admin/db" + "fmt" + "os" + "os/signal" + "syscall" + + "applet/app/cfg" + "applet/app/task" + "applet/app/utils" + "applet/app/utils/logx" +) + +func init() { + // 加载任务配置 + cfg.InitTaskCfg() + // 日志配置 + cfg.InitLog() + // 初始化redis + cfg.InitCache() + baseDb := *cfg.DB + baseDb.Path = fmt.Sprintf(cfg.DB.Path, cfg.DB.Name) + if err := db.InitDB(&baseDb); err != nil { + panic(err) + } + utils.CurlDebug = true + //cfg.InitMemCache() +} + +func main() { + go func() { + // 初始化jobs方法列表、添加reload方法定时更新任务 + task.Init() + task.Run() + }() + // graceful shutdown + quit := make(chan os.Signal) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + _ = logx.Info("Server exiting...") +} diff --git a/cmd_db.bat b/cmd_db.bat new file mode 100644 index 0000000..af99b67 --- /dev/null +++ b/cmd_db.bat @@ -0,0 +1,25 @@ +@echo off + +set Table=* +set TName="" +set one=%1 + +if "%one%" NEQ "" ( + set Table=%one% + set TName="^%one%$" +) + +set BasePath="./" +set DBUSER="root" +set DBPSW="Fnuo123com@" +set DBNAME="smart_canteen" +set DBHOST="119.23.182.117" +set DBPORT="3306" + +del "app\db\model\%Table%.go" + +echo start reverse table %Table% + +xorm reverse mysql "%DBUSER%:%DBPSW%@tcp(%DBHOST%:%DBPORT%)/%DBNAME%?charset=utf8" %BasePath%/etc/db_tpl %BasePath%/app/db/model/ %TName% + +echo end \ No newline at end of file diff --git a/cmd_db.sh b/cmd_db.sh new file mode 100644 index 0000000..d885aac --- /dev/null +++ b/cmd_db.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 使用方法, 直接执行该脚本更新所有表, cmd_db.sh 表名, 如 ./cmd_db.sh tableName + +Table=* +TName="" +if [ "$1" ] ;then + Table=$1 + TName="^$1$" +fi + +BasePath="./" +DBUSER="root" +DBPSW="Fnuo123com@" +DBNAME="smart_canteen" +DBHOST="119.23.182.117" +DBPORT="3306" + +rm -rf $BasePath/app/db/model/$Table.go && \ + +xorm reverse mysql "$DBUSER:$DBPSW@tcp($DBHOST:$DBPORT)/$DBNAME?charset=utf8" $BasePath/etc/db_tpl $BasePath/app/db/model/ $TName \ No newline at end of file diff --git a/cmd_run.bat b/cmd_run.bat new file mode 100644 index 0000000..51d7b81 --- /dev/null +++ b/cmd_run.bat @@ -0,0 +1,12 @@ +@echo off + +set BasePath=%~dp0 +set APP=applet.exe +set CfgPath=%BasePath%\etc\cfg.yml + +del %BasePath%\bin\%APP% + +go build -o %BasePath%\bin\%APP% %BasePath%\cmd\main.go && %BasePath%\bin\%APP% -c=%CfgPath% + + +pause diff --git a/cmd_run.sh b/cmd_run.sh new file mode 100644 index 0000000..6758f1b --- /dev/null +++ b/cmd_run.sh @@ -0,0 +1,8 @@ +#!/bin/bash +APP=applet +BasePath=$(dirname $(readlink -f $0)) +CfgPath=$BasePath/etc/cfg.yml +cd $BasePath +rm -rf $BasePath/bin/$APP +go build -o $BasePath/bin/$APP $BasePath/main.go \ +&& $BasePath/bin/$APP -c=$CfgPath \ No newline at end of file diff --git a/cmd_task.bat b/cmd_task.bat new file mode 100644 index 0000000..f70eabc --- /dev/null +++ b/cmd_task.bat @@ -0,0 +1,13 @@ +@echo off + +set Name=task +set BasePath=%~dp0 +set APP=%Name%.exe +set CfgPath=%BasePath%etc\%Name%.yml + +del %BasePath%\bin\%APP% + +go build -o %BasePath%\bin\%APP% %BasePath%\cmd\%Name%\main.go && %BasePath%\bin\%APP% -c=%CfgPath% + + +pause diff --git a/cmd_task.sh b/cmd_task.sh new file mode 100644 index 0000000..8f7da10 --- /dev/null +++ b/cmd_task.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +APP=task +BasePath=$(dirname $(readlink -f $0)) +CfgPath=$BasePath/etc/task.yml +cd $BasePath +rm -rf $BasePath/bin/$APP +go build -o $BasePath/bin/$APP $BasePath/cmd/$APP/main.go \ +&& $BasePath/bin/$APP -c=$CfgPath \ No newline at end of file diff --git a/etc/db_tpl/config b/etc/db_tpl/config new file mode 100644 index 0000000..34c75ee --- /dev/null +++ b/etc/db_tpl/config @@ -0,0 +1,7 @@ +lang=go +genJson=1 +prefix=cos_ +ignoreColumnsJSON= +created= +updated= +deleted= \ No newline at end of file diff --git a/etc/db_tpl/struct.go.tpl b/etc/db_tpl/struct.go.tpl new file mode 100644 index 0000000..74b2896 --- /dev/null +++ b/etc/db_tpl/struct.go.tpl @@ -0,0 +1,17 @@ +package {{.Models}} + +{{$ilen := len .Imports}} +{{if gt $ilen 0}} +import ( + {{range .Imports}}"{{.}}"{{end}} +) +{{end}} + +{{range .Tables}} +type {{Mapper .Name}} struct { +{{$table := .}} +{{range .ColumnsSeq}}{{$col := $table.GetColumn .}} {{Mapper $col.Name}} {{Type $col}} {{Tag $table $col}} +{{end}} +} +{{end}} + diff --git a/etc/task.yml b/etc/task.yml new file mode 100644 index 0000000..449483c --- /dev/null +++ b/etc/task.yml @@ -0,0 +1,30 @@ +# debug release test +debug: true +prd: false +local: true +# 缓存 +redis_addr: '120.24.28.6:32572' + +# 数据库 +db: + host: '119.23.182.117:3306' + name: 'zyos_website' + user: 'root' + psw: 'Fnuo123com@' + show_log: true + max_lifetime: 30 + max_open_conns: 100 + max_idle_conns: 100 + path: 'tmp/task_sql_%v.log' + +# 日志 +log: + level: 'debug' # 普通日志级别 #debug, info, warn, fatal, panic + is_stdout: true + time_format: 'standard' # sec, second, milli, nano, standard, iso + encoding: 'console' + is_file_out: true + file_dir: './tmp/' + file_max_size: 256 + file_max_age: 1 + file_name: 'task.log' diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..09a1263 --- /dev/null +++ b/go.mod @@ -0,0 +1,37 @@ +module applet + +go 1.15 + +require ( + github.com/boombuler/barcode v1.0.1 + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/forgoer/openssl v0.0.0-20201023062029-c3112b0c8700 + github.com/gin-gonic/gin v1.6.3 + github.com/go-playground/locales v0.13.0 + github.com/go-playground/universal-translator v0.17.0 + github.com/go-playground/validator/v10 v10.4.2 + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-sql-driver/mysql v1.7.1 + github.com/golang/protobuf v1.5.2 // indirect + github.com/gomodule/redigo v2.0.0+incompatible + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/makiuchi-d/gozxing v0.0.0-20210324052758-57132e828831 + github.com/mcuadros/go-defaults v1.2.0 + github.com/onsi/ginkgo v1.15.0 // indirect + github.com/onsi/gomega v1.10.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/qiniu/api.v7/v7 v7.8.2 + github.com/robfig/cron/v3 v3.0.1 + github.com/sony/sonyflake v1.0.0 + github.com/syyongx/php2go v0.9.4 + github.com/ugorji/go v1.2.5 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.16.0 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect + golang.org/x/net v0.7.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 + gopkg.in/yaml.v2 v2.4.0 + honnef.co/go/tools v0.0.1-2020.1.4 // indirect + xorm.io/xorm v1.3.2 +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..e5d7246 --- /dev/null +++ b/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "applet/app/db" + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "applet/app/cfg" + "applet/app/router" +) + +//系统初始化 +func init() { + cfg.InitCfg() //配置初始化 + cfg.InitLog() //日志初始化 + cfg.InitCache() //缓存初始化 + if cfg.Debug { //判断是否是debug + if err := db.InitDB(cfg.DB); err != nil { //主数据库初始化 + panic(err) + } + } + fmt.Println("init success") + +} + +func main() { + r := router.Init() //创建路由 + + srv := &http.Server{ //设置http服务参数 + Addr: cfg.SrvAddr, //指定ip和端口 + Handler: r, //指定路由 + } + + go func() { //协程启动监听http服务 + fmt.Println("Listening and serving HTTP on " + cfg.SrvAddr) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }() + + //退出go守护进程 + quit := make(chan os.Signal) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutting down server...") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server forced to shutdown:", err) + } + log.Println("Server exiting") + +} diff --git a/static/html/LandingPage.html b/static/html/LandingPage.html new file mode 100644 index 0000000..313f321 --- /dev/null +++ b/static/html/LandingPage.html @@ -0,0 +1,179 @@ + + + + + + Landing Page + + + + + + + +
+
+ PullNew +
+ 恭喜您获得 +
+
+ 现金红包 +
+ ImgBtn + +
+
+ + + + \ No newline at end of file diff --git a/static/html/article.html b/static/html/article.html new file mode 100644 index 0000000..ad76ce5 --- /dev/null +++ b/static/html/article.html @@ -0,0 +1,95 @@ + + + + + + + + {{.title}} + + + + + + +
+
+

{{.title}}

+
+
+

{{.author}}

+ {{.time}} +
+ +
+ {{.studyCount}}人已学习 +
+
+
+
+
+ + + + diff --git a/static/html/kz_goods_share.html b/static/html/kz_goods_share.html new file mode 100644 index 0000000..348bddf --- /dev/null +++ b/static/html/kz_goods_share.html @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + + 好货 + + + + 下载APP + + + + + +
+ +
+ + + +
+ + + + + + + +
+ +

{{.goodTitle}}

+ +
+ +

+ + + + 券后价 + 价格 + + + + + ¥{{.goodPrice}}¥{{.goodCostPrice}} + +

+ + + +
+ + + +

{{.goodCouponPrice}}元券

+ +
+ +
+ +
+ +
+ + + +
{{.tkl}}
+ + + +
+ + 一键复制 + +
+ + + +
+ + 长按复制上方口令,打开手机【】购买 + +
+ +
\ No newline at end of file diff --git a/static/image/rbac.png b/static/image/rbac.png new file mode 100644 index 0000000000000000000000000000000000000000..28b1a337901cb611f7d4e76d402d7fd1cd0d3fea GIT binary patch literal 119311 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