gen_entities.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # -*- coding: utf-8 -*-
  2. """按数据库表结构生成 Java 实体 + Mapper 骨架。
  3. 生成规则与项目既有实体保持一致:
  4. - 表名为全名(MyBatis-Plus 的 table-prefix 对显式 @TableName 无效)
  5. - int/tinyint -> Integer,bigint -> Long,decimal -> BigDecimal(ToStringSerializer,PHP 输出字符串)
  6. - double -> Double(PhpFloatSerializer,对齐 PHP 1.0 -> 1)
  7. - create_time / update_time -> @JsonIgnore Integer + 输出格式化字符串的 getter
  8. - is_delete -> @TableLogic(value="0", delval="1")(本库统一 0/1)
  9. """
  10. import re
  11. import sys
  12. import pymysql
  13. from pathlib import Path
  14. from _root import ROOT
  15. YML = str(ROOT / "yoshop-java/src/main/resources/application.yml")
  16. ROOT = Path(str(ROOT / "yoshop-java/src/main/java/cn/yoshop"))
  17. pwd = re.findall(r"password:\s*(.+)", open(YML, encoding="utf-8").read())[0].strip().strip("\"'")
  18. # 表名 -> (类名, 说明, 需转义的列)
  19. TABLES = {
  20. "yoshop_category": ("Category", "商品分类"),
  21. "yoshop_goods_service": ("GoodsService", "商品服务(保障服务)"),
  22. "yoshop_upload_group": ("UploadGroup", "文件库分组"),
  23. "yoshop_region": ("Region", "地区(省/市/区)"),
  24. "yoshop_user": ("User", "商城会员"),
  25. "yoshop_user_grade": ("UserGrade", "会员等级"),
  26. "yoshop_page": ("PageEntity", "页面(首页/自定义页)"),
  27. "yoshop_article_category": ("ArticleCategory", "文章分类"),
  28. "yoshop_article": ("Article", "文章"),
  29. "yoshop_help": ("Help", "帮助"),
  30. "yoshop_coupon": ("Coupon", "优惠券"),
  31. "yoshop_delivery": ("Delivery", "配送模板"),
  32. "yoshop_express": ("Express", "物流公司"),
  33. "yoshop_printer": ("Printer", "打印机"),
  34. "yoshop_store_address": ("StoreAddress", "商城地址(发货/退货)"),
  35. "yoshop_store_menu": ("StoreMenu", "后台菜单"),
  36. "yoshop_store_role": ("StoreRole", "角色"),
  37. "yoshop_store_role_menu": ("StoreRoleMenu", "角色-菜单关联"),
  38. "yoshop_store_user_role": ("StoreUserRole", "管理员-角色关联"),
  39. "yoshop_store_setting": ("StoreSetting", "商城设置项"),
  40. "yoshop_comment": ("Comment", "商品评价"),
  41. "yoshop_order": ("Order", "订单"),
  42. "yoshop_order_goods": ("OrderGoods", "订单商品"),
  43. "yoshop_order_address": ("OrderAddress", "订单收货地址"),
  44. "yoshop_payment_trade": ("PaymentTrade", "第三方支付交易"),
  45. "yoshop_order_delivery": ("OrderDelivery", "订单发货单"),
  46. "yoshop_user_balance_log": ("UserBalanceLog", "余额变动记录"),
  47. "yoshop_recharge_plan": ("RechargePlan", "充值套餐"),
  48. "yoshop_order_refund": ("OrderRefund", "订单退款单"),
  49. "yoshop_delivery_rule": ("DeliveryRule", "配送模板规则"),
  50. }
  51. RESERVED = {"describe", "values", "key", "interval", "condition", "order", "group", "system"}
  52. INT_TYPES = ("int", "tinyint", "smallint", "mediumint")
  53. def camel(name, upper_first=False):
  54. parts = name.split("_")
  55. out = "".join(p[:1].upper() + p[1:] for p in parts)
  56. if not upper_first:
  57. out = out[:1].lower() + out[1:]
  58. return out
  59. def java_type(col_type):
  60. t = col_type.lower()
  61. # 形如 "int(10) unsigned" -> "int";"decimal(10,2) unsigned" -> "decimal"
  62. base = t.split("(")[0].split()[0]
  63. if base == "bigint":
  64. return "Long"
  65. if base in INT_TYPES:
  66. return "Integer"
  67. if base == "decimal":
  68. return "BigDecimal"
  69. if base in ("double", "float"):
  70. return "Double"
  71. return "String"
  72. def gen_entity(table, cls, comment, cols):
  73. pk = None
  74. body = []
  75. imports = {
  76. "import com.baomidou.mybatisplus.annotation.IdType;",
  77. "import com.baomidou.mybatisplus.annotation.TableId;",
  78. "import com.baomidou.mybatisplus.annotation.TableName;",
  79. "import lombok.Data;",
  80. }
  81. if any(c[1].lower().startswith("decimal") for c in cols):
  82. imports.add("import com.fasterxml.jackson.databind.annotation.JsonSerialize;")
  83. imports.add("import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;")
  84. imports.add("import java.math.BigDecimal;")
  85. if any(c[1].lower().startswith("double") for c in cols):
  86. imports.add("import com.fasterxml.jackson.databind.annotation.JsonSerialize;")
  87. imports.add("import cn.yoshop.common.util.PhpFloatSerializer;")
  88. has_delete = any(c[0] == "is_delete" for c in cols)
  89. has_time = any(c[0] in ("create_time", "update_time") for c in cols)
  90. if has_delete:
  91. imports.add("import com.baomidou.mybatisplus.annotation.TableLogic;")
  92. for name, ctype, extra, cmt in cols:
  93. jt = java_type(ctype)
  94. field = camel(name)
  95. base = ctype.lower().split("(")[0].split()[0]
  96. if name == "id" and table == "yoshop_region":
  97. pk = name
  98. body.append(" @TableId(value = \"id\", type = IdType.AUTO)")
  99. body.append(f" private {jt} {field};")
  100. body.append("")
  101. continue
  102. if name.endswith("_id") and pk is None and name == table.replace("yoshop_", "") + "_id":
  103. pk = name
  104. body.append(f" @TableId(value = \"{name}\", type = IdType.AUTO)")
  105. elif name.endswith("_id") and pk is None and name in (
  106. "category_id", "service_id", "group_id", "grade_id", "page_id",
  107. "article_id", "help_id", "coupon_id", "delivery_id", "express_id",
  108. "printer_id", "address_id", "menu_id", "role_id", "comment_id",
  109. "order_id", "order_goods_id", "trade_id", "log_id", "plan_id",
  110. "refund_id", "rule_id"):
  111. pk = name
  112. body.append(f" @TableId(value = \"{name}\", type = IdType.AUTO)")
  113. elif name in ("create_time", "update_time"):
  114. body.append(f" @JsonIgnore")
  115. body.append(f" private Integer {field};")
  116. body.append("")
  117. continue
  118. elif name == "is_delete":
  119. body.append(" @TableLogic(value = \"0\", delval = \"1\")")
  120. elif name in RESERVED:
  121. body.append(f" @TableField(\"`{name}`\")")
  122. imports.add("import com.baomidou.mybatisplus.annotation.TableField;")
  123. annotations = []
  124. if jt == "BigDecimal":
  125. annotations.append(" @JsonSerialize(using = ToStringSerializer.class)")
  126. elif jt == "Double":
  127. annotations.append(" @JsonSerialize(using = PhpFloatSerializer.class)")
  128. for a in annotations:
  129. body.append(a)
  130. body.append(f" private {jt} {field};" + (f" // {cmt}" if cmt else ""))
  131. body.append("")
  132. # 时间字段输出
  133. if has_time:
  134. imports.add("import com.fasterxml.jackson.annotation.JsonIgnore;")
  135. imports.add("import com.fasterxml.jackson.annotation.JsonProperty;")
  136. imports.add("import cn.yoshop.common.util.TimeUtil;")
  137. if any(c[0] == "create_time" for c in cols):
  138. body.append(" @JsonProperty(\"create_time\")")
  139. body.append(" public String getCreateTimeText() {")
  140. body.append(" return TimeUtil.format(createTime);")
  141. body.append(" }")
  142. body.append("")
  143. if any(c[0] == "update_time" for c in cols):
  144. body.append(" @JsonProperty(\"update_time\")")
  145. body.append(" public String getUpdateTimeText() {")
  146. body.append(" return TimeUtil.format(updateTime);")
  147. body.append(" }")
  148. body.append("")
  149. src = "package cn.yoshop.common.entity;\n\n"
  150. src += "\n".join(sorted(imports)) + "\n\n"
  151. src += f"/**\n * {comment}({table}),字段与库内结构一一对应。\n */\n"
  152. src += "@Data\n"
  153. src += f"@TableName(\"{table}\")\n"
  154. src += f"public class {cls} {{\n\n"
  155. src += "\n".join(body).rstrip() + "\n"
  156. src += "}\n"
  157. (ROOT / "common" / "entity" / f"{cls}.java").write_text(src, encoding="utf-8")
  158. mapper = (
  159. "package cn.yoshop.common.mapper;\n\n"
  160. "import cn.yoshop.common.entity." + cls + ";\n"
  161. "import com.baomidou.mybatisplus.core.mapper.BaseMapper;\n"
  162. "import org.apache.ibatis.annotations.Mapper;\n\n"
  163. f"@Mapper\npublic interface {cls}Mapper extends BaseMapper<{cls}> {{\n}}\n"
  164. )
  165. (ROOT / "common" / "mapper" / f"{cls}Mapper.java").write_text(mapper, encoding="utf-8")
  166. def main():
  167. conn = pymysql.connect(host="192.168.1.177", port=3306, user="root", password=pwd, database="shop")
  168. cur = conn.cursor()
  169. for table, (cls, comment) in TABLES.items():
  170. cur.execute(
  171. "SELECT column_name,column_type,extra,column_comment FROM information_schema.columns "
  172. "WHERE table_schema='shop' AND table_name=%s ORDER BY ordinal_position", (table,))
  173. cols = cur.fetchall()
  174. if not cols:
  175. print("!! 表不存在:", table)
  176. continue
  177. gen_entity(table, cls, comment, cols)
  178. print("ok", cls, len(cols), "列")
  179. conn.close()
  180. if __name__ == "__main__":
  181. main()