feat(park):基本完成园区功能接口的迁移。

This commit is contained in:
徐涛 2023-06-03 22:48:33 +08:00
parent 98f3bdec0a
commit c22e7e7dc0
9 changed files with 1041 additions and 0 deletions

373
controller/park.go Normal file
View File

@ -0,0 +1,373 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/vo"
"net/http"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
var parkLog = logger.Named("Handler", "Park")
func InitializeParkHandlers(router *fiber.App) {
router.Get("/park", security.EnterpriseAuthorize, listParksBelongsToCurrentUser)
router.Post("/park", security.EnterpriseAuthorize, createPark)
router.Get("/park/belongs/:uid", security.OPSAuthorize, listParksBelongsTo)
router.Get("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail)
router.Put("/park/:pid", security.EnterpriseAuthorize, modifySpecificPark)
router.Delete("/park/:pid", security.EnterpriseAuthorize, deleteSpecificPark)
router.Put("/park/:pid/enabled", security.EnterpriseAuthorize, modifyParkEnabling)
router.Get("/park/:pid/building", security.EnterpriseAuthorize, listBuildingsBelongsToPark)
router.Post("/park/:pid/building", security.EnterpriseAuthorize, createBuildingInPark)
router.Put("/park/:pid/building/:bid", security.EnterpriseAuthorize, modifySpecificBuildingInPark)
router.Delete("/park/:pid/building/:bid", security.EnterpriseAuthorize, deletedParkBuilding)
router.Put("/park/:pid/building/:bid/enabled", security.EnterpriseAuthorize, modifyParkBuildingEnabling)
}
// 列出隶属于当前用户的全部园区
func listParksBelongsToCurrentUser(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("列出当前用的全部园区,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
parkLog.Info("列出当前用户下的全部园区", zap.String("user id", session.Uid))
parks, err := repository.ParkRepository.RetrieveParkbelongs(session.Uid)
if err != nil {
parkLog.Error("无法获取园区列表。", zap.String("user id", session.Uid))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已获取到指定用户的下的园区", fiber.Map{"parks": parks})
}
// 列出隶属于指定用户的全部园区
func listParksBelongsTo(c *fiber.Ctx) error {
result := response.NewResult(c)
userId := c.Params("userId")
parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId))
parks, err := repository.ParkRepository.RetrieveParkbelongs(userId)
if err != nil {
parkLog.Error("无法获取园区列表。", zap.String("user id", userId))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已获取到指定用户的下的园区", fiber.Map{"parks": parks})
}
// 获取指定园区的详细信息
func fetchParkDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
parkLog.Info("获取指定园区的详细信息", zap.String("park id", parkId))
park, err := repository.ParkRepository.RetrieveParkDetail(parkId)
if err != nil {
parkLog.Error("无法获取园区信息。", zap.String("park id", parkId))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已获取到指定园区的详细信息", fiber.Map{"park": park})
}
// 创建一个新的园区
func createPark(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("创建一个新的园区,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
parkLog.Info("创建一个新的园区", zap.String("user id", session.Uid))
creationForm := new(vo.ParkInformationForm)
if err := c.BodyParser(creationForm); err != nil {
parkLog.Error("无法解析园区表单数据。", zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
park, err := creationForm.TryIntoPark()
if err != nil {
parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := repository.ParkRepository.CreatePark(session.Uid, park)
switch {
case err == nil && !ok:
parkLog.Error("无法创建新的园区。", zap.String("user id", session.Uid))
return result.NotAccept("无法创建新的园区。")
case err != nil:
parkLog.Error("无法创建新的园区。", zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("已创建一个新的园区")
}
// 修改指定园区的信息
func modifySpecificPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("修改指定园区的信息,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
parkForm := new(vo.ParkInformationForm)
if err := c.BodyParser(parkForm); err != nil {
parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
park, err := parkForm.TryIntoPark()
if err != nil {
parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err = repository.ParkRepository.UpdatePark(parkId, park)
switch {
case err == nil && !ok:
parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新园区信息。")
case err != nil:
parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已获取到指定园区的详细信息")
}
// 修改指定园区的可用性
func modifyParkEnabling(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("修改指定园区的可用性,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
stateForm := new(vo.StateForm)
if err := c.BodyParser(stateForm); err != nil {
parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err = repository.ParkRepository.EnablingPark(parkId, stateForm.Enabled)
switch {
case err == nil && !ok:
parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新园区可用性。")
case err != nil:
parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已获取到指定园区的详细信息")
}
// 删除指定的园区
func deleteSpecificPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("删除指定的园区,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
ok, err = repository.ParkRepository.DeletePark(parkId)
switch {
case err == nil && !ok:
parkLog.Error("无法删除园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法删除园区。")
case err != nil:
parkLog.Error("无法删除园区。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Deleted("已删除指定的园区")
}
// 列出指定园区中已经登记的建筑
func listBuildingsBelongsToPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("列出指定园区中已经登记的建筑,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
buildings, err := repository.ParkRepository.RetrieveParkBuildings(parkId)
if err != nil {
parkLog.Error("无法获取园区中的建筑列表。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已获取到指定园区中的建筑列表", fiber.Map{"buildings": buildings})
}
// 在指定园区中创建一个新的建筑
func createBuildingInPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("在指定园区中创建一个新的建筑,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
buildingForm := new(vo.ParkBuildingInformationForm)
if err := c.BodyParser(buildingForm); err != nil {
parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err = repository.ParkRepository.CreateParkBuilding(parkId, buildingForm.Name, &buildingForm.Floors)
switch {
case err == nil && !ok:
parkLog.Error("无法创建新的建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法创建新的建筑。")
case err != nil:
parkLog.Error("无法创建新的建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("已创建一个新的建筑")
}
// 修改指定园区中的指定建筑的信息
func modifySpecificBuildingInPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
buildingId := c.Params("bid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("修改指定园区中的指定建筑的信息,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
buildingForm := new(vo.ParkBuildingInformationForm)
if err := c.BodyParser(buildingForm); err != nil {
parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err = repository.ParkRepository.ModifyParkBuilding(buildingId, parkId, buildingForm.Name, &buildingForm.Floors)
switch {
case err == nil && !ok:
parkLog.Error("无法更新建筑信息。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新建筑信息。")
case err != nil:
parkLog.Error("无法更新建筑信息。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已更新指定建筑的信息")
}
// 修改指定园区中指定建筑的可用性
func modifyParkBuildingEnabling(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
buildingId := c.Params("bid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("修改指定园区中指定建筑的可用性,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
stateForm := new(vo.StateForm)
if err := c.BodyParser(stateForm); err != nil {
parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err = repository.ParkRepository.EnablingParkBuilding(buildingId, parkId, stateForm.Enabled)
switch {
case err == nil && !ok:
parkLog.Error("无法更新建筑可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新建筑可用性。")
case err != nil:
parkLog.Error("无法更新建筑可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已更新指定建筑的可用性")
}
// 删除指定园区中的指定建筑
func deletedParkBuilding(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
buildingId := c.Params("bid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("删除指定园区中的指定建筑,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
ok, err = repository.ParkRepository.DeleteParkBuilding(buildingId, parkId)
switch {
case err == nil && !ok:
parkLog.Error("无法删除建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法删除建筑。")
case err != nil:
parkLog.Error("无法删除建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Deleted("已删除指定的建筑")
}

51
model/enums.go Normal file
View File

@ -0,0 +1,51 @@
package model
const (
ELECTRICITY_CATE_TWO_PART int16 = iota
ELECTRICITY_CATE_UNITARY_PV
ELECTRICITY_CATE_FULL_PV
)
const (
METER_TYPE_UNITARY int16 = iota
METER_TYPE_PV
)
const (
METER_INSTALLATION_TENEMENT int16 = iota
METER_INSTALLATION_PARK
METER_INSTALLATION_POOLING
)
const (
PRICING_POLICY_CONSUMPTION int16 = iota
PRICING_POLICY_ALL
)
const (
POOLING_MODE_NONE int16 = iota
POOLING_MODE_CONSUMPTION
POOLING_MODE_AREA
)
const (
PAYMENT_CASH int16 = iota
PAYMENT_BANK_CARD
PAYMENT_ALIPAY
PAYMENT_WECHAT
PAYMENT_UNION_PAY
PAYMENT_OTHER int16 = 99
)
const (
METER_TELEMETER_HYBRID int16 = iota
METER_TELEMETER_AUTOMATIC
METER_TELEMETER_MANUAL
)
const (
RETRY_INTERVAL_ALGORITHM_EXPONENTIAL_BACKOFF int16 = iota
RETRY_INTERVAL_ALGORITHM_DOUBLE_LINEAR_BACKOFF
RETRY_INTERVAL_ALGORITHM_TRIPLE_LINEAR_BACKOFF
RETRY_INTERVAL_ALGORITHM_FIXED
)

32
model/park.go Normal file
View File

@ -0,0 +1,32 @@
package model
import (
"time"
"github.com/shopspring/decimal"
)
type Park struct {
Id string `json:"id"`
UserId string `json:"userId"`
Name string `json:"name"`
Area decimal.NullDecimal `json:"area"`
TenementQuantity decimal.NullDecimal `json:"tenementQuantity"`
Capacity decimal.NullDecimal `json:"capacity"`
Category int16 `json:"category"`
MeterType int16 `json:"meter04kvType"`
PricePolicy int16 `json:"pricePolicy"`
BasicPooled int16 `json:"basicPooled"`
AdjustPooled int16 `json:"adjustPooled"`
LossPooled int16 `json:"lossPooled"`
PublicPooled int16 `json:"publicPooled"`
TaxRate decimal.NullDecimal `json:"taxRate"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"createdAt"`
LastModifiedAt time.Time `json:"lastModifiedAt"`
DeletedAt *time.Time `json:"deletedAt"`
}

14
model/park_building.go Normal file
View File

@ -0,0 +1,14 @@
package model
import "time"
type ParkBuilding struct {
Id string `json:"id"`
Park string `json:"parkId" db:"park_id"`
Name string `json:"name"`
Floors *string `json:"floors"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"createdAt"`
LastModifiedAt time.Time `json:"lastModifiedAt"`
DeletedAt *time.Time `json:"deletedAt"`
}

473
repository/park.go Normal file
View File

@ -0,0 +1,473 @@
package repository
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/tools/serial"
"electricity_bill_calc/tools/time"
"fmt"
"strings"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _ParkRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ParkRepository = _ParkRepository{
log: logger.Named("Repository", "Park"),
ds: goqu.Dialect("postgres"),
}
// 列出指定用户下的所有园区
func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) {
pr.log.Info("列出指定用户下的所有园区", zap.String("uid", uid))
cacheConditions := []string{
uid,
}
if parks, err := cache.RetrieveSearch[[]*model.Park]("park_belongs", cacheConditions...); err == nil && parks != nil {
pr.log.Info("已经从缓存获取到了指定用户下的所有园区。")
return *parks, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var parks []*model.Park
parkQuerySql, parkParams, _ := pr.ds.
From("park").
Where(
goqu.I("user_id").Eq(uid),
goqu.I("deleted_at").IsNull(),
).
Order(goqu.I("created_at").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &parks, parkQuerySql, parkParams...); err != nil {
pr.log.Error("列出指定用户下的所有园区失败!", zap.Error(err))
return make([]*model.Park, 0), err
}
cache.CacheSearch(parks, []string{"park", fmt.Sprintf("park:%s", uid)}, "park_belongs", cacheConditions...)
return parks, nil
}
// 检查并确定指定园区的归属情况
func (pr _ParkRepository) IsParkBelongs(pid, uid string) (bool, error) {
pr.log.Info("检查并确定指定园区的归属情况", zap.String("pid", pid), zap.String("uid", uid))
cacheConditions := []string{
pid, "belongs", uid,
}
if exists, err := cache.CheckExists("park", cacheConditions...); err == nil && exists {
pr.log.Info("已经从缓存获取到了指定园区的归属情况。")
return true, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var count int64
parkQuerySql, parkParams, _ := pr.ds.
From("park").
Select(goqu.COUNT("*")).
Where(
goqu.I("id").Eq(pid),
goqu.I("user_id").Eq(uid),
).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &count, parkQuerySql, parkParams...); err != nil {
pr.log.Error("检查并确定指定园区的归属情况失败!", zap.Error(err))
return false, err
}
if count > 0 {
cache.CacheExists([]string{"park", fmt.Sprintf("park:%s", uid)}, "park", cacheConditions...)
}
return count > 0, nil
}
// 创建一个属于指定用户的新园区。该创建功能不会对园区的名称进行检查。
func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, error) {
pr.log.Info("创建一个属于指定用户的新园区", zap.String("ownerId", ownerId))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
createSql, createArgs, _ := pr.ds.
Insert("park").
Cols(
"id", "user_id", "name", "abbr", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
serial.GeneratePrefixedUniqueSerialString("P"),
ownerId, park.Name, tools.PinyinAbbr(park.Name),
park.Area, park.TenementQuantity, park.Capacity, park.Category,
park.MeterType, park.Region, park.Address, park.Contact, park.Phone, park.Enabled, park.PricePolicy, park.TaxRate,
park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("创建一个属于指定用户的新园区失败!", zap.Error(err))
return false, err
}
cache.AbolishRelation("park")
return rs.RowsAffected() > 0, nil
}
// 获取指定园区的详细信息
func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) {
pr.log.Info("获取指定园区的详细信息", zap.String("pid", pid))
if park, err := cache.RetrieveEntity[model.Park]("park", pid); err == nil && park != nil {
pr.log.Info("已经从缓存获取到了指定园区的详细信息。")
return park, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var park model.Park
parkSql, parkArgs, _ := pr.ds.
From("park").
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &park, parkSql, parkArgs...); err != nil {
pr.log.Error("获取指定园区的详细信息失败!", zap.Error(err))
return nil, err
}
cache.CacheEntity(park, []string{"park", fmt.Sprintf("park:%s", pid)}, "park", pid)
return &park, nil
}
// 获取园区对应的用户ID
func (pr _ParkRepository) RetrieveParkbelongs(pid string) (string, error) {
pr.log.Info("获取园区对应的用户ID", zap.String("pid", pid))
if uid, err := cache.RetrieveEntity[string]("park_belongs", pid); err == nil && uid != nil {
pr.log.Info("已经从缓存获取到了园区对应的用户ID。")
return *uid, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var uid string
parkSql, parkArgs, _ := pr.ds.
From("park").
Select(goqu.I("user_id")).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &uid, parkSql, parkArgs...); err != nil {
pr.log.Error("获取园区对应的用户ID失败", zap.Error(err))
return "", err
}
cache.CacheEntity(uid, []string{"park", fmt.Sprintf("park:%s", pid)}, "park_belongs", pid)
return uid, nil
}
// 更新指定园区的信息
func (pr _ParkRepository) UpdatePark(pid string, park *model.Park) (bool, error) {
pr.log.Info("更新指定园区的信息", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"name": park.Name,
"abbr": tools.PinyinAbbr(park.Name),
"area": park.Area,
"tenement_quantity": park.TenementQuantity,
"capacity": park.Capacity,
"category": park.Category,
"meter_04kv_type": park.MeterType,
"region": park.Region,
"address": park.Address,
"contact": park.Contact,
"phone": park.Phone,
"price_policy": park.PricePolicy,
"tax_rate": park.TaxRate,
"basic_pooled": park.BasicPooled,
"adjust_pooled": park.AdjustPooled,
"loss_pooled": park.LossPooled,
"public_pooled": park.PublicPooled,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("更新指定园区的信息失败!", zap.Error(err))
return false, err
}
if ok.RowsAffected() > 0 {
cache.AbolishRelation("park")
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
}
return ok.RowsAffected() > 0, nil
}
// 设定园区的可用状态
func (pr _ParkRepository) EnablingPark(pid string, enabled bool) (bool, error) {
pr.log.Info("设定园区的可用状态", zap.String("pid", pid), zap.Bool("enabled", enabled))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"enabled": enabled,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("设定园区的可用状态失败!", zap.Error(err))
return false, err
}
if ok.RowsAffected() > 0 {
cache.AbolishRelation("park")
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
}
return ok.RowsAffected() > 0, nil
}
// 删除指定园区(软删除)
func (pr _ParkRepository) DeletePark(pid string) (bool, error) {
pr.log.Info("删除指定园区(软删除)", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"deleted_at": timeNow,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("删除指定园区(软删除)失败!", zap.Error(err))
return false, err
}
if ok.RowsAffected() > 0 {
cache.AbolishRelation("park")
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
}
return ok.RowsAffected() > 0, nil
}
// 检索给定的园区详细信息列表
func (pr _ParkRepository) RetrieveParks(pids []string) ([]*model.Park, error) {
pr.log.Info("检索给定的园区详细信息列表", zap.Strings("pids", pids))
if len(pids) == 0 {
pr.log.Info("给定要检索的园区ID列表为空执行快速返回。")
return make([]*model.Park, 0), nil
}
cacheConditions := []string{strings.Join(pids, ",")}
if parks, err := cache.RetrieveSearch[[]*model.Park]("park", cacheConditions...); err == nil && parks != nil {
pr.log.Info("已经从缓存获取到了给定的园区详细信息列表。")
return *parks, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var parks []*model.Park
parkSql, parkArgs, _ := pr.ds.
From("park").
Where(goqu.I("id").In(pids)).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &parks, parkSql, parkArgs...); err != nil {
pr.log.Error("检索给定的园区详细信息列表失败!", zap.Error(err))
return nil, err
}
cache.CacheSearch(parks, []string{"park", fmt.Sprintf("park:%s", strings.Join(pids, ":"))}, "park", cacheConditions...)
return parks, nil
}
// 获取指定园区中的建筑
func (pr _ParkRepository) RetrieveParkBuildings(pid string) ([]*model.ParkBuilding, error) {
pr.log.Info("获取指定园区中的建筑", zap.String("pid", pid))
if buildings, err := cache.RetrieveSearch[[]*model.ParkBuilding]("park_building", pid); err == nil && buildings != nil {
pr.log.Info("已经从缓存获取到了指定园区中的建筑。")
return *buildings, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var buildings []*model.ParkBuilding
buildingSql, buildingArgs, _ := pr.ds.
From("park_building").
Where(
goqu.I("park_id").Eq(pid),
goqu.I("deleted_at").IsNull(),
).
Order(goqu.I("created_at").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &buildings, buildingSql, buildingArgs...); err != nil {
pr.log.Error("获取指定园区中的建筑失败!", zap.Error(err))
return nil, err
}
cache.CacheSearch(buildings, []string{"park_building", fmt.Sprintf("park_building:%s", pid)}, "park_building", pid)
return buildings, nil
}
// 在指定园区中创建一个新建筑
func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (bool, error) {
pr.log.Info("在指定园区中创建一个新建筑", zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
createSql, createArgs, _ := pr.ds.
Insert("park_building").
Cols(
"id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
serial.GeneratePrefixedUniqueSerialString("B"),
pid, name, floor, true, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("在指定园区中创建一个新建筑失败!", zap.Error(err))
return false, err
}
if rs.RowsAffected() > 0 {
cache.AbolishRelation("park_building")
cache.AbolishRelation(fmt.Sprintf("park_building:%s", pid))
}
return rs.RowsAffected() > 0, nil
}
// 修改指定园区中指定建筑的信息
func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string) (bool, error) {
pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"name": name,
"floors": floor,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("修改指定园区中指定建筑的信息失败!", zap.Error(err))
return false, err
}
if rs.RowsAffected() > 0 {
cache.AbolishRelation("park_building")
cache.AbolishRelation(fmt.Sprintf("park_building:%s", pid))
}
return rs.RowsAffected() > 0, nil
}
// 修改指定建筑的可以状态
func (pr _ParkRepository) EnablingParkBuilding(id, pid string, enabled bool) (bool, error) {
pr.log.Info("修改指定建筑的可以状态", zap.String("id", id), zap.String("pid", pid), zap.Bool("enabled", enabled))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"enabled": enabled,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("修改指定建筑的可以状态失败!", zap.Error(err))
return false, err
}
if rs.RowsAffected() > 0 {
cache.AbolishRelation("park_building")
cache.AbolishRelation(fmt.Sprintf("park_building:%s", pid))
}
return rs.RowsAffected() > 0, nil
}
// 删除指定建筑(软删除)
func (pr _ParkRepository) DeleteParkBuilding(id, pid string) (bool, error) {
pr.log.Info("删除指定建筑(软删除)", zap.String("id", id), zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := time.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"deleted_at": timeNow,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("删除指定建筑(软删除)失败!", zap.Error(err))
return false, err
}
if rs.RowsAffected() > 0 {
cache.AbolishRelation("park_building")
cache.AbolishRelation(fmt.Sprintf("park_building:%s", pid))
}
return rs.RowsAffected() > 0, nil
}

View File

@ -47,6 +47,7 @@ func App() *fiber.App {
controller.InitializeUserHandlers(app)
controller.InitializeRegionHandlers(app)
controller.InitializeChargeHandlers(app)
controller.InitializeParkHandlers(app)
return app
}

View File

@ -7,6 +7,7 @@ import (
"github.com/mozillazg/go-pinyin"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func ContainsInsensitive(element string, slice []string) bool {
@ -86,3 +87,27 @@ func CondFn[T, R any](exprFn func(val T) bool, value T, trueValue, falseValue R)
func CondOr[T any](exprFn func(val T) bool, value, elseValue T) T {
return CondFn(exprFn, value, value, elseValue)
}
// 将指定的字符串指针解析为一个可空的`decimal.NullDecimal`类型的值。
func NewNullDecimalFromString(val *string) (decimal.NullDecimal, error) {
if val == nil {
return decimal.NullDecimal{Valid: false}, nil
}
nd, err := decimal.NewFromString(*val)
if err != nil {
return decimal.NullDecimal{Valid: false}, err
}
return decimal.NullDecimal{Decimal: nd, Valid: true}, nil
}
// 将指定的字符串指针解析为一个`decimal.Decimal`类型的值,必须提供一个默认值,以用来替换解析失败以及空指针的情况。
func NewDecimalFromString(val *string, defaultValue decimal.Decimal) decimal.Decimal {
if val == nil {
return defaultValue
}
nd, err := decimal.NewFromString(*val)
if err != nil {
return defaultValue
}
return nd
}

67
vo/park.go Normal file
View File

@ -0,0 +1,67 @@
package vo
import (
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
)
type ParkInformationForm struct {
Name string `json:"name"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Area *string `json:"area"`
Capacity *string `json:"capacity"`
TenementQuantity *string `json:"tenement"`
TaxRate *string `json:"taxRate"`
Category int16 `json:"category"`
MeterType int16 `json:"submeter"`
PricePolicy int16 `json:"pricePolicy"`
BasicPooled int16 `json:"basicDiluted"`
AdjustPooled int16 `json:"adjustDiluted"`
LossPooled int16 `json:"lossDiluted"`
PublicPooled int16 `json:"publicDiluted"`
}
func (pcf ParkInformationForm) TryIntoPark() (*model.Park, error) {
area, err := tools.NewNullDecimalFromString(pcf.Area)
if err != nil {
return nil, err
}
tenementQuantity, err := tools.NewNullDecimalFromString(pcf.TenementQuantity)
if err != nil {
return nil, err
}
capacity, err := tools.NewNullDecimalFromString(pcf.Capacity)
if err != nil {
return nil, err
}
taxRate, err := tools.NewNullDecimalFromString(pcf.TaxRate)
if err != nil {
return nil, err
}
return &model.Park{
Name: pcf.Name,
Region: pcf.Region,
Address: pcf.Address,
Contact: pcf.Contact,
Phone: pcf.Phone,
Area: area,
TenementQuantity: tenementQuantity,
Capacity: capacity,
Category: pcf.Category,
MeterType: pcf.MeterType,
PricePolicy: pcf.PricePolicy,
BasicPooled: pcf.BasicPooled,
AdjustPooled: pcf.AdjustPooled,
LossPooled: pcf.LossPooled,
PublicPooled: pcf.PublicPooled,
TaxRate: taxRate,
}, nil
}
type ParkBuildingInformationForm struct {
Name string `json:"name"`
Floors string `json:"floors"`
}

5
vo/shares.go Normal file
View File

@ -0,0 +1,5 @@
package vo
type StateForm struct {
Enabled bool `json:"enabled"`
}