Jinping HeSenior Full-Stack Developer & Data Analyst · Auckland
XiaoJunBao (XJB): A Multi-Tenant SaaS Platform for Training-School Operations

2 Nov 2026

XiaoJunBao (XJB): A Multi-Tenant SaaS Platform for Training-School Operations

XiaoJunBao is a multi-tenant SaaS system for training schools. It covers enrollment, scheduling, lesson consumption, remaining hours, tuition, and WeChat access, built on .NET 10, PostgreSQL, React, and a database-per-school design.

XiaoJunBao (XJB): A Multi-Tenant SaaS Platform for Training-School Operations
Abstract
XiaoJunBao, shortened to XJB, is a software-as-a-service platform for training schools that sell lessons by the hour, by the term, or by a course package. A school uses it to enroll students, arrange classes, take attendance, deduct course hours, and keep the remaining balance and the money side of that balance in the same system. Teachers and families reach the same records from WeChat. The platform operator runs many schools from a separate console. Under the product is a .NET 10 service split, a PostgreSQL database for each school, and a set of web, WeChat, and classroom clients that all talk to one gateway.
A school runs on hours
A training school is not a generic company with a customer list and an invoice book. The thing parents buy is a quantity of lessons, sometimes with a gift quantity attached, sometimes shared inside a family, sometimes tied to a date range instead of a count of hours. The thing teachers do every afternoon is stand in a classroom and decide who was present, who was late, who asked for leave, and who is making up a missed class. The office then has to know how many hours remain, which signup order those hours came from, and whether the tuition behind them has been recognized.
XiaoJunBao is built around that loop. Enrollment creates a course balance. Scheduling places that balance on a timetable. Consumption writes an attendance record and moves the balance. Finance reads the same movement when it needs a ledger, a refund, or a remaining-tuition figure. The product name in Chinese is 校君宝. The codebase is coursehourmicro. This article is a product and architecture introduction, written for a technical blog rather than a user manual.
What the school actually does in it
The school console is the daily workplace. Staff sign in under a tenant, which is one training institution, and then work inside the campuses they are allowed to see.
Enrollment covers new signups and renewals. A signup can contain several courses, a package price, gift hours, and a payment that is allowed to be partial. Renewals stay on the student's home campus. Once a course has been sold, or already has a student balance, its definition is locked so later edits do not rewrite history. Orders can be deleted only while the purchased remaining hours are still the hours that were bought. After the balance has moved, the signup stays.
Academic work covers courses, classes, and the timetable. Classes are group classes or one-to-one classes. A schedule names the course, the class, the classroom, the teacher, and the time. Consumption then follows that schedule, or it can be entered without a schedule when a school needs to record a lesson that was never placed on the timetable. Attendance is not a single present-or-absent flag. Present, late, leave, makeup, absence, and trial each carry their own default deducted hours, and the school sets those defaults in academic settings. Leave recorded by a teacher at consumption time is stored as a leave, and deleting the lesson record removes that leave and returns the hours.
The hour ledger is a first-class record. The current remaining hours live on the student course balance: purchased hours in one column, gift hours in another. Every change also writes a transaction with the quantity before and the quantity after. Schools use that when they transfer hours between courses, clear a negative balance, move a student across campuses, or explain to a parent why a number changed. Negative balances can be offset by money, by an equal number of hours, or cleared directly without taking hours from another course. Gift hours stay out of that offset.
Finance sits next to the academic record instead of in a separate spreadsheet. The school can look up remaining hours and remaining tuition, teacher class hours, payment transactions, and refunds. Lists that are used every day, including lesson records and remaining hours, are paged on the server. Export of a long lesson history is limited to a one-year window so a download cannot try to pull the entire life of the school in one file. Ordinary search is not limited that way.
People and permissions are part of the same product. Students, parents, and staff have their own profiles. A staff account can be disabled, and a disabled account does not appear when someone is picking a teacher for a new class or a new consumption. Courses, classes, and classrooms that have been turned off stay out of those same pickers. Menus and actions are granted by permission codes, and data is further limited by campus. A teacher who may see only their own classes does not receive the rest of the school by accident.
Four surfaces, one school
The school console is a React application. It is the place where the office enrolls students, builds the timetable, consumes lessons, and reads finance.
The platform console is a second React application, used by the operator of the SaaS rather than by a school. It is where institutions are opened, trials are registered, and platform-level settings live. A school user never signs in there to take attendance.
WeChat is the third surface. Teachers open an H5 application inside the official account to see their classes and to consume a scheduled lesson or an unscheduled one. The same consumption rules apply there as on the office computer: default hours for leave and makeup, the same attendance values, and the same refusal when a lesson would duplicate one that already exists. Families use the official account for the student side of the same tenant. Template messages, such as a consumption notice, use the field names the WeChat template already has. The school pastes its own template id. It does not redesign the message contract.
The fourth surface sits in the classroom. FaceConsume is a .NET desktop program for a computer with a camera. It stores a few face samples for a student and, when that student is recognized, consumes the earliest scheduled lesson that falls inside the match window the school configured. It is a tray application, not another copy of the business rules. The rules stay on the server.
A public marketing site, built separately, is where a new school reads about the product and starts a trial. After that, the school lives on its own host name and its own database.
Technology stack
The server is .NET 10. Four processes matter in production.
Gateway. A YARP reverse proxy. Browsers and the WeChat H5 call one API host. The gateway forwards to the service that owns the route.
Auth. Identity, tenants, campuses, staff, roles, and JWT issuance. This database is the directory of who may sign in. It does not store course hours.
Operations. Enrollment, courses, classes, schedules, consumption, balances, and the school's finance. This is the large service. It is organized as API, application, domain, and infrastructure projects.
Support. System logs, background jobs, and the operational side of notifications.
PostgreSQL 16 is the system of record. Entity Framework Core 10 and Npgsql map the domain. Redis caches data that is read often and does not need to be the ledger. RabbitMQ, with MassTransit, carries integration events between services. Hangfire runs scheduled work on PostgreSQL. Local development is orchestrated with .NET Aspire, which starts PostgreSQL, Redis, RabbitMQ, and the services together. Production is a Docker Compose stack: one Postgres container, the service containers, and nginx in front of the public hosts.
The school console and the platform console are React 19, TypeScript, and Vite, with MUI for layout and data grids. Forms use React Hook Form and Zod. Dates use Day.js. Copy is in i18next, with Simplified Chinese, Traditional Chinese, English, Japanese, and Korean. The WeChat client is uni-app on Vue 3, so the same project can be published as an H5 page inside the official account. The marketing site is Next.js 16 with Tailwind CSS. The classroom face program is WPF on .NET 10.
Technical characteristics
One database per school
Auth, the shared operations catalog, and support each have their own database. Each school then receives its own database, named from the tenant id. The schema inside that database is the same for every school: students, orders, course balances, schedules, and lesson records all live under one tenant schema. Provisioning runs the Operations migrations into that database when the school is created. A query for one school does not scan another school's rows, because those rows are not in the same database.
That choice has a cost. Each Operations process keeps a connection pool per school it has touched, and PostgreSQL counts every pool against one server-wide connection limit. The design expects a pooler in front of Postgres when the number of schools grows. The application already knows how to address that pooler. The important property is the isolation: backup, restore, and inspection of one school can be done on one database.
The balance and the ledger are different tables
Remaining hours are a current value, not a sum the screen has to recompute from every past lesson. Purchased remaining hours and gift remaining hours are stored on the student course balance, next to the quantities from the day of purchase. A separate course-transaction table records the previous value, the delta, and the new value whenever the balance moves. Reports can sort and filter the current number quickly. A dispute can still be answered from the ledger. Deleting a signup is allowed only when the purchased remaining hours are still the original purchased hours, and when the balance has not been through an operation that the product treats as irreversible.
The same consumption rules on every client
Office staff, WeChat teachers, and the face terminal do not each invent attendance. They call the Operations service. Default deducted hours for leave and makeup come from the school's academic settings. A leave that the schedule has already marked as free stays at zero hours. An unscheduled lesson that matches an existing lesson on course, class, teacher, date, and time is rejected, so the continue-entry screen cannot file the same class twice. Disabled courses, classes, classrooms, and locked staff accounts are omitted from the pickers that create new work.
Permissions are codes, and scope is a campus list
A screen is not secure because the menu is hidden. Each API checks a permission code and an action such as view or create. On top of that, the caller's data scope limits campuses, or limits the caller to their own classes. The WeChat teacher and the office administrator can share an endpoint and still not see the same students.
Language is a layer, not a fork
The school console, the platform console, and the WeChat H5 carry their own translation catalogs. The server returns business messages in Chinese for the rules that staff read as errors, and the clients translate the surrounding interface. Adding a language means adding catalogs, not copying a page.
What this shape is for
XiaoJunBao is a vertical system. It will not become a general ERP, and it does not try to. The bet is that a training school will stay on one product if enrollment, the timetable, the hour balance, the tuition, and the WeChat conversation all describe the same lesson. The technical bet that supports that is smaller and stricter: put each school in its own database, put the current balance next to an explicit ledger, and make every client consume through the same service.
The repository name is coursehourmicro. The product name is XiaoJunBao. The short name on the file you are reading is XJB.